【linux】v4l2 アプリケーション プログラミング (1) 画像(jpg) 収集 操作手順

ここに画像の説明を挿入

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);

参照リンク

V4L2 キャプチャー動画表示

おすすめ

転載: blog.csdn.net/weixin_42442319/article/details/126052294