用FFmpeg把H264数据流解码成YUV420P

在FFmpeg中,H264在编码前必须要转换成YUV420P,本文就分享一下怎么将h264转成YUV420P。

以下就是yuv420:

八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7]

注意:码流12字节个代表8个像素

理解需要画矩阵,如下:

码流数据:(4:2:0 ~ 4:0:2)

Y0 U0
Y1
Y2 U2
Y3
 
Y5     V5
Y6
Y7	   V7
Y8

映射像素:

Y0 U0 V5
Y1 U0 V5
Y2 U2 V7
Y3 U2 V7
 
Y5 U0 V5
Y6 U0 V5
Y7 U2 V7
Y8 U2 V7

YUV 4:2:0采样,每四个Y共用一组UV分量。

所以要把H264解码YUV420。首先需要把ffmpeg初始化:

代码如下:

//下面初始化h264解码库  
avcodec_init();  
av_register_all();  
  
AVFrame *pFrame_ = NULL;  
  
AVCodecContext *codec_ = avcodec_alloc_context();  
  
/* find the video encoder */  
AVCodec *videoCodec = avcodec_find_decoder(CODEC_ID_H264);  
  
if (!videoCodec)   
{  
    cout << "codec not found!" << endl;  
    return -1;  
}  
  
//初始化参数,下面的参数应该由具体的业务决定  
codec_->time_base.num = 1;  
codec_->frame_number = 1; //每包一个视频帧  
codec_->codec_type = AVMEDIA_TYPE_VIDEO;  
codec_->bit_rate = 0;  
codec_->time_base.den = 30;//帧率  
codec_->width = 1280;//视频宽  
codec_->height = 720;//视频高  
  
if(avcodec_open(codec_, videoCodec) >= 0)  
    pFrame_ = avcodec_alloc_frame();// Allocate video frame  
else  
    return -1;  

初始化完成,然后就需要把h264帧传进去进行解码出YUV420:
代码如下:

AVPacket pAvPacket = { 0 };
	decoderObj.mVideoFrame420->pict_type = picType;
	pAvPacket.data = buf;
	pAvPacket.size = size;
 
	int res = 0;
	int gotPic = 0;
	res = avcodec_decode_video2(decoderObj.pVideoCodecCtx, decoderObj.mVideoFrame420, &gotPic, &pAvPacket);
	if (!gotPic) return -9;
 
	decoderObj.pSws_ctx = sws_getContext(decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
		decoderObj.pVideoCodecCtx->pix_fmt, decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
		AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
	sws_scale(decoderObj.pSws_ctx, decoderObj.mVideoFrame420->data, decoderObj.mVideoFrame420->linesize, 0,
		decoderObj.mVideoFrame420->height, decoderObj.pYuvFrame.data, decoderObj.pYuvFrame.linesize);

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

拿到的decoderObj.pYuvFrame.data[0]就是YUV420数据。
最后也不要忘记释放内存。
代码如下:

if (NULL != decoderObj.mVideoFrame420)
	{
		av_frame_free(&decoderObj.mVideoFrame420);
		decoderObj.mVideoFrame420 = NULL;
	}
	if (NULL != decoderObj.pVideoCodecCtx)
	{
		avcodec_close(decoderObj.pVideoCodecCtx);
		if (NULL != decoderObj.pVideoCodecCtx->priv_data)	free(decoderObj.pVideoCodecCtx->priv_data);
		if (NULL != decoderObj.pVideoCodecCtx->extradata)	free(decoderObj.pVideoCodecCtx->extradata);
		avcodec_free_context(&decoderObj.pVideoCodecCtx);
		decoderObj.pVideoCodecCtx = NULL;
	}
	if (NULL != &decoderObj.pYuvFrame)
	{
		avpicture_free(&decoderObj.pYuvFrame);
		//decoderObj.pYuvFrame = NULL;
	}
	if (NULL != decoderObj.pSws_ctx)
	{
		sws_freeContext(decoderObj.pSws_ctx);
		decoderObj.pSws_ctx = NULL;
	}
	if (NULL != decoderObj.pVideoCodec)
	{
		decoderObj.pVideoCodec = NULL;
	}
	if (NULL != decoderObj.pBuffYuv420)
	{
		av_free(decoderObj.pBuffYuv420);
		decoderObj.pBuffYuv420 = NULL;
	}
	if (decoderObj.pSws_ctx) {
		sws_freeContext(decoderObj.pSws_ctx);
		decoderObj.pSws_ctx = NULL;
	}

 最终效果:使用ffplay指令播放yuv一帧数据

ffplay -i -video_size 700*700 $FILE
PS:avcodec_decode_video2这个函数会修改codec_里面的参数的,也就是说如果原来里面填的分别率是1280X720,运行avcodec_decode_video2后codec_里面会变成实际视频的分辨率。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值