写在前面

学习雷神的博客,向雷神致敬~

看了雷神的小学期视频课,在Github上下载了simplest_ffmpeg_player的代码,为代码加上了注释,作为留存。

2019.07.16

视频中的前置知识点
simple_ffmpeg_decoder.cpp注释
simple_ffmpeg_decoder_pure.cpp注释


链接及参考资料

《基于 FFmpeg + SDL 的视频播放器的制作》课程的视频
FFmpeg Documentation
FFmpeg源代码简单分析


知识点

封装、编码格式


FFmpeg解码流程及数据结构

FFmpeg数据结构简介

AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
AVInputFormat:每种封装格式对应一个该结构体
AVStream:视频文件每个视频(音频)流对应一个该结构体
AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodec:每种视频(音频)编解码器对应一个该结构体
AVPacket:存储一帧压缩编码数据
AVFrame:存储一帧解码后像素(采样)数据

AVFormatContext

  • iformat:输入视频的AVIputFormat
  • nb_streams:输入视频的AVStream个数
  • streams:输入视频的AVStream[]数组
  • duration:输入视频的时长(以微秒为单位)
  • bit_rate:输入视频的码率

AVIputFormat

  • name:封装格式名称
  • long_name:封装格式的长名称
  • extensions:封装格式的扩展名
  • id:封装格式ID
  • 一些封装格式处理的接口函数

AVStream

  • id:序号
  • codec:该流对应的AVCodecContext
  • time_base:该流的时基
  • r_frame_rate:该流的帧率

AVCodecContext

  • codec:编解码器的AVCodec
  • width,height:图像的宽高(只针对视频)
  • pix_fmt:像素格式(只针对视频)
  • sample_rate:采样率(只针对音频)
  • channels:声道数(只针对音频)
  • sample_fmt:采样格式(只针对音频)

AVCodec

  • name:编解码器名称
  • long_name:编解码器长名称
  • type:编解码器类型
  • id:编解码器ID
  • 一些编解码的接口函数

