Ehome:智慧家居之基於USB攝像頭免驅的視訊採集伺服器
阿新 • • 發佈:2019-01-30
4. 視訊伺服器
4.1 攝像頭的驅動['uvc子系統']: usb video class
核心中自帶了滿足uvc格式的攝像頭驅動,如果你手中的攝像頭滿足uvc規範,該攝像頭就是免驅,只需要對核心進行配置,將uvc模組對應的程式碼編譯到uImage
判斷攝像頭滿足uvc格式規範?
#:' lsusb
再將攝像頭插入開發板
#:' lsusb
Bus 001 Device 003: ID 046d:0825
網路搜尋 uvc官網 有一個的頁面:
列出了uvc框架支援的usb攝像頭ID,核對lsusb命令即可。
配置核心,將uvc子系統編譯進核心
$:' cp arch/arm/config/x6818-config .config
$:' make menuconfig
Device Drivers --->
<*> Multimedia support --->
[*] Video capture adapters --->
[*] V4L USB devices --->
<*> USB Video Class (UVC)
// 檢查是否選中狀態,未選中就需要重新編譯核心
$:' make uImage
// 讓開發板載入包含uvc模組的新的核心,已經包含就不需重新編譯
#:' ls /dev/video*
再次插入攝像頭
發現多了一個video9 ,就是插入攝像頭的裝置檔案
4.2 應用程式
4.2.1操作攝像頭,抓取影象資料
['v4l2']: video for linux ver2 (v four linux)
它屬於攝像頭軟體的中間層
向下統一攝像頭驅動的格式
向上為應用軟體訪問控制攝像頭提供統一的介面
簡化應用層軟體控制攝像頭的程式設計工作
v4l2使用者態程式設計:
v4l2提供的有小程式:// v4l_demo.zip/capture.c
open裝置
ioctl設定工作引數
ioctl(fd, VIDIOC_STREAMON, &type);//開始攝像頭開始工作
// 獲取影象資料
ioctl(fd, VIDIOC_DQBUF, &buf);
ioctl(fd, VIDIOC_QBUF, &buf);
mjpeg-streamer包含了按照v4l2框架去操作攝像頭的程式碼
而且其中也包含了按照http協議向客戶端傳送影象資料的程式碼
重點:移植部署執行mjpeg-streamer
4.2.2mjpeg-streamer的移植: // mjpg-streamer.tar.bz2
$:' cd project
$:' mkdir video
$:' cd video
$:' cp /mnt/hgfs/project/env/mjpg-streamer.tar.bz2 .
$:' tar xvf mjpg-streamer.tar.bz2
$:' cd mjpg-streamer/
$:' vi README
make clean all
./mjpg-streamer ....
start.sh
$:' vi Makefile
CC = gcc
$:' find ./ -name "Makefile" -exec sed -i "s/CC = gcc/CC = arm-cortex_a9-linux-gnueabi-gcc/g" {} \;
sed: 操作檔案
awk: 操作行處理
結合正則表示式 功能非常強大
$:' make
$:' file mjpg_streamer
// 確認檔案是ARM執行平臺
4.2.3 部署到開發板
$:' cp mjpg_streamer ../../rootfs/home/bin/
// 拷貝可執行檔案 mjpg_streamer
$:' cp *.so ../../rootfs/home/lib/ -a
// 拷貝相關的共享庫
$:' cp www/ ../../rootfs/home/ -r
// 拷貝www目錄
www目錄作用:瀏覽器連線視訊採集服務。
4.2.4 執行
#:' /home/bin/mjpg_streamer --help
#:' /home/bin/mjpg_streamer -i "input_uvc.so --help"
#:' /home/bin/mjpg_streamer -i "/home/lib/input_uvc.so -d /dev/video9 -y -r 320x240 -f 30" -o "/home/lib/output_http.so -w /home/www"
// 有攝像頭的情況下啟動www服務。
-i: 指定輸入外掛
-d: 指定訪問的攝像頭裝置檔案
-y: 採集影象的格式為YUYV
-r: 採集影象的大小
-f: 幀頻率
-o: 指定輸出外掛
-w: 網頁資原始檔所在目錄
開啟瀏覽器,輸入" http://192.168.1.6:8080/ "
如果手裡沒有攝像頭,可以使用如下方案:
#:' /home/bin/mjpg_streamer -i "/home/lib/input_testpicture.so -r 320x240 -d 500" -o "/home/lib/output_http.so -w /home/www -p 80"
// input_testpicture.so不是採集攝像頭資料,是自身有兩張圖片,將這兩張圖片交替的傳送給客戶端,用於模擬測試攝像頭獲取影象幀
開啟瀏覽器,輸入" http://192.168.1.6:8080/ "
4.3 mjpg-streamer 原始碼分析
4.3.1 mjpg_streamer.c
高內聚 低耦合:模組與模組之間的關聯度,越小越好。不編譯某個模組,程式一樣可以編譯執行。
$:' ctags -R *int main(int argc, char *argv[])
{
/*共享庫的執行階段載入有兩種方式
gcc xxx -o a.out -lpthread
./a.out 作業系統載入共享庫
程式中自主主動載入共享庫(外掛庫)
*/
"input_uvc.so"
global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY);
/*找到input_uvc.so中的input_init函式對應程式碼在記憶體中的位置*/
global.in[i].init = dlsym(global.in[i].handle, "input_init");
global.in[i].run = dlsym(global.in[i].handle, "input_run");
/*執行input_uvc.so中的input_init函式*/
global.in[i].init(&global.in[i].param, i);
"output_http.so"
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
global.out[i].init = dlsym(global.out[i].handle, "output_init");
global.out[i].run = dlsym(global.out[i].handle, "output_run");
global.out[i].init(&global.out[i].param, i);
global.in[i].run(i);
global.out[i].run(global.out[i].param.id);
pause();
return 0;
}
4.3.2 輸入外掛 plugins/input_uvc/
按照v4l2程式設計步驟去操作uvc格式的攝像頭
官方例程:capture.c
Video for linux 2 example (v4l2 demo) - MetalSeed - 部落格頻道 - CSDN.NET.png
$:' vi plugins/input_uvc/input_uvc.c
/*開啟攝像頭 設定工作引數*/
int input_init(input_parameter *param, int id)
{
init_videoIn(cams[id].videoIn, dev, width, he ight, fps, format, 1, cams[id].pglobal, id)
{
init_v4l2(vd)
{
/*開啟"/dev/video9"裝置檔案*/
vd->fd = OPEN_VIDEO(vd->videodevice, O_RDWR)
/*查詢當前硬體的工作能力*/
xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap)
/*影象格式設定*/
ret = xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
...
}
}
}
int input_run(int id)
{
pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id]));
}
void *cam_thread(void *arg)
{
while(!pglobal->stop)
{
uvcGrab(pcontext->videoIn)
{
video_enable(vd)
{
/*VIDIOC_STREAMON:讓攝像頭開始工作*/
ret = xioctl(vd->fd, VIDIOC_STREAMON, &type);
}
/*獲取一幀影象*/
xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf)
}
/* copy JPG picture to global buffer */
memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused);
}
}
BUG修改:input_uvc/v4l2uvc.c
428 do{
429 memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
430 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
431 vd->buf.memory = V4L2_MEMORY_MMAP;
432
433 ret = xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
434 if(ret < 0) {
435 perror("Unable to dequeue buffer");
436 // goto err;
437 }
438 }while(ret < 0);
4.3.3 輸出外掛 plugins/output_http/
將影象資料封裝成http資料包
通過TCP方式下客戶端傳送
$:' vi plugins/output_http/output_http.c
int output_init(output_parameter *param, int id)
{
port = htons(8080);
...
}
int output_run(int id)
{
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
}
void *server_thread(void *arg)
{
socket(aip2->ai_family, aip2->ai_socktype, 0)
bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen)
listen(pcontext->sd[i], 10)
while(!pglobal->stop)
{
accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len)
pthread_create(&client, NULL, &client_thread, pcfd)
}
}
void *client_thread(void *arg)
{
_readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)
{
_read(fd, iobuf, &c, 1, timeout)
{
read(fd, &iobuf->buffer, IO_BUFFER)
}
}
else if(strstr(buffer, "GET /?action=stream") != NULL)
{
req.type = A_STREAM;//確定客戶端請求型別
}
switch(req.type) {
case A_STREAM:
send_stream(lcfd.fd, input_number)
{
while(!pglobal->stop)
{
/*將pglobal->buf資料拷貝到 frame緩衝區*/
memcpy(frame, pglobal->in[input_number].buf, frame_size);
write(fd, frame, frame_size)
}
}
break;
}