imx6uLL應用-v4l2

Linux V4L2 視頻采集 + JPEG 解碼 + LCD 顯示實踐

本文記錄一個完整的嵌入式視頻處理項目:使用 V4L2 接口從攝像頭采集 MJPEG 圖像,使用 libjpeg 解碼為 RGB 格式,并通過 framebuffer 顯示在 LCD 屏幕上。適用于使用 ARM Cortex-A 系列開發板進行嵌入式 Linux 多媒體開發的學習和實踐。


開發環境

  • 操作系統:Linux(支持 V4L2 和 framebuffer)
  • 攝像頭:支持 MJPEG 輸出格式,分辨率 640×480
  • 顯示屏:支持 framebuffer 顯示,分辨率 800×480,RGB565 格式
  • 編程語言:C
  • 編譯依賴:libjpeg 解碼庫

實現功能

  • 打開攝像頭 /dev/video1,設置 MJPEG 格式采集
  • 申請并映射視頻緩沖區
  • 解碼采集到的 JPEG 數據為 RGB 圖像
  • 將 RGB 圖像轉換為 RGB565 并顯示在 LCD(/dev/fb0)上

關鍵流程

1. 打開 LCD 設備并映射 framebuffer

int lcdfd = open("/dev/fb0", O_RDWR);
lcdptr = (unsigned int *)mmap(NULL, 800*480*4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0);

2.打開攝像頭設備并設置采集格式

int fd = open("/dev/video1", O_RDWR);
struct v4l2_format v4formt;
v4formt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
v4formt.fmt.pix.width = 640;
v4formt.fmt.pix.height = 480;
ioctl(fd, VIDIOC_S_FMT, &v4formt);

3.申請緩沖區并映射到用戶空間

struct v4l2_requestbuffers v4rqbuffer;
v4rqbuffer.count = 4;
v4rqbuffer.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &v4rqbuffer);for (int i = 0; i < 4; i++) {ioctl(fd, VIDIOC_QUERYBUF, &v4buffer);mptr[i] = (unsigned char *)mmap(NULL, v4buffer.length, ...);ioctl(fd, VIDIOC_QBUF, &v4buffer); // 放回隊列
}

4.啟動采集并循環抓圖

ioctl(fd, VIDIOC_STREAMON, &type);
while (1) {ioctl(fd, VIDIOC_DQBUF, &readbuffer); // 取幀read_JPEG_file(mptr[readbuffer.index], rgbdata, readbuffer.length);lcd_show_rgb(rgbdata, 640, 480);      // 顯示圖像ioctl(fd, VIDIOC_QBUF, &readbuffer);  // 放回隊列
}

圖像解碼與顯示

攝像頭輸出的是 MJPEG 格式(實質是一幀幀 JPEG 圖像),我們使用 libjpeg 將其解碼為 RGB888 格式(三通道,每像素 3 字節):

jpeg_mem_src(&cinfo, jpegData, size);        // 將 JPEG 數據源指向內存
jpeg_read_header(&cinfo, TRUE);              // 讀取頭部信息
jpeg_start_decompress(&cinfo);               // 啟動解壓
jpeg_read_scanlines(&cinfo, &buffer, 1);     // 逐行讀取 RGB 數據

RGB → RGB565 顯示

LCD framebuffer 使用的是 RGB565 格式(每像素 2 字節),我們將 RGB888 的三通道數據壓縮為 RGB565,并寫入 /dev/fb0

unsigned char r = rgbdata[j*3 + 0];
unsigned char g = rgbdata[j*3 + 1];
unsigned char b = rgbdata[j*3 + 2];
unsigned short color = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
ptr[j] = color;

編譯方法

需要下載libjpeg源碼,然后把一些庫文件一直到imx6u里面。交叉編譯使用命令

source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi  
${CC} -o video_show video_show.c  -I/home/zwl/linux/tool/jpeg/include -L/home/zwl/linux/tool/jpeg/lib -ljpeg -Wl,-rpath,/home/zwl/linux/tool/jpeg/lib

運行效果

image-20250504215104659
請添加圖片描述
請添加圖片描述

使用win系統下的obs打開攝像頭,對比發現拍攝畫質基本相似。

image-20250504215531986

源碼附錄

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <stdio.h>
#include <jpeglib.h>
#include <linux/fb.h>int read_JPEG_file (const char *jpegData, char *rgbdata, int size)
{struct jpeg_error_mgr jerr;struct jpeg_decompress_struct cinfo;cinfo.err = jpeg_std_error(&jerr);//1創建解碼對象并且初始化jpeg_create_decompress(&cinfo);//2.裝備解碼的數據//jpeg_stdio_src(&cinfo, infile);jpeg_mem_src(&cinfo,jpegData, size);//3.獲取jpeg圖片文件的參數(void) jpeg_read_header(&cinfo, TRUE);/* Step 4: set parameters for decompression *///5.開始解碼(void) jpeg_start_decompress(&cinfo);//6.申請存儲一行數據的內存空間int row_stride = cinfo.output_width * cinfo.output_components;unsigned char *buffer = malloc(row_stride);int i=0;while (cinfo.output_scanline < cinfo.output_height) {//printf("****%d\n",i);(void) jpeg_read_scanlines(&cinfo, &buffer, 1); memcpy(rgbdata+i*640*3, buffer, row_stride );i++;}//7.解碼完成(void) jpeg_finish_decompress(&cinfo);//8.釋放解碼對象jpeg_destroy_decompress(&cinfo);return 1;
}int lcdfd = 0;
unsigned int *lcdptr = NULL;void lcd_show_rgb(unsigned char *rgbdata, int w, int h)
{   unsigned short *ptr = (unsigned short *)lcdptr;  // 重要!!16位屏幕要用short指針!!for (int i = 0; i < h; i++){for (int j = 0; j < w; j++){unsigned char r = rgbdata[j*3 + 0];unsigned char g = rgbdata[j*3 + 1];unsigned char b = rgbdata[j*3 + 2];unsigned short color = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);ptr[j] = color;}ptr += 800;         // 每行跳800列rgbdata += w * 3;   // 每行跳 w 個像素 * 3字節}
}int main(void)
{lcdfd = open("/dev/fb0", O_RDWR);if (lcdfd < 0){perror("/dev/fb0打開失敗\n");return -1;}lcdptr = (unsigned int *)mmap(NULL, 800*480*4, PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0);if(lcdptr < 0){perror("lcd內存映射失敗\n");return -1;}//1.打開設備int fd = open("/dev/video1",O_RDWR);if (fd < 0){perror("video0 打開失敗");return -1;}//2.獲取攝像頭支持的格式struct v4l2_fmtdesc v4fmtdesc;v4fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for (int i = 0; i < 3; i++) {v4fmtdesc.index = i;int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmtdesc);if (ret < 0){perror("VIDIOC_ENUM_FMT獲取結束!");break;}printf("index=%d\n",v4fmtdesc.index);printf("flags=%d\n",v4fmtdesc.flags);printf("description=%s\n",v4fmtdesc.description);unsigned char *p = (unsigned char *)&v4fmtdesc.pixelformat;printf("pixelformat=%C%C%C%C\n",p[0],p[1],p[2],p[3]);printf("reserved[0]=%d\n",v4fmtdesc.reserved[0]);      }printf("---------------設置采集格式--------------\n");//3.設置采集格式struct v4l2_format v4formt;v4formt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //攝像頭采集v4formt.fmt.pix.width = 640;  //設置寬 不能任意大小v4formt.fmt.pix.height = 480; //設置高v4formt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //設置視頻采集格式int ret = ioctl(fd, VIDIOC_S_FMT, &v4formt);if(ret < 0){perror("VIDIOC_S_FMT:設置格式失敗");}//驗證memset(&v4formt, 0, sizeof(v4formt));  //清空v4formt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_G_FMT, &v4formt);if(ret < 0){perror("獲取格式失敗");}else{printf("v4formt.fmt.pix.width = %d\n",v4formt.fmt.pix.width);printf("v4formt.fmt.pix.height = %d\n",v4formt.fmt.pix.height);unsigned char *p = (unsigned char *)&v4formt.fmt.pix.pixelformat;printf("v4formt.fmt.pix.pixelformat = %C%C%C%C\n",p[0],p[1],p[2],p[3]);printf("設置成功\n");}printf("---------------4.申請內核緩沖隊列--------------\n");//4.申請內核緩沖區隊列struct v4l2_requestbuffers v4rqbuffer;v4rqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;v4rqbuffer.count = 4; //申請4個緩沖區v4rqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式ret = ioctl(fd, VIDIOC_REQBUFS, &v4rqbuffer);if (ret < 0){perror("申請隊列空間失敗");}printf("---------------5.映射隊列空間到用戶空間--------------\n");
//5.映射隊列空間到用戶空間unsigned char *mptr[4]; //保存映射后空間的首地址  重要!!!unsigned int size[4];struct v4l2_buffer v4buffer;v4buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for (int i = 0; i < 4; i++){v4buffer.index = i;ret = ioctl(fd, VIDIOC_QUERYBUF, &v4buffer); //從內核空間中查詢一個空間做映射if (ret < 0){perror("查詢內核空間隊列失敗");}mptr[i] = (unsigned char *)mmap(NULL,v4buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, v4buffer.m.offset);size[i] = v4buffer.length;//通知使用完畢--‘放回去’ret = ioctl(fd, VIDIOC_QBUF, &v4buffer);if(ret < 0){perror("返回失敗");}}/*  VIDIOC_STREAMON(開始采集寫數據到隊列中)VIDIOC_DQBUF(告訴內核我要某一個數據,內核不可以修改)VIDIOC_QBUF(告訴內核我已經使用完畢)VIDIOC_STREAMOFF(停止采集-不在向隊列中寫數據)*/printf("---------------6.開始采集--------------\n");   
//6.開始采集int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMON, &type);if (ret < 0){perror("開啟失敗");}printf("---------------7.采集數據  從隊列中提取一幀數據--------------\n");
//7.采集數據  從隊列中提取一幀數據unsigned char rgbdata[640*480*3];  //定義一個空間存儲解碼后的RGB數據struct v4l2_buffer readbuffer;readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while (1){    ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);if (ret < 0){perror("讀取數據失敗");}//顯示在lcd上read_JPEG_file(mptr[readbuffer.index], rgbdata, readbuffer.length);//把jpeg數據解碼為RGB數據lcd_show_rgb(rgbdata, 640, 480);//通知內核已經使用完畢ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);if (ret < 0){perror("放回隊列失敗");}}printf("---------------8.停止采集--------------\n");
//8.停止采集ret = ioctl(fd, VIDIOC_STREAMOFF, &type);printf("---------------9.釋放映射--------------\n");
//9.釋放映射for (int i = 0; i < 4; i++){printf("size[%d]: %d\n",i,size[i]);munmap(mptr[i],size[i]);}printf("---------------10.關閉設備--------------\n");
//10.關閉設備close(fd);printf("all end\n");return 0;
}

