使用 AVAssetWriter 和 AVAssetReader 进行音频录制和播放

Posted

技术标签:

【中文标题】使用 AVAssetWriter 和 AVAssetReader 进行音频录制和播放【英文标题】:Using AVAssetWriter and AVAssetReader for Audio Recording and Playback 【发布时间】:2015-02-04 09:19:33 【问题描述】:

我的应用使用 AVAssetReader 播放 iPod 库中的歌曲。现在我想添加录音功能。

我使用 AVAssetWriter 录制了音频。我通过使用 AVAudioPlayer 成功播放来检查生成的音频文件(MPEG4AAC 格式)。我的目标是使用 AVAssetReader 播放音频。但是当我为文件创建 AVURLAsset 时,它没有轨道,因此 AVAssetReader 失败(错误代码:-11828 文件格式无法识别)。

我应该怎么做才能让 AVAsset 识别文件格式? AVAsset 是否需要一些特殊的文件格式?

以下是录制代码:

void setup_ASBD(void *f, double fs, int sel, int numChannels);
static AVAssetWriter *assetWriter = NULL;
static AVAssetWriterInput *assetWriterInput = NULL;
static CMAudioFormatDescriptionRef formatDesc;
AVAssetWriter *newAssetWriter(NSURL *url) 
    NSError *outError;
    assetWriter = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeAppleM4A error:&outError];

    if(assetWriter == nil) 
        NSLog(@"%s: asset=%x, %@\n", __FUNCTION__, (int)assetWriter, outError);
        return assetWriter;
    

    AudioChannelLayout audioChannelLayout = 
        .mChannelLayoutTag = kAudioChannelLayoutTag_Mono,
        .mChannelBitmap = 0,
        .mNumberChannelDescriptions = 0
    ;

    // Convert the channel layout object to an NSData object.
    NSData *channelLayoutAsData = [NSData dataWithBytes:&audioChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];

    // Get the compression settings for 128 kbps AAC.
    NSDictionary *compressionAudiosettings = @
                                               AVFormatIDKey         : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
                                               AVEncoderBitRateKey   : [NSNumber numberWithInteger:128000],
                                               AVSampleRateKey       : [NSNumber numberWithInteger:44100],
                                               AVChannelLayoutKey    : channelLayoutAsData,
                                               AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:1]
                                               ;

    // Create the asset writer input with the compression settings and specify the media type as audio.
    assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:compressionAudioSettings];
    assetWriterInput.expectsMediaDataInRealTime = YES;

    // Add the input to the writer if possible.
    if (assetWriterInput != NULL && [assetWriter canAddInput:assetWriterInput]) 
        [assetWriter addInput:assetWriterInput];
    
    else 
        NSLog(@"%s:assetWriteInput problem: %x\n", __FUNCTION__, (int)assetWriterInput);
        return NULL;
    

    [assetWriter startWriting];
    // Start a sample-writing session.
    [assetWriter startSessionAtSourceTime:kCMTimeZero];

    if(assetWriter.status != AVAssetWriterStatusWriting) 
        NSLog(@"%s: Bad writer status=%d\n", __FUNCTION__, (int)assetWriter.status);
        return NULL;
    

    AudioStreamBasicDescription ASBD;
    setup_ASBD(&ASBD, 44100, 2, 1);
    CMAudioFormatDescriptionCreate (NULL, &ASBD, sizeof(audioChannelLayout), &audioChannelLayout, 0, NULL, NULL, &formatDesc);
    //CMAudioFormatDescriptionCreate (NULL, &ASBD, 0, NULL, 0, NULL, NULL, &formatDesc);

    return assetWriter;



static int sampleCnt = 0;
void writeNewSamples(void *buffer, int len) 
    if(assetWriterInput == NULL) return;
    if([assetWriterInput isReadyForMoreMediaData]) 
        OSStatus result;
        CMBlockBufferRef blockBuffer = NULL;
        result = CMBlockBufferCreateWithMemoryBlock (NULL, buffer, len, NULL, NULL, 0, len, 0, &blockBuffer);
        if(result == noErr) 
            CMItemCount numSamples = len >> 1;

            const CMSampleTimingInfo sampleTiming = CMTimeMake(1, 44100), CMTimeMake(sampleCnt, 44100), kCMTimeInvalid;
            CMItemCount numSampleTimingEntries = 1;

            const size_t sampleSize = 2;
            CMItemCount numSampleSizeEntries = 1;

            CMSampleBufferRef sampleBuffer;
            result = CMSampleBufferCreate(NULL, blockBuffer, true, NULL, NULL, formatDesc, numSamples, numSampleTimingEntries, &sampleTiming, numSampleSizeEntries, &sampleSize, &sampleBuffer);

            if(result == noErr) 
                if([assetWriterInput appendSampleBuffer:sampleBuffer] == YES) sampleCnt += numSamples;
                else 
                    NSLog(@"%s: ERROR\n", __FUNCTION__);
                
                printf("sampleCnt = %d\n", sampleCnt);
                CFRelease(sampleBuffer);

            
        
    
    else 
        NSLog(@"%s: AVAssetWriterInput not taking input data: status=%ld\n", __FUNCTION__, assetWriter.status);
    


void stopAssetWriter(AVAssetWriter *assetWriter) 
    [assetWriterInput markAsFinished];
    [assetWriter finishWritingWithCompletionHandler:^
        NSLog(@"%s: Done: %ld: %d samples\n", __FUNCTION__, assetWriter.status, sampleCnt);
        sampleCnt = 0;
    ];
    assetWriterInput = NULL;

【问题讨论】:

【参考方案1】:

事实证明,AVAsset 需要“有效”的文件扩展名。因此,当文件名不具有 *.mp3、*.caf、*.m4a 等常见扩展名之一时,AVAsset 拒绝查看文件头来确定媒体格式。另一方面,AVAudioPlay 似乎对文件名完全无动于衷,通过查看文件头自行判断媒体格式。

这种差异不会出现在 Apple 文档的任何地方。我最终在这上面浪费了一个多星期。叹息……

【讨论】:

以上是关于使用 AVAssetWriter 和 AVAssetReader 进行音频录制和播放的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 AVAssetReader 和 AVAssetWriter 创建 AAC 文件?

我可以使用 AVAssetWriter 代替 AVExportSession 吗?

使用 AVCaptureSession 和 AVAssetWriter 在翻转相机时无缝录制音频

如何使用 AVAssetWriter 在 ios 中写入 AAC 音频?

使用 AVAssetWriter 和 CoreML 的相机上的 FPS 不一致

从使用 AVAssetWriter 创建的视频生成缩略图