/* * GStreamer QuickTime audio decoder codecs wrapper * Copyright <2006, 2007> Fluendo * Copyright <2006, 2007, 2008> Pioneers of the Inevitable * * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Alternatively, the contents of this file may be used under the * GNU Lesser General Public License Version 2.1 (the "LGPL"), in * which case the following provisions apply instead of the ones * mentioned above: * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "qtwrapper.h" #include "codecmapping.h" #include "qtutils.h" #ifdef G_OS_WIN32 #include #else #include #endif #define QTWRAPPER_ADEC_PARAMS_QDATA g_quark_from_static_string("qtwrapper-adec-params") #define NO_MORE_INPUT_DATA 42 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-float, " "endianness = (int) {" G_STRINGIFY (G_BYTE_ORDER) " }, " "signed = (boolean) { TRUE }, " "width = (int) 32, " "depth = (int) 32, " "rate = (int) [1, MAX], " "channels = (int) [1, MAX]") ); typedef struct _QTWrapperAudioDecoder QTWrapperAudioDecoder; typedef struct _QTWrapperAudioDecoderClass QTWrapperAudioDecoderClass; struct _QTWrapperAudioDecoder { GstElement parent; GstPad *sinkpad; GstPad *srcpad; /* FIXME : all following should be protected by a mutex */ ComponentInstance adec; /* The Audio Decoder component */ AudioStreamBasicDescription indesc, outdesc; guint samplerate; guint channels; AudioBufferList *bufferlist; AudioStreamPacketDescription aspd[1]; /* first time received after NEWSEGMENT */ GstClockTime initial_time; /* offset in samples from the initial time */ guint64 cur_offset; /* TRUE just after receiving a NEWSEGMENT */ gboolean gotnewsegment; /* Data for StdAudio callbacks */ GstBuffer *input_buffer; }; struct _QTWrapperAudioDecoderClass { GstElementClass parent_class; /* fourcc of the format */ guint32 componentSubType; GstPadTemplate *sinktempl; }; typedef struct _QTWrapperAudioDecoderParams QTWrapperAudioDecoderParams; struct _QTWrapperAudioDecoderParams { Component component; GstCaps *sinkcaps; }; static gboolean qtwrapper_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps); static GstFlowReturn qtwrapper_audio_decoder_chain (GstPad * pad, GstBuffer * buf); static gboolean qtwrapper_audio_decoder_sink_event (GstPad * pad, GstEvent * event); static void qtwrapper_audio_decoder_init (QTWrapperAudioDecoder * qtwrapper) { QTWrapperAudioDecoderClass *oclass; oclass = (QTWrapperAudioDecoderClass *) (G_OBJECT_GET_CLASS (qtwrapper)); /* Sink pad */ qtwrapper->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink"); gst_pad_set_setcaps_function (qtwrapper->sinkpad, GST_DEBUG_FUNCPTR (qtwrapper_audio_decoder_sink_setcaps)); gst_pad_set_chain_function (qtwrapper->sinkpad, GST_DEBUG_FUNCPTR (qtwrapper_audio_decoder_chain)); gst_pad_set_event_function (qtwrapper->sinkpad, GST_DEBUG_FUNCPTR (qtwrapper_audio_decoder_sink_event)); gst_element_add_pad (GST_ELEMENT (qtwrapper), qtwrapper->sinkpad); /* Source pad */ qtwrapper->srcpad = gst_pad_new_from_static_template (&src_templ, "src"); gst_element_add_pad (GST_ELEMENT (qtwrapper), qtwrapper->srcpad); } static void clear_AudioStreamBasicDescription (AudioStreamBasicDescription * desc) { desc->mSampleRate = 0; desc->mFormatID = 0; desc->mFormatFlags = 0; desc->mBytesPerPacket = 0; desc->mFramesPerPacket = 0; desc->mBytesPerFrame = 0; desc->mChannelsPerFrame = 0; desc->mBitsPerChannel = 0; desc->mReserved = 0; } static void fill_indesc_mp3 (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc, gint rate, gint channels) { GST_INFO_OBJECT (qtwrapper, "Filling input description for MP3 data"); clear_AudioStreamBasicDescription (&qtwrapper->indesc); /* only the samplerate is needed apparently */ qtwrapper->indesc.mSampleRate = (double) rate; qtwrapper->indesc.mFormatID = kAudioFormatMPEGLayer3; qtwrapper->indesc.mChannelsPerFrame = channels; } static void fill_indesc_aac (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc, gint rate, gint channels) { clear_AudioStreamBasicDescription (&qtwrapper->indesc); qtwrapper->indesc.mSampleRate = (double) rate; qtwrapper->indesc.mFormatID = kAudioFormatMPEG4AAC; /* aac always has 1024 frames per packet */ qtwrapper->indesc.mFramesPerPacket = 1024; qtwrapper->indesc.mChannelsPerFrame = channels; } static void fill_indesc_samr (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc, gint channels) { clear_AudioStreamBasicDescription (&qtwrapper->indesc); qtwrapper->indesc.mSampleRate = 8000; qtwrapper->indesc.mFormatID = fourcc; qtwrapper->indesc.mChannelsPerFrame = 1; qtwrapper->indesc.mFramesPerPacket = 160; } static void fill_indesc_generic (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc, gint rate, gint channels) { clear_AudioStreamBasicDescription (&qtwrapper->indesc); qtwrapper->indesc.mSampleRate = rate; qtwrapper->indesc.mFormatID = fourcc; qtwrapper->indesc.mChannelsPerFrame = channels; } static void fill_indesc_alac (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc, gint rate, gint channels) { clear_AudioStreamBasicDescription (&qtwrapper->indesc); qtwrapper->indesc.mSampleRate = rate; qtwrapper->indesc.mFormatID = fourcc; qtwrapper->indesc.mChannelsPerFrame = channels; // This has to be set, but the particular value doesn't seem to matter much qtwrapper->indesc.mFramesPerPacket = 4096; } static gpointer make_alac_magic_cookie (GstBuffer * codec_data, gsize * len) { guint8 *res; if (GST_BUFFER_SIZE (codec_data) < 4) return NULL; *len = 20 + GST_BUFFER_SIZE (codec_data); res = g_malloc0 (*len); /* 12 first bytes are 'frma' (format) atom with 'alac' value */ GST_WRITE_UINT32_BE (res, 0xc); /* Atom length: 12 bytes */ GST_WRITE_UINT32_LE (res + 4, QT_MAKE_FOURCC_BE ('f', 'r', 'm', 'a')); GST_WRITE_UINT32_LE (res + 8, QT_MAKE_FOURCC_BE ('a', 'l', 'a', 'c')); /* Write the codec_data, but with the first four bytes reversed (different endianness). This is the 'alac' atom. */ GST_WRITE_UINT32_BE (res + 12, GST_READ_UINT32_LE (GST_BUFFER_DATA (codec_data))); memcpy (res + 16, GST_BUFFER_DATA (codec_data) + 4, GST_BUFFER_SIZE (codec_data) - 4); /* Terminator atom */ GST_WRITE_UINT32_BE (res + 12 + GST_BUFFER_SIZE (codec_data), 8); GST_WRITE_UINT32_BE (res + 12 + GST_BUFFER_SIZE (codec_data) + 4, 0); return res; } static gpointer make_samr_magic_cookie (GstBuffer * codec_data, gsize * len) { guint8 *res; *len = 48; res = g_malloc0 (0x30); /* 12 first bytes are 'frma' (format) atom with 'samr' value */ GST_WRITE_UINT32_BE (res, 0xc); GST_WRITE_UINT32_LE (res + 4, QT_MAKE_FOURCC_BE ('f', 'r', 'm', 'a')); GST_WRITE_UINT32_LE (res + 8, QT_MAKE_FOURCC_BE ('s', 'a', 'm', 'r')); /* 10 bytes for 'enda' atom with 0 */ GST_WRITE_UINT32_BE (res + 12, 10); GST_WRITE_UINT32_LE (res + 16, QT_MAKE_FOURCC_BE ('e', 'n', 'd', 'a')); /* 17(+1) bytes for the codec_data contents */ GST_WRITE_UINT32_BE (res + 22, 18); memcpy (res + 26, GST_BUFFER_DATA (codec_data) + 4, 17); /* yes... we need to replace 'damr' by 'samr'. Blame Apple ! */ GST_WRITE_UINT8 (res + 26, 's'); /* Terminator atom */ GST_WRITE_UINT32_BE (res + 40, 8); #if DEBUG_DUMP gst_util_dump_mem (res, 48); #endif return res; } static int write_len (guint8 * buf, int val) { /* This is some sort of variable-length coding, but the quicktime * file(s) I have here all just use a 4-byte version, so we'll do that. * Return the number of bytes written; */ buf[0] = ((val >> 21) & 0x7f) | 0x80; buf[1] = ((val >> 14) & 0x7f) | 0x80; buf[2] = ((val >> 7) & 0x7f) | 0x80; buf[3] = ((val >> 0) & 0x7f); return 4; } static void aac_parse_codec_data (GstBuffer * codec_data, gint * channels) { guint8 *data = GST_BUFFER_DATA (codec_data); guint codec_channels; if (GST_BUFFER_SIZE (codec_data) < 2) { GST_WARNING ("Cannot parse codec_data for channel count"); return; } codec_channels = (data[1] & 0x7f) >> 3; if (*channels != codec_channels) { GST_INFO ("Overwriting channels %d with %d", *channels, codec_channels); *channels = (gint) codec_channels; } else { GST_INFO ("Retaining channel count %d", codec_channels); } } /* The AAC decoder requires the entire mpeg4 audio elementary stream * descriptor, which is the body (except the 4-byte version field) of * the quicktime 'esds' atom. However, qtdemux only passes through the * (two byte, normally) payload, so we need to reconstruct the ESD */ /* TODO: Get the AAC spec, and verify this implementation */ static gpointer make_aac_magic_cookie (GstBuffer * codec_data, gsize * len) { guint8 *cookie; int offset = 0; int decoder_specific_len = GST_BUFFER_SIZE (codec_data); int config_len = 13 + 5 + decoder_specific_len; int es_len = 3 + 5 + config_len + 5 + 1; int total_len = es_len + 5; cookie = g_malloc0 (total_len); *len = total_len; /* Structured something like this: * [ES Descriptor * [Config Descriptor * [Specific Descriptor]] * [Unknown]] */ QT_WRITE_UINT8 (cookie + offset, 0x03); offset += 1; /* ES Descriptor tag */ offset += write_len (cookie + offset, es_len); QT_WRITE_UINT16 (cookie + offset, 0); offset += 2; /* Track ID */ QT_WRITE_UINT8 (cookie + offset, 0); offset += 1; /* Flags */ QT_WRITE_UINT8 (cookie + offset, 0x04); offset += 1; /* Config Descriptor tag */ offset += write_len (cookie + offset, config_len); /* TODO: Fix these up */ QT_WRITE_UINT8 (cookie + offset, 0x40); offset += 1; /* object_type_id */ QT_WRITE_UINT8 (cookie + offset, 0x15); offset += 1; /* stream_type */ QT_WRITE_UINT24 (cookie + offset, 0x1800); offset += 3; /* buffer_size_db */ QT_WRITE_UINT32 (cookie + offset, 128000); offset += 4; /* max_bitrate */ QT_WRITE_UINT32 (cookie + offset, 128000); offset += 4; /* avg_bitrate */ QT_WRITE_UINT8 (cookie + offset, 0x05); offset += 1; /* Specific Descriptor tag */ offset += write_len (cookie + offset, decoder_specific_len); memcpy (cookie + offset, GST_BUFFER_DATA (codec_data), decoder_specific_len); offset += decoder_specific_len; /* TODO: What is this? 'SL descriptor' apparently, but what does that mean? */ QT_WRITE_UINT8 (cookie + offset, 0x06); offset += 1; /* SL Descriptor tag */ offset += write_len (cookie + offset, 1); QT_WRITE_UINT8 (cookie + offset, 2); offset += 1; return cookie; } static void close_decoder (QTWrapperAudioDecoder * qtwrapper) { if (qtwrapper->adec) { CloseComponent (qtwrapper->adec); qtwrapper->adec = NULL; } if (qtwrapper->bufferlist) { DestroyAudioBufferList (qtwrapper->bufferlist); qtwrapper->bufferlist = NULL; } } static gboolean open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, GstCaps ** othercaps) { gboolean ret = FALSE; QTWrapperAudioDecoderClass *oclass; /* TODO: these will be used as the output rate/channels for formats that * don't supply these in the caps. This isn't very nice! */ gint channels = 2; gint rate = 44100; OSStatus status; GstStructure *s; gchar *tmp; const GValue *value; GstBuffer *codec_data = NULL; gboolean have_esds = FALSE; /* Clean up any existing decoder */ close_decoder (qtwrapper); tmp = gst_caps_to_string (caps); GST_LOG_OBJECT (qtwrapper, "caps: %s", tmp); g_free (tmp); /* extract rate/channels information from the caps */ s = gst_caps_get_structure (caps, 0); gst_structure_get_int (s, "rate", &rate); gst_structure_get_int (s, "channels", &channels); /* get codec_data */ if ((value = gst_structure_get_value (s, "codec_data"))) { codec_data = GST_BUFFER_CAST (gst_value_get_mini_object (value)); } oclass = (QTWrapperAudioDecoderClass *) (G_OBJECT_GET_CLASS (qtwrapper)); if (codec_data && oclass->componentSubType == QT_MAKE_FOURCC_LE ('m', 'p', '4', 'a')) { /* QuickTime/iTunes creates AAC files with the wrong channel count in the header, so parse that out of the codec data if we can. */ aac_parse_codec_data (codec_data, &channels); } /* If the quicktime demuxer gives us a full esds atom, use that instead of * the codec_data */ if ((value = gst_structure_get_value (s, "quicktime_esds"))) { have_esds = TRUE; codec_data = GST_BUFFER_CAST (gst_value_get_mini_object (value)); } #if DEBUG_DUMP if (codec_data) gst_util_dump_mem (GST_BUFFER_DATA (codec_data), GST_BUFFER_SIZE (codec_data)); #endif GST_INFO_OBJECT (qtwrapper, "rate:%d, channels:%d", rate, channels); GST_INFO_OBJECT (qtwrapper, "componentSubType is %" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (oclass->componentSubType)); /* Setup the input format description, some format require special handling */ switch (oclass->componentSubType) { case QT_MAKE_FOURCC_LE ('.', 'm', 'p', '3'): fill_indesc_mp3 (qtwrapper, oclass->componentSubType, rate, channels); break; case QT_MAKE_FOURCC_LE ('m', 'p', '4', 'a'): fill_indesc_aac (qtwrapper, oclass->componentSubType, rate, channels); break; case QT_MAKE_FOURCC_LE ('s', 'a', 'm', 'r'): fill_indesc_samr (qtwrapper, oclass->componentSubType, channels); rate = 8000; break; case QT_MAKE_FOURCC_LE ('a', 'l', 'a', 'c'): fill_indesc_alac (qtwrapper, oclass->componentSubType, rate, channels); break; default: fill_indesc_generic (qtwrapper, oclass->componentSubType, rate, channels); break; } #if DEBUG_DUMP gst_util_dump_mem ((gpointer) & qtwrapper->indesc, sizeof (AudioStreamBasicDescription)); #endif qtwrapper->samplerate = rate; qtwrapper->channels = channels; /* Create an instance of SCAudio */ status = OpenADefaultComponent (StandardCompressionType, StandardCompressionSubTypeAudio, &qtwrapper->adec); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error instantiating SCAudio component: %ld", status); qtwrapper->adec = NULL; goto beach; } /* This is necessary to make setting the InputBasicDescription succeed; without it SCAudio only accepts PCM as input. Presumably a bug in QuickTime. Thanks to Arek for figuring this one out! */ { QTAtomContainer audiosettings = NULL; SCGetSettingsAsAtomContainer (qtwrapper->adec, &audiosettings); SCSetSettingsFromAtomContainer (qtwrapper->adec, audiosettings); /* TODO: Figure out if disposing of the QTAtomContainer is needed here */ } /* Set the input description info on the SCAudio instance */ status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_InputBasicDescription, sizeof (qtwrapper->indesc), &qtwrapper->indesc); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error setting input description on SCAudio: %ld", status); GST_ELEMENT_ERROR (qtwrapper, STREAM, NOT_IMPLEMENTED, ("A QuickTime error occurred trying to decode this stream"), ("QuickTime returned error status %lx", status)); goto beach; } /* TODO: we can select a channel layout here, figure out if we want to */ /* if we have codec_data, give it to the converter ! */ if (codec_data) { gsize len = 0; gpointer magiccookie; switch (oclass->componentSubType) { /* Some decoders want the 'magic cookie' in a different format from how * gstreamer represents it. So, convert... */ case QT_MAKE_FOURCC_LE ('s', 'a', 'm', 'r'): magiccookie = make_samr_magic_cookie (codec_data, &len); break; case QT_MAKE_FOURCC_LE ('a', 'l', 'a', 'c'): magiccookie = make_alac_magic_cookie (codec_data, &len); break; case QT_MAKE_FOURCC_LE ('m', 'p', '4', 'a'): if (!have_esds) { magiccookie = make_aac_magic_cookie (codec_data, &len); break; } /* Else: fallthrough */ default: len = GST_BUFFER_SIZE (codec_data); magiccookie = GST_BUFFER_DATA (codec_data); break; } if (magiccookie) { GST_LOG_OBJECT (qtwrapper, "Setting magic cookie %p of size %" G_GSIZE_FORMAT, magiccookie, len); #if DEBUG_DUMP gst_util_dump_mem (magiccookie, len); #endif status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_InputMagicCookie, len, magiccookie); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error setting extra codec data: %ld", status); goto beach; } g_free (magiccookie); } } /* Set output to be interleaved raw PCM */ { OSType outputFormat = kAudioFormatLinearPCM; SCAudioFormatFlagsRestrictions restrictions = { 0 }; /* Set the mask in order to set this flag to zero */ restrictions.formatFlagsMask = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian; restrictions.formatFlagsValues = kAudioFormatFlagIsFloat; status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_ClientRestrictedLPCMFlags, sizeof (restrictions), &restrictions); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error setting PCM to interleaved: %ld", status); goto beach; } status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_ClientRestrictedCompressionFormatList, sizeof (outputFormat), &outputFormat); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error setting output to PCM: %ld", status); goto beach; } } qtwrapper->outdesc.mSampleRate = 0; /* Use recommended; we read this out later */ qtwrapper->outdesc.mFormatID = kAudioFormatLinearPCM; qtwrapper->outdesc.mFormatFlags = kAudioFormatFlagIsFloat; qtwrapper->outdesc.mBytesPerPacket = 0; qtwrapper->outdesc.mFramesPerPacket = 0; qtwrapper->outdesc.mBytesPerFrame = 4 * channels; qtwrapper->outdesc.mChannelsPerFrame = channels; qtwrapper->outdesc.mBitsPerChannel = 32; qtwrapper->outdesc.mReserved = 0; status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_BasicDescription, sizeof (qtwrapper->outdesc), &qtwrapper->outdesc); if (status) { GST_WARNING_OBJECT (qtwrapper, "Error setting output description: %ld", status); goto beach; } status = QTGetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_BasicDescription, sizeof (qtwrapper->outdesc), &qtwrapper->outdesc, NULL); if (status) { GST_WARNING_OBJECT (qtwrapper, "Failed to get output audio description: %ld", status); ret = FALSE; goto beach; } if (qtwrapper->outdesc.mFormatID != kAudioFormatLinearPCM /*|| (qtwrapper->outdesc.mFormatFlags & kAudioFormatFlagIsFloat) != kAudioFormatFlagIsFloat */ ) { GST_WARNING_OBJECT (qtwrapper, "Output is not floating point PCM"); ret = FALSE; goto beach; } qtwrapper->samplerate = (int) qtwrapper->outdesc.mSampleRate; qtwrapper->channels = qtwrapper->outdesc.mChannelsPerFrame; GST_DEBUG_OBJECT (qtwrapper, "Output is %d Hz, %d channels", qtwrapper->samplerate, qtwrapper->channels); /* Create output bufferlist, big enough for 200ms of audio */ GST_DEBUG_OBJECT (qtwrapper, "Allocating bufferlist for %d channels", channels); qtwrapper->bufferlist = AllocateAudioBufferList (channels, qtwrapper->samplerate / 5 * qtwrapper->channels * 4); /* Create output caps matching the format the component is giving us */ *othercaps = gst_caps_new_simple ("audio/x-raw-float", "endianness", G_TYPE_INT, G_BYTE_ORDER, "signed", G_TYPE_BOOLEAN, TRUE, "width", G_TYPE_INT, 32, "depth", G_TYPE_INT, 32, "rate", G_TYPE_INT, qtwrapper->samplerate, "channels", G_TYPE_INT, qtwrapper->channels, NULL); ret = TRUE; beach: return ret; } static gboolean qtwrapper_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) { QTWrapperAudioDecoder *qtwrapper; gboolean ret = FALSE; GstCaps *othercaps = NULL; qtwrapper = (QTWrapperAudioDecoder *) gst_pad_get_parent (pad); GST_LOG_OBJECT (qtwrapper, "caps:%" GST_PTR_FORMAT, caps); /* 1. open decoder */ if (!(open_decoder (qtwrapper, caps, &othercaps))) goto beach; /* 2. set caps downstream */ ret = gst_pad_set_caps (qtwrapper->srcpad, othercaps); beach: if (othercaps) gst_caps_unref (othercaps); gst_object_unref (qtwrapper); return ret; } static OSStatus process_buffer_cb (ComponentInstance inAudioConverter, UInt32 * ioNumberDataPackets, AudioBufferList * ioData, AudioStreamPacketDescription ** outDataPacketDescription, QTWrapperAudioDecoder * qtwrapper) { GST_LOG_OBJECT (qtwrapper, "ioNumberDataPackets:%lu, iodata:%p, outDataPacketDescription:%p", *ioNumberDataPackets, ioData, outDataPacketDescription); if (outDataPacketDescription) GST_LOG ("*outDataPacketDescription:%p", *outDataPacketDescription); GST_LOG ("mNumberBuffers : %u", (guint32) ioData->mNumberBuffers); GST_LOG ("mData:%p , mDataByteSize:%u", ioData->mBuffers[0].mData, (guint32) ioData->mBuffers[0].mDataByteSize); ioData->mBuffers[0].mData = NULL; ioData->mBuffers[0].mDataByteSize = 0; *ioNumberDataPackets = 1; if (qtwrapper->input_buffer && GST_BUFFER_SIZE (qtwrapper->input_buffer)) { ioData->mBuffers[0].mData = GST_BUFFER_DATA (qtwrapper->input_buffer); ioData->mBuffers[0].mDataByteSize = GST_BUFFER_SIZE (qtwrapper->input_buffer); /* if we have a valid outDataPacketDescription, we need to fill it */ if (outDataPacketDescription) { qtwrapper->aspd[0].mStartOffset = 0; qtwrapper->aspd[0].mVariableFramesInPacket = 0; qtwrapper->aspd[0].mDataByteSize = GST_BUFFER_SIZE (qtwrapper->input_buffer); *outDataPacketDescription = qtwrapper->aspd; } GST_LOG_OBJECT (qtwrapper, "returning %d bytes at %p", GST_BUFFER_SIZE (qtwrapper->input_buffer), ioData->mBuffers[0].mData); qtwrapper->input_buffer = 0; return noErr; } GST_LOG_OBJECT (qtwrapper, "No remaining input data, returning NO_MORE_INPUT_DATA"); return NO_MORE_INPUT_DATA; } static GstFlowReturn qtwrapper_audio_decoder_chain (GstPad * pad, GstBuffer * buf) { GstFlowReturn ret = GST_FLOW_OK; QTWrapperAudioDecoder *qtwrapper; GstBuffer *outbuf; OSStatus status; guint32 outsamples; guint32 savedbytes; guint32 realbytes; qtwrapper = (QTWrapperAudioDecoder *) gst_pad_get_parent (pad); if (!qtwrapper->adec) { GST_WARNING_OBJECT (qtwrapper, "QTWrapper not initialised"); goto beach; } GST_LOG_OBJECT (qtwrapper, "buffer:%p , timestamp:%" GST_TIME_FORMAT " ,size:%d", buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)), GST_BUFFER_SIZE (buf)); #if DEBUG_DUMP gst_util_dump_mem (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); #endif if (qtwrapper->gotnewsegment) { GST_DEBUG_OBJECT (qtwrapper, "SCAudioReset()"); SCAudioReset (qtwrapper->adec); /* some formats can give us a better initial time using the buffer * timestamp. */ if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buf))) qtwrapper->initial_time = GST_BUFFER_TIMESTAMP (buf); qtwrapper->gotnewsegment = FALSE; } outsamples = qtwrapper->bufferlist->mBuffers[0].mDataByteSize / 8; savedbytes = qtwrapper->bufferlist->mBuffers[0].mDataByteSize; qtwrapper->input_buffer = buf; do { GST_LOG_OBJECT (qtwrapper, "Calling SCAudioFillBuffer(outsamples:%d , outdata:%p)", outsamples, qtwrapper->bufferlist->mBuffers[0].mData); /* Ask SCAudio to give us data ! */ status = SCAudioFillBuffer (qtwrapper->adec, (SCAudioInputDataProc) process_buffer_cb, qtwrapper, (UInt32 *) & outsamples, qtwrapper->bufferlist, NULL); if ((status != noErr) && (status != NO_MORE_INPUT_DATA)) { if (status < 0) GST_WARNING_OBJECT (qtwrapper, "Error in SCAudioFillBuffer() : %d", (gint32) status); else GST_WARNING_OBJECT (qtwrapper, "Error in SCAudioFillBuffer() : %" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (status)); ret = GST_FLOW_ERROR; goto beach; } realbytes = qtwrapper->bufferlist->mBuffers[0].mDataByteSize; GST_LOG_OBJECT (qtwrapper, "We now have %d samples [%d bytes]", outsamples, realbytes); qtwrapper->bufferlist->mBuffers[0].mDataByteSize = savedbytes; if (!outsamples) goto beach; /* 4. Create buffer and copy data in it */ ret = gst_pad_alloc_buffer (qtwrapper->srcpad, qtwrapper->cur_offset, realbytes, GST_PAD_CAPS (qtwrapper->srcpad), &outbuf); if (ret != GST_FLOW_OK) goto beach; /* copy data from bufferlist to output buffer */ g_memmove (GST_BUFFER_DATA (outbuf), qtwrapper->bufferlist->mBuffers[0].mData, realbytes); /* 5. calculate timestamp and duration */ GST_BUFFER_TIMESTAMP (outbuf) = qtwrapper->initial_time + gst_util_uint64_scale_int (GST_SECOND, (gint) qtwrapper->cur_offset, qtwrapper->samplerate); GST_BUFFER_SIZE (outbuf) = realbytes; GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (GST_SECOND, realbytes / (qtwrapper->channels * 4), qtwrapper->samplerate); GST_LOG_OBJECT (qtwrapper, "timestamp:%" GST_TIME_FORMAT ", duration:%" GST_TIME_FORMAT "offset:%lld, offset_end:%lld", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf)); qtwrapper->cur_offset += outsamples; /* 6. push buffer downstream */ ret = gst_pad_push (qtwrapper->srcpad, outbuf); if (ret != GST_FLOW_OK) goto beach; GST_DEBUG_OBJECT (qtwrapper, "Read %d bytes, could have read up to %d bytes", realbytes, savedbytes); } while (status != NO_MORE_INPUT_DATA); beach: gst_buffer_unref (buf); gst_object_unref (qtwrapper); return ret; } static gboolean qtwrapper_audio_decoder_sink_event (GstPad * pad, GstEvent * event) { QTWrapperAudioDecoder *qtwrapper; gboolean ret = FALSE; qtwrapper = (QTWrapperAudioDecoder *) gst_pad_get_parent (pad); GST_LOG_OBJECT (qtwrapper, "event:%s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { /* TODO: Flush events should reset the decoder component */ case GST_EVENT_NEWSEGMENT:{ gint64 start, stop, position; gboolean update; gdouble rate; GstFormat format; GST_LOG ("We've got a newsegment"); gst_event_parse_new_segment (event, &update, &rate, &format, &start, &stop, &position); /* if the format isn't time, we need to create a new time newsegment */ /* FIXME : This is really bad, we should convert the values properly to time */ if (format != GST_FORMAT_TIME) { GstEvent *newevent; GST_WARNING_OBJECT (qtwrapper, "Original event wasn't in GST_FORMAT_TIME, creating new fake one."); start = 0; newevent = gst_event_new_new_segment (update, rate, GST_FORMAT_TIME, start, GST_CLOCK_TIME_NONE, start); gst_event_unref (event); event = newevent; } qtwrapper->initial_time = start; qtwrapper->cur_offset = 0; GST_LOG ("initial_time is now %" GST_TIME_FORMAT, GST_TIME_ARGS (start)); if (qtwrapper->adec) qtwrapper->gotnewsegment = TRUE; break; } default: break; } ret = gst_pad_push_event (qtwrapper->srcpad, event); gst_object_unref (qtwrapper); return TRUE; } static void qtwrapper_audio_decoder_base_init (QTWrapperAudioDecoderClass * klass) { GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gchar *name = NULL; gchar *info = NULL; char *longname, *description; ComponentDescription desc; QTWrapperAudioDecoderParams *params; params = (QTWrapperAudioDecoderParams *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (klass), QTWRAPPER_ADEC_PARAMS_QDATA); g_assert (params); get_name_info_from_component (params->component, &desc, &name, &info); /* Fill in details */ longname = g_strdup_printf ("QTWrapper SCAudio Audio Decoder : %s", GST_STR_NULL (name)); description = g_strdup_printf ("QTWrapper SCAudio wrapper for decoder: %s", GST_STR_NULL (info)); gst_element_class_set_details_simple (element_class, longname, "Codec/Decoder/Audio", description, "Fluendo , " "Pioneers of the Inevitable "); g_free (longname); g_free (description); g_free (name); g_free (info); /* Add pad templates */ klass->sinktempl = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, params->sinkcaps); gst_element_class_add_pad_template (element_class, klass->sinktempl); gst_element_class_add_static_pad_template (element_class, &src_templ); /* Store class-global values */ klass->componentSubType = desc.componentSubType; } static void qtwrapper_audio_decoder_dispose (GObject * object) { QTWrapperAudioDecoder *qtwrapper = (QTWrapperAudioDecoder *) object; QTWrapperAudioDecoderClass *oclass = (QTWrapperAudioDecoderClass *) (G_OBJECT_GET_CLASS (qtwrapper)); GObjectClass *parent_class = g_type_class_peek_parent (oclass); close_decoder (qtwrapper); G_OBJECT_CLASS (parent_class)->dispose (object); } static void qtwrapper_audio_decoder_class_init (QTWrapperAudioDecoderClass * klass) { GObjectClass *object_class; object_class = (GObjectClass *) klass; object_class->dispose = qtwrapper_audio_decoder_dispose; } gboolean qtwrapper_audio_decoders_register (GstPlugin * plugin) { gboolean res = TRUE; Component componentID = NULL; ComponentDescription desc = { kSoundDecompressor, 0, 0, 0, 0 }; GTypeInfo typeinfo = { sizeof (QTWrapperAudioDecoderClass), (GBaseInitFunc) qtwrapper_audio_decoder_base_init, NULL, (GClassInitFunc) qtwrapper_audio_decoder_class_init, NULL, NULL, sizeof (QTWrapperAudioDecoder), 0, (GInstanceInitFunc) qtwrapper_audio_decoder_init, }; /* Find all SoundDecompressors ! */ GST_DEBUG ("There are %ld decompressors available", CountComponents (&desc)); /* loop over SoundDecompressors */ do { componentID = FindNextComponent (componentID, &desc); GST_LOG ("componentID : %p", componentID); if (componentID) { ComponentDescription thisdesc; gchar *name = NULL, *info = NULL; GstCaps *caps = NULL; gchar *type_name = NULL; GType type; QTWrapperAudioDecoderParams *params = NULL; if (!(get_name_info_from_component (componentID, &thisdesc, &name, &info))) goto next; GST_LOG (" name:%s", GST_STR_NULL (name)); GST_LOG (" info:%s", GST_STR_NULL (info)); GST_LOG (" type:%" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (thisdesc.componentType)); GST_LOG (" subtype:%" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (thisdesc.componentSubType)); GST_LOG (" manufacturer:%" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (thisdesc.componentManufacturer)); if (!(caps = fourcc_to_caps (QT_READ_UINT32 (&thisdesc.componentSubType)))) goto next; type_name = g_strdup_printf ("qtwrapperaudiodec_%" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (thisdesc.componentSubType)); g_strdelimit (type_name, " .", '_'); if (g_type_from_name (type_name)) { GST_WARNING ("We already have a registered plugin for %s", type_name); goto next; } params = g_new0 (QTWrapperAudioDecoderParams, 1); params->component = componentID; params->sinkcaps = gst_caps_ref (caps); type = g_type_register_static (GST_TYPE_ELEMENT, type_name, &typeinfo, 0); /* Store params in type qdata */ g_type_set_qdata (type, QTWRAPPER_ADEC_PARAMS_QDATA, (gpointer) params); /* register type */ if (!gst_element_register (plugin, type_name, GST_RANK_MARGINAL, type)) { g_warning ("Failed to register %s", type_name);; g_type_set_qdata (type, QTWRAPPER_ADEC_PARAMS_QDATA, NULL); g_free (params); res = FALSE; goto next; } next: if (name) g_free (name); if (info) g_free (info); if (type_name) g_free (type_name); if (caps) gst_caps_unref (caps); } } while (componentID && res); return res; }