AudioConverterFileConvertTest/AudioFileConvertOperation.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Demonstrates converting audio using AudioConverterFillComplexBuffer. |
*/ |
#import "AudioFileConvertOperation.h" |
@import Darwin; |
@import AVFoundation; |
// *********************** |
#pragma mark- Converter |
/* The main Audio Conversion function using AudioConverter */ |
enum { |
kMyAudioConverterErr_CannotResumeFromInterruptionError = 'CANT', |
eofErr = -39 // End of file |
}; |
typedef struct { |
AudioFileID srcFileID; |
SInt64 srcFilePos; |
char * srcBuffer; |
UInt32 srcBufferSize; |
AudioStreamBasicDescription srcFormat; |
UInt32 srcSizePerPacket; |
UInt32 numPacketsPerRead; |
AudioStreamPacketDescription *packetDescriptions; |
} AudioFileIO, *AudioFileIOPtr; |
#pragma mark- |
// Input data proc callback |
static OSStatus EncoderDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) |
{ |
AudioFileIOPtr afio = (AudioFileIOPtr)inUserData; |
OSStatus error; |
// figure out how much to read |
UInt32 maxPackets = afio->srcBufferSize / afio->srcSizePerPacket; |
if (*ioNumberDataPackets > maxPackets) *ioNumberDataPackets = maxPackets; |
// read from the file |
UInt32 outNumBytes = maxPackets * afio->srcSizePerPacket; |
error = AudioFileReadPacketData(afio->srcFileID, false, &outNumBytes, afio->packetDescriptions, afio->srcFilePos, ioNumberDataPackets, afio->srcBuffer); |
if (eofErr == error) error = noErr; |
if (error) { printf ("Input Proc Read error: %d (%4.4s)\n", (int)error, (char*)&error); return error; } |
//printf("Input Proc: Read %lu packets, at position %lld size %lu\n", *ioNumberDataPackets, afio->srcFilePos, outNumBytes); |
// advance input file packet position |
afio->srcFilePos += *ioNumberDataPackets; |
// put the data pointer into the buffer list |
ioData->mBuffers[0].mData = afio->srcBuffer; |
ioData->mBuffers[0].mDataByteSize = outNumBytes; |
ioData->mBuffers[0].mNumberChannels = afio->srcFormat.mChannelsPerFrame; |
// don't forget the packet descriptions if required |
if (outDataPacketDescription) { |
if (afio->packetDescriptions) { |
*outDataPacketDescription = afio->packetDescriptions; |
} else { |
*outDataPacketDescription = NULL; |
} |
} |
return error; |
} |
typedef NS_ENUM(NSInteger, AudioConverterState) { |
AudioConverterStateInitial, |
AudioConverterStateRunning, |
AudioConverterStatePaused, |
AudioConverterStateDone |
}; |
@interface AudioFileConvertOperation () |
// MARK: Properties |
@property (nonatomic, strong) dispatch_queue_t queue; |
@property (nonatomic, strong) dispatch_semaphore_t semaphore; |
@property (nonatomic, assign) AudioConverterState state; |
@end |
@implementation AudioFileConvertOperation |
// MARK: Initialization |
- (instancetype)initWithSourceURL:(NSURL *)sourceURL destinationURL:(NSURL *)destinationURL sampleRate:(Float64)sampleRate outputFormat:(AudioFormatID)outputFormat { |
if ((self = [super init])) { |
_sourceURL = sourceURL; |
_destinationURL = destinationURL; |
_sampleRate = sampleRate; |
_outputFormat = outputFormat; |
_state = AudioConverterStateInitial; |
_queue = dispatch_queue_create("com.example.apple-samplecode.AudioConverterFileConvertTest.AudioFileConverOperation.queue", DISPATCH_QUEUE_CONCURRENT); |
_semaphore = dispatch_semaphore_create(0); |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruptionNotification:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; |
} |
return self; |
} |
- (void)dealloc { |
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; |
} |
- (void)main { |
[super main]; |
// This should never run on the main thread. |
assert(![NSThread isMainThread]); |
AudioStreamPacketDescription *outputPacketDescriptions = NULL; |
// Set the state to running. |
__weak __typeof__(self) weakSelf = self; |
dispatch_sync(self.queue, ^{ |
weakSelf.state = AudioConverterStateRunning; |
}); |
// Get the source file. |
AudioFileID sourceFileID = 0; |
if (![self checkError:(AudioFileOpenURL((__bridge CFURLRef _Nonnull)self.sourceURL, kAudioFileReadPermission, 0, &sourceFileID)) withErrorString:[NSString stringWithFormat:@"AudioFileOpenURL failed for sourceFile with URL: %@", self.sourceURL]]) { |
return; |
} |
// Get the source data format. |
AudioStreamBasicDescription sourceFormat = {}; |
UInt32 size = sizeof(sourceFormat); |
if (![self checkError:AudioFileGetProperty(sourceFileID, kAudioFilePropertyDataFormat, &size, &sourceFormat) withErrorString:@"AudioFileGetProperty couldn't get the source data format"]) { |
return; |
} |
// Setup the output file format. |
AudioStreamBasicDescription destinationFormat = {}; |
destinationFormat.mSampleRate = (self.sampleRate == 0 ? sourceFormat.mSampleRate : self.sampleRate); |
if (self.outputFormat == kAudioFormatLinearPCM) { |
// If the output format is PCM, create a 16-bit file format description. |
destinationFormat.mFormatID = self.outputFormat; |
destinationFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame; |
destinationFormat.mBitsPerChannel = 16; |
destinationFormat.mBytesPerPacket = destinationFormat.mBytesPerFrame = 2 * destinationFormat.mChannelsPerFrame; |
destinationFormat.mFramesPerPacket = 1; |
destinationFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; // little-endian |
} else { |
// This is a compressed format, need to set at least format, sample rate and channel fields for kAudioFormatProperty_FormatInfo. |
destinationFormat.mFormatID = self.outputFormat; |
// For iLBC, the number of channels must be 1. |
destinationFormat.mChannelsPerFrame = (self.outputFormat == kAudioFormatiLBC ? 1 : sourceFormat.mChannelsPerFrame); |
// Use AudioFormat API to fill out the rest of the description. |
size = sizeof(destinationFormat); |
if (![self checkError:AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &destinationFormat) withErrorString:@"AudioFormatGetProperty couldn't fill out the destination data format"]) { |
return; |
} |
} |
printf("Source File format:\n"); |
[AudioFileConvertOperation printAudioStreamBasicDescription:sourceFormat]; |
printf("Destination File format:\n"); |
[AudioFileConvertOperation printAudioStreamBasicDescription:destinationFormat]; |
// Create the AudioConverterRef. |
AudioConverterRef converter = NULL; |
if (![self checkError:AudioConverterNew(&sourceFormat, &destinationFormat, &converter) withErrorString:@"AudioConverterNew failed"]) { |
return; |
} |
// If the source file has a cookie, get ir and set it on the AudioConverterRef. |
[self readCookieFromAudioFile:sourceFileID converter:converter]; |
// Get the actuall formats (source and destination) from the AudioConverterRef. |
size = sizeof(sourceFormat); |
if (![self checkError:AudioConverterGetProperty(converter, kAudioConverterCurrentInputStreamDescription, &size, &sourceFormat) withErrorString:@"AudioConverterGetProperty kAudioConverterCurrentInputStreamDescription failed!"]) { |
return; |
} |
size = sizeof(destinationFormat); |
if (![self checkError:AudioConverterGetProperty(converter, kAudioConverterCurrentOutputStreamDescription, &size, &destinationFormat) withErrorString:@"AudioConverterGetProperty kAudioConverterCurrentOutputStreamDescription failed!"]) { |
return; |
} |
printf("Formats returned from AudioConverter:\n"); |
printf("Source File format:\n"); |
[AudioFileConvertOperation printAudioStreamBasicDescription:sourceFormat]; |
printf("Destination File format:\n"); |
[AudioFileConvertOperation printAudioStreamBasicDescription:destinationFormat]; |
/* |
If encoding to AAC set the bitrate kAudioConverterEncodeBitRate is a UInt32 value containing |
the number of bits per second to aim for when encoding data when you explicitly set the bit rate |
and the sample rate, this tells the encoder to stick with both bit rate and sample rate |
but there are combinations (also depending on the number of channels) which will not be allowed |
if you do not explicitly set a bit rate the encoder will pick the correct value for you depending |
on samplerate and number of channels bit rate also scales with the number of channels, |
therefore one bit rate per sample rate can be used for mono cases and if you have stereo or more, |
you can multiply that number by the number of channels. |
*/ |
if (destinationFormat.mFormatID == kAudioFormatMPEG4AAC) { |
UInt32 outputBitRate = 64000; |
UInt32 propSize = sizeof(outputBitRate); |
if (destinationFormat.mSampleRate >= 44100) { |
outputBitRate = 192000; |
} else if (destinationFormat.mSampleRate < 22000) { |
outputBitRate = 32000; |
} |
// Set the bit rate depending on the sample rate chosen. |
if (![self checkError:AudioConverterSetProperty(converter, kAudioConverterEncodeBitRate, propSize, &outputBitRate) withErrorString:@"AudioConverterSetProperty kAudioConverterEncodeBitRate failed!"]) { |
return; |
} |
// Get it back and print it out. |
AudioConverterGetProperty(converter, kAudioConverterEncodeBitRate, &propSize, &outputBitRate); |
printf ("AAC Encode Bitrate: %u\n", (unsigned int)outputBitRate); |
} |
/* |
Can the Audio Converter resume after an interruption? |
this property may be queried at any time after construction of the Audio Converter after setting its output format |
there's no clear reason to prefer construction time, interruption time, or potential resumption time but we prefer |
construction time since it means less code to execute during or after interruption time. |
*/ |
BOOL canResumeFromInterruption = YES; |
UInt32 canResume = 0; |
size = sizeof(canResume); |
OSStatus error = AudioConverterGetProperty(converter, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume); |
if (error == noErr) { |
/* |
we recieved a valid return value from the GetProperty call |
if the property's value is 1, then the codec CAN resume work following an interruption |
if the property's value is 0, then interruptions destroy the codec's state and we're done |
*/ |
if (canResume == 0) { |
canResumeFromInterruption = NO; |
} |
printf("Audio Converter %s continue after interruption!\n", (!canResumeFromInterruption ? "CANNOT" : "CAN")); |
} else { |
/* |
if the property is unimplemented (kAudioConverterErr_PropertyNotSupported, or paramErr returned in the case of PCM), |
then the codec being used is not a hardware codec so we're not concerned about codec state |
we are always going to be able to resume conversion after an interruption |
*/ |
if (error == kAudioConverterErr_PropertyNotSupported) { |
printf("kAudioConverterPropertyCanResumeFromInterruption property not supported - see comments in source for more info.\n"); |
} else { |
printf("AudioConverterGetProperty kAudioConverterPropertyCanResumeFromInterruption result %d, paramErr is OK if PCM\n", (int)error); |
} |
error = noErr; |
} |
// Create the destination audio file. |
AudioFileID destinationFileID = 0; |
if (![self checkError:AudioFileCreateWithURL((__bridge CFURLRef _Nonnull)(self.destinationURL), kAudioFileCAFType, &destinationFormat, kAudioFileFlags_EraseFile, &destinationFileID) withErrorString:@"AudioFileCreateWithURL failed!"]) { |
return; |
} |
// Setup source buffers and data proc info struct. |
AudioFileIO afio = {}; |
afio.srcFileID = sourceFileID; |
afio.srcBufferSize = 32768; |
afio.srcBuffer = malloc(afio.srcBufferSize * sizeof(char)); |
afio.srcFilePos = 0; |
afio.srcFormat = sourceFormat; |
if (sourceFormat.mBytesPerPacket == 0) { |
/* |
if the source format is VBR, we need to get the maximum packet size |
use kAudioFilePropertyPacketSizeUpperBound which returns the theoretical maximum packet size |
in the file (without actually scanning the whole file to find the largest packet, |
as may happen with kAudioFilePropertyMaximumPacketSize) |
*/ |
size = sizeof(afio.srcSizePerPacket); |
if (![self checkError:AudioFileGetProperty(sourceFileID, kAudioFilePropertyPacketSizeUpperBound, &size, &afio.srcSizePerPacket) withErrorString:@"AudioFileGetProperty kAudioFilePropertyPacketSizeUpperBound failed!"]) { |
return; |
} |
// How many packets can we read for our buffer size? |
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket; |
// Allocate memory for the PacketDescription structs describing the layout of each packet. |
afio.packetDescriptions = malloc(afio.numPacketsPerRead * sizeof(AudioStreamPacketDescription)); |
} else { |
// CBR source format |
afio.srcSizePerPacket = sourceFormat.mBytesPerPacket; |
afio.numPacketsPerRead = afio.srcBufferSize / afio.srcSizePerPacket; |
afio.packetDescriptions = NULL; |
} |
// Set up output buffers |
UInt32 outputSizePerPacket = destinationFormat.mBytesPerPacket; |
UInt32 theOutputBufferSize = 32768; |
char *outputBuffer = malloc(theOutputBufferSize * sizeof(char)); |
if (outputSizePerPacket == 0) { |
// if the destination format is VBR, we need to get max size per packet from the converter |
size = sizeof(outputSizePerPacket); |
if (![self checkError:AudioConverterGetProperty(converter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &outputSizePerPacket) withErrorString:@"AudioConverterGetProperty kAudioConverterPropertyMaximumOutputPacketSize failed!"]) { |
if (afio.srcBuffer) { free(afio.srcBuffer); } |
if (outputBuffer) { free(outputBuffer); } |
return; |
} |
// allocate memory for the PacketDescription structures describing the layout of each packet |
outputPacketDescriptions = calloc(theOutputBufferSize / outputSizePerPacket, sizeof(AudioStreamPacketDescription));//malloc((theOutputBufferSize / outputSizePerPacket) * sizeof(AudioStreamPacketDescription)); |
} |
UInt32 numberOutputPackets = theOutputBufferSize / outputSizePerPacket; |
// If the destination format has a cookie, get it and set it on the output file. |
[self writeCookieForAudioFile:destinationFileID converter:converter]; |
// Write destination channel layout. |
if (sourceFormat.mChannelsPerFrame > 2) { |
[self writeChannelLayoutWithConverter:converter sourceFile:sourceFileID destinationFile:destinationFileID]; |
} |
// Used for debugging printf |
UInt64 totalOutputFrames = 0; |
SInt64 outputFilePosition = 0; |
// Loop to convert data. |
printf("Converting...\n"); |
while (YES) { |
// Set up output buffer list. |
AudioBufferList fillBufferList = {}; |
fillBufferList.mNumberBuffers = 1; |
fillBufferList.mBuffers[0].mNumberChannels = destinationFormat.mChannelsPerFrame; |
fillBufferList.mBuffers[0].mDataByteSize = theOutputBufferSize; |
fillBufferList.mBuffers[0].mData = outputBuffer; |
BOOL wasInterrupted = [self checkIfPausedDueToInterruption]; |
if ((error != noErr || wasInterrupted) && (!canResumeFromInterruption)) { |
// this is our interruption termination condition |
// an interruption has occured but the Audio Converter cannot continue |
error = kMyAudioConverterErr_CannotResumeFromInterruptionError; |
break; |
} |
// Convert data |
UInt32 ioOutputDataPackets = numberOutputPackets; |
printf("AudioConverterFillComplexBuffer...\n"); |
error = AudioConverterFillComplexBuffer(converter, EncoderDataProc, &afio, &ioOutputDataPackets, &fillBufferList, outputPacketDescriptions); |
// if interrupted in the process of the conversion call, we must handle the error appropriately |
if (error) { |
if (error == kAudioConverterErr_HardwareInUse) { |
printf("Audio Converter returned kAudioConverterErr_HardwareInUse!\n"); |
} else { |
if (![self checkError:error withErrorString:@"AudioConverterFillComplexBuffer error!"]) { |
return; |
} |
} |
} else { |
if (ioOutputDataPackets == 0) { |
// This is the EOF condition. |
error = noErr; |
break; |
} |
} |
if (error == noErr) { |
// Write to output file. |
UInt32 inNumBytes = fillBufferList.mBuffers[0].mDataByteSize; |
if (![self checkError:AudioFileWritePackets(destinationFileID, false, inNumBytes, outputPacketDescriptions, outputFilePosition, &ioOutputDataPackets, outputBuffer) withErrorString:@"AudioFileWritePackets failed!"]) { |
return; |
} |
printf("Convert Output: Write %u packets at position %lld, size: %u\n", (unsigned int)ioOutputDataPackets, outputFilePosition, (unsigned int)inNumBytes); |
// Advance output file packet position. |
outputFilePosition += ioOutputDataPackets; |
if (destinationFormat.mFramesPerPacket) { |
// The format has constant frames per packet. |
totalOutputFrames += (ioOutputDataPackets * destinationFormat.mFramesPerPacket); |
} else if (outputPacketDescriptions != NULL) { |
// variable frames per packet require doing this for each packet (adding up the number of sample frames of data in each packet) |
for (UInt32 i = 0; i < ioOutputDataPackets; ++i) { |
totalOutputFrames += outputPacketDescriptions[i].mVariableFramesInPacket; |
} |
} |
} |
} |
if (![self checkError:error withErrorString:@"An Error Occured during the conversion!"]) { |
return; |
} |
// write out any of the leading and trailing frames for compressed formats only |
if (destinationFormat.mBitsPerChannel == 0) { |
// our output frame count should jive with |
printf("Total number of output frames counted: %lld\n", totalOutputFrames); |
[self writePacketTableInfoWithConverter:converter toDestination:destinationFileID]; |
} |
[self writeCookieForAudioFile:destinationFileID converter:converter]; |
// Cleanup |
if (converter) { AudioConverterDispose(converter); } |
if (destinationFileID) { AudioFileClose(destinationFileID); } |
if (sourceFileID) { AudioFileClose(sourceFileID); } |
if (afio.srcBuffer) { free(afio.srcBuffer); } |
if (afio.packetDescriptions) { free(afio.packetDescriptions); } |
if (outputBuffer) { free(outputBuffer); } |
if (outputPacketDescriptions) { free(outputPacketDescriptions); } |
// Set the state to done. |
dispatch_sync(self.queue, ^{ |
weakSelf.state = AudioConverterStateDone; |
}); |
if (error == noErr) { |
if ([self.delegate respondsToSelector:@selector(audioFileConvertOperation:didCompleteWithURL:)]) { |
[self.delegate audioFileConvertOperation:self didCompleteWithURL:self.destinationURL]; |
} |
} |
} |
/* |
Some audio formats have a magic cookie associated with them which is required to decompress audio data |
When converting audio data you must check to see if the format of the data has a magic cookie |
If the audio data format has a magic cookie associated with it, you must add this information to anAudio Converter |
using AudioConverterSetProperty and kAudioConverterDecompressionMagicCookie to appropriately decompress the data |
http://developer.apple.com/mac/library/qa/qa2001/qa1318.html |
*/ |
- (void)readCookieFromAudioFile:(AudioFileID)sourceFileID converter:(AudioConverterRef)converter { |
// Grab the cookie from the source file and set it on the converter. |
UInt32 cookieSize = 0; |
OSStatus error = AudioFileGetPropertyInfo(sourceFileID, kAudioFilePropertyMagicCookieData, &cookieSize, NULL); |
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as some formats do not. |
if (error == noErr && cookieSize != 0) { |
char *cookie = malloc(cookieSize * sizeof(char)); |
error = AudioFileGetProperty(sourceFileID, kAudioFilePropertyMagicCookieData, &cookieSize, cookie); |
if (error == noErr) { |
error = AudioConverterSetProperty(converter, kAudioConverterDecompressionMagicCookie, cookieSize, cookie); |
if (error != noErr) { |
printf("Could not Set kAudioConverterDecompressionMagicCookie on the Audio Converter!\n"); |
} |
} else { |
printf("Could not Get kAudioFilePropertyMagicCookieData from source file!\n"); |
} |
free(cookie); |
} |
} |
/* |
Some audio formats have a magic cookie associated with them which is required to decompress audio data |
When converting audio, a magic cookie may be returned by the Audio Converter so that it may be stored along with |
the output data -- This is done so that it may then be passed back to the Audio Converter at a later time as required |
*/ |
- (void)writeCookieForAudioFile:(AudioFileID)destinationFileID converter:(AudioConverterRef)converter { |
// Grab the cookie from the converter and write it to the destination file. |
UInt32 cookieSize = 0; |
OSStatus error = AudioConverterGetPropertyInfo(converter, kAudioConverterCompressionMagicCookie, &cookieSize, NULL); |
// If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. |
if (error == noErr && cookieSize != 0) { |
char *cookie = malloc(cookieSize * sizeof(char)); |
error = AudioConverterGetProperty(converter, kAudioConverterCompressionMagicCookie, &cookieSize, cookie); |
if (error == noErr) { |
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyMagicCookieData, cookieSize, cookie); |
if (error == noErr) { |
printf("Writing magic cookie to destination file: %u\n", (unsigned int)cookieSize); |
} else { |
printf("Even though some formats have cookies, some files don't take them and that's OK\n"); |
} |
} else { |
printf("Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n"); |
} |
free(cookie); |
} |
} |
/* |
Sets the packet table containing information about the number of valid frames in a file and where they begin and end |
for the file types that support this information. |
Calling this function makes sure we write out the priming and remainder details to the destination file |
*/ |
- (void)writePacketTableInfoWithConverter:(AudioConverterRef)converter toDestination:(AudioFileID)destinationFileID { |
UInt32 isWritable; |
UInt32 dataSize; |
OSStatus error = AudioFileGetPropertyInfo(destinationFileID, kAudioFilePropertyPacketTableInfo, &dataSize, &isWritable); |
if (error == noErr && isWritable) { |
AudioConverterPrimeInfo primeInfo; |
dataSize = sizeof(primeInfo); |
// retrieve the leadingFrames and trailingFrames information from the converter, |
error = AudioConverterGetProperty(converter, kAudioConverterPrimeInfo, &dataSize, &primeInfo); |
if (error == noErr) { |
/* we have some priming information to write out to the destination file |
The total number of packets in the file times the frames per packet (or counting each packet's |
frames individually for a variable frames per packet format) minus mPrimingFrames, minus |
mRemainderFrames, should equal mNumberValidFrames. |
*/ |
AudioFilePacketTableInfo pti; |
dataSize = sizeof(pti); |
error = AudioFileGetProperty(destinationFileID, kAudioFilePropertyPacketTableInfo, &dataSize, &pti); |
if (noErr == error) { |
// there's priming to write out to the file |
UInt64 totalFrames = pti.mNumberValidFrames + pti.mPrimingFrames + pti.mRemainderFrames; // get the total number of frames from the output file |
printf("Total number of frames from output file: %lld\n", totalFrames); |
pti.mPrimingFrames = primeInfo.leadingFrames; |
pti.mRemainderFrames = primeInfo.trailingFrames; |
pti.mNumberValidFrames = totalFrames - pti.mPrimingFrames - pti.mRemainderFrames; |
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyPacketTableInfo, sizeof(pti), &pti); |
if (noErr == error) { |
printf("Writing packet table information to destination file: %ld\n", sizeof(pti)); |
printf(" Total valid frames: %lld\n", pti.mNumberValidFrames); |
printf(" Priming frames: %d\n", (int)pti.mPrimingFrames); |
printf(" Remainder frames: %d\n\n", (int)pti.mRemainderFrames); |
} else { |
printf("Some audio files can't contain packet table information and that's OK\n"); |
} |
} else { |
printf("Getting kAudioFilePropertyPacketTableInfo error: %d\n", (int)error); |
} |
} else { |
printf("No kAudioConverterPrimeInfo available and that's OK\n"); |
} |
} else { |
printf("GetPropertyInfo for kAudioFilePropertyPacketTableInfo error: %d, isWritable: %u\n", (int)error, (unsigned int)isWritable); |
} |
} |
- (void)writeChannelLayoutWithConverter:(AudioConverterRef)converter sourceFile:(AudioFileID)sourceFileID destinationFile:(AudioFileID)destinationFileID { |
UInt32 layoutSize = 0; |
bool layoutFromConverter = true; |
OSStatus error = AudioConverterGetPropertyInfo(converter, kAudioConverterOutputChannelLayout, &layoutSize, NULL); |
// if the Audio Converter doesn't have a layout see if the input file does |
if (error || 0 == layoutSize) { |
error = AudioFileGetPropertyInfo(sourceFileID, kAudioFilePropertyChannelLayout, &layoutSize, NULL); |
layoutFromConverter = false; |
} |
if (noErr == error && 0 != layoutSize) { |
char* layout = malloc(layoutSize * sizeof(char)); |
if (layoutFromConverter) { |
error = AudioConverterGetProperty(converter, kAudioConverterOutputChannelLayout, &layoutSize, layout); |
if (error) printf("Could not Get kAudioConverterOutputChannelLayout from Audio Converter!\n"); |
} else { |
error = AudioFileGetProperty(sourceFileID, kAudioFilePropertyChannelLayout, &layoutSize, layout); |
if (error) printf("Could not Get kAudioFilePropertyChannelLayout from source file!\n"); |
} |
if (noErr == error) { |
error = AudioFileSetProperty(destinationFileID, kAudioFilePropertyChannelLayout, layoutSize, layout); |
if (noErr == error) { |
printf("Writing channel layout to destination file: %u\n", (unsigned int)layoutSize); |
} else { |
printf("Even though some formats have layouts, some files don't take them and that's OK\n"); |
} |
} |
free(layout); |
} |
} |
- (BOOL)checkError:(OSStatus)error withErrorString:(NSString *)string { |
if (error == noErr) { |
return YES; |
} |
if ([self.delegate respondsToSelector:@selector(audioFileConvertOperation:didEncounterError:)]) { |
NSError *err = [NSError errorWithDomain:@"AudioFileConvertOperationErrorDomain" code:error userInfo:@{NSLocalizedDescriptionKey : string}]; |
[self.delegate audioFileConvertOperation:self didEncounterError:err]; |
} |
return NO; |
} |
- (BOOL)checkIfPausedDueToInterruption { |
__block BOOL wasInterrupted = NO; |
__weak __typeof__(self) weakSelf = self; |
dispatch_sync(self.queue, ^{ |
assert(weakSelf.state != AudioConverterStateDone); |
while (weakSelf.state == AudioConverterStatePaused) { |
dispatch_semaphore_wait(weakSelf.semaphore, DISPATCH_TIME_FOREVER); |
wasInterrupted = YES; |
} |
}); |
// We must be running or something bad has happened. |
assert(self.state == AudioConverterStateRunning); |
return wasInterrupted; |
} |
// MARK: Notification Handlers. |
- (void)handleAudioSessionInterruptionNotification:(NSNotification *)notification { |
AVAudioSessionInterruptionType interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue]; |
printf("Session interrupted > --- %s ---\n", interruptionType == AVAudioSessionInterruptionTypeBegan ? "Begin Interruption" : "End Interruption"); |
__weak __typeof__(self) weakSelf = self; |
if (interruptionType == AVAudioSessionInterruptionTypeBegan) { |
dispatch_sync(self.queue, ^{ |
if (weakSelf.state == AudioConverterStateRunning) { |
weakSelf.state = AudioConverterStatePaused; |
} |
}); |
} else { |
NSError *error = nil; |
[[AVAudioSession sharedInstance] setActive:YES error:&error]; |
if (error != nil) { |
NSLog(@"AVAudioSession setActive failed with error: %@", error.localizedDescription); |
} |
if (self.state == AudioConverterStatePaused) { |
dispatch_semaphore_signal(self.semaphore); |
} |
dispatch_sync(self.queue, ^{ |
weakSelf.state = AudioConverterStateRunning; |
}); |
} |
} |
+ (void)printAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd { |
char formatID[5]; |
UInt32 mFormatID = CFSwapInt32HostToBig(asbd.mFormatID); |
bcopy (&mFormatID, formatID, 4); |
formatID[4] = '\0'; |
printf("Sample Rate: %10.0f\n", asbd.mSampleRate); |
printf("Format ID: %10s\n", formatID); |
printf("Format Flags: %10X\n", (unsigned int)asbd.mFormatFlags); |
printf("Bytes per Packet: %10d\n", (unsigned int)asbd.mBytesPerPacket); |
printf("Frames per Packet: %10d\n", (unsigned int)asbd.mFramesPerPacket); |
printf("Bytes per Frame: %10d\n", (unsigned int)asbd.mBytesPerFrame); |
printf("Channels per Frame: %10d\n", (unsigned int)asbd.mChannelsPerFrame); |
printf("Bits per Channel: %10d\n", (unsigned int)asbd.mBitsPerChannel); |
printf("\n"); |
} |
@end |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-29