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

时间:2015-02-04 09:19:33

标签: ios audio recording avassetwriter avassetreader

我的应用使用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 个答案:

答案 0 :(得分:2)

事实证明,AVAsset需要“有效”的文件扩展名。因此,当文件名没有其中一个常见扩展名(如* .mp3,* .caf,* .m4a等)时,AVAsset拒绝查看文件头以确定媒体格式。另一方面,AVAudioPlay似乎对文件名完全无动于衷,并通过查看文件头来自行计算出媒体格式。

这种差异在Apple doc中没有出现。最后我浪费了一个多星期的时间。叹息......