原文链接:https://blog.csdn.net/u014078003/article/details/128554153
1.封装格式相关函数
- avformat_alloc_context():负责申请一个AVFormatContext结构的内存,并进行简单初始化,这个函数可以不用手动调用,内部会自动调用。
- avformat_free_context():释放该结构里的所有东西以及该结构本身,如果不手动alloc,这个也可以不用调用。
- avformat_open_input():打开输入视频文件,如果传入的AVFormatContext未进行初始化,内部会进行初始化。
int avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
if (!s->av_class) {
av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
return AVERROR(EINVAL);
}
...
}
- avformat_close_input():关闭解复用器。内部会自动调用avformat_free_context 进行释放。
void avformat_close_input(AVFormatContext **ps)
{
AVFormatContext *s;
AVIOContext *pb;
if (!ps || !*ps)
return;
s = *ps;
pb = s->pb;
if ((s->iformat && strcmp(s->iformat->name, "image2") && s->iformat->flags & AVFMT_NOFILE) ||
(s->flags & AVFMT_FLAG_CUSTOM_IO))
pb = NULL;
flush_packet_queue(s);
if (s->iformat)
if (s->iformat->read_close)
s->iformat->read_close(s);
avformat_free_context(s);
*ps = NULL;
avio_close(pb);
}
- avformat_find_stream_info():获取视频文件信息
- av_read_frame(): 读取音视频包,得到的是AVPacket
- avformat_seek_file():定位文件
- av_seek_frame():定位文件
2.解复用流程
编辑切换为居中
添加图片注释,不超过 140 字(可选)
注意:flv封装格式的视频,如果不调用avformat_find_stream_info解复用拿不到音视频帧的信息,mp4能拿到
由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟。
3.得到音频视频流index
新版本的ffmpeg可以通过av_find_best_stream来得到相应的index
int video_index = av_find_best_stream(av_format_cxontext, AVMEDIA_TYPE_VIDEO,
-1,-1, NULL, 0);
int audio_index=av_find_best_stream(av_format_cxontext,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
printf("使用av_find_best_stream方式 video_index=%d,audio_index=%d \n",video_index,audio_index);
旧版本的ffmpeg也可以通过遍历streams的方式来拿到
for(int i=0;i<av_format_cxontext->nb_streams;i++){
AVStream* av_stream= av_format_cxontext->streams[i]; //可能为音频流 、视频流 、字幕流
if(AVMEDIA_TYPE_AUDIO==av_stream->codecpar->codec_type){
printf("-----audio info -----\n");
//index 是解服用之后每个流的唯一身份标识
printf("audio index is %d \n",av_stream->index);
audioindex = i; // 获取音频的索引
}else if(AVMEDIA_TYPE_VIDEO==av_stream->codecpar->codec_type){
printf("----- 视频信息 -----\n");
videoindex = i;
}
}
4.ffmpeg实战解封装
// AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体
AVFormatContext *av_format_cxontext = NULL; // 输入文件的demux
int videoindex = -1; // 视频索引
int audioindex = -1; // 音频索引
// 打开文件,主要是探测协议类型,如果是网络文件则创建网络链接
int ret = avformat_open_input(&av_format_cxontext, filename, NULL, NULL);
if (ret < 0) {
char buf[1024] = {0};
av_strerror(ret, buf, sizeof(buf) - 1);
printf("open %s failed:%s\n", filename, buf);
goto failed;
} else {
printf("avformat_open_input 成功 ret is %d\n", ret);
}
//查找流信息
ret = avformat_find_stream_info(av_format_cxontext,NULL);
if(ret<0){ //失败
char buf[1024]={0};
av_strerror(ret,buf,sizeof (buf)-1);
printf("avformat_find_stream_info %s failed:%s\n",filename,buf);
goto failed;
}else{
printf("avformat_find_stream_info 成功 %d \n",ret);
}
printf("\n ====av_dump_format filename is %s====\n",filename);
av_dump_format(av_format_cxontext,0,filename,0);
printf("\n====av_dump_format finish ====\n\n");
// url: 调用avformat_open_input读取到的媒体文件的路径/名字
printf("media name is %s\n",av_format_cxontext->url);
printf("stream number is %d \n",av_format_cxontext->nb_streams);
printf("media average ratio:%lldkbps \n",(int64_t)av_format_cxontext->bit_rate/1024);
int total_seconds,hour,minute,second;
//duration:媒体文件的长度 单位微妙
total_seconds=av_format_cxontext->duration/AV_TIME_BASE;
hour=total_seconds/3600;
minute=(total_seconds%3600)/60;
second=(total_seconds%60);
printf("total duration is : %02d:%02d:%02d\n\n",hour,minute,second);
// av_find_best_stream() 这个是新版本的ffmpeg 获取流的方式
//老的方式还是通过遍历的方式获取音频流以及视频流
int video_index = av_find_best_stream(av_format_cxontext, AVMEDIA_TYPE_VIDEO,
-1,-1, NULL, 0);
int audio_index=av_find_best_stream(av_format_cxontext,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
printf("使用av_find_best_stream方式 video_index=%d,audio_index=%d \n",video_index,audio_index);
for(int i=0;i<av_format_cxontext->nb_streams;i++){
AVStream* av_stream= av_format_cxontext->streams[i]; //可能为音频流 、视频流 、字幕流
if(AVMEDIA_TYPE_AUDIO==av_stream->codecpar->codec_type){
printf("-----audio info -----\n");
//index 是解服用之后每个流的唯一身份标识
printf("audio index is %d \n",av_stream->index);
printf("sample rate %dHZ \n",av_stream->codecpar->sample_rate);
printf("av_stream->codecpar->format is %d \n",av_stream->codecpar->format);
switch(av_stream->codecpar->format){
case AV_SAMPLE_FMT_FLTP:
printf("audio format is AV_SAMPLE_SLTP \n");
break;
case AV_SAMPLE_FMT_S16:
printf("audio format is AV_SAMPLE_FMT_S16 \n");
break;
case AV_SAMPLE_FMT_S32:
printf("audio format is AV_SAMPLE_FMT_S32 \n");
break;
case AV_SAMPLE_FMT_S16P:
printf("audio format is AV_SAMPLE_FMT_S16P \n");
break;
default:
printf("audio format is other \n");
break;
}
//音频通道数量
printf("channel number is %d \n",av_stream->codecpar->channels);
switch(av_stream->codecpar->codec_id){
case AV_CODEC_ID_AAC:
printf("音频压缩编码格式 是AV_CODEC_ID_AAC \n");
break;
case AV_CODEC_ID_MP3:
printf("音频压缩编码格式 AV_CODEC_ID_MP3 \n");
break;
default:
printf("音频压缩编码格式 %d \n",av_stream->codecpar->codec_id);
break;
}
//音频总时长单位是秒 注释如果把时间单位放大为毫秒或者微妙 音频总时长根音频总时长不一定相等
if(av_stream->duration!=AV_NOPTS_VALUE){
int duration_audio=(av_stream->duration)*av_q2d(av_stream->time_base);
printf("audio duration: %02d:%02d:%02d\n",
duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60));
}else{
printf("audio duration unknown");
}
audioindex = i; // 获取音频的索引
}else if(AVMEDIA_TYPE_VIDEO==av_stream->codecpar->codec_type){
printf("----- 视频信息 -----\n");
printf("视频流index:%d\n", av_stream->index);
printf("fps is %lffps \n",av_q2d(av_stream->avg_frame_rate));
switch(av_stream->codecpar->codec_id){
case AV_CODEC_ID_H264:
printf("video codec:H264\n");
break;
case AV_CODEC_ID_MPEG4:
printf("video codec:MPEG4\n");
break;
default:
printf("video codec_id:%d\n", av_stream->codecpar->codec_id);
break;
}
printf("视频width:%d,height:%d\n",av_stream->codecpar->width,av_stream->codecpar->height);
if(av_stream->duration!=AV_NOPTS_VALUE){
int video_duration=av_stream->duration*av_q2d(av_stream->time_base);
printf("video duration: %02d:%02d:%02d\n",
video_duration / 3600,
(video_duration % 3600) / 60,
(video_duration % 60)); //将视频总时长转换为时分秒的格式打印到控制台上 }else{
printf("video duration unknown");
}
videoindex = i;
}
}
AVPacket* pkt=av_packet_alloc();
int pkt_count=0;
int print_max_count = 6;
printf("------start av_read_frame------\n");
while(1){
ret= av_read_frame(av_format_cxontext,pkt);
if(ret<0){
printf("av read frame end\n");
break;
}
if(pkt_count++<print_max_count){
if(pkt->stream_index==audioindex){
printf("audio pts:%lld\n",pkt->pts);
printf("audio dts:%lld\n",pkt->dts);
printf("audio size:%d\n",pkt->size);
printf("audio pos:%lld\n",pkt->pos);
printf("audio duration::%lf\n\n",pkt->duration*av_q2d(av_format_cxontext->streams[audioindex]->time_base));
}else if(pkt->stream_index==videoindex){
printf("video pts:%lld\n",pkt->pts);
printf("video dts:%lld\n",pkt->dts);
printf("video size:%d\n",pkt->size);
printf("video pos:%lld\n",pkt->pos);
printf("video duration: %lf\n\n",
pkt->duration * av_q2d(av_format_cxontext->streams[videoindex]->time_base));
}else{
printf("unknown stream_index:%d\n",pkt->stream_index);
}
}
av_packet_unref(pkt);
}
if(pkt){
av_packet_free(pkt);
}
failed:
if(av_format_cxontext){
avformat_close_input(&av_format_cxontext);
}
通过解封装可以得到音频视频流相关的信息以及解码器相关的信息,这些信息会影响视频、音频的解码以及显示。
>>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864
>>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群: 739729163 领取