ffmpeg---最关键的结构体及其关系 +最简单的基于FFmpeg的解码器官方参考http://ffmpeg.org/doxygen/3.1/annotated.html

在这里插入图片描述

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

修改自FFMPEG结构体分析:AVFormatContext
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,转换为秒需要除以1000000int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
	AVDictionary *metadata:元数据
} AVFormatContext;

AVOutputFormat 输出文件格式 官方参考
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)+相关信息

修改自FFMPEG结构体分析:AVFrame

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:94: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 <stdio.h>  
#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<pFormatCtx->【音视频流个数】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;  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值