昨年12月に作った録画ServiceリポジトリにMP4ファイルの自動分割用クラス達を追加しました。
録画Serviceって何って人は以前の記事録画Serviceリポジトリを公開した(^o^)vを参照してくだされ。
リポジトリ自体はここにあります。RecordingService
MP4ファイルの自動分割って必要なん?
必要です。キッパリ
生成ファイルが小さいということを保証できるのであれば必要ないですが^^;
なんで?
Androidで動画を録画する場合にはMP4ファイルとして出力することがほとんどだと思います。昔々は当初はMediaRecorderを使って内蔵カメラの映像を直接録画する以外、例えば内蔵カメラからの映像に何か映像効果を加えたり、あるいは外付けUVC機器からの映像を動画として保存しようとするとffmpegを使って出力する必要がありました。ですがAndroid 4.3辺りから動画関係のAPIが充実してきた結果ffmpegを使わずとも比較的簡単に動画を作成したり編集することができるようになりました。
現在では、カメラからの映像を単純に保存するだけならMediaRecorder、なんらかの処理を行って保存するのであればMediaCodec(のエンコーダー)とMediaMuxerを使うのが一般的だと思います。
MediaMuxerというのがMP4ファイルとして出力する機能を担っているわけですが、実は生成することができるMP4ファイルのサイズに制限があります。明確なドキュメントはありませんが、AOSPのソースを読むと4GB×95%≒3.8GBが上限になっています。おそらくFAT32フォーマットのSDカードに書込み可能なファイルサイズが最大で4GBというところで若干の余裕をもたせた値を使っているのではないかと思います。
AOSPソースを読む限りでは64ビットオフセットモードにすればこの制限を超えることができそうなのですが、今のところどの端末でも使えるのかどうかがわかりませんし、設定をしても実際に64ビットモードになっているのかどうかがよくわからないので、最大で約4GBの壁は越えることができないものとしてアプリを作る必要があります。
一番の問題は、この約4GBの最大ファイルサイズの制限にヒットした時にMP4ファイルへの出力を正常に終了することができない点です。以前の記事にも書きましたが、MP4ファイルは全てのデータを書き込んだ後にmoovボックスを書き込まない限り再生することができません。
では約4GBの最大ファイル制限にかかるまでにどれぐらいの時間録画できるかと言うと、1280×720(HD解像度)@30fps, bpp=0.15で90〜100分程度です。これでは長時間の録画ができませんよね。
もっとも、長時間録画&4GBを超えるような巨大ファイルを生成して最後の最後にmoovボックスを書けずに再生不可なファイルができてしまうのとどちらがいいのかということもありますが。
いずれにせよ最大ファイルサイズが存在するからにはそれ以上の時間録画するにはファイルを分割する必要があります。
ということで分割録画
一番簡単に分割録画するには、MediaRecorderを使うにしてもMediaCodecエンコーダー+MediaMuxerを使うにしても普通に録画開始してファイルサイズが一定以上になりそうになったら録画を終了して、ついでまた別のファイルへ録画を開始しなおす方法があります。しかし簡単に作れる反面ファイルとファイルの間に数フレーム〜数十フレームほど録画できない映像ができてしまいます。
どうしたらええんじゃ?
MediaRecorderを使う場合には、録画ファイル切替時の数フレーム〜数十フレームのフレーム落ちは防ぎようがありませんので、MediaCodecエンコーダー+MediaMuxerを使う場合に限定して考えてみましょう。
普通にMediaCodecエンコーダー+MediaMuxerを使って録画する場合の処理の流れは次のようになっています。
- MediaMuxerを生成
- MediaCodecエンコーダーを生成
- MeidaCodec#prepareを呼んで初期化
- MediaCodec#startを実行
- MediaCodecエンコーダーへデーターを入力
- MP4ファイルへの出力に必要なデータ(CSD,Codec-specific Data)を取得してMediaMuxer#addTrackを呼ぶ
- MediaMuxer#startを呼ぶ
- MediaCodecエンコーダーへデーターを入力
- MediaCodecエンコーダーからエンコード済みデータを取得
- MediaMuxer#writeSampleDataを呼び出してエンコード済みデータの書き込み要求を行う
- 8,9, 10を必要なだけ繰り返す
- MeiaCodec#stop
- MediaMuxer#stop
相変わらず長いなぁ。実際にはマルチスレッドで非同期実行するかMediaCodecからのコールバックを使って実装する必要がありますのでもっと面倒ですが。
上記処理のうち次の2種類のデータがあればMediaMuxerを使ってMP4ファイルへ出力することができますので、どないかしてこの2種類のデータを保持してさえいれば直ぐにMediaMuxerへ書き込む必要はありません。
別な言い方をすると、MediaMuxerの状態に書かわらずエンコードは継続することができます。
- MP4ファイルへの出力に必要なデータ(CSD,Codec-specific Data)
- エンコード済みデータ
このことを利用して以前作ったのがタイムシフト録画サービスと後mux録画サービスです(それぞれがどんなことをするかは以前の記事録画Serviceリポジトリを公開した(^o^)vを参照してね)。
今回も基本的にはほとんど同じことをします。違うのはどのようにエンコード済みデータを保持するかです。
タイムシフト録画サービスの場合は、ディスクキャッシュでエンコード済みデータを保持しました。また後mux録画サービスの場合は独自形式の中間ファイルで保持していました。
概要
今回のクラスではせいぜい十数フレーム程度保持すればいいのでオンメモリキューにします。Javaでキューを実現する方法はいくつもありますが、LinkedBlockingQueueを使いました。
また最大で十数フレームとの予測をしていますが、映像だけでも1秒間に30フレーム届きます。これをフレーム毎にデータ保持用のオブジェクトを生成破棄していてはあっという間にガベージコレクションが走ってしまいパフォーマンスが悪化します。そこでこちらも定番の方法、バッファプールというのを使います。簡単に言えば生成したオブジェクトを使い終わった後プールに貯めておいて必要になれば再利用するということです。再利用すればオブジェクトのアロケーションを行う必要が無いのでパフォーマンス低下を最小限に留めることができます。
基本的には次の処理がメインになります。
- MediaCodecエンコーダーの生成・初期化処理
- MP4ファイルへの出力に必要なデータ(CSD,Codec-specific Data)を保持する
- MediaCodecエンコーダーでエンコードしたデータをキューに放り込む
- データキューから取り出してMeduaMuxerへ書き込んで使用済みのバッファをプールに戻す
- ファイルサイズをチェックして大きくなりすぎれば使用中のMediaMuxerをstopして新しいMediaMuxerを生成する
いざ実食、じゃなくて実装
のつもりだったんだけど、文章書くのに飽きてきたので詳しくは次回以降ということでm(__)m
お疲れ様でした(^.^)/~~~
コメント
[…] GitHub/さきちゃんバージョンのjanus-gateway-android […]