diff --git a/src/transcoder.cpp b/src/transcoder.cpp index 29d8a16c4..a1de2b0ad 100644 --- a/src/transcoder.cpp +++ b/src/transcoder.cpp @@ -19,8 +19,12 @@ #include #include +#include #include +#include + +using boost::shared_ptr; GstElement* TranscoderFormat::CreateElement(const QString &factory_name, @@ -30,11 +34,8 @@ GstElement* TranscoderFormat::CreateElement(const QString &factory_name, factory_name.toAscii().constData(), name.isNull() ? factory_name.toAscii().constData() : name.toAscii().constData()); - if (ret) { - if (bin) gst_bin_add(GST_BIN(bin), ret); - } else { - gst_object_unref(GST_OBJECT(bin)); - } + if (ret && bin) + gst_bin_add(GST_BIN(bin), ret); return ret; } @@ -45,7 +46,10 @@ GstElement* TranscoderFormat::CreateBin(const QStringList& elements) const { GstElement* last_element = NULL; for (int i=0 ; i(data); + GstPad* const audiopad = gst_element_get_pad(state->convert_element, "sink"); + + if (GST_PAD_IS_LINKED(audiopad)) { + qDebug() << "audiopad is already linked. Unlinking old pad."; + gst_pad_unlink(audiopad, GST_PAD_PEER(audiopad)); + } + + gst_pad_link(pad, audiopad); + gst_object_unref(audiopad); +} + +gboolean Transcoder::BusCallback(GstBus*, GstMessage* msg, gpointer data) { + JobState* state = reinterpret_cast(data); + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + state->success = false; + state->event_loop->exit(); + break; + + default: + break; + } + return GST_BUS_DROP; +} + +GstBusSyncReply Transcoder::BusCallbackSync(GstBus*, GstMessage* msg, gpointer data) { + JobState* state = reinterpret_cast(data); + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + state->event_loop->exit(); + break; + + case GST_MESSAGE_ERROR: + state->success = false; + state->event_loop->exit(); + break; + + default: + break; + } + return GST_BUS_PASS; +} + +bool Transcoder::Transcode(const Job &job) const { + // Create the pipeline + shared_ptr pipeline(gst_pipeline_new("pipeline"), + boost::bind(gst_object_unref, _1)); + if (!pipeline) return false; + + // Create all the elements + const TranscoderFormat* f = job.output_format; + GstElement* src = f->CreateElement("filesrc", pipeline.get()); + GstElement* decode = f->CreateElement("decodebin", pipeline.get()); + GstElement* convert = f->CreateElement("audioconvert", pipeline.get()); + GstElement* encode = f->CreateEncodeBin(); + GstElement* sink = f->CreateElement("filesink", pipeline.get()); + + if (!src || !decode || !convert || !encode || !sink) + return false; + + // Join them together + gst_bin_add(GST_BIN(pipeline.get()), encode); + gst_element_link(src, decode); + gst_element_link_many(convert, encode, sink, NULL); + + // Set properties + g_object_set(src, "location", job.input.toLocal8Bit().constData(), NULL); + g_object_set(sink, "location", job.output.toLocal8Bit().constData(), NULL); + + // Set callbacks + JobState state; + state.convert_element = convert; + state.event_loop.reset(new QEventLoop); + state.success = true; + + g_signal_connect(decode, "new-decoded-pad", G_CALLBACK(NewPadCallback), &state); + gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline.get())), BusCallbackSync, &state); + gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline.get())), BusCallback, &state); + + // Start the pipeline and wait until it finishes + gst_element_set_state(pipeline.get(), GST_STATE_PLAYING); + + state.event_loop->exec(); + + // Do this explicitly so that it's guaranteed to happen before the event + // loop is destroyed. + pipeline.reset(); + + return state.success; } void Transcoder::JobsFinished() { diff --git a/src/transcoder.h b/src/transcoder.h index 877e14cf8..e3edee331 100644 --- a/src/transcoder.h +++ b/src/transcoder.h @@ -24,6 +24,10 @@ #include #include +#include + +class QEventLoop; + class Transcoder; class TranscoderFormat { @@ -36,7 +40,7 @@ class TranscoderFormat { virtual QString file_extension() const = 0; protected: - virtual GstElement* CreateOutputBin() const = 0; + virtual GstElement* CreateEncodeBin() const = 0; GstElement* CreateElement(const QString& factory_name, GstElement* bin = NULL, const QString& name = QString()) const; @@ -65,13 +69,27 @@ class Transcoder : public QObject { void AllJobsComplete(); private: + // The description of a file to transcode - lives in the main thread. struct Job { QString input; QString output; const TranscoderFormat* output_format; }; + // State held by a job and shared across gstreamer callbacks - lives in the + // job's thread. + struct JobState { + GstElement* convert_element; + boost::scoped_ptr event_loop; + bool success; + }; + void RunJob(const Job& job); + bool Transcode(const Job& job) const; + + static void NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer data); + static gboolean BusCallback(GstBus*, GstMessage* msg, gpointer data); + static GstBusSyncReply BusCallbackSync(GstBus*, GstMessage* msg, gpointer data); private slots: void JobsFinished(); diff --git a/src/transcoderformats.cpp b/src/transcoderformats.cpp index 27a8da617..8082dca93 100644 --- a/src/transcoderformats.cpp +++ b/src/transcoderformats.cpp @@ -16,22 +16,22 @@ #include "transcoderformats.h" -GstElement* OggVorbisTranscoder::CreateOutputBin() const { +GstElement* OggVorbisTranscoder::CreateEncodeBin() const { return CreateBin(QStringList() << "vorbisenc" << "oggmux"); } -GstElement* OggSpeexTranscoder::CreateOutputBin() const { +GstElement* OggSpeexTranscoder::CreateEncodeBin() const { return CreateBin(QStringList() << "speexenc" << "oggmux"); } -GstElement* FlacTranscoder::CreateOutputBin() const { +GstElement* FlacTranscoder::CreateEncodeBin() const { return CreateElement("flacenc"); } -GstElement* Mp3Transcoder::CreateOutputBin() const { +GstElement* Mp3Transcoder::CreateEncodeBin() const { return CreateElement("lamemp3enc"); } -GstElement* AacTranscoder::CreateOutputBin() const { +GstElement* AacTranscoder::CreateEncodeBin() const { return CreateElement("faac"); } diff --git a/src/transcoderformats.h b/src/transcoderformats.h index be5662b44..06296e52c 100644 --- a/src/transcoderformats.h +++ b/src/transcoderformats.h @@ -24,7 +24,7 @@ class OggVorbisTranscoder : public TranscoderFormat { QString name() const { return "Ogg Vorbis"; } QString file_extension() const { return "ogg"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; class OggSpeexTranscoder : public TranscoderFormat { @@ -32,7 +32,7 @@ class OggSpeexTranscoder : public TranscoderFormat { QString name() const { return "Ogg Speex"; } QString file_extension() const { return "spx"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; class FlacTranscoder : public TranscoderFormat { @@ -40,7 +40,7 @@ class FlacTranscoder : public TranscoderFormat { QString name() const { return "FLAC"; } QString file_extension() const { return "flac"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; class Mp3Transcoder : public TranscoderFormat { @@ -48,7 +48,7 @@ class Mp3Transcoder : public TranscoderFormat { QString name() const { return "MP3"; } QString file_extension() const { return "mp3"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; class AacTranscoder : public TranscoderFormat { @@ -56,7 +56,7 @@ class AacTranscoder : public TranscoderFormat { QString name() const { return "AAC"; } QString file_extension() const { return "aac"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; class WindowsMediaTranscoder : public TranscoderFormat { @@ -64,7 +64,7 @@ class WindowsMediaTranscoder : public TranscoderFormat { QString name() const { return "Windows Media"; } QString file_extension() const { return "wma"; } - GstElement* CreateOutputBin() const; + GstElement* CreateEncodeBin() const; }; #endif // TRANSCODERFORMATS_H