简体   繁体   English

如何在iOS上使用ExtAudioFile进行AAC编码?

[英]How can I get AAC encoding with ExtAudioFile on iOS to work?

I need to convert a WAVE file into an AAC encoded M4A file on iOS. 我需要在iOS上将WAVE文件转换为AAC编码的M4A文件。 I'm aware that AAC encoding is not supported on older devices or in the simulator. 我知道旧设备或模拟器不支持AAC编码。 I'm testing that before I run the code. 我在运行代码之前测试它。 But I still can't get it to work. 但我仍然无法让它发挥作用。

I looked into Apple's very own iPhoneExtAudioFileConvertTest example and I thought I followed it exactly, but still no luck! 我查看了Apple自己的iPhoneExtAudioFileConvertTest示例,我认为我完全遵循它,但仍然没有运气!

Currently, I get a -50 (= error in user parameter list) while trying to set the client format on the destination file. 目前,我在尝试在目标文件上设置客户端格式时得到-50(=用户参数列表中的错误)。 On the source file, it works. 在源文件上,它的工作原理。

Below is my code. 以下是我的代码。 Any help is very much appreciated, thanks! 非常感谢任何帮助,谢谢!

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 );

Answered my own question: I had to pass this problem to my colleague and he got it to work! 回答了我自己的问题:我不得不将这个问题传递给我的同事并让他开始工作! I never had the chance to analyze my original problem but I thought, I'd post it here for the sake of completeness. 我从来没有机会分析我原来的问题,但我想,为了完整起见,我会在这里发布。 The following method is called from within an NSThread. 从NSThread中调用以下方法。 Parameters are set via the 'threadDictionary' and he created a custom delegate to transmit progress feedback (sorry, SO doesn't understand the formatting properly, the following is supposed to be one block of method implementation): 参数是通过'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];
}

I tried out the code in Sebastian's answer and while it worked for uncompressed files (aif, wav, caf), it didn't for a lossy compressed file (mp3). 我在Sebastian的答案中尝试了代码,虽然它适用于未压缩的文件(aif,wav,caf),但它并没有用于有损压缩文件(mp3)。 I also had an error code of -50 , but in ExtAudioFileRead rather than ExtAudioFileSetProperty . 我也有错误代码-50 ,但在ExtAudioFileRead而不是ExtAudioFileSetProperty From this question I learned that this error signifies a problem with the function parameters. 从这个问题我得知这个错误表示函数参数有问题。 Turns out the buffer for reading the audio file had a size of 0 bytes, a result of this line: 原来用于读取音频文件的缓冲区大小为0字节,这一行的结果如下:

int bufferSize = ( bufferSizeInFrames * sourceFormat.mBytesPerFrame );

Switching it to use the the bytes per frame from clientFormat instead ( sourceFormat 's value was 0) worked for me: 将它切换为使用来自clientFormat每帧字节( sourceFormat的值为0)对我clientFormat

int bufferSize = ( bufferSizeInFrames * clientFormat.mBytesPerFrame );

This line was also in the question code, but I don't think that was the problem (but I had too much text for a comment). 这行也在问题代码中,但我不认为这是问题(但我有太多的评论文本)。

Are you sure the sample rates match? 你确定样本率匹配吗? Can you print the values for clientFormat and outputFormat at the point you're getting the error? 你可以在收到错误时打印clientFormatoutputFormat的值吗? Otherwise I think you might need an AudioConverter . 否则我认为你可能需要一个AudioConverter

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM