博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ffmpeg---最关键的结构体及其关系 +最简单的基于FFmpeg的解码器官方参考http://ffmpeg.org/doxygen/3.1/annotated.html
阅读量:3920 次
发布时间:2019-05-23

本文共 10297 字,大约阅读时间需要 34 分钟。

在这里插入图片描述

a) 解协议(http,rtsp,rtmp,mms)

AVIOContext,URLProtocol,URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注意:FFMPEG中文件也被当做一种协议“file”)

b) 解封装(flv,avi,rmvb,mp4)

AVFormatContext----------音视频封装格式中包含的信息
AVInputFormat-----------输入音视频使用的封装格式

c) 解码(h264,mpeg2,aac,mp3)

d) 存数据

视频的话,每个结构一般是存一帧;音频可能有好几帧

解码前数据:AVPacket(压缩后)
解码后数据:AVFrame

他们之间的对应关系如下所示:

在这里插入图片描述
在这里插入图片描述

AVFormatContext

typedef struct AVFormatContext{
----struct AVOutputFormat *oformat;//AVOutputFormat 和 AVInputFormat 最多只能同时存在一个----struct AVInputFormat *iformat; {
long_name:封装格式的长名称 extensions:封装格式的扩展名 id:封装格式ID 一些封装格式处理的接口函数 }----AVIOContext *pb;【IO数据的缓存的管理结构体】 {
unsigned char *buffer;//缓存数据 int buffer_size; unsigned char *buf_ptr;//当前指针读取到的位置 unsigned char *buf_end;//缓存结束的位置 void *opaque;//→→URLContext结构体(并不在FFMPEG提供的头文件中,而是在FFMPEG的源代码中。) } unsigned int nb_streams; //AVStream通道数量----AVStream **streams; {
AVCodecParameters * codecpar :用于记录编码后的流信息,即通道中存储的流的编码信息(原先是CodecContext* codec) AVRational time_base;时间基(准),真正的时间 =PTS*time_base {
int num; // numerator分子,比如1 int den; // denominator分母 如视频的分母用采样频率90k 表示时间戳的增量是1/90k } 【AVStream用采样频率做分母 而AVCodecContext中使用的是帧率做分母】 int index; 在AVFormatContext 中所处的通道索引--------AVCodecContext *codec;【编解码器上下文】 {
enum AVMediaType codec_type;//编解码器的类型 {
AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse AVMEDIA_TYPE_NB }------------struct AVCodec *codec; {
const char *name:编解码器的名字,比较短 const char *long_name:编解码器的名字,全称,比较长 enum AVMediaType type:指明了类型,是视频,音频,还是字幕 enum AVCodecID id; 编解码接口函数 } int bit_rate;//平均比特率 uint8_t *extradata; int extradata_size; //针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等) AVRational time_base; //根据该参数,可以把PTS转化为实际的时间(单位为秒s) {
int num; ///< numerator分子 比如1 int den; ///< denominator分母 比如分母25->->时间戳单位为1/25秒,其实就是每帧时间 【AVCodecContext中时间刻度用的帧率,即1/帧率】【AVStream中用采样频率,即1/90k】 }//结构体AVRational原本是用来表示有理数 int width, height;//如果是视频的话,代表宽和高 int refs;//运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了) int sample_rate;//采样率(音频) int channels;//声道数(音频) enum AVSampleFormat sample_fmt;//采样格式 int profile;//型(H.264里面就有,其他编码标准应该也有) int level;//级(和profile差不太多) } int64_t duration:该视频/音频流时间长度 AVDictionary *metadata:元数据信息 AVRational avg_frame_rate:帧率--------AVPacket attached_pic: {
uint8_t *data; 【数据域空间在av_read_frame内分配的→每次循环的结束不能忘记调用av_packet_unref减少数据域的引用技术】 【当引用技术减为0时,会自动释放数据域所占用的空间】 //例如对于H.264来说。1个AVPacket的data通常对应一个NAL。 //注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流 //因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。 int size:data的大小 int64_t pts:显示时间戳 Presentation Time Stamp int64_t dts:解码时间戳 Decoding Time Stamp int stream_index:标识该AVPacket所属的视频/音频流。 } } char filename[1024]:文件名 int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000) int bit_rate:比特率(单位bps,转换为kbps需要除以1000) AVDictionary *metadata:元数据} AVFormatContext;

ffmpeg支持各种各样的输出文件格式,MP4,FLV,3GP等等。而 AVOutputFormat 结构体则保存了这些格式的信息和一些常规设置。

typedef struct AVOutputFormat {
const char *name; const char *long_name; const char *mime_type; const char *extensions; /**< comma-separated filename extensions */ /* output support */ enum AVCodecID audio_codec; /**< default audio codec */ enum AVCodecID video_codec; /**< default video codec */ enum AVCodecID subtitle_codec; /**< default subtitle codec */ int flags; const struct AVCodecTag * const *codec_tag; const AVClass *priv_class; ///< AVClass for the private context struct AVOutputFormat *next; int priv_data_size; enum AVCodecID data_codec; /**< default data codec */ int (*write_header)(struct AVFormatContext *); int (*write_packet)(struct AVFormatContext *, AVPacket *pkt); int (*write_trailer)(struct AVFormatContext *); 等函数指针} AVOutputFormat;

在这里插入图片描述

AVFrame 一帧解码后像素(采样)数据。

 原始非压缩数据(例如对视频来说是YUV,RGB,对音频来说是PCM)+相关信息

typedef struct AVFrame {
uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM) int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。 int width, height:视频帧宽和高(1920x1080,1280x720...) int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个 int format:解码后原始数据类型(YUV420,YUV422,RGB24...) int key_frame:是否是关键帧 enum AVPictureType pict_type:帧类型(I,B,P...) AVRational sample_aspect_ratio:宽高比(16:9,4:3...) int64_t pts:显示时间戳 Presentation Time Stamp} AVFrame;
typedef struct AVPicture {
uint8_t *data[4]; int linesize[4]; // number of bytes per line} AVPicture;typedef struct AVFrame{
uint8_t *data[4]; int linesize[4]; uint8_t *base[4]; //......其他的数据} AVFrame;故而可以进行类型转换: (AVPicture *)AVFrame 因为2个结构体前面的2个成员一致AVPicture是AVFrame的一个子集,他们都是数据流在编解过程中用来保存数据缓存的对像

从int av_read_frame(AVFormatContext *s, AVPacket *pkt)函数看,从数据流读出的数据首先是保存在AVPacket里,也可以理解为一个AVPacket最多只包含一个AVFrame,而一个AVFrame可能包含好几个AVPacket,AVPacket是种数据流分包的概念。记录一些音视频相关的属性值,如pts,dts等。

FFmpeg解码的流程图如下所示:

在这里插入图片描述

代码 《最简单的基于FFmpeg的解码器》

#include 
#define __STDC_CONSTANT_MACROS extern "C" {
#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" }; int main(int argc, char* argv[]) {
AVFormatContext *pFormatCtx; int i, videoindex; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame,*pFrameYUV; uint8_t *out_buffer; AVPacket *packet; int y_size; int ret, got_picture; struct SwsContext *img_convert_ctx; char filepath[]="../video/Titanic.ts"; //输入文件路径 int frame_cnt; av_register_all();【注册所有组件】 avformat_network_init(); 【打开网络视频流】 pFormatCtx = avformat_alloc_context(); //创建初始化通用的AVFormatContext //打开输入视频文件 【打开文件或URL,并使基于字节流的底层输入模块得到初始化;解析多媒体文件或多媒体流的头信息,】【创建AVFormatContext结构并填充其中的关键字段,依次为各个原始流建立AVStream结构。】-------------------- if(avformat_open_input(&pFormatCtx【返回AVFormatContext结构体】, filepath 【指定文件名】, NULL【用于显式指定输入文件的格式,如果设为空则自动判断其输入格式】, NULL【传入的附加参数】)!=0) {
printf("Couldn't open input stream.\n"); return -1; } //获取视频文件信息 //用于获取必要的编解码器参数。需要得到各媒体流对应编解码器的类型AVMediaType和ID:CodecID即获取【编码器上下文】VCodecContext→【编码器】AVCodec→【编码器枚举类型】AVMediaType+【编码器ID】AVCodecID-------- if(avformat_find_stream_info(pFormatCtx,NULL)<0) {
printf("Couldn't find stream information.\n"); return -1; } 找到streams[数量nb_streams]中视频流对应的那个stream[videoindex]----------------------------------------- videoindex=-1; for(i=0; i
【音视频流个数】nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO【视频编码器枚举】) {
videoindex=i;//找到视频的数组位置 即多的stream中的序号i break; } if(videoindex==-1){
printf("Didn't find a video stream.\n"); return -1; } 根据编解码器上下文(ID或者name)查找到编解码器AVCode----------------------------------------- pCodecCtx=pFormatCtx->streams[videoindex]->codec; //提取出streams->pCodecCtx编码器上下文 【https://blog.csdn.net/HandsomeHong/article/details/73732410】 pCodec=avcodec_find_decoder(pCodecCtx->codec_id);【(遍历AVCodec结构的链表)根据指定解码器ID查找相应的解码器并返回AVCodec】 if(pCodec==NULL){
printf("Codec not found.\n"); return -1; } 用AVCodec初始化AVCodecContext------------------------ if(avcodec_open2(需要初始化的pCodecCtx, 输入的pCodec, 选项NULL)<0) {
printf("Could not open codec.\n"); return -1; } /* * 在此处添加输出视频信息的代码 * 取自于pFormatCtx,使用fprintf() */ printf("视频的时长:%dμs\n", pFormatCtx->duration);//输入视频的时长(以微秒为单位) 准备AVPacket和AVFrame用于读和转换----------------- pFrame=av_frame_alloc(); pFrameYUV=av_frame_alloc(); out_buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); packet=(AVPacket *)av_malloc(sizeof(AVPacket)); 【压缩前数据】 //Output Info----------------------------- printf("--------------- File Information ----------------\n"); av_dump_format(pFormatCtx,0,filepath,0); //该函数的作用就是检查下初始化过程中设置的参数是否符合规范 或者另一个名字dump_format() printf("-------------------------------------------------\n"); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); frame_cnt=0; 读取一帧压缩数据AVPacket-------------------------------- while(av_read_frame(pFormatCtx, 读出的压缩数据packet)>=0) {
if(packet->stream_index==videoindex){
/* * 在此处添加输出H264码流的代码 * 取自于packet,使用fwrite() */ 解码一帧视频数据-------------------------------------- ret = avcodec_decode_video2(pCodecCtx, pFrame,&got_picture, packet); if(ret < 0){
printf("Decode Error.\n"); return -1; } if(got_picture) {
【sws_scale() 转变图片的存储格式 ①初始化sws_getContext。 ②使用 sws_scale 转化。 ③释放环境sws_freeContext()】 sws_scale(图片信息img_convert_ctx, 源数据(const uint8_t* const*)pFrame->data, 源数据长度pFrame->linesize, 源切片起始位置0, 源切片的高度pCodecCtx->height, 目的数据pFrameYUV->data, 目的数据的长度pFrameYUV->linesize); printf("Decoded frame index: %d\n",frame_cnt); /* * 在此处添加输出YUV的代码 * 取自于pFrameYUV,使用fwrite() */ frame_cnt++; } } } sws_freeContext(img_convert_ctx); 【释放像素格式转换的环境】 av_frame_free(&pFrameYUV); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx);【关闭AVFormatContext 一般和avformat_open_input()成对使用】 //1关闭输入流 2释放AVFormatContext 3释放AVIOContext //关闭输入视频文件。 return 0; }
你可能感兴趣的文章
C#如何回到主线程,如何在委托指定线程执行
查看>>
服务器重新部署踩坑记
查看>>
.NET应用程序安全操作概述
查看>>
C# WPF:把文件给我拖进来!!!
查看>>
生态和能力是国内自研操作系统发展的关键
查看>>
银河麒麟V10入选2020中国十大科技新闻
查看>>
Amazing 2020
查看>>
代码改变世界,也改变了我
查看>>
2021,未来可期
查看>>
阿星Plus:基于abp vNext开源一个博客网站
查看>>
写给自己,2020的年终总结
查看>>
Flash 生命终止,HTML5能否完美替代?
查看>>
ML.NET生成器带来了许多错误修复和增强功能以及新功能
查看>>
微信适配国产操作系统:原生支持 Linux
查看>>
我的2020年终总结:新的角色,新的开始
查看>>
C# 9 新特性 —— 增强的模式匹配
查看>>
ASP.NET Core Controller与IOC的羁绊
查看>>
如何实现 ASP.NET Core WebApi 的版本化
查看>>
探索 .Net Core 的 SourceLink
查看>>
AgileConfig-如何使用AgileConfig.Client读取配置
查看>>