• スポンサードリンク

Nexus7(2013)でカメラのプレビュー

Android Camera SurfaceView

をしたら見た目のアスペクト比が変(´・ω・`)

前から気にはなってたんだけど、Nexus7(2013)だけ内蔵カメラのプレビュー表示のアスペクト比が合わない。ピクセル単位でのアスペクト比の調整はしてるんだけど見た目が変なんだよぉ(泣)

少し事情があって今更ながらカメラ周りを調べてます。プレビュー画面のアスペクト比を合わせる方法は、

  1. Viewのサイズに近いサイズをカメラがサポートしている解像度の中から選択する
  2. カメラのプレビューサイズに合わせてViewの大きさを変える

この2つのいずれか、または両方を実行するのが定番の処理ですよね。
SurfaceViewから取得したSurfaceHolderをCamera#setPreviewDisplayへ引き渡してプレビュー表示する場合は、描画部分がブラックボックスへの丸投げなのでこの方法しかプレビュー画面のアスペクト比を合わせる方法ありません。

TextureViewから取得したSurfaceTextureをCamera#setPreviewTextureへ引き渡してプレビュー表示する場合にもこの方法は有効ですが、座標変換行列を自前で計算してTexture#setTransformを使って設定することでアスペクト比を調整することも可能です。

でも、描画を自前で行うのであれば、例えばSurfaceTextureをCamera#setPreviewTextureへ引き渡してテクスチャとしてカメラ映像を受け取ってそれをOpenGL|ESで描画するのであれば話は別で、座標系も座標値も自由に決定できるのでアスペクト比を合わせるように描画すればいいわけですよね。coordinates

画面全面に配置したViewへ特に何も設定せずにOpenGL|ES2.0で描画すると座標系は上段右側の図のようになります。なので、(-1.0,1.0)-(-1.0,-1.0)-(1.0,-1.0)-(1.0,1.0)を頂点とする四角を描画してそれにカメラからSurfaceTexture経由でテクスチャとして受け取った映像を貼り付ければ全面に描画できます。ただし、画面のサイズにかかわらず常に左上が(-1.0, 1.0), 右下が(1.0,-1.0)になりますので、プレビューサイズのアスペクト比とViewサイズのアスペクト比が異なる場合には映像が歪んで表示されます。

OpenGL|ES2.0でアスペクト比を合わせて描画する方法を考えてみるとすぐに思いつくだけでも次のような方法があります。

  1. テクスチャを貼り付けて表示する矩形の頂点座標をアスペクト比に応じて調整する
  2. Viewportの設定をする
  3. モデルビュー変換行列を計算して描画時に適用する

1つ目はあまり賢いやり方では無いので省略することにして、2つ目のViewportの設定をしてみることにします。OpenGL|ES2.0でViewportの設定を行うと図の下段のように座標系が割り当てられますので、Viewportの幅と高さをプレビュー映像サイズと同じアスペクト比になるように設定すれば、(-1.0,1.0)-(-1.0,-1.0)-(1.0,-1.0)-(1.0,1.0)を頂点とする四角を描画するだけで自動的にアスペクトの合った歪の無い映像が表示されるはずです。

Viewportでカメラプレビューのアスペクト比を合わせてみる

こんなコードでViewportの設定をしてみました。

この計算方法だと、Viewとカメラプレビューサイズが異なる場合にはViewの上下または左右に未描画領域が出来るはずです。

実際に動かしてみると、

  • Nexus7(2012, Android4.4.4)
  • Nexus5(Android5.1)
  • Nexus9(Android5.0.1)
  • MeMoPad7(ME176C, Android4.4.2)
  • GALAXY Note2(GT-N7100, Android4.4.2)

は想定通りアスペクト比が正常な表示になりました。縦画面でも横画面でもOKです\(^o^)/
でもなぜか同じプログラムをNexus7(2013)で走らせるとおかしいんです。

Viewportの幅と高さ、オフセットは正常に計算・セットされていて、Viewの上下または左右に同じ幅の未描画領域ができています。でも表示内容はViewportの設定をしない方がましな状態…

Nexus7(2013)での実行結果
縦画面:

  • Viewport設定無し
    若干縦長に表示されるがほぼ実物通りの表示
  • Viewport設定有り
    更に縦長に表示される

横画面:

  • Viewport設定無し
    横長に表示される
  • Viewport設定有り
    Viewport設定無しよりましだが横長に表示される

むぅ〜(゜レ゜) もちろんSurfaceTexture#getTransformMatrixでテクスチャ変換行列は取得して適用してありますよ。

モデルビュー変換行列でカメラプレビューのアスペクト比を合わせてみる

今度は、ViewportはView全面に表示するように設定しておいて、テクスチャ貼り付けの矩形描画時の頂点座標をモデルビュー変換行列で変換して描画してみます。

View内にプレビュー表示のアスペクト比を保ったまま全体が収まるように変換するためのモデルビュー変換行列の計算は例えばこんな感じにします。

このモデルビュー変換行列を使って描画すると、Viewとカメラプレビューサイズが異なる場合にはViewの上下または左右に未描画領域が出来るはずです。
ちなみに、3行目を

に変えるとアスペクト比を保ったままView内に未描画領域が出来ないように拡大縮小して表示、いわゆるcrop centerで表示されます。

Viewportで表示領域を設定した場合と同様に、Nexus7(2013)だけはやっぱり歪んで表示されてしまいます。crop centerにしてもおかしい(´・ω・`)