請添加圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/78485.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/78485.shtml
英文地址,請注明出處:http://en.pswp.cn/web/78485.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

強化學習機器人模擬器——QAgent:一個支持多種強化學習算法的 Python 實現

QAgent 是一個靈活的 Python 類,專為實現經典的強化學習(Reinforcement Learning, RL)算法而設計,支持 Q-learning、SARSA 和 SARSA(λ) 三種算法。本篇博客將基于提供的 q_agent.py 代碼,詳細介紹 QAgent 類的功能、結構和使用方法,幫助您理解其在強化學習任務中的應用,…

Feign的原理

為什么 SpringCloud 中的Feign&#xff0c;可以幫助我們像使用本地接口一樣調用遠程 HTTP服務&#xff1f; Feign底層是如何實現的&#xff1f;這篇文章&#xff0c;我們一起來聊一聊。 1. Feign 的基本原理 Feign 的核心思想是通過接口和注解定義 HTTP 請求&#xff0c;將接…

探索正態分布:交互式實驗帶你體驗統計之美

探索正態分布&#xff1a;交互式實驗帶你體驗統計之美 正態分布&#xff0c;這條優美的鐘形曲線&#xff0c;可以說是統計學中最重要、最無處不在的概率分布。從自然現象&#xff08;如身高、測量誤差&#xff09;到金融市場&#xff0c;再到機器學習&#xff0c;它的身影隨處…

