当前位置: 首页 > news >正文

FFmpeg --14-视频解码:h264解码为yuv

文章目录

      • 视频解码过程
      • FFmpeg关键函数
      • 关键数据结构
      • API使用流程
      • 函数详解
      • 实用命令
      • 完整代码

视频解码过程

视频解码流程如下:

[输入视频文件] → [解复用] → [编码数据包] → [解码] → [原始帧] → [输出YUV/RGB]

解码后通常输出420p格式的YUV数据。

FFmpeg关键函数

解码相关函数

  • avcodec_find_decoder:通过AVCodecID查找解码器
  • av_parser_init:初始化AVCodecParserContext
  • avcodec_alloc_context3:分配AVCodecContext内存
  • avcodec_open2:打开解码器
  • av_parser_parse2:解析获取Packet
  • avcodec_send_packet:向解码器发送压缩数据包
  • avcodec_receive_frame:接收解码后的帧数据
  • av_get_bytes_per_sample:获取采样字节数

关键数据结构

AVCodecParser结构体
解析数据流并分割为帧级压缩数据,示例(H264解析器):

AVCodecParser ff_h264_parser = {.codec_ids      = { AV_CODEC_ID_H264 },.priv_data_size = sizeof(H264ParseContext),.parser_init    = init,.parser_parse   = h264_parse,.parser_close   = h264_close,.split          = h264_split,
};

从AVCodecParser结构的实例化我们可以看出来,不同编码类型的parser是和CODE_ID进行绑定的。所以也就可以解释:

parser = av_parser_init(AV_CODEC_ID_H264);

可以通过CODE_ID查找到对应的码流 parser。

API使用流程

编解码API分组

  • 解码:avcodec_send_packet() + avcodec_receive_frame()
  • 编码:avcodec_send_frame() + avcodec_receive_packet()

标准流程

  1. 初始化并打开AVCodecContext
  2. 输入数据:
    • 解码:调用avcodec_send_packet()发送AVPacket
    • 编码:调用avcodec_send_frame()发送AVFrame
  3. 循环接收输出数据直至返回AVERROR(EAGAIN)
  4. 流结束时调用NULL参数刷新缓冲
  5. 重新启用编解码器需调用avcodec_flush_buffers()

函数详解

avcodec_send_packet

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  • 输入包需满足缓冲区大小 > AV_INPUT_PADDING_SIZE
  • 返回值:
    • 0:成功
    • AVERROR(EAGAIN):需先接收帧
    • AVERROR_EOF:解码器已刷新

avcodec_receive_frame

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
  • 返回值:
    • 0:成功获取帧
    • AVERROR(EAGAIN):需发送新数据包
    • AVERROR_EOF:无更多输出帧

实用命令

提取裸流
提取H264:

ffmpeg -i input.flv -vcodec libx264 -an -f h264 output.h264

提取MPEG2:

ffmpeg -i input.flv -vcodec mpeg2video -an -f mpeg2video output.mpeg2v

播放YUV

ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 input.yuv

查找格式
查找视频解复用器:

ffmpeg -formats | findstr /i video

完整代码