ピクセルのアスペクト比がかなり長方形なのかもと思ってDisplayMetricsで取得してみると、Nexus7(2013)の場合はこんな感じでした。
DisplayMetrics{density=2.0, width=1920, height=1104, scaledDensity=2.0, xdpi=320.842, ydpi=322.966}
xdpiとydpiが1インチ中のピクセル数ですが比率で0.993程度なのでほぼ正方形のピクセルと考えていいですよね。

一方他の機種のピクセルのアスペクト比がどうかというと、Nexus9だと
DisplayMetrics{density=2.0, width=1536, height=1952, scaledDensity=2.0, xdpi=288.995, ydpi=288.995}
Nexus5だと
DisplayMetrics{density=3.0, width=1080, height=1776, scaledDensity=3.0, xdpi=442.451, ydpi=443.345}
いずれにしてもピクセルのアスペクト比はほぼ正方形なので関係なさそうです。

標準のカメラアプリだとNexus7(2013)でも正常に表示されるのでAOSPのカメラアプリのソースを見てみると…そんなに変わったことをしてる気がしない。SurfaceTexture経由でカメラから映像をテクスチャとして受け取ってcrop cnterで表示しているだけのように見えるんだけどなぁ。

どっか間違ってるか足らない事があるんだろうなぁとは思うものの、それなら他の機種は何で正しく表示されるのかがよくわからん…なんかすっきりしないよぉ(ToT)

とりあえずGitHubに上げているAudioVideoRecordingSampleに、viewportを使う方法、モデルビュー変換行列を使う方法(全体が収まる様にする場合とcrop centerで表示する場合)を追加してみました。
他にも色々と不具合を見つけたのを修正していますので、もし使ってられる方が居ましたら更新をおねがいしますね。

お疲れ様でした。

« »

  • スポンサードリンク

