同时使用AVCaptureSession和Audio Units会导致AVAssetWriterInput出现问题

时间:2015-07-08 03:40:01

标签: ios audio core-audio audio-recording

我正在开发一款同时执行两项操作的iOS应用程序:

  1. 它捕获音频和视频,并将它们中继到服务器以提供视频聊天功能。
  2. 它捕获本地音频和视频,并将它们编码为mp4文件,以便为后代保存。
  3. 不幸的是,当我们使用音频设备配置应用以启用回声消除时,录制功能会中断:我们用于编码音频的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缓冲区就可以了我想尽可能避免的工作。

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

1 个答案:

答案 0 :(得分:4)

最终,我能够通过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... 来解决此问题。编码器。

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

.navbar

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