/**
* @projectName   07-05-decode_audio
* @brief         解码音频,主要的测试格式aac和mp3
* @author        Liao Qingfu
* @date          2020-01-16
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavutil/frame.h>
#include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFILL_THRESH 4096static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}static void print_video_format(const AVFrame *frame)
{printf("width: %u\n", frame->width);printf("height: %u\n", frame->height);printf("format: %u\n", frame->format);// 格式需要注意
}static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,FILE *outfile)
{int ret;/* send the packet with the compressed data to the decoder */ret = avcodec_send_packet(dec_ctx, pkt);if(ret == AVERROR(EAGAIN)){fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");}else if (ret < 0){fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",av_get_err(ret), pkt->size);return;}/* read all the output frames (infile general there may be any number of them */while (ret >= 0){// 对于frame, avcodec_receive_frame内部每次都先调用ret = avcodec_receive_frame(dec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0){fprintf(stderr, "Error during decoding\n");exit(1);}static int s_print_format = 0;if(s_print_format == 0){s_print_format = 1;print_video_format(frame);}// 一般H264默认为 AV_PIX_FMT_YUV420P, 具体怎么强制转为 AV_PIX_FMT_YUV420P 在音视频合成输出的时候讲解// frame->linesize[1]  对齐的问题// 正确写法  linesize[]代表每行的字节数量,所以每行的偏移是linesize[]for(int j=0; j<frame->height; j++)fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, outfile);for(int j=0; j<frame->height/2; j++)fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, outfile);// 错误写法 用source.200kbps.766x322_10s.h264测试时可以看出该种方法是错误的//  写入y分量
//        fwrite(frame->data[0], 1, frame->width * frame->height,  outfile);//Y
//        // 写入u分量
//        fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:宽高均是Y的一半
//        //  写入v分量
//        fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:宽高均是Y的一半}
}
// 注册测试的时候不同分辨率的问题
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25  source.200kbps.768x320_10s.yuv
int main(int argc, char **argv)
{const char *outfilename;const char *filename;const AVCodec *codec;AVCodecContext *codec_ctx= NULL;AVCodecParserContext *parser = NULL;int len = 0;int ret = 0;FILE *infile = NULL;FILE *outfile = NULL;// AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];uint8_t *data = NULL;size_t   data_size = 0;AVPacket *pkt = NULL;AVFrame *decoded_frame = NULL;if (argc <= 2){fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename    = argv[1];outfilename = argv[2];pkt = av_packet_alloc();enum AVCodecID video_codec_id = AV_CODEC_ID_H264;if(strstr(filename, "264") != NULL){video_codec_id = AV_CODEC_ID_H264;}else if(strstr(filename, "mpeg2") != NULL){video_codec_id = AV_CODEC_ID_MPEG2VIDEO;}else{printf("default codec id:%d\n", video_codec_id);}// 查找解码器codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}// 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)parser = av_parser_init(codec->id);if (!parser) {fprintf(stderr, "Parser not found\n");exit(1);}// 分配codec上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate audio codec context\n");exit(1);}// 将解码器和解码器上下文进行关联if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}// 打开输入文件infile = fopen(filename, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}// 打开输出文件outfile = fopen(outfilename, "wb");if (!outfile) {av_free(codec_ctx);exit(1);}// 读取文件进行解码data      = inbuf;data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);while (data_size > 0){if (!decoded_frame){if (!(decoded_frame = av_frame_alloc())){fprintf(stderr, "Could not allocate audio frame\n");exit(1);}}ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,data, data_size,AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);if (ret < 0){fprintf(stderr, "Error while parsing\n");exit(1);}data      += ret;   // 跳过已经解析的数据data_size -= ret;   // 对应的缓存大小也做相应减小if (pkt->size)decode(codec_ctx, pkt, decoded_frame, outfile);if (data_size < VIDEO_REFILL_THRESH)    // 如果数据少了则再次读取{memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置data = inbuf;// 读取数据 长度: VIDEO_INBUF_SIZE - data_sizelen = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);if (len > 0)data_size += len;}}/* 冲刷解码器 */pkt->data = NULL;   // 让其进入drain modepkt->size = 0;decode(codec_ctx, pkt, decoded_frame, outfile);fclose(outfile);fclose(infile);avcodec_free_context(&codec_ctx);av_parser_close(parser);av_frame_free(&decoded_frame);av_packet_free(&pkt);printf("main finish, please enter Enter and exit\n");return 0;
}
http://www.dtcms.com/a/516343.html

相关文章:

  • PixelShuffle原理
  • 昆明做网站价格网站屏蔽省份
  • 创建网站需要学什么知识2017民非单位年检那个网站做
  • LABVIEW依赖关系显示文件删除、移动或重命名,每次打开都要指定很多路径【解决方案】
  • 东莞网站建设seo浙江住房和城乡建设厅网站首页
  • MLOps 的CI/CD VS DevOps 的CI/CD
  • spark组件-spark sql-读取数据
  • 网站开发大致需要哪些步骤可视化开发工具推荐
  • zabbix实现配置监控Windows设备、SNMP协议设备的全流程实操教程
  • 天津做网站找哪家公司好建设网站公司哪里好相关的热搜问题解决方案
  • 友情链接价格seo官网制作规划
  • 桦甸市城乡建设局网站技术外包网站
  • 英文网站设计网络广告策划方案怎么做
  • go前后端项目的启动 、打包和部署
  • redis三主三从集群升级6.2.20, 保留数据
  • 导入部署天机AI助手智能体的全流程(详细图解,包含导入虚拟机后无法ping通百度的解决办法)
  • 物联网运维中的容器化服务部署与弹性扩展技术
  • cms建站程序免费个人网站建站能上传视频吗
  • 「用Python来学微积分」8. 极限的概念
  • GJOI 10.17/10.18 题解
  • CAN总线的物联网桥梁:以太网网关如何赋能工业4.0
  • C语言需要掌握的基础知识点之递归
  • 建设网站学什么wordpress zip格式
  • RFSoC在射频阵列信号采集分析中的应用
  • [Agent可视化] 会话管理 | Redis缓存 | PostgreSQL持久化 | 智能上下文处理
  • [Agent可视化] 编排工作流(Go) | Temporal引擎 | DAG调度器 | ReAct模式实现
  • 自定义时间服务器主机的时间通过ntp.aliyun.com主机同步时间
  • 做移动端网站设计做交通事故的网站
  • 【论文精读】EvalCrafter:文本到视频生成模型的全面评测框架
  • 普林尼与LLM提示词注入:AI安全防线的隐秘挑战