前回の記事でUVC規格対応の機器からH.264で圧縮された映像を取得する方法が2種類あること、そのうちの1つがMJPEGペイロードに埋め込んで転送する方法であること、しかもその方法でH.264映像を取得するのが面倒であると書きました。
今回からは何がそんなに面倒なのかってあたりを書きたいと思います。面倒な部分は大きく3つあります。
UVC1.1でのH.264対応ではエクステンションユニットへアクセスがする必要
通常のWebカメラからの映像取得では次のような順序で処理します。ちょっと省略し過ぎ?(汗)。
通常のWebカメラからの映像取得手順
- カメラと接続(ファイルオープン)する
- デバイスディスクリプタを解析。フォーマットディスクリプタとフレームディスクリプタからカメラが対応している映像フォーマット、解像度、フレームインターバル(フレームレート)等を取得する
- 使用したい条件をカメラとネゴシエーションする
- カメラから映像を受け取る
- 必要なだけ映像を受け取ったらカメラからの映像ストリームを停止する
- カメラから切断(ファイルクローズ)する
一方UVC1.1機器からH.264映像を取得しようとすると、フォーマットディスクリプタにもフレームディスクリプタにも定義がないのでUVC機器がH.264に対応しているのかどうか、対応しているのであればどんな解像度やフレームレートが使用できるのかがわかりません。UVC1.1でのH.264対応の場合はエクステンションユニットディスクリプタの解析とエクステンションユニットへのアクセスによって必要な情報を取得しネゴシエーションする必要があります。
MJPEGペイロードに多重化されたH.264映像の取得手順
- カメラと接続(ファイルオープン)する
- デバイスディスクリプタを解析。フォーマットディスクリプタとフレームディスクリプタからカメラが対応している映像フォーマット、解像度、フレームインターバル(フレームレート)等を取得する
- デバイスディスクリプタを解析。エクステンションユニットディスクリプタが存在する場合にはそれがH.264エクステンションユニットかどうかをguidExtensionCodeを使って確認する。
- H.264エクステンションユニットが存在する場合には使用可能なH.24 configurationを問い合わせる
- H.264エクステンションユニットへネゴシエーションを行う
これによってMJPEGペイロードにH.264ペイロードが多重化されて転送されてくるようになります - 使用したい条件(MJPEG)をカメラとネゴシエーションする
- カメラから映像(MJPEG)を受け取る
- MJPEGペイロードを解析してH.264ペイロードを取り出す
取り出したH.264ペイロードはよきにはからいたもうれ。表示するならごにょごにょしてからMediaCodecのデコーダーに放り込んでSurfaceへ描画させればOKです - 必要なだけ映像を受け取ったらカメラからの映像ストリームを停止する
- H.264エクステンションユニットの設定をクリアする
- カメラから切断(ファイルクローズ)する
2倍近い手順が必要になります。面倒くさぁ(´・ω・`)
エクステンションユニットディスクリプタの解析
通常通りデバイスディスクリプタを解析するとビデオコントロールインターフェースディスクリプタの下にエクステンションユニットディスクリプタが見つかる事があります。
エクステンションディスクリプタの定義はこんな感じ(Cの構造体としての定義)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
typedef struct uvc_extension_unit_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint8_t bDescriptorSubType; uint8_t bUnitID; uint8_t guidExtensionCode[16]; uint8_t bNumControls; uint8_t bNrInPins; uint8_t baSourceID[0]; uint8_t bControlSize; uint8_t bmControls[0]; uint8_t iExtension; } __attribute__((__packed__)) extension_unit_descriptor_t; |
UVC1.1機器がH.264に対応しているかどうかは、この構造体のguidExtensionCodeフィールドの値をチェックすることでわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Codec (H.264) Control: A29E7641-DE04-47e3-8B2B-F4341AFF003B static const uint8_t GUID_UVCX_H264_XU[] = { 0x41, 0x76, 0x9e, 0xa2, 0x04, 0xde, 0xe3, 0x47, 0x8b, 0x2b, 0xf4, 0x34, 0x1a, 0xff, 0x00, 0x3b}; // エクステンションユニットディスクリプタの先頭へのポインタ(別途取得) const extension_unit_descriptor_t *desc; // memcmpでguidExtensionCodeを比較する if (!memcmp(GUID_UVCX_H264_XU, desc->guidExtensionCode, 16)) { // H.264のエクステンションユニットが見つかった\(^o^)/ ... } |
実際の解像度等はH.264エクステンションユニットに対して問い合わせを行うことで取得できます。こんな感じのコードになりまする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
typedef struct _uvcx_video_config_probe_commit_t { uint32_t dwFrameInterval; uint32_t dwBitRate; uint16_t bmHints; uint16_t wConfigurationIndex; uint16_t wWidth; uint16_t wHeight; uint16_t wSliceUnits; uint16_t wSliceMode; uint16_t wProfile; uint16_t wIFramePeriod; uint16_t wEstimatedVideoDelay; uint16_t wEstimatedMaxConfigDelay; uint8_t bUsageType; uint8_t bRateControlMode; uint8_t bTemporalScaleMode; uint8_t bSpatialScaleMode; uint8_t bSNRScaleMode; uint8_t bStreamMuxOption; uint8_t bStreamFormat; uint8_t bEntropyCABAC; uint8_t bTimestamp; uint8_t bNumOfReorderFrames; uint8_t bPreviewFlipped; uint8_t bView; uint8_t bReserved1; uint8_t bReserved2; uint8_t bStreamID; uint8_t bSpatialLayerRatio; uint16_t wLeakyBucketSize; } __attribute__((__packed__)) uvcx_video_config_probe_commit_t; uvcx_video_config_probe_commit_t config; UVCDevice *dev = (UVCDevice *)device; // 最大値を取得 int result = dev->query_config(config, desc->bUnitID, true, REQ_GET_MAX); if (!result) { // 最大値を取得できた h264->addConfig(config); const int num_configs = config.wConfigurationIndex; // C930eはなぜかいつもゼロ // configurationを全て取得 for (int i = 0; i < num_configs; i++) { result= dev->query_config(config, desc->bUnitID, true, REQ_GET_CUR); if (!result) h264->addConfig(config); } // 現在値を取得 result = dev->query_config(config, desc->bUnitID, true, REQ_GET_CUR); } |
ただですねぇUVC1.1規格ではwConfigurationIndexの値は1〜maxとなっていて、最大値を取得すると対応しているconfigurationの個数がわかるという記述があるのですが、c930eで最大値を取得(GET_MAX)するとwConfigurationIndexがいつも0が返ってくるのです(゜゜) UVC規格にのっとれば対応するconfigurationはゼロ個…つまり対応していないってことになってしまいます。
しかも、現在値(GET_CUR)を呼ぶたびにwConfigurationIndexがインクリメントして対応している数分を順に返すと書いてあるのですが、c930eでは複数回現在値を読み込んでも(GET_CUR)全て同じ値が返ってきます。また最小値(GET_MIN)も同じ値です。
まぁUVC機器…に限らずUSB機器全般でこういった規格外のバギーなディスクリプタを返す機器は沢山、ほんっとに沢山あるのでconfigurationが1個あるとみなして後続の処理を行います。ちなみに、先ほどのコードで最大値取得に成功した際にh264->addConfigを呼んでいるのはこういったバギーデバイス対応のworkaroundです。本来は後のforループ内でaddConfigするだけでいいはずなんですけどね。
前々回の記事で書きましたがH.264対応機器は非常に少ないのとどれも高価なのでなかなか全部確認するってわけにも行かずこういった振る舞いが普通なのかどうか、あるいは違ったworkaroundが必要なのかがよくわからないのが現実です。
しかし困りました、configurationが1つしかありません。これでは解像度・ビットレート等の最大値しかわかりません。いろいろ試した結果c930eの場合はMJPEGで対応している解像度であればh.264でも大丈夫なようです。
でも、バッファーローのBSW50KM02のようにH.264ハードウェアエンコードは1280×720のみってなカメラもあるようなので、ネゴシエーションに失敗した時のフォールバック処理を考えとかないといけないですね。
自分の場合は、解像度は指定値に固定してH.264→H.264多重化モード(これがUVC1.1でのH.264対応)→MJPEG→YUY2の順にネゴシエーションを試みるようにしています。
次回はH.264エクステンションユニットのネゴシエーションです。
まだまだ続く^^/
お疲れ様でした。
コメント
Hi
Depending on your settings, get the h264 file , but with a resolution of 1280 * 720 , the actual output is 1920 * 960 ,Change camera resolution, with VLC player is correct , but has always been to get libuvc 1280 * 720 ,Meanwhile, the video bitrate is not the same,You can not pass libuvc provide api to set the resolution with bitrate frames