1.simplest_ffmpeg_decoder.cpp
/*** 最简单的基于FFmpeg的视频解码器* Simplest FFmpeg Decoder** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序实现了视频文件解码为YUV数据。它使用了libavcodec和* libavformat。是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* This software is a simplest decoder based on FFmpeg.* It decodes video to YUV pixel data.* It uses libavcodec and libavformat.* Suitable for beginner of FFmpeg.**/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif/*** 将视频解封装、解码,转换为yuv格式**/
int main(int argc, char* argv[])
{// 封装格式上下文的结构体,也是统领全局的结构体,保存了视频文件封装格式的相关信息AVFormatContext    *pFormatCtx;// 视频流在文件中的位置int             i, videoindex;// 编码器上下文结构体,保存了视频(音频)编解码相关信息AVCodecContext *pCodecCtx;// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体AVCodec          *pCodec;// 存储一帧解码后像素(采样)数据AVFrame    *pFrame,*pFrameYUV;//unsigned char *out_buffer;// 存储一帧压缩编码数据AVPacket *packet;// width×height,用于计算YUV数据分布int y_size;// 视频是否解码成功的返回int ret, got_picture;// libswsscale 上下文struct SwsContext *img_convert_ctx;// 输入文件char filepath[]="Titanic.mkv";// 输出文件FILE *fp_yuv=fopen("output.yuv","wb+");  // 注册复用器,编码器等(参考FFmpeg解码流程图)av_register_all();avformat_network_init();pFormatCtx = avformat_alloc_context();// 打开多媒体数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){printf("Couldn't open input stream.\n");return -1;}// 读取一部分视音频数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_find_stream_info(pFormatCtx,NULL)<0){printf("Couldn't find stream information.\n");return -1;}// 每个视频文件中有多个流(视频流、音频流、字幕流等,而且可有多个),循环遍历找到视频流// 判断方式:AVFormatContext->AVStream->AVCodecContext->codec_type是否为AVMEDIA_TYPE_VIDEOvideoindex=-1;for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){videoindex=i;break;}// 如果没有视频流,返回if(videoindex==-1){printf("Didn't find a video stream.\n");return -1;}// 保存视频流中的AVCodecContextpCodecCtx=pFormatCtx->streams[videoindex]->codec;// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL){printf("Codec not found.\n");return -1;}// (参考FFmpeg解码流程图)if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){printf("Could not open codec.\n");return -1;}// 创建一个AVFrame,用来存放解码后的一帧的数据pFrame=av_frame_alloc();pFrameYUV=av_frame_alloc();// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,  pCodecCtx->width, pCodecCtx->height,1));// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);// 创建一个AVPacket,用来存放下面循环获取到的未解码帧packet=(AVPacket *)av_malloc(sizeof(AVPacket));//Output Info-----------------------------printf("--------------- File Information ----------------\n");av_dump_format(pFormatCtx,0,filepath,0);printf("-------------------------------------------------\n");// sws_getContext():初始化一个SwsContextimg_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); // 循环读取帧数据while(av_read_frame(pFormatCtx, packet)>=0){// 取出视频流,if(packet->stream_index==videoindex){// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if(ret < 0){printf("Decode Error.\n");return -1;}if(got_picture){// sws_scale():处理图像数据,用于转换像素sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);// 根据YUV数据格式,分离Y、U、V数据// 如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据// 其中前w*h Byte存储Y,接着的w*h*1/4 Byte存储U,最后w*h*1/4 Byte存储Vy_size=pCodecCtx->width*pCodecCtx->height;  fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //Ufwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //Vprintf("Succeed to decode 1 frame!\n");}}av_free_packet(packet);}//flush decoder//FIX: Flush Frames remained in Codecwhile (1) {// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if (ret < 0)break;if (!got_picture)break;// // sws_scale():处理图像数据,用于转换像素sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);int y_size=pCodecCtx->width*pCodecCtx->height;  fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //Ufwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //Vprintf("Flush Decoder: Succeed to decode 1 frame!\n");}// sws_freeContext():释放一个SwsContextsws_freeContext(img_convert_ctx);// close and freefclose(fp_yuv);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0;
}
2.simplest_ffmpeg_decoder_pure
/*** 最简单的基于FFmpeg的视频解码器(纯净版)* Simplest FFmpeg Decoder Pure** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序实现了视频码流(支持HEVC,H.264,MPEG2等)解码为YUV数据。* 它仅仅使用了libavcodec(而没有使用libavformat)。* 是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* This software is a simplest decoder based on FFmpeg.* It decode bitstreams to YUV pixel data.* It just use libavcodec (do not contains libavformat).* Suitable for beginner of FFmpeg.*/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
};
#endif
#endif//test different codec
#define TEST_H264  1
#define TEST_HEVC  0int main(int argc, char* argv[])
{// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体AVCodec *pCodec;// 编码器上下文结构体,保存了视频(音频)编解码相关信息AVCodecContext *pCodecCtx= NULL;// 保存了当前帧的信息,包括offset、dts、pts、宽高等AVCodecParserContext *pCodecParserCtx=NULL;FILE *fp_in;FILE *fp_out;// 存储一帧解码后像素(采样)数据AVFrame    *pFrame;const int in_buffer_size=4096;// FF_INPUT_BUFFER_PADDING_SIZE:在输入比特流的末尾用于解码的额外分配字节的所需数量。// 这主要是因为一些优化的比特流读取器一次读取32位或64位并且可以读取结束。// 注意:如果附加字节的前23位不为0,则损坏的MPEG比特流可能导致过度读取和段错误。unsigned char in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};unsigned char *cur_ptr;int cur_size;// 存储一帧压缩编码数据AVPacket packet;// 视频是否解码成功的返回int ret, got_picture;// hevc h264 m2v -> yuv
#if TEST_HEVCenum AVCodecID codec_id=AV_CODEC_ID_HEVC;char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264AVCodecID codec_id=AV_CODEC_ID_H264;char filepath_in[]="bigbuckbunny_480x272.h264";
#elseAVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;char filepath_in[]="bigbuckbunny_480x272.m2v";
#endifchar filepath_out[]="bigbuckbunny_480x272.yuv";int first_time=1;//av_log_set_level(AV_LOG_DEBUG);// 注册复用器,编码器等(参考FFmpeg解码流程图)avcodec_register_all();// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)pCodec = avcodec_find_decoder(codec_id);if (!pCodec) {printf("Codec not found\n");return -1;}// 创建AVCodecContext结构体pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("Could not allocate video codec context\n");return -1;}// 初始化AVCodecParserContext。其参数是codec_id,所以同时只能解析一种// AVCodecParser用于解析输入的数据流并把它们分成一帧一帧的压缩编码数据。// 比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。// 核心函数是av_parser_parse2()pCodecParserCtx=av_parser_init(codec_id);if (!pCodecParserCtx){printf("Could not allocate video parser context\n");return -1;}//if(pCodec->capabilities&CODEC_CAP_TRUNCATED)//    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; // 使用给定的AVCodec初始化AVCodecContext;// 在使用这个函数之前需要使用avcodec_alloc_context3()分配的contextif (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {printf("Could not open codec\n");return -1;}//Input Filefp_in = fopen(filepath_in, "rb");if (!fp_in) {printf("Could not open input stream\n");return -1;}//Output Filefp_out = fopen(filepath_out, "wb");if (!fp_out) {printf("Could not open output YUV file\n");return -1;}pFrame = av_frame_alloc();// 把packet的参数设为默认值,要求packet的内存已经分配好了av_init_packet(&packet);while (1) {// 获取视频文件的总长度cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);if (cur_size == 0)break;cur_ptr=in_buffer;while (cur_size>0){/*** 解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据* Parse a packet.** @param s             parser context.* @param avctx         codec context.* @param poutbuf       set to pointer to parsed buffer or NULL if not yet finished.* @param poutbuf_size  set to size of parsed buffer or zero if not yet finished.* @param buf           input buffer.* @param buf_size      input length, to signal EOF, this should be 0 (so that the last frame can be output).* @param pts           input presentation timestamp.* @param dts           input decoding timestamp.* @param pos           input byte position in stream.* @return the number of bytes of the input bitstream used.** Example:* @code*   while(in_len){*       len = av_parser_parse2(myparser, AVCodecContext, &data, &size,*                                        in_data, in_len,*                                        pts, dts, pos);*       in_data += len;*       in_len  -= len;**       if(size)*          decode_frame(data, size);*   }* @endcode** 其中poutbuf指向解析后输出的压缩编码数据帧,buf指向输入的压缩编码数据。* 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。* 当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理。*/int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,&packet.data, &packet.size,cur_ptr , cur_size ,AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);cur_ptr += len;cur_size -= len;// 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。if(packet.size==0)continue;//Some Info from AVCodecParserContextprintf("[Packet]Size:%6d\t",packet.size);switch(pCodecParserCtx->pict_type){case AV_PICTURE_TYPE_I: printf("Type:I\t");break;case AV_PICTURE_TYPE_P: printf("Type:P\t");break;case AV_PICTURE_TYPE_B: printf("Type:B\t");break;default: printf("Type:Other\t");break;}printf("Number:%4d\n",pCodecParserCtx->output_picture_number);// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (got_picture) {if(first_time){printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);first_time=0;}//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Succeed to decode 1 frame!\n");}}}//Flush Decoderpacket.data = NULL;packet.size = 0;while(1){// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (!got_picture){break;}else {//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Flush Decoder: Succeed to decode 1 frame!\n");}}// close and freefclose(fp_in);fclose(fp_out);av_parser_close(pCodecParserCtx);av_frame_free(&pFrame);avcodec_close(pCodecCtx);av_free(pCodecCtx);return 0;
}