コメント

  • […] これについては以前の記事にいっぱい書いたので詳しくはこちらをどうぞ。 […]

  • KUMA より:

    返信ありがとうございます。
    1980×1080と1280×720等の全てのサイズ全は歪んでエンコードされます。
    う~ん・・・
    プログラム的にはカメラをopenGL側に接続していますので表示はどうにでもなるのですが、流れてくるデータが歪んでることから、どうもフロントカメラ側が問題のようです。
    海外のサイトでも見かけましたが、カメラ画像データを拡大し液晶からはみ出した部分を切り取って表示していることが指摘されてます。
    推測ですが
    カメラからのデータはあくまでも液晶に合わせて作っちゃったので、しょうがないから録画結果は切り取ってしまえみたいな(笑
    NEXUS系液晶は発売当初は話題となった変則的なアスペクト比なのでこのような仕様なのでしょうかね?
    Nexus7のプレビュー画質も荒いので、拡大して切り取ってるような気がします。
    ユーザーには不便をかけますが係数は液晶のアスペクト比4:3から正規のサイズを計算すると1.333になりますので、オプションで選択してもらうしかないです。
    NEXUS7も同じ現象になりますが、その他メーカーでも稀ににですが、歪んでいるとコメントがありますので、
    本当のリファレンス仕様=開発側は、はみ出した部分は切り取る
    ではないでしょうか?(笑
    いずれにしろ根本的な解決方法が見いだせていません

    • saki より:

      こんにちは。

      カメラからの映像のアスペクト比と液晶のアスペクト比が違う場合の表示方法は、
      1)引き伸ばして表示
      2)アスペクトを維持して表示
      3)中心を表示
      の3タイプが考えられますが、普通に見て一番違和感が少ないのが3)なので表示の際にはみ出したところを切り捨てる(crop)事自体は問題ではないでしょうね。
      なのでハードウエア的にカメラ映像を拡大縮小している事自体が問題ではなくてプレビューにしろ録画にしろ解像度の値が正しく返ってこないあるいは設定したのと異なる値になるにもかかわらずそれを取得できないってのが問題な気がします。
      と言うのも自分はUSBで接続する外付けのカメラ(一般的に言うUSB Webカメラ)を使うためのアプリを公開していますが、Nexus7でも特に表示や録画結果歪んだりはしないので(Nexus7上とPC上でものさしで測っただけですが^^)。
      自分でももう少し確認してみますが、Nexus7にかぎらずAndroidの場合はメーカー独自仕様に悩まされる事が多いので、ハードウエアにしろソフトウエアにしろ標準と違うことをしている時はちゃんと情報を公開しといて欲しいです。もっともレファレンスのNexus7がこの有り様だとメーカーに強く言えないんでしょうけど(汗)

  • KUMA より:

    同じようなアプリを公開しておりますが、Nexus7(2013)だけアスペクトが変で対処に困っておりました。
    現在の対処方法ですが、全ての解像度で1.3333倍を掛け合わせてからエンコーダー処理を行うと正しい録画結果になるみたいです。
    例えば
    1980×1080->1980/1.3333:1080
    1280×720 ->1280/1.3333:720
    1080Pであれば1980側を1.3333で割り返したサイズがエンコード出来、それ以外に設定するとエンコードが落ちるみたいです。
    カメラ解像度が低いためアップリンクさせて液晶側に無理やり合わせているような感じです。
    どうにか標準サイズにてエンコード出来ないか困っておりました。

    • saki より:

      こんにちは。
      情報有り難うございます。

      そうなんです。表示用に無理やり係数掛けるのは出来るんですけどその係数(3/4倍)の出処が見た目で判断という至極曖昧なものになってしまうので…
      でも動画エンコード自体は1280✕720とか1980×1080のままでも大丈夫ではないですか?そこを表示用に合わせて係数を掛けてしまうと、他の機種で再生した時におかしくなってしまうのではないかと思うのですが。Nexus7(2012)だと縦が16の倍数でないとだめという縛りがあるので1080と言うのはだめで1088にしないとだめでしたが。
      それともエンコーダーの設定自体は1280✕720とか1980×1080のままで、書き込む映像側を横方向に伸縮させるって感じでしょうか?

      ちなみに、自分はUSBのWebカメラを接続して動画録画・静止画撮影するアプリとライブストリーミングできるアプリを公開しています。そのアプリ達だとNexus7(2013)でも1600×1200ぐらいまでであれば1980✕1080とかそれ以外でも解像度に関係なく正しいアスペクト比で表示・録画できます。なのでおかしいのはNexus7(2013)のカメラって事になるんじゃないかと思ってるんですけど。。2048×1536とかはNexus7(2013)だと録画できませんでしたが。
      saki