Audacity 3.2.0
ImportGStreamer.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ImportGStreamer.cpp
6
7Copyright 2008 LRN
8Based on ImportFFmpeg.cpp by LRN
9
10Rework for gstreamer 1.0 by LLL
11
12Licensed under the GNU General Public License v2 or later
13
14*//****************************************************************//****************************************************************//*******************************************************************/
25
26#include "../Audacity.h" // needed before GStreamer.h // for USE_* macros
27
28#if defined(USE_GSTREAMER)
29#include "ImportGStreamer.h"
30
31#include <wx/window.h>
32#include <wx/log.h>
33
34#define DESC XO("GStreamer-compatible files")
35
36
37// On Windows we don't have configure script to turn this on or off,
38// so let's use msw-specific pragma to add required libraries.
39// Of course, library search path still has to be updated manually
40#if defined(__WXMSW__)
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")
46#endif
47
48// all the includes live here by default
49#include "../Tags.h"
50#include "../WaveTrack.h"
51
52extern "C"
53{
54// #include <gio/gio.h>
55 #include <gst/app/gstappsink.h>
56 #include <gst/audio/audio-format.h>
57}
58
59// Convenience macros
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));
64
65// Capabilities that Audacity can handle
66//
67// This resolves to: (on little endian)
68//
69// "audio/x-raw, "
70// "format = (string) {S16LE, S24_32LE, F32LE}, "
71// "rate = (int) [ 1, max ]",
72// "channels = (int) [ 1, max ]"
73
74static GstStaticCaps supportedCaps =
75 GST_STATIC_CAPS(
76 GST_AUDIO_CAPS_MAKE(
77 "{"
78 GST_AUDIO_NE(S16) ", "
79 GST_AUDIO_NE(S24_32) ", "
80 GST_AUDIO_NE(F32)
81 "}"
82 )
83 );
84
86{
87 explicit g_mutex_locker(GMutex &mutex_)
88 : mutex(mutex_)
89 {
90 g_mutex_lock(&mutex);
91 }
92
94 {
95 g_mutex_unlock(&mutex);
96 }
97
98 GMutex &mutex;
99};
100
101template<typename T, void(*Fn)(T*)> struct Deleter {
102 inline void operator() (void *p) const
103 {
104 if (p)
105 Fn(static_cast<T*>(p));
106 }
107};
108using GstString = std::unique_ptr < gchar, Deleter<void, g_free> > ;
109using GErrorHandle = std::unique_ptr < GError, Deleter<GError, g_error_free> > ;
110
111using ParseFn = void (*)(GstMessage *message, GError **gerror, gchar **debug);
112inline void GstMessageParse(ParseFn fn, GstMessage *msg, GErrorHandle &err, GstString &debug)
113{
114 GError *error;
115 gchar *string;
116 fn(msg, &error, &string);
117 err.reset(error);
118 debug.reset(string);
119}
120
121// Context used for private stream data
123{
124 GstElement *mConv{}; // Audio converter
125 GstElement *mSink{}; // Application sink
126 bool mUse{}; // True if this stream should be imported
127 NewChannelGroup mChannels; // Array of WaveTrack pointers, one for each channel
128 unsigned mNumChannels{}; // Number of channels
129 gdouble mSampleRate{}; // Sample rate
130 GstString mType; // Audio type
131 sampleFormat mFmt{ floatSample }; // Sample format
132 gint64 mPosition{}; // Start position of stream
133 gint64 mDuration{}; // Duration of stream
134 GstElement *mPipeline{};
135
138 {
139 // Remove the appsink element
140 if (mSink)
141 {
142 gst_bin_remove(GST_BIN(mPipeline), mSink);
143 }
144
145 // Remove the audioconvert element
146 if (mConv)
147 {
148 gst_bin_remove(GST_BIN(mPipeline), mConv);
149 }
150 }
151};
152
153// For RAII on gst objects
154template<typename T> using GstObjHandle =
155 std::unique_ptr < T, Deleter<void, gst_object_unref > > ;
156
159{
160public:
161 GStreamerImportFileHandle(const wxString & name);
163
166 bool Init();
167
170
173 wxInt32 GetStreamCount() override;
174
177 const TranslatableStrings &GetStreamInfo() override;
178
182 void SetStreamUsage(wxInt32 index, bool use) override;
183
186 int Import(TrackFactory *trackFactory,
187 TrackHolders &outTracks,
188 Tags *tags) override;
189
190 // =========================================================================
191 // Handled within the gstreamer threads
192 // =========================================================================
193
196 void OnPadAdded(GstPad *pad);
197
200 void OnPadRemoved(GstPad *pad);
201
205 bool ProcessBusMessage(bool & success);
206
210 void OnTag(GstAppSink *appsink, GstTagList *tags);
211
215 void OnNewSample(GStreamContext *c, GstSample *sample);
216
217private:
220 TrackFactory *mTrackFactory;
221
225 GstElement *mDec;
227
229 std::vector<std::unique_ptr<GStreamContext>> mStreams;
230};
231
235{
236public:
239
241 virtual ~GStreamerImportPlugin();
242
244
245 wxString GetPluginStringID() override;
246
248
250 std::unique_ptr<ImportFileHandle> Open(
251 const wxString &Filename, AudacityProject*) override;
252};
253
254// ============================================================================
255// Initialization
256// ============================================================================
257
258// ----------------------------------------------------------------------------
259// Instantiate GStreamerImportPlugin and add to the list of known importers
260
261static
263 []() -> std::unique_ptr< ImportPlugin > {
264 wxLogMessage(_TS("Audacity is built against GStreamer version %d.%d.%d-%d"),
265 GST_VERSION_MAJOR,
266 GST_VERSION_MINOR,
267 GST_VERSION_MICRO,
268 GST_VERSION_NANO);
269
270 // Initialize gstreamer
271 GErrorHandle error;
272 bool initError;
273 {
274 int argc = 0;
275 char **argv = NULL;
276 GError *ee;
277 initError = !gst_init_check(&argc, &argv, &ee);
278 error.reset(ee);
279 }
280 if ( initError )
281 {
282 wxLogMessage(wxT("Failed to initialize GStreamer. Error %d: %s"),
283 error.get()->code,
284 wxString::FromUTF8(error.get()->message));
285 return {};
286 }
287
288 guint major, minor, micro, nano;
289 gst_version(&major, &minor, &micro, &nano);
290 wxLogMessage(wxT("Linked to GStreamer version %d.%d.%d-%d"),
291 major,
292 minor,
293 micro,
294 nano);
295
296 // Instantiate plugin
297 auto plug = std::make_unique<GStreamerImportPlugin>();
298
299 // No supported extensions...no gstreamer plugins installed
300 if (plug->GetSupportedExtensions().size() == 0)
301 return {};
302
303 // Add to list of importers
304 return std::move(plug);
305}() } registered;
306
307// ============================================================================
308// GStreamerImportPlugin Class
309// ============================================================================
310
311// ----------------------------------------------------------------------------
312// Constructor
314: ImportPlugin( {} )
315{
316}
317
318// ----------------------------------------------------------------------------
319// Destructor
321{
322}
323
324// ----------------------------------------------------------------------------
325// Return the plugin description
328{
329 return DESC;
330}
331
332// ----------------------------------------------------------------------------
333// Return the plugin name
334wxString
336{
337 return wxT("gstreamer");
338}
339
340// Obtains a list of supported extensions from typefind factories
341// TODO: improve the list. It is obviously incomplete.
344{
345 // We refresh the extensions each time this is called in case the
346 // user had installed additional gstreamer plugins while Audacity
347 // was active.
348 mExtensions.clear();
349
350 // Gather extensions from all factories that support audio
351 {
352 std::unique_ptr < GList, Deleter<GList, gst_plugin_feature_list_free> >
353 factories{ gst_type_find_factory_get_list() };
354
355 for (GList *list = factories.get(); list != NULL; list = g_list_next(list))
356 {
357 GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY(list->data);
358
359 // We need the capabilities to determine if it handles audio
360 GstCaps *caps = gst_type_find_factory_get_caps(factory);
361 if (!caps)
362 {
363 continue;
364 }
365
366 // Check each structure in the caps for audio
367 for (guint c = 0, clen = gst_caps_get_size(caps); c < clen; c++)
368 {
369 // Bypass if it isn't for audio
370 GstStructure *str = gst_caps_get_structure(caps, c);
371 if (!g_str_has_prefix(gst_structure_get_name(str), "audio"))
372 {
373 continue;
374 }
375
376 // This factory can handle audio, so get the extensions
377 const gchar *const *extensions = gst_type_find_factory_get_extensions(factory);
378 if (!extensions)
379 {
380 continue;
381 }
382
383 // Add each extension to the list
384 for (guint i = 0; extensions[i] != NULL; i++)
385 {
386 wxString extension = wxString::FromUTF8(extensions[i]);
387 if (mExtensions.Index(extension, false) == wxNOT_FOUND)
388 {
389 mExtensions.push_back(extension);
390 }
391 }
392 }
393 }
394 }
395
396 // Get them in a decent order
397 mExtensions.Sort();
398
399 // Log it for debugging
400 wxString extensions = wxT("Extensions:");
401 for (size_t i = 0; i < mExtensions.size(); i++)
402 {
403 extensions = extensions + wxT(" ") + mExtensions[i];
404 }
405 wxLogMessage(wxT("%s"), extensions);
406
407 return mExtensions;
408}
409
410// ----------------------------------------------------------------------------
411// Open the file and return an importer "file handle"
412std::unique_ptr<ImportFileHandle> GStreamerImportPlugin::Open(
413 const wxString &filename, AudacityProject*)
414{
415 auto handle = std::make_unique<GStreamerImportFileHandle>(filename);
416
417 // Initialize the handle
418 if (!handle->Init())
419 {
420 return nullptr;
421 }
422
423 // This std::move is needed to "upcast" the pointer type
424 return std::move(handle);
425}
426
427// ============================================================================
428// GStreamerImportFileHandle Class
429// ============================================================================
430
431// ----------------------------------------------------------------------------
432// The following methods/functions run within gstreamer thread contexts. No
433// interaction with wxWidgets should be allowed. See explanation at the top
434// of this file.
435// ----------------------------------------------------------------------------
436
437// ----------------------------------------------------------------------------
438// Filter out any video streams
439//
440// LRN found that by doing this here, video streams aren't decoded thus
441// reducing the time/processing needed to extract the audio streams.
442//
443// This "gint" is really GstAutoplugSelectResult enum
444static gint
445GStreamerAutoplugSelectCallback(GstElement * WXUNUSED(element),
446 GstPad * WXUNUSED(pad),
447 GstCaps * WXUNUSED(caps),
448 GstElementFactory *factory,
449 gpointer WXUNUSED(data))
450{
451 // Check factory class
452 const gchar *fclass = gst_element_factory_get_klass(factory);
453
454 // Skip video decoding
455 if (g_strrstr(fclass,"Video"))
456 {
457 return 2; // GST_AUTOPLUG_SELECT_SKIP
458 }
459
460 return 0; // GST_AUTOPLUG_SELECT_TRY
461}
462
463// ----------------------------------------------------------------------------
464// Handle the "pad-added" signal from uridecodebin
465static void
466GStreamerPadAddedCallback(GstElement * WXUNUSED(element),
467 GstPad *pad,
468 gpointer data)
469{
470 ((GStreamerImportFileHandle *) data)->OnPadAdded(pad);
471
472 return;
473}
474
475// ----------------------------------------------------------------------------
476// Handle the "pad-removed" signal from uridecodebin
477static void
478GStreamerPadRemovedCallback(GstElement * WXUNUSED(element),
479 GstPad *pad,
480 gpointer data)
481{
482 ((GStreamerImportFileHandle *) data)->OnPadRemoved(pad);
483
484 return;
485}
486
487// ----------------------------------------------------------------------------
488// Handle the "NEW-sample" signal from uridecodebin
489inline void GstSampleUnref(GstSample *p) { gst_sample_unref(p); } // I can't use the static function name directly...
490
491static GstFlowReturn
492GStreamerNewSample(GstAppSink *appsink, gpointer data)
493{
494 // Don't let C++ exceptions propagate through GStreamer
495 return GuardedCall< GstFlowReturn > ( [&] {
497 static GMutex mutex;
498
499 // Get the sample
500 std::unique_ptr < GstSample, Deleter< GstSample, GstSampleUnref> >
501 sample{ gst_app_sink_pull_sample(appsink) };
502
503 // We must single thread here to prevent concurrent use of the
504 // Audacity track functions.
505 g_mutex_locker locker{ mutex };
506
507 handle->OnNewSample(GETCTX(appsink), sample.get());
508
509 return GST_FLOW_OK;
510 }, MakeSimpleGuard(GST_FLOW_ERROR) );
511}
512
513// ----------------------------------------------------------------------------
514// AppSink callbacks are used instead of signals to reduce processing
515static GstAppSinkCallbacks AppSinkCallbacks =
516{
517 NULL, // eos
518 NULL, // new_preroll
519 GStreamerNewSample // new_sample
520};
521
522static GstAppSinkCallbacks AppSinkBitBucket =
523{
524 NULL, // eos
525 NULL, // new_preroll
526 NULL // new_sample
527};
528
529inline void GstCapsUnref(GstCaps *p) { gst_caps_unref(p); } // I can't use the static function name directly...
530using GstCapsHandle = std::unique_ptr < GstCaps, Deleter<GstCaps, GstCapsUnref> >;
531
532// ----------------------------------------------------------------------------
533// Handle the "pad-added" message
534void
536{
537 GStreamContext *c{};
538
539 {
540 // Retrieve the stream caps...skip stream if unavailable
541 GstCaps *caps = gst_pad_get_current_caps(pad);
542 GstCapsHandle handle{ caps };
543
544 if (!caps)
545 {
546 WARN(mPipeline.get(), ("OnPadAdded: unable to retrieve stream caps"));
547 return;
548 }
549
550 // Get the caps structure...no need to release
551 GstStructure *str = gst_caps_get_structure(caps, 0);
552 if (!str)
553 {
554 WARN(mPipeline.get(), ("OnPadAdded: unable to retrieve caps structure"));
555 return;
556 }
557
558 // Only accept audio streams...no need to release
559 const gchar *name = gst_structure_get_name(str);
560 if (!g_strrstr(name, "audio"))
561 {
562 WARN(mPipeline.get(), ("OnPadAdded: bypassing '%s' stream", name));
563 return;
564 }
565
566 {
567 // Allocate a NEW stream context
568 auto uc = std::make_unique<GStreamContext>();
569 c = uc.get();
570 if (!c)
571 {
572 WARN(mPipeline.get(), ("OnPadAdded: unable to allocate stream context"));
573 return;
574 }
575
576 // Set initial state
577 c->mUse = true;
578
579 // Always add it to the context list to keep the number of contexts
580 // in sync with the number of streams
582 // Pass the buck from uc
583 mStreams.push_back(std::move(uc));
584 }
585
586 c->mPipeline = mPipeline.get();
587
588 // Need pointer to context during pad removal (pad-remove signal)
589 SETCTX(pad, c);
590
591 // Save the stream's start time and duration
592 gst_pad_query_position(pad, GST_FORMAT_TIME, &c->mPosition);
593 gst_pad_query_duration(pad, GST_FORMAT_TIME, &c->mDuration);
594
595 // Retrieve the number of channels and validate
596 gint channels = -1;
597 gst_structure_get_int(str, "channels", &channels);
598 if (channels <= 0)
599 {
600 WARN(mPipeline.get(), ("OnPadAdded: channel count is invalid %d", channels));
601 return;
602 }
603 c->mNumChannels = channels;
604
605 // Retrieve the sample rate and validate
606 gint rate = -1;
607 gst_structure_get_int(str, "rate", &rate);
608 if (rate <= 0)
609 {
610 WARN(mPipeline.get(), ("OnPadAdded: sample rate is invalid %d", rate));
611 return;
612 }
613 c->mSampleRate = (double)rate;
614
615 c->mType.reset(g_strdup(name));
616 if (!c->mType)
617 {
618 WARN(mPipeline.get(), ("OnPadAdded: unable to allocate audio type"));
619 return;
620 }
621
622 // Done with capabilities
623 }
624
625 // Create audioconvert element
626 c->mConv = gst_element_factory_make("audioconvert", NULL);
627 if (!c->mConv)
628 {
629 WARN(mPipeline.get(), ("OnPadAdded: failed to create audioconvert element"));
630 return;
631 }
632
633 // Create appsink element
634 c->mSink = gst_element_factory_make("appsink", NULL);
635 if (!c->mSink)
636 {
637 WARN(mPipeline.get(), ("OnPadAdded: failed to create appsink element"));
638 return;
639 }
640 SETCTX(c->mSink, c);
641
642 // Set the appsink callbacks and add the context pointer
643 gst_app_sink_set_callbacks(GST_APP_SINK(c->mSink), &AppSinkCallbacks, this, NULL);
644
645 {
646 // Set the capabilities that we desire
647 GstCaps *caps = gst_static_caps_get(&supportedCaps);
648 GstCapsHandle handle{ caps };
649 if (!caps)
650 {
651 WARN(mPipeline.get(), ("OnPadAdded: failed to create static caps"));
652 return;
653 }
654 gst_app_sink_set_caps(GST_APP_SINK(c->mSink), caps);
655 }
656
657 // Do not sync to the clock...process as quickly as possible
658 gst_base_sink_set_sync(GST_BASE_SINK(c->mSink), FALSE);
659
660 // Don't drop buffers...allow queue to build unfettered
661 gst_app_sink_set_drop(GST_APP_SINK(c->mSink), FALSE);
662
663 // Add both elements to the pipeline
664 gst_bin_add_many(GST_BIN(mPipeline.get()), c->mConv, c->mSink, NULL);
665
666 // Link them together
667 if (!gst_element_link(c->mConv, c->mSink))
668 {
669 WARN(mPipeline.get(), ("OnPadAdded: failed to link audioconvert and appsink"));
670 return;
671 }
672
673 // Link the audiconvert sink pad to the src pad
674 GstPadLinkReturn ret = GST_PAD_LINK_OK;
675 {
676 GstObjHandle<GstPad> convsink{ gst_element_get_static_pad(c->mConv, "sink") };
677 if (convsink)
678 ret = gst_pad_link(pad, convsink.get());
679 if (!convsink || ret != GST_PAD_LINK_OK)
680 {
681 WARN(mPipeline.get(), ("OnPadAdded: failed to link uridecodebin to audioconvert - %d", ret));
682 return;
683 }
684 }
685
686 // Synchronize audioconvert state with parent
687 if (!gst_element_sync_state_with_parent(c->mConv))
688 {
689 WARN(mPipeline.get(), ("OnPadAdded: unable to sync audioconvert state"));
690 return;
691 }
692
693 // Synchronize appsink state with parent
694 if (!gst_element_sync_state_with_parent(c->mSink))
695 {
696 WARN(mPipeline.get(), ("OnPadAdded: unable to sync appaink state"));
697 return;
698 }
699
700 return;
701}
702
703// ----------------------------------------------------------------------------
704// Process the "pad-removed" signal from uridecodebin
705void
707{
708 GStreamContext *c = GETCTX(pad);
709
710 // Set audioconvert and appsink states to NULL
711 gst_element_set_state(c->mSink, GST_STATE_NULL);
712 gst_element_set_state(c->mConv, GST_STATE_NULL);
713
714 // Unlink audioconvert -> appsink
715 gst_element_unlink(c->mConv, c->mSink);
716
717 // Remove the pads from the pipeilne
718 gst_bin_remove_many(GST_BIN(mPipeline.get()), c->mConv, c->mSink, NULL);
719
720 // And reset context
721 c->mConv = NULL;
722 c->mSink = NULL;
723
724 return;
725}
726
727// ----------------------------------------------------------------------------
728// Handle the "NEW-sample" message
729void
731{
732 // Allocate NEW tracks
733 //
734 // It is done here because, at least in the case of chained oggs,
735 // not all streams are known ahead of time.
736 if (c->mChannels.empty())
737 {
738 // Get the sample format...no need to release caps or structure
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");
742 if (!fmt)
743 {
744 WARN(mPipeline.get(), ("OnNewSample: missing audio format"));
745 return;
746 }
747
748 // Determinate sample format based on negotiated format
749 if (strcmp(fmt, GST_AUDIO_NE(S16)) == 0)
750 {
751 c->mFmt = int16Sample;
752 }
753 else if (strcmp(fmt, GST_AUDIO_NE(S24_32)) == 0)
754 {
755 c->mFmt = int24Sample;
756 }
757 else if (strcmp(fmt, GST_AUDIO_NE(F32)) == 0)
758 {
759 c->mFmt = floatSample;
760 }
761 else
762 {
763 // This shouldn't really happen since audioconvert will only give us
764 // the formats we said we could handle.
765 WARN(mPipeline.get(), ("OnNewSample: unrecognized sample format %s", fmt));
766 return;
767 }
768
769 // Allocate the track array
770 c->mChannels.resize(c->mNumChannels);
771 if (c->mChannels.size() != c->mNumChannels)
772 {
773 WARN(mPipeline.get(), ("OnNewSample: unable to allocate track array"));
774 return;
775 }
776
777 // Allocate all channels
778 for (int ch = 0; ch < c->mNumChannels; ch++)
779 {
780 // Create a track
781 c->mChannels[ch] = mTrackFactory->NewWaveTrack(c->mFmt, c->mSampleRate);
782 if (!c->mChannels[ch])
783 {
784 WARN(mPipeline.get(), ("OnNewSample: unable to create track"));
785 return;
786 }
787 }
788 }
789
790 // Get the buffer for the sample...no need to release
791 GstBuffer *buffer = gst_sample_get_buffer(sample);
792 if (!buffer)
793 {
794 // No buffer...not sure if this is an error or not,
795 // but we can't do anything else, so just bail silently.
796 return;
797 }
798
799 // Map the buffer
800 GstMapInfo info;
801 if (!gst_buffer_map(buffer, &info, GST_MAP_READ))
802 {
803 WARN(mPipeline.get(), ("OnNewSample: mapping buffer failed"));
804 return;
805 }
806 auto cleanup = finally([&]{
807 // Release buffer
808 gst_buffer_unmap(buffer, &info);
809 });
810
811 // Cache a few items
812 auto nChannels = c->mNumChannels;
813 sampleFormat fmt = c->mFmt;
814 samplePtr data = (samplePtr) info.data;
815 size_t samples = info.size / nChannels / SAMPLE_SIZE(fmt);
816
817 // Add sample data to tracks...depends on interleaved src data
818 for (int chn = 0; chn < nChannels; chn++)
819 {
820 // Append one channel
821 c->mChannels[chn]->Append(data,
822 fmt,
823 samples,
824 nChannels);
825
826 // Bump src to next channel
827 data += SAMPLE_SIZE(fmt);
828 }
829
830 return;
831}
832
833// ----------------------------------------------------------------------------
834// The following methods run within the main thread.
835// ----------------------------------------------------------------------------
836
837// ----------------------------------------------------------------------------
838// Constructor
841{
842 mDec = NULL;
843 mTrackFactory = NULL;
844 mAsyncDone = false;
845
846 g_mutex_init(&mStreamsLock);
847}
848
849// ----------------------------------------------------------------------------
850// Destructor
852{
853 // Make sure the pipeline isn't running
854 if (mPipeline)
855 {
856 gst_element_set_state(mPipeline.get(), GST_STATE_NULL);
857 }
858
859 // Delete all of the contexts
860 if (mStreams.size())
861 {
862 // PRL: is the FIFO destruction order important?
863 // If not, then you could simply clear mStreams.
864
865 {
867 while (mStreams.size() > 0)
868 {
869 // remove context from the array
870 mStreams.erase(mStreams.begin());
871 }
872 }
873
874 // Done with the context array
875 }
876
877 // Release the decoder
878 if (mDec != NULL)
879 {
880 gst_bin_remove(GST_BIN(mPipeline.get()), mDec);
881 }
882
883 g_mutex_clear(&mStreamsLock);
884}
885
886// ----------------------------------------------------------------------------
887// Return number of readable audio streams in the file
888wxInt32
890{
891 return mStreamInfo.size();
892}
893
894// ----------------------------------------------------------------------------
895// Return array of strings - descriptions of the streams
898{
899 return mStreamInfo;
900}
901
902// ----------------------------------------------------------------------------
903// Mark streams to process as selected by the user
904void
906{
908 if ((guint) index < mStreams.size())
909 {
910 GStreamContext *c = mStreams[index].get();
911 c->mUse = use;
912 }
913}
914
915// ----------------------------------------------------------------------------
916// Initialize importer
917bool
919{
920 // Create a URI from the filename
921 mUri.reset(g_strdup_printf("file:///%s", mFilename.ToUTF8().data()));
922 if (!mUri)
923 {
924 wxLogMessage(wxT("GStreamerImport couldn't create URI"));
925 return false;
926 }
927
928 // Create a pipeline
929 mPipeline.reset(gst_pipeline_new("pipeline"));
930
931 // Get its bus
932 mBus.reset(gst_pipeline_get_bus(GST_PIPELINE(mPipeline.get())));
933
934 // Create uridecodebin and set up signal handlers
935 mDec = gst_element_factory_make("uridecodebin", "decoder");
936 g_signal_connect(mDec, "autoplug-select", G_CALLBACK(GStreamerAutoplugSelectCallback), (gpointer) this);
937 g_signal_connect(mDec, "pad-added", G_CALLBACK(GStreamerPadAddedCallback), (gpointer) this);
938 g_signal_connect(mDec, "pad-removed", G_CALLBACK(GStreamerPadRemovedCallback), (gpointer) this);
939
940 // Set the URI
941 g_object_set(G_OBJECT(mDec), "uri", mUri, NULL);
942
943 // Add the decoder to the pipeline
944 if (!gst_bin_add(GST_BIN(mPipeline.get()), mDec))
945 {
947 XO("Unable to add decoder to pipeline"),
948 XO("GStreamer Importer"));
949
950 // Cleanup expected to occur in destructor
951 return false;
952 }
953
954 // Run the pipeline
955 GstStateChangeReturn state = gst_element_set_state(mPipeline.get(), GST_STATE_PAUSED);
956 if (state == GST_STATE_CHANGE_FAILURE)
957 {
959 XO("Unable to set stream state to paused."),
960 XO("GStreamer Importer"));
961 return false;
962 }
963
964 // Collect info while the stream is prerolled
965 //
966 // Unfortunately, for some files this may cause a slight "pause" in the GUI
967 // without a progress dialog appearing. Not much can be done about it other
968 // than throwing up an additional progress dialog and displaying two dialogs
969 // may be confusing to the users.
970
971 // Process messages until we get an error or the ASYNC_DONE message is received
972 bool success;
973 while (ProcessBusMessage(success) && success)
974 {
975 // Give wxWidgets a chance to do housekeeping
976 wxSafeYield();
977 }
978
979 // Build the stream info array
981 for (guint i = 0; i < mStreams.size(); i++)
982 {
983 GStreamContext *c = mStreams[i].get();
984
985 // Create stream info string
986 auto strinfo = XO("Index[%02d], Type[%s], Channels[%d], Rate[%d]")
987 .Format(
988 (unsigned int) i,
989 wxString::FromUTF8(c->mType.get()),
990 (int) c->mNumChannels,
991 (int) c->mSampleRate );
992 mStreamInfo.push_back(strinfo);
993 }
994
995 return success;
996}
997
998// ----------------------------------------------------------------------------
999// Return file dialog filter description
1002{
1003 return DESC;
1004}
1005
1006// ----------------------------------------------------------------------------
1007// Return number of uncompressed bytes in file...doubtful this is possible
1008auto
1010{
1011 return 0;
1012}
1013
1014// ----------------------------------------------------------------------------
1015// Import streams
1016int
1017GStreamerImportFileHandle::Import(TrackFactory *trackFactory,
1018 TrackHolders &outTracks,
1019 Tags *tags)
1020{
1021 outTracks.clear();
1022
1023 // Save track factory pointer
1024 mTrackFactory = trackFactory;
1025
1026 // Create the progress dialog
1028
1029 // Block streams that are to be bypassed
1030 bool haveStreams = false;
1031 {
1032 g_mutex_locker locker{ mStreamsLock };
1033 for (guint i = 0; i < mStreams.size(); i++)
1034 {
1035 GStreamContext *c = mStreams[i].get();
1036
1037 // Did the user choose to skip this stream?
1038 if (!c->mUse)
1039 {
1040 // Get the audioconvert sink pad and unlink
1041 {
1042 GstObjHandle<GstPad> convsink{ gst_element_get_static_pad(c->mConv, "sink") };
1043
1044 {
1045 GstObjHandle<GstPad> convpeer{ gst_pad_get_peer(convsink.get()) };
1046 gst_pad_unlink(convpeer.get(), convsink.get());
1047 }
1048
1049 // Set bitbucket callbacks so the prerolled sample won't get processed
1050 // when we change the state to PLAYING
1051 gst_app_sink_set_callbacks(GST_APP_SINK(c->mSink), &AppSinkBitBucket, this, NULL);
1052
1053 // Set state to playing for conv and sink so EOS gets processed
1054 gst_element_set_state(c->mConv, GST_STATE_PLAYING);
1055 gst_element_set_state(c->mSink, GST_STATE_PLAYING);
1056
1057 // Send an EOS event to the pad to force them to drain
1058 gst_pad_send_event(convsink.get(), gst_event_new_eos());
1059
1060 // Resync state with pipeline
1061 gst_element_sync_state_with_parent(c->mConv);
1062 gst_element_sync_state_with_parent(c->mSink);
1063
1064 // Done with the pad
1065 }
1066
1067 // Unlink audioconvert and appsink
1068 gst_element_unlink(c->mConv, c->mSink);
1069
1070 // Remove them from the bin
1071 gst_bin_remove_many(GST_BIN(mPipeline.get()), c->mConv, c->mSink, NULL);
1072
1073 // All done with them
1074 c->mConv = NULL;
1075 c->mSink = NULL;
1076
1077 continue;
1078 }
1079
1080 // We have a stream to process
1081 haveStreams = true;
1082 }
1083 }
1084
1085 // Can't do much if we don't have any streams to process
1086 if (!haveStreams)
1087 {
1089 XO("File doesn't contain any audio streams."),
1090 XO("GStreamer Importer"));
1091 return ProgressResult::Failed;
1092 }
1093
1094 // Get the ball rolling...
1095 GstStateChangeReturn state = gst_element_set_state(mPipeline.get(), GST_STATE_PLAYING);
1096 if (state == GST_STATE_CHANGE_FAILURE)
1097 {
1099 XO("Unable to import file, state change failed."),
1100 XO("GStreamer Importer"));
1101 return ProgressResult::Failed;
1102 }
1103
1104 // Get the duration of the stream
1105 gint64 duration;
1106 gst_element_query_duration(mPipeline.get(), GST_FORMAT_TIME, &duration);
1107
1108 // Handle bus messages and update progress while files is importing
1109 bool success = true;
1110 int updateResult = ProgressResult::Success;
1111 while (ProcessBusMessage(success) && success && updateResult == ProgressResult::Success)
1112 {
1113 gint64 position;
1114
1115 // Update progress indicator and give user chance to abort
1116 if (gst_element_query_position(mPipeline.get(), GST_FORMAT_TIME, &position))
1117 {
1118 updateResult = mProgress->Update((wxLongLong_t) position,
1119 (wxLongLong_t) duration);
1120 }
1121 }
1122
1123 // Disable pipeline
1124 gst_element_set_state(mPipeline.get(), GST_STATE_NULL);
1125
1126 // Something bad happened
1127 if (!success || updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled)
1128 {
1129 return updateResult;
1130 }
1131
1132 // Grab the streams lock
1133 g_mutex_locker locker{ mStreamsLock };
1134
1135 // Copy audio from mChannels to newly created tracks (destroying mChannels in process)
1136 int trackindex = 0;
1137 for (guint s = 0; s < mStreams.size(); s++)
1138 {
1139 GStreamContext *c = mStreams[s].get();
1140 if (c->mNumChannels)
1141 {
1142 for (int ch = 0; ch < c->mNumChannels; ch++)
1143 c->mChannels[ch]->Flush();
1144 outTracks.push_back(std::move(c->mChannels));
1145 }
1146 }
1147
1148 // Set any tags found in the stream
1149 *tags = mTags;
1150
1151 return updateResult;
1152}
1153
1154// ----------------------------------------------------------------------------
1155// Message handlers
1156// ----------------------------------------------------------------------------
1157
1158inline void GstMessageUnref(GstMessage *p) { gst_message_unref(p); }
1159
1160// ----------------------------------------------------------------------------
1161// Retrieve and process a bus message
1162bool
1164{
1165 bool cont = true;
1166
1167 // Default to no errors
1168 success = true;
1169
1170 // Get the next message
1171 std::unique_ptr < GstMessage, Deleter < GstMessage, GstMessageUnref > >
1172 msg{ gst_bus_timed_pop(mBus.get(), 100 * GST_MSECOND) };
1173
1174 if (!msg)
1175 {
1176 // Timed out...not an error
1177 return cont;
1178 }
1179
1180#if defined(_DEBUG)
1181 {
1182 GstString objname;
1183 if (msg->src != NULL)
1184 {
1185 objname.reset(gst_object_get_name(msg->src));
1186 }
1187
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(""));
1192 }
1193#endif
1194
1195 // Handle based on message type
1196 switch (GST_MESSAGE_TYPE(msg.get()))
1197 {
1198 // Handle error message from gstreamer
1199 case GST_MESSAGE_ERROR:
1200 {
1201 GErrorHandle err;
1202 GstString debug;
1203
1204 GstMessageParse(gst_message_parse_error, msg.get(), err, debug);
1205 if (err)
1206 {
1207 wxString m;
1208
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 );
1214#if defined(_DEBUG)
1215 AudacityMessageBox( msg );
1216#else
1217 wxLogMessage( msg.Debug() );
1218#endif
1219 }
1220
1221 success = false;
1222 cont = false;
1223 }
1224 break;
1225
1226 // Handle warning message from gstreamer
1227 case GST_MESSAGE_WARNING:
1228 {
1229 GErrorHandle err;
1230 GstString debug;
1231 GstMessageParse(gst_message_parse_warning, msg.get(), err, debug);
1232
1233 if (err)
1234 {
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(""));
1239 }
1240 }
1241 break;
1242
1243 // Handle warning message from gstreamer
1244 case GST_MESSAGE_INFO:
1245 {
1246 GErrorHandle err;
1247 GstString debug;
1248
1249 GstMessageParse(gst_message_parse_info, msg.get(), err, debug);
1250 if (err)
1251 {
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(""));
1256 }
1257 }
1258 break;
1259
1260 // Handle metadata tags
1261 case GST_MESSAGE_TAG:
1262 {
1263 GstTagList *tags = NULL;
1264 auto cleanup = finally([&]{
1265 // Done with list
1266 if(tags) gst_tag_list_unref(tags);
1267 });
1268
1269 // Retrieve tag list from message...just ignore failure
1270 gst_message_parse_tag(msg.get(), &tags);
1271 if (tags)
1272 {
1273 // Go process the list
1274 OnTag(GST_APP_SINK(GST_MESSAGE_SRC(msg.get())), tags);
1275 }
1276 }
1277 break;
1278
1279 // Pre-roll is done...will happen for each group
1280 // (like with chained OGG files)
1281 case GST_MESSAGE_ASYNC_DONE:
1282 {
1283 // If this is the first async-done message, then tell
1284 // caller to end loop, but leave it active so that
1285 // gstreamer threads can still queue up.
1286 //
1287 // We'll receive multiple async-done messages for chained
1288 // ogg files, so ignore the message the 2nd and subsequent
1289 // occurrences.
1290 if (!mAsyncDone)
1291 {
1292 cont = false;
1293 mAsyncDone = true;
1294 }
1295
1296 }
1297 break;
1298
1299 // End of the stream (and all sub-streams)
1300 case GST_MESSAGE_EOS:
1301 {
1302 // Terminate loop
1303 cont = false;
1304 }
1305 break;
1306 }
1307
1308 return cont;
1309}
1310
1311// ----------------------------------------------------------------------------
1312// Handle the "tag" message
1313void
1314GStreamerImportFileHandle::OnTag(GstAppSink * WXUNUSED(appsink), GstTagList *tags)
1315{
1316 // Collect all of the associates tags
1317 for (guint i = 0, icnt = gst_tag_list_n_tags(tags); i < icnt; i++)
1318 {
1319 wxString string;
1320
1321 // Get tag name...should always succeed...no need to release
1322 const gchar *name = gst_tag_list_nth_tag_name(tags, i);
1323 if (!name)
1324 {
1325 continue;
1326 }
1327
1328 // For each tag, determine its type and retrieve if possible
1329 for (guint j = 0, jcnt = gst_tag_list_get_tag_size(tags, name); j < jcnt; j++)
1330 {
1331 const GValue *val;
1332
1333 val = gst_tag_list_get_value_index(tags, name, j);
1334
1335 if (G_VALUE_HOLDS_STRING(val))
1336 {
1337 string = wxString::FromUTF8(g_value_get_string(val));
1338 }
1339 else if (G_VALUE_HOLDS_UINT(val))
1340 {
1341 string.Printf(wxT("%u"), (unsigned int) g_value_get_uint(val));
1342 }
1343 else if (G_VALUE_HOLDS_DOUBLE(val))
1344 {
1345 string.Printf(wxT("%g"), g_value_get_double(val));
1346 }
1347 else if (G_VALUE_HOLDS_BOOLEAN(val))
1348 {
1349 string = g_value_get_boolean(val) ? wxT("true") : wxT("false");
1350 }
1351 else if (GST_VALUE_HOLDS_DATE_TIME(val))
1352 {
1353 GstDateTime *dt = (GstDateTime *) g_value_get_boxed(val);
1354 GstString str{ gst_date_time_to_iso8601_string(dt) };
1355 string = wxString::FromUTF8(str.get());
1356 }
1357 else if (G_VALUE_HOLDS(val, G_TYPE_DATE))
1358 {
1359 GstString str{ gst_value_serialize(val) };
1360 string = wxString::FromUTF8(str.get());
1361 }
1362 else
1363 {
1364 wxLogMessage(wxT("Tag %s has unhandled type: %s"),
1365 wxString::FromUTF8(name),
1366 wxString::FromUTF8(G_VALUE_TYPE_NAME(val)));
1367 continue;
1368 }
1369
1370 // Translate known tag names
1371 wxString tag;
1372 if (strcmp(name, GST_TAG_TITLE) == 0)
1373 {
1374 tag = TAG_TITLE;
1375 }
1376 else if (strcmp(name, GST_TAG_ARTIST) == 0)
1377 {
1378 tag = TAG_ARTIST;
1379 }
1380 else if (strcmp(name, GST_TAG_ALBUM) == 0)
1381 {
1382 tag = TAG_ALBUM;
1383 }
1384 else if (strcmp(name, GST_TAG_TRACK_NUMBER) == 0)
1385 {
1386 tag = TAG_TRACK;
1387 }
1388 else if (strcmp(name, GST_TAG_DATE) == 0)
1389 {
1390 tag = TAG_YEAR;
1391 }
1392 else if (strcmp(name, GST_TAG_GENRE) == 0)
1393 {
1394 tag = TAG_GENRE;
1395 }
1396 else if (strcmp(name, GST_TAG_COMMENT) == 0)
1397 {
1398 tag = TAG_COMMENTS;
1399 }
1400 else
1401 {
1402 tag = wxString::FromUTF8(name);
1403 }
1404
1405 if (jcnt > 1)
1406 {
1407 tag.Printf(wxT("%s:%d"), tag, j);
1408 }
1409
1410 // Store the tag
1411 mTags.SetTag(tag, string);
1412 }
1413 }
1414}
1415#endif //USE_GSTREAMER
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)
static RegisteredToolbarFactory factory
#define str(a)
const TranslatableString name
Definition: Distortion.cpp:82
std::vector< std::shared_ptr< WaveTrack > > NewChannelGroup
Definition: Import.cpp:62
std::vector< std::vector< std::shared_ptr< WaveTrack > > > TrackHolders
Definition: Import.h:39
static Importer::RegisteredImportPlugin registered
Definition: ImportAUP.cpp:278
void GstMessageUnref(GstMessage *p)
std::unique_ptr< GstCaps, Deleter< GstCaps, GstCapsUnref > > GstCapsHandle
static void GStreamerPadRemovedCallback(GstElement *WXUNUSED(element), GstPad *pad, gpointer data)
#define SETCTX(o, c)
void GstSampleUnref(GstSample *p)
void(*)(GstMessage *message, GError **gerror, gchar **debug) ParseFn
void GstCapsUnref(GstCaps *p)
#define DESC
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
#define GETCTX(o)
#define WARN(e, msg)
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
#define _TS(s)
Definition: Internat.h:27
#define XO(s)
Definition: Internat.h:31
sampleFormat
Definition: SampleFormat.h:29
@ floatSample
Definition: SampleFormat.h:34
@ int16Sample
Definition: SampleFormat.h:32
@ int24Sample
Definition: SampleFormat.h:33
char * samplePtr
Definition: SampleFormat.h:49
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:44
#define TAG_TRACK
Definition: Tags.h:61
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_YEAR
Definition: Tags.h:62
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
std::vector< TranslatableString > TranslatableStrings
static const auto fn
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
! 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.
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.
Definition: ImportPlugin.h:107
FilePath mFilename
Definition: ImportPlugin.h:158
unsigned long long ByteCount
Definition: ImportPlugin.h:127
std::unique_ptr< ProgressDialog > mProgress
Definition: ImportPlugin.h:159
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:67
const FileExtensions mExtensions
Definition: ImportPlugin.h:99
ID3 Tags (for MP3)
Definition: Tags.h:73
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:441
Holds a msgid for the translation catalog; may also bind format arguments.
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void operator()(void *p) const
NewChannelGroup mChannels
GstElement * mPipeline
GstElement * mSink
GstElement * mConv
sampleFormat mFmt
g_mutex_locker(GMutex &mutex_)