使用来自原始 PCM 流的 CMSampleTimingInfo、CMSampleBuffer 和 AudioBufferList
Using CMSampleTimingInfo, CMSampleBuffer and AudioBufferList from raw PCM stream
我正在从 Google 的 WebRTC C++ 参考实现(插入 VoEBaseImpl::GetPlayoutData
的挂钩)接收原始 PCM 流。音频似乎是线性 PCM,符号为 int16,但在使用 AssetWriter 录制时,它会保存到音频文件中,高度失真且音调更高。
我假设这是输入参数某处的错误,很可能是关于将立体声 int16 转换为 AudioBufferList,然后再转换为 CMSampleBuffer。下面的代码有问题吗?
void RecorderImpl::RenderAudioFrame(void* audio_data, size_t number_of_frames, int sample_rate, int64_t elapsed_time_ms, int64_t ntp_time_ms) {
OSStatus status;
AudioChannelLayout acl;
bzero(&acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = sample_rate;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = audioFormat.mFramesPerPacket * audioFormat.mChannelsPerFrame * audioFormat.mBitsPerChannel / 8;
audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket / audioFormat.mFramesPerPacket;
CMSampleTimingInfo timing = { CMTimeMake(1, sample_rate), CMTimeMake(elapsed_time_ms, 1000), kCMTimeInvalid };
CMFormatDescriptionRef format = NULL;
status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &audioFormat, sizeof(acl), &acl, 0, NULL, NULL, &format);
if(status != 0) {
NSLog(@"Failed to create audio format description");
return;
}
CMSampleBufferRef buffer;
status = CMSampleBufferCreate(kCFAllocatorDefault, NULL, false, NULL, NULL, format, (CMItemCount)number_of_frames, 1, &timing, 0, NULL, &buffer);
if(status != 0) {
NSLog(@"Failed to allocate sample buffer");
return;
}
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = audioFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mDataByteSize = (UInt32)(number_of_frames * audioFormat.mBytesPerFrame);
bufferList.mBuffers[0].mData = audio_data;
status = CMSampleBufferSetDataBufferFromAudioBufferList(buffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, &bufferList);
if(status != 0) {
NSLog(@"Failed to convert audio buffer list into sample buffer");
return;
}
[recorder writeAudioFrames:buffer];
CFRelease(buffer);
}
作为参考,我在 iPhone 6S+ / iOS 9.2 上从 WebRTC 接收的采样率为 48kHz,每次调用此挂钩有 480 个样本,我每 10 次接收一次数据女士
首先,恭喜您冒昧地从头开始创建音频 CMSampleBuffer
。对于大多数人来说,它们既没有被创造也没有被摧毁,而是从 CoreMedia
和 AVFoundation
.
完美无瑕而神秘地流传下来
你的计时信息中的presentationTimeStamp
是整数毫秒,不能代表你的48kHz样本的时间位置。
尝试 CMTimeMake(elapsed_frames, sample_rate)
而不是 CMTimeMake(elapsed_time_ms, 1000)
,其中 elapsed_frames
是您之前写入的帧数。
这可以解释失真,但不能解释音高,因此请确保 AudioStreamBasicDescription
与您的 AVAssetWriterInput
设置相匹配。没有看到您的 AVAssetWriter
代码很难说。
p.s 留意 writeAudioFrames
- 如果它是异步的,您将遇到 audio_data
的所有权问题。
p.p.s。看起来你在泄露 CMFormatDescriptionRef
.
我最终打开了在 Audacity 中生成的音频文件,发现每一帧都掉了一半,如这个看起来很奇怪的波形所示:
将acl.mChannelLayoutTag
更改为kAudioChannelLayoutTag_Mono
并将audioFormat.mChannelsPerFrame
更改为1
解决了问题,现在音频质量完美。万岁!
我正在从 Google 的 WebRTC C++ 参考实现(插入 VoEBaseImpl::GetPlayoutData
的挂钩)接收原始 PCM 流。音频似乎是线性 PCM,符号为 int16,但在使用 AssetWriter 录制时,它会保存到音频文件中,高度失真且音调更高。
我假设这是输入参数某处的错误,很可能是关于将立体声 int16 转换为 AudioBufferList,然后再转换为 CMSampleBuffer。下面的代码有问题吗?
void RecorderImpl::RenderAudioFrame(void* audio_data, size_t number_of_frames, int sample_rate, int64_t elapsed_time_ms, int64_t ntp_time_ms) {
OSStatus status;
AudioChannelLayout acl;
bzero(&acl, sizeof(acl));
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = sample_rate;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = audioFormat.mFramesPerPacket * audioFormat.mChannelsPerFrame * audioFormat.mBitsPerChannel / 8;
audioFormat.mBytesPerFrame = audioFormat.mBytesPerPacket / audioFormat.mFramesPerPacket;
CMSampleTimingInfo timing = { CMTimeMake(1, sample_rate), CMTimeMake(elapsed_time_ms, 1000), kCMTimeInvalid };
CMFormatDescriptionRef format = NULL;
status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &audioFormat, sizeof(acl), &acl, 0, NULL, NULL, &format);
if(status != 0) {
NSLog(@"Failed to create audio format description");
return;
}
CMSampleBufferRef buffer;
status = CMSampleBufferCreate(kCFAllocatorDefault, NULL, false, NULL, NULL, format, (CMItemCount)number_of_frames, 1, &timing, 0, NULL, &buffer);
if(status != 0) {
NSLog(@"Failed to allocate sample buffer");
return;
}
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = audioFormat.mChannelsPerFrame;
bufferList.mBuffers[0].mDataByteSize = (UInt32)(number_of_frames * audioFormat.mBytesPerFrame);
bufferList.mBuffers[0].mData = audio_data;
status = CMSampleBufferSetDataBufferFromAudioBufferList(buffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, &bufferList);
if(status != 0) {
NSLog(@"Failed to convert audio buffer list into sample buffer");
return;
}
[recorder writeAudioFrames:buffer];
CFRelease(buffer);
}
作为参考,我在 iPhone 6S+ / iOS 9.2 上从 WebRTC 接收的采样率为 48kHz,每次调用此挂钩有 480 个样本,我每 10 次接收一次数据女士
首先,恭喜您冒昧地从头开始创建音频 CMSampleBuffer
。对于大多数人来说,它们既没有被创造也没有被摧毁,而是从 CoreMedia
和 AVFoundation
.
你的计时信息中的presentationTimeStamp
是整数毫秒,不能代表你的48kHz样本的时间位置。
尝试 CMTimeMake(elapsed_frames, sample_rate)
而不是 CMTimeMake(elapsed_time_ms, 1000)
,其中 elapsed_frames
是您之前写入的帧数。
这可以解释失真,但不能解释音高,因此请确保 AudioStreamBasicDescription
与您的 AVAssetWriterInput
设置相匹配。没有看到您的 AVAssetWriter
代码很难说。
p.s 留意 writeAudioFrames
- 如果它是异步的,您将遇到 audio_data
的所有权问题。
p.p.s。看起来你在泄露 CMFormatDescriptionRef
.
我最终打开了在 Audacity 中生成的音频文件,发现每一帧都掉了一半,如这个看起来很奇怪的波形所示:
将acl.mChannelLayoutTag
更改为kAudioChannelLayoutTag_Mono
并将audioFormat.mChannelsPerFrame
更改为1
解决了问题,现在音频质量完美。万岁!