V4L2 和 YUV

在 void v4l2_process_image(struct buffer buf)中對 v4l2 採集來的一幀進行處理,存在 struct buffer bu
f 中
buffer 結構定義爲:
struct buffer {
void *    start;
size_t   length;
};
buffer.start 爲 YUV422 格式數據的起始地址。
有關 YUV 格式:
YUV 格式通常有兩大類:打包(packed)格式和平面(planar)格式。前者將 YUV 分量存放在同一個數組中,
通常是幾個相鄰的像素組成一個宏像素(macro-pixel);而後者使用三個數組分開存放 YUV 三個分量,就像
是一個三維平面一樣。
對於 YUV422(YUV2,V4L2_PIX_FMT_YUYV)格式,屬於打包格式,存儲順序爲:
Byte Order. Each cell is one byte.
start + 0:     Y'00 Cb00 Y'01 Cr00 Y'02 Cb01 Y'03 Cr01
start + 8:     Y'10 Cb10 Y'11 Cr10 Y'12 Cb11 Y'13 Cr11
start + 16: Y'20 Cb20 Y'21 Cr20 Y'22 Cb21 Y'23 Cr21
start + 24: Y'30 Cb30 Y'31 Cr30 Y'32 Cb31 Y'33 Cr31
參見: http://www.linuxtv.org/downloads/v4l-dvb-apis/re09.html#id2765148 (2010.7.12)
對於 YUV420(YUV2,V4L2_PIX_FMT_YVU420)格式,屬於平面格式,存儲順序爲:
Byte Order. Each cell is one byte.
start + 0:      Y'00 Y'01 Y'02 Y'03
start + 4:      Y'10 Y'11 Y'12 Y'13
start + 8:      Y'20 Y'21 Y'22 Y'23
start + 12: Y'30 Y'31 Y'32 Y'33
start + 16: Cr00 Cr01
start + 18: Cr10 Cr11
start + 20: Cb00 Cb01
start + 22: Cb10 Cb11
參見: http://www.linuxtv.org/downloads/v4l-dvb-apis/re14.html#id2770792 (2010.7.12)
v4l2 抓取的幀爲 YUV422,但 ffmpeg 中 mpeg4 編碼的輸入幀格式爲 YUV420,在 ffmpeg 編碼中輸入的幀結構
爲 AVFrame ,其數據結構中有關幀數據的部分爲:
{
uint8_t *data[4];
int linesize[4];   // number of bytes per line
其它信息(是否是 key_frame,已編碼圖像書 coded_picture_number、
是否作爲參考幀 reference、宏塊類型 *mb_type 等等,目前未用到);
}
另外要提到的一種數據結構 AVPicture :
typedef struct AVPicture {
uint8_t *data[4];
int linesize[4];   //number of bytes per line
} AVPicture;
AVPicture 的存在有以下原因,AVPicture 將 Picture 的概念從 Frame 中提取出來,就只
由 Picture(圖片)本身的信息,亮度、色度和行大小。而 Frame 還有如是否是 key_frame 之類的信息。
所以要從 v4l2 採集到的幀(v4l2_process_image 中 buf.start)轉換爲 YUV420 格式給編碼器,需要兩個 A
VFrame(其實 AVPicture 已經足夠了):
AVFrame *srcbuf ;  //源格式 YUV422
AVFrame *dstbuf ;    //目標 YUV420
對於 YUV422 格式只用到了 srcbuf->data[0]存放 YUV 數據(打包格式),和 srcbuf->linesize[0]這是一
幀每行所站的 bytes 數(YUV422 爲 width *2)。
對於 YUV420 格式(平面格式),則 data[0]、data[1]、data[2]對應 YUV 三個平面。
data[0]:Y 起始 addr,size 個 y 數據。       (size=width *height)
data[1] = data[0] + size;    // U 起始 addr ,size/4 個 U
data[2] = data[1] + size / 4;   // V 起始 addr,size/4 個 V
從 YUV422 轉換爲 YUV420 格式可以利用 ffmpeg 下 libavcodec/imgconvert.c 中的
void yuyv422_to_yuv420p(AVPicture *dst, const AVPicture *src,
int width, int height) 函數,
但要設置好 srcbuf , dstbuf,(強制類型轉換)併爲分配好內存,剛開始就是在這方面出現問題,後面
再提。
總結,現在有了 buffer buf 結構的幀數據(在 buf.start 中以 YUV422 存儲),先要將其放到 AVFrame                    *
srcbuf 中(仍爲 YUV422 格式),再用 yuyv422_to_yuv420p 轉換爲 YUV420 格式並存在 AVFrame *dstb
uf ,dstbuf 交給編碼器 mpeg4 編碼。
在版本 v1.0,爲初次遇到的內存錯誤:

dstbuf = avcodec_alloc_frame();
只是這樣就以爲爲 srcbuf ,               dstbuf 分配好了內存。
srcbuf->data[0] = (uint8_t*)buf.start;srcbuf->data[0]指向 buf.start 就開始 yuyv422_to_yuv42
0p 轉換了。(見 main.c v4l2_process_image 函數)
運行時錯誤信息: 段錯誤
調試信息:
Breakpoint 1, v4l2_process_image (buf=...) at main.c:29
29        srcbuf = avcodec_alloc_frame();
(gdb) s
30        dstbuf = avcodec_alloc_frame();
(gdb) s
31       srcbuf->data[0] = (uint8_t*)buf.start;
(gdb) s
32       srcbuf->linesize[0] = V4L2_WIDTH*2;
(gdb) p srcbuf->data[0]
$1 = (uint8_t *) 0xb7bf6000 <Address 0xb7bf6000 out of bounds>
(gdb) p srcbuf->data[0][0]@10
Cannot access memory at address 0xb7bf6000
(gdb) p   buf.start[0]@10
Attempt to dereference a generic pointer.
(gdb) p dstbuf->data[0]
$2 = (uint8_t *) 0x0
(gdb) p dstbuf->data[0][0]@5
Cannot access memory at address 0x0
srcbuf->data[0]已指向 buf.start,但是無法訪問數組的數據,可能是指針爲 void*的原因,(uint8_t
*)強制轉換也沒用。
dstbuf->data[0]的值爲 (uint8_t *) 0x0,並沒有指向可用的內存。所以 srcbuf = avcodec_alloc_fr
ame()並沒有分配內存,可能只是聲明瞭 srcbuf 爲 AVFrame。還需要用 malloc()分配內存。
在版本 v1.1,針對以上問題的處理爲:

