在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 <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上

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的C语言示例,它演示了如何使用ffmpeg4.0以上版本的FIFO和解码器,将H264视频流解码为YUV格式: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> #define INBUF_SIZE 4096 int main(int argc, char *argv[]) { AVFormatContext *fmt_ctx = NULL; AVCodecContext *dec_ctx = NULL; AVCodec *dec = NULL; AVPacket pkt; AVFrame *frame = NULL; AVFrame *frame_yuv = NULL; uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; int inbuf_size; uint8_t *data[4]; int linesize[4]; int ret, i, j; if (argc <= 1) { printf("Usage: %s <input file>\n", argv[0]); return 0; } av_register_all(); avformat_network_init(); avcodec_register_all(); if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) < 0) { printf("Cannot open input file.\n"); return -1; } if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { printf("Cannot find stream information.\n"); return -1; } int video_stream_index = -1; for (i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { printf("Cannot find video stream.\n"); return -1; } dec = avcodec_find_decoder(fmt_ctx->streams[video_stream_index]->codecpar->codec_id); if (!dec) { printf("Failed to find decoder.\n"); return -1; } dec_ctx = avcodec_alloc_context3(dec); if (!dec_ctx) { printf("Failed to allocate codec context.\n"); return -1; } if (avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_stream_index]->codecpar) < 0) { printf("Failed to copy codec parameters to context.\n"); return -1; } if (avcodec_open2(dec_ctx, dec, NULL) < 0) { printf("Failed to open codec.\n"); return -1; } frame = av_frame_alloc(); if (!frame) { printf("Failed to allocate frame.\n"); return -1; } frame_yuv = av_frame_alloc(); if (!frame_yuv) { printf("Failed to allocate YUV frame.\n"); return -1; } int numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, dec_ctx->width, dec_ctx->height, 1); uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(frame_yuv->data, frame_yuv->linesize, buffer, AV_PIX_FMT_YUV420P, dec_ctx->width, dec_ctx->height, 1); struct SwsContext *sws_ctx = sws_getContext(dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, dec_ctx->width, dec_ctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); if (!sws_ctx) { printf("Failed to create SwsContext.\n"); return -1; } av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; while (av_read_frame(fmt_ctx, &pkt) >= 0) { if (pkt.stream_index == video_stream_index) { ret = avcodec_send_packet(dec_ctx, &pkt); if (ret < 0) { printf("Error sending packet to decoder.\n"); break; } while (ret >= 0) { ret = avcodec_receive_frame(dec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { printf("Error receiving frame from decoder.\n"); goto end; } sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0, dec_ctx->height, frame_yuv->data, frame_yuv->linesize); printf("Decoded frame %d (%d bytes).\n", dec_ctx->frame_number, ret); } } av_packet_unref(&pkt); } end: avcodec_free_context(&dec_ctx); avformat_close_input(&fmt_ctx); avformat_free_context(fmt_ctx); av_frame_free(&frame); av_frame_free(&frame_yuv); sws_freeContext(sws_ctx); av_free(buffer); return 0; } ``` 这个示例程序使用了FIFO和解码器来读取H264视频流并将其解码为YUV格式。它首先打开输入文件并查找视频流,然后为解码器分配上下文并打开它。然后,它创建一个AVFrame结构体来存储解码的帧,并创建一个AVFrame结构体来存储YUV格式的帧。它还分配了一个缓冲区来存储YUV帧的像素数据。 接下来,它创建一个SwsContext结构体,用于执行YUV格式转换。然后,它循环读取视频流的数据包并将其发送到解码器。每当解码器成功解码一帧时,它将使用SwsContext将帧转换为YUV格式,并将其输出到控制台。 最后,它清理并释放分配的内存,并关闭输入文件。 请注意,此示例程序仅用于演示如何使用ffmpeg4.0以上版本的FIFO和解码器,实际应用可能需要添加更多的错误处理和异常情况处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go2coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值