目次
1. デバイスの電源を入れます
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 1. 打开设备
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("failed open");
return -1;
}
// 2. 关闭设备
close(fd);
return 0;
}
ファイル記述子を返します。
2. デバイス サポート形式の取得/構成
2.1 主な機能
この機能を実現するために、最も重要なことは次の機能です。
int ioctl(int fd, unsinged long request, ...);
// 即ioctl(文件描述符,操作命令,与命令的对应的结构体)
// 查看方式man ioctl
2.2 未署名の長いリクエスト
ioctl 関数のパラメーターを表示する方法:
- https://v4l.videotechnology.com/dwg/v4l2.html
/usr/include/linux/videodev2.h
キーマクロ | 関数 | 対応する構造 |
---|---|---|
VIDIOC_QUERYCAP | 映像機器対応機能問い合わせ | struct v4l2_capability (発信パラメーター) |
VIDIOC_ENUM_FMT | USB カメラでサポートされている画像形式を表示する | struct v4l2_fmtdesc (着信および発信パラメーター) |
VIDIOC_G_FMT | 画像フォーマットの取得 (画像の高さと幅、ピクセル形式など) | struct v4l2_format (着信および発信パラメーター) |
VIDIOC_S_FMT | 画像フォーマットの設定 | struct v4l2_format (着信および発信パラメーター) |
構造体の具体的な内容に対応
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
struct v4l2_fmtdesc {
__u32 index; /* Format number 设置好index*/
__u32 type; /* enum v4l2_buf_type, 设置好类型 */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */
__u32 reserved[4];
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
V4L2_BUF_TYPE_VBI_CAPTURE = 4,
V4L2_BUF_TYPE_VBI_OUTPUT = 5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
V4L2_BUF_TYPE_SDR_CAPTURE = 11,
V4L2_BUF_TYPE_SDR_OUTPUT = 12,
V4L2_BUF_TYPE_META_CAPTURE = 13,
V4L2_BUF_TYPE_META_OUTPUT = 14,
/* Deprecated, do not use */
V4L2_BUF_TYPE_PRIVATE = 0x80,
};
struct v4l2_format {
__u32 type; // 设置好类型
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
2.3 コードの実装
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev2.h>
int v4l2_check_cap(int fd) {
struct v4l2_capability cap = {};
if (-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
printf("this device unable to query capability.");
return -1;
}
// 查询是否是视频设备
if (0 == cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
printf("this device does not support capture.\n");
return -1;
}
// 查询是否支持IO数据流
if (0 == cap.capabilities & V4L2_CAP_STREAMING) {
printf("this device does not support I/O stream.\n");
return -1;
}
return 0;
}
void v4l2_enum_fmt(int fd) {
struct v4l2_fmtdesc fmtd = {};
fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtd.index = 0; // 取第几个格式
while (0 == ioctl(fd, VIDIOC_ENUM_FMT, &fmtd)) {
printf("pixel format: %s \n", fmtd.description);
fmtd.index ++;
}
}
int v4l2_get_format(int fd) {
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)) {
printf("cannot get pixel format");
return -1;
}
printf("format: %u x %u, pixeformat:%c%c%c%c\n",
fmt.fmt.pix.width, fmt.fmt.pix.height,
fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
}
int v4l2_set_format(int fd) {
struct v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 320;
fmt.fmt.pix.height = 240;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if (-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)) {
printf("cannot get pixel format");
return -1;
}
return 0;
}
int main () {
int fd, rval;
fd = open("/dev/video0", O_RDWR);
if (fd == -1) {
perror("cannot open device.");
exit(1);
}
// 1. 视频设备支持的功能查询
rval = v4l2_check_cap(fd);
if (rval == -1) {
close(fd);
return rval;
}
// 2. 查看usb摄像头支持的图片格式
v4l2_enum_fmt(fd);
// 3. 获取、设置摄像头的图像格式
// 获取图片格式
rval = v4l2_get_format(fd);
if (rval < 0) {
close(fd);
return rval;
}
// 设置图像格式
rval = v4l2_set_format(fd);
if (rval < 0) {
close(fd);
return rval;
}
v4l2_get_format(fd);
close(fd);
return 0;
}
3. カーネル バッファ キューを申請する
キャッシュキューはカーネル内で画像データを管理するために使用されます. ユーザー空間が画像データを取得するには2つの方法があります. 1つはカーネル空間のキャッシュを読み取りと書き込みで読み取る方法です (複数の読み取りと書き込みは遅いです)。もう 1 つはカーネル空間を使用することです。キャッシュはユーザー空間にマップされます。
キーマクロ | 関数 | 対応する構造 |
---|---|---|
VIEWER_REQBUFS | メモリ マッピングまたはユーザー ポインター I/O の開始 | struct v4l2_requestbuffers (着信パラメーター) |
struct v4l2_requestbuffers {
__u32 count; // 设置缓冲区的数量
__u32 type; /* enum v4l2_buf_type */ // 设置类型
__u32 memory; /* enum v4l2_memory */ //设置映射方式
__u32 capabilities;
__u32 reserved[1];
};
int v4l2_request_kernel_buffer(int fd) {
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
perror("申请队列空间失败");
return -1;
}
return 0;
}
4. キュー空間をユーザー空間にマップする
4 つのバッファーが以前に構成されており、マッピングのために 1 つのバッファーが順番に照会されます。
メモリマップ
ユーザー空間のメモリ領域の一部がカーネル空間にマッピングされます. マッピングが成功した後, ユーザーによるこのメモリ領域の変更はカーネル空間に直接反映されます. 同様に, カーネル空間のこの領域の変更も.ユーザー空間に直接反映されます。
mmap 関数は unix/linux のシステム コールです. mmap システム コールは、同じ通常のファイルをマッピングすることで、プロセスがメモリを共有できるようにします。mmap はスペースを割り当てず、ファイルを呼び出しプロセスのアドレス空間にマップするだけで (ただし、仮想メモリを消費します)、memcpy およびその他の操作を使用して、write() の代わりにファイルを書き込むことができます。 , メモリ内のコンテンツはすぐにファイルに更新されませんが, 一定時間遅延があります. msync() を呼び出して明示的に同期することができます.ポイント ドライバーに関連する必要があります。ただし、mmap() を呼び出した時点でマッピングする長さが決まるため、mmap でファイルを書き込んでファイルの長さを増やす方法はありません。メモリマッピングをキャンセルします。
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
-
start: マップされるメモリ領域の開始アドレスで、通常は NULL (NULL は 0) です。NULL は、メモリアドレスがカーネルによって指定されていることを意味します
-
length: マップされるメモリ領域のサイズ
-
prot: メモリ マッピング領域のパーミッション。必要なメモリ保護フラグは、ファイルのオープン モードと競合してはなりません。次のいずれかの値であり、OR 演算によって適切に組み合わせることができます
PROT_EXEC //ページ コンテンツを実行できます
PROT_READ //ページのコンテンツを読み取ることができます
PROT_WRITE //ページを書き込むことができます
PROT_NONE //ページにアクセスできません
-
flags: マップされたオブジェクトのタイプと、メモリ上の変更がディスクに反映されるかどうかを指定します。マップされたオプションとマップされたページを共有できるかどうか。その値は、次のビットの 1 つまたは複数の組み合わせにすることができます。
MAP_SHARED : マップされた領域に書き込まれたデータはファイルにコピーされ、ファイルをマップする他のプロセスで共有できるようになります。
MAP_PRIVATE: マッピング領域で行われた変更は、物理デバイスには反映されません。
-
fd: ファイル記述子 (open 関数によって返される)
-
オフセット: マップされたファイルのオフセット (4k の整数倍、4096 バイト、Linux ページのサイズ)。マップされたオブジェクト (つまり、ファイル) がマップを開始する場所を示します。通常は 0 を使用します。値は PAGE_SIZE の整数倍である必要があります
戻り値の説明: 正常に実行されると、mmap() はマップされた領域の最初のアドレスを返します。失敗すると、mmap() は MAP_FAILED を返します [その値は (void *)-1]
1 つ購入するともう 1 つ無料:munmap(void* addr, size_t length)
リリース マッピング エリアを閉じる
- addr: mmap の戻り値に対応するポインタ
- 長さ: mmap の長さに対応
mmap機能の応用:親子プロセス通信、匿名マッピング、血縁関係のないプロセス間通信
マッピングの実装
キーマクロ | 関数 | 対応する構造 |
---|---|---|
VIDIOC_QUERYBUF | バッファのステータスを問い合わせる | struct v4l2_buffer (着信および発信) |
VIDIOC_QBUF | ドライバとのバッファ交換 | struct v4l2_buffer (着信) |
struct v4l2_buffer {
__u32 index; // 设置好索引
__u32 type; // 设置好类型
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
union {
__s32 request_fd;
__u32 reserved;
};
};
unsigned char *mptr[4];//保存映射后用户空间的首地址,之后通过该指针取数据即可
unsigned int size[4];
struct v4l2_buffer mapbuffer;
//初始化type, index
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(int i=0; i<4; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
if(ret < 0)
{
perror("查询内核空间队列失败");
}
mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, mapbuffer.m.offset);
size[i]=mapbuffer.length;
//通知使用完毕--‘放回去’
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if(ret < 0)
{
perror("放回失败");
}
}
5. データ収集
このセクションには、収集の開始、収集データ、収集の停止が含まれます
キーマクロ | 関数 | 対応する構造 |
---|---|---|
VIDIOC_STREAMON | 収集を開始し、データをキューに書き込みます | const int * |
VIDIOC_DQBUF | 特定のデータが必要であることをカーネルに伝えると、カーネルはそれを変更できません | 構造体 v4l2_buffer |
VIDIOC_QBUF | 使い終わったことをカーネルに伝える | 構造体 v4l2_buffer |
VIDIOC_STREAMOFF | 収集を停止し、データをキューに書き込まないようにする | const int * |
// 开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("开启失败");
}
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if(ret < 0)
{
perror("提取数据失败");
}
FILE *file=fopen("my.jpg", "w+");
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
fclose(file);
//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}
// 停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
6.リリースマッピング
for(int i=0; i<4; i++)
munmap(mptr[i], size[i]);
7. デバイスの電源をオフにします
close(fd);