• スポンサードリンク

MediaCodecの制限について調べてみた

Android MediaCodec

調子乗って、UsbWebカメラアプリにHD(1280×720)やFullHD(1920×1080)の設定を追加したのはいいんだけど、画面には表示されているものの、いざ動画として保存しようとすると駄目な時が有るみたい。
調べてみるとMediaCodecではHD/FullHDのエンコードがちゃんと出来ない時がある有るみたい。
う〜ん(゜レ゜)。

色々調べてみた結果、結論は・・・古い機種だと、CPU/GPUの能力が足りなかったり、GPUのドライバやエンコーダーに問題が有るみたい・・・ということにしとこうm(__)m

もう少し詳しくね

手持ちのNexus5(Android4.4.4)だとHD(1280×720)でもFullHD(1920×1080)でも30fpsぐらいなら殆どコマ落ちせずに表示して録画可能。
でも例えば、Nexus7(2012, Android4.4.4)だと、FullHD(1920×1080)のフレームを取り込んでMediaCodecを使ってAVC(H.264)でエンコードして動画を書き出すと、画面下に緑の帯が入ったり、画面全体が色ずれしたりする。
一方Galaxy S3(Android4.1.2)だとHD/FullHDどちらもエンコード・ファイルへの書き出し自体が出来てない。
おっかしいなぁ〜ってことでもっとちゃんと調べてみた。

条件
  • WiiUのHDMI出力(1080p/1080i/720p)をFEBON168というフレームグラバー経由で取り込む。
    特にFEBON168だからおかしくなるというわけではなく、LogicoolのWebカメラでも一緒。単に載せる動画が少しは楽しいほうがいいかなと思ったのと、実写よりブロックノイズがわかりやすから(^^)v
  • MediaCodecを使ってAVC(H.264)でエンコード
  • MediaMuxerまたはそのバリエーションでMPEG4として書き出す
  • MediaCodeの設定は、フレームサイズFullHD(1920×1080)、15fps
  • BPP=0.125として、ビットレートは0.125✕15✕1920✕1080=3888[kbps]を100[kbps]単位で切り捨てて3.8[Mbps]とした。
    ※bitrate = BPP ✕ fps ✕ width ✕ height[bps]
実行結果

※Nexus5での動画が大きすぎて(ビットレートが高いから)そのままではブログにアップロード出来ないので、Nexus7のも含めて同じ設定でビットレートを少し落として1280×720に変換したのを載せています。

  • Nexus7で実行すると、色ずれして下端に緑の帯が入る。
    変化の多い部分にはブロックノイズが大量発生。動画はこれ。
    普通のWebカメラならブロックノイズはそうは目立たないけど、色ずれと下端の緑の帯は発生。
    パソコンで元の動画のフォーマットを見てみると、画像サイズが1920×1088。ビットレートは500〜900kbps前後。っと言うより高さ1088って何?そんな設定しとらんよ?
    ちなみに、スクリーン上ではカメラからの画像自体は普通に見えてる(CPU/GPUの能力が足りずコマ落ちして少しカクカクするけど)。
  • Nexus5で、同じアプリ・同じ設定で実行すると正常に保存される。ほぼ画面で見たまま。動画はこれ。
    パソコンで動画のフォーマットを見てみると、画像サイズが1920×1080・ビットレートは約3.8Mbpsで設定通り。BPP=0.125だとビットレート少し高めだからもう少し下げてもいいのかも。
  • Galaxy S3は、ちゃんと保存できない。ちゃんと調べてないけどエンコーダーから出力がされてないみたい。Galaxy S3はTCPIP経由でデバッグできないから調べるのが面倒なだけやけど(-_-;) 640×480なら問題なく録画できるのでこれもHD/FullHDでの端末固有の問題な気がする。
実行結果2

ちなみに、HD(1280×720)にして同じBPP=0.125から計算するとビットレートは1.7Mbpsになるんだけど、

ビットレートをBPPから計算するのを止めて固定値で2Mbpsとか1Mbpsに設定しても同じ状態。
むむむ〜MediaCodecInfo#CodecProfileLevelってのを使えば前もって分かるのかな?

プロファイルとレベルを調べてみる

MediaCodecでMIMEにvideo/avcを指定した時のエンコーダーのプロファイルとレベルを取得してみた。

さんぷるこ〜ど

まずは次のコードで指定したMIMEに対応できるエンコーダーのMediaCodecInfoを取得します。
(カラーフォーマットの制限もしているけどそこは気にしないでください。YUV420PlanarかYUV420SemiPlanarまたはそれらと互換のものを選択しています)

続いてプロファイルとレベルを取得します。デバッグ用の関数なんでLog出力もベタ書きです。

#getProfileLevelStringは巨大なswitch/case文の塊なので省略です。単にprofile/levelの整数値を文字列に置き換えるだけです。
こんな感じで呼び出します。

プロファイル・レベルの取得結果

結果を表にしたのがこれ。

 Nexus7(2012)
Android4.4.4
Nexus5
Android4.4.4
Galaxy S3(SC-06D)
Android4.1.2
 
MIMEvideo/avc
(H.264)
resolution1920x1080
1280x720
frame rate15
bit rate
(BPP=0.125)
3.8Mbps(1920x1080)
1.7Mbps(1280x720)
codecOMX.Nvidia.h264.encoderOMX.qcom.video.encoder.avcOMX.qcom.video.encoder.avc
対応profile/level(その1)AVCProfileBaseline
AVCLevel41
AVCProfileBaseline
AVCLevel51
AVCProfileBaseline
AVCLevel4
対応profile/level(その2)---AVCProfileMain
AVCLevel51
AVCProfileMain
AVCLevel4
対応profile/level(その3)---AVCProfileHigh
AVCLevel51
AVCProfileHigh
AVCLevel4

