実現機能
デバイス側は、カメラ センサーのデータを取得し、UVC プロトコルを介してホスト コンピューターに送信します。同時に、ホストコンピュータは制御コマンドをデバイス側に送信します。
参考ソースコード: https: //github.com/wlhe/uvc-gadget
1. コンセプト
UVC: USB ビデオ デバイス ドライバーです。USBビデオデバイスをサポートするために使用され、USBインターフェースを備えたカメラはすべてサポートできます
V4L2: Linux でのビデオ キャプチャおよび出力フレームワークです。インターフェイスを統一し、アプリケーション層に API を提供するために使用されます
UVC と V4L2 の関係: V4L2 は UVC デバイスの管理に使用され、ビデオ関連のアプリケーション プログラム インターフェイスを提供できます。Linux システムには、V4L2 をサポートできるオープン ソース ソフトウェアが多数あります。一般的なものは、FFmpeg、opencv、Skype、Mplayer などです。
2.具体的な工程
2.1 ビデオ デバイスの電源を入れる
Linux ではすべてがファイルです。最初にデバイス ファイルを開いてビデオ データを出力します。/dev/video18 の場合
dev->fd = open("/dev/video18", O_RDWR | O_NONBLOCK);
ノンブロッキング方式でデバイス ファイルを開きます。ドライバーは起動時に、最初にキャッシュ内の初期化データをデバイスを介してホスト コンピューターに出力し、次にビデオ データがキャッシュをいっぱいにするのを待ちます。
2.2 ビデオ デバイスのプロパティを取得する
struct v4l2_capability cap;
ret = ioctl(dev->fd, VIDIOC_QUERYCAP, &cap);
VIDIOC_QUERYCAP コマンドを使用して、現在のデバイスのさまざまな属性を取得し、さまざまな機能に対するデバイスのサポートを確認します. ここでは、主に if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)、つまりデバイスにビデオ出力機能があるかどうかに焦点を当てます.
2.3 その他の構成
2.3.1 デバイス出力形式 fmt クエリ
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0; //查询格式序号
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(dev->fd, VIDIOC_ENUM_FMT, &fmtdesc);
VIDIOC_ENUM_FMT を使用して、デバイスでサポートされているすべての画像形式を一覧表示します
2.3.2 cropcap 機能の取得と出力画像のトリミングの設定
入手:
struct v4l2_cropcap cropcap;
memset(&cropcap, 0, sizeof(cropcap));
cropcap.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(dev->fd, VIDIOC_CROPCAP, &cropcap);//查询驱动的修剪能力
設定:
struct v4l2_crop crop;
crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
crop.c.top = g_display_top; // 0
crop.c.left = g_display_left; // 0
crop.c.width = g_display_width; // 显示宽度
crop.c.height = g_display_height; // 显示高度
ioctl(dev->fd, VIDIOC_S_CROP, &crop);
2.3.3 出力映像フォーマット fmt 設定
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.width= g_in_width;
fmt.fmt.pix.height= g_in_height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.bytesperline = g_in_width;
fmt.fmt.pix.priv = 0;
fmt.fmt.pix.sizeimage = 0;
ioctl(dev->fd, VIDIOC_S_FMT, &fmt);
ioctl(dev->fd, VIDIOC_G_FMT, &fmt);
ビデオ画像データの長さ、幅、画像形式(JPEG、YUYV形式)の設定など、ビデオデバイスのビデオデータ形式を設定します。
ビデオ デバイス ドライバーが設定した画像形式をサポートしていない場合、ビデオ ドライバーは struct v4l2_format 構造体変数の値をビデオ デバイスでサポートされている画像形式に再変更するため、プログラムの設計では、すべてのビデオ形式を設定します。その後、実際のビデオ フォーマットを取得するために、struct v4l2_format 構造体変数を再度読み取ります。
2.4 UVC イベントの初期化とサブスクライブ
2.4.1 フロー制御プローブの初期化とデータ構造のコミット
uvc_streaming_control probe;
uvc_streaming_control commit;
データ構造 uvc_streaming_control:
な
2.4.2 イベントの購読
struct v4l2_event_subscription sub;
memset(&sub, 0, sizeof sub);
sub.type = UVC_EVENT_CONNECT;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
sub.type = UVC_EVENT_DISCONNECT;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
sub.type = UVC_EVENT_SETUP;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
sub.type = UVC_EVENT_DATA;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
sub.type = UVC_EVENT_STREAMON;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
sub.type = UVC_EVENT_STREAMOFF;
ioctl(dev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
VIDIOC_SUBSCRIBE_EVENT (または登録、登録) を介して、UVC イベントの接続、切断、セットアップ、データ、streamon、および streamoff をドライバーに設定すると、ホスト コンピューターは UVC イベントを介して v4l2 と対話します。
2.5 イベントのトリガーを待機する while ループ
fd_set efds;
FD_ZERO(&efds);
FD_SET(dev->fd, &efds);
ret = select(dev->fd + 1, NULL, NULL, &efds, &tv);
ブロック、efds シグナルを待機中
struct v4l2_event v4l2_event;
struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
ret = ioctl(dev->fd, VIDIOC_DQEVENT, &v4l2_event);
サブスクライブされたイベントが到着すると、efds シグナルをトリガーし、イベント情報を取得します。
2.6 メインイベント処理
2.6.1 UVC_EVENT_SETUP
ホストから要求された UVC_EVENT_SETUP イベントを処理します。標準 USB コマンドとクラス コマンドに分けられます。
USB_TYPE_STANDARD: 処理はありません。ドライバーのこの部分は応答するだけでよく、追加情報は必要ありません。
USB_TYPE_CLASS: クラス コマンドは、コントロールとストリーミングに分けられます。これらは、UVC プロトコルに関連する 2 つのインターフェイス VC と VS です。
1) VSインターフェース処理(参照関数:uvc_events_process_streaming)
フロー制御コマンド: (UVC 1.5 クラス仕様のセクション 4.3 を参照)
な
パラメータの説明:
- bmRequestType 要求タイプ、標準 USB プロトコルを参照
- 表A-8で定義されているbRequestサブクラス
- CS、プローブやコミットなど、表 A-16 で定義されているコントロール セレクタ
- wIndex の上位バイトは 0 で、下位バイトはインターフェイス番号です。
- wLength と Data は標準の USB プロトコルと同じで、データ長とデータ
パラメータ設定のプロセスには、ホストと USB デバイス間のネゴシエーションが必要であり、ネゴシエーション プロセスは下図に大まかに示されています。
な
フローの説明:
- ホストは最初に必要な設定を USB デバイスに送信します (PROBE)
- デバイスは、自身の機能内でホストの期待値設定を変更し、それをホスト (プローブ) に返します。
- ホストが設定が実行可能であると判断した場合、コミット (COMMIT)
- インターフェイスの現在の設定を特定の設定に設定する
2) VCインタフェース処理(参照関数:uvc_events_process_control)
VC インターフェイス内には、カメラを制御するための多くのユニットと端子があります。たとえば、Process ユニットを介してホワイト バランスや露出などを設定できます。
参照構造の定義:
struct uvc_camera_terminal camera_terminal;
struct uvc_processing_unit processing_unit;
2.6.2 UVC_EVENT_DATA
ホストから要求された UVC_EVENT_DATA イベントを処理する: パラメータ情報を渡す
VS コマンド: プローブとコミット
VC コマンド: 露出、ホワイト バランス、明るさ、コントラストなど。
要求に従って、ローカル パラメータを更新します。
2.6.3 UVC_EVENT_STREAMON
ホストから要求された UVC_EVENT_STREAMON イベントを処理します。キャッシュ スペースを構成し、ビデオ ストリーミング送信を開始します。
1) キャッシュスペースを構成する
オペレーティング システムは一般に、システムが使用するメモリをユーザー空間とカーネル空間に分割し、それぞれアプリケーション プログラムとオペレーティング システムによって管理します。アプリケーション プログラムはメモリのアドレスに直接アクセスできますが、カーネル空間にはカーネルがアクセスするコードとデータが格納され、ユーザーは直接アクセスできません。
V4l2 によってキャッシュされたデータはカーネル空間に格納されます。つまり、ユーザーはこのメモリ セグメントに直接アクセスできず、何らかの手段を使用してアドレスを変換する必要があります。主にメモリ マッピングとユーザー ポインター モードを使用します。
メモリ マッピング方式: デバイス内のメモリをアプリケーション プログラム内のメモリ空間にマッピングし、デバイス メモリを直接処理します。
ユーザー ポインター モード: メモリ セグメントは、アプリケーション自体によって割り当てられます。これには、v4l2_requestbuffers でメモリ フィールドを V4L2_MEMORY_USERPTR に設定する必要があります。
struct v4l2_requestbuffers {
__u32 count;
__u32 type; /* enum v4l2_buf_type */
__u32 memory; /* enum v4l2_memory */
__u32 reserved[2];
};
count: 適用するバッファの数
メモリ: V4L2_MEMORY_MMAP または V4L2_MEMORY_USERPTR のいずれか
ここでは主に V4L2_MEMORY_USERPTR モードについて説明します。
a. ビデオ ストリーム データのフレーム バッファ用ドライバに適用します。
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof rb);
rb.count = nbufs;
rb.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
rb.memory = V4L2_MEMORY_USERPTR;//V4L2_MEMORY_MMAP;
ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);
b. ユーザーがメモリ セグメントを申請し、それを出力バッファ キューに追加します。
struct v4l2_buffer buf;
for (i = 0; i < dev->nbufs; ++i)
{
memset(&buf, 0, sizeof buf);
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf.memory = V4L2_MEMORY_USERPTR;
buf.length = MAX_BUFFER_SIZE;
buf.m.userptr = (unsigned long)dev->dummy_buf[i].start;//用户空间
ret = ioctl(dev->fd, VIDIOC_QBUF, &buf);
}
制御コマンド VIDIOC_QBUF は、buf を出力バッファ キューの最後に 1 つずつ配置します。いくつかのフレーム バッファに適用します。通常は 3 つ以上の nbufs を適用します。
注: ユーザーが適用したスペース dev->dummy_buf[i].start を 0 に初期化する必要があることをテストおよび検証します。そうしないと、ビデオ データを正常に送信できません。
v4l2_buffer 数据结构:
__u32 index; // 应用程序来设定,仅仅用来申明是哪个 buffer
__u32 type;
__u32 bytesused; //buffer 中已经使用的 byte 数,如果是 input stream 由 driver 来设 定,相反则由应用程序来设定
__u32 flags; // 定义了 buffer 的一些标志位,来表明这个 buffer 处在哪个队列,比如输入 队列或者输出队列 (V4L2_BUF_FLAG_QUEUED ,V4L2_BUF_FLAG_DONE) , 是否 关键帧等等
__u32 memory; //V4L2_MEOMORY_MMAP / V4L2_MEMORY_USERPTR / V4L2_MEMORY_OVERLAY
union m :
__u32 offset; // 当 memory 类型是 V4L2_MEOMORY_MMAP 的时候,主要用来表明 buffer 在 device momory 中相对起始位置的偏移,主要用在 mmap() 参数 中,对应用程序没有左右
unsigned long userptr; // 当 memory 类型是 V4L2_MEMORY_USERPTR 的时候,这是 一个指向虚拟内存中 buffer 的指针,由应用程序来设定。
__u32 length; //buffer 的 size
ドライバー内部で管理される 2 つのバッファー キュー (入力キューと出力キュー) があります。
キャプチャ デバイスの場合、入力キューのバッファがデータでいっぱいになると、自動的に出力キューになり、VIDIOC_DQBUF を呼び出してデータを処理するのを待ち、再度 VIDIOC_QBUF を呼び出してバッファを入力キューに戻します。
出力デバイスの場合、バッファーが表示 (または読み取り) された後、自動的に出力キューになります。VIDIOC_DQBUF の呼び出しを待ち、バッファーがデータでいっぱいになったら、VIDIOC_QBUF を呼び出してバッファーを入力キューに戻します。
2) ビデオ ストリーム データの収集を開始します。
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl (fd_v4l, VIDIOC_STREAMON, &type)
2.6.4 UVC_EVENT_STREAMOFF
ホストの UVC_EVENT_STREAMOFF リクエストを処理する
a. ビデオ ストリームの出力を停止する
int type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
ioctl(dev->fd, VIDIOC_STREAMOFF, &type);
b. カーネル キャッシュを解放する
nbufs = 0;//设置为0
memset(&rb, 0, sizeof rb);
rb.count = nbufs;
rb.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
rb.memory = V4L2_MEMORY_USERPTR;//V4L2_MEMORY_MMAP;
ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);
2.7 ビデオデータ出力
2.7.1 書き込み準備完了信号待ちの while ループ
a. wfds シグナルが到着すると、出力キューに書き込み可能であることを意味します
ret = select(dev->fd + 1, NULL, &wfds, NULL, &tv);//&wfds
if(ret > 0)
{
ret = uvc_video_process(dev);
}
b. ビデオ バッファーの出力キューから書き込み可能なバッファーを取得し、ビデオ データをバッファーにコピーします。
struct v4l2_buffer buf;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf.memory = V4L2_MEMORY_USERPTR;//V4L2_MEMORY_MMAP;
buf.length = MAX_BUFFER_SIZE;
ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf);
uvc_video_fill_buffer(dev, &buf);
c. バッファーをビデオ バッファーの入力キューに戻し、ホスト コンピューターによる読み取りまたは表示を待機します。
ret = ioctl(dev->fd, VIDIOC_QBUF, &buf);
2.8 終了 ビデオ装置の電源を切る
close(dev->fd);
free(dev->mem); //释放用户申请的内存