ffmpeg 视频解码h264和yuv
时间:2022-06-10
本文章向大家介绍ffmpeg 视频解码h264和yuv,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
之前学习 ffmpeg
在 android
平台上,发现很不方便,所以打算在 vs
上重新搭建环境,然后重新学习,之后如果需要用到的话在移植到其他平台。环境搭建参考的是: https://blog.csdn.net/weixinhum/article/details/37699025
环境
Microsoft Visual C++ 2017 vs2017 ffmpeg 3.4.2
步骤主要是以下几大步骤:
- 初始化
av_register_all();
avformat_network_init();
- AVFormatContext获取和初始化
//AVFormatContext获取
avformat_alloc_context()
//打开文件,和AVFormatContext关联
avformat_open_input()
//获取文件流信息
avformat_find_stream_info()
- 获取解码器
//AVCodecContext获取
avcodec_alloc_context3()
//将AVCodecParameters转换为AVCodecContext
avcodec_parameters_to_context()
//获取解码器
avcodec_find_decoder()
//打开解码器
avcodec_open2()
- 解码准备
//获取解码数据包装 AVFrame
av_frame_alloc()
//根据宽高,解码类型(yuv420)获取缓存buffer大小
av_image_get_buffer_size()
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的AVFrame里面
av_image_fill_arrays()
//获取编码数据 包装 AVPacket
av_packet_alloc()
//获取SwsContext 图片转换(宽高这些)需要用到
sws_getContext()
- 读取数据源解码存储
//读取编码数据源到AVPacket
av_read_frame()
//发送数据源
avcodec_send_packet()
//解码数据源 ,和avcodec_send_packet配合使用
avcodec_receive_frame()
//图像转换
sws_scale()
//写入文件
fwrite()
- 回收
具体代码和步骤如下代码:
#include <iostream>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
//初始化
void init() {
av_register_all();
avformat_network_init();
}
void log(const char * msg, int d=-1123) {
if (d == -1123) {
printf_s("%sn", msg);
}
else {
printf_s("%s %d n", msg ,d);
}
}
int video2YuvAndH264(const char * filePath,FILE * yuvFilePath, FILE * h264FilePath){
AVFormatContext * pFmtCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVFrame *pFrame = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *outBuffer = NULL;
AVPacket *pPacket = NULL;
SwsContext *pSwsCtx = NULL;
//1. 初始化
init();
//2. AVFormatContext获取
pFmtCtx = avformat_alloc_context();
//3. 打开文件
if (avformat_open_input(&pFmtCtx,filePath,NULL,NULL)!=0) {
log("Couldn't open input stream.n");
return -1;
}
//4. 获取文件信息
if (avformat_find_stream_info(pFmtCtx,NULL)<0) {
log("Couldn't find stream information.");
return -1;
}
//5. 获取视频的index
int i = 0,videoIndex =-1;
for (; i < pFmtCtx->nb_streams; i++) {
if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
break;
}
}
if (videoIndex == -1) {
log("Didn't find a video stream.");
return -1;
}
//6. 获取解码器并打开
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, pFmtCtx->streams[videoIndex]->codecpar) < 0) {
log("Didn't parameters to contex.");
return -1;
}
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
log("Codec not found.");
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打开解码器
log("Could not open codec.");
return -1;
}
//7. 解码开始准备工作
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
//根据需要解码的类型,获取需要的buffer,不要忘记free
outBuffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)*sizeof(uint8_t));
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的pFrameYUV里面
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, outBuffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);
pPacket = av_packet_alloc();
log("--------------- File Information ----------------");
av_dump_format(pFmtCtx, 0, filePath, 0);
log("-------------------------------------------------");
//获取SwsContext
pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, NULL, NULL, NULL, NULL);
int count=0;
//8. 读取数据
while (av_read_frame(pFmtCtx, pPacket) == 0) {//读取一帧压缩数据
if (pPacket->stream_index == videoIndex) {
//写入H264数据到文件
fwrite(pPacket->data, 1, pPacket->size, h264FilePath); //把H264数据写入h264FilePath文件
//解码数据
//avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
if (avcodec_send_packet(pCodecCtx, pPacket) != 0) {//解码一帧压缩数据
log("Decode end or Error.n");
break;
}else {//处理解码数据并写入文件
avcodec_receive_frame(pCodecCtx,pFrame);
if (sws_scale(pSwsCtx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize) == 0) {
continue;
}
count++;
int y_size = pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0], 1, y_size, yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, y_size / 4, yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, y_size / 4, yuvFilePath); //V
log("Succeed to decode frame!",count);
}
}
av_packet_unref(pPacket);
}
//flush decoder
/*当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。
因此需要通过“flush_decoder”将这几帧数据输出。
“flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递AVPacket。*/
while (1) {
if (avcodec_send_packet(pCodecCtx, pPacket)!= 0) {
log("Decode end or Error.n");
break;
}
else {
avcodec_receive_frame(pCodecCtx, pFrame);
sws_scale(pSwsCtx, (const unsigned char *const *)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
int y_size = pCodecCtx->width * pCodecCtx->height;
// yuv-> 4:1:1
fwrite(pFrameYUV->data[0], 1, static_cast<size_t>(y_size), yuvFilePath); //Y
fwrite(pFrameYUV->data[1], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //U
fwrite(pFrameYUV->data[2], 1, static_cast<size_t>(y_size / 4), yuvFilePath); //V
log("Flush Decoder: Succeed to decode frame!", count);
}
}
if (pSwsCtx != NULL) {
sws_freeContext(pSwsCtx);
}
if (outBuffer != NULL) {
av_free(outBuffer);
}
if (pFrameYUV != NULL) {
av_frame_free(&pFrameYUV);
}
if (pFrame != NULL) {
av_frame_free(&pFrame);
}
if (pCodecCtx != NULL) {
avcodec_close(pCodecCtx);
}
if (pFmtCtx != NULL) {
avformat_close_input(&pFmtCtx);
}
}
int main()
{
FILE * yuvFile;
FILE * h264File;
fopen_s(& yuvFile,"F:/视频资源/gxsp.yuv", "wb+");
fopen_s(& h264File,"F:/视频资源/gxsp.h264", "wb+");
video2YuvAndH264("F:/视频资源/gxsp.mp4", yuvFile, h264File);
fclose(yuvFile);
fclose(h264File);
getchar();
return 0;
}
参考链接: https://blog.csdn.net/king1425/article/details/71160339
- jquery easyui datagrid mvc server端分页排序筛选的实现
- 左手用R右手Python系列——使用多进程进行任务处理
- 2017.9.17校内noip模拟赛解题报告
- MySQL基础入门——MySQL与R语言、Python交互
- BizTalk Orchestration execute Flat file disassembler ReceivePipeline
- MySQL基础入门系列之——字符与日期数据处理
- P2038 无线网络发射器选址
- ggplot2双坐标轴的解决方案
- Modifying namespace in XML document programmatically
- ggplot2学习笔记——图例系统及其调整函数
- 一款脑洞大开的表格可视化神器
- P3908 异或之和
- P1939 【模板】矩阵加速(数列)
- R语言学习笔记之——数据处理神器data.table
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法