UseAC-AF.cpp
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
AudioConverter and AudioFile Sample |
*/ |
/* |
This is a more complex version of the ConvertFile call - see UseExtAF first |
The version below shows you what is in the implementation of the ExtendedAudioFile as |
it uses the AudioConverter and AudioFile APIs directly. Its alot more code to write, but |
it is equivalent and will produce the same results. |
This is provided more for educational purposes as going forward we would recommend |
developers use the Extended Audio File API directly. |
*/ |
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) |
#include <AudioToolbox/AudioToolbox.h> |
#include <CoreFoundation/CoreFoundation.h> |
#else |
#include "AudioToolbox.h" |
#include "CoreFoundation.h" |
#endif |
#include "CAStreamBasicDescription.h" |
#include "CAXException.h" |
// a struct to hold info for the input data proc |
struct AudioFileIO |
{ |
AudioFileID afid; |
SInt64 pos; |
char * srcBuffer; |
UInt32 srcBufferSize; |
CAStreamBasicDescription srcFormat; |
UInt32 srcSizePerPacket; |
UInt32 numPacketsPerRead; |
AudioStreamPacketDescription * pktDescs; |
}; |
// input data proc callback |
OSStatus InputDataProc( AudioConverterRef inAudioConverter, |
UInt32* ioNumberDataPackets, |
AudioBufferList* ioData, |
AudioStreamPacketDescription** outDataPacketDescription, |
void* inUserData) |
{ |
AudioFileIO* afio = (AudioFileIO*)inUserData; |
// figure out how much to read |
if (*ioNumberDataPackets > afio->numPacketsPerRead) *ioNumberDataPackets = afio->numPacketsPerRead; |
// read from the file |
UInt32 outNumBytes = afio->srcBufferSize; |
OSStatus err = AudioFileReadPacketData(afio->afid, false, &outNumBytes, afio->pktDescs, |
afio->pos, ioNumberDataPackets, afio->srcBuffer); |
if (err) { |
printf ("Input Proc Read error: %d (%4.4s)\n", (int)err, (char*)&err); |
return err; |
} |
// printf ("Input Proc: Read %u packets from %lld, size: %u\n", *ioNumberDataPackets, afio->pos, outNumBytes); |
// advance input file packet position |
afio->pos += *ioNumberDataPackets; |
// put the data pointer into the buffer list |
ioData->mBuffers[0].mData = afio->srcBuffer; |
ioData->mBuffers[0].mDataByteSize = outNumBytes; |
ioData->mBuffers[0].mNumberChannels = afio->srcFormat.mChannelsPerFrame; |
if (outDataPacketDescription) { |
if (afio->pktDescs) |
*outDataPacketDescription = afio->pktDescs; |
else |
*outDataPacketDescription = NULL; |
} |
return err; |
} |
void ReadCookie (AudioConverterRef converter, AudioFileID infile) |
{ |
// for decoding, grab the cookie from the file and write it to the converter |
UInt32 cookieSize = 0; |
OSStatus err = AudioFileGetPropertyInfo(infile, kAudioFilePropertyMagicCookieData, &cookieSize, NULL); |
// if there is an error here, then the format doesn't have a cookie, so on we go |
if (!err && cookieSize) { |
char* cookie = new char [cookieSize]; |
err = AudioFileGetProperty(infile, kAudioFilePropertyMagicCookieData, &cookieSize, cookie); |
XThrowIfError (err, "Get Cookie From File"); |
err = AudioConverterSetProperty (converter, kAudioConverterDecompressionMagicCookie, cookieSize, cookie); |
XThrowIfError (err, "Set Cookie To AudioConverter"); |
delete [] cookie; |
} |
} |
void WriteCookie (AudioConverterRef converter, AudioFileID outfile) |
{ |
// grab the cookie from the converter and write it to the file |
UInt32 cookieSize = 0; |
OSStatus err = AudioConverterGetPropertyInfo(converter, kAudioConverterCompressionMagicCookie, &cookieSize, NULL); |
// if there is an error here, then the format doesn't have a cookie, so on we go |
if (!err && cookieSize) { |
char* cookie = new char [cookieSize]; |
err = AudioConverterGetProperty(converter, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); |
XThrowIfError (err, "Get Cookie From AudioConverter"); |
/*err =*/ AudioFileSetProperty (outfile, kAudioFilePropertyMagicCookieData, cookieSize, cookie); |
// even though some formats have cookies, some files don't take them, so we ignore the error |
delete [] cookie; |
} |
} |
int ConvertFile (CFURLRef inputFileURL, |
CAStreamBasicDescription &inputFormat, |
CFURLRef outputFileURL, |
AudioFileTypeID outputFileType, |
CAStreamBasicDescription &outputFormat, |
UInt32 outputBitRate) |
{ |
AudioFileID infile, outfile; |
OSStatus err = AudioFileOpenURL(inputFileURL, kAudioFileReadPermission, 0, &infile); |
XThrowIfError (err, "AudioFileOpen"); |
// create the AudioConverter |
AudioConverterRef converter; |
err = AudioConverterNew(&inputFormat, &outputFormat, &converter); |
XThrowIfError (err, "AudioConverterNew"); |
ReadCookie (converter, infile); |
// get the actual output format |
UInt32 size = sizeof(inputFormat); |
err = AudioConverterGetProperty(converter, kAudioConverterCurrentInputStreamDescription, &size, &inputFormat); |
XThrowIfError (err, "get kAudioConverterCurrentInputStreamDescription"); |
size = sizeof(outputFormat); |
err = AudioConverterGetProperty(converter, kAudioConverterCurrentOutputStreamDescription, &size, &outputFormat); |
XThrowIfError (err, "get kAudioConverterCurrentOutputStreamDescription"); |
if( outputBitRate > 0 ) { |
printf ("Dest bit rate: %d\n", (int)outputBitRate); |
err = AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, |
sizeof(outputBitRate), &outputBitRate); |
XThrowIfError (err, "AudioConverterSetProperty, kAudioConverterEncodeBitRate"); |
} |
// create the output file (this will erase an existing file) |
err = AudioFileCreateWithURL(outputFileURL, outputFileType, &outputFormat, kAudioFileFlags_EraseFile, &outfile); |
XThrowIfError (err, "AudioFileCreate"); |
// mActualToBaseSampleRateRatio is just for aach, since aach has two layers |
// the basic aac layer is of half sample rate of the aach layer |
double mActualToBaseSampleRateRatio = 1.0; // for aach |
CAStreamBasicDescription baseFormat; |
UInt32 propertySize = sizeof(AudioStreamBasicDescription); |
AudioFileGetProperty(infile, kAudioFilePropertyDataFormat, &propertySize, &baseFormat); |
if (inputFormat.mSampleRate != baseFormat.mSampleRate && inputFormat.mSampleRate != 0. && baseFormat.mSampleRate != 0.) |
mActualToBaseSampleRateRatio = inputFormat.mSampleRate / baseFormat.mSampleRate; // should be 2.0 for aach |
else |
mActualToBaseSampleRateRatio = 1.0; |
double srcRatio; |
if (outputFormat.mSampleRate != 0 && inputFormat.mSampleRate != 0) { |
srcRatio = outputFormat.mSampleRate / inputFormat.mSampleRate; |
} else { |
srcRatio = 1.0; |
} |
// if the bitstream file contains priming info, overwrite the audio converter's |
// priming info with the one got from the bitstream to do correct trimming |
SInt64 mDecodeValidFrames = 0; |
AudioFilePacketTableInfo srcPti; |
if (inputFormat.mBitsPerChannel == 0) { // input is compressed, decode to linear PCM |
size = sizeof(srcPti); |
err = AudioFileGetProperty(infile, kAudioFilePropertyPacketTableInfo, &size, &srcPti); // try to get priming info from bitstream file |
if (err == noErr) { // has priming info |
mDecodeValidFrames = (SInt64)(mActualToBaseSampleRateRatio * srcRatio * srcPti.mNumberValidFrames + 0.5); |
AudioConverterPrimeInfo primeInfo; // overwrite audio converter's priming info |
primeInfo.leadingFrames = (SInt32)(srcPti.mPrimingFrames * mActualToBaseSampleRateRatio + 0.5); // overwrite the audio converter's prime info |
primeInfo.trailingFrames = 0; // since the audio converter does not cut off trailing zeros |
err = AudioConverterSetProperty(converter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo); |
XThrowIfError (err, "AudioConverterSetProperty, kAudioConverterPrimeInfo"); |
} |
} |
// set up buffers and data proc info struct |
AudioFileIO afio; |
afio.afid = infile; |
afio.srcBufferSize = 32768; |
afio.srcBuffer = new char [ afio.srcBufferSize ]; |
afio.pos = 0; |
afio.srcFormat = inputFormat; |
if (inputFormat.mBytesPerPacket == 0) { |
// format is VBR, so we need to get max size per packet |
size = sizeof(afio.srcSizePerPacket); |
err = AudioFileGetProperty(infile, kAudioFilePropertyPacketSizeUpperBound, &size, &afio.srcSizePerPacket); |
XThrowIfError (err, "kAudioFilePropertyPacketSizeUpperBound"); |
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket; |
afio.pktDescs = new AudioStreamPacketDescription [afio.numPacketsPerRead]; |
} |
else { |
afio.srcSizePerPacket = inputFormat.mBytesPerPacket; |
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket; |
afio.pktDescs = NULL; |
} |
// set up our output buffers |
AudioStreamPacketDescription* outputPktDescs = NULL; |
int outputSizePerPacket = outputFormat.mBytesPerPacket; // this will be non-zero if the format is CBR |
UInt32 theOutputBufSize = 32768; |
char* outputBuffer = new char[theOutputBufSize]; |
if (outputSizePerPacket == 0) { |
UInt32 size = sizeof(outputSizePerPacket); |
err = AudioConverterGetProperty(converter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &outputSizePerPacket); |
XThrowIfError (err, "Get Max Packet Size"); |
outputPktDescs = new AudioStreamPacketDescription [theOutputBufSize / outputSizePerPacket]; |
} |
UInt32 numOutputPackets = theOutputBufSize / outputSizePerPacket; |
WriteCookie (converter, outfile); |
// write dest channel layout |
if (inputFormat.mChannelsPerFrame > 2) { |
UInt32 layoutSize = 0; |
bool layoutFromConverter = true; |
err = AudioConverterGetPropertyInfo(converter, kAudioConverterOutputChannelLayout, &layoutSize, NULL); |
// if the converter doesn't have a layout does the input file? |
if (err || !layoutSize) { |
err = AudioFileGetPropertyInfo (infile, kAudioFilePropertyChannelLayout, &layoutSize, NULL); |
layoutFromConverter = false; |
} |
if (!err && layoutSize) { |
char* layout = new char[layoutSize]; |
if (layoutFromConverter) { |
err = AudioConverterGetProperty(converter, kAudioConverterOutputChannelLayout, &layoutSize, layout); |
XThrowIfError (err, "Get Layout From AudioConverter"); |
} else { |
err = AudioFileGetProperty(infile, kAudioFilePropertyChannelLayout, &layoutSize, layout); |
XThrowIfError (err, "Get Layout From AudioFile"); |
} |
err = AudioFileSetProperty (outfile, kAudioFilePropertyChannelLayout, layoutSize, layout); |
// even though some formats have layouts, some files don't take them |
if (!err) |
printf ("write channel layout to file: %d\n", (int)layoutSize); |
delete [] layout; |
} |
} |
// loop to convert data |
SInt64 outputPos = 0; |
while (1) { |
// set up output buffer list |
AudioBufferList fillBufList; |
fillBufList.mNumberBuffers = 1; |
fillBufList.mBuffers[0].mNumberChannels = inputFormat.mChannelsPerFrame; |
fillBufList.mBuffers[0].mDataByteSize = theOutputBufSize; |
fillBufList.mBuffers[0].mData = outputBuffer; |
// convert data |
UInt32 ioOutputDataPackets = numOutputPackets; |
err = AudioConverterFillComplexBuffer(converter, InputDataProc, &afio, &ioOutputDataPackets, &fillBufList, outputPktDescs); |
XThrowIfError (err, "AudioConverterFillComplexBuffer"); |
if (ioOutputDataPackets == 0) { |
// this is the EOF conditon |
break; |
} |
SInt64 frame1 = outputPos + ioOutputDataPackets; |
if (mDecodeValidFrames != 0 && frame1 > mDecodeValidFrames) { |
SInt64 framesToTrim64 = frame1 - mDecodeValidFrames; |
UInt32 framesToTrim = (framesToTrim64 > ioOutputDataPackets) ? ioOutputDataPackets : UInt32(framesToTrim64); |
int bytesToTrim = framesToTrim * outputFormat.mBytesPerFrame; |
fillBufList.mBuffers[0].mDataByteSize -= bytesToTrim; |
ioOutputDataPackets -= framesToTrim; |
} |
// write to output file |
UInt32 inNumBytes = fillBufList.mBuffers[0].mDataByteSize; |
err = AudioFileWritePackets(outfile, false, inNumBytes, outputPktDescs, outputPos, &ioOutputDataPackets, outputBuffer); |
XThrowIfError (err, "AudioFileWritePackets"); |
// advance output file packet position |
outputPos += ioOutputDataPackets; |
// printf ("Convert Output: Write %ld packets, size: %ld\n", ioOutputDataPackets, inNumBytes); |
} |
// we write out any of the leading and trailing frames for compressed formats only |
if (outputFormat.mBitsPerChannel == 0) { |
UInt32 isWritable; |
err = AudioFileGetPropertyInfo(outfile, kAudioFilePropertyPacketTableInfo, &size, &isWritable); |
if (err == noErr && isWritable) { |
// last job is to make sure we write out the priming and remainder details to the file |
AudioConverterPrimeInfo primeInfo; |
UInt32 primeSize = sizeof(primeInfo); |
err = AudioConverterGetProperty(converter, kAudioConverterPrimeInfo, &primeSize, &primeInfo); |
// if there's an error we don't care |
if (err == noErr) { |
AudioFilePacketTableInfo pti; |
size = sizeof(pti); |
err = AudioFileGetProperty(outfile, kAudioFilePropertyPacketTableInfo, &size, &pti); |
if (err == noErr) { |
// there's priming to write out to the file |
UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames; // get the total number of frames from the output file |
pti.mPrimingFrames = primeInfo.leadingFrames; |
pti.mRemainderFrames = primeInfo.trailingFrames; |
pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames; // update number of valid frames |
err = AudioFileSetProperty(outfile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti); |
XThrowIfError (err, "AudioFileSetProperty, kAudioFilePropertyPacketTableInfo"); |
} |
} |
} |
} |
// write the cookie again - sometimes codecs will |
// update cookies at the end of a conversion |
WriteCookie (converter, outfile); |
// cleanup |
delete [] afio.srcBuffer; |
if (inputFormat.mBytesPerPacket == 0) { |
delete afio.pktDescs; |
} |
delete [] outputPktDescs; |
delete [] outputBuffer; |
AudioConverterDispose(converter); |
AudioFileClose(outfile); |
AudioFileClose(infile); |
return 0; |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-29