USB摄像头推流
参考:https://bbs.aw-ol.com/topic/1158/r818-tina-linux-%E8%BF%90%E8%A1%8C-mjpg-streamer-usb-camera?lang=zh-CN
Tina文件系统配置:make menuconfig
Linux Kernel配置:make kernel_menuconfig
拷贝动态链接库到开发板
需要拷贝几个动态链接库到开发板的/usr/lib/
:
./out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib/input_uvc.so
./out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib/output_http.so
./out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib/libjpeg.so.9
这里使用adb的方式拷贝到开发板
liefyuan@ubuntu:~/Liefyuan/Tina-Linux/Tina-Linux/out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib$ adb push output_http.so /lib/
5609 KB/s (34120 bytes in 0.005s)
liefyuan@ubuntu:~/Liefyuan/Tina-Linux/Tina-Linux/out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib$ adb push input_uvc.so /lib/
5273 KB/s (35408 bytes in 0.006s)
liefyuan@ubuntu:~/Liefyuan/Tina-Linux/Tina-Linux/out/t113-mq_r/compile_dir/target/rootfs-tmp/usr/lib$ adb push libjpeg.so.9 /usr/lib/
WiFi联网
insmod /lib/modules/5.4.61/8723ds.ko
ifconfig wlan0 up
mkdir -p /var/run/wpa_supplicant
wpa_supplicant -B -c /etc/wpa_supplicant.conf -i wlan0
udhcpc -i wlan0
wpa_supplicant 文件:
ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
ap_scan=1
network={
ssid="KKKK"
scan_ssid=1
key_mgmt=WPA-EAP WPA-PSK IEEE8021X NONE
pairwise=TKIP CCMP
group=CCMP TKIP WEP104 WEP40
psk="99999999"
priority=5
}
推流:
mjpg_streamer -i "/usr/lib/input_uvc.so -r 1280x720 -d /dev/vi
deo1" -o "/usr/lib/output_http.so -w /www/webcam"
实际效果:
root@TinaLinux:/# mjpg_streamer -i "/usr/lib/input_uvc.so -r 1280x720 -d /dev/vi
deo0" -o "/usr/lib/output_http.so -w /www/webcam"
MJPG-streamer [242]: starting application
MJPG Streamer Version: svn rev: Unversioned directory
MJPG-streamer [242]: MJPG Streamer Version: svn rev: Unversioned directory
i: Using V4L2 device.: /dev/video0
MJPG-streamer [242]: Using V4L2 device.: /dev/video0
i: Desired Resolution: 1280 x 720
MJPG-streamer [242]: Desired Resolution: 1280 x 720
i: Frames Per Second.: not limited
MJPG-streamer [242]: Frames Per Second.: not limited
i: Format............: JPEG
MJPG-streamer [242]: Format............: JPEG
i: TV-Norm...........: DEFAULT
MJPG-streamer [242]: TV-Norm...........: DEFAULT
[ 630.855036] uvcvideo: Failed to query (GET_DEF) UVC control 12 on unit 2: -32 (exp. 4).
o: www-folder-path...: /www/webcam/
MJPG-streamer [242]: www-folder-path...: /www/webcam/
o: HTTP TCP port.....: 8080
MJPG-streamer [242]: HTTP TCP port.....: 8080
o: username:password.: disabled
MJPG-streamer [242]: username:password.: disabled
o: commands..........: enabled
MJPG-streamer [242]: commands..........: enabled
MJPG-streamer [242]: starting input plugin /usr/lib/input_uvc.so
MJPG-streamer [242]: starting output plugin: /usr/lib/output_http.so (ID: 00)
MJPG-streamer [242]: serving client: 192.168.1.102
MJPG-streamer [242]: serving client: 192.168.1.102
MJPG-streamer [242]: serving client: 192.168.1.102
[ 654.665144] NOHZ: local_softirq_pending 08
[ 654.670175] NOHZ: local_softirq_pending 08
[ 654.674802] NOHZ: local_softirq_pending 08
[ 654.679764] NOHZ: local_softirq_pending 08
[ 654.684552] NOHZ: local_softirq_pending 08
[ 655.264838] NOHZ: local_softirq_pending 08
[ 655.626925] NOHZ: local_softirq_pending 08
[ 655.631554] NOHZ: local_softirq_pending 08
[ 655.647364] NOHZ: local_softirq_pending 08
[ 655.653526] NOHZ: local_softirq_pending 08
浏览器看推流内容
然后在浏览器地址栏输入:http://192.168.1.105:8080/
就可以看到视频了!
运行了一个小时很稳定、清晰,芯片也很热,烫手!
推流的时候看看top命令:
Mem: 83516K used, 29136K free, 0K shrd, 916K buff, 6968K cached
CPU: 29% usr 68% sys 0% nic 0% idle 0% io 0% irq 2% sirq
Load average: 2.80 2.60 1.62 4/69 257
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
145 1 root S 1312 1% 26% /bin/adbd -D
206 2 root RW 0 0% 26% [RTWHALXT]
252 159 root S 23520 21% 23% mjpg_streamer -i /usr/lib/input_uvc.so
203 2 root RW 0 0% 19% [RTW_XMIT_THREAD]
246 2 root IW< 0 0% 1% [kworker/u5:1-uv]
201 2 root SW 0 0% 1% [ksdioirqd/mmc1]
204 2 root SW 0 0% 1% [RTW_RECV_THREAD]
148 1 root S 696 1% 1% /sbin/swupdate-progress -w
10 2 root IW 0 0% 0% [rcu_preempt]
9 2 root SW 0 0% 0% [ksoftirqd/0]
15 2 root SW 0 0% 0% [ksoftirqd/1]
216 1 root S 2440 2% 0% wpa_supplicant -B -c /etc/wpa_supplica
159 1 root S 1052 1% 0% -/bin/sh
1 0 root S 1048 1% 0% /sbin/init
257 159 root R 1048 1% 0% top
226 1 root S 1048 1% 0% udhcpc -i wlan0
205 2 root SW 0 0% 0% [RTW_CMD_THREAD]
21 2 root IW 0 0% 0% [kworker/0:1-eve]
55 2 root SW 0 0% 0% [spi0]
7 2 root IW 0 0% 0% [kworker/u4:0-ev]
进程信息:
- PID:进程的ID
- PPID:父进程ID
- USER:进程所有者
- STAT:也就是当前进程的状态,其中S-睡眠,s-表示该进程是会话的先导进程,N-表示进程拥有比普通优先级更低的优先级,R-正在运行,D-短期等待,Z-僵死进程,T-被跟踪或者被停止等等
- VSZ:进程的虚拟内存占用大小(单位:kb(killobytes))
- %VSZ:进程的虚拟内存占用率
- %CPU:进程占用CPU的使用率
- COMMOND–命令的名称和参数
显示在屏幕上面(测试OK)
因为我的摄像头不支持RGB模式,所以就设置成YUV格式的输出模式,然后再将YUV格式转成RGB格式的视频流显示。
我的屏幕是32bpp(ARGB),所以一个像素是需要四个字节来描述的。
转码后的视频流是连续的。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <math.h>
#include <wchar.h>
#include <time.h>
#include <stdbool.h>
#define CAM_WIDTH 640
#define CAM_HEIGHT 480
static char *dev_video;
static char *dev_fb0;
static char *yuv_buffer;
static char *rgb_buffer;
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
#define YUVToRGB(Y) \
((u16)((((u8)(Y) >> 3) << 11) | (((u8)(Y) >> 2) << 5) | ((u8)(Y) >> 3)))
struct v4l2_buffer video_buffer;
/*全局变量*/
int lcd_fd;
int video_fd;
static unsigned char *lcd_mem_p = NULL; //保存LCD屏映射到进程空间的首地址
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
char *video_buff_buff[4]; /*保存摄像头缓冲区的地址*/
int video_height = 0;
int video_width = 0;
unsigned char *lcd_display_buff; //LCD显存空间
unsigned char *lcd_display_buff2; //LCD显存空间
static void errno_exit(const char *s)
{
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
exit(EXIT_FAILURE);
}
static int xioctl(int fh, int request, void *arg)
{
int r;
do {
r = ioctl(fh, request, arg);
} while (-1 == r && EINTR == errno);
return r;
}
static int video_init(void)
{
struct v4l2_capability cap;
ioctl(video_fd, VIDIOC_QUERYCAP, &cap);
struct v4l2_fmtdesc dis_fmtdesc;
dis_fmtdesc.index = 0;
dis_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("-----------------------支持格式---------------------\n");
while (ioctl(video_fd, VIDIOC_ENUM_FMT, &dis_fmtdesc) != -1) {
printf("\t%d.%s\n", dis_fmtdesc.index + 1,
dis_fmtdesc.description);
dis_fmtdesc.index++;
}
struct v4l2_format video_format;
video_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_format.fmt.pix.width = CAM_WIDTH;
video_format.fmt.pix.height = CAM_HEIGHT;
video_format.fmt.pix.pixelformat =
V4L2_PIX_FMT_YUYV; //使用JPEG格式帧,用于静态图像采集
ioctl(video_fd, VIDIOC_S_FMT, &video_format);
printf("当前摄像头支持的分辨率:%dx%d\n", video_format.fmt.pix.width,
video_format.fmt.pix.height);
if (video_format.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {
printf("当前摄像头不支持YUYV格式输出.\n");
video_height = video_format.fmt.pix.height;
video_width = video_format.fmt.pix.width;
//return -3;
} else {
video_height = video_format.fmt.pix.height;
video_width = video_format.fmt.pix.width;
printf("当前摄像头支持YUYV格式输出.width %d height %d\n",
video_height, video_height);
}
/*3. 申请缓冲区*/
struct v4l2_requestbuffers video_requestbuffers;
memset(&video_requestbuffers, 0, sizeof(struct v4l2_requestbuffers));
video_requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_requestbuffers.count = 4;
video_requestbuffers.memory = V4L2_MEMORY_MMAP;
if (ioctl(video_fd, VIDIOC_REQBUFS, &video_requestbuffers))
return -4;
printf("成功申请的缓冲区数量:%d\n", video_requestbuffers.count);
/*4. 得到每个缓冲区的地址: 将申请的缓冲区映射到进程空间*/
struct v4l2_buffer video_buffer;
memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
int i;
for (i = 0; i < video_requestbuffers.count; i++) {
video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_buffer.index = i;
video_buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(video_fd, VIDIOC_QUERYBUF, &video_buffer))
return -5;
/*映射缓冲区的地址到进程空间*/
video_buff_buff[i] =
mmap(NULL, video_buffer.length, PROT_READ | PROT_WRITE,
MAP_SHARED, video_fd, video_buffer.m.offset);
printf("第%d个缓冲区地址:%#X\n", i, video_buff_buff[i]);
}
/*5. 将缓冲区放入到采集队列*/
memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
for (i = 0; i < video_requestbuffers.count; i++) {
video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_buffer.index = i;
video_buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(video_fd, VIDIOC_QBUF, &video_buffer)) {
printf("VIDIOC_QBUF error\n");
return -6;
}
}
printf("启动摄像头采集\n");
/*6. 启动摄像头采集*/
int opt_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(video_fd, VIDIOC_STREAMON, &opt_type)) {
printf("VIDIOC_STREAMON error\n");
return -7;
}
return 0;
}
int lcd_init(void)
{
/*2. 获取可变参数*/
if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &vinfo))
return -2;
printf("屏幕X:%d 屏幕Y:%d 像素位数:%d\n", vinfo.xres, vinfo.yres,
vinfo.bits_per_pixel);
//分配显存空间,完成图像显示
lcd_display_buff =
malloc(vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
/*3. 获取固定参数*/
if (ioctl(lcd_fd, FBIOGET_FSCREENINFO, &finfo))
return -3;
printf("smem_len=%d Byte,line_length=%d Byte\n", finfo.smem_len,
finfo.line_length);
/*4. 映射LCD屏物理地址到进程空间*/
lcd_mem_p = (unsigned char *)mmap(0, finfo.smem_len,
PROT_READ | PROT_WRITE, MAP_SHARED,
lcd_fd, 0); //从文件的那个地方开始映射
memset(lcd_mem_p, 0xFFFFFF, finfo.smem_len);
printf("映射LCD屏物理地址到进程空间\n");
return 0;
}
static void close_device(void)
{
if (-1 == close(video_fd))
errno_exit("close");
video_fd = -1;
if (-1 == close(lcd_fd))
errno_exit("close");
lcd_fd = -1;
}
static void open_device(void)
{
video_fd = open(dev_video, O_RDWR /* required */ | O_NONBLOCK, 0);
if (-1 == video_fd) {
fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_video, errno,
strerror(errno));
exit(EXIT_FAILURE);
}
lcd_fd = open(dev_fb0, O_RDWR, 0);
if (-1 == lcd_fd) {
fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_fb0, errno,
strerror(errno));
exit(EXIT_FAILURE);
}
}
/*
将YUV格式数据转为RGB
*/
void yuv_to_rgb(unsigned char *yuv_buffer, unsigned char *rgb_buffer,
int iWidth, int iHeight)
{
int x;
int z = 0;
unsigned char *ptr = rgb_buffer;
unsigned char *yuyv = yuv_buffer;
for (x = 0; x < iWidth * iHeight; x++) {
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] << 8;
u = yuyv[1] - 128;
v = yuyv[3] - 128;
r = (y + (359 * v)) >> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); // b color
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); // g color
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); // r color
*(ptr++) = 0xff; // a color
if (z++) {
z = 0;
yuyv += 4;
}
}
}
void rgb24_to_rgb565(char *rgb24, char *rgb16)
{
int i = 0, j = 0;
for (i = 0; i < 240 * 240 * 3; i += 3) {
rgb16[j] = rgb24[i] >> 3; // B
rgb16[j] |= ((rgb24[i + 1] & 0x1C) << 3); // G
rgb16[j + 1] = rgb24[i + 2] & 0xF8; // R
rgb16[j + 1] |= (rgb24[i + 1] >> 5); // G
j += 2;
}
}
static void lcd_image(unsigned int start_x, unsigned int end_x,
unsigned int start_y, unsigned int end_y,
unsigned char* color)
{
unsigned long i;
unsigned int j;
/* 填充颜色 */
i = start_y * vinfo.xres; //定位到起点行首
for ( ; start_y <= end_y; start_y++, i+=(vinfo.xres*4))
{
for (j = start_x; j <= end_x*4; j++)
{
lcd_mem_p[i + j] = *color++;
}
*color--;
}
}
int main(int argc, char **argv)
{
dev_video = "/dev/video0";
dev_fb0 = "/dev/fb0";
open_device();
video_init();
lcd_init();
/*3. 读取摄像头的数据*/
struct pollfd video_fds;
video_fds.events = POLLIN;
video_fds.fd = video_fd;
memset(&video_buffer, 0, sizeof(struct v4l2_buffer));
rgb_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 4);
yuv_buffer = malloc(CAM_WIDTH * CAM_HEIGHT * 4);
unsigned char *rgb_p;
int w, h, i, j;
unsigned char r, g, b;
unsigned int c;
while (1) {
/*等待摄像头采集数据*/
poll(&video_fds, 1, -1);
/*得到缓冲区的编号*/
video_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_buffer.memory = V4L2_MEMORY_MMAP;
ioctl(video_fd, VIDIOC_DQBUF, &video_buffer);
//printf("当前采集OK的缓冲区编号:%d,地址:%#X num:%d\n",
//video_buffer.index, video_buff_buff[video_buffer.index],
//strlen(video_buff_buff[video_buffer.index]));
/*对缓冲区数据进行处理*/
yuv_to_rgb(video_buff_buff[video_buffer.index], yuv_buffer, video_height, video_width);
//rgb24_to_rgb565(yuv_buffer, rgb_buffer);
//printf("显示屏进行显示\n");
//显示屏进行显示: 将显存空间的数据拷贝到LCD屏进行显示
/*memcpy(lcd_mem_p, yuv_buffer,
vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);*/
// video stream show
lcd_image(0, 640, 0, 480, yuv_buffer);
//printf("buffer size: %d\r\n", (vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8));
/*将缓冲区放入采集队列*/
ioctl(video_fd, VIDIOC_QBUF, &video_buffer);
//printf("将缓冲区放入采集队列\n");
}
/*4. 关闭视频设备*/
close(video_fd);
return 0;
}
使用Tina系统的交叉编译器进行交叉编译
arm-openwrt-linux-gcc main.c -o cam2fb.elf
使用ADB传输到开发板
adb push cam2fb.elf /opt
看了一下top命令:CPU占了68%,其中这个从摄像头采集视频流显示到屏幕就占了46%的CPU占用率。
Mem: 32268K used, 80384K free, 0K shrd, 520K buff, 7940K cached
CPU: 68% usr 31% sys 0% nic 0% idle 0% io 0% irq 0% sirq
Load average: 2.08 2.03 2.01 3/66 497
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
146 1 root S 1312 1% 50% /bin/adbd -D
495 218 root R 8504 8% 46% ./cam2fb.elf
74 2 root IW< 0 0% 3% [kworker/u5:0-uv]
148 1 root S 696 1% 1% /sbin/swupdate-progress -w
497 218 root R 1048 1% 0% top
10 2 root IW 0 0% 0% [rcu_preempt]
194 2 root SW 0 0% 0% [ksdioirqd/mmc1]
198 2 root SW 0 0% 0% [RTW_RECV_THREAD]
206 1 root S 2440 2% 0% wpa_supplicant -B -c /etc/wpa_supplica
1 0 root S 1048 1% 0% /sbin/init
218 1 root S 1048 1% 0% -/bin/sh
217 1 root S 1048 1% 0% udhcpc -i wlan0
430 2 root IW< 0 0% 0% [kworker/u5:1-uv]
199 2 root SW 0 0% 0% [RTW_CMD_THREAD]
238 2 root IW 0 0% 0% [kworker/0:0-eve]
66 2 root SW 0 0% 0% [irq/37-mmc0]
465 2 root IW 0 0% 0% [kworker/u4:2-ev]
21 2 root IW 0 0% 0% [kworker/1:1-mm_]
9 2 root SW 0 0% 0% [ksoftirqd/0]
^C 67 2 root IW 0 0% 0% [kworker/0:2-eve]