26#include "../Audacity.h"
28#if defined(USE_GSTREAMER)
29#include "ImportGStreamer.h"
34#define DESC XO("GStreamer-compatible files")
41# pragma comment(lib,"gstreamer-1.0.lib")
42# pragma comment(lib,"gstapp-1.0.lib")
43# pragma comment(lib,"gstbase-1.0.lib")
44# pragma comment(lib,"glib-2.0.lib")
45# pragma comment(lib,"gobject-2.0.lib")
50#include "../WaveTrack.h"
55 #include <gst/app/gstappsink.h>
56 #include <gst/audio/audio-format.h>
60#define AUDCTX "audacity::context"
61#define GETCTX(o) (GStreamContext *) g_object_get_data(G_OBJECT((o)), AUDCTX)
62#define SETCTX(o, c) g_object_set_data(G_OBJECT((o)), AUDCTX, (gpointer) (c))
63#define WARN(e, msg) GST_ELEMENT_WARNING((e), STREAM, FAILED, msg, (NULL));
78 GST_AUDIO_NE(S16)
", "
79 GST_AUDIO_NE(S24_32)
", "
95 g_mutex_unlock(&
mutex);
101template<
typename T,
void(*Fn)(T*)>
struct Deleter {
105 Fn(
static_cast<T*
>(p));
108using GstString = std::unique_ptr < gchar, Deleter<void, g_free> > ;
109using GErrorHandle = std::unique_ptr < GError, Deleter<GError, g_error_free> > ;
111using ParseFn = void (*)(GstMessage *message, GError **gerror, gchar **debug);
116 fn(msg, &error, &
string);
155 std::unique_ptr < T, Deleter<void, gst_object_unref > > ;
186 int Import(TrackFactory *trackFactory,
188 Tags *tags)
override;
210 void OnTag(GstAppSink *appsink, GstTagList *tags);
229 std::vector<std::unique_ptr<GStreamContext>>
mStreams;
250 std::unique_ptr<ImportFileHandle>
Open(
263 []() -> std::unique_ptr< ImportPlugin > {
264 wxLogMessage(
_TS(
"Audacity is built against GStreamer version %d.%d.%d-%d"),
277 initError = !gst_init_check(&
argc, &
argv, &ee);
282 wxLogMessage(
wxT(
"Failed to initialize GStreamer. Error %d: %s"),
284 wxString::FromUTF8(error.get()->message));
288 guint major, minor, micro, nano;
289 gst_version(&major, &minor, µ, &nano);
290 wxLogMessage(
wxT(
"Linked to GStreamer version %d.%d.%d-%d"),
297 auto plug = std::make_unique<GStreamerImportPlugin>();
300 if (plug->GetSupportedExtensions().size() == 0)
304 return std::move(plug);
337 return wxT(
"gstreamer");
352 std::unique_ptr < GList, Deleter<GList, gst_plugin_feature_list_free> >
353 factories{ gst_type_find_factory_get_list() };
355 for (GList *list = factories.get(); list != NULL; list = g_list_next(list))
357 GstTypeFindFactory *
factory = GST_TYPE_FIND_FACTORY(list->data);
360 GstCaps *caps = gst_type_find_factory_get_caps(
factory);
367 for (guint c = 0, clen = gst_caps_get_size(caps); c < clen; c++)
370 GstStructure *
str = gst_caps_get_structure(caps, c);
371 if (!g_str_has_prefix(gst_structure_get_name(
str),
"audio"))
377 const gchar *
const *extensions = gst_type_find_factory_get_extensions(
factory);
384 for (guint i = 0; extensions[i] != NULL; i++)
386 wxString extension = wxString::FromUTF8(extensions[i]);
387 if (
mExtensions.Index(extension,
false) == wxNOT_FOUND)
400 wxString extensions =
wxT(
"Extensions:");
405 wxLogMessage(
wxT(
"%s"), extensions);
415 auto handle = std::make_unique<GStreamerImportFileHandle>(filename);
424 return std::move(handle);
446 GstPad * WXUNUSED(pad),
447 GstCaps * WXUNUSED(caps),
449 gpointer WXUNUSED(data))
452 const gchar *fclass = gst_element_factory_get_klass(
factory);
455 if (g_strrstr(fclass,
"Video"))
495 return GuardedCall< GstFlowReturn > ( [&] {
500 std::unique_ptr < GstSample, Deleter< GstSample, GstSampleUnref> >
501 sample{ gst_app_sink_pull_sample(appsink) };
530using GstCapsHandle = std::unique_ptr < GstCaps, Deleter<GstCaps, GstCapsUnref> >;
541 GstCaps *caps = gst_pad_get_current_caps(pad);
546 WARN(
mPipeline.get(), (
"OnPadAdded: unable to retrieve stream caps"));
551 GstStructure *
str = gst_caps_get_structure(caps, 0);
554 WARN(
mPipeline.get(), (
"OnPadAdded: unable to retrieve caps structure"));
559 const gchar *
name = gst_structure_get_name(
str);
560 if (!g_strrstr(
name,
"audio"))
568 auto uc = std::make_unique<GStreamContext>();
572 WARN(
mPipeline.get(), (
"OnPadAdded: unable to allocate stream context"));
592 gst_pad_query_position(pad, GST_FORMAT_TIME, &c->mPosition);
593 gst_pad_query_duration(pad, GST_FORMAT_TIME, &c->mDuration);
597 gst_structure_get_int(
str,
"channels", &channels);
600 WARN(
mPipeline.get(), (
"OnPadAdded: channel count is invalid %d", channels));
603 c->mNumChannels = channels;
607 gst_structure_get_int(
str,
"rate", &rate);
610 WARN(
mPipeline.get(), (
"OnPadAdded: sample rate is invalid %d", rate));
613 c->mSampleRate = (double)rate;
615 c->mType.reset(g_strdup(
name));
618 WARN(
mPipeline.get(), (
"OnPadAdded: unable to allocate audio type"));
626 c->mConv = gst_element_factory_make(
"audioconvert", NULL);
629 WARN(
mPipeline.get(), (
"OnPadAdded: failed to create audioconvert element"));
634 c->mSink = gst_element_factory_make(
"appsink", NULL);
637 WARN(
mPipeline.get(), (
"OnPadAdded: failed to create appsink element"));
643 gst_app_sink_set_callbacks(GST_APP_SINK(c->mSink), &
AppSinkCallbacks,
this, NULL);
651 WARN(
mPipeline.get(), (
"OnPadAdded: failed to create static caps"));
654 gst_app_sink_set_caps(GST_APP_SINK(c->mSink), caps);
658 gst_base_sink_set_sync(GST_BASE_SINK(c->mSink), FALSE);
661 gst_app_sink_set_drop(GST_APP_SINK(c->mSink), FALSE);
664 gst_bin_add_many(GST_BIN(
mPipeline.get()), c->mConv, c->mSink, NULL);
667 if (!gst_element_link(c->mConv, c->mSink))
669 WARN(
mPipeline.get(), (
"OnPadAdded: failed to link audioconvert and appsink"));
674 GstPadLinkReturn ret = GST_PAD_LINK_OK;
678 ret = gst_pad_link(pad, convsink.get());
679 if (!convsink || ret != GST_PAD_LINK_OK)
681 WARN(
mPipeline.get(), (
"OnPadAdded: failed to link uridecodebin to audioconvert - %d", ret));
687 if (!gst_element_sync_state_with_parent(c->mConv))
689 WARN(
mPipeline.get(), (
"OnPadAdded: unable to sync audioconvert state"));
694 if (!gst_element_sync_state_with_parent(c->mSink))
696 WARN(
mPipeline.get(), (
"OnPadAdded: unable to sync appaink state"));
711 gst_element_set_state(c->
mSink, GST_STATE_NULL);
712 gst_element_set_state(c->
mConv, GST_STATE_NULL);
739 GstCaps *caps = gst_sample_get_caps(sample);
740 GstStructure *
str = gst_caps_get_structure(caps, 0);
741 const gchar *fmt = gst_structure_get_string(
str,
"format");
744 WARN(
mPipeline.get(), (
"OnNewSample: missing audio format"));
749 if (strcmp(fmt, GST_AUDIO_NE(S16)) == 0)
753 else if (strcmp(fmt, GST_AUDIO_NE(S24_32)) == 0)
757 else if (strcmp(fmt, GST_AUDIO_NE(F32)) == 0)
765 WARN(
mPipeline.get(), (
"OnNewSample: unrecognized sample format %s", fmt));
773 WARN(
mPipeline.get(), (
"OnNewSample: unable to allocate track array"));
784 WARN(
mPipeline.get(), (
"OnNewSample: unable to create track"));
791 GstBuffer *buffer = gst_sample_get_buffer(sample);
801 if (!gst_buffer_map(buffer, &info, GST_MAP_READ))
803 WARN(
mPipeline.get(), (
"OnNewSample: mapping buffer failed"));
806 auto cleanup =
finally([&]{
808 gst_buffer_unmap(buffer, &info);
815 size_t samples = info.size / nChannels /
SAMPLE_SIZE(fmt);
818 for (
int chn = 0; chn < nChannels; chn++)
856 gst_element_set_state(
mPipeline.get(), GST_STATE_NULL);
908 if ((guint) index <
mStreams.size())
921 mUri.reset(g_strdup_printf(
"file:///%s",
mFilename.ToUTF8().data()));
924 wxLogMessage(
wxT(
"GStreamerImport couldn't create URI"));
929 mPipeline.reset(gst_pipeline_new(
"pipeline"));
932 mBus.reset(gst_pipeline_get_bus(GST_PIPELINE(
mPipeline.get())));
935 mDec = gst_element_factory_make(
"uridecodebin",
"decoder");
941 g_object_set(G_OBJECT(
mDec),
"uri",
mUri, NULL);
947 XO(
"Unable to add decoder to pipeline"),
948 XO(
"GStreamer Importer"));
955 GstStateChangeReturn state = gst_element_set_state(
mPipeline.get(), GST_STATE_PAUSED);
956 if (state == GST_STATE_CHANGE_FAILURE)
959 XO(
"Unable to set stream state to paused."),
960 XO(
"GStreamer Importer"));
981 for (guint i = 0; i <
mStreams.size(); i++)
986 auto strinfo =
XO(
"Index[%02d], Type[%s], Channels[%d], Rate[%d]")
989 wxString::FromUTF8(c->
mType.get()),
1030 bool haveStreams =
false;
1033 for (guint i = 0; i <
mStreams.size(); i++)
1046 gst_pad_unlink(convpeer.get(), convsink.get());
1054 gst_element_set_state(c->
mConv, GST_STATE_PLAYING);
1055 gst_element_set_state(c->
mSink, GST_STATE_PLAYING);
1058 gst_pad_send_event(convsink.get(), gst_event_new_eos());
1061 gst_element_sync_state_with_parent(c->
mConv);
1062 gst_element_sync_state_with_parent(c->
mSink);
1089 XO(
"File doesn't contain any audio streams."),
1090 XO(
"GStreamer Importer"));
1091 return ProgressResult::Failed;
1095 GstStateChangeReturn state = gst_element_set_state(
mPipeline.get(), GST_STATE_PLAYING);
1096 if (state == GST_STATE_CHANGE_FAILURE)
1099 XO(
"Unable to import file, state change failed."),
1100 XO(
"GStreamer Importer"));
1101 return ProgressResult::Failed;
1106 gst_element_query_duration(
mPipeline.get(), GST_FORMAT_TIME, &duration);
1109 bool success =
true;
1116 if (gst_element_query_position(
mPipeline.get(), GST_FORMAT_TIME, &position))
1118 updateResult =
mProgress->Update((wxLongLong_t) position,
1119 (wxLongLong_t) duration);
1124 gst_element_set_state(
mPipeline.get(), GST_STATE_NULL);
1129 return updateResult;
1137 for (guint s = 0; s <
mStreams.size(); s++)
1144 outTracks.push_back(std::move(c->
mChannels));
1151 return updateResult;
1171 std::unique_ptr < GstMessage, Deleter < GstMessage, GstMessageUnref > >
1172 msg{ gst_bus_timed_pop(
mBus.get(), 100 * GST_MSECOND) };
1183 if (msg->src != NULL)
1185 objname.reset(gst_object_get_name(msg->src));
1188 wxLogMessage(
wxT(
"GStreamer: Got %s%s%s"),
1189 wxString::FromUTF8(GST_MESSAGE_TYPE_NAME(msg.get())),
1190 objname ?
wxT(
" from ") :
wxT(
""),
1191 objname ? wxString::FromUTF8(objname.get()) :
wxT(
""));
1196 switch (GST_MESSAGE_TYPE(msg.get()))
1199 case GST_MESSAGE_ERROR:
1209 m.Printf(
wxT(
"%s%s%s"),
1210 wxString::FromUTF8(err.get()->message),
1211 debug ?
wxT(
"\n") :
wxT(
""),
1212 debug ? wxString::FromUTF8(debug.get()) :
wxT(
""));
1213 auto msg =
XO(
"GStreamer Error: %s").Format( m );
1217 wxLogMessage( msg.Debug() );
1227 case GST_MESSAGE_WARNING:
1235 wxLogMessage(
wxT(
"GStreamer Warning: %s%s%s"),
1236 wxString::FromUTF8(err.get()->message),
1237 debug ?
wxT(
"\n") :
wxT(
""),
1238 debug ? wxString::FromUTF8(debug.get()) :
wxT(
""));
1244 case GST_MESSAGE_INFO:
1252 wxLogMessage(
wxT(
"GStreamer Info: %s%s%s"),
1253 wxString::FromUTF8(err.get()->message),
1254 debug ?
wxT(
"\n") :
wxT(
""),
1255 debug ? wxString::FromUTF8(debug.get()) :
wxT(
""));
1261 case GST_MESSAGE_TAG:
1263 GstTagList *tags = NULL;
1264 auto cleanup =
finally([&]{
1266 if(tags) gst_tag_list_unref(tags);
1270 gst_message_parse_tag(msg.get(), &tags);
1274 OnTag(GST_APP_SINK(GST_MESSAGE_SRC(msg.get())), tags);
1281 case GST_MESSAGE_ASYNC_DONE:
1300 case GST_MESSAGE_EOS:
1317 for (guint i = 0, icnt = gst_tag_list_n_tags(tags); i < icnt; i++)
1322 const gchar *
name = gst_tag_list_nth_tag_name(tags, i);
1329 for (guint j = 0, jcnt = gst_tag_list_get_tag_size(tags,
name); j < jcnt; j++)
1333 val = gst_tag_list_get_value_index(tags,
name, j);
1335 if (G_VALUE_HOLDS_STRING(val))
1337 string = wxString::FromUTF8(g_value_get_string(val));
1339 else if (G_VALUE_HOLDS_UINT(val))
1341 string.Printf(
wxT(
"%u"), (
unsigned int) g_value_get_uint(val));
1343 else if (G_VALUE_HOLDS_DOUBLE(val))
1345 string.Printf(
wxT(
"%g"), g_value_get_double(val));
1347 else if (G_VALUE_HOLDS_BOOLEAN(val))
1349 string = g_value_get_boolean(val) ?
wxT(
"true") :
wxT(
"false");
1351 else if (GST_VALUE_HOLDS_DATE_TIME(val))
1353 GstDateTime *dt = (GstDateTime *) g_value_get_boxed(val);
1355 string = wxString::FromUTF8(
str.get());
1357 else if (G_VALUE_HOLDS(val, G_TYPE_DATE))
1360 string = wxString::FromUTF8(
str.get());
1364 wxLogMessage(
wxT(
"Tag %s has unhandled type: %s"),
1365 wxString::FromUTF8(
name),
1366 wxString::FromUTF8(G_VALUE_TYPE_NAME(val)));
1372 if (strcmp(
name, GST_TAG_TITLE) == 0)
1376 else if (strcmp(
name, GST_TAG_ARTIST) == 0)
1380 else if (strcmp(
name, GST_TAG_ALBUM) == 0)
1384 else if (strcmp(
name, GST_TAG_TRACK_NUMBER) == 0)
1388 else if (strcmp(
name, GST_TAG_DATE) == 0)
1392 else if (strcmp(
name, GST_TAG_GENRE) == 0)
1396 else if (strcmp(
name, GST_TAG_COMMENT) == 0)
1402 tag = wxString::FromUTF8(
name);
1407 tag.Printf(
wxT(
"%s:%d"), tag, j);
SimpleGuard< R > MakeSimpleGuard(R value) noexcept(noexcept(SimpleGuard< R >{ value }))
Convert a value to a handler function returning that value, suitable for GuardedCall<R>
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
const TranslatableString name
std::vector< std::shared_ptr< WaveTrack > > NewChannelGroup
std::vector< std::vector< std::shared_ptr< WaveTrack > > > TrackHolders
static Importer::RegisteredImportPlugin registered
void GstMessageUnref(GstMessage *p)
std::unique_ptr< GstCaps, Deleter< GstCaps, GstCapsUnref > > GstCapsHandle
static void GStreamerPadRemovedCallback(GstElement *WXUNUSED(element), GstPad *pad, gpointer data)
void GstSampleUnref(GstSample *p)
void(*)(GstMessage *message, GError **gerror, gchar **debug) ParseFn
void GstCapsUnref(GstCaps *p)
static void GStreamerPadAddedCallback(GstElement *WXUNUSED(element), GstPad *pad, gpointer data)
std::unique_ptr< T, Deleter< void, gst_object_unref > > GstObjHandle
static GstAppSinkCallbacks AppSinkBitBucket
std::unique_ptr< GError, Deleter< GError, g_error_free > > GErrorHandle
static GstAppSinkCallbacks AppSinkCallbacks
static gint GStreamerAutoplugSelectCallback(GstElement *WXUNUSED(element), GstPad *WXUNUSED(pad), GstCaps *WXUNUSED(caps), GstElementFactory *factory, gpointer WXUNUSED(data))
static GstFlowReturn GStreamerNewSample(GstAppSink *appsink, gpointer data)
static GstStaticCaps supportedCaps
void GstMessageParse(ParseFn fn, GstMessage *msg, GErrorHandle &err, GstString &debug)
std::unique_ptr< gchar, Deleter< void, g_free > > GstString
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
! Does actual import, returned by GStreamerImportPlugin::Open
wxInt32 GetStreamCount() override
void OnTag(GstAppSink *appsink, GstTagList *tags)
int Import(TrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override
void SetStreamUsage(wxInt32 index, bool use) override
void OnPadRemoved(GstPad *pad)
const TranslatableStrings & GetStreamInfo() override
GMutex mStreamsLock
Mutex protecting the mStreams array.
virtual ~GStreamerImportFileHandle()
void OnNewSample(GStreamContext *c, GstSample *sample)
GstObjHandle< GstElement > mPipeline
GStreamer pipeline.
TranslatableStrings mStreamInfo
Array of stream descriptions. Length is the same as mStreams.
GstString mUri
URI of file.
bool mAsyncDone
true = 1st async-done message received
std::vector< std::unique_ptr< GStreamContext > > mStreams
Array of pointers to stream contexts.
GstObjHandle< GstBus > mBus
Message bus.
Tags mTags
Tags to be passed back to Audacity.
ByteCount GetFileUncompressedBytes() override
void OnPadAdded(GstPad *pad)
GStreamerImportFileHandle(const wxString &name)
GstElement * mDec
uridecodebin element
bool ProcessBusMessage(bool &success)
TrackFactory * mTrackFactory
Factory to create tracks when samples arrive.
TranslatableString GetFileDescription() override
An ImportPlugin for GStreamer data.
TranslatableString GetPluginFormatDescription() override
std::unique_ptr< ImportFileHandle > Open(const wxString &Filename, AudacityProject *) override
! Probes the file and opens it if appropriate
virtual ~GStreamerImportPlugin()
! Destructor
GStreamerImportPlugin()
! Constructor
wxString GetPluginStringID() override
FileExtensions GetSupportedExtensions() override
An ImportFileHandle for data.
unsigned long long ByteCount
std::unique_ptr< ProgressDialog > mProgress
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
const FileExtensions mExtensions
Holds a msgid for the translation catalog; may also bind format arguments.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
UTILITY_API const char *const * argv
A copy of argv; responsibility of application startup to assign it.
UTILITY_API int argc
A copy of argc; responsibility of application startup to assign it.
static RegisteredToolbarFactory factory
void operator()(void *p) const
NewChannelGroup mChannels
g_mutex_locker(GMutex &mutex_)