7

ネットワーク経由でビデオとオーディオをストリーミングする iOS アプリを作成しています。

私はAVCaptureSessionを使用して、 AVCaptureVideoDataOutputを使用して生のビデオ フレームを取得し、 x264 を使用してソフトウェアでエンコードしています。これはうまくいきます。

オーディオについても同じことをしたかったのですが、オーディオ側でそれほど多くの制御を必要としないため、組み込みのハードウェアエンコーダーを使用して AAC ストリームを生成したかっただけです。これは、オーディオ ツールボックス レイヤーからオーディオ コンバーターを使用することを意味していました。そのために、AVCaptudeAudioDataOutputのオーディオ フレームのハンドラーを追加しました。

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection 
{
    // get the audio samples into a common buffer _pcmBuffer
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);

    // use AudioConverter to
    UInt32 ouputPacketsCount = 1;
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mDataByteSize = sizeof(_aacBuffer);
    bufferList.mBuffers[0].mData = _aacBuffer;
    OSStatus st = AudioConverterFillComplexBuffer(_converter, converter_callback, (__bridge void *) self, &ouputPacketsCount, &bufferList, NULL);
    if (0 == st) {
        // ... send bufferList.mBuffers[0].mDataByteSize bytes from _aacBuffer...
    }
}

この場合、オーディオ コンバーターのコールバック関数は非常に単純です (パケット サイズとカウントが適切に設定されていると仮定します)。

- (void) putPcmSamplesInBufferList:(AudioBufferList *)bufferList withCount:(UInt32 *)count
{
    bufferList->mBuffers[0].mData = _pcmBuffer;         
    bufferList->mBuffers[0].mDataByteSize = _pcmBufferSize;
}

オーディオコンバーターのセットアップは次のようになります。

{
    // ...
    AudioStreamBasicDescription pcmASBD = {0};
    pcmASBD.mSampleRate = ((AVAudioSession *) [AVAudioSession sharedInstance]).currentHardwareSampleRate;
    pcmASBD.mFormatID = kAudioFormatLinearPCM;
    pcmASBD.mFormatFlags = kAudioFormatFlagsCanonical;
    pcmASBD.mChannelsPerFrame = 1;
    pcmASBD.mBytesPerFrame = sizeof(AudioSampleType);
    pcmASBD.mFramesPerPacket = 1;
    pcmASBD.mBytesPerPacket = pcmASBD.mBytesPerFrame * pcmASBD.mFramesPerPacket;
    pcmASBD.mBitsPerChannel = 8 * pcmASBD.mBytesPerFrame;

    AudioStreamBasicDescription aacASBD = {0};
    aacASBD.mFormatID = kAudioFormatMPEG4AAC;
    aacASBD.mSampleRate = pcmASBD.mSampleRate;
    aacASBD.mChannelsPerFrame = pcmASBD.mChannelsPerFrame;
    size = sizeof(aacASBD);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &aacASBD);

    AudioConverterNew(&pcmASBD, &aacASBD, &_converter);
    // ...
}

これは、 IT DOES NOT WORKだけでかなり単純明快に思えます。AVCaptureSession が実行されると、オーディオ コンバーター (具体的には AudioConverterFillComplexBuffer) が「hwiu」(使用中のハードウェア) エラーを返します。セッションが停止している場合、変換は正常に機能しますが、何もキャプチャできません...

AVCaptureSession から AAC ストリームを取得する方法があるかどうか疑問に思っていました。私が検討しているオプションは次のとおりです。

  1. AVAssetWriterInput を使用してオーディオ サンプルを AAC にエンコードし、エンコードされたパケットを何らかの方法で取得します (ファイルにのみ書き込む AVAssetWriter 経由ではありません)。

  2. ビデオ側でのみ AVCaptureSession を使用し、オーディオ側でAudio Queuesを使用するように、アプリを再編成します。これにより、フロー制御 (録画の開始と停止、中断への対応) がより複雑になり、オーディオとビデオの間の同期の問題が発生する可能性があります。また、良いデザインとは思えません。

AVCaptureSession から AAC を取得できるかどうかは誰にもわかりませんか? ここで Audio Queues を使用する必要がありますか? これにより、同期または制御の問題が発生する可能性はありますか?

4

1 に答える 1

7

私は結局Appleにアドバイスを求めた(有料の開発者アカウントを持っていればそれができることがわかった)。

AVCaptureSessionはAACハードウェアエンコーダーを取得しているようですが、それを使用してファイルに直接書き込むことしかできません。

ソフトウェアエンコーダーを使用できますが、AudioConverterNewを使用する代わりに、具体的に要求する必要があります。

AudioClassDescription *description = [self
        getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC
                        fromManufacturer:kAppleSoftwareAudioCodecManufacturer];
if (!description) {
    return false;
}
// see the question as for setting up pcmASBD and arc ASBD
OSStatus st = AudioConverterNewSpecific(&pcmASBD, &aacASBD, 1, description, &_converter);
if (st) {
    NSLog(@"error creating audio converter: %s", OSSTATUS(st));
    return false;
}

- (AudioClassDescription *)getAudioClassDescriptionWithType:(UInt32)type
                                           fromManufacturer:(UInt32)manufacturer
{
    static AudioClassDescription desc;

    UInt32 encoderSpecifier = type;
    OSStatus st;

    UInt32 size;
    st = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
                                    sizeof(encoderSpecifier),
                                    &encoderSpecifier,
                                    &size);
    if (st) {
        NSLog(@"error getting audio format propery info: %s", OSSTATUS(st));
        return nil;
    }

    unsigned int count = size / sizeof(AudioClassDescription);
    AudioClassDescription descriptions[count];
    st = AudioFormatGetProperty(kAudioFormatProperty_Encoders,
                                sizeof(encoderSpecifier),
                                &encoderSpecifier,
                                &size,
                                descriptions);
    if (st) {
        NSLog(@"error getting audio format propery: %s", OSSTATUS(st));
        return nil;
    }

    for (unsigned int i = 0; i < count; i++) {
        if ((type == descriptions[i].mSubType) &&
            (manufacturer == descriptions[i].mManufacturer)) {
            memcpy(&desc, &(descriptions[i]), sizeof(desc));
            return &desc;
        }
    }

    return nil;
}

もちろん、ソフトウェアエンコーダーはCPUリソースを消費しますが、仕事は完了します。

于 2012-07-06T07:47:52.297 に答える