本文共 6438 字,大约阅读时间需要 21 分钟。
版本 v1.0,存在内存问题 在 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?) 转载地址:http://tuqci.baihongyu.com/