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.
This commit is contained in:
Michael Smith 2008-10-30 19:54:38 +00:00
parent aa67ed1d14
commit a1ed30d406
3 changed files with 199 additions and 72 deletions

View file

@ -1,3 +1,16 @@
2008-10-30 Michael Smith <msmith@songbirdnest.com>
* 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.
2008-10-30 Tim-Philipp Müller <tim.muller at collabora co uk> 2008-10-30 Tim-Philipp Müller <tim.muller at collabora co uk>
* configure.ac: * configure.ac:

View file

@ -210,6 +210,49 @@ fill_indesc_generic (QTWrapperAudioDecoder * qtwrapper, guint32 fourcc,
qtwrapper->indesc.mChannelsPerFrame = channels; 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 static gpointer
make_samr_magic_cookie (GstBuffer * codec_data, gsize * len) 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 ! */ /* yes... we need to replace 'damr' by 'samr'. Blame Apple ! */
GST_WRITE_UINT8 (res + 26, 's'); GST_WRITE_UINT8 (res + 26, 's');
/* padding 8 bytes */ /* Terminator atom */
GST_WRITE_UINT32_BE (res + 40, 8); GST_WRITE_UINT32_BE (res + 40, 8);
#if DEBUG_DUMP #if DEBUG_DUMP
@ -259,6 +302,27 @@ write_len (guint8 * buf, int val)
return 4; 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 /* The AAC decoder requires the entire mpeg4 audio elementary stream
* descriptor, which is the body (except the 4-byte version field) of * descriptor, which is the body (except the 4-byte version field) of
* the quicktime 'esds' atom. However, qtdemux only passes through the * 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)); 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 /* If the quicktime demuxer gives us a full esds atom, use that instead of
* the codec_data */ * the codec_data */
if ((value = gst_structure_get_value (s, "quicktime_esds"))) { 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); 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, GST_INFO_OBJECT (qtwrapper, "componentSubType is %" GST_FOURCC_FORMAT,
QT_FOURCC_ARGS (oclass->componentSubType)); QT_FOURCC_ARGS (oclass->componentSubType));
@ -391,6 +464,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
fill_indesc_samr (qtwrapper, oclass->componentSubType, channels); fill_indesc_samr (qtwrapper, oclass->componentSubType, channels);
rate = 8000; rate = 8000;
break; break;
case QT_MAKE_FOURCC_LE ('a', 'l', 'a', 'c'):
fill_indesc_alac (qtwrapper, oclass->componentSubType, rate, channels);
break;
default: default:
fill_indesc_generic (qtwrapper, oclass->componentSubType, rate, channels); fill_indesc_generic (qtwrapper, oclass->componentSubType, rate, channels);
break; break;
@ -433,6 +509,10 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
if (status) { if (status) {
GST_WARNING_OBJECT (qtwrapper, GST_WARNING_OBJECT (qtwrapper,
"Error setting input description on SCAudio: %ld", status); "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; goto beach;
} }
@ -450,6 +530,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
case QT_MAKE_FOURCC_LE ('s', 'a', 'm', 'r'): case QT_MAKE_FOURCC_LE ('s', 'a', 'm', 'r'):
magiccookie = make_samr_magic_cookie (codec_data, &len); magiccookie = make_samr_magic_cookie (codec_data, &len);
break; 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'): case QT_MAKE_FOURCC_LE ('m', 'p', '4', 'a'):
if (!have_esds) { if (!have_esds) {
magiccookie = make_aac_magic_cookie (codec_data, &len); magiccookie = make_aac_magic_cookie (codec_data, &len);
@ -462,6 +545,7 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
break; break;
} }
if (magiccookie) {
GST_LOG_OBJECT (qtwrapper, "Setting magic cookie %p of size %" GST_LOG_OBJECT (qtwrapper, "Setting magic cookie %p of size %"
G_GSIZE_FORMAT, magiccookie, len); G_GSIZE_FORMAT, magiccookie, len);
@ -469,7 +553,8 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
gst_util_dump_mem (magiccookie, len); gst_util_dump_mem (magiccookie, len);
#endif #endif
status = QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio, status =
QTSetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_InputMagicCookie, len, magiccookie); kQTSCAudioPropertyID_InputMagicCookie, len, magiccookie);
if (status) { if (status) {
GST_WARNING_OBJECT (qtwrapper, "Error setting extra codec data: %ld", GST_WARNING_OBJECT (qtwrapper, "Error setting extra codec data: %ld",
@ -477,6 +562,7 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
goto beach; goto beach;
} }
} }
}
/* Set output to be interleaved raw PCM */ /* Set output to be interleaved raw PCM */
{ {
@ -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, status = QTGetComponentProperty (qtwrapper->adec, kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_BasicDescription, kQTSCAudioPropertyID_BasicDescription,
sizeof (qtwrapper->outdesc), &qtwrapper->outdesc, NULL); sizeof (qtwrapper->outdesc), &qtwrapper->outdesc, NULL);
@ -518,9 +623,9 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
goto beach; goto beach;
} }
if (qtwrapper->outdesc.mFormatID != kAudioFormatLinearPCM || if (qtwrapper->outdesc.mFormatID != kAudioFormatLinearPCM /*||
(qtwrapper->outdesc.mFormatFlags & kAudioFormatFlagIsFloat) != (qtwrapper->outdesc.mFormatFlags & kAudioFormatFlagIsFloat) !=
kAudioFormatFlagIsFloat) { kAudioFormatFlagIsFloat */ ) {
GST_WARNING_OBJECT (qtwrapper, "Output is not floating point PCM"); GST_WARNING_OBJECT (qtwrapper, "Output is not floating point PCM");
ret = FALSE; ret = FALSE;
goto beach; goto beach;
@ -531,21 +636,21 @@ open_decoder (QTWrapperAudioDecoder * qtwrapper, GstCaps * caps,
GST_DEBUG_OBJECT (qtwrapper, "Output is %d Hz, %d channels", GST_DEBUG_OBJECT (qtwrapper, "Output is %d Hz, %d channels",
qtwrapper->samplerate, qtwrapper->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", GST_DEBUG_OBJECT (qtwrapper, "Allocating bufferlist for %d channels",
channels); channels);
qtwrapper->bufferlist = qtwrapper->bufferlist =
AllocateAudioBufferList (channels, 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 matching the format the component is giving us */
/* Create output caps */
*othercaps = gst_caps_new_simple ("audio/x-raw-float", *othercaps = gst_caps_new_simple ("audio/x-raw-float",
"endianness", G_TYPE_INT, G_BYTE_ORDER, "endianness", G_TYPE_INT, G_BYTE_ORDER,
"signed", G_TYPE_BOOLEAN, TRUE, "signed", G_TYPE_BOOLEAN, TRUE,
"width", G_TYPE_INT, 32, "width", G_TYPE_INT, 32,
"depth", 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; ret = TRUE;
@ -671,8 +776,10 @@ qtwrapper_audio_decoder_chain (GstPad * pad, GstBuffer * buf)
qtwrapper->input_buffer = buf; qtwrapper->input_buffer = buf;
GST_LOG_OBJECT (qtwrapper, "Calling FillBuffer(outsamples:%d , outdata:%p)", do {
outsamples, qtwrapper->bufferlist->mBuffers[0].mData); GST_LOG_OBJECT (qtwrapper,
"Calling SCAudioFillBuffer(outsamples:%d , outdata:%p)", outsamples,
qtwrapper->bufferlist->mBuffers[0].mData);
/* Ask SCAudio to give us data ! */ /* Ask SCAudio to give us data ! */
status = SCAudioFillBuffer (qtwrapper->adec, status = SCAudioFillBuffer (qtwrapper->adec,
@ -736,6 +843,10 @@ qtwrapper_audio_decoder_chain (GstPad * pad, GstBuffer * buf)
if (ret != GST_FLOW_OK) if (ret != GST_FLOW_OK)
goto beach; goto beach;
GST_DEBUG_OBJECT (qtwrapper,
"Read %d bytes, could have read up to %d bytes", realbytes, savedbytes);
} while (realbytes == savedbytes);
beach: beach:
gst_buffer_unref (buf); gst_buffer_unref (buf);
gst_object_unref (qtwrapper); gst_object_unref (qtwrapper);
@ -875,8 +986,6 @@ qtwrapper_audio_decoders_register (GstPlugin * plugin)
}; };
/* Find all SoundDecompressors ! */ /* Find all SoundDecompressors ! */
fprintf (stderr, "There are %ld decompressors available\n",
CountComponents (&desc));
GST_DEBUG ("There are %ld decompressors available", CountComponents (&desc)); GST_DEBUG ("There are %ld decompressors available", CountComponents (&desc));
/* loop over SoundDecompressors */ /* loop over SoundDecompressors */

View file

@ -42,7 +42,12 @@
* Boston, MA 02111-1307, USA. * Boston, MA 02111-1307, USA.
*/ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h> #include <string.h>
#include <glib.h>
#include "qtutils.h" #include "qtutils.h"