使用 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 音频?