264 解码之 yuv

博客访问量日渐减少,于是我决定丢一点技术东东上去,吸引爬虫光顾一下。

先谈谈 h.264 的编解码问题。
个人建议做视频、音频的孩子们,一定要抓住 RFC 和 standard ,然后多看开源编解码程序。
近来抽空看了两个 decoder ,一个是从 ffmpeg 里面抽取出来的 h.264 部分,还有从 JM 。

解码器生成最后结果是 yuv420 格式(后面会提到)。
关于 yuv ,看到论坛里不少人都急于找工具去看自己解得一帧图像是否正确,更有不少人提供 yuv420 2 rgb24 和 yuv422 2 rgb24 等代码。
这里建议使用 强大的 ps 的 .raw 文件的功能。
.raw 文件可用来查看 纯 rgb 图片 和 纯 yuv 图片,很适合做图像。

yuv 格式:
YUV是指亮度参量和色度参量分开表示的像素格式,而这样分开的好处就是不但可以避免相互干扰,还可以降低色度的采样率而不会对图像质量影响太大。YUV 是一个比较笼统地说法,针对它的具体排列方式,可以分为很多种具体的格式。
YUV格式通常有两大类:打包(packed)格式和平面(planar)格式。前者将YUV分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像 素(macro-pixel);而后者使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。
YUY2到Y211都是打包格式,而IF09到YVU9都是平面格式。
(注意:在介绍各种具体格式时,YUV各分量都会带有下标,如Y0、 U0、V0表示第一个像素的YUV分量,Y1、U1、V1表示第二个像素的YUV分量,以此类推。)

MEDIASUBTYPE_YUY2 YUY2格式,以4:2:2方式打包
MEDIASUBTYPE_YUYV YUYV格式(实际格式与YUY2相同)
MEDIASUBTYPE_YVYU YVYU格式,以4:2:2方式打包
MEDIASUBTYPE_UYVY UYVY格式,以4:2:2方式打包
MEDIASUBTYPE_AYUV 带Alpha通道的4:4:4 YUV格式
MEDIASUBTYPE_Y41P Y41P格式,以4:1:1方式打包
MEDIASUBTYPE_Y411 Y411格式(实际格式与Y41P相同)
MEDIASUBTYPE_Y211 Y211格式
MEDIASUBTYPE_IF09 IF09格式
MEDIASUBTYPE_IYUV IYUV格式
MEDIASUBTYPE_YV12 YV12格式
MEDIASUBTYPE_YVU9 YVU9格式
yuv采样:

4:4:4 表示色度频道没有下采样。每像素 24 位

4:2:2 表示 2:1 的水平下采样,没有垂直下采样。对于每两个 U 样例或 V 样例,每个扫描行都包含四个 Y 样例。每像素 16 位

4:2:0 表示 2:1 的水平下采样,2:1 的垂直下采样。每像素 16 位

4:1:1 表示 4:1 的水平下采样,没有垂直下采样。对于每个 U 样例或 V 样例,每个扫描行都包含四个 Y 样例。与其他格式相比,4:1:1 采样不太常用,本文不对其进行详细讨论。每像素 16 位



在我做的 h264 中,支 持420的连续或隔行视频的编码和解码。
其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma),作用 是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是通过RGB输入信号来创建的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的 两个方面—色调与饱和度,分别用Cr和CB来表示。其中,Cr反映了GB输入信号红色部分与RGB信号亮度值之间的差异。而CB反映的是RGB输入信号蓝 色部分与RGB信号亮度值之同的差异。 
YUV色彩模型来源于RGB模型,
该模型的特点是将亮度和色度分离开,从而适合于图像处理领域。
应用:模拟领域
Y'= 0.299*R' + 0.587*G' + 0.114*B'
U'= -0.147*R' - 0.289*G' + 0.436*B' = 0.492*(B'- Y')
V'= 0.615*R' - 0.515*G' - 0.100*B' = 0.877*(R'- Y')


