用FFmpeg把H264数据流解码成YUV420P
Posted 音视频开发老舅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用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_里面会变成实际视频的分辨率。
ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)将YUV图片序列作为流读入,编码封装成x264 MP4视频
参考文章:【FFmpeg编码实战】(1)将YUV420P图片集编码成H.264视频文件
文章目录
第二个项目:将YUV图片序列作为流读入,编码封装成x264 MP4视频
将YUV图片序列编码成.h264文件
直接把博主的代码拷到我们继承的上一个项目中,感觉应该有一些地方需要修改,果不其然
#pragma warning(disable : 4996)
#include <iostream>
#include <string> // 字符串操作
#include <direct.h> // 文件夹
extern "C" // ffmpeg 相关头文件
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include "libavutil/imgutils.h"
using namespace std;
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "avdevice.lib")
// C++ 中 ffmpeg有些地方报错,修改这样后 OK
//
static char* av_ts_make_string1(char* buf, int64_t ts)
if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS");
else snprintf(buf, AV_TS_MAX_STRING_SIZE, "%" PRId64, ts);
return buf;
static char* av_ts_make_time_string1(char* buf, int64_t ts, AVRational* tb)
if (ts == AV_NOPTS_VALUE) snprintf(buf, AV_TS_MAX_STRING_SIZE, "NOPTS");
else snprintf(buf, AV_TS_MAX_STRING_SIZE, "%.6g", av_q2d(*tb) * ts);
return buf;
//
/// <summary>
/// 输入输出文件信息
/// </summary>
const char* Out_File_Name = "video.h264";
// 源文件名:video/video_out.yuv420p.0.yuv
const char* Folder = "video";
const char* YUV_File_Name = "video_out.yuv420p";
const char* YUV = "yuv"; // 图片文件后缀
#define YUV_Width 1050 // 宽
#define YUV_Height 540 // 高
#define Stream_Frame_Rate 25 // 帧率:每秒25帧
// 参考:doc\\examples\\encode_video.c、doc\\examples\\muxing.c
int main(int* argc, char* argv[])
// 1. 初始化视频文件格式上下文
AVFormatContext* p_FormatCtx = NULL;
avformat_alloc_output_context2(&p_FormatCtx, NULL, "h264", Out_File_Name);
if (!p_FormatCtx)
cout << "无法从输出文件后缀判断视频格式,默认使用h264" << endl;
avformat_alloc_output_context2(&p_FormatCtx, NULL, "h264", Out_File_Name);
// 2. 获得输出格式 Format信息
AVOutputFormat* p_OutputFmt = NULL;
p_OutputFmt = p_FormatCtx->oformat;
bool have_video = false, have_audio = false, encode_video = false, encode_audio = false;
AVCodec* p_Video_Enc = NULL, * p_Vudio_Enc = NULL;
AVCodecContext* p_Video_Enc_Ctx = NULL, * p_Audio_Enc_Ctx = NULL;
AVStream* p_Video_st = NULL, * p_Audio_st = NULL;
AVFrame* p_Video_Frame = NULL;
// 3. 添加 视频输出流,查找并打开视频解码器
if (p_OutputFmt->video_codec != AV_CODEC_ID_NONE)
// 3.1 找到视频解码器
p_Video_Enc = avcodec_find_encoder(p_OutputFmt->video_codec);
// 3.2 创建视频流
p_Video_st = avformat_new_stream(p_FormatCtx, p_Video_Enc);
p_Video_st->id = p_FormatCtx->nb_streams - 1;
// 3.3 创建编码器上下文
p_Video_Enc_Ctx = avcodec_alloc_context3(p_Video_Enc);
// 3.4 配置视频格式
p_Video_Enc_Ctx->codec_type = AVMediaType::AVMEDIA_TYPE_VIDEO;
p_Video_Enc_Ctx->codec_id = p_OutputFmt->video_codec; // Code id
p_Video_Enc_Ctx->pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P; // 图片格式
p_Video_Enc_Ctx->bit_rate = 2000000; // 码率越高,画面质量越好,相应视频越大 // 采样器码率
p_Video_Enc_Ctx->width = YUV_Width; // 宽
p_Video_Enc_Ctx->height = YUV_Height; // 高
p_Video_st->time_base.num = 1; // 音位 1s
p_Video_st->time_base.den = 25; // 帧率,每秒25帧
p_Video_Enc_Ctx->framerate.num = 25;
p_Video_Enc_Ctx->framerate.den = 1;
p_Video_Enc_Ctx->time_base = p_Video_st->time_base;
p_Video_Enc_Ctx->gop_size = 12; // 连续画面组大小
if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_H264)
p_Video_Enc_Ctx->qmin = 10; // 最小的量化因子
p_Video_Enc_Ctx->qmax = 51; // 最大的量化因子
p_Video_Enc_Ctx->qcompress = 0.6;
if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
p_Video_Enc_Ctx->max_b_frames = 2;
if (p_Video_Enc_Ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
p_Video_Enc_Ctx->mb_decision = 2;
if (p_OutputFmt->flags & AVFMT_GLOBALHEADER)
p_Video_Enc_Ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// 3.5 打开编码器
avcodec_open2(p_Video_Enc_Ctx, p_Video_Enc, NULL);
avcodec_parameters_from_context(p_Video_st->codecpar, p_Video_Enc_Ctx);
// 3.6 分配视频帧内存
p_Video_Frame = av_frame_alloc();
p_Video_Frame->format = p_Video_Enc_Ctx->pix_fmt;
p_Video_Frame->width = p_Video_Enc_Ctx->width;
p_Video_Frame->height = p_Video_Enc_Ctx->height;
av_frame_get_buffer(p_Video_Frame, 0);
have_video = true;
encode_video = true;
// 预留
// 4. 添加 音频输出流,查找并打开音频解码器
if (p_OutputFmt->audio_codec != AV_CODEC_ID_NONE)
have_audio = true;
encode_video = true;
// 5. 打印输出信息
av_dump_format(p_FormatCtx, 0, Out_File_Name, 1);
// 6. 打开输出文件
if (!(p_OutputFmt->flags & AVFMT_NOFILE))
avio_open(&p_FormatCtx->pb, Out_File_Name, AVIO_FLAG_WRITE);
// 7. 写头信息
avformat_write_header(p_FormatCtx, NULL);
char pic_file_name[50] = "";
int pic_index = 0;
const int pic_size = YUV_Height * YUV_Width * 3 / 2;
FILE* pic_file = NULL;
uint8_t* pic_buff = (uint8_t*)av_malloc(pic_size);
int file_end = 0;
int ret;
char errbuf[AV_ERROR_MAX_STRING_SIZE] = 0 ;
int y, x;
// 8. 开始循环写入数据
while (file_end == 0)
// 打开文件 获取数据
memset(pic_file_name, '\\0', 50);
sprintf_s(pic_file_name, 50, "%s/%s.%d.%s", Folder, YUV_File_Name, pic_index, YUV);
cout << "打开文件:" << pic_file_name << endl;
// 注意,此处一定要,使用 "rb+" 否则一次读取的数据大小远小于预期的大小
pic_file = fopen(pic_file_name, "rb+"); // fopen(&pic_file, pic_file_name, "rb+")
if (pic_file == NULL)
cout << "打开失败:" << pic_file_name << ", 开始刷新编码缓冲区\\n";
file_end = 1;
if (file_end == 0)
//ret = fread_s(pic_buff, pic_size, 1, pic_size, pic_file);
ret = fread(pic_buff, 1, pic_size, pic_file);
cout << "读取文件大小为:" << ret << " 应读大小:" << pic_size << endl;
if (ret <= 0)
cout << "读取内容失败,ret=" << ret << endl;
break;
fclose(pic_file);
pic_file = NULL;
/* make sure the frame data is writable */
ret = av_frame_make_writable(p_Video_Frame);
if (ret < 0)
cout << "p_Video_Frame data 不可写,强制退出\\n";
exit(1);
for (x = 0, y = 0; x < YUV_Height; x++)
memcpy(p_Video_Frame->data[0] + x * p_Video_Frame->linesize[0], pic_buff + y, YUV_Width);
y += YUV_Width;
for (x = 0, y = 0; x < YUV_Height / 2; x++)
memcpy(p_Video_Frame->data[1] + x * p_Video_Frame->linesize[1], pic_buff + y + YUV_Width * YUV_Height, YUV_Width / 2);
y += YUV_Width / 2;
for (x = 0, y = 0; x < YUV_Height / 2; x++)
memcpy(p_Video_Frame->data[2] + x * p_Video_Frame->linesize[2], pic_buff + y + YUV_Width * YUV_Height * 5 / 4, YUV_Width / 2);
y += YUV_Width / 2;
//赋值
///* prepare a dummy image */
///* Y */
//for (y = 0; y < p_Video_Enc_Ctx->height; y++)
// for (x = 0; x < p_Video_Frame->linesize[0]; x++)
// if (x < YUV_Width)
// p_Video_Frame->data[0][y * p_Video_Frame->linesize[0] + x] = pic_buff[y * YUV_Height + x]; //x + y + pic_index * 3
// else
// p_Video_Frame->data[0][y * p_Video_Frame->linesize[0] + x] = 128;
//
//
//
///* Cb and Cr */
//for (y = 0; y < p_Video_Enc_Ctx->height / 2; y++)
// for (x = 0; x < p_Video_Frame->linesize[1]; x++)
// if (x < YUV_Width / 2)
// p_Video_Frame->data[1][y * p_Video_Frame->linesize[1] + x] = pic_buff[y * YUV_Height/2 + x + YUV_Height * YUV_Width]; //128 + y + pic_index * 2;
// p_Video_Frame->data[2][y * p_Video_Frame->linesize[2] + x] = pic_buff[y * YUV_Height/2 + x + YUV_Height * YUV_Width * 5 / 4]; // 64 + x + pic_index * 5;
//
// else
// p_Video_Frame->data[1][y * p_Video_Frame->linesize[1] + x] = 128;
// p_Video_Frame->data[2][y * p_Video_Frame->linesize[2] + x] = 128;
//
//
//
//
//cout << " linesize: " << p_Video_Frame->linesize[0] << " " << p_Video_Frame->linesize[1] << " " << p_Video_Frame->linesize[2] << " " << p_Video_Frame->linesize[3] << endl;
// 开始编码
// PTS: 设置播放时间
p_Video_Frame->pts = pic_index * (p_Video_st->time_base.den) / (p_Video_st->time_base.num * 25);
// Encode 开始编码
if (file_end == 0)
ret = avcodec_send_frame(p_Video_Enc_Ctx, p_Video_Frame);
else
ret = avcodec_send_frame(p_Video_Enc_Ctx, NULL);
if (ret < 0)
cout << "编码失败:" << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
exit(1);
// 接收编码后的数据
while (ret >= 0)
AVPacket pkt = 0 ;
ret = avcodec_receive_packet(p_Video_Enc_Ctx, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
cout << "编码失败:" << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
break;
av_packet_rescale_ts(&pkt, p_Video_Enc_Ctx->time_base, p_Video_st->time_base);
pkt.stream_index = p_Video_st->index;
AVRational* time_base = &p_FormatCtx->streams[pkt.stream_index]->time_base;
cout << "pts:" << av_ts_make_string1(errbuf, pkt.pts) << " pts_time:" << av_ts_make_time_string1(errbuf, pkt.pts, time_base);
cout << " dts:" << av_ts_make_string1(errbuf, pkt.dts) << " duration:" << av_ts_make_string1(errbuf, pkt.duration);
cout << " duration_time:" << av_ts_make_time_string1(errbuf, pkt.duration, time_base) << " stream_index:" << pkt.stream_index << endl;
// 将编码后的数据写入文件中
ret = av_interleaved_write_frame(p_FormatCtx, &pkt);
av_packet_unref(&pkt);
if (ret < 0)
cout << "Error while writing output packet: " << av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE) << endl;
break;
pic_index++;
// 9. 写入尾信息
av_write_trailer(p_FormatCtx);
// 10.关闭文件
if (!(p_FormatCtx->flags & AVFMT_NOFILE))
avio_closep(&p_FormatCtx->pb);
// 11. 释放资源
if (have_video)
if (pic_buff)
av_free(pic_buff);
if (p_Video_Frame)
av_frame_free(&p_Video_Frame);
if (p_Video_Enc_Ctx)
avcodec_free_context(&p_Video_Enc_Ctx); // 释放视频解码器
if (p_FormatCtx)
avformat_free_context(p_FormatCtx);
system("ffplay video.h264");
return 0;
跑完后,项目根目录多了个video.h264文件
用VLC播放器能打开浏览
将YUV图片序列编码成mp4文件
参考文章:ffmpeg:将YUV原始数据编码封装为mp4格式
还是老样子,把博主代码搞下来,vs配置一下,跑
#pragma warning(disable : 4996)
// 2-muxing编码视频.cpp
#include <iostream>
#include <string> // 字符串操作
#include <direct.h> // 文件夹
#include <stdbool.h>
extern "C" // ffmpeg 相关头文件
#include "libavcodec/avcodec.h"
#include <libavutil/opt.h>
#include "libavutil/imgutils.h"
using namespace std;
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "avdevice.lib")
/// 输入输出文件信息
//const ch以上是关于用FFmpeg把H264数据流解码成YUV420P的主要内容,如果未能解决你的问题,请参考以下文章
ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片
ffmpeg视频编解码 demo初探(包含下载指定windows版本ffmpeg)分离视频文件中的视频流每一帧YUV图片
2023-02-25:请用go语言调用ffmpeg,解码mp4文件并保存为YUV420SP格式文件,YUV420P不要转换成YUV420SP。
2023-02-24:请用go语言调用ffmpeg,解码mp4文件并保存为YUV420SP格式文件,采用YUV420P转YUV420SP的方式。