雷神simplest_ffmpeg_player解析(一)相关推荐

  1. 音视频编解码 -- 编码参数 CRF

    之前多多少少接触过一些编解码参数,CRF 参数也用过,但是最近在和朋友们聊天时,说到使用 FFMPEG 过程中碰到 CRF 参数,以及具体作用流程,这个之前一直没有跟踪过,也没有详细记录过,所以吊起了 ...

  2. SpringBoot入门-源码解析(雷神)

    一.Spring Boot入门 视频学习资料(雷神): https://www.bilibili.com/video/BV19K4y1L7MT?p=1 github: https://github.c ...

  3. 音视频学习笔记(雷神)—技术解析

    音视频技术解析 封装技术+视频压缩编解码+音频压缩编解码 这是技术层 流媒体传输协议 这是网络层 视频播放器解析 解协议 从视频播放器的角度做解析,拿到传输而来的视频数据后,首先要解协议(传输协议) ...

  4. 【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

    一 前言 最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大.那写这 ...

  5. 雷神3开方算法解析,游戏运行速度提高四倍。

    我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然 ...

  6. 音视频 FLV格式解析

    前言 本文介绍FLV封装格式,直播场景下拉流比较常见的是http-flv直播流,具有延时低.易传输等特点. 格式概览 总体上看,FLV包括文件头(File Header)和文件体(File Body) ...

  7. SpringBoot 自动装配原理解析

    自动装配是 Spring Boot 的核心部分,也是 Spring Boot 功能的基础,正是由于自动装配,才 将我们从 Bean 的繁复配置中解脱出来.那么 Spring Boot 中的自动装配指的 ...

  8. 雷神轮胎携手JBL 演绎科技降噪、感受非凡音悦

    近日,雷神轮胎与哈曼旗下知名音响品牌JBL进行合作,携手演绎科技降噪,致力于为广大车主带来更舒适愉悦的驾乘体验. 雷神轮胎作为一个中高端黑科技静音轮胎品牌,此次与全球久负盛名的专业扬声器生产商JBL强 ...

  9. UDP-RTP协议解析

    一.RTP协议 数据传输协议RTP,用于实时传输数据.RTP报文由两部分组成:报头和有效载荷 二.RTP的会话过程 当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址.目的传输地址由一个网 ...

  10. 音视频学习-h264裸流的解析

    h264的解析代码,来自雷神的博客 //============================================================================ // ...

