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

完整教程:FFmepg--25-h265解码yuv格式

文章目录

    • 程序介绍
      • 头文件引入和宏定义
      • 错误处理函数
      • 打印视频格式函数
      • 打印H.264和H.265的NALU类型函数
      • 解码函数
      • 主函数
      • 注意事项
    • 提问
    • H.264 vs H.265 的主要不同?
    • 为什么在写入YUV420数据时,使用整体写入(一次fwrite)的方式是错误的,而使用逐行写入(循环fwrite)的方式是正确的?
    • code

程序介绍

使用FFmpeg库解码视频文件(H.264/H.265/MPEG2)并输出YUV原始数据的程序

头文件引入和宏定义

引入了必要的标准库和FFmpeg库头文件。
定义了两个宏:

错误处理函数

av_get_err:将FFmpeg返回的错误码转换为错误信息字符串。

打印视频格式函数

print_video_format:打印AVFrame的宽度、高度和格式。

打印H.264和H.265的NALU类型函数

print_h264_nal_unit_typeprint_h265_nal_unit_type:分别用于打印H.264和H.265码流中的NALU类型和位置。

解码函数

decode:发送压缩数据包(AVPacket)到解码器,接收解码后的帧(AVFrame),并将YUV数据写入文件。

主函数

  • 参数检查:要求输入文件和输出文件。
  • 根据输入文件名的后缀判断视频编码格式(H.264、H.265或MPEG2)。
  • 查找对应的解码器。
  • 初始化解析器(用于解析裸流)。
  • 分配解码器上下文并打开解码器。
  • 打开输入文件和输出文件。
  • 分配输入缓冲区,并读取初始数据。
  • 循环读取输入文件,使用解析器解析出AVPacket,调用解码函数解码。
  • 在解析过程中,打印NALU的类型和位置(针对H.264和H.265)。
  • 当数据不足时,重新读取文件数据到缓冲区。
  • 冲刷解码器:发送空包处理缓冲区中剩余的数据。
  • 清理资源:关闭文件、释放内存等。

注意事项

写入YUV数据时提供了两种方法:

该程序支持将H.264、H.265或MPEG2视频文件解码为YUV420P格式的原始数据。

提问

H.264 vs H.265 的主要不同?

  1. NAL单元类型解析方式不同
    c
    // H.264的NAL类型解析
    printf(“nal_type:%d”, pkt->data[4]&0x1f); // 取低5位

// H.265的NAL类型解析
printf(“nal_type:%d”, (pkt->data[4]&0x7e)>>1); // 取第2-7位,然后右移1位
2. NAL头结构差异
H.264: NAL类型在第一个字节的低5位

H.265: NAL类型在第一个字节的第2-7位,需要更复杂的位操作

  1. 编码效率
    H.265相比H.264有更好的压缩效率,相同质量下码率可降低50%

H.265支持更高的分辨率和更复杂的预测模式

为什么在写入YUV420数据时,使用整体写入(一次fwrite)的方式是错误的,而使用逐行写入(循环fwrite)的方式是正确的?

错误写法中的 /4

frame->width * frame->height / 4  // 为什么是/4?  
  • 宽度:U/V分量的水平分辨率是Y的一半 → /2
  • 高度:U/V分量的垂直分辨率是Y的一半 → /2
  • 总面积:(width/2) × (height/2) = width × height / 4

正确写法中的 /2

// Y分量:全分辨率  
for(int j=0; j<frame->height; j++)           // 高度:不除  fwrite(..., frame->width, ...);          // 宽度:不除  // U/V分量:半分辨率    for(int j=0; j<frame->height/2; j++)         // 高度:/2  fwrite(..., frame->width/2, ...);        // 宽度:/2  

YUV420格式采用4:2:0采样:

  • Y分量:每个像素都有亮度值
  • U/V分量:每2×2的像素块共享1个U和1个V值

问题不在 /4 vs /2,而在于内存对齐:
假设 frame->width = 766, frame->height = 322

