Nakajijapan

生きるのに必死です。

録画時にブラックシーンができてしまう問題

WWDC2014に行って来たの話になるのですが、 プライベートでアプリ作成しているときにAV Foundation絡みでわからないところがあったのでAppleのエンジニアさんに質問してきました。

現象

問題は、自分が作成したNKJMultiMovieCaptureViewでその現象が発生しました。 作成したときの話はこちらになります。 Vine動画のようにタッチしたら録画し、タッチが終了したら(指を離したら)録画を終了し、録画した複数の動画を一つにする仕組みを作成したのですが、 実際作成した動画を見てみると最初のほんの一瞬だけなぜかブラックシーンができてしまいます。

現状の処理として以下のようなことをしています。

1. 録画開始

1
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

タッチイベントで録画の処理を開始します。このときにファイルに対しての書き込み処理を開始します。

1
2
[self.assetWriter startWriting];
[self.assetWriter startSessionAtSourceTime:self.recordStartTime];

2. 録画

AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegateをプロトコルとして指定して、 以下のメソッドで録画の処理を行います。

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

ざっくりですが、内部でやっていることはCMSampleBufferというフレーム情報が取得でき、それを1秒間に何回も実行されるこのメソッドでAVAssetWriterInputオブジェクトに 追加処理をしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CFRetain(sampleBuffer);
CFRetain(formatDescription);
dispatch_async(self.movieWritingQueue, ^{

    if (_assetWriter.status == AVAssetWriterStatusWriting) {

        if (assetWriterInput.readyForMoreMediaData) {

            if (![assetWriterInput appendSampleBuffer:sampleBuffer]) {
                NSLog(@"%@",[self.assetWriter error]);
            }

        }
    }

    CFRelease(sampleBuffer);
    CFRelease(formatDescription);
});

3. 録画終了

タッチが終了したら録画終了の処理を行います。

1
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

問題

どうやらスレッドを利用しての保存方法に問題があったようです。

修正前

AVAssetWriter

録画開始とフレーム情報を保存する処理が別スレッドで行われているせいで微妙にタイミングがずれたフレーム情報を取得してしまっている。 ここっだと、開始した時間よりも一瞬だけ先のフレーム情報を取得してしまうので最初の一瞬はブラックシーンになります。

修正後

AVAssetWriter

なので録画開始も同じスレッドで順次処理させるようにし、時間の誤差を最小限にしました。こうすることで無事ブラックシーンが無くなりました。

実際には以下のように修正しています。

1
2
3
4
5
6
7
8
9
 // Record
 NSLog(@"[Starting to record]");
-    [self.assetWriter startWriting];
-    [self.assetWriter startSessionAtSourceTime:self.recordStartTime];
+    dispatch_async(self.movieWritingQueue, ^{
+        [self.assetWriter startWriting];
+        [self.assetWriter startSessionAtSourceTime:self.recordStartTime];
+    });
+

まとめ

とはいえ、この実装も完璧というわけではなくてタイミングがずれることはあるとおっしゃっていましたが、 今のところ発生していないので安心しています。

参考