最新文章

  1. Windows下Wireshark安装版本选择方式
  2. FLEX SharedObject介绍及应用
  3. java 获取麦克_Java Sound API-捕获麦克风
  4. Linux下创建用户、切换用户、删除用户
  5. Bzoj 2127 happiness 最小割
  6. 有关findviewbyid 一个错误用法
  7. Spring Security(四) —— 核心过滤器源码分析
  8. NEC协议——红外遥控的使用
  9. 进程间通信(匿名管道、命名管道、共享内存)
  10. HBase的java操作,最新API。(查询指定行、列、插入数据等)
  11. 数据迁移软件|如何将旧电脑的数据传输到新电脑?
  12. AI创作教程之 Stable Diffusion 为何是人工智能新时代艺术创作的基石
  13. 电脑快捷方式变白原因及解决方法——血的教训呜呜呜
  14. 冰点还原忘记密码如何修改配置或卸载
  15. 深入理解计算机系统bomb实验
  16. 中山大学计算机软件专业,【广州日报】中山大学在珠海校区新成立人工智能学院和软件工程学院...
  17. 计算机课情感态度与价值观,浅谈信息技术课中情感态度价值观的培养
  18. 《A brief review of image denoising algorithms and beyond》
  19. 华为云服务器使用之搭建极简服务器
  20. 机器学习入门(二) 准备工作

热门文章

  1. bilibili 弹幕协议分析,golang 还原代码
  2. codevs 1024 一塔湖图 floyd 解题报告
  3. 单片机常用芯片总结(一)——LCD1602液晶屏
  4. 东西帝国时代:西半球的罗马帝国和东半球的秦汉帝国
  5. Qt 弹出对话框选择图片并显示
  6. 姿态估计1-06:FSA-Net(头部姿态估算)-源码无死角讲解(1)-训练代码总览
  7. 腾讯与阿里巴巴投资可编程芯片公司Barefoot Networks
  8. 奥鹏计算机基础18秋在线作业答案,1056《 计算机基础》20秋西南大学在线作业答案答案...
  9. 学习推荐!吴恩达 AI 课程及学习路线最全梳理
  10. 心形函数的正确打开方式(Unity3D Shader)