1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-08 16:37:44 +02:00
audacity/src/import/ImportGStreamer.cpp

1038 lines
32 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ImportGStreamer.cpp
Copyright 2008 LRN
Based on ImportFFmpeg.cpp by LRN
Licensed under the GNU General Public License v2 or later
*//****************************************************************//**
\class GStreamerImportFileHandle
\brief An ImportFileHandle for GStreamer data
*//****************************************************************//**
\class GStreamerImportPlugin
\brief An ImportPlugin for GStreamer data
*//*******************************************************************/
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#include "../Audacity.h" // needed before GStreamer.h
#include "../GStreamerLoader.h" // which brings in gst.h
#ifndef WX_PRECOMP
// Include your minimal set of headers here, or wx.h
#include <wx/window.h>
#endif
#define DESC _("GStreamer-compatible files")
#if defined(USE_GSTREAMER)
// all the includes live here by default
#include "Import.h"
#include "ImportGStreamer.h"
#include "../Tags.h"
#include "../Internat.h"
#include "../WaveTrack.h"
#include "ImportPlugin.h"
extern "C" {
#include <gio/gio.h>
#include <gst/app/gstappsink.h>
}
#include "stdint.h" //for int16_t
// A stucture, we create and maintain one for each audio stream
struct GStreamContext {
gchar *mAConvName; // Name of an audio converter element that serves this stream
gchar *mASinkName; // Name of an AppSink element that passes this stream to Audacity
GstElement *mConv; // Audio converter
GstElement *mSink; // Application sink
bool mUse; // True if this stream should be imported, False if it should not be
WaveTrack **mChannels; // Array of WaveTrack pointers, one for each channel
gint mNumChannels; // Number of channels
gdouble mSampleRate; // Sample rate
gint mWidth; // Alignment
gint mDepth; // Sample resolution
GstClockTime mLastTime; // For synchronization
};
class GStreamerImportFileHandle;
/// A representative of GStreamer loader in
/// the Audacity import plugin list
class GStreamerImportPlugin : public ImportPlugin
{
public:
GStreamerImportPlugin():
ImportPlugin(wxArrayString())
{
if (GStreamerInst != NULL)
{
mExtensions = GStreamerInst->GetExtensions();
}
else
{
// It should be an empty list, but i've noticed that empty list doesn't work here
mExtensions.Add(wxT("wav"));
}
}
~GStreamerImportPlugin() { }
wxString GetPluginFormatDescription();
///! Probes the file and opens it if appropriate
ImportFileHandle *Open(wxString Filename);
};
///! Does actual import, returned by GStreamerImportPlugin::Open
class GStreamerImportFileHandle : public ImportFileHandle
{
public:
GStreamerImportFileHandle(const wxString & name);
~GStreamerImportFileHandle();
///! Format initialization
///\param nameIsUri - true if mName should be treated as uri, false if mName is a filename
///\return true if successful, false otherwise
bool Init(bool nameIsUri);
wxString GetFileDescription();
int GetFileUncompressedBytes();
///! Imports audio
///\return import status (see Import.cpp)
int Import(TrackFactory *trackFactory, Track ***outTracks,
int *outNumTracks, Tags *tags);
///! Called when pad-added signal comes in from UriDecodeBin (as GstElement)
///\param uridecodebin - element that sent the signal
///\param pad - source pad of uridecodebin that will be serving the data
void OnNewStream(GstElement *uridecodebin, GstPad *pad);
///! Called when unknown-pad signal comes in from UriDecodeBin
///\param uridecodebin - element that sent the signal
///\param pad - source pad of uridecodebin that will not be serving any data
///\param caps - information about the stream
void OnUnknownStream(GstElement *uridecodebin, GstPad *pad, GstCaps *caps);
///! Adds new stream context
///\return pointer to newly-allocated context
GStreamContext *AddNewStream();
///! Removes stream context
///\param index - index of a context in context array
void DeleteStream(guint index);
///! Called when a message comes through GStreamer message bus
///\param bus - a bus that served the message
///\param message - a message
///\return true if we want to continue to watch the bus, false otherwise
gboolean OnBusMessage(GstBus *bus, GstMessage *message);
///! Called when new data comes in from the pipeline
///\param sc - stream context
///\param buffer - GStreamer Audio Buffer
///\return eImportSuccess if everything is OK
int WriteData(GStreamContext *sc, GstBuffer *buffer);
///! Called by the pipeline when GST_MESSAGE_TAG arrives
///\param list - list of tags
///\param tag - a tag in the list
void WriteTags(const GstTagList *list, const gchar *tag);
///! Called by Import.cpp
///\return number of readable audio streams in the file
wxInt32 GetStreamCount()
{
return mScs->len;
}
///! Called by Import.cpp
///\return array of strings - descriptions of the streams
wxArrayString *GetStreamInfo()
{
return mStreamInfo;
}
///! Called by Import.cpp
///\param StreamID - index of the stream in mStreamInfo and mScs arrays
///\param Use - true if this stream should be imported, false otherwise
void SetStreamUsage(wxInt32 StreamID, bool Use)
{
if ((guint)StreamID < mScs->len)
{
GStreamContext *c = (GStreamContext*)g_ptr_array_index(mScs,StreamID);
c->mUse = Use;
}
}
//! Convenience function - processes all the messages on the bus
void ProcessMessages()
{
GstMessage *msg = gst_bus_pop(mBus);
while (msg != NULL)
{
OnBusMessage(mBus,msg);
msg = gst_bus_pop(mBus);
}
}
private:
GstElement *mPipeline; //!< GStreamer pipeline
GstBus *mBus; //!< Message bus
GstElement *mDec; //!< uridecodebin element
GstStaticCaps mStaticCaps; //!< Decribes our input capabilities
GPtrArray *mScs; //!< Array of pointers to stream contexts.
wxArrayString *mStreamInfo; //!< Array of stream descriptions. Length is the same as mScs
wxString mName; //!< Source name
Track ***mOutTracks; //!< Tracks to be passed back to Audacity
int *mOutNumTracks; //!< Number of tracks to be passed back to Audacity
Tags mTags; //!< Tags to be passed back to Audacity
};
void GetGStreamerImportPlugin(ImportPluginList *importPluginList,
UnusableImportPluginList *unusableImportPluginList)
{
importPluginList->Append(new GStreamerImportPlugin);
}
wxString GStreamerImportPlugin::GetPluginFormatDescription()
{
return DESC;
}
ImportFileHandle *GStreamerImportPlugin::Open(wxString filename)
{
if (!GStreamerInst || !GStreamerInst->Loaded())
return NULL;
GStreamerImportFileHandle *handle = new GStreamerImportFileHandle(filename);
// Open the file for import
bool success = handle->Init(false);
if (!success) {
delete handle;
return NULL;
}
return handle;
}
GStreamerImportFileHandle::GStreamerImportFileHandle(const wxString & name)
: ImportFileHandle(name)
{
mStreamInfo = new wxArrayString();
mName = name;
mPipeline = mDec = NULL;
mScs = g_ptr_array_new();
memset(&mStaticCaps,0,sizeof(mStaticCaps));
mStaticCaps.string = ( // We are guaranteed to get audio in following format:
"audio/x-raw-int, " // Audaciy can handle floats too, but ints should be ok.
"signed = (boolean) { TRUE }, " // I don't think that Audacity supports unsigned ints.
"width = (int) { 16, 32 }, " // 2- (for 16-bit depth) or 4-byte (for 24-bit depth) alignment.
"depth = (int) { 16, 24 }, " // 16- and 24-bit depth. 32-bit ints are not supported.
"rate = (int) [ 1, MAX ], " // AFAIK Audacity supports any sample rate.
"channels = (int) [ 1, MAX ], " // Same here - Audacity supports any number of channel.
"endianness = (int) BYTE_ORDER" // I think 'BYTE_ORDER' means 'native'. Right?
);
}
GStreamerImportFileHandle::~GStreamerImportFileHandle()
{
delete mStreamInfo;
if (mBus != NULL)
gst_object_unref(mBus);
while (mScs->len > 0)
DeleteStream(0);
g_ptr_array_free(mScs,TRUE);
if (mPipeline != NULL)
{
gst_object_unref(GST_OBJECT(mPipeline));
}
}
// Transforms a taglist to a string
void ConvertTagsToString(const GstTagList *list, const gchar *tag, gpointer user_data)
{
GString *gstring = (GString*)user_data;
guint valuecount = gst_tag_list_get_tag_size(list, tag);
const gchar *strvalue = NULL;
GValue value = {0};
// Get a GValue of the tag
if (gst_tag_list_copy_value(&value, list, tag))
{
GType valtype = G_VALUE_TYPE(&value);
const gchar *gtypename = g_type_name(valtype);
GValue value_str = {0};
// Transform it to string GValue
g_value_init(&value_str, G_TYPE_STRING);
if (g_value_transform(&value, &value_str))
{
// Get the string representation
strvalue = g_value_get_string(&value_str);
if (strvalue)
g_string_append_printf(gstring, "%s[%s] ", tag, strvalue, NULL);
}
}
}
void GStreamerWriteTags(const GstTagList *list, const gchar *tag, gpointer user_data)
{
GStreamerImportFileHandle *handle = (GStreamerImportFileHandle*)user_data;
handle->WriteTags(list, tag);
}
// Writes tags to mTags member
void GStreamerImportFileHandle::WriteTags(const GstTagList *list, const gchar *tag)
{
guint valuecount = gst_tag_list_get_tag_size(list, tag);
const gchar *strvalue = NULL;
GValue value = {0};
if (gst_tag_list_copy_value(&value, list, tag))
{
GValue value_str = {0};
g_value_init(&value_str, G_TYPE_STRING);
if (g_value_transform(&value, &value_str))
{
strvalue = g_value_get_string(&value_str);
if (strvalue)
{
wxString wxstrvalue = wxString::FromUTF8(strvalue);
if (g_utf8_collate(tag, GST_TAG_TITLE) == 0)
mTags.SetTag(TAG_TITLE, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_ARTIST) == 0)
mTags.SetTag(TAG_ARTIST, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_ALBUM) == 0)
mTags.SetTag(TAG_ALBUM, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_TRACK_NUMBER) == 0)
mTags.SetTag(TAG_TRACK, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_DATE) == 0)
mTags.SetTag(TAG_YEAR, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_GENRE) == 0)
mTags.SetTag(TAG_GENRE, wxstrvalue);
else if (g_utf8_collate(tag, GST_TAG_COMMENT) == 0)
mTags.SetTag(TAG_COMMENTS, wxstrvalue);
}
}
}
}
// Message handling
gboolean GStreamerImportFileHandle::OnBusMessage(GstBus *bus, GstMessage *message)
{
if (message != NULL && message->src != NULL)
{
gchar *objname = gst_object_get_name(message->src);
if (objname != NULL)
{
wxLogMessage(wxT("GStreamer: Got message %s from object %s"), wxString::FromUTF8(GST_MESSAGE_TYPE_NAME(message)).c_str() ,wxString::FromUTF8(objname).c_str());
}
g_free(objname);
}
else
if (message)
wxLogMessage(wxT("GStreamer: Got message %s"), wxString::FromUTF8(GST_MESSAGE_TYPE_NAME(message)).c_str());
GError *err;
gchar *debug;
GstTagList *storedTaglist;
switch (GST_MESSAGE_TYPE(message))
{
case GST_MESSAGE_ERROR:
gst_message_parse_error(message, &err, &debug);
wxLogMessage(wxT("GStreamer Error: %s - %s"), wxString::FromUTF8(err->message).c_str(), wxString::FromUTF8(debug).c_str());
g_error_free(err);
g_free(debug);
return FALSE;
break;
case GST_MESSAGE_WARNING:
gst_message_parse_warning(message, &err, &debug);
wxLogMessage(wxT("GStreamer Warning: %s - %s"), wxString::FromUTF8(err->message).c_str(), wxString::FromUTF8(debug).c_str());
g_error_free(err);
g_free(debug);
break;
case GST_MESSAGE_INFO:
gst_message_parse_info(message, &err, &debug);
wxLogMessage(wxT("GStreamer Info: %s - %s"), wxString::FromUTF8(err->message).c_str(), wxString::FromUTF8(debug).c_str());
g_error_free(err);
g_free(debug);
break;
case GST_MESSAGE_STATE_CHANGED:
GstState oldstate, newstate, pending;
gst_message_parse_state_changed(message, &oldstate, &newstate, &pending);
wxLogMessage(wxT("GStreamer State: %d -> %d ... %d"), oldstate, newstate, pending);
break;
case GST_MESSAGE_ASYNC_DONE:
wxLogMessage(wxT("GStreamer: Asynchronous state change is done"));
return FALSE;
break;
case GST_MESSAGE_EOS:
wxLogMessage(wxT("GStreamer: End of the source stream"));
gst_element_set_state(mPipeline, GST_STATE_NULL);
return FALSE;
break;
case GST_MESSAGE_TAG:
GstTagList *tags;
tags = NULL;
gst_message_parse_tag(message, &tags);
storedTaglist = (GstTagList*)g_object_get_data(G_OBJECT(message->src), "Audacity::StoredTagList");
if (storedTaglist)
{
gst_tag_list_insert(storedTaglist, tags, GST_TAG_MERGE_APPEND);
}
gst_tag_list_foreach(tags, GStreamerWriteTags, (gpointer)this);
break;
default:
/* unhandled message */
break;
}
/* we want to be notified again the next time there is a message
* on the bus, so returning TRUE (FALSE means we want to stop watching
* for messages on the bus and our callback should not be called again)
*/
return TRUE;
}
gboolean GStreamerBusCallback(GstBus *bus, GstMessage *message, gpointer data)
{
GStreamerImportFileHandle *handle = (GStreamerImportFileHandle*)data;
return handle->OnBusMessage(bus, message);
}
void GStreamerUnknownStreamCallback(GstElement *uridecodebin, GstPad *pad, GstCaps *caps, gpointer data)
{
GStreamerImportFileHandle *handle = (GStreamerImportFileHandle*)data;
handle->OnUnknownStream(uridecodebin, pad, caps);
}
// Event handler for audioconv'es sink pads, reroutes tags to to the message bus
gboolean GStreamerConvEventCallback(GstPad *pad, GstEvent *event)
{
gboolean ret;
GstTagList *taglist;
GstObject *element = gst_pad_get_parent(pad);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_TAG:
gst_event_parse_tag (event, &taglist);
gst_element_post_message (GST_ELEMENT(element), gst_message_new_tag (element, gst_tag_list_copy(taglist)));
break;
}
gst_object_unref(element);
return gst_pad_event_default (pad, event);
}
// Called when there is going to be no more pads, i.e. demuxer found and autoplugged all streams
void GStreamerNoMorePadsCallback(GstElement *gstelement, gpointer data)
{
GStreamerImportFileHandle *handle = (GStreamerImportFileHandle*)data;
gpointer itdata;
gboolean done;
GstIterator *it;
it = gst_element_iterate_src_pads(gstelement);
done = FALSE;
while (!done) {
switch (gst_iterator_next (it, &itdata)) {
case GST_ITERATOR_OK:
handle->OnNewStream(gstelement, GST_PAD(itdata));
gst_object_unref (itdata);
break;
case GST_ITERATOR_RESYNC:
// Rollback changes to items
gst_iterator_resync (it);
break;
case GST_ITERATOR_ERROR:
// Wrong parameter were given
done = TRUE;
break;
case GST_ITERATOR_DONE:
done = TRUE;
break;
}
}
gst_iterator_free(it);
}
// This "gint" is really GstAutoplugSelectResult enum, but we may not have gstplay-enum.h, so just use gint
gint GStreamerAutoplugSelectCallback(GstElement *element, GstPad *pad, GstCaps *caps, GstElementFactory *factory, gpointer data)
{
// Check factory class
const gchar *fclass = gst_element_factory_get_klass(factory);
gboolean quit = FALSE;
// Skip video decoding
if (g_strrstr(fclass,"Video"))
return 2;
return 0;
}
// Called for each new stream
void GStreamerImportFileHandle::OnNewStream(GstElement *uridecodebin, GstPad *pad)
{
GstCaps *caps;
GstStructure *str;
gboolean quit;
const gchar *name;
caps = gst_pad_get_caps(pad);
str = gst_caps_get_structure(caps, 0);
// Check stream type
name = gst_structure_get_name(str);
// Only accept audio streams
quit = FALSE;
if (!g_strrstr(name, "audio"))
{
quit = TRUE;
}
gst_caps_unref (caps);
if (quit)
return;
// Create unique name
gchar *mAConvName = g_strdup_printf("aconv-%d",mScs->len);
// Create an audioconv
GstElement *mConv = gst_element_factory_make("audioconvert", mAConvName);
// Get it's sink pad
GstPad *convpad = gst_element_get_static_pad(mConv, "sink");
GstTagList *storedTaglist = gst_tag_list_new();
g_object_set_data(G_OBJECT(mConv), "Audacity::StoredTagList", (gpointer)storedTaglist);
gst_pad_set_event_function(convpad, GStreamerConvEventCallback);
// Add it to the pipeline
gst_bin_add(GST_BIN(mPipeline), mConv);
GstPadLinkReturn ret = gst_pad_link(pad, convpad);
gst_object_unref(convpad);
if (ret)
{
// TODO: insert wxLogMessage here
g_print("Failed to link uridecodebin to audioconvert - %d\n",ret);
g_free(mAConvName);
gst_bin_remove(GST_BIN(mPipeline), mConv);
gst_object_unref(mConv);
return;
}
gchar *mASinkName = g_strdup_printf("asink-%d",mScs->len);
GstElement *mSink = gst_element_factory_make("appsink", mASinkName);
// Set up sink properties
caps = gst_static_caps_get(&mStaticCaps);
gst_app_sink_set_caps(GST_APP_SINK(mSink), caps); //!< NULL by default? (only accept data that matches caps)
gst_caps_unref(caps);
gst_base_sink_set_sync(GST_BASE_SINK(mSink), FALSE); //!< TRUE by default (sync to the clock)
gst_app_sink_set_drop(GST_APP_SINK(mSink), FALSE); //!< FALSE by default (don't drop buffers when queue is full)
gst_app_sink_set_emit_signals(GST_APP_SINK(mSink), FALSE); //!< FALSE by default (work in blocking mode)
//gst_app_sink_set_max_buffers(GST_APP_SINK(mSink), 0); //!< 0 by default, which means unlimited buffering
gst_bin_add(GST_BIN(mPipeline), mSink);
if (!gst_element_link(mConv, mSink))
{
g_print("Failed to link autioconvert to appsink\n");
g_free(mAConvName);
gst_bin_remove(GST_BIN(mPipeline), mConv);
gst_object_unref(mConv);
g_free(mASinkName);
gst_bin_remove(GST_BIN(mPipeline), mSink);
gst_object_unref(mSink);
}
// Run newly added elements
GstStateChangeReturn statechange;
statechange = gst_element_set_state(mConv, GST_STATE_PAUSED);
statechange = gst_element_set_state(mSink, GST_STATE_PAUSED);
// Allocate memory
GStreamContext *c = g_new0(GStreamContext,1);
c->mAConvName = mAConvName;
c->mASinkName = mASinkName;
c->mConv = mConv;
c->mSink = mSink;
// Add new stream context to context array
g_ptr_array_add(mScs,c);
}
// For unknown streams just dump info to the debug log
void GStreamerImportFileHandle::OnUnknownStream(GstElement *uridecodebin, GstPad *pad, GstCaps *caps)
{
GstStructure *str;
str = gst_caps_get_structure(caps, 0);
gst_structure_foreach(str, LogStructure, NULL);
}
// Free memory occupied by the stream and remove it
void GStreamerImportFileHandle::DeleteStream(guint index)
{
if (index < mScs->len)
{
GStreamContext *str = (GStreamContext*)g_ptr_array_index(mScs,index);
g_free(str->mAConvName);
g_free(str->mASinkName);
gst_bin_remove(GST_BIN(mPipeline), str->mSink);
gst_bin_remove(GST_BIN(mPipeline), str->mConv);
gst_object_unref(GST_OBJECT(str->mSink));
gst_object_unref(GST_OBJECT(str->mConv));
g_ptr_array_remove(mScs,(gpointer)str);
g_free(str);
}
}
// Pipeline initialization
bool GStreamerImportFileHandle::Init(bool nameIsUri)
{
// Create a pipeline
mPipeline = gst_pipeline_new("pipeline");
// Get it's bus an add a message watch to it
mBus = gst_pipeline_get_bus(GST_PIPELINE(mPipeline));
// Set up source location
gchar *name_utf8 = g_strdup(mName.ToUTF8());
gchar *name_with_proto_utf8;
if (!nameIsUri)
name_with_proto_utf8 = g_strdup_printf("file:///%s",name_utf8);
// FIXME: it is possible that g_filename_from_utf8 should be used to convert uri to filesystem encoding
/*
GError *err = NULL;
gchar *name_filesystem = g_filename_from_utf8(name_with_proto_utf8, -1, NULL, NULL, &err);
g_free(name_with_proto_utf8);
if (err != NULL)
{
return false;
}
*/
// Create uridecodebin and set up signal handlers
mDec = gst_element_factory_make("uridecodebin", "decoder");
g_object_set_data(G_OBJECT(mDec), "Audacity.Object", (gpointer)this);
g_signal_connect(mDec, "no-more-pads", G_CALLBACK(GStreamerNoMorePadsCallback), (gpointer)this);
g_signal_connect(mDec, "unknown-type", G_CALLBACK(GStreamerUnknownStreamCallback), (gpointer)this);
g_signal_connect(mDec, "autoplug-select", G_CALLBACK(GStreamerAutoplugSelectCallback), (gpointer)this);
if (!nameIsUri)
{
g_object_set(G_OBJECT(mDec), "uri", name_with_proto_utf8, NULL);
g_free(name_with_proto_utf8);
}
else
g_object_set(G_OBJECT(mDec), "uri", name_utf8, NULL);
g_free(name_utf8);
// Add everything into the pipeline
gst_bin_add_many(GST_BIN(mPipeline), mDec, NULL);
// Run the pipeline
GstStateChangeReturn statechange = gst_element_set_state(mPipeline, GST_STATE_PAUSED);
while (TRUE)
{
GstMessage *msg = gst_bus_pop(mBus);
if (msg)
{
if (!OnBusMessage(mBus,msg))
break;
}
}
// Make streaminfo strings for stream selector
for (gint i = 0; i < mScs->len; i++)
{
wxString sinfo;
GString *gstring_tags, *gstring_caps;
GstTagList *storedTaglist;
GstPad *convpad;
GstCaps *ncaps;
GstStructure *str;
gstring_tags = g_string_new(NULL);
gstring_caps = g_string_new(NULL);
GStreamContext *c = (GStreamContext*) g_ptr_array_index(mScs, i);
// Retrieve the taglist for that pad
storedTaglist = (GstTagList *)g_object_get_data(G_OBJECT(c->mConv), "Audacity::StoredTagList");
// Remove it from the pad so it won't accumulate tags anymore
g_object_set_data(G_OBJECT(c->mConv), "Audacity::StoredTagList", NULL);
// Convert taglist to string
gst_tag_list_foreach(storedTaglist,ConvertTagsToString, (gpointer)gstring_tags);
// Get negotiated caps
convpad = gst_element_get_static_pad(c->mConv, "sink");
ncaps = gst_pad_get_negotiated_caps(convpad);
str = gst_caps_get_structure(ncaps, 0);
// Convert it to gstring
gst_structure_foreach(str, LogStructure, (gpointer)gstring_caps);
sinfo.Printf(wxT("%s negotiated as: %s"), wxString::FromUTF8(gstring_tags->str).c_str(), wxString::FromUTF8(gstring_caps->str).c_str());
mStreamInfo->Add(sinfo);
gst_caps_unref(ncaps);
gst_object_unref(convpad);
gst_object_unref(storedTaglist);
g_string_free(gstring_tags, TRUE);
g_string_free(gstring_caps, TRUE);
}
return true;
}
wxString GStreamerImportFileHandle::GetFileDescription()
{
return DESC;
}
int GStreamerImportFileHandle::GetFileUncompressedBytes()
{
// TODO: Get Uncompressed byte count. Is it possible for GStreamer?
return 0;
}
int GStreamerImportFileHandle::Import(TrackFactory *trackFactory,
Track ***outTracks,
int *outNumTracks,
Tags *tags)
{
CreateProgress();
mOutTracks = outTracks;
mOutNumTracks = outNumTracks;
/*
// Dumps pipeline to file. At the moment it's for debugging only.
FILE *f = fopen("gstreamer.import.pipeline.xml", "wb");
xmlDocPtr cur = gst_xml_write (mPipeline);
int memsize;
xmlChar *mem;
xmlDocDumpMemory (cur, &mem, &memsize);
fwrite(mem,1,memsize,f);
fclose(f);
*/
/*
for (guint i = 0; i < mScs->len; i++)
{
GStreamContext *sc = (GStreamContext *)g_ptr_array_index(mScs, i);
if (!sc->mUse)
{
DeleteStream(i);
i--;
}
}
*/
// For each stream
for (guint i = 0; i < mScs->len; i++)
{
GStreamContext *sc = (GStreamContext *)g_ptr_array_index(mScs, i);
// For used streams - get actual stream caps
if (sc->mUse)
{
GstPad *pad = gst_element_get_static_pad(sc->mSink,"sink");
if (pad != NULL)
{
GstCaps *caps = gst_pad_get_negotiated_caps(pad);
if (caps != NULL)
{
gint capcount = gst_caps_get_size(caps);
GstStructure *str = gst_caps_get_structure(caps, 0);
gint channels = -1;
gst_structure_get_int(str, "channels", &channels);
gint rate = 0;
gst_structure_get_int(str, "rate", &rate);
sc->mNumChannels = channels;
sc->mSampleRate = (double)rate;
gst_structure_get_int(str, "width", &sc->mWidth);
gst_structure_get_int(str, "depth", &sc->mDepth);
gst_caps_unref(caps);
if (channels <= 0) sc->mUse = false;
}
gst_object_unref(pad);
}
else sc->mUse = false;
}
// For used streams that got negotiated properly - allocate stuff
if (sc->mUse)
{
sc->mChannels = new WaveTrack *[sc->mNumChannels];
sampleFormat sfmt = int16Sample;
switch(sc->mDepth)
{
case 16:
sfmt = int16Sample;
break;
case 24:
sfmt = int24Sample;
break;
default:
;// TODO: Error? We do not support 32-bit int samples, do we?
}
for (int ch = 0; ch < sc->mNumChannels; ch++)
{
sc->mChannels[ch] = trackFactory->NewWaveTrack(sfmt, sc->mSampleRate);
if (sc->mNumChannels == 2)
{
switch (ch)
{
case 0:
sc->mChannels[ch]->SetChannel(Track::LeftChannel);
sc->mChannels[ch]->SetLinked(true);
break;
case 1:
sc->mChannels[ch]->SetChannel(Track::RightChannel);
sc->mChannels[ch]->SetTeamed(true);
break;
}
}
else
{
sc->mChannels[ch]->SetChannel(Track::MonoChannel);
}
}
}
// I'm not sure this is going to work with queue limit set to zero...
else
{
gst_app_sink_set_drop(GST_APP_SINK(sc->mSink), true);
}
}
// The result of Import() to be returend. It will be something other than zero if user canceled or some error appears.
int res = eProgressSuccess;
// Run the pipeline
GstStateChangeReturn statechange = gst_element_set_state(mPipeline, GST_STATE_PLAYING);
if (statechange == GST_STATE_CHANGE_FAILURE)
res = eProgressFailed;
// This is the heart of the importing process
gboolean doIt = TRUE;
while (doIt && res == eProgressSuccess)
{
doIt = FALSE;
// Keep an eye on any messages we could have
ProcessMessages();
guint sinkToPull = 0;
GstClockTime lowestTime = -1;
GstFormat fmt = GST_FORMAT_TIME;
// Find a sink with lowest last timestamp (the one that lags behind the most)
for (guint i = 0; i < mScs->len; i++)
{
GStreamContext *c = (GStreamContext *)g_ptr_array_index(mScs, i);
if (c->mUse)
{
if (c->mLastTime < lowestTime || lowestTime < 0)
{
sinkToPull = i;
lowestTime = c->mLastTime;
}
}
}
// Pull the data from that sink
GStreamContext *c = (GStreamContext *)g_ptr_array_index(mScs, sinkToPull);
GstBuffer *incbuf = gst_app_sink_pull_buffer(GST_APP_SINK(c->mSink));
if (incbuf)
{
c->mLastTime = GST_BUFFER_TIMESTAMP(incbuf);
doIt = TRUE;
res = WriteData(c, incbuf);
gst_buffer_unref(incbuf);
}
}
// Something bad happened - destroy everything!
if (res == eProgressFailed || res == eProgressCancelled)
{
for (guint s = 0; s < mScs->len; s++)
{
GStreamContext *c = (GStreamContext *)g_ptr_array_index(mScs, s);
delete[] c->mChannels;
}
return res;
}
*outNumTracks = 0;
for (guint s = 0; s < mScs->len; s++)
{
GStreamContext *c = (GStreamContext*)g_ptr_array_index(mScs,s);
if (c->mUse)
*outNumTracks += c->mNumChannels;
}
// Create new tracks
*outTracks = new Track *[*outNumTracks];
// Copy audio from mChannels to newly created tracks (destroying mChannels elements in process)
int trackindex = 0;
for (guint s = 0; s < mScs->len; s++)
{
GStreamContext *c = (GStreamContext*)g_ptr_array_index(mScs,s);
if (c->mUse)
{
for(int ch = 0; ch < c->mNumChannels; ch++)
{
c->mChannels[ch]->Flush();
(*outTracks)[trackindex++] = c->mChannels[ch];
}
delete[] c->mChannels;
c->mChannels = NULL;
c->mUse = false;
}
}
// Pray that copycon works correctly
*tags = mTags;
return eProgressSuccess;
}
int GStreamerImportFileHandle::WriteData(GStreamContext *sc,GstBuffer *buffer)
{
size_t pos = 0;
int updateResult = eProgressSuccess;
gpointer data = GST_BUFFER_DATA(buffer);
guint length = GST_BUFFER_SIZE(buffer);
// Allocate the buffer to store audio.
int nChannels = sc->mNumChannels;
int16_t **tmp16 = NULL;
int32_t **tmp32 = NULL;
// For 16-bit samples use 16-bit output buffer
if (sc->mWidth == 16)
{
tmp16 = (int16_t**)malloc(sizeof(int16_t*)*nChannels);
for (int chn = 0; chn < nChannels; chn++)
{
tmp16[chn] = (int16_t*)malloc(sizeof(int16_t) * length/sizeof(int16_t)/nChannels);
}
}
// For 24- and 32-bit samples use 32-bit output buffer
else if (sc->mWidth == 24 || sc->mWidth == 32)
{
tmp32 = (int32_t**)malloc(sizeof(int32_t*)*nChannels);
for (int chn = 0; chn < nChannels; chn++)
{
tmp32[chn] = (int32_t*)malloc(sizeof(int32_t) * length/sizeof(int32_t)/nChannels);
}
}
// Separate the channels
int16_t *int16_data = NULL;
int32_t *int32_data = NULL;
int index = 0;
// Samples may be 16-, 24- or 32-bit, but input buffer's width is independant
// I'm assuming that we'll never get width=16 and depth=24 or 32, that's impossible
// Or width=32 and depth=16, that's stupid
if (sc->mWidth == 16)
{
int16_data = (int16_t*)data;
for (pos = 0; pos < length/sizeof(int16_t);)
{
for (int chn = 0; chn < nChannels; chn++)
{
tmp16[chn][index] = int16_data[pos];
pos++;
}
index++;
}
}
else if (sc->mWidth == 24 || sc->mWidth == 32)
{
int32_data = (int32_t*)data;
for (pos = 0; pos < length/sizeof(int32_t);)
{
for (int chn = 0; chn < nChannels; chn++)
{
tmp32[chn][index] = int32_data[pos];
pos++;
}
index++;
}
}
// Write audio into WaveTracks
if (sc->mDepth == 16)
{
for (int chn = 0; chn < nChannels; chn++)
{
sc->mChannels[chn]->Append((samplePtr)tmp16[chn],int16Sample,index);
free(tmp16[chn]);
}
free(tmp16);
}
else if (sc->mDepth == 24 || sc->mDepth == 32)
{
for (int chn = 0; chn < nChannels; chn++)
{
sc->mChannels[chn]->Append((samplePtr)tmp32[chn],int24Sample,index);
free(tmp32[chn]);
}
free(tmp32);
}
// Try to update the progress indicator (and see if user wants to stop or cancel)
GstFormat fmt = GST_FORMAT_TIME;
gint64 tpos, tlen;
if (gst_element_query_position (mPipeline, &fmt, &tpos) &&
gst_element_query_duration (mPipeline, &fmt, &tlen) )
{
updateResult = mProgress->Update((wxLongLong)tpos, (wxLongLong)tlen);
if (((gdouble)tpos/tlen) >= 0.98103365830952650)
{
if (tpos > 0)
tpos = 0;
}
if (updateResult != 1)
{
if (updateResult == 0)
{
updateResult = eProgressCancelled;
}
else
updateResult = eProgressStopped;
GstStateChangeReturn statechange = gst_element_set_state(mPipeline, GST_STATE_NULL);
}
else
updateResult = eProgressSuccess;
}
return updateResult;
}
#endif //USE_GSTREAMER