• スポンサードリンク

録画Serviceリポジトリを公開した(^o^)v

Android MediaCodec

GitHubで録画Serviceリポジトリを公開しました。
Urlはここ;RecordingService
ライセンスはApache V2です。

そもそもなに?

一言で言えば、今回の録画ServiceクラスもMediaCodecとMediaMuxerを使った録音録画用のヘルパークラス群になります。

Androidでカメラからの映像などをそのまま録画するにはMediaRecorderというクラスを使うと割と簡単に録画できるようになります。多少クセのある仕様なのですがまぁWebに色々情報が転がっているし状態遷移に気を使えばそれほど難しくはありません。
しかしながら、カメラからの映像に何らかの映像効果を加えたり静止画と合成してから録画しようとする場合、Android5以前はMediaRecorderでは正式には対応しておらずリフレクションでごまかすしかありませんでした。

MediaRecorderよりももっと細かく設定したり制御しようとすると、別のAPI、MediaCodecとMediaMuxerというのを使う必要があります。大雑把にいってしまえばMediaRecorderのエンコード部分とファイル出力部分をそれぞれMediaCodecとMediaMuxerに分離したようなAPIです。
ただし細かく設定したり制御できるというのは低レベルなAPIということで、MediaRecorderと比べ物にならないぐらいクセのあるAPIで、登場当時…というよりも数年経つまではAndroid Developerのレファレンスを読んだだけでは殆どの人には手を出せる代物ではありませんでした。
このブログでも2014年頃にMediaCodecを使った録画録音関係の記事を書いていますが、えらく苦労した覚えがあります。
音&動画の同時キャプチャがした〜い(その1)
音&動画の同時キャプチャがした〜い(その2)
音&動画の同時キャプチャがした〜い(その3)
間欠撮影した映像を1つの動画ファイルとして保存したーい〜その1〜
などなど。

ここ1-2年ほどでようやくAndroid DeveloperのMediaCodec関係のレファレンスも充実してきましたし、ヘルパーメソッドも数多く追加されましたので、いまであればそれほど苦労することはないでしょう(わざわざ一覧表が載せてあるぐらいAPIバージョンによって使えるメソッドがかなり違うのでAndroid4.xから全対応しようとするとそれなりに面倒ですが)。

なんでそんなんいるん?

既に、GitHub上でカメラ映像の録画、動画再生、画面録画などをMediaCodec+MediaMuxerを使って行うためのサンプルを公開しています。
例えば録音録画を同時にするサンプル;AudioVideoRecordingSample
まぁもう古いので色々最新のAndroidの対応していない部分もありますが、MediaCodec/MediaMuxerの基本的な使い方としてはいまでも十分役に立つと思います。

それがここに来てなぜ新しくリポジトリ・サンプルを作ったのか?それには深〜いわけがあるかもしれないかもしれないです(*ノω・*)テヘ

以前から少数ですがMediaCodec+MediaMuxerを使った録音録画で不具合のでやすい端末がありました。まぁぶっちゃけ中華系端末は微妙な挙動をするものが多かったです。でもまぁ一応は動いてました。

ところがっ!、なんとっ!、大変なことにっ!この1年ほどで普通にMediaCodec+MediaMuxerを使っただけでは高頻度で不具合…再生できない動画ファイルが生成されてしまう、あるいは音声または映像が飛び飛びになってしまうといった不具合の発生するうんこ端末が大量に出回るようになってしまいました。どことはいいませんが、ASUSとかHUAWEIの比較的廉価な1-3万ぐらいの端末に多いようですが5万以上するスマホでも起こるケースも。
めっちゃ言うとるやん(汗)

鋭意検討を行った結果、うんこ端末対策として今回の録画Serviceに行き着いたわけです\(^o^)/

何がうれしい?

不具合の発生するパターンとして、複数のMediaCodecインスタンスの同時使用中にMediaMuxerを使うと生成した動画ファイルがおかしくなる(壊れる)、というのがあります。

例えば、

  • 映像用のMediaCodecエンコーダー + 音声用MediaCodecエンコーダー + MediaMuxer
  • 映像用のMediaCodecデコーダー + 映像用MediaCodecエンコーダー + MediaMuxer
  • 映像用のMediaCodecデコーダー + 映像用MediaCodecエンコーダー + 音声用MediaCodecエンコーダー +MediaMuxer

