Working transcoder. No error handling yet

This commit is contained in:
David Sansome 2010-05-03 19:56:21 +00:00
parent d5c52f32d8
commit 6fa0f28ca4
4 changed files with 136 additions and 20 deletions

View File

@ -19,8 +19,12 @@
#include <QtConcurrentMap>
#include <QtDebug>
#include <QEventLoop>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
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<elements.count() ; ++i) {
GstElement* element = CreateElement(elements[i], bin);
if (!element) return NULL;
if (!element) {
gst_object_unref(bin);
return NULL;
}
if (i == 0) {
// If this is the first element, make it the bin's sink
@ -120,9 +124,103 @@ void Transcoder::Start() {
}
void Transcoder::RunJob(const Job& job) {
// TODO
bool success = Transcode(job);
emit JobComplete(job.input, true);
emit JobComplete(job.input, success);
}
void Transcoder::NewPadCallback(GstElement*, GstPad* pad, gboolean, gpointer data) {
JobState* state = reinterpret_cast<JobState*>(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<JobState*>(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<JobState*>(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<GstElement> 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() {

View File

@ -24,6 +24,10 @@
#include <QFutureWatcher>
#include <QMetaType>
#include <boost/scoped_ptr.hpp>
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<QEventLoop> 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();

View File

@ -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");
}

View File

@ -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