uint8_t   *picture_bufdst,*picture_bufsrc;
AVFrame *srcbuf = NULL;  //源 YUV422
AVFrame *dstbuf = NULL; //目標 YUV420
srcbuf = avcodec_alloc_frame();
dstbuf = avcodec_alloc_frame();
picture_bufsrc = malloc(640 * 480 *2);
srcbuf->data[0] = picture_bufsrc;
memcpy(srcbuf->data[0], buf.start, 640 * 480 * 2);
srcbuf->linesize[0] = V4L2_WIDTH*2;        //每行 bytes 數
picture_bufdst = malloc((640 * 480 * 3) / 2); /* size for YUV 420 */
dstbuf->data[0] = picture_bufdst;  //Y 起始 addr,size 個 Y
dstbuf->data[1] = dstbuf->data[0] + 640*480;   // U 起始 addr ,size/4 個 U
dstbuf->data[2] = dstbuf->data[1] + 640*480/4;   //  V 起始 addr,size/4 個 V
dstbuf->linesize[0] = c->width;
dstbuf->linesize[1] = c->width / 2;
dstbuf->linesize[2] = c->width / 2;
可見,不但要分配內存,還要使 data[0]等指針指向正確的位置。即對 AVFrame 的初始化(其實也就是內
存分配)。
以下爲調試信息:
(gdb) p srcbuf->data[0][0]@10
$2 = "/213r/214t/214t/213u/212r"
(gdb) p   buf.start[0]@10
Attempt to dereference a generic pointer.
(gdb)   p dstbuf->data[0][0]@5
$3 = "/000/000/000/000"
buf.start[0]仍無法訪問, srcbuf , dstbuf 已可用.
在版本 v1.2 改進:

encod_init() 爲編碼的初始化相關的函數。
Srcbuf 直接指向 buf.start ,省略了 memcpy()。
在版本 v1.3 改進:

AVCodecContext *c,c->pix_fmt = PIX_FMT_YUYV422 可設置 Pixel forma,將其設爲 YUV422 格式,
出錯:only YUV420 and YUV422 are supported ,原來是設錯了,但知道了支持的格式了。
改爲 c->pix_fmt = PIX_FMT_YUV422P ,這樣省去了到 YUV420 的轉換。
首先 srcbuf 直接指向 buf.start ,出現了段錯誤,
(gdb) p srcbuf->data[0][0]@10
Cannot access memory at address 0xb7bf5000
(gdb) s
73           out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, srcbuf);
(gdb) s
Program received signal SIGSEGV, Segmentation fault.
0x00b76fc6 in ?? () from /lib/tls/i686/cmov/libc.so.6
(gdb) q
再用 memcpy(srcbuf->data[0], buf.start, 640 * 480 * 2);
(gdb) p srcbuf->data[0][0]@10
$1 = "Bk@|>l>{Bm"
77           out_size = avcodec_encode_video(c, outbuf, OUTBUF_SIZE, srcbuf);
(gdb) p out_size
$1 = 0
(gdb) s
Program received signal SIGSEGV, Segmentation fault.
0x00b76fc6 in ?? () from /lib/tls/i686/cmov/libc.so.6
srcbuf->data[0]有數據,但仍然在 avcodec_encode_video 中出現段錯誤。
原因呢?
在 ffmpeg 中對各種格式的解釋爲:
PIX_FMT_YUV422P
planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
PIX_FMT_YUV420P
planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
PIX_FMT_YUYV422
packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
引自:file:///usr/share/doc/ffmpeg-doc/html/pixfmt_8h.html#a60883d4958a60b91661e97027a85072a
在 V4L2 下的解釋:
V4L2_PIX_FMT_YUV422P 4 × 4 pixel image
Byte Order. Each cell is one byte.
start + 0:     Y'00 Y'01 Y'02 Y'03
start + 4:     Y'10 Y'11 Y'12 Y'13
start + 8:     Y'20 Y'21 Y'22 Y'23
start + 12: Y'30 Y'31 Y'32 Y'33
start + 16: Cb00 Cb01
start + 18: Cb10 Cb11
start + 20: Cb20 Cb21
start + 22: Cb30 Cb31
start + 24: Cr00 Cr01
start + 26: Cr10 Cr11
start + 28: Cr20 Cr21
start + 30: Cr30 Cr31
引自:http://www.linuxtv.org/downloads/v4l-dvb-apis/re16.html#id3090524

其 YUV422 是指 PIX_FMT_YUV422P ,仍爲平面格式(planar),而 video4linux 輸入的應該是 PIX_FMT
_YUYV422 打包格式,所以始終還是要進行轉換。
所以本文涉及的 YUV 三中格式總結爲:
YUYV422:v4l 輸出格式,打包格式
YUV420P,YUV422P:平面格式,ffmpeg 編碼器支持的輸入格式。(帶 P 的爲 planar?)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章