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,转换为秒需要除以1000000)
int 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)+相关信息
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 <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;
}