AVAssetWriterによるビデオ録画時に、先頭にblank frameが入ってしまう

急にiosのネタですが、かなりハマったので、メモしておきます。

 

非常にレアケースですが、カメラ、マイクを使ったAVAssetWriterによる音声付きビデオの録画時、まれに先頭フレームが空になるケースがあります。これは、ビデオに対し遅延が発生するようなエフェクト等の重い処理を行った場合に特に発生しやすいようです。

 

現象を追跡してみました。

 

カメラ、マイクから毎フレーム送られてくるCMSampleBufferRefには、タイミング情報が含まれています。

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

CMTime timestamp = CMSampleBufferGetPresentationTimeStamp( sampleBuffer );

ここで取得できる時間情報はおそらくelapsed timeだと思います。(アプリの起動からの時間)

 

録画処理を開始するとAVAssetWriterに対して、AudioとVideoのCMSampleBufferを追記していくわけですが、このタイミング情報を一緒に渡すことで、同期が取られるようです。タイミング情報は前述のとおりelapsedTimeとなっているので、先頭フレームの書き込み時には、下記のように録画開始のタイミングをAVAssetWriterに通知します。

[_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];

この先頭フレームのタイミングは、通常、ほぼ同時またはVideoバッファが先行して送られてくるわけですが、ビデオに対し一定の処理時間をかけた場合、前フレームの処理遅延により、Audioバッファが数フレーム先行して記録されてしまうケースが発生します。startSessionAtSourceTimeでは、audio,videoを識別せずに追記した場合、videoが抜け、Audioだけ存在するフレームが発生してしまいます。先頭フレームの場合、dropframeにならないため、ブランクビデオが記録されたような状態になります。

 

以上がほぼ原因として間違いなさそうなので、初期フレームをVideoに限定することで対応が可能です。

if(!_haveStartedSession){
  //初期バッファの処理
  if(mediaType == AVMediaTypeVideo){
    //ビデオの場合にタイミングを通知
    [_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
    _haveStartedSession = YES;
  }else{
    //初期バッファがオーディオなら一旦破棄して次のバッファーを待機
    CFRelease( sampleBuffer );
    return;
  }
}
//以降sampleBufferを記録

AVFoundation、膨大で大変すね・・・。