• 在ffmpeg中,网络视频流h264为什么默认的转为YUV而不是其他格式


    在做网络视频的时候,有些视频的编程概念,早点知道,早点弄清楚会少走很多的弯路。对应视频的转码,传输,一开始如果直接跟着代码跑的话,很容易觉得自己都明白了,但是为什么这样做,好像也讲出去个一二三来。

    比如:在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格式,这主要是由于以下几个原因:

    1. 压缩效率:YUV格式的颜色编码更适合于视频压缩。在YUV格式中,亮度信息(Y)和色度信息(UV)是分开的,这使得在压缩过程中可以对色度信息进行更大程度的压缩,因为人眼对亮度的敏感度远高于色度。这就意味着,在相同的视频质量下,YUV格式的视频数据通常比RGB格式的视频数据更小。

    2. 兼容性:许多视频设备和系统,包括电视和DVD播放器等,都使用YUV格式。因此,解码为YUV格式可以确保视频在这些设备和系统上的兼容性。

    3. 色彩空间转换:虽然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 
        #include 
    }
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    在这个示例中,我们首先打开一个视频文件,并查找视频流。然后,我们找到并打开适当的解码器。接着,我们读取和解码视频帧,每当我们收到一个新的帧时,我们就处理它的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);
    		}
    		
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    显示的效果如下:
    在这里插入图片描述
    刚才我们说过,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]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    所有的代码都已在git上

  • 相关阅读:
    Unity 编辑器选择器工具类Selection 常用函数和用法
    Java基础知识面试题(2022年最新版,持续更新...)整理
    3.5、Linux:命令行git的使用
    SBF浅谈FTX扩张计划,否认收购Coinbase传闻
    实时互动还有哪些可能?RTE 2022 创新编程挑战赛正式开启
    GB/T28181协议介绍
    Linux C语言编译报错:undefined reference to `sem_init‘(编译时加 -lpthread)
    盲人咖啡厅导航:科技之光点亮独立生活新里程
    [附源码]java毕业设计餐厅卫生安全系统
    生信豆芽菜-机器学习筛选特征基因
  • 原文地址:https://blog.csdn.net/weixin_40425640/article/details/134078975