【问题标题】:Using AVCaptureSession and Audio Units Together Causes Problems for AVAssetWriterInput一起使用 AVCaptureSession 和音频单元会导致 AVAssetWriterInput 出现问题
【发布时间】:2015-09-25 19:12:10
【问题描述】:

我正在开发一个同时做两件事的 iOS 应用:

  1. 它捕获音频和视频并将它们中继到服务器以提供视频聊天功能。
  2. 它捕获本地音频和视频并将它们编码为 mp4 文件以供后代保存。

不幸的是,当我们为应用配置音频单元以启用回声消除时,录制功能会中断:我们用于编码音频的 AVAssetWriterInput 实例会拒绝传入的样本。当我们不设置音频单元时,录音工作,但我们有可怕的回声。

为了启用回声消除,我们像这样配置一个音频单元(为了简洁起见进行解释):

AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
OSStatus status = AudioComponentInstanceNew(comp, &_audioUnit);
status = AudioUnitInitialize(_audioUnit);

这适用于视频聊天,但它破坏了像这样设置的录制功能(再次解释一下,实际实现分散在几种方法中)。

_captureSession = [[AVCaptureSession alloc] init];

// Need to use the existing audio session & configuration to ensure we get echo cancellation
_captureSession.usesApplicationAudioSession = YES;
_captureSession.automaticallyConfiguresApplicationAudioSession = NO;

[_captureSession beginConfiguration];
AVCaptureDeviceInput *audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self audioCaptureDevice] error:NULL];
[_captureSession addInput:audioInput];
_audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];
[_audioDataOutput setSampleBufferDelegate:self queue:_cameraProcessingQueue];
[_captureSession addOutput:_audioDataOutput];
[_captureSession commitConfiguration];

captureOutput 的相关部分看起来像这样:

NSLog(@"Audio format, channels: %d, sample rate: %f, format id: %d, bits per channel: %d", basicFormat->mChannelsPerFrame, basicFormat->mSampleRate, basicFormat->mFormatID, basicFormat->mBitsPerChannel);
if (_assetWriter.status == AVAssetWriterStatusWriting) {
    if (_audioEncoder.readyForMoreMediaData) {
        if (![_audioEncoder appendSampleBuffer:sampleBuffer]) {
            NSLog(@"Audio encoder couldn't append sample buffer");
        }
    }
}

发生的情况是对appendSampleBuffer 的呼叫失败,但是——这是奇怪的部分——只有当我没有将耳机插入我的手机时。检查发生这种情况时产生的日志,我发现在未连接耳机的情况下,日志消息中报告的频道数为3,而在连接耳机的情况下,频道数为1强>。这解释了编码操作失败的原因,因为编码器被配置为只需要一个通道。

我不明白的是为什么我在这里获得三个频道。如果我注释掉初始化音频单元的代码,我只会得到一个通道并且录制工作正常,但回声消除不起作用。此外,如果我删除这些行

// Need to use the existing audio session & configuration to ensure we get echo cancellation
_captureSession.usesApplicationAudioSession = YES;
_captureSession.automaticallyConfiguresApplicationAudioSession = NO;

录音工作(我只得到一个带或不带耳机的通道),但同样,我们失去了回声消除。

所以,我的问题的关键是:为什么当我配置音频单元以提供回声消除时,我会获得三个音频通道?此外,是否有任何方法可以防止这种情况发生或使用AVCaptureSession 解决此问题?

我考虑过将麦克风音频直接从低级音频单元回调传送到编码器以及聊天管道,但看起来需要使用必要的 Core Media 缓冲区来做到这一点如果可能的话,我想避免的工作。

请注意,聊天和录音功能是由不同的人编写的——我都不是——这就是这段代码没有更加集成的原因。如果可能的话,我想避免重构整个混乱。

【问题讨论】:

    标签: ios audio core-audio audio-recording


    【解决方案1】:

    最终,我能够通过 I/O 音频单元从麦克风收集音频样本,将这些样本重新打包到 CMSampleBuffer,并将新构建的 CMSampleBuffer 传递到编码器,从而解决这个问题。

    进行转换的代码如下所示(为简洁起见进行了缩写)。

    // Create a CMSampleBufferRef from the list of samples, which we'll own
    AudioStreamBasicDescription monoStreamFormat;
    memset(&monoStreamFormat, 0, sizeof(monoStreamFormat));
    monoStreamFormat.mSampleRate = 48000;
    monoStreamFormat.mFormatID = kAudioFormatLinearPCM;
    monoStreamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
    monoStreamFormat.mBytesPerPacket = 2;
    monoStreamFormat.mFramesPerPacket = 1;
    monoStreamFormat.mBytesPerFrame = 2;
    monoStreamFormat.mChannelsPerFrame = 1;
    monoStreamFormat.mBitsPerChannel = 16;
    
    CMFormatDescriptionRef format = NULL;
    OSStatus status = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &monoStreamFormat, 0, NULL, 0, NULL, NULL, &format);
    
    // Convert the AudioTimestamp to a CMTime and create a CMTimingInfo for this set of samples
    uint64_t timeNS = (uint64_t)(hostTime * _hostTimeToNSFactor);
    CMTime presentationTime = CMTimeMake(timeNS, 1000000000);
    CMSampleTimingInfo timing = { CMTimeMake(1, 48000), presentationTime, kCMTimeInvalid };
    
    CMSampleBufferRef sampleBuffer = NULL;
    status = CMSampleBufferCreate(kCFAllocatorDefault, NULL, false, NULL, NULL, format, numSamples, 1, &timing, 0, NULL, &sampleBuffer);
    
    // add the samples to the buffer
    status = CMSampleBufferSetDataBufferFromAudioBufferList(sampleBuffer,
                                                            kCFAllocatorDefault,
                                                            kCFAllocatorDefault,
                                                            0,
                                                            samples);
    
    // Pass the buffer into the encoder...
    

    请注意,我已删除分配对象的错误处理和清理。

    【讨论】:

    • 嗨,这是一个非常古老的线程,但我们也在尝试使用相同的设置进行本地录制。你有完整的代码来解决你的问题吗?我们正面临着同样的事情。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2016-02-04
    • 2016-09-08
    • 1970-01-01
    • 2012-05-08
    相关资源
    最近更新 更多