つまり映像音声の同時録画、動画再生しながら動画エンコードというアプリでよくあるシチュエーションで不具合が発生するのです?というかほとんどだめじゃん(●`ε´●)

しかーし、今回の録画Serviceを使うとうんこ端末でもほぼ不具合を解消できるのです(^o^)v

技術的詳細

Android端末で動画をファイルに保存するときには、ほとんどの場合はMP4コンテナ形式で保存していると思います。
MediaMuxerもMP4コンテナ形式での保存に対応しているのですが、全ての映像・音声データを出力し終わった後に再生する上で必須なあるデータを書き込む必要があります(それが何かは自分で調べてくだされ)。
つまり、全てのデータを出力し終わり必須データの書き込みまで全て完了しなければ再生できる動画ファイルになりません。

MediaMuxerの処理の流れとしては通常次のようになります。

  1. MediaMuxerインスタンスを生成
  2. MediaMuxerを初期化
  3. MediaMuxer#start
  4. MediaMuxer#addTrackで映像and/or音声トラックを追加
  5. 必要な分だけMediaMuxer#writeSampleDataを呼び出して映像and/or音声データを入力
  6. MediaMuxer#stop
  7. MediaMuxer#release
  8. MediaMuxerインスタンスの強参照をクリア
  9. しばらくするとGCがMediaMuxerインスタンスを解放

ここで問題となるのが6-9の部分です。#stopまたは#release呼び出しでファイルへの出力完了を保証してくれればいいのですが、実際には、MediaMuxerは非同期でnative側で実際のファイル出力を行なっており、2017年末の時点でもいつファイル出力が終わったかを知るすべは殆どありません。その結果、自分のサンプルもそうですが、MediaMuxerへのデータ入力を終了した後MediaMuxerの強参照を開放してあとはGCに解放を任せることになります。
一方、ここ最近のAndroidではかなり積極的にGCが走って強参照のないオブジェクトの開放処理が行われるようになっています。
native側でのファイル出力が完了する前にMediaMuxerインスタンスが開放されると…再生できない動画ファイルの出来上がりです?多くの端末ではGCで解放されるまで3-5秒かかりますが、MediaMuxerでの出力は1-2秒で終わるので通常は問題にならないはずだったのですが。
かといっていつファイル出力が終わるのかがわからない以上強参照を開放するタイミングは明示的には定まりませんし、いつまでも強参照を保持し続けるわけにもいきません(リークする)。
ですが、MediaCodecのエンコーダーも内部ではFIFOなキューを使って非同期で処理が行われており、MediaCodecエンコーダーへの入力を止めても直ぐに全てのエンコード出力が止まるわけではありません。またMediaMuxerの同じくFIFOなキューを使って非同期で処理が行われています。MediaCodecが最後のフレームを処理してEOSフラグの付いたフレームデータが出力され、それがMediaMuxerで処理されるまではMediaMuxerが破棄されては困るのです。

いろいろ試したりdmesgやlogCatを調べた結果、不具合の発生しやすいASUSやHUAWEIの端末での不具合の発生原因は主に次の2つです。

  1. 複数MediaCodecインスタンス+MediaMuxerではMediaCodecまたはMediaMuxerの処理が非常に遅くなる
  2. 独自のメモリークリーナー処理が動いている

HUAWEI端末でもNexus6pでは不具合になりませんが、例えばMediapad T2 10.0 proだと8-9割再生できない動画ファイルが生成されたりします。条件によっては20-30秒近く処理が遅延します、そんなに待てるかぁー(●`ε´●)

ということでさっくりと複数MediaCodecインスタンスとMediaMuxerの同時使用を諦めましょう(^o^)/

単純なシーケンシャルなファイル出力であれば複数MediaCodecインスタンスと併用しても不具合はほぼ起こりません(百パーセント大丈夫かどうかは知りません、キッパリ)。またシーケンシャルなファイル読み込みとMediaMuxer単体の組み合わせでも不具合は起こりません。たぶん。
ですのでエンコード中はオレオレraw形式でシーケンシャルにファイル出力しておいて、エンコード終了後にMediaMuxerを使ってMP4コンテナ形式へ変換すれば良いのです。この場合は単純ファイルコピーに近い状態なのでたとえうんこ端末を使っていてもMediaMuxerはほぼ直ぐにファイル出力が完了します。

なのでServiceにせんとあかん

エンコード&rawファイル出力処理後に別途MP4コンテナ形式への変換処理を行わないといけないので、録画終了=ファイル出力完了ではありません。エンコードしながらと比べると遥かに速く、数倍〜数十倍速く出力はできますが、録画を止めた直後にアプリを終了されてしまうかもしれません。ここでMP4コンテナ形式終了まで強参照を保持して…となると結局いつ終わるかわからない問題が再発してしまいます。そこでアプリのUI部分とは異なるライフサイクルで処理する仕組みが必要になります。

AndroidにはUI部分と非同期で処理を行う仕組みが幾つかありますが、今回の目的にはServiceを使うのがよろしいでしょう、ということで録画Serviceと相成りました。

まだ部分的

動画部分は一通り実装して動作確認していますが、録音部分がまだ動きません。
あと一緒に以前このブログの記事にしたタイムシフト録画も同じ仕組みで動くので、共通ベースクラスを使うように修正中ですが、こちらもまだ動きません。

ちょっと横着してDataInputStream/DataOutputStreamを使っているので最速ではないですが、間にBufferedOutputStream /BufferedInputStreamを挟んでいるので数分程度の録画では実用上は問題にならないかと。
ただし原理的に、数十分や数時間の録画だとストレージ容量が2倍以上必要になる、変換出力に時間がかかるなどの問題はありますのでそこんところを理解した上で使うようにしてくださいな。

ちゃんちゃん。(^.^)/~~~

« »

  • スポンサードリンク