R' = Y' + 1.140*V'
G' = Y' - 0.394*U' - 0.581*V'
B' = Y' + 2.032*U'
YCbCr模型来源于YUV模型。YCbCr是 YUV 颜色空间的偏移版本.
应用:数字视频,ITU-R BT.601建议
Y’ = 0.257*R' + 0.504*G' + 0.098*B' + 16
Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128
R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y’-16) + 2.017*(Cb'-128)
PS: 上面各个符号都带了一撇,表示该符号在原值基础上进行了伽马校正,伽马校正有助于弥补在抗锯齿的过程中,线性分配伽马值所带来的细节损失,使图像细节更加 丰富。在没有采用伽马校正的情况下,暗部细节不容易显现出来,而采用了这一图像增强技术以后,图像的层次更加明晰了。
所以说H264里面的YUV应属于YCbCr.

常见H264测试的YUV序列:

CIF图像大小的YUV序列(352*288),在文件 开始并没有文件头,直接就是YUV数据,先存第一帧的Y信息,长度为352*288个byte, 然后是第一帧U信息长度是352*288/4个byte, 最后是第一帧的V信息,长度是352*288/4个byte, 因此可以算出第一帧数据总长度是352*288*1.5,即152064个byte, 如果这个序列是300帧的话, 那么序列总长度即为152064*300=44550KB,这也就是为什么常见的300帧CIF序列总是44M的原因.

Y41P(和Y411)(packed格式)格式为每个像素保留Y分量,而UV分量在 水平方向上每4个像素采样一次。一个宏像素为12个字节,实际表示8个像素。图像数据中YUV分量排列顺序如下: U0 Y0 V0 Y1 U4 Y2 V4 Y3 Y4 Y5 Y6 Y8 …

IYUV格式(planar)为每个像素都提取Y分量,而在UV分量的提取时,首先将 图像分成若干个2 x 2的宏块,然后每个宏块提取一个U分量和一个V分量。YV12格式与 IYUV类似,但仍然是平面模式。


YUV411、YUV420格式多见于DV数据中。
YUV411用于NTSC制。为每个像素都提取Y分量,而UV分量在水平方向上每4个像素采样一次。
YUV420用于 PAL制。并非V分量采样为0,而是跟YUV411相比, 在水平方向上提高一倍色差采样频率,在垂直方向上以U/V间隔的方式减小一半色差采样。

各种格式的具体使用位数的需求(使用4:2:0采样,对于每个元素用8个位大小表示):

格式: Sub-QCIF 亮度分辨率: 128*96   每帧使用的位: 147456
格式: QCIF   亮度分辨率: 176*144   每帧使用的位: 304128
格式: CIF   亮度分辨率: 352*288   每帧使用的位: 1216512
格 式: 4CIF   亮度分辨率: 704*576   每帧使用的位: 4866048

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个最新的FFmpeg API将H264解码YUV的demo: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #define INBUF_SIZE 4096 int main(int argc, char **argv) { AVCodec *codec; AVCodecContext *codec_ctx = NULL; AVFrame *frame = NULL; AVPacket pkt; int ret, got_frame; uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; uint8_t *data = NULL; int data_size; FILE *yuv_file = NULL; struct SwsContext *sws_ctx = NULL; if (argc < 3) { printf("Usage: %s <input file> <output file>\n", argv[0]); return 1; } av_register_all(); avcodec_register_all(); codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Codec not found\n"); return 1; } codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { fprintf(stderr, "Could not allocate video codec context\n"); return 1; } if (avcodec_open2(codec_ctx, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); return 1; } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n"); return 1; } yuv_file = fopen(argv[2], "wb"); if (!yuv_file) { fprintf(stderr, "Could not open %s\n", argv[2]); return 1; } av_init_packet(&pkt); pkt.data = inbuf; pkt.size = sizeof(inbuf); FILE *file = fopen(argv[1], "rb"); if (!file) { fprintf(stderr, "Could not open %s\n", argv[1]); return 1; } while (1) { data_size = fread(inbuf, 1, INBUF_SIZE, file); if (data_size <= 0) { break; } data = inbuf; while (data_size > 0) { ret = avcodec_decode_video2(codec_ctx, frame, &got_frame, &pkt); if (ret < 0) { fprintf(stderr, "Error decoding video frame (%d)\n", ret); return 1; } if (got_frame) { if (!sws_ctx) { sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { fprintf(stderr, "Could not initialize the conversion context\n"); return 1; } } uint8_t *dst_data[4] = { NULL }; int dst_linesize[4] = { 0 }; av_image_alloc(dst_data, dst_linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, 16); sws_scale(sws_ctx, frame->data, frame->linesize, 0, codec_ctx->height, dst_data, dst_linesize); fwrite(dst_data[0], 1, codec_ctx->width * codec_ctx->height, yuv_file); fwrite(dst_data[1], 1, codec_ctx->width * codec_ctx->height / 4, yuv_file); fwrite(dst_data[2], 1, codec_ctx->width * codec_ctx->height / 4, yuv_file); av_freep(&dst_data[0]); av_freep(&dst_data[1]); av_freep(&dst_data[2]); } data += ret; data_size -= ret; if (pkt.size > 0 && data_size <= 0) { memcpy(inbuf, pkt.data, pkt.size); pkt.data = inbuf; ret = avcodec_decode_video2(codec_ctx, frame, &got_frame, &pkt); if (ret < 0) { fprintf(stderr, "Error decoding video frame (%d)\n", ret); return 1; } if (got_frame) { if (!sws_ctx) { sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { fprintf(stderr, "Could not initialize the conversion context\n"); return 1; } } uint8_t *dst_data[4] = { NULL }; int dst_linesize[4] = { 0 }; av_image_alloc(dst_data, dst_linesize, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, 16); sws_scale(sws_ctx, frame->data, frame->linesize, 0, codec_ctx->height, dst_data, dst_linesize); fwrite(dst_data[0], 1, codec_ctx->width * codec_ctx->height, yuv_file); fwrite(dst_data[1], 1, codec_ctx->width * codec_ctx->height / 4, yuv_file); fwrite(dst_data[2], 1, codec_ctx->width * codec_ctx->height / 4, yuv_file); av_freep(&dst_data[0]); av_freep(&dst_data[1]); av_freep(&dst_data[2]); } pkt.size = 0; } } } fclose(file); fclose(yuv_file); avcodec_free_context(&codec_ctx); av_frame_free(&frame); sws_freeContext(sws_ctx); return 0; } ``` 这个demo使用了FFmpeg的API,可以将H264解码YUV,并将输出写入到文件中。注意,这个demo只是一个示例,具体的使用方法可能需要根据你的具体需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值