9

我需要在 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 );
4

3 回答 3

13

回答了我自己的问题:我不得不将这个问题传递给我的同事,他才得以解决!我从来没有机会分析我原来的问题,但我想,为了完整起见,我会把它贴在这里。从 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];
}
于 2011-06-11T09:19:33.047 回答
0

我在塞巴斯蒂安的回答中尝试了代码,虽然它适用于未压缩的文件(aif、wav、caf),但它不适用于有损压缩文件(mp3)。我也有一个错误代码-50,但在ExtAudioFileRead而不是ExtAudioFileSetProperty。从这个问题我了解到这个错误表示函数参数有问题。原来用于读取音频文件的缓冲区大小为 0 字节,这是这一行的结果:

int bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );

将其切换为使用每帧的字节数clientFormatsourceFormat的值为 0)对我有用:

int bufferSize = ( bufferSizeInFrames * clientFormat.mBytesPerFrame );

这一行也在问题代码中,但我认为这不是问题(但我的评论文本太多)。

于 2012-12-25T02:03:54.553 回答
0

你确定采样率匹配吗?你能打印出错误的值clientFormatoutputFormat?否则我认为你可能需要一个AudioConverter.

于 2011-05-15T11:29:20.737 回答