あれれ・・・

  • エンコード出来てないGalaxy S3でもBaseline/Main/Highプロファイルでレベル4対応
  • 1920×1080だと下端に緑の帯が入るNexus7(2012)はBaselineだけだけどレベル4.1対応
  • 何の問題もないNexus5はBaseline/Main/Highプロファイルでレベル5.1対応

という結果になりました。

グルグル先生によると、AVC(H.264)のレベル別のビットレート・解像度・フレームレートの上限は次の通りらしいです。

  • レベル4だと、最大ビットレート20Mbps
    フレームレート的には、最大で、1280×720だと約68fps、1920×1080でも30fps程度
  • レベル4.1だと50MBps
    フレームレート的には、最大で、1280×720だと約68fps、1920×1080でも30fps程度
    でも、実際にはNexus7(2012)の場合1280×720でもせいぜい800kbps台がいいところです。でもレベルの値の約70分の1ってのに意味有るの!?
  • レベル5.1にだと240Mbps。最大でFullHDx4画面x30fps出来るってこと?
    フレームレート的には、最大で、1920×1080で120fps、4096×2048(4k2k)で30fps

と言う事で

プロファイル/レベル的にはどれでも余裕じゃんって感じなんだけどなぁ。
実際の所、どの機種でもMediaCodec#createEncoderByTypeもMediaCodec#configureも正常に終了してLogCatにも”setupVideoEncoder succeeded”なんて出力されるのに(;_;)
なんか他に設定しなきゃならないことあるのかなぁ・・・
ただ、プロファイル/レベルで定めているのはあくまでも最大であって、最小は何の規定もないみたいなんだよね(-_-;)

色々とビットレートやfpsを変えたりしても結果は変わらず。機種と画像サイズでエンコード出来る・出来ないが決まってしまうようです。
とりあえず、エンコーダーのプロファイル/レベルを取得してみても何の役にも立たないということがよくわかりました。もしかしてデコーダーとしてのプロファイル/レベルを返してるのかも?

とはいうものの、全然イケてないGalaxy S3は別にして、保存までされているのに色がずれて下に緑の帯が入るNexus7(2012)を何とかしたいです。

あ〜\(^o^)/

とは言っても台風も来てるしお昼寝していると(^_^;)・・・閃きました(^^)v
出来た動画の高さと「下端に緑の帯」がヒントです。判りました?
そう、画像の高さを1080に設定しているにも関わらず1088になっているのです。アプリ内では高さ1080のつもりで変換・書込しているにも関わらず実際には1088、つまりカラープレーンが8行分ずつずれてしまうのです。たぶん中の人は高さも16バイト境界にしたかったんでしょうね(゜レ゜)。
それはそうとして、いつになれば高さが1088になったのがわかるかを調べてみると・・・MediaCodec.INFO_OUTPUT_FORMAT_CHANGEDが来た時でした。
えぇ〜?1フレーム目を書き込まなければMediaCodec.INFO_OUTPUT_FORMAT_CHANGEDは決して来ないし、エンコード処理の方が書き込み処理よりも遅いので、このままだと最初の数フレームには目をつむるしか無いですね。はぁ(´・ω・`)
せめてMediacodec#configureを呼んだ時に、引数のMediaFormatに書き戻してくれれば良いのに・・・

マハリク マハリタ ヤンバラ ヤンヤンヤン!

とりあえず呪文を唱えてみました。何のこっちゃ(笑)
実はアプリ内ではAPI>=18以上向けのSurface経由でMediaCodecへ書き込む方法とAPI<18向けにnativeでゴニョゴニョしてからbyte配列としてMediaCodecへ書き込むのと2種類をどちらも実装してあるのでした。今まではSurface経由の方は(大人の事情で微妙に画質が落ちるので)無効にしてたんだけど、API>=18の場合はSurface経由に有効にします(^o^)/
Surface経由の方は中の人が実装しているので、MediaCodecがイメージの高さを変えても対応してくれるので最初から緑の帯が出たりせずに出力できるのです\(^o^)/。API<18未満の人は自前実装の方が走るので、もしかすると最初の数フレームは下端に緑の帯が出たりするかもしれないですけど。出来る範囲で頑張りますm(__)m Nexus7(2012)でSurace経由でエンコードした動画はこちら(^^)v[video width="640" height="360" mp4="http://serenegiant.com/blog/wp/wp-content/uploads/2014/08/Nexus7_1920x1080-3.mp4"][/video] 処理の都合でフレームレートは少し高くなって800kbps前後だったので、多少はブロックノイズも少なくなってかえって画質が少しましになったかも?

結論

まぁとりあえず、古い機種だとCPU/GPUの能力が足りなかったり、GPUのドライバやエンコーダーに問題が有るということにしましょうm(__)m
アプリではとりあえず、レベル5未満の時には、ちゃんと録画できないかもってのをToastで表示することにしました。
これが出てうまく録画できない時は諦めるか、最新の端末に買い替えましょう(^o^)/


と言う事でおしまい、お疲れ様でした。

« »

  • スポンサードリンク

%d人のブロガーが「いいね」をつけました。