• スポンサードリンク

Webカメラからh264動画を取得した〜い〜その4〜

Android Camera USB

一年ぶりー(^o^)/にWebカメラからh264動画を取得した〜い〜その4〜を(汗)
途中まではその3を書いたついでに書いてあったんやけど、放置している間に意外とアクセス数が増えとったんで^^;

まずはじめに1年経つ間に判明した新事実を。その2でこんなことを書きました。

Logicoolの専用ドライバを入れたPCでしかアクセス出来ない悪いやつなのか、いえいえそんな事はありません。実はUVC規格でのH.264対応方法には2種類あるのです。

  • YUY2やMJPEGと同様にフォーマットディスクリプタとフレームディスクリプタを使う方法
    UVC1.5規格でのH.264対応
  • MJPEGのペイロードに埋め込んで転送する方法
    UVC1.1規格でのH.264対応

はい、これは正確ではありませんでしたm(_ _)m。

  1. YUY2やMJPEGと同様にフォーマットディスクリプタとフレームディスクリプタを使う方法
    UVC1.5規格でのH.264対応…例えばリコーのTHETA Sがこのタイプです。
  2. YUY2やMJPEGと同様にフォーマットディスクリプタとフレームディスクリプタを使う方法
    UVC1.1規格のフレームベースフォーマットでのH.264対応…例えばロジクールのC920rがこのタイプです。
  3. MJPEGのペイロードに埋め込んで転送する方法
    UVC1.1規格でのH.264対応…例えばロジクールのC930eがこのタイプです。

つまりUVC規格でのh.264対応は3通りありました。前回までに載せたのはこのうち一番古くからある3番めの方法です。
備忘として前回(っていつやねん)も載せましたがMJPEGペイロードに多重化されたH.264映像の取得手順をもう一度載せておきます。

MJPEGペイロードに多重化されたH.264映像の取得手順

  1. カメラと接続(ファイルオープン)する
  2. デバイスディスクリプタを解析。フォーマットディスクリプタとフレームディスクリプタからカメラが対応している映像フォーマット、解像度、フレームインターバル(フレームレート)等を取得する
  3. デバイスディスクリプタを解析。エクステンションユニットディスクリプタが存在する場合にはそれがH.264エクステンションユニットかどうかをguidExtensionCodeを使って確認する。
  4. H.264エクステンションユニットが存在する場合には使用可能なH.24 configurationを問い合わせる
  5. H.264エクステンションユニットへネゴシエーションを行う
    これによってMJPEGペイロードにH.264ペイロードが多重化されて転送されてくるようになります
  6. 使用したい条件(MJPEG)をカメラとネゴシエーションする
  7. カメラから映像(MJPEG)を受け取る
  8. MJPEGペイロードを解析してH.264ペイロードを取り出す
    取り出したH.264ペイロードはよきにはからいたもうれ。表示するならごにょごにょしてからMediaCodecのデコーダーに放り込んでSurfaceへ描画させればOKです
  9. 必要なだけ映像を受け取ったらカメラからの映像ストリームを停止する
  10. H.264エクステンションユニットの設定をクリアする
  11. カメラから切断(ファイルクローズ)する

前回はUVC(1.1)機器がH.264に対応しているかどうか、また対応している場合の設定取得について書きました。
今回は取得した情報を使ってH.264エクステンションユニットとネゴシエーションを行う方法についてです。

H.264エクステンションユニットとネゴシエーションを行う方法

すること自体はプルーブ&コミットクエリというおなじみに手順ですが、これはっきり言って超めんどいです。1年も記事を書かずに放置したのはやることも説明することも超めんどいからですm(__)m
めんどいのでコードを載せてごまかすことにします(*ノω・*)テヘ

※currentH264Configは前回取得したuvcx_video_config_probe_commit_t構造体です。

