1.解封装流程图



 

2.函数介绍

av_register_all()

注册所有的解封装格式,也可以根据不同的封装格式,单个注册。

 

avformat_network_init()

注册网络,如rtsp,http

 

avformat_open_input(...)

打开输入文件,可以是本地视频文件,也可以是网络链接。

在打开网络链接的时候,该函数默认是阻塞的,遇到下列情况:

* 网络不稳定
* 服务器响应比较慢
* 直播流不存在或者没有数据
会导致该函数长时间不返回。

我们可以通过设置timeout超时时间,或者是设置interrupt_callback定义返回机制。

设置超时
AVFormatContext *ic = NULL; //设置超时时间,不同的协议设置关键字不一样 AVDictionary *options = 0;
//设置rtsp超时 (in microseconds) av_dict_set(&options, "stimeout", "5000000", 0);
//单位微秒 //设置tcp or udp,默认一般优先tcp再尝试udp av_dict_set(&opts, "rtsp_transport",
m_bIsTcp ? "tcp" : "udp", 0); //设置http udp超时 av_dict_set(&options, "timeout",
"5000000", 0); //单位微秒 int re = avformat_open_input(&ic, path, 0, &options);
设置回调
// 回调函数的参数,用了时间 typedef struct { time_t lasttime; } Runner; // 回调函数
//回调函数中返回1,则代表ffmpeg结束阻塞可以将操纵权交给用户线程并返回错误码
//回调函数中返回0,则代表ffmpeg继续阻塞直到ffmpeg正常工作为止 static int interrupt_callback(void *p) {
Runner *r = (Runner *)p; if (r->lasttime > 0) { if (time(NULL) - r->lasttime >
8) { // 等待超过8s则中断 return 1; } } return 0; } // usage Runner input_runner = {0};
AVFormatContext *ifmt_ctx = avformat_alloc_context();
ifmt_ctx->interrupt_callback.callback = interrupt_callback;
ifmt_ctx->interrupt_callback.opaque = &input_runner; input_runner.lasttime =
time(NULL); // 调用之前初始化时间 ret = avformat_open_input(&ifmt_ctx, url, NULL, NULL);
if(ret < 0) { // error }
 

avformat_find_stream_info(...)

探测获取封装格式的上下文信息。


在一些格式当中没有头部信息,如flv,h264,mpeg,调用avformat_open_input()在打开文件之后会没有参数,也就无法获取到里面的信息。这个时候就可以调用此函数,因为它会试着去探测文件的格式,但是如果格式当中没有头部信息,那么它只能获取到编码、宽高这些信息,还是无法获得总时长。如果总时长无法获取到,那么需要把整个文件读一遍,计算一下它的总帧数。
avformat_find_stream_info(ic, 0)
 

av_find_best_stream(...)

获取音视频流索引
int videoStream = 0; int audioStream = 1; //获取音频流索引 audioStream =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); //获取视频流索引
videoStream = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
 

av_read_frame(...)

读取码流中的音频若干帧或者视频一帧,该函数也是阻塞的,可以通过设置超时或者是回调函数让函数立即返回。

使用FFmpeg的av_read_frame函数后,每读完一个packet,必须调用av_packet_unref函数进行内存释放,否则会导致内存释泄漏
// 返回值小于0代表错误或者读完了 int av_read_frame(AVFormatContext *s, AVPacket *pkt);
 

3.关键结构体介绍

AVFormatContext 封装格式上下文
// I/O context.自定义格式读或者从内存读可用 AVIOContext *pb; // 输入的文件名 char filename[1024];
// 流的数量 unsigned int nb_streams; //音频视频字幕流 AVStream **streams; //
总时长(单位:微秒us,转换为秒需要除以1000000) int64_t duration; // 比特率 bit/s,网络适应的时候会用 int64_t
bit_rate; //释放之前在动态链接库中申请的空间,并置0 void avformat_close_input(AVFormatContext **s);
 

AVStream 存储解码前的音视频信息
//时间基数,通过分子分母计算 (double) r.num / (double) r.den AVRational time_base;
//通过time_base来计算 //duration * ((double)time_base.num / (double)time_base.den) 秒
//这里面的时长在有些格式里面没有,以AVFormatContext里面的为准 int64_t duration; //帧率(注:对视频来说,这个挺重要的)
AVRational avg_frame_rate; //附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面 AVPacket
attached_pic; //音视频参数 AVCodecParameters *codecpar;
 

AVCodecParameters 音视频参数
//时间基数,通过分子分母计算 (double) r.num / (double) r.den AVRational time_base;
//通过time_base来计算 //duration * ((double)time_base.num / (double)time_base.den) 秒
//这里面的时长在有些格式里面没有,以AVFormatContext里面的为准 int64_t duration; //帧率(注:对视频来说,这个挺重要的)
AVRational avg_frame_rate; //附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面 AVPacket
attached_pic; //音视频参数 AVCodecParameters *codecpar; AVCodecParameters 音视频参数 //
标志是否是音频还是视频 enum AVMediaType codec_type; // 对应的编码格式 h264 mpeg4等 enum AVCodecID
codec_id; // 音视频不一样 //音频 采样格式 enum AVSampleFormat //视频 像素格式 enum AVPixelFormat
int format; //视频宽高 int width; int height; //升到数 int channels; //采样率 int
sample_rate;
 

AVPacket 存储解码后的数据
// 显示时间戳 int64_t pts; // 解码时间戳 int64_t dts; //解码后的数据 uint8_t *data; int size;
 

4.解封装关键代码
//把分数转换成浮点数 static double r2d(AVRational r) { return (r.num == 0 || r.den ==
0) ? 0 : (double)r.num / (double)r.den; } char path[] = "/sdcard/v1080.mp4";
//初始化解封装 av_register_all(); //初始化网络 avformat_network_init(); AVFormatContext
*ic = NULL; //设置超时 // AVDictionary *options = NULL; // av_dict_set(&options,
"stimeout", "3000000", 0); //打开文件 int re = avformat_open_input(&ic, path, 0,
0); if (re != 0) { LOGI("avformat_open_input failed! %s", av_err2str(re)); }
LOGI("duration = %lld", ic->duration); //获取封装格式的相关信息 re =
avformat_find_stream_info(ic, 0); if (re != 0) {
LOGI("avformat_find_stream_info! %s", av_err2str(re)); } int fps = 0; int
videoStream = 0; int audioStream = 1; //获取音频信息 audioStream =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); AVStream *aStream
= ic->streams[audioStream]; LOGI("音频流 %d", audioStream); LOGI("sample_rate =
%d, channels = %d, sample_format = %d", aStream->codecpar->sample_rate,
aStream->codecpar->channels, aStream->codecpar->format); //获取视频信息 videoStream =
av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); AVStream *vStream
= ic->streams[videoStream]; LOGI("视频流 %d", vStream); fps =
r2d(vStream->avg_frame_rate); LOGI("fps = %d, width = %d, height = %d, codecId
= %d", fps, vStream->codecpar->width, vStream->codecpar->height,
vStream->codecpar->codec_id); //读取帧数据,注意释放AVPacket申请的空间 AVPacket *pkt =
av_packet_alloc(); for (;;) { int re = av_read_frame(ic, pkt); if (re != 0) {
LOGI("读到结尾处后,跳转到第20秒的位置"); //seek到20秒的位置 需要用videoStream的时间基数来计算 int pos = 20 *
r2d(ic->stream[videoStream]->time_base); //seek 操作,向后找并且要找到关键帧
av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_FRAME|AVSEEK_FLAG_BACKWARD); }
//////////其他操作///////////// //pkt的内存一定要释放 不然会导致内存泄露 av_packet_unref(pkt); }
//关闭AVFormatContext avformat_close_input(&ic);