使用 IDEA + Maven 搭建傳統 Spring MVC 項目的詳細步驟(非Spring Boot)

搭建Spring MVC項目 第一步&#xff1a;創建Maven項目第二步&#xff1a;配置pom.xml第三步&#xff1a;配置web.xml第四步&#xff1a;創建Spring配置文件第五步&#xff1a;創建控制器第六步&#xff1a;創建JSP視圖第七步&#xff1a;配置Tomcat并運行目錄結構常見問題解決與…

AI日報 · 2025年5月04日|Hugging Face 啟動 MCP 全球創新挑戰賽

1、Hugging Face 啟動 MCP 全球創新挑戰賽 Hugging Face 于?5?月?3?日發布 MCP?Global?Innovation?Challenge&#xff0c;面向全球開發者征集基于模型上下文協議&#xff08;MCP&#xff09;的創新工具與應用&#xff0c;賽事持續至?5?月?31?日&#xff0c;設立多檔…

學習spring boot-攔截器Interceptor,過濾器Filter

目錄 攔截器Interceptor 過濾器Filter 關于過濾器的前置知識可以參考&#xff1a; 過濾器在springboot項目的應用 一&#xff0c;使用WebfilterServletComponentScan 注解 1 創建過濾器類實現Filter接口 2 在啟動類中添加 ServletComponentScan 注解 二&#xff0c;創建…

匯編常用語法

GNU匯編語句&#xff1a; [lable:] instruction [comment] lable 表示標號&#xff0c;表示地址位置&#xff0c;可選. instruction即指令&#xff0c;也就是匯編指令或偽指令。 comment 就是注釋內容。 用戶使用.section 偽操作來定義一個段&#xff0c;匯編系統預定義了一些…

terraform resource創建了5臺阿里云ecs,如要使用terraform刪除其中一臺主機,如何刪除?

在 Terraform 中刪除阿里云 5 臺 ECS 實例中的某一臺&#xff0c;具體操作取決于你創建資源時使用的 多實例管理方式&#xff08;count 或 for_each&#xff09;。以下是詳細解決方案&#xff1a; 方法一&#xff1a;使用 for_each&#xff08;推薦&#xff09; 如果創建時使…

pycharm terminal 窗口打不開了

參考添加鏈接描述powershell.exe改為cmd.exe發現有一個小正方形&#xff0c;最大化可以看見了。

百度「心響」:左手“多智能體”右手“保姆級服務”,C端用戶能看懂這技術告白嗎?

——當技術名詞撞上“傻瓜式”需求&#xff0c;誰是贏家&#xff1f; 「多智能體」是什么&#xff1f;用戶&#xff1a;不重要&#xff0c;能一鍵搞定就行 百度最新推出的多智能體平臺“心響”&#xff0c;號稱能用自然語言交互一鍵托管復雜任務。 從旅游攻略到法律咨詢&#x…

57認知干貨:AI機器人產業

機器人本質上由可移動的方式和可交互萬物的機構組成,即適應不同環境下不同場景的情況,機器人能夠做到根據需求調整交互機構和移動方式。因此,隨著人工智能技術的發展,AI機器人的產業也將在未來逐步從單一任務的執行者,發展為能夠完成復雜多樣任務的智能體。 在未來的社會…