// 错误写法计算的总字节数:  
Y: 766 × 322 = 246,652 字节
U: 766 × 322 / 4 = 61,663 字节
V: 766 × 322 / 4 = 61,663 字节
// 但如果 linesize[1] = 384(对齐到32字节):  
实际U分量大小:384 × 161 = 61,824 字节  // 大于61,663!  
  • /4:用于计算U/V分量的总像素数(宽高各减半)
  • /2:用于确定循环次数和每行像素数(分别处理宽高减半)
  • 核心问题:错误写法忽略了linesize的内存对齐,直接假设数据连续存储,导致写入的数据可能包含填充字节或缺少有效数据。

code

/**
* @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 (1024*1024)#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);// 格式需要注意}void print_h264_nal_unit_type(uint8_t *data, size_t size){int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n", data[i], data[i]&0x1f, i);continue;}i++;}}void print_h265_nal_unit_type(uint8_t *data, size_t size){int i = 0;while (i+3 < size ) {if(data[i] == 0 && data[i+1]==0 && data[i+2] == 0 && data[i+3] == 1 ) {i += 4;printf("%02x nal_type:%d, pos:%d\n", data[i],(data[i]&0x7e)>>1, i);continue;}if(data[i] == 0 && data[i+1]==0 && data[i+2] == 1) {i += 3;printf("%02x nal_type:%d, pos:%d\n",data[i], (data[i]&0x7e)>>1, i);continue;}i++;}}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/h265测试时可以看出该种方法是错误的//  写入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.yuvint 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 = malloc(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_H265;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(video_codec_id == AV_CODEC_ID_H264) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], pkt->data[4]&0x1f,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  pkt->data[3]&0x1f,pkt->size);print_h264_nal_unit_type(pkt->data, pkt->size);}if(video_codec_id == AV_CODEC_ID_H265) {if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 0 && pkt->data[3] == 1 )printf("\nstart_code:%02x %02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],pkt->data[3], (pkt->data[4]&0x7e)>>1,pkt->size);if(pkt->data[0] == 0 && pkt->data[1]==0 && pkt->data[2] == 1 )printf("\nstart_code:%02x %02x %02x, nal_type:%d, size:%d\n", pkt->data[0],pkt->data[1],pkt->data[2],  (pkt->data[3]&0x7e)>>1,pkt->size);print_h265_nal_unit_type(pkt->data, pkt->size);}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);free(inbuf);printf("main finish, please enter Enter and exit\n");return 0;}
http://www.jsqmd.com/news/115860/

相关文章:

  • 提示工程架构师必备,实用工具箱大放送
  • 2025年大模型使用全景图:6大趋势助你抢占AI先机
  • 20251220
  • 在duckdb 递归CTE中实现深度优先搜索DFS
  • 线索二叉树
  • 实用指南:【javaEE】多线程进阶--CAS与原子类
  • 第3章 字符串向量数组
  • 大模型微调实战指南:从全参数微调到BitFit的低成本学习路径
  • 灵活用工平台,我的实践复盘
  • 敢不敢用一年时间读完这12本书,模型入门必看的12本书!建议收藏!!
  • 曲线的极坐标方程输入法 | Desmos 玩法系列 02
  • Windows10/11右键-超级菜单(动态菜单)批处理安装,所有任务、环境变量、设备管理器、网络链接、设备和打印机、重启资源管理器、电源选项、 区域语言、查看串口、获取本机IP等
  • 卡帕西年度预测:大模型只释放10%潜力,2025年AI发展6大趋势
  • AVL
  • STM32学习——编码器接口测速
  • AI写论文哪个软件好?9款AI论文写作软件实测,查重率低至6%!
  • 鸿蒙系统
  • 11.20 脚本网页 数学分支
  • 学Simulink——基础MPPT控制场景实例:基于Simulink的电导增量法(INC)光伏MPPT仿真
  • 本地运行可以打印东西,docker run后却没有日志产生?记录一次AI编程的小蠢行为
  • 排序--基数排序
  • 正点原子移植linux-imx6.12的一个容易犯得问题
  • 大模型微调优化方案:PEFT-LoRA轻量化与量化技术,成本降低70%训练周期缩短65%
  • 解析:One-API 与 New-API 核心原理
  • 模板和策略模式的区别
  • 好友圈模块 Cordova 与 OpenHarmony 混合开发实战
  • 学Simulink——机器人控制场景实例:基于Simulink的SCARA机械臂关节空间PD控制仿真
  • 第4章 运算符
  • 工厂模式和抽象工厂模式的区别
  • 洞察:MCP与Function Calling区别