From a1ed30d406a02035b20a3deb2ddd1f67d811f726 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 30 Oct 2008 19:54:38 +0000 Subject: sys/qtwrapper/audiodecoders.c: Add ALAC support. Original commit message from CVS: * sys/qtwrapper/audiodecoders.c: Add ALAC support. Fix decode of mono AAC files created by itunes. Set output format correctly (don't ask quicktime to resample for us). Use a larger decode buffer to avoid problems with large ALAC packets. Fix decode to loop until we have all output data. * sys/qtwrapper/qtutils.c: Fix includes so we compile on more OSes. --- sys/qtwrapper/audiodecoders.c | 257 ++++++++++++++++++++++++++++++------------ sys/qtwrapper/qtutils.c | 5 + 2 files changed, 188 insertions(+), 74 deletions(-) (limited to 'sys/qtwrapper') diff --git a/sys/qtwrapper/audiodecoders.c b/sys/qtwrapper/audiodecoders.c index ed5c69ce..d1616443 100644 --- a/sys/qtwrapper/audiodecoders.c +++ b/sys/qtwrapper/audiodecoders.c @@ -210,6 +210,49 @@ fill_indesc_generic (QTWrapperAudioDecoder * qtwrapper, guint32 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) { @@ -234,7 +277,7 @@ make_samr_magic_cookie (GstBuffer * codec_data, gsize * len) /* yes... we need to replace 'damr' by 'samr'. Blame Apple ! */ GST_WRITE_UINT8 (res + 26, 's'); - /* padding 8 bytes */ + /* Terminator atom */ GST_WRITE_UINT32_BE (res + 40, 8); #if DEBUG_DUMP @@ -259,6 +302,27 @@ write_len (guint8 * buf, int val) return 4; } +static void +aac_parse_codec_data (GstBuffer * codec_data, guint * channels) +{ + guint8 *data = GST_BUFFER_DATA (codec_data); + int 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 = 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 @@ -360,6 +424,16 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, 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"))) { @@ -375,7 +449,6 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, GST_INFO_OBJECT (qtwrapper, "rate:%d, channels:%d", rate, channels); - oclass = (QTWrapperAudioDecoderClass *) (G_OBJECT_GET_CLASS (qtwrapper)); GST_INFO_OBJECT (qtwrapper, "componentSubType is %" GST_FOURCC_FORMAT, QT_FOURCC_ARGS (oclass->componentSubType)); @@ -391,6 +464,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, 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; @@ -433,6 +509,10 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, 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; } @@ -450,6 +530,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, 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); @@ -462,19 +545,22 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, break; } - GST_LOG_OBJECT (qtwrapper, "Setting magic cookie %p of size %" - G_GSIZE_FORMAT, magiccookie, len); + 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); + 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; + 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; + } } } @@ -507,6 +593,25 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, } } + 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); @@ -518,9 +623,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, goto beach; } - if (qtwrapper->outdesc.mFormatID != kAudioFormatLinearPCM || - (qtwrapper->outdesc.mFormatFlags & kAudioFormatFlagIsFloat) != - kAudioFormatFlagIsFloat) { + if (qtwrapper->outdesc.mFormatID != kAudioFormatLinearPCM /*|| + (qtwrapper->outdesc.mFormatFlags & kAudioFormatFlagIsFloat) != + kAudioFormatFlagIsFloat */ ) { GST_WARNING_OBJECT (qtwrapper, "Output is not floating point PCM"); ret = FALSE; goto beach; @@ -531,21 +636,21 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps, GST_DEBUG_OBJECT (qtwrapper, "Output is %d Hz, %d channels", qtwrapper->samplerate, qtwrapper->channels); - /* Create output bufferlist, big enough for 50ms of audio */ + /* 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 * qtwrapper->channels * 4 / 20); + qtwrapper->samplerate / 5 * qtwrapper->channels * 4); - /* TODO: Figure out how the output format is determined, can we pick this? */ - /* Create output caps */ + /* 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, rate, "channels", G_TYPE_INT, channels, NULL); + "rate", G_TYPE_INT, qtwrapper->samplerate, "channels", G_TYPE_INT, + qtwrapper->channels, NULL); ret = TRUE; @@ -671,70 +776,76 @@ qtwrapper_audio_decoder_chain (GstPad * pad, GstBuffer * buf) qtwrapper->input_buffer = buf; - GST_LOG_OBJECT (qtwrapper, "Calling FillBuffer(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); - - /* TODO: What's this '42' crap?? It does seem to be needed, though. */ - if ((status != noErr) && (status != 42)) { - 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; - } + 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); + + /* TODO: What's this '42' crap?? It does seem to be needed, though. */ + if ((status != noErr) && (status != 42)) { + 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; + realbytes = qtwrapper->bufferlist->mBuffers[0].mDataByteSize; - GST_LOG_OBJECT (qtwrapper, "We now have %d samples [%d bytes]", - outsamples, realbytes); + GST_LOG_OBJECT (qtwrapper, "We now have %d samples [%d bytes]", + outsamples, realbytes); - qtwrapper->bufferlist->mBuffers[0].mDataByteSize = savedbytes; + qtwrapper->bufferlist->mBuffers[0].mDataByteSize = savedbytes; - if (!outsamples) - goto beach; + 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; + /* 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); + /* 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); + /* 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)); + 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; + qtwrapper->cur_offset += outsamples; - /* 6. push buffer downstream */ + /* 6. push buffer downstream */ - ret = gst_pad_push (qtwrapper->srcpad, outbuf); - if (ret != GST_FLOW_OK) - goto beach; + 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 (realbytes == savedbytes); beach: gst_buffer_unref (buf); @@ -875,8 +986,6 @@ qtwrapper_audio_decoders_register (GstPlugin * plugin) }; /* Find all SoundDecompressors ! */ - fprintf (stderr, "There are %ld decompressors available\n", - CountComponents (&desc)); GST_DEBUG ("There are %ld decompressors available", CountComponents (&desc)); /* loop over SoundDecompressors */ diff --git a/sys/qtwrapper/qtutils.c b/sys/qtwrapper/qtutils.c index b698dc85..9b71bd06 100644 --- a/sys/qtwrapper/qtutils.c +++ b/sys/qtwrapper/qtutils.c @@ -42,7 +42,12 @@ * Boston, MA 02111-1307, USA. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include +#include #include "qtutils.h" -- cgit v1.2.1