在做网络视频的时候,有些视频的编程概念,早点知道,早点弄清楚会少走很多的弯路。对应视频的转码,传输,一开始如果直接跟着代码跑的话,很容易觉得自己都明白了,但是为什么这样做,好像也讲出去个一二三来。
比如:在ffmpeg中,网络视频流h264为什么默认的转为YUV而不是其他格式?
从这个问题开始,我们慢慢的把一些概念理清楚,这样一来,编程的时候就容易理解了。
什么是h264
也被称为AVC(高级视频编码),是一种视频压缩标准。这是一种高效的视频编码方法,可以在保持高质量的同时,大幅度减少所需的带宽和存储空间。H264编码的视频可以在各种设备上播放,包括电视、电脑、智能手机等。在视频处理中,H264用于压缩视频数据,使其更便于传输和存储。
什么是YUV
这是一种颜色编码系统,常用于视频系统。YUV模型定义了一个颜色空间,其中Y表示亮度(灰度),而U和V表示色度(色彩和饱和度)。这种颜色编码方式的优点是可以更有效地压缩颜色信息,因为人眼对亮度的敏感度远高于色度。在视频处理中,YUV通常用于在保留视觉质量的同时减少所需的带宽或存储。
还有一个概念那就是我们经常提到的RGB格式。
什么是RGB
RGB是一种加色模型,其中R代表红色,G代表绿色,B代表蓝色。RGB模型采用红、绿、蓝三种颜色的光以不同的比例混合,以产生其他颜色。RGB模型主要用于显示设备,如电脑屏幕、电视和手机等,因为这些设备通过发射红、绿、蓝三种颜色的光来显示图像。RGB模型的优点是可以表示大范围的颜色,并且直观易于理解。
可以这么说RGB,YUV是不同时代的产物,一开始我们在设计黑白电视机的时候,只要有灰度就能显示图片,图片知识黑白的而已,到了彩色电视以后,又引入了U和V这两个向量,颜色的问题也就兼容了。
到了LED的时代,采用红、绿、蓝三种颜色的光以不同的比例混合,以产生其他颜色,也就是RGB。
为什么H264编码通常解码为YUV格式,而不是RGB格式的
H264编码的视频通常首先解码为YUV格式,而不是RGB格式,这主要是由于以下几个原因:
-
压缩效率:YUV格式的颜色编码更适合于视频压缩。在YUV格式中,亮度信息(Y)和色度信息(UV)是分开的,这使得在压缩过程中可以对色度信息进行更大程度的压缩,因为人眼对亮度的敏感度远高于色度。这就意味着,在相同的视频质量下,YUV格式的视频数据通常比RGB格式的视频数据更小。
-
兼容性:许多视频设备和系统,包括电视和DVD播放器等,都使用YUV格式。因此,解码为YUV格式可以确保视频在这些设备和系统上的兼容性。
-
色彩空间转换:虽然H264编码的视频可以被解码为RGB格式,但这通常需要额外的色彩空间转换步骤。相比之下,直接解码为YUV格式则更为简单和高效。
因此,虽然H264编码的视频可以被解码为RGB格式,但由于压缩效率、兼容性和处理效率的考虑,通常首先解码为YUV格式。
在视频的传输中用的是yuv还是h264
在视频的传输中,通常使用的是H264格式。这是因为H264是一种高效的视频压缩标准,它可以在保持高质量的同时,大幅度减少所需的带宽和存储空间。由于其高效的压缩性能,H264已经成为网络视频和流媒体的主流编码格式。
然而,这并不意味着YUV在视频传输中没有用处。实际上,YUV是一种颜色编码系统,用于在视频处理中有效地压缩颜色信息。在视频被编码为H264格式之前,通常会先将其转换为YUV格式。
因此,虽然在视频传输中主要使用H264格式,但YUV格式在视频处理和编码的过程中仍然起着重要的作用。
利用ffmpeg,把h264转为YUV
以下是一个使用FFmpeg库的基本示例,它展示了如何打开一个H.264格式的视频文件,读取每一帧,并将其转换为YUV格式:
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
int main() {
// 注册所有的编解码器和格式
av_register_all();
// 打开视频文件
AVFormatContext* formatContext = NULL;
if (avformat_open_input(&formatContext, "a.h264", NULL, NULL) < 0) {
// 错误处理...
}
// 查找视频流
if (avformat_find_stream_info(formatContext, NULL) < 0) {
// 错误处理...
}
int videoStreamIndex = -1;
for (int i = 0; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
if (videoStreamIndex == -1) {
// 错误处理...
}
// 找到并打开解码器
AVCodec* codec = avcodec_find_decoder(formatContext->streams[videoStreamIndex]->codecpar->codec_id);
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar);
if (avcodec_open2(codecContext, codec, NULL) < 0) {
// 错误处理...
}
// 读取和解码视频帧
AVPacket packet;
AVFrame* frame = av_frame_alloc();
while (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == videoStreamIndex) {
if (avcodec_send_packet(codecContext, &packet) < 0) {
// 错误处理...
}
while (avcodec_receive_frame(codecContext, frame) == 0) {
// 此时,frame包含YUV数据
// ... 处理frame的数据 ...
}
}
av_packet_unref(&packet);
}
// 释放资源
av_frame_free(&frame);
avcodec_close(codecContext);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return 0;
}
在这个示例中,我们首先打开一个视频文件,并查找视频流。然后,我们找到并打开适当的解码器。接着,我们读取和解码视频帧,每当我们收到一个新的帧时,我们就处理它的YUV数据。最后,我们释放所有的资源。
利用SDL 把视频显示出来
在SDL2中,可以直接把YUV格式的数据显示出来,而且也比较容易,代码如下:
AVPacket packet;
AVFrame* frame = av_frame_alloc();
while (av_read_frame(pFormatCtx, &packet) >= 0) {
if (packet.stream_index == videoindex) {
if (avcodec_send_packet(codecContext, &packet) < 0) {
// 错误处理...
}
while (avcodec_receive_frame(codecContext, frame) == 0) {
// 此时,frame包含YUV数据
//开始显示
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
//Delay 1000/60ms--假设每分钟60帧
SDL_Delay(1000/60);
}
av_packet_unref(&packet);
}
}
显示的效果如下:
刚才我们说过,YUV其实可以不用UV通道,可以让他显示为黑白的视频。修改一下代码,把UV通道都赋值为128,就可以看到灰色的效果了。
代码修改如下:
memset(frame->data[1], 128, frame->linesize[1] * frame->height / 2);
memset(frame->data[2], 128, frame->linesize[2] * frame->height / 2);
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
所有的代码都已在git上。