PublicUtility/CAStreamBasicDescription.cpp
/* |
File: CAStreamBasicDescription.cpp |
Abstract: CAStreamBasicDescription.h |
Version: 1.2.2 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
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 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. |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
*/ |
#include "CAStreamBasicDescription.h" |
#include "CAMath.h" |
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) |
#include <CoreFoundation/CFByteOrder.h> |
#else |
#include <CFByteOrder.h> |
#endif |
#pragma mark This file needs to compile on earlier versions of the OS, so please keep that in mind when editing it |
char *CAStringForOSType (OSType t, char *writeLocation) |
{ |
char *p = writeLocation; |
unsigned char str[4] = {0}, *q = str; |
*(UInt32 *)str = CFSwapInt32HostToBig(t); |
bool hasNonPrint = false; |
for (int i = 0; i < 4; ++i) { |
if (!(isprint(*q) && *q != '\\')) { |
hasNonPrint = true; |
break; |
} |
q++; |
} |
q = str; |
if (hasNonPrint) |
p += sprintf (p, "0x"); |
else |
*p++ = '\''; |
for (int i = 0; i < 4; ++i) { |
if (hasNonPrint) { |
p += sprintf(p, "%02X", *q++); |
} else { |
*p++ = *q++; |
} |
} |
if (!hasNonPrint) |
*p++ = '\''; |
*p = '\0'; |
return writeLocation; |
} |
const AudioStreamBasicDescription CAStreamBasicDescription::sEmpty = { 0.0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
CAStreamBasicDescription::CAStreamBasicDescription() |
{ |
memset (this, 0, sizeof(AudioStreamBasicDescription)); |
} |
CAStreamBasicDescription::CAStreamBasicDescription(const AudioStreamBasicDescription &desc) |
{ |
SetFrom(desc); |
} |
CAStreamBasicDescription::CAStreamBasicDescription(double inSampleRate, UInt32 inFormatID, |
UInt32 inBytesPerPacket, UInt32 inFramesPerPacket, |
UInt32 inBytesPerFrame, UInt32 inChannelsPerFrame, |
UInt32 inBitsPerChannel, UInt32 inFormatFlags) |
{ |
mSampleRate = inSampleRate; |
mFormatID = inFormatID; |
mBytesPerPacket = inBytesPerPacket; |
mFramesPerPacket = inFramesPerPacket; |
mBytesPerFrame = inBytesPerFrame; |
mChannelsPerFrame = inChannelsPerFrame; |
mBitsPerChannel = inBitsPerChannel; |
mFormatFlags = inFormatFlags; |
mReserved = 0; |
} |
char *CAStreamBasicDescription::AsString(char *buf, size_t _bufsize) const |
{ |
int bufsize = (int)_bufsize; // must be signed to protect against overflow |
char *theBuffer = buf; |
int nc; |
char formatID[24]; |
CAStringForOSType (mFormatID, formatID); |
nc = snprintf(buf, bufsize, "%2d ch, %6.0f Hz, %s (0x%08X) ", (int)NumberChannels(), mSampleRate, formatID, (int)mFormatFlags); |
buf += nc; if ((bufsize -= nc) <= 0) goto exit; |
if (mFormatID == kAudioFormatLinearPCM) { |
bool isInt = !(mFormatFlags & kLinearPCMFormatFlagIsFloat); |
int wordSize = SampleWordSize(); |
const char *endian = (wordSize > 1) ? |
((mFormatFlags & kLinearPCMFormatFlagIsBigEndian) ? " big-endian" : " little-endian" ) : ""; |
const char *sign = isInt ? |
((mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) ? " signed" : " unsigned") : ""; |
const char *floatInt = isInt ? "integer" : "float"; |
char packed[32]; |
if (wordSize > 0 && PackednessIsSignificant()) { |
if (mFormatFlags & kLinearPCMFormatFlagIsPacked) |
snprintf(packed, sizeof(packed), "packed in %d bytes", wordSize); |
else |
snprintf(packed, sizeof(packed), "unpacked in %d bytes", wordSize); |
} else |
packed[0] = '\0'; |
const char *align = (wordSize > 0 && AlignmentIsSignificant()) ? |
((mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) ? " high-aligned" : " low-aligned") : ""; |
const char *deinter = (mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? ", deinterleaved" : ""; |
const char *commaSpace = (packed[0]!='\0') || (align[0]!='\0') ? ", " : ""; |
char bitdepth[20]; |
int fracbits = (mFormatFlags & kLinearPCMFormatFlagsSampleFractionMask) >> kLinearPCMFormatFlagsSampleFractionShift; |
if (fracbits > 0) |
snprintf(bitdepth, sizeof(bitdepth), "%d.%d", (int)mBitsPerChannel - fracbits, fracbits); |
else |
snprintf(bitdepth, sizeof(bitdepth), "%d", (int)mBitsPerChannel); |
/* nc =*/ snprintf(buf, bufsize, "%s-bit%s%s %s%s%s%s%s", |
bitdepth, endian, sign, floatInt, |
commaSpace, packed, align, deinter); |
// buf += nc; if ((bufsize -= nc) <= 0) goto exit; |
} else if (mFormatID == 'alac') { // kAudioFormatAppleLossless |
int sourceBits = 0; |
switch (mFormatFlags) |
{ |
case 1: // kAppleLosslessFormatFlag_16BitSourceData |
sourceBits = 16; |
break; |
case 2: // kAppleLosslessFormatFlag_20BitSourceData |
sourceBits = 20; |
break; |
case 3: // kAppleLosslessFormatFlag_24BitSourceData |
sourceBits = 24; |
break; |
case 4: // kAppleLosslessFormatFlag_32BitSourceData |
sourceBits = 32; |
break; |
} |
if (sourceBits) |
nc = snprintf(buf, bufsize, "from %d-bit source, ", sourceBits); |
else |
nc = snprintf(buf, bufsize, "from UNKNOWN source bit depth, "); |
buf += nc; if ((bufsize -= nc) <= 0) goto exit; |
/* nc =*/ snprintf(buf, bufsize, "%d frames/packet", (int)mFramesPerPacket); |
// buf += nc; if ((bufsize -= nc) <= 0) goto exit; |
} |
else |
/* nc =*/ snprintf(buf, bufsize, "%d bits/channel, %d bytes/packet, %d frames/packet, %d bytes/frame", |
(int)mBitsPerChannel, (int)mBytesPerPacket, (int)mFramesPerPacket, (int)mBytesPerFrame); |
exit: |
return theBuffer; |
} |
void CAStreamBasicDescription::NormalizeLinearPCMFormat(AudioStreamBasicDescription& ioDescription) |
{ |
// the only thing that changes is to make mixable linear PCM into the canonical linear PCM format |
if((ioDescription.mFormatID == kAudioFormatLinearPCM) && ((ioDescription.mFormatFlags & kIsNonMixableFlag) == 0)) |
{ |
// the canonical linear PCM format |
ioDescription.mFormatFlags = kAudioFormatFlagsCanonical; |
ioDescription.mBytesPerPacket = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; |
ioDescription.mFramesPerPacket = 1; |
ioDescription.mBytesPerFrame = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; |
ioDescription.mBitsPerChannel = 8 * SizeOf32(AudioSampleType); |
} |
} |
void CAStreamBasicDescription::NormalizeLinearPCMFormat(bool inNativeEndian, AudioStreamBasicDescription& ioDescription) |
{ |
// the only thing that changes is to make mixable linear PCM into the canonical linear PCM format |
if((ioDescription.mFormatID == kAudioFormatLinearPCM) && ((ioDescription.mFormatFlags & kIsNonMixableFlag) == 0)) |
{ |
// the canonical linear PCM format |
ioDescription.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked; |
if(inNativeEndian) |
{ |
#if TARGET_RT_BIG_ENDIAN |
ioDescription.mFormatFlags |= kAudioFormatFlagIsBigEndian; |
#endif |
} |
else |
{ |
#if TARGET_RT_LITTLE_ENDIAN |
ioDescription.mFormatFlags |= kAudioFormatFlagIsBigEndian; |
#endif |
} |
ioDescription.mBytesPerPacket = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; |
ioDescription.mFramesPerPacket = 1; |
ioDescription.mBytesPerFrame = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; |
ioDescription.mBitsPerChannel = 8 * SizeOf32(AudioSampleType); |
} |
} |
void CAStreamBasicDescription::ResetFormat(AudioStreamBasicDescription& ioDescription) |
{ |
ioDescription.mSampleRate = 0; |
ioDescription.mFormatID = 0; |
ioDescription.mBytesPerPacket = 0; |
ioDescription.mFramesPerPacket = 0; |
ioDescription.mBytesPerFrame = 0; |
ioDescription.mChannelsPerFrame = 0; |
ioDescription.mBitsPerChannel = 0; |
ioDescription.mFormatFlags = 0; |
} |
void CAStreamBasicDescription::FillOutFormat(AudioStreamBasicDescription& ioDescription, const AudioStreamBasicDescription& inTemplateDescription) |
{ |
if(fiszero(ioDescription.mSampleRate)) |
{ |
ioDescription.mSampleRate = inTemplateDescription.mSampleRate; |
} |
if(ioDescription.mFormatID == 0) |
{ |
ioDescription.mFormatID = inTemplateDescription.mFormatID; |
} |
if(ioDescription.mFormatFlags == 0) |
{ |
ioDescription.mFormatFlags = inTemplateDescription.mFormatFlags; |
} |
if(ioDescription.mBytesPerPacket == 0) |
{ |
ioDescription.mBytesPerPacket = inTemplateDescription.mBytesPerPacket; |
} |
if(ioDescription.mFramesPerPacket == 0) |
{ |
ioDescription.mFramesPerPacket = inTemplateDescription.mFramesPerPacket; |
} |
if(ioDescription.mBytesPerFrame == 0) |
{ |
ioDescription.mBytesPerFrame = inTemplateDescription.mBytesPerFrame; |
} |
if(ioDescription.mChannelsPerFrame == 0) |
{ |
ioDescription.mChannelsPerFrame = inTemplateDescription.mChannelsPerFrame; |
} |
if(ioDescription.mBitsPerChannel == 0) |
{ |
ioDescription.mBitsPerChannel = inTemplateDescription.mBitsPerChannel; |
} |
} |
void CAStreamBasicDescription::GetSimpleName(const AudioStreamBasicDescription& inDescription, char* outName, UInt32 inMaxNameLength, bool inAbbreviate, bool inIncludeSampleRate) |
{ |
if(inIncludeSampleRate) |
{ |
int theCharactersWritten = snprintf(outName, inMaxNameLength, "%.0f ", inDescription.mSampleRate); |
outName += theCharactersWritten; |
inMaxNameLength -= theCharactersWritten; |
} |
switch(inDescription.mFormatID) |
{ |
case kAudioFormatLinearPCM: |
{ |
const char* theEndianString = NULL; |
if((inDescription.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0) |
{ |
#if TARGET_RT_LITTLE_ENDIAN |
theEndianString = "Big Endian"; |
#endif |
} |
else |
{ |
#if TARGET_RT_BIG_ENDIAN |
theEndianString = "Little Endian"; |
#endif |
} |
const char* theKindString = NULL; |
if((inDescription.mFormatFlags & kAudioFormatFlagIsFloat) != 0) |
{ |
theKindString = (inAbbreviate ? "Float" : "Floating Point"); |
} |
else if((inDescription.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0) |
{ |
theKindString = (inAbbreviate ? "SInt" : "Signed Integer"); |
} |
else |
{ |
theKindString = (inAbbreviate ? "UInt" : "Unsigned Integer"); |
} |
const char* thePackingString = NULL; |
if((inDescription.mFormatFlags & kAudioFormatFlagIsPacked) == 0) |
{ |
if((inDescription.mFormatFlags & kAudioFormatFlagIsAlignedHigh) != 0) |
{ |
thePackingString = "High"; |
} |
else |
{ |
thePackingString = "Low"; |
} |
} |
const char* theMixabilityString = NULL; |
if((inDescription.mFormatFlags & kIsNonMixableFlag) == 0) |
{ |
theMixabilityString = "Mixable"; |
} |
else |
{ |
theMixabilityString = "Unmixable"; |
} |
if(inAbbreviate) |
{ |
if(theEndianString != NULL) |
{ |
if(thePackingString != NULL) |
{ |
snprintf(outName, inMaxNameLength, "%s %d Ch %s %s %s%d/%s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theEndianString, thePackingString, theKindString, (int)inDescription.mBitsPerChannel, theKindString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8); |
} |
else |
{ |
snprintf(outName, inMaxNameLength, "%s %d Ch %s %s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theEndianString, theKindString, (int)inDescription.mBitsPerChannel); |
} |
} |
else |
{ |
if(thePackingString != NULL) |
{ |
snprintf(outName, inMaxNameLength, "%s %d Ch %s %s%d/%s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, thePackingString, theKindString, (int)inDescription.mBitsPerChannel, theKindString, (int)((inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8)); |
} |
else |
{ |
snprintf(outName, inMaxNameLength, "%s %d Ch %s%d", theMixabilityString, (int)inDescription.mChannelsPerFrame, theKindString, (int)inDescription.mBitsPerChannel); |
} |
} |
} |
else |
{ |
if(theEndianString != NULL) |
{ |
if(thePackingString != NULL) |
{ |
snprintf(outName, inMaxNameLength, "%s %d Channel %d Bit %s %s Aligned %s in %d Bits", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theEndianString, theKindString, thePackingString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8); |
} |
else |
{ |
snprintf(outName, inMaxNameLength, "%s %d Channel %d Bit %s %s", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theEndianString, theKindString); |
} |
} |
else |
{ |
if(thePackingString != NULL) |
{ |
snprintf(outName, inMaxNameLength, "%s %d Channel %d Bit %s Aligned %s in %d Bits", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theKindString, thePackingString, (int)(inDescription.mBytesPerFrame / inDescription.mChannelsPerFrame) * 8); |
} |
else |
{ |
snprintf(outName, inMaxNameLength, "%s %d Channel %d Bit %s", theMixabilityString, (int)inDescription.mChannelsPerFrame, (int)inDescription.mBitsPerChannel, theKindString); |
} |
} |
} |
} |
break; |
case kAudioFormatAC3: |
strlcpy(outName, "AC-3", sizeof(outName)); |
break; |
case kAudioFormat60958AC3: |
strlcpy(outName, "AC-3 for SPDIF", sizeof(outName)); |
break; |
default: |
CACopy4CCToCString(outName, inDescription.mFormatID); |
break; |
}; |
} |
#if CoreAudio_Debug |
#include "CALogMacros.h" |
void CAStreamBasicDescription::PrintToLog(const AudioStreamBasicDescription& inDesc) |
{ |
PrintFloat (" Sample Rate: ", inDesc.mSampleRate); |
Print4CharCode (" Format ID: ", inDesc.mFormatID); |
PrintHex (" Format Flags: ", inDesc.mFormatFlags); |
PrintInt (" Bytes per Packet: ", inDesc.mBytesPerPacket); |
PrintInt (" Frames per Packet: ", inDesc.mFramesPerPacket); |
PrintInt (" Bytes per Frame: ", inDesc.mBytesPerFrame); |
PrintInt (" Channels per Frame: ", inDesc.mChannelsPerFrame); |
PrintInt (" Bits per Channel: ", inDesc.mBitsPerChannel); |
} |
#endif |
bool operator<(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y) |
{ |
bool theAnswer = false; |
bool isDone = false; |
// note that if either side is 0, that field is skipped |
// format ID is the first order sort |
if((!isDone) && ((x.mFormatID != 0) && (y.mFormatID != 0))) |
{ |
if(x.mFormatID != y.mFormatID) |
{ |
// formats are sorted numerically except that linear |
// PCM is always first |
if(x.mFormatID == kAudioFormatLinearPCM) |
{ |
theAnswer = true; |
} |
else if(y.mFormatID == kAudioFormatLinearPCM) |
{ |
theAnswer = false; |
} |
else |
{ |
theAnswer = x.mFormatID < y.mFormatID; |
} |
isDone = true; |
} |
} |
// mixable is always better than non-mixable for linear PCM and should be the second order sort item |
if((!isDone) && ((x.mFormatID == kAudioFormatLinearPCM) && (y.mFormatID == kAudioFormatLinearPCM))) |
{ |
if(((x.mFormatFlags & kIsNonMixableFlag) == 0) && ((y.mFormatFlags & kIsNonMixableFlag) != 0)) |
{ |
theAnswer = true; |
isDone = true; |
} |
else if(((x.mFormatFlags & kIsNonMixableFlag) != 0) && ((y.mFormatFlags & kIsNonMixableFlag) == 0)) |
{ |
theAnswer = false; |
isDone = true; |
} |
} |
// floating point vs integer for linear PCM only |
if((!isDone) && ((x.mFormatID == kAudioFormatLinearPCM) && (y.mFormatID == kAudioFormatLinearPCM))) |
{ |
if((x.mFormatFlags & kAudioFormatFlagIsFloat) != (y.mFormatFlags & kAudioFormatFlagIsFloat)) |
{ |
// floating point is better than integer |
theAnswer = y.mFormatFlags & kAudioFormatFlagIsFloat; |
isDone = true; |
} |
} |
// bit depth |
if((!isDone) && ((x.mBitsPerChannel != 0) && (y.mBitsPerChannel != 0))) |
{ |
if(x.mBitsPerChannel != y.mBitsPerChannel) |
{ |
// deeper bit depths are higher quality |
theAnswer = x.mBitsPerChannel < y.mBitsPerChannel; |
isDone = true; |
} |
} |
// sample rate |
if((!isDone) && fnonzero(x.mSampleRate) && fnonzero(y.mSampleRate)) |
{ |
if(fnotequal(x.mSampleRate, y.mSampleRate)) |
{ |
// higher sample rates are higher quality |
theAnswer = x.mSampleRate < y.mSampleRate; |
isDone = true; |
} |
} |
// number of channels |
if((!isDone) && ((x.mChannelsPerFrame != 0) && (y.mChannelsPerFrame != 0))) |
{ |
if(x.mChannelsPerFrame != y.mChannelsPerFrame) |
{ |
// more channels is higher quality |
theAnswer = x.mChannelsPerFrame < y.mChannelsPerFrame; |
//isDone = true; |
} |
} |
return theAnswer; |
} |
static bool MatchFormatFlags(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y) |
{ |
UInt32 xFlags = x.mFormatFlags; |
UInt32 yFlags = y.mFormatFlags; |
// match wildcards |
if (x.mFormatID == 0 || y.mFormatID == 0 || xFlags == 0 || yFlags == 0) |
return true; |
if (x.mFormatID == kAudioFormatLinearPCM) |
{ |
// knock off the all clear flag |
xFlags = xFlags & ~kAudioFormatFlagsAreAllClear; |
yFlags = yFlags & ~kAudioFormatFlagsAreAllClear; |
// if both kAudioFormatFlagIsPacked bits are set, then we don't care about the kAudioFormatFlagIsAlignedHigh bit. |
if (xFlags & yFlags & kAudioFormatFlagIsPacked) { |
xFlags = xFlags & ~kAudioFormatFlagIsAlignedHigh; |
yFlags = yFlags & ~kAudioFormatFlagIsAlignedHigh; |
} |
// if both kAudioFormatFlagIsFloat bits are set, then we don't care about the kAudioFormatFlagIsSignedInteger bit. |
if (xFlags & yFlags & kAudioFormatFlagIsFloat) { |
xFlags = xFlags & ~kAudioFormatFlagIsSignedInteger; |
yFlags = yFlags & ~kAudioFormatFlagIsSignedInteger; |
} |
// if the bit depth is 8 bits or less and the format is packed, we don't care about endianness |
if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) |
{ |
xFlags = xFlags & ~kAudioFormatFlagIsBigEndian; |
} |
if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) |
{ |
yFlags = yFlags & ~kAudioFormatFlagIsBigEndian; |
} |
// if the number of channels is 1, we don't care about non-interleavedness |
if (x.mChannelsPerFrame == 1 && y.mChannelsPerFrame == 1) { |
xFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; |
yFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; |
} |
} |
return xFlags == yFlags; |
} |
bool operator==(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y) |
{ |
// the semantics for equality are: |
// 1) Values must match exactly -- except for PCM format flags, see above. |
// 2) wildcard's are ignored in the comparison |
#define MATCH(name) ((x.name) == 0 || (y.name) == 0 || (x.name) == (y.name)) |
return |
// check the sample rate |
(fiszero(x.mSampleRate) || fiszero(y.mSampleRate) || fequal(x.mSampleRate, y.mSampleRate)) |
// check the format ids |
&& MATCH(mFormatID) |
// check the format flags |
&& MatchFormatFlags(x, y) |
// check the bytes per packet |
&& MATCH(mBytesPerPacket) |
// check the frames per packet |
&& MATCH(mFramesPerPacket) |
// check the bytes per frame |
&& MATCH(mBytesPerFrame) |
// check the channels per frame |
&& MATCH(mChannelsPerFrame) |
// check the channels per frame |
&& MATCH(mBitsPerChannel) ; |
} |
bool CAStreamBasicDescription::IsEqual(const AudioStreamBasicDescription &other, bool interpretingWildcards) const |
{ |
if (interpretingWildcards) |
return *this == other; |
return memcmp(this, &other, offsetof(AudioStreamBasicDescription, mReserved)) == 0; |
} |
bool SanityCheck(const AudioStreamBasicDescription& x) |
{ |
// This function returns false if there are sufficiently insane values in any field. |
// It is very conservative so even some very unlikely values will pass. |
// This is just meant to catch the case where the data from a file is corrupted. |
return |
(x.mSampleRate >= 0.) |
&& (x.mSampleRate < 3e6) // SACD sample rate is 2.8224 MHz |
&& (x.mBytesPerPacket < 1000000) |
&& (x.mFramesPerPacket < 1000000) |
&& (x.mBytesPerFrame < 1000000) |
&& (x.mChannelsPerFrame <= 1024) |
&& (x.mBitsPerChannel <= 1024) |
&& (x.mFormatID != 0) |
&& !(x.mFormatID == kAudioFormatLinearPCM && (x.mFramesPerPacket != 1 || x.mBytesPerPacket != x.mBytesPerFrame)); |
} |
bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasicDescription &fmt) |
{ |
const char *p = inTextDesc; |
memset(&fmt, 0, sizeof(fmt)); |
bool isPCM = true; // until proven otherwise |
UInt32 pcmFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; |
if (p[0] == '-') // previously we required a leading dash on PCM formats |
++p; |
if (p[0] == 'B' && p[1] == 'E') { |
pcmFlags |= kLinearPCMFormatFlagIsBigEndian; |
p += 2; |
} else if (p[0] == 'L' && p[1] == 'E') { |
p += 2; |
} else { |
// default is native-endian |
#if TARGET_RT_BIG_ENDIAN |
pcmFlags |= kLinearPCMFormatFlagIsBigEndian; |
#endif |
} |
if (p[0] == 'F') { |
pcmFlags = (pcmFlags & ~kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsFloat; |
++p; |
} else { |
if (p[0] == 'U') { |
pcmFlags &= ~kAudioFormatFlagIsSignedInteger; |
++p; |
} |
if (p[0] == 'I') |
++p; |
else { |
// it's not PCM; presumably some other format (NOT VALIDATED; use AudioFormat for that) |
isPCM = false; |
p = inTextDesc; // go back to the beginning |
char buf[4] = { ' ',' ',' ',' ' }; |
for (int i = 0; i < 4; ++i) { |
if (*p != '\\') { |
if ((buf[i] = *p++) == '\0') { |
// special-case for 'aac' |
if (i != 3) return false; |
--p; // keep pointing at the terminating null |
buf[i] = ' '; |
break; |
} |
} else { |
// "\xNN" is a hex byte |
if (*++p != 'x') return false; |
int x; |
if (sscanf(++p, "%02X", &x) != 1) return false; |
buf[i] = x; |
p += 2; |
} |
} |
if (strchr("-@/#", buf[3])) { |
// further special-casing for 'aac' |
buf[3] = ' '; |
--p; |
} |
fmt.mFormatID = CFSwapInt32BigToHost(*(UInt32 *)buf); |
} |
} |
if (isPCM) { |
fmt.mFormatID = kAudioFormatLinearPCM; |
fmt.mFormatFlags = pcmFlags; |
fmt.mFramesPerPacket = 1; |
fmt.mChannelsPerFrame = 1; |
int bitdepth = 0, fracbits = 0; |
while (isdigit(*p)) |
bitdepth = 10 * bitdepth + *p++ - '0'; |
if (*p == '.') { |
++p; |
if (!isdigit(*p)) { |
fprintf(stderr, "Expected fractional bits following '.'\n"); |
goto Bail; |
} |
while (isdigit(*p)) |
fracbits = 10 * fracbits + *p++ - '0'; |
bitdepth += fracbits; |
fmt.mFormatFlags |= (fracbits << kLinearPCMFormatFlagsSampleFractionShift); |
} |
fmt.mBitsPerChannel = bitdepth; |
fmt.mBytesPerPacket = fmt.mBytesPerFrame = (bitdepth + 7) / 8; |
if (bitdepth & 7) { |
// assume unpacked. (packed odd bit depths are describable but not supported in AudioConverter.) |
fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsPacked; |
// alignment matters; default to high-aligned. use ':L_' for low. |
fmt.mFormatFlags |= kLinearPCMFormatFlagIsAlignedHigh; |
} |
} |
if (*p == '@') { |
++p; |
while (isdigit(*p)) |
fmt.mSampleRate = 10 * fmt.mSampleRate + (*p++ - '0'); |
} |
if (*p == '/') { |
UInt32 flags = 0; |
while (true) { |
char c = *++p; |
if (c >= '0' && c <= '9') |
flags = (flags << 4) | (c - '0'); |
else if (c >= 'A' && c <= 'F') |
flags = (flags << 4) | (c - 'A' + 10); |
else if (c >= 'a' && c <= 'f') |
flags = (flags << 4) | (c - 'a' + 10); |
else break; |
} |
fmt.mFormatFlags = flags; |
} |
if (*p == '#') { |
++p; |
while (isdigit(*p)) |
fmt.mFramesPerPacket = 10 * fmt.mFramesPerPacket + (*p++ - '0'); |
} |
if (*p == ':') { |
++p; |
fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsPacked; |
if (*p == 'L') |
fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsAlignedHigh; |
else if (*p == 'H') |
fmt.mFormatFlags |= kLinearPCMFormatFlagIsAlignedHigh; |
else |
goto Bail; |
++p; |
int bytesPerFrame = 0; |
while (isdigit(*p)) |
bytesPerFrame = 10 * bytesPerFrame + (*p++ - '0'); |
fmt.mBytesPerFrame = fmt.mBytesPerPacket = bytesPerFrame; |
} |
if (*p == ',') { |
++p; |
int ch = 0; |
while (isdigit(*p)) |
ch = 10 * ch + (*p++ - '0'); |
fmt.mChannelsPerFrame = ch; |
if (*p == 'D') { |
++p; |
if (fmt.mFormatID != kAudioFormatLinearPCM) { |
fprintf(stderr, "non-interleaved flag invalid for non-PCM formats\n"); |
goto Bail; |
} |
fmt.mFormatFlags |= kAudioFormatFlagIsNonInterleaved; |
} else { |
if (*p == 'I') ++p; // default |
if (fmt.mFormatID == kAudioFormatLinearPCM) |
fmt.mBytesPerPacket = fmt.mBytesPerFrame *= ch; |
} |
} |
if (*p != '\0') { |
fprintf(stderr, "extra characters at end of format string: %s\n", p); |
goto Bail; |
} |
return true; |
Bail: |
fprintf(stderr, "Invalid format string: %s\n", inTextDesc); |
fprintf(stderr, "Syntax of format strings is: \n"); |
return false; |
} |
const char *CAStreamBasicDescription::sTextParsingUsageString = |
"format[@sample_rate_hz][/format_flags][#frames_per_packet][:LHbytesPerFrame][,channelsDI].\n" |
"Format for PCM is [-][BE|LE]{F|I|UI}{bitdepth}; else a 4-char format code (e.g. aac, alac).\n"; |
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-01-29