libavformat的使用

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 ,0NULL,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 = NULLavformat_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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值