在兩個bean之間進行數據傳遞的解決方案

簡介 在日常開發中&#xff0c;在兩個bean之間進行數據傳遞是常見的操作&#xff0c;例如在日常開發中&#xff0c;將數據從VO類轉移到DO類等。在兩個bean之間進行數據傳遞&#xff0c;最常見的解決方案&#xff0c;就是手動復制&#xff0c;但是它比較繁瑣&#xff0c;充斥著…

基于開閉原則優化數據庫查詢語句拼接方法

背景 在開發實踐中&#xff0c;曾有同事在實現新功能時&#xff0c;因直接修改一段數據庫查詢條件拼接方法的代碼邏輯&#xff0c;導致生產環境出現故障。 具體來看&#xff0c;該方法通過在函數內部直接編寫條件判斷語句實現查詢拼接&#xff0c;盡管從面向對象設計的開閉原…

QT開發工具對比:Qt Creator、Qt Designer、Qt Design Studio

前端開發工具—Qt Designer Qt Designer是Qt框架的一部分&#xff0c;是一個圖形用戶界面設計工具。它允許開發者通過可視化方式設計和布局GUI組件&#xff0c;而無需手動編寫UI代碼。設計完成后&#xff0c;Qt Designer生成UI文件&#xff08;通常以.ui為擴展名&#xff09;&…

0基礎 | STM32 | TB6612電機驅動使用

TB6612介紹及使用 單片機通過驅動板連接至電機 原因&#xff1a;單品機I/O口輸出電流I小 驅動板&#xff1a;從外部引入高電壓&#xff0c;控制電機驅動 電源部分 VM&#xff1a;電機驅動電源輸入&#xff0c;輸入電壓范圍建議為3.7&#xff5e;12V GND&#xff1a;邏輯電…

【操作系統】死鎖

1. 定義 死鎖是指兩個或多個進程&#xff08;或線程&#xff09;在執行過程中&#xff0c;因爭奪資源而造成的一種僵局&#xff0c;每個進程都無限期地等待其他進程釋放它們所持有的資源。在這種情況下&#xff0c;沒有任何進程能夠繼續執行&#xff0c;除非有外部干預。 2. …

C++入門?關于類的一些特殊知識點

涉及的關于類中的默認成員函數的知識點可以看我的這篇博客哦~ C入門必須知道的知識?類的默認成員函數&#xff0c;一文講透運用 目錄 初始化列表 類型轉換 static成員 友元 內部類 匿名對象 對象拷貝時的一些編譯器的優化 初始化列表 我們知道類中的構造函數的任務是完…

只用Prettier進行格式化項目

1.下載Prettier插件&#xff0c;禁用ESlint 2.在項目根目錄新建.prettierrc文件 {"singleQuote": true,"jsxSingleQuote": true,"printWidth": 100,"trailingComma": "none","tabWidth": 2,"semi": f…

XXL-TOOL v1.4.0 發布 | Java工具類庫

Release Notes 1、【新增】JsonRpc模塊&#xff1a;一個輕量級、跨語言遠程過程調用實現&#xff0c;基于json、http實現&#xff08;從XXL-JOB底層通訊組件提煉抽象&#xff09;。2、【新增】Concurrent模塊&#xff1a;一系列并發編程工具&#xff0c;具備良好的線程安全、高…

基于LVGL的登錄界面設計

目錄 一、演示 二、前言 三、部件知識 3.1 圖片按鈕部件 3.1.1 圖片按鈕部件的組成 3.1.2 圖片的來源 3.1.3 添加/清除的狀態 3.1.4 圖片按鈕部件 API 函數 3.2 鍵盤部件(lv_keyboard) 3.2.1 鍵盤部件的組成 3.2.2 鍵盤部件的相關知識 3.2.2.1 鍵盤部件模式 3.…