1
/* Copyright: ļæ½ Copyright 2005 Apple Computer, Inc. All rights reserved.
3
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
4
("Apple") in consideration of your agreement to the following terms, and your
5
use, installation, modification or redistribution of this Apple software
6
constitutes acceptance of these terms. If you do not agree with these terms,
7
please do not use, install, modify or redistribute this Apple software.
9
In consideration of your agreement to abide by the following terms, and subject
10
to these terms, Apple grants you a personal, non-exclusive license, under Appleļæ½s
11
copyrights in this original Apple software (the "Apple Software"), to use,
12
reproduce, modify and redistribute the Apple Software, with or without
13
modifications, in source and/or binary forms; provided that if you redistribute
14
the Apple Software in its entirety and without modifications, you must retain
15
this notice and the following text and disclaimers in all such redistributions of
16
the Apple Software. Neither the name, trademarks, service marks or logos of
17
Apple Computer, Inc. may be used to endorse or promote products derived from the
18
Apple Software without specific prior written permission from Apple. Except as
19
expressly stated in this notice, no other rights or licenses, express or implied,
20
are granted by Apple herein, including but not limited to any patent rights that
21
may be infringed by your derivative works or by other works in which the Apple
22
Software may be incorporated.
24
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
25
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
26
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
28
COMBINATION WITH YOUR PRODUCTS.
30
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
31
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
32
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
34
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
35
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
36
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
/*=============================================================================
41
=============================================================================*/
43
#include "CAAudioFile.h"
45
#if !CAAF_USE_EXTAUDIOFILE
47
#include "CAXException.h"
49
#include "CAHostTimeBase.h"
50
#include "CADebugMacros.h"
52
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
53
#include <AudioToolbox/AudioToolbox.h>
55
#include <AudioFormat.h>
59
//#define VERBOSE_IO 1
60
//#define VERBOSE_CONVERTER 1
61
//#define VERBOSE_CHANNELMAP 1
62
//#define LOG_FUNCTION_ENTRIES 1
64
#if VERBOSE_CHANNELMAP
65
#include "CAChannelLayouts.h" // this is in Source/Tests/AudioFileTools/Utility
69
#if LOG_FUNCTION_ENTRIES
70
class FunctionLogger {
72
FunctionLogger(const char *name, const char *fmt=NULL, ...) : mName(name) {
74
printf("-> %s ", name);
87
printf("<- %s\n", mName);
92
static void Indent() {
93
for (int i = sIndent; --i >= 0; ) {
94
putchar(' '); putchar(' ');
101
int FunctionLogger::sIndent = 0;
103
#define LOG_FUNCTION(name, format, ...) FunctionLogger _flog(name, format, ## __VA_ARGS__);
105
#define LOG_FUNCTION(name, format, foo)
108
static const UInt32 kDefaultIOBufferSizeBytes = 0x10000;
110
#if CAAUDIOFILE_PROFILE
111
#define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : 0
112
#define ElapsedTime(af, starttime, counter) if (af->mProfiling) counter += (CAHostTimeBase::GetTheCurrentTime() - starttime)
114
#define StartTiming(af, starttime)
115
#define ElapsedTime(af, starttime, counter)
118
#define kNoMoreInputRightNow 'nein'
120
// _______________________________________________________________________________________
122
CAAudioFile::CAAudioFile() :
125
mFinishingEncoding(false),
128
mFramesToSkipFollowingSeek(0),
130
mClientOwnsIOBuffer(false),
135
mWriteBufferList(NULL)
136
#if CAAUDIOFILE_PROFILE
139
mTicksInConverter(0),
140
mTicksInReadInConverter(0),
145
mIOBufferList.mBuffers[0].mData = NULL;
146
mIOBufferList.mBuffers[0].mDataByteSize = 0;
147
mClientMaxPacketSize = 0;
148
mIOBufferSizeBytes = kDefaultIOBufferSizeBytes;
151
// _______________________________________________________________________________________
153
CAAudioFile::~CAAudioFile()
158
// _______________________________________________________________________________________
160
void CAAudioFile::Close()
162
LOG_FUNCTION("CAAudioFile::Close", NULL, NULL);
163
if (mMode == kClosed)
165
if (mMode == kWriting)
168
if (mAudioFile != 0 && mOwnOpenFile) {
169
AudioFileClose(mAudioFile);
172
if (!mClientOwnsIOBuffer) {
173
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
174
mIOBufferList.mBuffers[0].mData = NULL;
175
mIOBufferList.mBuffers[0].mDataByteSize = 0;
177
delete[] mPacketDescs; mPacketDescs = NULL; mNumPacketDescs = 0;
178
delete[] mMagicCookie; mMagicCookie = NULL;
179
delete mWriteBufferList; mWriteBufferList = NULL;
183
// _______________________________________________________________________________________
185
void CAAudioFile::CloseConverter()
188
#if VERBOSE_CONVERTER
189
printf("CAAudioFile %p : CloseConverter\n", this);
191
AudioConverterDispose(mConverter);
196
// =======================================================================================
198
// _______________________________________________________________________________________
200
void CAAudioFile::Open(const FSRef &fsref)
202
LOG_FUNCTION("CAAudioFile::Open", "%p", this);
203
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
205
XThrowIfError(AudioFileOpen(&mFSRef, fsRdPerm, 0, &mAudioFile), "open audio file");
208
GetExistingFileInfo();
211
// _______________________________________________________________________________________
213
void CAAudioFile::Wrap(AudioFileID fileID, bool forWriting)
215
LOG_FUNCTION("CAAudioFile::Wrap", "%p", this);
216
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
219
mOwnOpenFile = false;
220
mMode = forWriting ? kPreparingToWrite : kReading;
221
GetExistingFileInfo();
226
// _______________________________________________________________________________________
228
void CAAudioFile::CreateNew(const FSRef &parentDir, CFStringRef filename, AudioFileTypeID filetype, const AudioStreamBasicDescription &dataFormat, const AudioChannelLayout *layout)
230
LOG_FUNCTION("CAAudioFile::CreateNew", "%p", this);
231
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
233
mFileDataFormat = dataFormat;
235
mFileChannelLayout = layout;
236
#if VERBOSE_CHANNELMAP
237
printf("PrepareNew passed channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
240
mMode = kPreparingToCreate;
241
FileFormatChanged(&parentDir, filename, filetype);
244
// _______________________________________________________________________________________
246
// called to create the file -- or update its format/channel layout/properties based on an encoder
248
void CAAudioFile::FileFormatChanged(const FSRef *parentDir, CFStringRef filename, AudioFileTypeID filetype)
250
LOG_FUNCTION("CAAudioFile::FileFormatChanged", "%p", this);
251
XThrowIf(mMode != kPreparingToCreate && mMode != kPreparingToWrite, kExtAudioFileError_InvalidOperationOrder, "new file not prepared");
255
AudioStreamBasicDescription saveFileDataFormat = mFileDataFormat;
257
#if VERBOSE_CONVERTER
258
mFileDataFormat.PrintFormat(stdout, "", "Specified file data format");
261
// Find out the actual format the converter will produce. This is necessary in
262
// case the bitrate has forced a lower sample rate, which needs to be set correctly
263
// in the stream description passed to AudioFileCreate.
264
if (mConverter != NULL) {
265
propertySize = sizeof(AudioStreamBasicDescription);
266
Float64 origSampleRate = mFileDataFormat.mSampleRate;
267
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCurrentOutputStreamDescription, &propertySize, &mFileDataFormat), "get audio converter's output stream description");
268
// do the same for the channel layout being output by the converter
269
#if VERBOSE_CONVERTER
270
mFileDataFormat.PrintFormat(stdout, "", "Converter output");
272
if (fiszero(mFileDataFormat.mSampleRate))
273
mFileDataFormat.mSampleRate = origSampleRate;
274
err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterOutputChannelLayout, &propertySize, NULL);
275
if (err == noErr && propertySize > 0) {
276
AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
277
err = AudioConverterGetProperty(mConverter, kAudioConverterOutputChannelLayout, &propertySize, layout);
280
XThrow(err, "couldn't get audio converter's output channel layout");
282
mFileChannelLayout = layout;
283
#if VERBOSE_CHANNELMAP
284
printf("got new file's channel layout from converter: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
290
// create the output file
291
if (mMode == kPreparingToCreate) {
292
CAStreamBasicDescription newFileDataFormat = mFileDataFormat;
293
if (fiszero(newFileDataFormat.mSampleRate))
294
newFileDataFormat.mSampleRate = 44100; // just make something up for now
295
#if VERBOSE_CONVERTER
296
newFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
298
XThrowIfError(AudioFileCreate(parentDir, filename, filetype, &newFileDataFormat, 0, &mFSRef, &mAudioFile), "create audio file");
299
mMode = kPreparingToWrite;
301
} else if (saveFileDataFormat != mFileDataFormat || fnotequal(saveFileDataFormat.mSampleRate, mFileDataFormat.mSampleRate)) {
302
// second check must be explicit since operator== on ASBD treats SR of zero as "don't care"
303
if (fiszero(mFileDataFormat.mSampleRate))
304
mFileDataFormat.mSampleRate = mClientDataFormat.mSampleRate;
305
#if VERBOSE_CONVERTER
306
mFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
308
XThrowIf(fiszero(mFileDataFormat.mSampleRate), kExtAudioFileError_InvalidDataFormat, "file's sample rate is 0");
309
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyDataFormat, sizeof(AudioStreamBasicDescription), &mFileDataFormat), "couldn't update file's data format");
312
UInt32 deferSizeUpdates = 1;
313
err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyDeferSizeUpdates, sizeof(UInt32), &deferSizeUpdates);
315
if (mConverter != NULL) {
317
// get the magic cookie, if any, from the converter
318
delete[] mMagicCookie; mMagicCookie = NULL;
319
mMagicCookieSize = 0;
321
err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, NULL);
323
// we can get a noErr result and also a propertySize == 0
324
// -- if the file format does support magic cookies, but this file doesn't have one.
325
if (err == noErr && propertySize > 0) {
326
mMagicCookie = new Byte[propertySize];
327
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie");
328
mMagicCookieSize = propertySize; // the converter lies and tell us the wrong size
329
// now set the magic cookie on the output file
330
UInt32 willEatTheCookie = false;
331
// the converter wants to give us one; will the file take it?
332
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData,
333
NULL, &willEatTheCookie);
334
if (err == noErr && willEatTheCookie) {
335
#if VERBOSE_CONVERTER
336
printf("Setting cookie on encoded file\n");
338
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, mMagicCookieSize, mMagicCookie), "set audio file's magic cookie");
342
// get maximum packet size
343
propertySize = sizeof(UInt32);
344
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propertySize, &mFileMaxPacketSize), "get audio converter's maximum output packet size");
346
AllocateBuffers(true /* okToFail */);
348
InitFileMaxPacketSize();
351
if (mFileChannelLayout.IsValid() && mFileChannelLayout.NumberChannels() > 2) {
352
// don't bother tagging mono/stereo files
354
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, NULL, &isWritable);
355
if (!err && isWritable) {
356
#if VERBOSE_CHANNELMAP
357
printf("writing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
359
err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyChannelLayout,
360
mFileChannelLayout.Size(), &mFileChannelLayout.Layout());
362
CAXException::Warning("could not set the file's channel layout", err);
364
#if VERBOSE_CHANNELMAP
365
printf("file won't accept a channel layout (write)\n");
370
UpdateClientMaxPacketSize(); // also sets mFrame0Offset
375
// _______________________________________________________________________________________
377
void CAAudioFile::InitFileMaxPacketSize()
379
LOG_FUNCTION("CAAudioFile::InitFileMaxPacketSize", "%p", this);
380
UInt32 propertySize = sizeof(UInt32);
381
OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyMaximumPacketSize,
382
&propertySize, &mFileMaxPacketSize);
384
// workaround for 3361377: not all file formats' maximum packet sizes are supported
385
if (!mFileDataFormat.IsPCM())
386
XThrowIfError(err, "get audio file's maximum packet size");
387
mFileMaxPacketSize = mFileDataFormat.mBytesPerFrame;
389
AllocateBuffers(true /* okToFail */);
393
// _______________________________________________________________________________________
395
SInt64 CAAudioFile::FileDataOffset()
397
if (mFileDataOffset < 0) {
398
UInt32 propertySize = sizeof(SInt64);
399
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataOffset, &propertySize, &mFileDataOffset), "couldn't get file's data offset");
401
return mFileDataOffset;
404
// _______________________________________________________________________________________
406
SInt64 CAAudioFile::GetNumberFrames() const
408
AudioFilePacketTableInfo pti;
409
UInt32 propertySize = sizeof(pti);
410
OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
412
return pti.mNumberValidFrames;
413
return mFileDataFormat.mFramesPerPacket * GetNumberPackets() - mFrame0Offset;
416
// _______________________________________________________________________________________
418
void CAAudioFile::SetNumberFrames(SInt64 nFrames)
420
XThrowIf(mFileDataFormat.mFramesPerPacket != 1, kExtAudioFileError_InvalidDataFormat, "SetNumberFrames only supported for PCM");
421
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, sizeof(SInt64), &nFrames), "Couldn't set number of packets on audio file");
424
// _______________________________________________________________________________________
426
// call for existing file, NOT new one - from Open() or Wrap()
427
void CAAudioFile::GetExistingFileInfo()
429
LOG_FUNCTION("CAAudioFile::GetExistingFileInfo", "%p", this);
433
// get mFileDataFormat
434
propertySize = sizeof(AudioStreamBasicDescription);
435
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataFormat, &propertySize, &mFileDataFormat), "get audio file's data format");
437
// get mFileChannelLayout
438
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, NULL);
439
if (err == noErr && propertySize > 0) {
440
AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
441
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, layout);
443
mFileChannelLayout = layout;
444
#if VERBOSE_CHANNELMAP
445
printf("existing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
449
XThrowIfError(err, "get audio file's channel layout");
451
if (mMode != kReading)
455
// get mNumberPackets
456
propertySize = sizeof(mNumberPackets);
457
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &mNumberPackets), "get audio file's packet count");
459
printf("CAAudioFile::GetExistingFileInfo: %qd packets\n", mNumberPackets);
464
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL);
465
if (err == noErr && propertySize > 0) {
466
mMagicCookie = new Byte[propertySize];
467
mMagicCookieSize = propertySize;
468
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, mMagicCookie), "get audio file's magic cookie");
470
InitFileMaxPacketSize();
474
UpdateClientMaxPacketSize();
477
// =======================================================================================
479
// _______________________________________________________________________________________
481
void CAAudioFile::SetFileChannelLayout(const CAAudioChannelLayout &layout)
483
LOG_FUNCTION("CAAudioFile::SetFileChannelLayout", "%p", this);
484
mFileChannelLayout = layout;
485
#if VERBOSE_CHANNELMAP
486
printf("file channel layout set explicitly (%s): %s\n", mMode == kReading ? "read" : "write", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
488
if (mMode != kReading)
492
// _______________________________________________________________________________________
494
void CAAudioFile::SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout)
496
LOG_FUNCTION("CAAudioFile::SetClientFormat", "%p", this);
497
XThrowIf(!dataFormat.IsPCM(), kExtAudioFileError_NonPCMClientFormat, "non-PCM client format on audio file");
499
bool dataFormatChanging = (mClientDataFormat.mFormatID == 0 || mClientDataFormat != dataFormat);
501
if (dataFormatChanging) {
503
if (mWriteBufferList) {
504
delete mWriteBufferList;
505
mWriteBufferList = NULL;
507
mClientDataFormat = dataFormat;
510
if (layout && layout->IsValid()) {
511
XThrowIf(layout->NumberChannels() != mClientDataFormat.NumberChannels(), kExtAudioFileError_InvalidChannelMap, "inappropriate channel map");
512
mClientChannelLayout = *layout;
515
bool differentLayouts;
516
if (mClientChannelLayout.IsValid()) {
517
if (mFileChannelLayout.IsValid()) {
518
differentLayouts = mClientChannelLayout.Tag() != mFileChannelLayout.Tag();
519
#if VERBOSE_CHANNELMAP
520
printf("two valid layouts, %s\n", differentLayouts ? "different" : "same");
523
differentLayouts = false;
524
#if VERBOSE_CHANNELMAP
525
printf("valid client layout, unknown file layout\n");
529
differentLayouts = false;
530
#if VERBOSE_CHANNELMAP
531
if (mFileChannelLayout.IsValid())
532
printf("valid file layout, unknown client layout\n");
534
printf("two invalid layouts\n");
538
if (mClientDataFormat != mFileDataFormat || differentLayouts) {
539
// We need an AudioConverter.
540
if (mMode == kReading) {
541
// file -> client (decode)
542
//mFileDataFormat.PrintFormat( stdout, "", "File: ");
543
//mClientDataFormat.PrintFormat(stdout, "", "Client: ");
545
if (mConverter == NULL)
546
XThrowIfError(AudioConverterNew(&mFileDataFormat, &mClientDataFormat, &mConverter),
547
"create audio converter");
549
#if VERBOSE_CONVERTER
550
printf("CAAudioFile %p -- created converter\n", this);
553
// set the magic cookie, if any (for decode)
555
SetConverterProperty(kAudioConverterDecompressionMagicCookie, mMagicCookieSize, mMagicCookie, mFileDataFormat.IsPCM());
556
// we get cookies from some AIFF's but the converter barfs on them,
557
// so we set canFail to true for PCM
559
SetConverterChannelLayout(false, mFileChannelLayout);
560
SetConverterChannelLayout(true, mClientChannelLayout);
562
// propagate leading/trailing frame counts
563
if (mFileDataFormat.mBitsPerChannel == 0) {
566
AudioFilePacketTableInfo pti;
567
propertySize = sizeof(pti);
568
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
569
if (err == noErr && (pti.mPrimingFrames > 0 || pti.mRemainderFrames > 0)) {
570
AudioConverterPrimeInfo primeInfo;
571
primeInfo.leadingFrames = pti.mPrimingFrames;
572
primeInfo.trailingFrames = pti.mRemainderFrames;
573
/* ignore any error. better to play it at all than not. */
574
/*err = */AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo);
575
//XThrowIfError(err, "couldn't set prime info on converter");
578
} else if (mMode == kPreparingToCreate || mMode == kPreparingToWrite) {
579
// client -> file (encode)
580
if (mConverter == NULL)
581
XThrowIfError(AudioConverterNew(&mClientDataFormat, &mFileDataFormat, &mConverter), "create audio converter");
582
mWriteBufferList = CABufferList::New("", mClientDataFormat);
583
SetConverterChannelLayout(false, mClientChannelLayout);
584
SetConverterChannelLayout(true, mFileChannelLayout);
585
if (mMode == kPreparingToWrite)
588
XThrowIfError(kExtAudioFileError_InvalidOperationOrder, "audio file format not yet known");
590
UpdateClientMaxPacketSize();
593
// _______________________________________________________________________________________
595
OSStatus CAAudioFile::SetConverterProperty(
596
AudioConverterPropertyID inPropertyID,
597
UInt32 inPropertyDataSize,
598
const void* inPropertyData,
601
OSStatus err = noErr;
602
//LOG_FUNCTION("ExtAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID);
603
if (inPropertyID == kAudioConverterPropertySettings && *(CFPropertyListRef *)inPropertyData == NULL)
606
err = AudioConverterSetProperty(mConverter, inPropertyID, inPropertyDataSize, inPropertyData);
608
XThrowIfError(err, "set audio converter property");
611
UpdateClientMaxPacketSize();
612
if (mMode == kPreparingToWrite)
617
// _______________________________________________________________________________________
619
void CAAudioFile::SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout)
621
LOG_FUNCTION("CAAudioFile::SetConverterChannelLayout", "%p", this);
624
if (layout.IsValid()) {
625
#if VERBOSE_CHANNELMAP
626
printf("Setting converter's %s channel layout: %s\n", output ? "output" : "input",
627
CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
630
err = AudioConverterSetProperty(mConverter, kAudioConverterOutputChannelLayout,
631
layout.Size(), &layout.Layout());
632
XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's output channel layout");
634
err = AudioConverterSetProperty(mConverter, kAudioConverterInputChannelLayout,
635
layout.Size(), &layout.Layout());
636
XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's input channel layout");
638
if (mMode == kPreparingToWrite)
643
// _______________________________________________________________________________________
645
CFArrayRef CAAudioFile::GetConverterConfig()
648
UInt32 propertySize = sizeof(plist);
649
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertySettings, &propertySize, &plist), "get converter property settings");
653
// _______________________________________________________________________________________
655
void CAAudioFile::UpdateClientMaxPacketSize()
657
LOG_FUNCTION("CAAudioFile::UpdateClientMaxPacketSize", "%p", this);
659
if (mConverter != NULL) {
660
AudioConverterPropertyID property = (mMode == kReading) ?
661
kAudioConverterPropertyMaximumOutputPacketSize :
662
kAudioConverterPropertyMaximumInputPacketSize;
664
UInt32 propertySize = sizeof(UInt32);
665
XThrowIfError(AudioConverterGetProperty(mConverter, property, &propertySize, &mClientMaxPacketSize),
666
"get audio converter's maximum packet size");
668
if (mFileDataFormat.mBitsPerChannel == 0) {
669
AudioConverterPrimeInfo primeInfo;
670
propertySize = sizeof(primeInfo);
671
OSStatus err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
673
mFrame0Offset = primeInfo.leadingFrames;
674
#if VERBOSE_CONVERTER
675
printf("kAudioConverterPrimeInfo: err = %ld, leadingFrames = %ld\n", err, mFrame0Offset);
679
mClientMaxPacketSize = mFileMaxPacketSize;
683
// _______________________________________________________________________________________
684
// Allocates: mIOBufferList, mIOBufferSizePackets, mPacketDescs
685
// Dependent on: mFileMaxPacketSize, mIOBufferSizeBytes
686
void CAAudioFile::AllocateBuffers(bool okToFail)
688
LOG_FUNCTION("CAAudioFile::AllocateBuffers", "%p", this);
689
if (mFileMaxPacketSize == 0) {
692
XThrowIf(true, kExtAudioFileError_MaxPacketSizeUnknown, "file's maximum packet size is 0");
694
UInt32 bufferSizeBytes = mIOBufferSizeBytes = std::max(mIOBufferSizeBytes, mFileMaxPacketSize);
695
// must be big enough for at least one maximum size packet
697
if (mIOBufferList.mBuffers[0].mDataByteSize != bufferSizeBytes) {
698
mIOBufferList.mNumberBuffers = 1;
699
mIOBufferList.mBuffers[0].mNumberChannels = mFileDataFormat.mChannelsPerFrame;
700
if (!mClientOwnsIOBuffer) {
701
//printf("reallocating I/O buffer\n");
702
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
703
mIOBufferList.mBuffers[0].mData = new Byte[bufferSizeBytes];
705
mIOBufferList.mBuffers[0].mDataByteSize = bufferSizeBytes;
706
mIOBufferSizePackets = bufferSizeBytes / mFileMaxPacketSize;
709
UInt32 propertySize = sizeof(UInt32);
710
UInt32 externallyFramed;
711
XThrowIfError(AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed,
712
sizeof(AudioStreamBasicDescription), &mFileDataFormat, &propertySize, &externallyFramed),
713
"is format externally framed");
714
if (mNumPacketDescs != (externallyFramed ? mIOBufferSizePackets : 0)) {
715
delete[] mPacketDescs;
719
if (externallyFramed) {
720
//printf("reallocating packet descs\n");
721
mPacketDescs = new AudioStreamPacketDescription[mIOBufferSizePackets];
722
mNumPacketDescs = mIOBufferSizePackets;
727
// _______________________________________________________________________________________
729
void CAAudioFile::SetIOBuffer(void *buf)
731
if (!mClientOwnsIOBuffer)
732
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
733
mIOBufferList.mBuffers[0].mData = buf;
736
mClientOwnsIOBuffer = false;
737
SetIOBufferSizeBytes(mIOBufferSizeBytes);
739
mClientOwnsIOBuffer = true;
742
// printf("CAAudioFile::SetIOBuffer %p: %p, 0x%lx bytes, mClientOwns = %d\n", this, mIOBufferList.mBuffers[0].mData, mIOBufferSizeBytes, mClientOwnsIOBuffer);
745
// ===============================================================================
749
added kAudioFilePropertyPacketToFrame and kAudioFilePropertyFrameToPacket.
750
You pass in an AudioFramePacketTranslation struct, with the appropriate field filled in, to AudioFileGetProperty.
752
kAudioFilePropertyPacketToFrame = 'pkfr',
753
// pass a AudioFramePacketTranslation with mPacket filled out and get mFrame back. mFrameOffsetInPacket is ignored.
754
kAudioFilePropertyFrameToPacket = 'frpk',
755
// pass a AudioFramePacketTranslation with mFrame filled out and get mPacket and mFrameOffsetInPacket back.
757
struct AudioFramePacketTranslation
761
UInt32 mFrameOffsetInPacket;
765
SInt64 CAAudioFile::PacketToFrame(SInt64 packet) const
767
AudioFramePacketTranslation trans;
770
switch (mFileDataFormat.mFramesPerPacket) {
774
trans.mPacket = packet;
775
propertySize = sizeof(trans);
776
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketToFrame, &propertySize, &trans),
777
"packet <-> frame translation unimplemented for format with variable frames/packet");
780
return packet * mFileDataFormat.mFramesPerPacket;
783
SInt64 CAAudioFile::FrameToPacket(SInt64 inFrame) const
785
AudioFramePacketTranslation trans;
788
switch (mFileDataFormat.mFramesPerPacket) {
792
trans.mFrame = inFrame;
793
propertySize = sizeof(trans);
794
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyFrameToPacket, &propertySize, &trans),
795
"packet <-> frame translation unimplemented for format with variable frames/packet");
796
return trans.mPacket;
798
return inFrame / mFileDataFormat.mFramesPerPacket;
801
// _______________________________________________________________________________________
804
SInt64 CAAudioFile::Tell() const // frameNumber
806
return mFrameMark - mFrame0Offset;
809
void CAAudioFile::SeekToPacket(SInt64 packetNumber)
812
printf("CAAudioFile::SeekToPacket: %qd\n", packetNumber);
814
XThrowIf(mMode != kReading || packetNumber < 0 /*|| packetNumber >= mNumberPackets*/ , kExtAudioFileError_InvalidSeek, "seek to packet in audio file");
815
if (mPacketMark == packetNumber)
816
return; // already there! don't reset converter
817
mPacketMark = packetNumber;
819
mFrameMark = PacketToFrame(packetNumber) - mFrame0Offset;
820
mFramesToSkipFollowingSeek = 0;
822
// must reset -- if we reached end of stream. converter will no longer work otherwise
823
AudioConverterReset(mConverter);
827
Example: AAC, 1024 frames/packet, 2112 frame offset
831
Absolute frames: 0 1024 2048 | 3072
832
+---------+---------+--|------+---------+---------+
833
Packets: | 0 | 1 | | 2 | 3 | 4 |
834
+---------+---------+--|------+---------+---------+
835
Client frames: -2112 -1088 -64 | 960 SeekToFrame, TellFrame
839
* Offset between absolute and client frames is mFrame0Offset.
840
*** mFrameMark is in client frames ***
843
clientFrame 0 960 1000 1024
844
absoluteFrame 2112 3072 3112 3136
846
tempFrameMark* -2112 -2112 -2112 -1088
847
mFramesToSkipFollowingSeek 2112 3072 3112 2112
849
void CAAudioFile::Seek(SInt64 clientFrame)
851
if (clientFrame == mFrameMark)
852
return; // already there! don't reset converter
854
//SInt64 absoluteFrame = clientFrame + mFrame0Offset;
855
XThrowIf(mMode != kReading || clientFrame < 0 || !mClientDataFormat.IsPCM(), kExtAudioFileError_InvalidSeek, "seek to frame in audio file");
858
SInt64 prevFrameMark = mFrameMark;
862
packet = FrameToPacket(clientFrame);
865
SeekToPacket(packet);
866
// this will have backed up mFrameMark to match the beginning of the packet
867
mFramesToSkipFollowingSeek = std::max(UInt32(clientFrame - mFrameMark), UInt32(0));
868
mFrameMark = clientFrame;
871
printf("CAAudioFile::SeekToFrame: frame %qd (from %qd), packet %qd, skip %ld frames\n", mFrameMark, prevFrameMark, packet, mFramesToSkipFollowingSeek);
875
// _______________________________________________________________________________________
877
void CAAudioFile::Read(UInt32 &ioNumPackets, AudioBufferList *ioData)
878
// May read fewer packets than requested if:
879
// buffer is not big enough
880
// file does not contain that many more packets
881
// Note that eofErr is not fatal, just results in 0 packets returned
882
// ioData's buffer sizes may be shortened
884
XThrowIf(mClientMaxPacketSize == 0, kExtAudioFileError_MaxPacketSizeUnknown, "client maximum packet size is 0");
885
if (mIOBufferList.mBuffers[0].mData == NULL) {
887
printf("warning: CAAudioFile::AllocateBuffers called from ReadPackets\n");
891
UInt32 bufferSizeBytes = ioData->mBuffers[0].mDataByteSize;
892
UInt32 maxNumPackets = bufferSizeBytes / mClientMaxPacketSize;
893
// older versions of AudioConverterFillComplexBuffer don't do this, so do our own sanity check
894
UInt32 nPackets = std::min(ioNumPackets, maxNumPackets);
896
mMaxPacketsToRead = ~0UL;
898
if (mClientDataFormat.mFramesPerPacket == 1) { // PCM or equivalent
899
while (mFramesToSkipFollowingSeek > 0) {
900
UInt32 skipFrames = std::min(mFramesToSkipFollowingSeek, maxNumPackets);
901
UInt32 framesPerPacket;
902
if ((framesPerPacket=mFileDataFormat.mFramesPerPacket) > 0)
903
mMaxPacketsToRead = (skipFrames + framesPerPacket - 1) / framesPerPacket;
905
if (mConverter == NULL) {
906
XThrowIfError(ReadInputProc(NULL, &skipFrames, ioData, NULL, this), "read audio file");
908
#if CAAUDIOFILE_PROFILE
911
StartTiming(this, fill);
912
XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)");
913
ElapsedTime(this, fill, mTicksInConverter);
914
#if CAAUDIOFILE_PROFILE
915
mInConverter = false;
918
if (skipFrames == 0) { // hit EOF
922
mFrameMark += skipFrames;
924
printf("CAAudioFile::ReadPackets: skipped %ld frames\n", skipFrames);
927
mFramesToSkipFollowingSeek -= skipFrames;
929
// restore mDataByteSize
930
for (int i = ioData->mNumberBuffers; --i >= 0 ; )
931
ioData->mBuffers[i].mDataByteSize = bufferSizeBytes;
935
if (mFileDataFormat.mFramesPerPacket > 0)
936
// don't read more packets than we are being asked to produce
937
mMaxPacketsToRead = nPackets / mFileDataFormat.mFramesPerPacket + 1;
938
if (mConverter == NULL) {
939
XThrowIfError(ReadInputProc(NULL, &nPackets, ioData, NULL, this), "read audio file");
941
#if CAAUDIOFILE_PROFILE
944
StartTiming(this, fill);
945
XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)");
946
ElapsedTime(this, fill, mTicksInConverter);
947
#if CAAUDIOFILE_PROFILE
948
mInConverter = false;
951
if (mClientDataFormat.mFramesPerPacket == 1)
952
mFrameMark += nPackets;
954
ioNumPackets = nPackets;
957
// _______________________________________________________________________________________
959
OSStatus CAAudioFile::ReadInputProc( AudioConverterRef inAudioConverter,
960
UInt32* ioNumberDataPackets,
961
AudioBufferList* ioData,
962
AudioStreamPacketDescription** outDataPacketDescription,
965
CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
968
SInt64 remainingPacketsInFile = This->mNumberPackets - This->mPacketMark;
969
if (remainingPacketsInFile <= 0) {
970
*ioNumberDataPackets = 0;
971
ioData->mBuffers[0].mDataByteSize = 0;
972
if (outDataPacketDescription)
973
*outDataPacketDescription = This->mPacketDescs;
975
printf("CAAudioFile::ReadInputProc: EOF\n");
977
return noErr; // not eofErr; EOF is signified by 0 packets/0 bytes
981
// determine how much to read
982
AudioBufferList *readBuffer;
984
if (inAudioConverter != NULL) {
985
// getting called from converter, need to use our I/O buffer
986
readBuffer = &This->mIOBufferList;
987
readPackets = This->mIOBufferSizePackets;
989
// getting called directly from ReadPackets, use supplied buffer
990
if (This->mFileMaxPacketSize == 0)
991
return kExtAudioFileError_MaxPacketSizeUnknown;
993
readPackets = std::min(*ioNumberDataPackets, readBuffer->mBuffers[0].mDataByteSize / This->mFileMaxPacketSize);
994
// don't attempt to read more packets than will fit in the buffer
996
// don't try to read past EOF
997
// if (readPackets > remainingPacketsInFile)
998
// readPackets = remainingPacketsInFile;
999
// don't read more packets than necessary to produce the requested amount of converted data
1000
if (readPackets > This->mMaxPacketsToRead) {
1002
printf("CAAudioFile::ReadInputProc: limiting read to %ld packets (from %ld)\n", This->mMaxPacketsToRead, readPackets);
1004
readPackets = This->mMaxPacketsToRead;
1011
StartTiming(This, read);
1012
StartTiming(This, readinconv);
1013
err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData);
1014
#if CAAUDIOFILE_PROFILE
1015
if (This->mInConverter) ElapsedTime(This, readinconv, This->mTicksInReadInConverter);
1017
ElapsedTime(This, read, This->mTicksInIO);
1020
DebugMessageN1("Error %ld from AudioFileReadPackets!!!\n", err);
1025
printf("CAAudioFile::ReadInputProc: read %ld packets (%qd-%qd), %ld bytes, err %ld\n", readPackets, This->mPacketMark, This->mPacketMark + readPackets, bytesRead, err);
1027
if (This->mPacketDescs) {
1028
for (UInt32 i = 0; i < readPackets; ++i) {
1029
printf(" read packet %qd : offset %qd, length %ld\n", This->mPacketMark + i, This->mPacketDescs[i].mStartOffset, This->mPacketDescs[i].mDataByteSize);
1032
printf(" read buffer:"); CAShowAudioBufferList(readBuffer, 0, 4);
1035
if (readPackets == 0) {
1036
*ioNumberDataPackets = 0;
1037
ioData->mBuffers[0].mDataByteSize = 0;
1041
if (outDataPacketDescription)
1042
*outDataPacketDescription = This->mPacketDescs;
1043
ioData->mBuffers[0].mDataByteSize = bytesRead;
1044
ioData->mBuffers[0].mData = readBuffer->mBuffers[0].mData;
1046
This->mPacketMark += readPackets;
1047
if (This->mClientDataFormat.mFramesPerPacket != 1) { // for PCM client formats we update in Read
1048
// but for non-PCM client format (weird case) we must update here/now
1049
if (This->mFileDataFormat.mFramesPerPacket > 0)
1050
This->mFrameMark += readPackets * This->mFileDataFormat.mFramesPerPacket;
1052
for (UInt32 i = 0; i < readPackets; ++i)
1053
This->mFrameMark += This->mPacketDescs[i].mVariableFramesInPacket;
1056
*ioNumberDataPackets = readPackets;
1060
// _______________________________________________________________________________________
1062
void CAAudioFile::Write(UInt32 numPackets, const AudioBufferList *data)
1064
if (mIOBufferList.mBuffers[0].mData == NULL) {
1066
printf("warning: CAAudioFile::AllocateBuffers called from WritePackets\n");
1071
if (mMode == kPreparingToWrite)
1074
XThrowIf(mMode != kWriting, kExtAudioFileError_InvalidOperationOrder, "can't write to this file");
1075
if (mConverter != NULL) {
1076
mWritePackets = numPackets;
1077
mWriteBufferList->SetFrom(data);
1078
WritePacketsFromCallback(WriteInputProc, this);
1080
StartTiming(this, write);
1081
XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, data->mBuffers[0].mDataByteSize,
1082
NULL, mPacketMark, &numPackets, data->mBuffers[0].mData),
1083
"write audio file");
1084
ElapsedTime(this, write, mTicksInIO);
1086
printf("CAAudioFile::WritePackets: wrote %ld packets at %qd, %ld bytes\n", numPackets, mPacketMark, data->mBuffers[0].mDataByteSize);
1089
mPacketMark += numPackets;
1090
if (mFileDataFormat.mFramesPerPacket > 0)
1091
mFrameMark += numPackets * mFileDataFormat.mFramesPerPacket;
1092
// else: shouldn't happen since we're only called when there's no converter
1096
// _______________________________________________________________________________________
1098
void CAAudioFile::FlushEncoder()
1100
if (mConverter != NULL) {
1101
mFinishingEncoding = true;
1102
WritePacketsFromCallback(WriteInputProc, this);
1103
mFinishingEncoding = false;
1105
// get priming info from converter, set it on the file
1106
if (mFileDataFormat.mBitsPerChannel == 0) {
1107
UInt32 propertySize;
1109
AudioConverterPrimeInfo primeInfo;
1110
propertySize = sizeof(primeInfo);
1112
err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
1114
AudioFilePacketTableInfo pti;
1115
propertySize = sizeof(pti);
1116
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
1118
//printf("old packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
1119
UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames;
1120
pti.mPrimingFrames = primeInfo.leadingFrames;
1121
pti.mRemainderFrames = primeInfo.trailingFrames;
1122
pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames;
1123
//printf("new packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
1124
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti), "couldn't set packet table info on audio file");
1131
// _______________________________________________________________________________________
1133
OSStatus CAAudioFile::WriteInputProc( AudioConverterRef /*inAudioConverter*/,
1134
UInt32 * ioNumberDataPackets,
1135
AudioBufferList* ioData,
1136
AudioStreamPacketDescription ** outDataPacketDescription,
1139
CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
1140
if (This->mFinishingEncoding) {
1141
*ioNumberDataPackets = 0;
1142
ioData->mBuffers[0].mDataByteSize = 0;
1143
ioData->mBuffers[0].mData = NULL;
1144
if (outDataPacketDescription)
1145
*outDataPacketDescription = NULL;
1148
UInt32 numPackets = This->mWritePackets;
1149
if (numPackets == 0) {
1150
return kNoMoreInputRightNow;
1152
This->mWriteBufferList->ToAudioBufferList(ioData);
1153
This->mWriteBufferList->BytesConsumed(numPackets * This->mClientDataFormat.mBytesPerFrame);
1154
*ioNumberDataPackets = numPackets;
1155
if (outDataPacketDescription)
1156
*outDataPacketDescription = NULL;
1157
This->mWritePackets -= numPackets;
1161
// _______________________________________________________________________________________
1164
static void hexdump(const void *addr, long len)
1166
const Byte *p = (Byte *)addr;
1169
if (len > 0x400) len = 0x400;
1172
int n = len > 16 ? 16 : len;
1173
printf("%08lX: ", offset);
1174
for (int i = 0; i < 16; ++i)
1176
printf("%02X ", p[i]);
1178
for (int i = 0; i < 16; ++i)
1180
putchar(p[i] >= ' ' && p[i] < 127 ? p[i] : '.');
1190
// _______________________________________________________________________________________
1192
void CAAudioFile::WritePacketsFromCallback(
1193
AudioConverterComplexInputDataProc inInputDataProc,
1194
void * inInputDataProcUserData)
1197
// keep writing until we exhaust the input (temporary stop), or produce no output (EOF)
1198
UInt32 numEncodedPackets = mIOBufferSizePackets;
1199
mIOBufferList.mBuffers[0].mDataByteSize = mIOBufferSizeBytes;
1200
#if CAAUDIOFILE_PROFILE
1201
mInConverter = true;
1203
StartTiming(this, fill);
1204
OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData,
1205
&numEncodedPackets, &mIOBufferList, mPacketDescs);
1206
ElapsedTime(this, fill, mTicksInConverter);
1207
#if CAAUDIOFILE_PROFILE
1208
mInConverter = false;
1210
XThrowIf(err != 0 && err != kNoMoreInputRightNow, err, "convert audio packets (write)");
1211
if (numEncodedPackets == 0)
1213
Byte *buf = (Byte *)mIOBufferList.mBuffers[0].mData;
1215
printf("CAAudioFile::WritePacketsFromCallback: wrote %ld packets, %ld bytes\n", numEncodedPackets, mIOBufferList.mBuffers[0].mDataByteSize);
1217
for (UInt32 i = 0; i < numEncodedPackets; ++i) {
1218
printf(" write packet %qd : offset %qd, length %ld\n", mPacketMark + i, mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
1220
hexdump(buf + mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
1225
StartTiming(this, write);
1226
XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, mIOBufferList.mBuffers[0].mDataByteSize, mPacketDescs, mPacketMark, &numEncodedPackets, buf), "write audio file");
1227
ElapsedTime(this, write, mTicksInIO);
1228
mPacketMark += numEncodedPackets;
1229
//mNumberPackets += numEncodedPackets;
1230
if (mFileDataFormat.mFramesPerPacket > 0)
1231
mFrameMark += numEncodedPackets * mFileDataFormat.mFramesPerPacket;
1233
for (UInt32 i = 0; i < numEncodedPackets; ++i)
1234
mFrameMark += mPacketDescs[i].mVariableFramesInPacket;
1236
if (err == kNoMoreInputRightNow)
1241
#endif // !CAAF_USE_EXTAUDIOFILE