libavformat是FFmpeg中处理音频、视频以及字幕封装和解封装的通用框架,内置了很多处理多媒体文件的Muxer和Demuxer,它支持如AVInputFormat的输入容器和AVOuputFormat的输出容器,同时也支持基于网络的一些流媒体协议,如HTTP、RTSP、RTMP等
FFmpeg数据结构:
AVFormatContext 这个结构体描述了一个媒体文件或媒体的构成和基本信息
AVCodecContext 这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信
AVCodec 存储编码器信息的结构体 ,结构体具体内容
AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。
AVPacket 存储压缩编码数据相关信息的结构体
SwsContext 结构体主要用于视频图像的转换,主要用两个函数来实现对结构体的操作:sws_scale,sws_getContext
AVstream 存储每一个视频、音频流信息的结构体
AVIOContext 管理输入输出数据的结构体
详细的内容可去雷霄骅老师的博客https://blog.csdn.net/leixiaohua1020/article/details/15811977
音视频流封装
#函数介绍
avformat_alloc_output_context2()
初始化输出上下文
avformat_free_context()
释放输出上下文
avformat_new_stream()
申请新的数据流
avcodec_parameters_copy()
copy参数
avformat_write_header()
生成多媒体文件头
av_write_frame()/av_interleaved_write_frame()
写入数据
av_write_trailer()
写入尾部
(1)API注册
av_register_all();
(2)申请AVFormatContext
在使用FFmpeg进行,封装格式相关操作时,需要使用AVFormat作为操作的上下文线索
AVOutputFotmat *fmt;
AVFormatcontext * oc;
avformat_alloc_output_context2();//初始化输出上下文
if(!oc)
{
printf("cannot alloc flv format\n");
return 1;
}
fmt = oc->oformat;
(3)申请AVStream
申请一个将要写入的AVStream流,AVStream流将主要作为存放音频、视频、字幕数据流使用:
AVStream *st;
AVCodecContext *c;
st = avformat_new_stream(oc,NULL);
if(!ost->st)
{
fprintf(stderr,"Could not allocate stream\n");
exit(1);
}
st->id = oc->nb_streams-1;
至此。需要将Codec与AVStream进行对应,可以根据视频的编码参数对AVCodecContext的参数进行设置:
c->codec_id = codec_id;
c->bit_rate = 400000;
c->width = 352;
c->height = 288;
st->time_base = (AVRational){1,25};
c->time_base = st->time_base;
c->gop_size = 12;
c->pix_fmt = AV_PIX_FMT_YUV420P;
然后为了兼容新版本的FFnpeg的AVCodecparameters结构,需要做一个参数copy操作:
ret = avcodec_parameters_from_context(ost->st->codecpar,c);
if(ret < 0)
{
printf("Counld not copy the stream paramters\n");
exit(1);
}
至此,相关参数设置完毕,可以通过av_dump_format接口查看到参数信息
(4)增加目标容器的头信息
在操作封装格式时,有些封装格式需要写入头不信息,所以在FFmpeg写封装数据时,需要先写封装格式的头部:
ret = avformat_write_header(oc,&pkt);
if(ret < 0)
{
printf("Error occurred when opending output file:%s\n",av_err2str(ret));
return 1;
}
(5)写入帧数据
在FFmpeg操作数据包时,均采用写帧操作进行音视频数据包的写人,而每一帧在常规情况下均使用AVPacket结构进行音视频数据的存储,AVPacket 结构中包含了PTS
DTS、Data等信息,数据在写人封装中时,会根据封装的特性写人对应的信息:
AVFormatContext *ifmt ctx = NULL;
AVIOContext* read_in =avio_alloc_ context(inbuffer, 32 * 1024 ,0 ,NULL,get input_ buf fer, NULL, NULL) ;//avio_alloc_context是ffmpeg中用来读写buffer的函数,根据读写需求分配函数参数
if(read in= =NULL)
goto end ;
ifmt_ ctx->pb=read_in;
ifmt_ctx->flags=AVFMT_FLAG_CUSTOM_IO;
if ((ret = avformat_ open_ input(&ifmt_ ctx, "h264", NULL, NULL)) < 0)
{
av_log(NULL,AV_LOG_ERROR,"Cannot get h264 memory data\n");
return ret;
}
while(1)
{
AVPacket pkt = {0};
av_init_packet(&pkt);
ret = av_read_frame(ifmt_ctx,&pkt);
if(ret < 0)
{
break;
}
av_packet_rescale_ts(pkt,*time_base,st->time_base);
pkt->stream_index = st->index;
return av_interleaved_write_frame(fmt_ctx,pkt);
}
如上述这段代码所示,从内存中读取数据,需要将通过avio_alloc_context接口中获得的buffer与AVFormatConext建立关联,然后再像操作文件一样进行操作即可, 接下来就
可以从AVFormatContext中获得packet,然后将packet通过av_ interleaved_write_frame 写入到输出的封装格式中。
(6)写容器尾信息
在写人数据即将结束时,将会进行收尾工作,例如写人封装格式的结束标记等,例如FLV的sequence end标识等:
av_write_trailer(oc);
音视频解封装
#函数介绍
avformat_open_input() //打开多媒体文件
avformat_find_stream_info() //该函数可以读取一部分视音频数据并且获得一些相关的信息
av_init_packet()//初始化packet值
av_read_frame()// 作用是读取码流中的音频若干帧或者视频一帧
decode_packet()
av_packet_ unref()//将缓存空间的引用计数-1;并将packet中的其它字段设置为初始值.如果引用计数为0, 则释放缓存空间
avformat_close_input()// 该函数用于关闭一个AVFormatContext
解封装步骤
(1)API注册
av_register_all();
(2)构建AVFormatContext
在注册过FFmpeg之后,可以声明输入的封装结构体为主线,然后通过输入文件或者输入流媒体流链接为封装结构的句柄:
static AVFormatContext *fmt_ctx = NULL;
//打开输入文件,分配格式上下文
if(avformat_open_input(&fmt_ctx,input_filename,NULL,NULL) < 0)
{
fprintf(stderr,"Could not open source file %s\n",src_filename);
exit(1);
}
如上述代码所示,可通过avformat_open_input接口将input_filename句柄挂载至fmt_ctx结构里,之后FFmpeg即可对fmt_ctx进行操作
(3)查找音视频流信息
在输人封装与AVFormatConlext结构做好关联之后,即可通过avformat_find_stream_info从AVFormatContext中建立输人文件的对应的流信息:
/* retrieve stream information */
if(avformat_find_stream_info(fmt_ctx, NULL) < 0)
{
fprintf(stderr, "Could not find stream information\n");
exit(1);
}
如上述代码所示,从fmt_ctx 中可以获得音视频流信息。
(4)读取音视频流
获得音视频流之后,即可通过av_read_frame 从fmt_ctx中读取音视频流数据包,将音视频流数据包读取出来存储至AVPackets 中,然后就可以通过对AVPackets包进行判断,
确定其为音频、视频、字幕数据,最后进行解码,或者进行数据存储:
/* initialize packet, set data to NULL, let the demuxer fill it。*/
//初始化数据包,将数据设为空,让解复用器填充它
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
/* read frames from the file */
//从文件中读取帧
while(av_read_frame(fmt_ctx, &pkt) > 0) //pkt是输出参数输出的是一帧的数据
{
AVPacket orig_pkt = pkt;
do {
ret = decode_packet(&got_frame, pkt);
if (ret < 0)
break;
pkt.data += ret;
pkt.size -= ret;
} while (pkt.size > 0);
av_packet_unref(&orig_pkt);
}
如上述代码所示,通过循环调用av read frame读取fmt ct中的数据至pkt中,然后解码pkt,如果读取fmt_ctx中的数据结束,则退出循环,开始执行结束操作。
(5)收尾
执行结束操作主要为关闭输人文件以及释放资源等:
avformat_close_input (&fmt_ ctx);
音视频文件转封装
#函数介绍
avcodec_copy_context()//主要的功能就是编码参数上下文的拷贝
av_rescale_q_rnd()//从新计算时间戳
主要步骤:
(1)API注册
av_register_all();
(2)构建输入AVFormatContext
注册之后,打开输入文件与AVFormatContext建立关联:
AVFormatContext *ifmt_ctx = NULL;
if((ret = avformat_open_input(&ifm_ctx,in_filename,0,0)) < 0)
{
fprintf(stderr,"Could not open input file '%s'",in_filename);
goto end;
}
(3)查找流信息
建立关联之后,与解封装操作类似,可以通过avformat_find_stream_info获取流信息:
if((ret = avformat_find_stream_info(ifmt_ctx,0)) < 0)
{
fprintf(stderr,"Failed to retrieve input stream information");
goto end;
}
(4)构建输出AVFormatContext
输入文件打开完成后,可以打开输入文件并与AVFormatContext建立关联:
AVFormatContext *ofmt_ctx = NULL;
avformat_alloc_output_context2(&ofmt_ctx,NULL,NULL,out_filename);
if(!ofmt_ctx)
{
fprintf(stderr,"Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
(5)申请AVStream
建立关联之后,需要申请输入的stream信息与输出的stream信息,输入stream信息可以从ifmt_ctx中获得,但是存储至ofmt_ctx中的stream信息需要申请独立空间:
AVStream *out_stream = avformat_new_stream(ofm_ctx,in_stream->codec->codec);
if(!out_stream)
{
fprintf(stderr,"Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
}
(6)stream信息的复制
输出的stream信息建立完成之后,需要从输人的stream中将信息复制到输出的stream中,由于重点介绍转封装,所以stream的信息不变,仅仅是改变了封装格式:
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if(ret<0)
{
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
}
在新版本的FFmpeg中,AVStream中的AVCodecContext被逐步弃用,转而使用AVCodecParameters,所以在新版本的FFmpeg中可以增加一个操作步骤:
ret = avcodec_parameters_from_context(out stream->codecpar,out_ stream->codec );
if(ret<0)
{
fprintf(stderr,"Could not copy the stream parameters\n");
}
(7)写文件头信息
输出文件打开之后,根据前面介绍的封装方式,接下来可以进行好文件头的操件:
ret = avfomat_write_heder(ofmt_ctx, NULL);
if(ret<0)
{
fprintf(stderr,"Error occurred when opening output file\n");
}
(8)数据包读取和写人
输人与输出均已经打开,并与对应的AVFomaCcnet建立了关联, 接下来可以从输入格式中读取数据包,然后将数据包写人至输出文件中,当然,随着输入的封装格式与输出的封装格式的差测化.时间藏也需要进行对政高件事改变然,
while (1)
{
AVStream *in_stream, *out_ stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if(ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streamslpkt.stream_index];
/* copy packet */
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base,out_stream->time_base, AV_ROUND_NEAR_INP | AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base,out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt. duration = av_rescale_q(pkt.duration, in_stream->time_base, out_ stream->time_base);
pkt.pos = -1;
ret = av_interleaved_write_frame(ofmt_ctx,&pkt);
if(ret<0)
{
fprintf(stderr, "Error muxing packet\n");
break;
}
av_ packet _unref(&pkt);
}
(9)写文件尾信息
解封装读取数据并将数据写人新的封装格式的操作已经完成,接下来即可进行写文件尾至输出格式的操作:
av_write_trailer(ofmt_ctx);
(10)收尾
输出格式写完之后即可关闭输入格式
avformat_close_input(&ifmt_ctx);
详细的代码案例可以查看 雷霄骅老师的博客
https://blog.csdn.net/leixiaohua1020/article/details/25422685