如何在 iOS 上使用 ExtAudioFile 进行 AAC 编码?
Posted
技术标签:
【中文标题】如何在 iOS 上使用 ExtAudioFile 进行 AAC 编码?【英文标题】:How can I get AAC encoding with ExtAudioFile on iOS to work? 【发布时间】:2011-06-24 18:55:32 【问题描述】:我需要在 ios 上将 WAVE 文件转换为 AAC 编码的 M4A 文件。我知道旧设备或模拟器不支持 AAC 编码。在运行代码之前,我正在测试它。但我仍然无法让它工作。
我查看了 Apple 自己的 iPhoneExtAudioFileConvertTest 示例,我认为我完全遵循了它,但仍然没有运气!
目前,我在尝试在目标文件上设置客户端格式时得到 -50(= 用户参数列表中的错误)。在源文件上,它可以工作。
下面是我的代码。非常感谢任何帮助,谢谢!
UInt32 size;
// Open a source audio file.
ExtAudioFileRef sourceAudioFile;
ExtAudioFileOpenURL( (CFURLRef)sourceURL, &sourceAudioFile );
// Get the source data format
AudioStreamBasicDescription sourceFormat;
size = sizeof( sourceFormat );
result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &sourceFormat );
// Define the output format (AAC).
AudioStreamBasicDescription outputFormat;
outputFormat.mFormatID = kAudioFormatMPEG4AAC;
outputFormat.mSampleRate = 44100;
outputFormat.mChannelsPerFrame = 2;
// Use AudioFormat API to fill out the rest of the description.
size = sizeof( outputFormat );
AudioFormatGetProperty( kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outputFormat);
// Make a destination audio file with this output format.
ExtAudioFileRef destAudioFile;
ExtAudioFileCreateWithURL( (CFURLRef)destURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &destAudioFile );
// Create canonical PCM client format.
AudioStreamBasicDescription clientFormat;
clientFormat.mSampleRate = sourceFormat.mSampleRate;
clientFormat.mFormatID = kAudioFormatLinearPCM;
clientFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
clientFormat.mChannelsPerFrame = 2;
clientFormat.mBitsPerChannel = 16;
clientFormat.mBytesPerFrame = 4;
clientFormat.mBytesPerPacket = 4;
clientFormat.mFramesPerPacket = 1;
// Set the client format in source and destination file.
size = sizeof( clientFormat );
ExtAudioFileSetProperty( sourceAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
size = sizeof( clientFormat );
ExtAudioFileSetProperty( destAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
// Make a buffer
int bufferSizeInFrames = 8000;
int bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );
UInt8 * buffer = (UInt8 *)malloc( bufferSize );
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mData = buffer;
bufferList.mBuffers[0].mDataByteSize = ( bufferSize );
while( TRUE )
// Try to fill the buffer to capacity.
UInt32 framesRead = bufferSizeInFrames;
ExtAudioFileRead( sourceAudioFile, &framesRead, &bufferList );
// 0 frames read means EOF.
if( framesRead == 0 )
break;
// Write.
ExtAudioFileWrite( destAudioFile, framesRead, &bufferList );
free( buffer );
// Close the files.
ExtAudioFileDispose( sourceAudioFile );
ExtAudioFileDispose( destAudioFile );
【问题讨论】:
曾经得到这个工作?我也希望将 .wav 转换为 .acc。 嘿瑞恩,看看下面我自己的答案。 【参考方案1】:我尝试了 Sebastian 回答中的代码,虽然它适用于未压缩文件(aif、wav、caf),但不适用于有损压缩文件(mp3)。我也有一个错误代码-50
,但在ExtAudioFileRead
而不是ExtAudioFileSetProperty
。从这个question 我了解到这个错误表示函数参数有问题。原来用于读取音频文件的缓冲区大小为 0 字节,这是这一行的结果:
int bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );
将其切换为使用 clientFormat
中的每帧字节数(sourceFormat
的值为 0)对我有用:
int bufferSize = ( bufferSizeInFrames * clientFormat.mBytesPerFrame );
这一行也在问题代码中,但我认为这不是问题(但我的评论文本太多)。
【讨论】:
iOS 没有 mp3 压缩器【参考方案2】:回答了我自己的问题:我不得不将此问题传递给我的同事,他得到了解决!我从来没有机会分析我原来的问题,但我想,为了完整起见,我会把它贴在这里。从 NSThread 中调用以下方法。参数通过“threadDictionary”设置,他创建了一个自定义委托来传输进度反馈(抱歉,SO 没有正确理解格式,下面应该是一个方法实现块):
- (void)encodeToAAC
RXAudioEncoderStatusType encoderStatus;
OSStatus result = noErr;
BOOL success = NO;
BOOL cancelled = NO;
UInt32 size;
ExtAudioFileRef sourceAudioFile,destAudioFile;
AudioStreamBasicDescription sourceFormat,outputFormat, clientFormat;
SInt64 totalFrames;
unsigned long long encodedBytes, totalBytes;
int bufferSizeInFrames, bufferSize;
UInt8 * buffer;
AudioBufferList bufferList;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSFileManager * fileManager = [[[NSFileManager alloc] init] autorelease];
NSMutableDictionary * threadDict = [[NSThread currentThread] threadDictionary];
NSObject<RXAudioEncodingDelegate> * delegate = (NSObject<RXAudioEncodingDelegate> *)[threadDict objectForKey:@"Delegate"];
NSString *sourcePath = (NSString *)[threadDict objectForKey:@"SourcePath"];
NSString *destPath = (NSString *)[threadDict objectForKey:@"DestinationPath"];
NSURL * sourceURL = [NSURL fileURLWithPath:sourcePath];
NSURL * destURL = [NSURL fileURLWithPath:destPath];
// Open a source audio file.
result = ExtAudioFileOpenURL( (CFURLRef)sourceURL, &sourceAudioFile );
if( result != noErr )
DLog( @"Error in ExtAudioFileOpenURL: %ld", result );
goto bailout;
// Get the source data format
size = sizeof( sourceFormat );
result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &sourceFormat );
if( result != noErr )
DLog( @"Error in ExtAudioFileGetProperty: %ld", result );
goto bailout;
// Define the output format (AAC).
memset(&outputFormat, 0, sizeof(outputFormat));
outputFormat.mFormatID = kAudioFormatMPEG4AAC;
outputFormat.mSampleRate = 44100;
outputFormat.mFormatFlags = kMPEG4Object_AAC_Main;
outputFormat.mChannelsPerFrame = 2;
outputFormat.mBitsPerChannel = 0;
outputFormat.mBytesPerFrame = 0;
outputFormat.mBytesPerPacket = 0;
outputFormat.mFramesPerPacket = 1024;
// Use AudioFormat API to fill out the rest of the description.
//size = sizeof( outputFormat );
//AudioFormatGetProperty( kAudioFormatProperty_FormatInfo, 0, NULL, &size, &outputFormat);
// Make a destination audio file with this output format.
result = ExtAudioFileCreateWithURL( (CFURLRef)destURL, kAudioFileM4AType, &outputFormat, NULL, kAudioFileFlags_EraseFile, &destAudioFile );
if( result != noErr )
DLog( @"Error creating destination file: %ld", result );
goto bailout;
// Create the canonical PCM client format.
memset(&clientFormat, 0, sizeof(clientFormat));
clientFormat.mSampleRate = sourceFormat.mSampleRate;
clientFormat.mFormatID = kAudioFormatLinearPCM;
clientFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; //kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
clientFormat.mChannelsPerFrame = 2;
clientFormat.mBitsPerChannel = 16;
clientFormat.mBytesPerFrame = 4;
clientFormat.mBytesPerPacket = 4;
clientFormat.mFramesPerPacket = 1;
// Set the client format in source and destination file.
size = sizeof( clientFormat );
result = ExtAudioFileSetProperty( sourceAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
if( result != noErr )
DLog( @"Error while setting client format in source file: %ld", result );
goto bailout;
size = sizeof( clientFormat );
result = ExtAudioFileSetProperty( destAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat );
if( result != noErr )
DLog( @"Error while setting client format in destination file: %ld", result );
goto bailout;
// Make a buffer
bufferSizeInFrames = 8000;
bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );
buffer = (UInt8 *)malloc( bufferSize );
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mData = buffer;
bufferList.mBuffers[0].mDataByteSize = ( bufferSize );
// Obtain total number of audio frames to encode
size = sizeof( totalFrames );
result = ExtAudioFileGetProperty( sourceAudioFile, kExtAudioFileProperty_FileLengthFrames, &size, &totalFrames );
if( result != noErr )
DLog( @"Error in ExtAudioFileGetProperty, could not get kExtAudioFileProperty_FileLengthFrames from sourceFile: %ld", result );
goto bailout;
encodedBytes = 0;
totalBytes = totalFrames * sourceFormat.mBytesPerFrame;
[threadDict setValue:[NSValue value:&totalBytes withObjCType:@encode(unsigned long long)] forKey:@"TotalBytes"];
if (delegate != nil)
[self performSelectorOnMainThread:@selector(didStartEncoding) withObject:nil waitUntilDone:NO];
while( TRUE )
// Try to fill the buffer to capacity.
UInt32 framesRead = bufferSizeInFrames;
result = ExtAudioFileRead( sourceAudioFile, &framesRead, &bufferList );
if( result != noErr )
DLog( @"Error in ExtAudioFileRead: %ld", result );
success = NO;
break;
// 0 frames read means EOF.
if( framesRead == 0 )
success = YES;
break;
// Write.
result = ExtAudioFileWrite( destAudioFile, framesRead, &bufferList );
if( result != noErr )
DLog( @"Error in ExtAudioFileWrite: %ld", result );
success = NO;
break;
encodedBytes += framesRead * sourceFormat.mBytesPerFrame;
if (delegate != nil)
[self performSelectorOnMainThread:@selector(didEncodeBytes:) withObject:[NSValue value:&encodedBytes withObjCType:@encode(unsigned long long)] waitUntilDone:NO];
if ([[NSThread currentThread] isCancelled])
cancelled = YES;
DLog( @"Encoding was cancelled." );
success = NO;
break;
free( buffer );
// Close the files.
ExtAudioFileDispose( sourceAudioFile );
ExtAudioFileDispose( destAudioFile );
bailout:
encoderStatus.result = result;
[threadDict setValue:[NSValue value:&encoderStatus withObjCType:@encode(RXAudioEncoderStatusType)] forKey:@"EncodingError"];
// Report to the delegate if one exists
if (delegate != nil)
if (success)
[self performSelectorOnMainThread:@selector(didEncodeFile) withObject:nil waitUntilDone:YES];
else if (cancelled)
[self performSelectorOnMainThread:@selector(encodingCancelled) withObject:nil waitUntilDone:YES];
else
[self performSelectorOnMainThread:@selector(failedToEncodeFile) withObject:nil waitUntilDone:YES];
// Clear the partially encoded file if encoding failed or is cancelled midway
if ((cancelled || !success) && [fileManager fileExistsAtPath:destPath])
[fileManager removeItemAtURL:destURL error:NULL];
[threadDict setValue:[NSNumber numberWithBool:NO] forKey:@"isEncoding"];
[pool release];
【讨论】:
我认为这里的重要一点是mFramesPerPacket = 1024
用于 AAC 输出 - 没有它我会出现奇怪的行为。【参考方案3】:
您确定采样率匹配吗?您可以在收到错误时打印clientFormat
和outputFormat
的值吗?否则我想你可能需要AudioConverter
。
【讨论】:
以上是关于如何在 iOS 上使用 ExtAudioFile 进行 AAC 编码?的主要内容,如果未能解决你的问题,请参考以下文章
当 ExtAudioFile 从 AudioFile 转换时,ExtAudioFileGetProperty 失败
ExtAudioFile API 是不是曾经使用硬件来解码音频?
在 iOS 中将 ExtAudioFileRef 类型转换为 AudioFileID 类型
我可以在 AURenderCallback 函数中限制 iOS 传递的 inNumberFrames 吗?