ここでキーとなるのはbStreamMuxOptionとbAuxUnitNumberです。
bStreamMuxOptionはuvcx_video_config_probe_commit_tのフィールドで、H.264エキステンションユニットからの出力をどのようにして他のフレームデータ(MJPEGフレームデータ)と多重化させるかを指定します。これはネゴシエーション(プルーブ/コミットクエリ)中に必要になるのと、後でフレームデータを受け取った際に多重化されているかどうかをチェックして分岐させる時に使うので別途保持しておきます。
bAuxUnitNumberはH.264エクステンションユニットのユニットIDです。

まぁあえて別関数にするほどではないですが、H.264エクステンションユニットとのプルーブ/コミットクエリの部分はこんな感じにしています。
最終的に行き着く先はdevice->query_configを呼ぶだけなのでMJPEGストリームの場合と同じですが、MJPEGの場合にはMJPEGストリーミングインターフェースのIDを使ってネゴシエーションしますがその代わりにH.264エクステンションユニットのユニットIDを使います。

どやっ、参ったか^^
H.264エキステンションユニットに対するプルーブクエリと、対応するMJPEGストリームインターフェースに対するプルーブクエリの両方が成功すれば多重化H.264ストリームのネゴシエーションが半分ほど終わったことになります。

後はH.264エキステンションユニットに対するコミットクエリと、対応するMJPEGストリームインターフェースに対するコミットクエリを行って両方が成功すればいよいよフレームデータの受信です。
と言っても受信自体はMJPEGでアイソクロナス転送のペイロード受信するのと同じなので省略です。
違うのは、保存しておいたbStreamMuxOptionをチェックして多重化されていればMJPEGのフレームデータから多重化されているペイロードを抜き出す処理です。

MJPEGフレームデータに多重化されたH.264ペイロードを抜き出す

多重化されたH.264ペイロードはMJPEGのAPP4マーカーの直後のセグメントに入っています。なので最初はlibjpeg/libjpeg-turboのマーカーコールバックを試してみました…がうまく動きませんでした(´・ω・`)。設定が間違っているのかそもそもマーカーコールバックが呼ばれんのです。

という事でオレオレ実装してしまいました。
先ずはJPEGフレームデータからJFIFマーカーを探す処理。まぁこんな感じ。
線形探索するしかないのがボトルネックなのですが、JFIFの規格上APPxマーカーは必ずSOSマーカーよりも前に存在するのでSOSマーカー以降はスキップすることで速度向上を図っています。なお、MUX_CONTAINER_MJPEGに対応したカメラ(…が有るかどうかは知りませんが)であれば、MJPEG関係のフラグメントは殆ど入っていないはずなのでもっと速く探索できるかもしれません。

このfind_app_marker関数にMJPEGペイロードとAPP4マーカーコード(0xe4…実際のAPP4マーカーは0xff + 0xe4)を渡して呼び出して、APP4マーカーが見つかれば次のようにします。

  1. 一番最初のAPP4マーカーの時
    1. 最初に見つかったAPP4マーカーの直後にペイロードヘッダーがついているはずですのでそれを取り出します
    2. ペイロードヘッダーに含まれるFOURCCをチェックしてH.264かどうかを確認します
    3. APP4マーカーとペイロードヘッダー分を差し引いてセグメント内のデータをコピーします
  2. 2番目以降のAPPマーカーの場合はAPP4マーカー分を差し引いた残りをコピーします

コードにするとこないなことに。APP4マーカーは1つもMJPEGフレームデータ内に1つかもしれませんし複数存在するかもしれませんので、ループ中で繰り返し呼ぶことになります。

これでH.264ペイロードを抜き出すことが出来ました。ちなみにペイロードヘッダーはこのようになっています。

多重化されている可能性のあるFOURCCは次の通り。みたまんまやな。それぞれが、「H.264エクステンションユニットとネゴシエーションを行う方法」のところのコードで#defineしてあるフラグに対応しています。

これでとりあえずは多重化されているフレームデータを取り出せたんで、このままネットに投げるとかなら簡単やねんけど、表示しようとか録画しようとかとなると、まだまだ色々する必要があります。

続く…かも
(^.^)/~~~

« »

  • スポンサードリンク

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*

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