David Robillard
7bd41538d9
git-svn-id: svn://localhost/ardour2/branches/midi@921 d708f5d6-7413-0410-9779-e7cbd77b26cf
1242 lines
45 KiB
C++
1242 lines
45 KiB
C++
/* Copyright: © Copyright 2005 Apple Computer, Inc. All rights reserved.
|
|
|
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc.
|
|
("Apple") in consideration of your agreement to the following terms, and your
|
|
use, installation, modification or redistribution of this Apple software
|
|
constitutes acceptance of these terms. If you do not agree with these terms,
|
|
please do not use, install, modify or redistribute this Apple software.
|
|
|
|
In consideration of your agreement to abide by the following terms, and subject
|
|
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
|
|
copyrights in this original Apple software (the "Apple Software"), to use,
|
|
reproduce, modify and redistribute the Apple Software, with or without
|
|
modifications, in source and/or binary forms; provided that if you redistribute
|
|
the Apple Software in its entirety and without modifications, you must retain
|
|
this notice and the following text and disclaimers in all such redistributions of
|
|
the Apple Software. Neither the name, trademarks, service marks or logos of
|
|
Apple Computer, Inc. may be used to endorse or promote products derived from the
|
|
Apple Software without specific prior written permission from Apple. Except as
|
|
expressly stated in this notice, no other rights or licenses, express or implied,
|
|
are granted by Apple herein, including but not limited to any patent rights that
|
|
may be infringed by your derivative works or by other works in which the Apple
|
|
Software may be incorporated.
|
|
|
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO
|
|
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
|
|
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
|
COMBINATION WITH YOUR PRODUCTS.
|
|
|
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
|
|
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
|
|
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
|
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/*=============================================================================
|
|
CAAudioFile.cpp
|
|
|
|
=============================================================================*/
|
|
|
|
#include "CAAudioFile.h"
|
|
|
|
#if !CAAF_USE_EXTAUDIOFILE
|
|
|
|
#include "CAXException.h"
|
|
#include <algorithm>
|
|
#include "CAHostTimeBase.h"
|
|
#include "CADebugMacros.h"
|
|
|
|
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
|
|
#include <AudioToolbox/AudioToolbox.h>
|
|
#else
|
|
#include <AudioFormat.h>
|
|
#endif
|
|
|
|
#if DEBUG
|
|
//#define VERBOSE_IO 1
|
|
//#define VERBOSE_CONVERTER 1
|
|
//#define VERBOSE_CHANNELMAP 1
|
|
//#define LOG_FUNCTION_ENTRIES 1
|
|
|
|
#if VERBOSE_CHANNELMAP
|
|
#include "CAChannelLayouts.h" // this is in Source/Tests/AudioFileTools/Utility
|
|
#endif
|
|
#endif
|
|
|
|
#if LOG_FUNCTION_ENTRIES
|
|
class FunctionLogger {
|
|
public:
|
|
FunctionLogger(const char *name, const char *fmt=NULL, ...) : mName(name) {
|
|
Indent();
|
|
printf("-> %s ", name);
|
|
if (fmt) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vprintf(fmt, args);
|
|
va_end(args);
|
|
}
|
|
printf("\n");
|
|
++sIndent;
|
|
}
|
|
~FunctionLogger() {
|
|
--sIndent;
|
|
Indent();
|
|
printf("<- %s\n", mName);
|
|
if (sIndent == 0)
|
|
printf("\n");
|
|
}
|
|
|
|
static void Indent() {
|
|
for (int i = sIndent; --i >= 0; ) {
|
|
putchar(' '); putchar(' ');
|
|
}
|
|
}
|
|
|
|
const char *mName;
|
|
static int sIndent;
|
|
};
|
|
int FunctionLogger::sIndent = 0;
|
|
|
|
#define LOG_FUNCTION(name, format, ...) FunctionLogger _flog(name, format, ## __VA_ARGS__);
|
|
#else
|
|
#define LOG_FUNCTION(name, format, foo)
|
|
#endif
|
|
|
|
static const UInt32 kDefaultIOBufferSizeBytes = 0x10000;
|
|
|
|
#if CAAUDIOFILE_PROFILE
|
|
#define StartTiming(af, starttime) UInt64 starttime = af->mProfiling ? CAHostTimeBase::GetTheCurrentTime() : 0
|
|
#define ElapsedTime(af, starttime, counter) if (af->mProfiling) counter += (CAHostTimeBase::GetTheCurrentTime() - starttime)
|
|
#else
|
|
#define StartTiming(af, starttime)
|
|
#define ElapsedTime(af, starttime, counter)
|
|
#endif
|
|
|
|
#define kNoMoreInputRightNow 'nein'
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
CAAudioFile::CAAudioFile() :
|
|
mAudioFile(0),
|
|
mUseCache(false),
|
|
mFinishingEncoding(false),
|
|
mMode(kClosed),
|
|
mFileDataOffset(-1),
|
|
mFramesToSkipFollowingSeek(0),
|
|
|
|
mClientOwnsIOBuffer(false),
|
|
mPacketDescs(NULL),
|
|
mNumPacketDescs(0),
|
|
mConverter(NULL),
|
|
mMagicCookie(NULL),
|
|
mWriteBufferList(NULL)
|
|
#if CAAUDIOFILE_PROFILE
|
|
,
|
|
mProfiling(false),
|
|
mTicksInConverter(0),
|
|
mTicksInReadInConverter(0),
|
|
mTicksInIO(0),
|
|
mInConverter(false)
|
|
#endif
|
|
{
|
|
mIOBufferList.mBuffers[0].mData = NULL;
|
|
mIOBufferList.mBuffers[0].mDataByteSize = 0;
|
|
mClientMaxPacketSize = 0;
|
|
mIOBufferSizeBytes = kDefaultIOBufferSizeBytes;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
CAAudioFile::~CAAudioFile()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::Close()
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::Close", NULL, NULL);
|
|
if (mMode == kClosed)
|
|
return;
|
|
if (mMode == kWriting)
|
|
FlushEncoder();
|
|
CloseConverter();
|
|
if (mAudioFile != 0 && mOwnOpenFile) {
|
|
AudioFileClose(mAudioFile);
|
|
mAudioFile = 0;
|
|
}
|
|
if (!mClientOwnsIOBuffer) {
|
|
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
|
|
mIOBufferList.mBuffers[0].mData = NULL;
|
|
mIOBufferList.mBuffers[0].mDataByteSize = 0;
|
|
}
|
|
delete[] mPacketDescs; mPacketDescs = NULL; mNumPacketDescs = 0;
|
|
delete[] mMagicCookie; mMagicCookie = NULL;
|
|
delete mWriteBufferList; mWriteBufferList = NULL;
|
|
mMode = kClosed;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::CloseConverter()
|
|
{
|
|
if (mConverter) {
|
|
#if VERBOSE_CONVERTER
|
|
printf("CAAudioFile %p : CloseConverter\n", this);
|
|
#endif
|
|
AudioConverterDispose(mConverter);
|
|
mConverter = NULL;
|
|
}
|
|
}
|
|
|
|
// =======================================================================================
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::Open(const FSRef &fsref)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::Open", "%p", this);
|
|
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
|
|
mFSRef = fsref;
|
|
XThrowIfError(AudioFileOpen(&mFSRef, fsRdPerm, 0, &mAudioFile), "open audio file");
|
|
mOwnOpenFile = true;
|
|
mMode = kReading;
|
|
GetExistingFileInfo();
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::Wrap(AudioFileID fileID, bool forWriting)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::Wrap", "%p", this);
|
|
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
|
|
|
|
mAudioFile = fileID;
|
|
mOwnOpenFile = false;
|
|
mMode = forWriting ? kPreparingToWrite : kReading;
|
|
GetExistingFileInfo();
|
|
if (forWriting)
|
|
FileFormatChanged();
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::CreateNew(const FSRef &parentDir, CFStringRef filename, AudioFileTypeID filetype, const AudioStreamBasicDescription &dataFormat, const AudioChannelLayout *layout)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::CreateNew", "%p", this);
|
|
XThrowIf(mMode != kClosed, kExtAudioFileError_InvalidOperationOrder, "file already open");
|
|
|
|
mFileDataFormat = dataFormat;
|
|
if (layout) {
|
|
mFileChannelLayout = layout;
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("PrepareNew passed channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
}
|
|
mMode = kPreparingToCreate;
|
|
FileFormatChanged(&parentDir, filename, filetype);
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
// called to create the file -- or update its format/channel layout/properties based on an encoder
|
|
// setting change
|
|
void CAAudioFile::FileFormatChanged(const FSRef *parentDir, CFStringRef filename, AudioFileTypeID filetype)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::FileFormatChanged", "%p", this);
|
|
XThrowIf(mMode != kPreparingToCreate && mMode != kPreparingToWrite, kExtAudioFileError_InvalidOperationOrder, "new file not prepared");
|
|
|
|
UInt32 propertySize;
|
|
OSStatus err;
|
|
AudioStreamBasicDescription saveFileDataFormat = mFileDataFormat;
|
|
|
|
#if VERBOSE_CONVERTER
|
|
mFileDataFormat.PrintFormat(stdout, "", "Specified file data format");
|
|
#endif
|
|
|
|
// Find out the actual format the converter will produce. This is necessary in
|
|
// case the bitrate has forced a lower sample rate, which needs to be set correctly
|
|
// in the stream description passed to AudioFileCreate.
|
|
if (mConverter != NULL) {
|
|
propertySize = sizeof(AudioStreamBasicDescription);
|
|
Float64 origSampleRate = mFileDataFormat.mSampleRate;
|
|
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCurrentOutputStreamDescription, &propertySize, &mFileDataFormat), "get audio converter's output stream description");
|
|
// do the same for the channel layout being output by the converter
|
|
#if VERBOSE_CONVERTER
|
|
mFileDataFormat.PrintFormat(stdout, "", "Converter output");
|
|
#endif
|
|
if (fiszero(mFileDataFormat.mSampleRate))
|
|
mFileDataFormat.mSampleRate = origSampleRate;
|
|
err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterOutputChannelLayout, &propertySize, NULL);
|
|
if (err == noErr && propertySize > 0) {
|
|
AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
|
|
err = AudioConverterGetProperty(mConverter, kAudioConverterOutputChannelLayout, &propertySize, layout);
|
|
if (err) {
|
|
free(layout);
|
|
XThrow(err, "couldn't get audio converter's output channel layout");
|
|
}
|
|
mFileChannelLayout = layout;
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("got new file's channel layout from converter: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
free(layout);
|
|
}
|
|
}
|
|
|
|
// create the output file
|
|
if (mMode == kPreparingToCreate) {
|
|
CAStreamBasicDescription newFileDataFormat = mFileDataFormat;
|
|
if (fiszero(newFileDataFormat.mSampleRate))
|
|
newFileDataFormat.mSampleRate = 44100; // just make something up for now
|
|
#if VERBOSE_CONVERTER
|
|
newFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
|
|
#endif
|
|
XThrowIfError(AudioFileCreate(parentDir, filename, filetype, &newFileDataFormat, 0, &mFSRef, &mAudioFile), "create audio file");
|
|
mMode = kPreparingToWrite;
|
|
mOwnOpenFile = true;
|
|
} else if (saveFileDataFormat != mFileDataFormat || fnotequal(saveFileDataFormat.mSampleRate, mFileDataFormat.mSampleRate)) {
|
|
// second check must be explicit since operator== on ASBD treats SR of zero as "don't care"
|
|
if (fiszero(mFileDataFormat.mSampleRate))
|
|
mFileDataFormat.mSampleRate = mClientDataFormat.mSampleRate;
|
|
#if VERBOSE_CONVERTER
|
|
mFileDataFormat.PrintFormat(stdout, "", "Applied to new file");
|
|
#endif
|
|
XThrowIf(fiszero(mFileDataFormat.mSampleRate), kExtAudioFileError_InvalidDataFormat, "file's sample rate is 0");
|
|
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyDataFormat, sizeof(AudioStreamBasicDescription), &mFileDataFormat), "couldn't update file's data format");
|
|
}
|
|
|
|
UInt32 deferSizeUpdates = 1;
|
|
err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyDeferSizeUpdates, sizeof(UInt32), &deferSizeUpdates);
|
|
|
|
if (mConverter != NULL) {
|
|
// encoder
|
|
// get the magic cookie, if any, from the converter
|
|
delete[] mMagicCookie; mMagicCookie = NULL;
|
|
mMagicCookieSize = 0;
|
|
|
|
err = AudioConverterGetPropertyInfo(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, NULL);
|
|
|
|
// we can get a noErr result and also a propertySize == 0
|
|
// -- if the file format does support magic cookies, but this file doesn't have one.
|
|
if (err == noErr && propertySize > 0) {
|
|
mMagicCookie = new Byte[propertySize];
|
|
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterCompressionMagicCookie, &propertySize, mMagicCookie), "get audio converter's magic cookie");
|
|
mMagicCookieSize = propertySize; // the converter lies and tell us the wrong size
|
|
// now set the magic cookie on the output file
|
|
UInt32 willEatTheCookie = false;
|
|
// the converter wants to give us one; will the file take it?
|
|
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData,
|
|
NULL, &willEatTheCookie);
|
|
if (err == noErr && willEatTheCookie) {
|
|
#if VERBOSE_CONVERTER
|
|
printf("Setting cookie on encoded file\n");
|
|
#endif
|
|
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, mMagicCookieSize, mMagicCookie), "set audio file's magic cookie");
|
|
}
|
|
}
|
|
|
|
// get maximum packet size
|
|
propertySize = sizeof(UInt32);
|
|
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertyMaximumOutputPacketSize, &propertySize, &mFileMaxPacketSize), "get audio converter's maximum output packet size");
|
|
|
|
AllocateBuffers(true /* okToFail */);
|
|
} else {
|
|
InitFileMaxPacketSize();
|
|
}
|
|
|
|
if (mFileChannelLayout.IsValid() && mFileChannelLayout.NumberChannels() > 2) {
|
|
// don't bother tagging mono/stereo files
|
|
UInt32 isWritable;
|
|
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, NULL, &isWritable);
|
|
if (!err && isWritable) {
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("writing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
err = AudioFileSetProperty(mAudioFile, kAudioFilePropertyChannelLayout,
|
|
mFileChannelLayout.Size(), &mFileChannelLayout.Layout());
|
|
if (err)
|
|
CAXException::Warning("could not set the file's channel layout", err);
|
|
} else {
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("file won't accept a channel layout (write)\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
UpdateClientMaxPacketSize(); // also sets mFrame0Offset
|
|
mPacketMark = 0;
|
|
mFrameMark = 0;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::InitFileMaxPacketSize()
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::InitFileMaxPacketSize", "%p", this);
|
|
UInt32 propertySize = sizeof(UInt32);
|
|
OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyMaximumPacketSize,
|
|
&propertySize, &mFileMaxPacketSize);
|
|
if (err) {
|
|
// workaround for 3361377: not all file formats' maximum packet sizes are supported
|
|
if (!mFileDataFormat.IsPCM())
|
|
XThrowIfError(err, "get audio file's maximum packet size");
|
|
mFileMaxPacketSize = mFileDataFormat.mBytesPerFrame;
|
|
}
|
|
AllocateBuffers(true /* okToFail */);
|
|
}
|
|
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
SInt64 CAAudioFile::FileDataOffset()
|
|
{
|
|
if (mFileDataOffset < 0) {
|
|
UInt32 propertySize = sizeof(SInt64);
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataOffset, &propertySize, &mFileDataOffset), "couldn't get file's data offset");
|
|
}
|
|
return mFileDataOffset;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
SInt64 CAAudioFile::GetNumberFrames() const
|
|
{
|
|
AudioFilePacketTableInfo pti;
|
|
UInt32 propertySize = sizeof(pti);
|
|
OSStatus err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
|
|
if (err == noErr)
|
|
return pti.mNumberValidFrames;
|
|
return mFileDataFormat.mFramesPerPacket * GetNumberPackets() - mFrame0Offset;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::SetNumberFrames(SInt64 nFrames)
|
|
{
|
|
XThrowIf(mFileDataFormat.mFramesPerPacket != 1, kExtAudioFileError_InvalidDataFormat, "SetNumberFrames only supported for PCM");
|
|
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, sizeof(SInt64), &nFrames), "Couldn't set number of packets on audio file");
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
// call for existing file, NOT new one - from Open() or Wrap()
|
|
void CAAudioFile::GetExistingFileInfo()
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::GetExistingFileInfo", "%p", this);
|
|
UInt32 propertySize;
|
|
OSStatus err;
|
|
|
|
// get mFileDataFormat
|
|
propertySize = sizeof(AudioStreamBasicDescription);
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyDataFormat, &propertySize, &mFileDataFormat), "get audio file's data format");
|
|
|
|
// get mFileChannelLayout
|
|
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, NULL);
|
|
if (err == noErr && propertySize > 0) {
|
|
AudioChannelLayout *layout = static_cast<AudioChannelLayout *>(malloc(propertySize));
|
|
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyChannelLayout, &propertySize, layout);
|
|
if (err == noErr) {
|
|
mFileChannelLayout = layout;
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("existing file's channel layout: %s\n", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
}
|
|
free(layout);
|
|
XThrowIfError(err, "get audio file's channel layout");
|
|
}
|
|
if (mMode != kReading)
|
|
return;
|
|
|
|
#if 0
|
|
// get mNumberPackets
|
|
propertySize = sizeof(mNumberPackets);
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyAudioDataPacketCount, &propertySize, &mNumberPackets), "get audio file's packet count");
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::GetExistingFileInfo: %qd packets\n", mNumberPackets);
|
|
#endif
|
|
#endif
|
|
|
|
// get mMagicCookie
|
|
err = AudioFileGetPropertyInfo(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL);
|
|
if (err == noErr && propertySize > 0) {
|
|
mMagicCookie = new Byte[propertySize];
|
|
mMagicCookieSize = propertySize;
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyMagicCookieData, &propertySize, mMagicCookie), "get audio file's magic cookie");
|
|
}
|
|
InitFileMaxPacketSize();
|
|
mPacketMark = 0;
|
|
mFrameMark = 0;
|
|
|
|
UpdateClientMaxPacketSize();
|
|
}
|
|
|
|
// =======================================================================================
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::SetFileChannelLayout(const CAAudioChannelLayout &layout)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::SetFileChannelLayout", "%p", this);
|
|
mFileChannelLayout = layout;
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("file channel layout set explicitly (%s): %s\n", mMode == kReading ? "read" : "write", CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
if (mMode != kReading)
|
|
FileFormatChanged();
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::SetClientFormat(const CAStreamBasicDescription &dataFormat, const CAAudioChannelLayout *layout)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::SetClientFormat", "%p", this);
|
|
XThrowIf(!dataFormat.IsPCM(), kExtAudioFileError_NonPCMClientFormat, "non-PCM client format on audio file");
|
|
|
|
bool dataFormatChanging = (mClientDataFormat.mFormatID == 0 || mClientDataFormat != dataFormat);
|
|
|
|
if (dataFormatChanging) {
|
|
CloseConverter();
|
|
if (mWriteBufferList) {
|
|
delete mWriteBufferList;
|
|
mWriteBufferList = NULL;
|
|
}
|
|
mClientDataFormat = dataFormat;
|
|
}
|
|
|
|
if (layout && layout->IsValid()) {
|
|
XThrowIf(layout->NumberChannels() != mClientDataFormat.NumberChannels(), kExtAudioFileError_InvalidChannelMap, "inappropriate channel map");
|
|
mClientChannelLayout = *layout;
|
|
}
|
|
|
|
bool differentLayouts;
|
|
if (mClientChannelLayout.IsValid()) {
|
|
if (mFileChannelLayout.IsValid()) {
|
|
differentLayouts = mClientChannelLayout.Tag() != mFileChannelLayout.Tag();
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("two valid layouts, %s\n", differentLayouts ? "different" : "same");
|
|
#endif
|
|
} else {
|
|
differentLayouts = false;
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("valid client layout, unknown file layout\n");
|
|
#endif
|
|
}
|
|
} else {
|
|
differentLayouts = false;
|
|
#if VERBOSE_CHANNELMAP
|
|
if (mFileChannelLayout.IsValid())
|
|
printf("valid file layout, unknown client layout\n");
|
|
else
|
|
printf("two invalid layouts\n");
|
|
#endif
|
|
}
|
|
|
|
if (mClientDataFormat != mFileDataFormat || differentLayouts) {
|
|
// We need an AudioConverter.
|
|
if (mMode == kReading) {
|
|
// file -> client (decode)
|
|
//mFileDataFormat.PrintFormat( stdout, "", "File: ");
|
|
//mClientDataFormat.PrintFormat(stdout, "", "Client: ");
|
|
|
|
if (mConverter == NULL)
|
|
XThrowIfError(AudioConverterNew(&mFileDataFormat, &mClientDataFormat, &mConverter),
|
|
"create audio converter");
|
|
|
|
#if VERBOSE_CONVERTER
|
|
printf("CAAudioFile %p -- created converter\n", this);
|
|
CAShow(mConverter);
|
|
#endif
|
|
// set the magic cookie, if any (for decode)
|
|
if (mMagicCookie)
|
|
SetConverterProperty(kAudioConverterDecompressionMagicCookie, mMagicCookieSize, mMagicCookie, mFileDataFormat.IsPCM());
|
|
// we get cookies from some AIFF's but the converter barfs on them,
|
|
// so we set canFail to true for PCM
|
|
|
|
SetConverterChannelLayout(false, mFileChannelLayout);
|
|
SetConverterChannelLayout(true, mClientChannelLayout);
|
|
|
|
// propagate leading/trailing frame counts
|
|
if (mFileDataFormat.mBitsPerChannel == 0) {
|
|
UInt32 propertySize;
|
|
OSStatus err;
|
|
AudioFilePacketTableInfo pti;
|
|
propertySize = sizeof(pti);
|
|
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
|
|
if (err == noErr && (pti.mPrimingFrames > 0 || pti.mRemainderFrames > 0)) {
|
|
AudioConverterPrimeInfo primeInfo;
|
|
primeInfo.leadingFrames = pti.mPrimingFrames;
|
|
primeInfo.trailingFrames = pti.mRemainderFrames;
|
|
/* ignore any error. better to play it at all than not. */
|
|
/*err = */AudioConverterSetProperty(mConverter, kAudioConverterPrimeInfo, sizeof(primeInfo), &primeInfo);
|
|
//XThrowIfError(err, "couldn't set prime info on converter");
|
|
}
|
|
}
|
|
} else if (mMode == kPreparingToCreate || mMode == kPreparingToWrite) {
|
|
// client -> file (encode)
|
|
if (mConverter == NULL)
|
|
XThrowIfError(AudioConverterNew(&mClientDataFormat, &mFileDataFormat, &mConverter), "create audio converter");
|
|
mWriteBufferList = CABufferList::New("", mClientDataFormat);
|
|
SetConverterChannelLayout(false, mClientChannelLayout);
|
|
SetConverterChannelLayout(true, mFileChannelLayout);
|
|
if (mMode == kPreparingToWrite)
|
|
FileFormatChanged();
|
|
} else
|
|
XThrowIfError(kExtAudioFileError_InvalidOperationOrder, "audio file format not yet known");
|
|
}
|
|
UpdateClientMaxPacketSize();
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
OSStatus CAAudioFile::SetConverterProperty(
|
|
AudioConverterPropertyID inPropertyID,
|
|
UInt32 inPropertyDataSize,
|
|
const void* inPropertyData,
|
|
bool inCanFail)
|
|
{
|
|
OSStatus err = noErr;
|
|
//LOG_FUNCTION("ExtAudioFile::SetConverterProperty", "%p %-4.4s", this, (char *)&inPropertyID);
|
|
if (inPropertyID == kAudioConverterPropertySettings && *(CFPropertyListRef *)inPropertyData == NULL)
|
|
;
|
|
else {
|
|
err = AudioConverterSetProperty(mConverter, inPropertyID, inPropertyDataSize, inPropertyData);
|
|
if (!inCanFail) {
|
|
XThrowIfError(err, "set audio converter property");
|
|
}
|
|
}
|
|
UpdateClientMaxPacketSize();
|
|
if (mMode == kPreparingToWrite)
|
|
FileFormatChanged();
|
|
return err;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::SetConverterChannelLayout(bool output, const CAAudioChannelLayout &layout)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::SetConverterChannelLayout", "%p", this);
|
|
OSStatus err;
|
|
|
|
if (layout.IsValid()) {
|
|
#if VERBOSE_CHANNELMAP
|
|
printf("Setting converter's %s channel layout: %s\n", output ? "output" : "input",
|
|
CAChannelLayouts::ConstantToString(mFileChannelLayout.Tag()));
|
|
#endif
|
|
if (output) {
|
|
err = AudioConverterSetProperty(mConverter, kAudioConverterOutputChannelLayout,
|
|
layout.Size(), &layout.Layout());
|
|
XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's output channel layout");
|
|
} else {
|
|
err = AudioConverterSetProperty(mConverter, kAudioConverterInputChannelLayout,
|
|
layout.Size(), &layout.Layout());
|
|
XThrowIf(err && err != kAudioConverterErr_OperationNotSupported, err, "couldn't set converter's input channel layout");
|
|
}
|
|
if (mMode == kPreparingToWrite)
|
|
FileFormatChanged();
|
|
}
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
CFArrayRef CAAudioFile::GetConverterConfig()
|
|
{
|
|
CFArrayRef plist;
|
|
UInt32 propertySize = sizeof(plist);
|
|
XThrowIfError(AudioConverterGetProperty(mConverter, kAudioConverterPropertySettings, &propertySize, &plist), "get converter property settings");
|
|
return plist;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::UpdateClientMaxPacketSize()
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::UpdateClientMaxPacketSize", "%p", this);
|
|
mFrame0Offset = 0;
|
|
if (mConverter != NULL) {
|
|
AudioConverterPropertyID property = (mMode == kReading) ?
|
|
kAudioConverterPropertyMaximumOutputPacketSize :
|
|
kAudioConverterPropertyMaximumInputPacketSize;
|
|
|
|
UInt32 propertySize = sizeof(UInt32);
|
|
XThrowIfError(AudioConverterGetProperty(mConverter, property, &propertySize, &mClientMaxPacketSize),
|
|
"get audio converter's maximum packet size");
|
|
|
|
if (mFileDataFormat.mBitsPerChannel == 0) {
|
|
AudioConverterPrimeInfo primeInfo;
|
|
propertySize = sizeof(primeInfo);
|
|
OSStatus err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
|
|
if (err == noErr)
|
|
mFrame0Offset = primeInfo.leadingFrames;
|
|
#if VERBOSE_CONVERTER
|
|
printf("kAudioConverterPrimeInfo: err = %ld, leadingFrames = %ld\n", err, mFrame0Offset);
|
|
#endif
|
|
}
|
|
} else {
|
|
mClientMaxPacketSize = mFileMaxPacketSize;
|
|
}
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
// Allocates: mIOBufferList, mIOBufferSizePackets, mPacketDescs
|
|
// Dependent on: mFileMaxPacketSize, mIOBufferSizeBytes
|
|
void CAAudioFile::AllocateBuffers(bool okToFail)
|
|
{
|
|
LOG_FUNCTION("CAAudioFile::AllocateBuffers", "%p", this);
|
|
if (mFileMaxPacketSize == 0) {
|
|
if (okToFail)
|
|
return;
|
|
XThrowIf(true, kExtAudioFileError_MaxPacketSizeUnknown, "file's maximum packet size is 0");
|
|
}
|
|
UInt32 bufferSizeBytes = mIOBufferSizeBytes = std::max(mIOBufferSizeBytes, mFileMaxPacketSize);
|
|
// must be big enough for at least one maximum size packet
|
|
|
|
if (mIOBufferList.mBuffers[0].mDataByteSize != bufferSizeBytes) {
|
|
mIOBufferList.mNumberBuffers = 1;
|
|
mIOBufferList.mBuffers[0].mNumberChannels = mFileDataFormat.mChannelsPerFrame;
|
|
if (!mClientOwnsIOBuffer) {
|
|
//printf("reallocating I/O buffer\n");
|
|
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
|
|
mIOBufferList.mBuffers[0].mData = new Byte[bufferSizeBytes];
|
|
}
|
|
mIOBufferList.mBuffers[0].mDataByteSize = bufferSizeBytes;
|
|
mIOBufferSizePackets = bufferSizeBytes / mFileMaxPacketSize;
|
|
}
|
|
|
|
UInt32 propertySize = sizeof(UInt32);
|
|
UInt32 externallyFramed;
|
|
XThrowIfError(AudioFormatGetProperty(kAudioFormatProperty_FormatIsExternallyFramed,
|
|
sizeof(AudioStreamBasicDescription), &mFileDataFormat, &propertySize, &externallyFramed),
|
|
"is format externally framed");
|
|
if (mNumPacketDescs != (externallyFramed ? mIOBufferSizePackets : 0)) {
|
|
delete[] mPacketDescs;
|
|
mPacketDescs = NULL;
|
|
mNumPacketDescs = 0;
|
|
|
|
if (externallyFramed) {
|
|
//printf("reallocating packet descs\n");
|
|
mPacketDescs = new AudioStreamPacketDescription[mIOBufferSizePackets];
|
|
mNumPacketDescs = mIOBufferSizePackets;
|
|
}
|
|
}
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::SetIOBuffer(void *buf)
|
|
{
|
|
if (!mClientOwnsIOBuffer)
|
|
delete[] (Byte *)mIOBufferList.mBuffers[0].mData;
|
|
mIOBufferList.mBuffers[0].mData = buf;
|
|
|
|
if (buf == NULL) {
|
|
mClientOwnsIOBuffer = false;
|
|
SetIOBufferSizeBytes(mIOBufferSizeBytes);
|
|
} else {
|
|
mClientOwnsIOBuffer = true;
|
|
AllocateBuffers();
|
|
}
|
|
// printf("CAAudioFile::SetIOBuffer %p: %p, 0x%lx bytes, mClientOwns = %d\n", this, mIOBufferList.mBuffers[0].mData, mIOBufferSizeBytes, mClientOwnsIOBuffer);
|
|
}
|
|
|
|
// ===============================================================================
|
|
|
|
/*
|
|
For Tiger:
|
|
added kAudioFilePropertyPacketToFrame and kAudioFilePropertyFrameToPacket.
|
|
You pass in an AudioFramePacketTranslation struct, with the appropriate field filled in, to AudioFileGetProperty.
|
|
|
|
kAudioFilePropertyPacketToFrame = 'pkfr',
|
|
// pass a AudioFramePacketTranslation with mPacket filled out and get mFrame back. mFrameOffsetInPacket is ignored.
|
|
kAudioFilePropertyFrameToPacket = 'frpk',
|
|
// pass a AudioFramePacketTranslation with mFrame filled out and get mPacket and mFrameOffsetInPacket back.
|
|
|
|
struct AudioFramePacketTranslation
|
|
{
|
|
SInt64 mFrame;
|
|
SInt64 mPacket;
|
|
UInt32 mFrameOffsetInPacket;
|
|
};
|
|
*/
|
|
|
|
SInt64 CAAudioFile::PacketToFrame(SInt64 packet) const
|
|
{
|
|
AudioFramePacketTranslation trans;
|
|
UInt32 propertySize;
|
|
|
|
switch (mFileDataFormat.mFramesPerPacket) {
|
|
case 1:
|
|
return packet;
|
|
case 0:
|
|
trans.mPacket = packet;
|
|
propertySize = sizeof(trans);
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketToFrame, &propertySize, &trans),
|
|
"packet <-> frame translation unimplemented for format with variable frames/packet");
|
|
return trans.mFrame;
|
|
}
|
|
return packet * mFileDataFormat.mFramesPerPacket;
|
|
}
|
|
|
|
SInt64 CAAudioFile::FrameToPacket(SInt64 inFrame) const
|
|
{
|
|
AudioFramePacketTranslation trans;
|
|
UInt32 propertySize;
|
|
|
|
switch (mFileDataFormat.mFramesPerPacket) {
|
|
case 1:
|
|
return inFrame;
|
|
case 0:
|
|
trans.mFrame = inFrame;
|
|
propertySize = sizeof(trans);
|
|
XThrowIfError(AudioFileGetProperty(mAudioFile, kAudioFilePropertyFrameToPacket, &propertySize, &trans),
|
|
"packet <-> frame translation unimplemented for format with variable frames/packet");
|
|
return trans.mPacket;
|
|
}
|
|
return inFrame / mFileDataFormat.mFramesPerPacket;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
|
|
SInt64 CAAudioFile::Tell() const // frameNumber
|
|
{
|
|
return mFrameMark - mFrame0Offset;
|
|
}
|
|
|
|
void CAAudioFile::SeekToPacket(SInt64 packetNumber)
|
|
{
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::SeekToPacket: %qd\n", packetNumber);
|
|
#endif
|
|
XThrowIf(mMode != kReading || packetNumber < 0 /*|| packetNumber >= mNumberPackets*/ , kExtAudioFileError_InvalidSeek, "seek to packet in audio file");
|
|
if (mPacketMark == packetNumber)
|
|
return; // already there! don't reset converter
|
|
mPacketMark = packetNumber;
|
|
|
|
mFrameMark = PacketToFrame(packetNumber) - mFrame0Offset;
|
|
mFramesToSkipFollowingSeek = 0;
|
|
if (mConverter)
|
|
// must reset -- if we reached end of stream. converter will no longer work otherwise
|
|
AudioConverterReset(mConverter);
|
|
}
|
|
|
|
/*
|
|
Example: AAC, 1024 frames/packet, 2112 frame offset
|
|
|
|
2112
|
|
|
|
|
Absolute frames: 0 1024 2048 | 3072
|
|
+---------+---------+--|------+---------+---------+
|
|
Packets: | 0 | 1 | | 2 | 3 | 4 |
|
|
+---------+---------+--|------+---------+---------+
|
|
Client frames: -2112 -1088 -64 | 960 SeekToFrame, TellFrame
|
|
|
|
|
0
|
|
|
|
* Offset between absolute and client frames is mFrame0Offset.
|
|
*** mFrameMark is in client frames ***
|
|
|
|
Examples:
|
|
clientFrame 0 960 1000 1024
|
|
absoluteFrame 2112 3072 3112 3136
|
|
packet 0 0 0 1
|
|
tempFrameMark* -2112 -2112 -2112 -1088
|
|
mFramesToSkipFollowingSeek 2112 3072 3112 2112
|
|
*/
|
|
void CAAudioFile::Seek(SInt64 clientFrame)
|
|
{
|
|
if (clientFrame == mFrameMark)
|
|
return; // already there! don't reset converter
|
|
|
|
//SInt64 absoluteFrame = clientFrame + mFrame0Offset;
|
|
XThrowIf(mMode != kReading || clientFrame < 0 || !mClientDataFormat.IsPCM(), kExtAudioFileError_InvalidSeek, "seek to frame in audio file");
|
|
|
|
#if VERBOSE_IO
|
|
SInt64 prevFrameMark = mFrameMark;
|
|
#endif
|
|
|
|
SInt64 packet;
|
|
packet = FrameToPacket(clientFrame);
|
|
if (packet < 0)
|
|
packet = 0;
|
|
SeekToPacket(packet);
|
|
// this will have backed up mFrameMark to match the beginning of the packet
|
|
mFramesToSkipFollowingSeek = std::max(UInt32(clientFrame - mFrameMark), UInt32(0));
|
|
mFrameMark = clientFrame;
|
|
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::SeekToFrame: frame %qd (from %qd), packet %qd, skip %ld frames\n", mFrameMark, prevFrameMark, packet, mFramesToSkipFollowingSeek);
|
|
#endif
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::Read(UInt32 &ioNumPackets, AudioBufferList *ioData)
|
|
// May read fewer packets than requested if:
|
|
// buffer is not big enough
|
|
// file does not contain that many more packets
|
|
// Note that eofErr is not fatal, just results in 0 packets returned
|
|
// ioData's buffer sizes may be shortened
|
|
{
|
|
XThrowIf(mClientMaxPacketSize == 0, kExtAudioFileError_MaxPacketSizeUnknown, "client maximum packet size is 0");
|
|
if (mIOBufferList.mBuffers[0].mData == NULL) {
|
|
#if DEBUG
|
|
printf("warning: CAAudioFile::AllocateBuffers called from ReadPackets\n");
|
|
#endif
|
|
AllocateBuffers();
|
|
}
|
|
UInt32 bufferSizeBytes = ioData->mBuffers[0].mDataByteSize;
|
|
UInt32 maxNumPackets = bufferSizeBytes / mClientMaxPacketSize;
|
|
// older versions of AudioConverterFillComplexBuffer don't do this, so do our own sanity check
|
|
UInt32 nPackets = std::min(ioNumPackets, maxNumPackets);
|
|
|
|
mMaxPacketsToRead = ~0UL;
|
|
|
|
if (mClientDataFormat.mFramesPerPacket == 1) { // PCM or equivalent
|
|
while (mFramesToSkipFollowingSeek > 0) {
|
|
UInt32 skipFrames = std::min(mFramesToSkipFollowingSeek, maxNumPackets);
|
|
UInt32 framesPerPacket;
|
|
if ((framesPerPacket=mFileDataFormat.mFramesPerPacket) > 0)
|
|
mMaxPacketsToRead = (skipFrames + framesPerPacket - 1) / framesPerPacket;
|
|
|
|
if (mConverter == NULL) {
|
|
XThrowIfError(ReadInputProc(NULL, &skipFrames, ioData, NULL, this), "read audio file");
|
|
} else {
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = true;
|
|
#endif
|
|
StartTiming(this, fill);
|
|
XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &skipFrames, ioData, NULL), "convert audio packets (pcm read)");
|
|
ElapsedTime(this, fill, mTicksInConverter);
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = false;
|
|
#endif
|
|
}
|
|
if (skipFrames == 0) { // hit EOF
|
|
ioNumPackets = 0;
|
|
return;
|
|
}
|
|
mFrameMark += skipFrames;
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::ReadPackets: skipped %ld frames\n", skipFrames);
|
|
#endif
|
|
|
|
mFramesToSkipFollowingSeek -= skipFrames;
|
|
|
|
// restore mDataByteSize
|
|
for (int i = ioData->mNumberBuffers; --i >= 0 ; )
|
|
ioData->mBuffers[i].mDataByteSize = bufferSizeBytes;
|
|
}
|
|
}
|
|
|
|
if (mFileDataFormat.mFramesPerPacket > 0)
|
|
// don't read more packets than we are being asked to produce
|
|
mMaxPacketsToRead = nPackets / mFileDataFormat.mFramesPerPacket + 1;
|
|
if (mConverter == NULL) {
|
|
XThrowIfError(ReadInputProc(NULL, &nPackets, ioData, NULL, this), "read audio file");
|
|
} else {
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = true;
|
|
#endif
|
|
StartTiming(this, fill);
|
|
XThrowIfError(AudioConverterFillComplexBuffer(mConverter, ReadInputProc, this, &nPackets, ioData, NULL), "convert audio packets (read)");
|
|
ElapsedTime(this, fill, mTicksInConverter);
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = false;
|
|
#endif
|
|
}
|
|
if (mClientDataFormat.mFramesPerPacket == 1)
|
|
mFrameMark += nPackets;
|
|
|
|
ioNumPackets = nPackets;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
OSStatus CAAudioFile::ReadInputProc( AudioConverterRef inAudioConverter,
|
|
UInt32* ioNumberDataPackets,
|
|
AudioBufferList* ioData,
|
|
AudioStreamPacketDescription** outDataPacketDescription,
|
|
void* inUserData)
|
|
{
|
|
CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
|
|
|
|
#if 0
|
|
SInt64 remainingPacketsInFile = This->mNumberPackets - This->mPacketMark;
|
|
if (remainingPacketsInFile <= 0) {
|
|
*ioNumberDataPackets = 0;
|
|
ioData->mBuffers[0].mDataByteSize = 0;
|
|
if (outDataPacketDescription)
|
|
*outDataPacketDescription = This->mPacketDescs;
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::ReadInputProc: EOF\n");
|
|
#endif
|
|
return noErr; // not eofErr; EOF is signified by 0 packets/0 bytes
|
|
}
|
|
#endif
|
|
|
|
// determine how much to read
|
|
AudioBufferList *readBuffer;
|
|
UInt32 readPackets;
|
|
if (inAudioConverter != NULL) {
|
|
// getting called from converter, need to use our I/O buffer
|
|
readBuffer = &This->mIOBufferList;
|
|
readPackets = This->mIOBufferSizePackets;
|
|
} else {
|
|
// getting called directly from ReadPackets, use supplied buffer
|
|
if (This->mFileMaxPacketSize == 0)
|
|
return kExtAudioFileError_MaxPacketSizeUnknown;
|
|
readBuffer = ioData;
|
|
readPackets = std::min(*ioNumberDataPackets, readBuffer->mBuffers[0].mDataByteSize / This->mFileMaxPacketSize);
|
|
// don't attempt to read more packets than will fit in the buffer
|
|
}
|
|
// don't try to read past EOF
|
|
// if (readPackets > remainingPacketsInFile)
|
|
// readPackets = remainingPacketsInFile;
|
|
// don't read more packets than necessary to produce the requested amount of converted data
|
|
if (readPackets > This->mMaxPacketsToRead) {
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::ReadInputProc: limiting read to %ld packets (from %ld)\n", This->mMaxPacketsToRead, readPackets);
|
|
#endif
|
|
readPackets = This->mMaxPacketsToRead;
|
|
}
|
|
|
|
// read
|
|
UInt32 bytesRead;
|
|
OSStatus err;
|
|
|
|
StartTiming(This, read);
|
|
StartTiming(This, readinconv);
|
|
err = AudioFileReadPackets(This->mAudioFile, This->mUseCache, &bytesRead, This->mPacketDescs, This->mPacketMark, &readPackets, readBuffer->mBuffers[0].mData);
|
|
#if CAAUDIOFILE_PROFILE
|
|
if (This->mInConverter) ElapsedTime(This, readinconv, This->mTicksInReadInConverter);
|
|
#endif
|
|
ElapsedTime(This, read, This->mTicksInIO);
|
|
|
|
if (err) {
|
|
DebugMessageN1("Error %ld from AudioFileReadPackets!!!\n", err);
|
|
return err;
|
|
}
|
|
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::ReadInputProc: read %ld packets (%qd-%qd), %ld bytes, err %ld\n", readPackets, This->mPacketMark, This->mPacketMark + readPackets, bytesRead, err);
|
|
#if VERBOSE_IO >= 2
|
|
if (This->mPacketDescs) {
|
|
for (UInt32 i = 0; i < readPackets; ++i) {
|
|
printf(" read packet %qd : offset %qd, length %ld\n", This->mPacketMark + i, This->mPacketDescs[i].mStartOffset, This->mPacketDescs[i].mDataByteSize);
|
|
}
|
|
}
|
|
printf(" read buffer:"); CAShowAudioBufferList(readBuffer, 0, 4);
|
|
#endif
|
|
#endif
|
|
if (readPackets == 0) {
|
|
*ioNumberDataPackets = 0;
|
|
ioData->mBuffers[0].mDataByteSize = 0;
|
|
return noErr;
|
|
}
|
|
|
|
if (outDataPacketDescription)
|
|
*outDataPacketDescription = This->mPacketDescs;
|
|
ioData->mBuffers[0].mDataByteSize = bytesRead;
|
|
ioData->mBuffers[0].mData = readBuffer->mBuffers[0].mData;
|
|
|
|
This->mPacketMark += readPackets;
|
|
if (This->mClientDataFormat.mFramesPerPacket != 1) { // for PCM client formats we update in Read
|
|
// but for non-PCM client format (weird case) we must update here/now
|
|
if (This->mFileDataFormat.mFramesPerPacket > 0)
|
|
This->mFrameMark += readPackets * This->mFileDataFormat.mFramesPerPacket;
|
|
else {
|
|
for (UInt32 i = 0; i < readPackets; ++i)
|
|
This->mFrameMark += This->mPacketDescs[i].mVariableFramesInPacket;
|
|
}
|
|
}
|
|
*ioNumberDataPackets = readPackets;
|
|
return noErr;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::Write(UInt32 numPackets, const AudioBufferList *data)
|
|
{
|
|
if (mIOBufferList.mBuffers[0].mData == NULL) {
|
|
#if DEBUG
|
|
printf("warning: CAAudioFile::AllocateBuffers called from WritePackets\n");
|
|
#endif
|
|
AllocateBuffers();
|
|
}
|
|
|
|
if (mMode == kPreparingToWrite)
|
|
mMode = kWriting;
|
|
else
|
|
XThrowIf(mMode != kWriting, kExtAudioFileError_InvalidOperationOrder, "can't write to this file");
|
|
if (mConverter != NULL) {
|
|
mWritePackets = numPackets;
|
|
mWriteBufferList->SetFrom(data);
|
|
WritePacketsFromCallback(WriteInputProc, this);
|
|
} else {
|
|
StartTiming(this, write);
|
|
XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, data->mBuffers[0].mDataByteSize,
|
|
NULL, mPacketMark, &numPackets, data->mBuffers[0].mData),
|
|
"write audio file");
|
|
ElapsedTime(this, write, mTicksInIO);
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::WritePackets: wrote %ld packets at %qd, %ld bytes\n", numPackets, mPacketMark, data->mBuffers[0].mDataByteSize);
|
|
#endif
|
|
//mNumberPackets =
|
|
mPacketMark += numPackets;
|
|
if (mFileDataFormat.mFramesPerPacket > 0)
|
|
mFrameMark += numPackets * mFileDataFormat.mFramesPerPacket;
|
|
// else: shouldn't happen since we're only called when there's no converter
|
|
}
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::FlushEncoder()
|
|
{
|
|
if (mConverter != NULL) {
|
|
mFinishingEncoding = true;
|
|
WritePacketsFromCallback(WriteInputProc, this);
|
|
mFinishingEncoding = false;
|
|
|
|
// get priming info from converter, set it on the file
|
|
if (mFileDataFormat.mBitsPerChannel == 0) {
|
|
UInt32 propertySize;
|
|
OSStatus err;
|
|
AudioConverterPrimeInfo primeInfo;
|
|
propertySize = sizeof(primeInfo);
|
|
|
|
err = AudioConverterGetProperty(mConverter, kAudioConverterPrimeInfo, &propertySize, &primeInfo);
|
|
if (err == noErr) {
|
|
AudioFilePacketTableInfo pti;
|
|
propertySize = sizeof(pti);
|
|
err = AudioFileGetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, &propertySize, &pti);
|
|
if (err == noErr) {
|
|
//printf("old packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
|
|
UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames;
|
|
pti.mPrimingFrames = primeInfo.leadingFrames;
|
|
pti.mRemainderFrames = primeInfo.trailingFrames;
|
|
pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames;
|
|
//printf("new packet table info: %qd valid, %ld priming, %ld remainder\n", pti.mNumberValidFrames, pti.mPrimingFrames, pti.mRemainderFrames);
|
|
XThrowIfError(AudioFileSetProperty(mAudioFile, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti), "couldn't set packet table info on audio file");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
OSStatus CAAudioFile::WriteInputProc( AudioConverterRef /*inAudioConverter*/,
|
|
UInt32 * ioNumberDataPackets,
|
|
AudioBufferList* ioData,
|
|
AudioStreamPacketDescription ** outDataPacketDescription,
|
|
void* inUserData)
|
|
{
|
|
CAAudioFile *This = static_cast<CAAudioFile *>(inUserData);
|
|
if (This->mFinishingEncoding) {
|
|
*ioNumberDataPackets = 0;
|
|
ioData->mBuffers[0].mDataByteSize = 0;
|
|
ioData->mBuffers[0].mData = NULL;
|
|
if (outDataPacketDescription)
|
|
*outDataPacketDescription = NULL;
|
|
return noErr;
|
|
}
|
|
UInt32 numPackets = This->mWritePackets;
|
|
if (numPackets == 0) {
|
|
return kNoMoreInputRightNow;
|
|
}
|
|
This->mWriteBufferList->ToAudioBufferList(ioData);
|
|
This->mWriteBufferList->BytesConsumed(numPackets * This->mClientDataFormat.mBytesPerFrame);
|
|
*ioNumberDataPackets = numPackets;
|
|
if (outDataPacketDescription)
|
|
*outDataPacketDescription = NULL;
|
|
This->mWritePackets -= numPackets;
|
|
return noErr;
|
|
}
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
#if VERBOSE_IO
|
|
static void hexdump(const void *addr, long len)
|
|
{
|
|
const Byte *p = (Byte *)addr;
|
|
UInt32 offset = 0;
|
|
|
|
if (len > 0x400) len = 0x400;
|
|
|
|
while (len > 0) {
|
|
int n = len > 16 ? 16 : len;
|
|
printf("%08lX: ", offset);
|
|
for (int i = 0; i < 16; ++i)
|
|
if (i < n)
|
|
printf("%02X ", p[i]);
|
|
else printf(" ");
|
|
for (int i = 0; i < 16; ++i)
|
|
if (i < n)
|
|
putchar(p[i] >= ' ' && p[i] < 127 ? p[i] : '.');
|
|
else putchar(' ');
|
|
putchar('\n');
|
|
p += 16;
|
|
len -= 16;
|
|
offset += 16;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// _______________________________________________________________________________________
|
|
//
|
|
void CAAudioFile::WritePacketsFromCallback(
|
|
AudioConverterComplexInputDataProc inInputDataProc,
|
|
void * inInputDataProcUserData)
|
|
{
|
|
while (true) {
|
|
// keep writing until we exhaust the input (temporary stop), or produce no output (EOF)
|
|
UInt32 numEncodedPackets = mIOBufferSizePackets;
|
|
mIOBufferList.mBuffers[0].mDataByteSize = mIOBufferSizeBytes;
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = true;
|
|
#endif
|
|
StartTiming(this, fill);
|
|
OSStatus err = AudioConverterFillComplexBuffer(mConverter, inInputDataProc, inInputDataProcUserData,
|
|
&numEncodedPackets, &mIOBufferList, mPacketDescs);
|
|
ElapsedTime(this, fill, mTicksInConverter);
|
|
#if CAAUDIOFILE_PROFILE
|
|
mInConverter = false;
|
|
#endif
|
|
XThrowIf(err != 0 && err != kNoMoreInputRightNow, err, "convert audio packets (write)");
|
|
if (numEncodedPackets == 0)
|
|
break;
|
|
Byte *buf = (Byte *)mIOBufferList.mBuffers[0].mData;
|
|
#if VERBOSE_IO
|
|
printf("CAAudioFile::WritePacketsFromCallback: wrote %ld packets, %ld bytes\n", numEncodedPackets, mIOBufferList.mBuffers[0].mDataByteSize);
|
|
if (mPacketDescs) {
|
|
for (UInt32 i = 0; i < numEncodedPackets; ++i) {
|
|
printf(" write packet %qd : offset %qd, length %ld\n", mPacketMark + i, mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
|
|
#if VERBOSE_IO >= 2
|
|
hexdump(buf + mPacketDescs[i].mStartOffset, mPacketDescs[i].mDataByteSize);
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
StartTiming(this, write);
|
|
XThrowIfError(AudioFileWritePackets(mAudioFile, mUseCache, mIOBufferList.mBuffers[0].mDataByteSize, mPacketDescs, mPacketMark, &numEncodedPackets, buf), "write audio file");
|
|
ElapsedTime(this, write, mTicksInIO);
|
|
mPacketMark += numEncodedPackets;
|
|
//mNumberPackets += numEncodedPackets;
|
|
if (mFileDataFormat.mFramesPerPacket > 0)
|
|
mFrameMark += numEncodedPackets * mFileDataFormat.mFramesPerPacket;
|
|
else {
|
|
for (UInt32 i = 0; i < numEncodedPackets; ++i)
|
|
mFrameMark += mPacketDescs[i].mVariableFramesInPacket;
|
|
}
|
|
if (err == kNoMoreInputRightNow)
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif // !CAAF_USE_EXTAUDIOFILE
|