blob: 3aaa37711de42b4f0f651ad8c78ac6a8d582ec9f [file] [log] [blame]
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include <QuickTime/QuickTime.h>
#include <AudioUnit/AudioUnit.h>
#include "WebMExportStructs.h"
#include "WebMExportVersions.h"
#include "WebMAudioStream.h"
#include "Raw_debug.h"
static void _printSoundDesc(SoundDescriptionV2Ptr sd)
{
if (sd == NULL)
{
dbg_printf("[WebM] SoundDesc NULL\n");
return;
}
dbg_printf("[WebM] SoundDesc format '%4.4s', version %d, revlevel %d, vendor '%4.4s',"
"[num channels %ld, bits per channel %ld, audio Samplereate %lf] "
"[ LPCM Frames %ld, const Bytes %ld, Format Flags%ld])\n",
(char *) &sd->dataFormat, sd->version, sd->revlevel, (char *) &sd->vendor,
sd->numAudioChannels, sd->constBitsPerChannel, sd->audioSampleRate,
sd->constLPCMFramesPerAudioPacket, sd->constBytesPerAudioPacket, sd->formatSpecificFlags);
}
static void _printAudioStreamDesc(AudioStreamBasicDescription *desc)
{
dbg_printf("[WebM] stream desc asbd = %f sampleRate per frame, %d Frames Per packet, %d bytes per frame\n"
" %d bytes per packet, %d Channels per frame\n",
desc->mSampleRate, desc->mFramesPerPacket, desc->mBytesPerFrame,
desc->mBytesPerPacket, desc->mChannelsPerFrame);
}
ComponentResult getInputBasicDescription(GenericStreamPtr as, AudioStreamBasicDescription *inFormat)
{
dbg_printf("[webm] enter getInputBasicDescription\n" );
ComponentResult err = noErr;
initMovieGetParams(&as->source);
dbg_printf("[webm] getInputBasicDescription InvokeMovieExportGetDataUPP\n" );
dbg_printDataParams(&as->source);
err = InvokeMovieExportGetDataUPP(as->source.refCon, &as->source.params, as->source.dataProc);
if (err) goto bail;
SoundDescriptionHandle sdh = NULL;
dbg_printf("[webm] getInputBasicDescription QTSoundDescriptionConvert\n" );
err = QTSoundDescriptionConvert(kQTSoundDescriptionKind_Movie_AnyVersion,
(SoundDescriptionHandle) as->source.params.desc,
kQTSoundDescriptionKind_Movie_Version2,
&sdh);
SoundDescriptionV2Ptr sd = (SoundDescriptionV2Ptr) * sdh;
_printSoundDesc(sd);
if (sd->dataFormat != kAudioFormatLinearPCM)
{
dbg_printf("[Webm] - compressed formats not supported\n");
err = paramErr;
goto bail;
}
inFormat->mSampleRate = sd->audioSampleRate;
inFormat->mFormatID = sd->dataFormat;
inFormat->mFormatFlags = sd->formatSpecificFlags;
inFormat->mFramesPerPacket = sd->constLPCMFramesPerAudioPacket;
inFormat->mBytesPerFrame = sd->constBytesPerAudioPacket;
inFormat->mChannelsPerFrame = sd->numAudioChannels;
inFormat->mBitsPerChannel = sd->constBitsPerChannel;
inFormat->mReserved = 0;
//calculated
inFormat->mBytesPerPacket = inFormat->mBytesPerFrame * inFormat->mFramesPerPacket;
bail:
if (sdh != NULL)
DisposeHandle((Handle) sdh);
_printAudioStreamDesc(inFormat);
return err;
}
ComponentResult initVorbisComponent(WebMExportGlobalsPtr globals, GenericStreamPtr as)
{
dbg_printf("[WebM] enter initVorbisComponent\n");
ComponentResult err = noErr;
if (globals->audioSettingsAtom == NULL)
getDefaultVorbisAtom(globals);
//This chunk initializes the Component instance that will be used for decompression : TODO put this in its own function
err = OpenADefaultComponent(StandardCompressionType, StandardCompressionSubTypeAudio, &as->aud.vorbisComponentInstance);
if (err) goto bail;
AudioStreamBasicDescription *inFormat = NULL;
inFormat = calloc(1, sizeof(AudioStreamBasicDescription));
if (inFormat == NULL) goto bail;
err = getInputBasicDescription(as, inFormat);
if (err) goto bail;
getInputBasicDescription(as, inFormat);
err = SCSetSettingsFromAtomContainer(as->aud.vorbisComponentInstance, globals->audioSettingsAtom);
if (err) goto bail;
err = QTGetComponentProperty(as->aud.vorbisComponentInstance, kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_BasicDescription,
sizeof(AudioStreamBasicDescription), &as->aud.asbd, NULL);
if (err) goto bail;
err = QTSetComponentProperty(as->aud.vorbisComponentInstance, kQTPropertyClass_SCAudio, kQTSCAudioPropertyID_InputBasicDescription,
sizeof(AudioStreamBasicDescription), inFormat);
bail:
if (inFormat != NULL)
free(inFormat);
dbg_printf("[WebM]initVorbisComponent return %d\n", err);
return err;
}
static ComponentResult
_fillBuffer_callBack(ComponentInstance ci, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription, void *inRefCon)
{
GenericStreamPtr as = (GenericStreamPtr) inRefCon;
StreamSource *source = &as->source;
MovieExportGetDataParams *params = &source->params;
ComponentResult err = noErr;
dbg_printf("[WebM] _fillBuffer_callBack(%ld), current samples = %d\n",
*ioNumberDataPackets, params->actualSampleCount);
const UInt32 packetsRequested = *ioNumberDataPackets;
*ioNumberDataPackets = 0;
if (source->eos)
return err;
if (params->actualSampleCount > packetsRequested)
{
//we already have samples
dbg_printDataParams(source);
//TODO : this error can be accounted for... it just seems to never happen.
dbg_printf("[webm] *******error: TODO Potential unconsumed samples\n");
}
else if (params->actualSampleCount == 0)
{
initMovieGetParams(source);
params->requestedSampleCount = packetsRequested;
err = InvokeMovieExportGetDataUPP(source->refCon, params, source->dataProc);
dbg_printDataParams(source);
if (err == eofErr)
{
//source->eos = true;
dbg_printf("[Webm] Audio stream complete eos\n");
return err;
}
if (err) return err;
dbg_printf("[Webm] Audio Time = %f\n", getTimeAsSeconds(source));
}
if (params->actualSampleCount == 0)
{
dbg_printf("[WebM] fillBufferCallBack no more samples\n");
ioData->mBuffers[0].mDataByteSize = 0;
ioData->mBuffers[0].mData = NULL;
source->eos = true;
dbg_printf("[Webm] audio stream complete - no samples \n");
}
else
{
dbg_printf("[WebM] fillBufferCallBack %d samples, %d dataSize\n",
params->actualSampleCount, params->dataSize);
ioData->mBuffers[0].mDataByteSize = params->dataSize;
ioData->mBuffers[0].mData = params->dataPtr;
source->time += params->durationPerSample * params->actualSampleCount;
*ioNumberDataPackets = params->actualSampleCount;
params->actualSampleCount = 0;
}
as->framesIn += *ioNumberDataPackets;
return err;
}
static void _initAudioBufferList(GenericStreamPtr as, AudioBufferList **audioBufferList, UInt32 ioPackets)
{
int i;
UInt32 maxBytesPerPacket = 4096;
ComponentResult err = noErr;
err = QTGetComponentProperty(as->aud.vorbisComponentInstance, kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_MaximumOutputPacketSize,
sizeof(maxBytesPerPacket), &maxBytesPerPacket, NULL);
if (err)
{
dbg_printf("[Webm] Error getting max Bytes per packet\n");
maxBytesPerPacket = 255 *255; //this should be roughly valid for ogg Vorbis
err = noErr;
}
UInt32 bufferListSize = offsetof(AudioBufferList, mBuffers[ioPackets]);
dbg_printf("[WebM]Calling InitAudioBufferList size %ld, each buffer being %lu\n", bufferListSize, maxBytesPerPacket);
*audioBufferList = (AudioBufferList *) malloc(bufferListSize);
(*audioBufferList)->mNumberBuffers = ioPackets;
UInt32 wantedSize = maxBytesPerPacket * ioPackets;
if (as->aud.buf.data == NULL || as->aud.buf.size != wantedSize)
{
as->aud.buf.data = realloc(as->aud.buf.data, wantedSize);
as->aud.buf.size = wantedSize;
as->aud.buf.offset = 0;
}
for (i = 0; i < ioPackets; i++)
{
(*audioBufferList)->mBuffers[i].mNumberChannels = as->aud.asbd.mChannelsPerFrame;
(*audioBufferList)->mBuffers[i].mDataByteSize = maxBytesPerPacket;
(*audioBufferList)->mBuffers[i].mData = (void *)((unsigned char *)as->aud.buf.data + maxBytesPerPacket * i);
}
}
ComponentResult compressAudio(GenericStreamPtr as)
{
ComponentResult err = noErr;
if (as->source.eos)
return noErr; //shouldn't be called here.
UInt32 ioPackets = 1; //I want to get only one packet at a put it in a simple block
AudioStreamPacketDescription *packetDesc = NULL;
packetDesc = (AudioStreamPacketDescription *)calloc(ioPackets, sizeof(AudioStreamPacketDescription));
AudioBufferList *audioBufferList = NULL;
_initAudioBufferList(as, &audioBufferList, ioPackets); //allocates memory
dbg_printf("[WebM] call SCAudioFillBuffer(%x,%x,%x,%x,%x, %x)\n", as->aud.vorbisComponentInstance, _fillBuffer_callBack,
(void *) as, &ioPackets,
audioBufferList, packetDesc);
err = SCAudioFillBuffer(as->aud.vorbisComponentInstance, _fillBuffer_callBack,
(void *) as, &ioPackets,
audioBufferList, packetDesc);
dbg_printf("[WebM] exit SCAudioFillBuffer %d packets, err = %d\n", ioPackets, err);
if (err == eofErr)
{
dbg_printf("[WebM] Total Frames in = %lld, Total Frames Out = %lld\n",
as->framesIn, as->framesOut);
if (ioPackets == 0)
as->source.eos = true;
err= noErr;
}
if (err) goto bail;
if (ioPackets > 0)
{
as->aud.buf.offset = 0;
int i = 0;
for (i = 0; i < ioPackets; i++)
{
dbg_printf("[WebM] packet is %ld bytes, %ld frames\n", packetDesc[i].mDataByteSize, packetDesc[i].mVariableFramesInPacket);
as->framesOut += packetDesc[i].mVariableFramesInPacket;
as->aud.buf.offset += packetDesc[i].mDataByteSize;
}
//add a Frame Buffer to the queue
if (as->aud.buf.offset >0)
{
UInt8* buf = malloc(as->aud.buf.offset);
UInt32 timeMs = as->framesOut * 1000 / as->aud.asbd.mSampleRate;
memcpy(buf, as->aud.buf.data, as->aud.buf.offset);
UInt16 frameType = KEY_FRAME + AUDIO_FRAME;
dbg_printf("[WebM] Output audio packet size %ld, time %lu\n", as->aud.buf.offset, timeMs);
addFrameToQueue(&as->frameQueue, buf,as->aud.buf.offset, timeMs, frameType, as->framesOut);
as->aud.buf.offset =0; //reset the buffer for writing
}
}
if (as->frameQueue.size == 0 && as->source.eos)
{
as->complete = true;
}
bail:
if (audioBufferList != NULL)
free(audioBufferList);
if (packetDesc != NULL)
free(packetDesc);
return err;
}
//The first three packets of ogg vorbis data are taken from the magic cookie.
enum
{
kCookieTypeVorbisHeader = 'vCtH',
kCookieTypeVorbisComments = 'vCt#',
kCookieTypeVorbisCodebooks = 'vCtC',
kCookieTypeVorbisFirstPageNo = 'vCtN'
};
struct CookieAtomHeader
{
long size;
long type;
unsigned char data[1];
};
typedef struct CookieAtomHeader CookieAtomHeader;
static void _oggLacing(UInt8 **ptr, UInt32 size)
{
long tmpLacing = size;
while (tmpLacing >= 0)
{
if (tmpLacing >= 255)
**ptr = 255;
else
{
UInt8 ui8tmp = tmpLacing;
**ptr = ui8tmp;
}
(*ptr) ++;
tmpLacing -= 255;
}
}
static void _dbg_printVorbisHeader(const UInt8 *ptr)
{
UInt32 vorbisVersion = *(UInt32 *)(&ptr[7]);
UInt8 audioChannels = *(UInt8 *)(&ptr[11]);
UInt32 audioSampleRate = *(UInt32 *)(&ptr[12]);
long bitrate_maximum = *(long *)(&ptr[16]);
long bitrate_nominal = *(long *)(&ptr[20]);
long bitrate_minimum = *(long *)(&ptr[24]);
UInt8 blockSize0 = ptr[28] >> 4;
UInt8 blockSize1 = ptr[28] & 0x0f;
dbg_printf("Vorbis header reads vers %ld channels %d sampleRate %ld\n"
"\t bitrates max %ld nominal %ld min %ld\n"
"\t blockSize_0 %d, blockSize_1 %d\n",
vorbisVersion, audioChannels, audioSampleRate,
bitrate_maximum, bitrate_nominal, bitrate_minimum,
blockSize0, blockSize1);
}
ComponentResult write_vorbisPrivateData(GenericStreamPtr as, UInt8 **buf, UInt32 *bufSize)
{
ComponentResult err = noErr;
void *magicCookie = NULL;
UInt32 cookieSize = 0;
dbg_printf("[WebM] Get Vorbis Private Data\n");
err = QTGetComponentPropertyInfo(as->aud.vorbisComponentInstance,
kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_MagicCookie,
NULL, &cookieSize, NULL);
if (err) return err;
dbg_printf("[WebM] Cookie Size %d\n", cookieSize);
magicCookie = calloc(1, cookieSize);
err = QTGetComponentProperty(as->aud.vorbisComponentInstance,
kQTPropertyClass_SCAudio,
kQTSCAudioPropertyID_MagicCookie,
cookieSize, magicCookie, NULL);
if (err) goto bail;
UInt8 *ptrheader = (UInt8 *) magicCookie;
UInt8 *cend = ptrheader + cookieSize;
CookieAtomHeader *aheader = (CookieAtomHeader *) ptrheader;
WebMBuffer header, header_vc, header_cb;
header.size = header_vc.size = header_cb.size = 0;
while (ptrheader < cend)
{
aheader = (CookieAtomHeader *) ptrheader;
ptrheader += EndianU32_BtoN(aheader->size);
if (ptrheader > cend || EndianU32_BtoN(aheader->size) <= 0)
break;
switch (EndianS32_BtoN(aheader->type))
{
case kCookieTypeVorbisHeader:
header.size = EndianS32_BtoN(aheader->size) - 2 * sizeof(long);
header.data = aheader->data;
break;
case kCookieTypeVorbisComments:
header_vc.size = EndianS32_BtoN(aheader->size) - 2 * sizeof(long);
header_vc.data = aheader->data;
break;
case kCookieTypeVorbisCodebooks:
header_cb.size = EndianS32_BtoN(aheader->size) - 2 * sizeof(long);
header_cb.data = aheader->data;
break;
default:
break;
}
}
if (header.size == 0 || header_vc.size == 0 || header_cb.size == 0)
{
err = paramErr;
goto bail;
}
//1 + header1 /255 + header2 /255 + idheader.len +
*bufSize = 1; //the first byte which is always 0x02
*bufSize += (header.size - 1) / 255 + 1; //the header size lacing
*bufSize += (header_vc.size - 1) / 255 + 1; //the comment size lacing
*bufSize += header.size + header_vc.size + header_cb.size; //the packets
dbg_printf("[WebM]Packet headers %d %d %d -- total buffer %d\n",
header.size, header_vc.size , header_cb.size, *bufSize);
*buf = malloc(*bufSize);
UInt8 *ptr = *buf;
*ptr = 0x02;
ptr ++;
//using ogg lacing write out the size of the first two packets
_oggLacing(&ptr, header.size);
_oggLacing(&ptr, header_vc.size);
_dbg_printVorbisHeader(header.data);
memcpy(ptr, header.data, header.size);
ptr += header.size;
memcpy(ptr, header_vc.data, header_vc.size);
ptr += header_vc.size;
memcpy(ptr, header_cb.data, header_cb.size);
bail:
if (magicCookie != NULL)
{
free(magicCookie);
magicCookie = NULL;
}
return err;
}
ComponentResult initAudioStream(GenericStreamPtr as)
{
as->aud.vorbisComponentInstance = NULL;
memset(&as->aud.asbd, 0, sizeof(AudioStreamBasicDescription));
as->framesOut = 0;
as->framesIn =0;
as->complete = false;
as->aud.buf.size =0;
as->aud.buf.offset=0;
as->aud.buf.data = NULL;
return noErr;
}