ffmpeg解码带B帧视频时,输入包和输出帧的对应关系分析

 最近又在倒腾ffmpeg了,拿抖音下载的mp4视频测试解码,发现是带B帧的,分析了下ffmpeg解码时输入输出的细节,算是解决了些以前的疑问。附上分析日志

n4.4.4-94-g5d07afd482-20240331

0x14dc80
run in
0x376a300
"open imput ok! {D:\\Qt\\ffmpeg\\ffmpeg-n4.4.4-source+builds\\ffmpeg-n4.4.4-94-g5d07afd482-win64-gpl-4.4\\bin\\1.mp4} video index = 0"
0x174700 0x376a300 0x108200 0
openDecode for stream type: video(0), format: h264(27)
decoder is ready
new QImage 720 1280
sws_getContext 720 1280

##################################################################
日志说明
frame的pkt_dts, pkt_pts不用关心,对frame来说,只关心其pts即可
packet的dts和pts都是重要的
key=1表示是关键包(I包)
#号后面是pict_type,1就是I帧,2=p帧,3=B帧

##################################################################
	# (下面)拿到首个packet,dts=-10,编码器认为这是当前的时间。
	# 解码完成,输出时找缓存帧序列,没有pts值与当前时间相同的帧,不输出。
	# 这个packet是个I包,其实是可以直接输出的,但是这种滑窗的机制决定了暂不输出。
	# 这样可以保证正式输出开始后,后续frame都能连续输出。
	# 也就是后续都是输入一个packet,输出一个frame(frame可能
	# 是当前packet解码得到的,也可能是之前packet解码得到的。)
	# 解码器保证了输出时的帧顺序和动画本身显示顺序是一致的。
[0] packet: <dts=-10, pts=1014, key: 1, size=55634>                  I包
got_frame =  0                                      


	# 一样的,更新当前时间,解码,输出也没有找到pts=502的帧,不输出
	# 这实际是个P包,当前不能去显示它,因为它显示的帧号还靠后
[1] packet: <dts=502, pts=3062, key: 0, size=14410>                  P包
got_frame =  0

	# 一样的,更新当前时间为dts=1024,解码完成,
	# 找到一个缓存帧pts=1014,输出
	# 这个帧就是第一个packet解码得到的帧,是个i帧。
	# frame序列输出正式开始,这套机制保证后面都能连续输出
	# 这其实是个中间B包,其依赖两端的I、P帧
[2] packet: <dts=1014, pts=2038, key: 0, size=2816>                  B2包
[2] frame: <pkt_dts=1014, pkt_pts = 1014, pts=1014, key:0 #1#>       I帧


	# 这包数据解码完成,正好找到自身pts与当前时间相等,直接输出
	# 这其实是第一个B包(B1),其依赖前面的I帧和后一个B帧、
	# 以及最后的P帧
[3] packet: <dts=1526, pts=1526, key: 0, size=742>                   B1包
[3] frame: <pkt_dts=1526, pkt_pts = 1526, pts=1526, key:0 #3#>       B1帧


	# 输入第三个B包(B3)
[4] packet: <dts=2038, pts=2550, key: 0, size=339>                   B3包
[4] frame: <pkt_dts=2038, pkt_pts = 2038, pts=2038, key:0 #3#>       B2帧

	# 输入下一轮的P包,I帧还是前面的
[5] packet: <dts=2550, pts=5110, key: 0, size=14520>                 P包'
[5] frame: <pkt_dts=2550, pkt_pts = 2550, pts=2550, key:0 #3#>       B3帧

	# 下一轮的中间B包(B2包')
[6] packet: <dts=3062, pts=4086, key: 0, size=4801>                  B2包'
[6] frame: <pkt_dts=3062, pkt_pts = 3062, pts=3062, key:0 #2#>       P帧(前面第一个P帧)

##################################################################
至此,一小轮的输入输出完成了,输入7个包,输出5帧
输入顺序:I->P->B2->B1->B3->P'->B2'
输出顺序:       I->B1->B2->B3->P
输出顺序其实就是我们希望的显示顺序
文件流、网络流(只要是packet序列,其实都不一定是按照自然顺序(显示顺序)存放或传输的
(如果只有I/P编码,则和显示顺序一致,有B帧存在的情况,则未必了,比如这里的情况)
ffmpeg或者其它视频编解码器,帮我们屏蔽了这些细节,使输出的帧就是显示序列

##################################################################
[7] packet: <dts=3574, pts=3574, key: 0, size=2267>
[7] frame: <pkt_dts=3574, pkt_pts = 3574, pts=3574, key:0 #3#>


[8] packet: <dts=4086, pts=4598, key: 0, size=193>
[8] frame: <pkt_dts=4086, pkt_pts = 4086, pts=4086, key:0 #3#>


[9] packet: <dts=4598, pts=7158, key: 0, size=14551>
[9] frame: <pkt_dts=4598, pkt_pts = 4598, pts=4598, key:0 #3#>



[10] packet: <dts=5110, pts=6134, key: 0, size=5257>
[10] frame: <pkt_dts=5110, pkt_pts = 5110, pts=5110, key:0 #2#>



[11] packet: <dts=5622, pts=5622, key: 0, size=726>
[11] frame: <pkt_dts=5622, pkt_pts = 5622, pts=5622, key:0 #3#>
[12] packet: <dts=6134, pts=6646, key: 0, size=2169>
[12] frame: <pkt_dts=6134, pkt_pts = 6134, pts=6134, key:0 #3#>
[13] packet: <dts=6646, pts=9206, key: 0, size=18546>
[13] frame: <pkt_dts=6646, pkt_pts = 6646, pts=6646, key:0 #3#>
[14] packet: <dts=7158, pts=8182, key: 0, size=5896>
[14] frame: <pkt_dts=7158, pkt_pts = 7158, pts=7158, key:0 #2#>
[15] packet: <dts=7670, pts=7670, key: 0, size=816>
[15] frame: <pkt_dts=7670, pkt_pts = 7670, pts=7670, key:0 #3#>
[16] packet: <dts=8182, pts=8694, key: 0, size=2840>
[16] frame: <pkt_dts=8182, pkt_pts = 8182, pts=8182, key:0 #3#>
[17] packet: <dts=8694, pts=11254, key: 0, size=14489>
[17] frame: <pkt_dts=8694, pkt_pts = 8694, pts=8694, key:0 #3#>
[18] packet: <dts=9206, pts=10230, key: 0, size=3248>
[18] frame: <pkt_dts=9206, pkt_pts = 9206, pts=9206, key:0 #2#>
[19] packet: <dts=9718, pts=9718, key: 0, size=866>
[19] frame: <pkt_dts=9718, pkt_pts = 9718, pts=9718, key:0 #3#>
[20] packet: <dts=10230, pts=10742, key: 0, size=345>
[20] frame: <pkt_dts=10230, pkt_pts = 10230, pts=10230, key:0 #3#>
[21] packet: <dts=10742, pts=12790, key: 0, size=14771>
[21] frame: <pkt_dts=10742, pkt_pts = 10742, pts=10742, key:0 #3#>
[22] packet: <dts=11254, pts=11766, key: 0, size=3836>
[22] frame: <pkt_dts=11254, pkt_pts = 11254, pts=11254, key:0 #2#>

附上加了排水模式处理的日志,包和帧单独计数(每行开头的[ ])

排水模式用于把解码器最后的几帧取出来,规则就是按pts顺序依次输出。因为packet本身无效,因此输出帧的dts也无效

ref: FFmpeg-视频解码-YUV数据.md · 苦涩冰糖/myBlogMarkdown - Gitee.com

n4.4.4-94-g5d07afd482-20240331

0xa7ba00
run in
0x37b0180
"open imput ok! {D:\\Qt\\ffmpeg\\ffmpeg-n4.4.4-source+builds\\ffmpeg-n4.4.4-94-g5d07afd482-win64-gpl-4.4\\bin\\1.mp4} video index = 0"
0xaa0900 0x37b0180 0xa3b900 0
openDecode for stream type: video(0), format: h264(27)
decoder is ready
new QImage 720 1280
sws_getContext 720 1280
[0] packet: <dts=-10, pts=1014, key: 1, size=55634>
[1] packet: <dts=502, pts=3062, key: 0, size=14410>
[2] packet: <dts=1014, pts=2038, key: 0, size=2816>
[0] frame: <pkt_dts=1014, pts=1014, key:0 #1#>
[3] packet: <dts=1526, pts=1526, key: 0, size=742>
[1] frame: <pkt_dts=1526, pts=1526, key:0 #3#>
[4] packet: <dts=2038, pts=2550, key: 0, size=339>
[2] frame: <pkt_dts=2038, pts=2038, key:0 #3#>
[5] packet: <dts=2550, pts=5110, key: 0, size=14520>
[3] frame: <pkt_dts=2550, pts=2550, key:0 #3#>
[6] packet: <dts=3062, pts=4086, key: 0, size=4801>
[4] frame: <pkt_dts=3062, pts=3062, key:0 #2#>
[7] packet: <dts=3574, pts=3574, key: 0, size=2267>
[5] frame: <pkt_dts=3574, pts=3574, key:0 #3#>
[8] packet: <dts=4086, pts=4598, key: 0, size=193>
[6] frame: <pkt_dts=4086, pts=4086, key:0 #3#>
[9] packet: <dts=4598, pts=7158, key: 0, size=14551>
[7] frame: <pkt_dts=4598, pts=4598, key:0 #3#>
[10] packet: <dts=5110, pts=6134, key: 0, size=5257>
........中间部分省略
[248] frame: <pkt_dts=127990, pts=127990, key:0 #2#>
[251] packet: <dts=128502, pts=129526, key: 0, size=50>
[249] frame: <pkt_dts=128502, pts=128502, key:0 #2#>
[252] packet: <dts=129014, pts=130038, key: 0, size=3030>
[250] frame: <pkt_dts=129014, pts=129014, key:0 #1#>
[253] packet: <dts=129526, pts=130550, key: 0, size=34758>
[251] frame: <pkt_dts=129526, pts=129526, key:0 #2#>
[254] packet: <dts=130038, pts=131574, key: 0, size=11346>
[252] frame: <pkt_dts=130038, pts=130038, key:0 #2#>
[255] packet: <dts=130550, pts=131062, key: 0, size=223>
[253] frame: <pkt_dts=130550, pts=130550, key:0 #1#>
[256] packet: <dts=131062, pts=132086, key: 0, size=379>
[254] frame: <pkt_dts=131062, pts=131062, key:0 #3#>
av_read_frame: End of file

draining mode
[255] frame: <pkt_dts=-9223372036854775808, pts=131574, key:-1 #2#>
[256] frame: <pkt_dts=-9223372036854775808, pts=132086, key:-1 #2#>
avcodec_receive_frame: End of file (-541478725)

draining over
total: 257 257
run out

要使用FFmpeg解码B视频,可以结合使用avcodec_send_packet()和avcodec_receive_frame()函数进行解码,然后使用av_write_frame()函数将解码后的写入输出文件。具体步骤如下: 1. 初始化FFmpeg ``` av_register_all(); avcodec_register_all(); ``` 2. 打开输入文件 ``` AVFormatContext *inputFormatCtx = NULL; avformat_open_input(&inputFormatCtx, inputFilePath, NULL, NULL); avformat_find_stream_info(inputFormatCtx, NULL); ``` 3. 找到视频流 ``` AVCodec *inputCodec = NULL; int videoStreamIndex = av_find_best_stream(inputFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &inputCodec, 0); AVCodecContext *inputCodecCtx = inputFormatCtx->streams[videoStreamIndex]->codec; ``` 4. 打开解码器 ``` avcodec_open2(inputCodecCtx, inputCodec, NULL); ``` 5. 初始化输出文件 ``` AVFormatContext *outputFormatCtx = NULL; avformat_alloc_output_context2(&outputFormatCtx, NULL, NULL, outputFilePath); avio_open(&outputFormatCtx->pb, outputFilePath, AVIO_FLAG_WRITE); ``` 6. 写入输出文件头 ``` avformat_write_header(outputFormatCtx, NULL); ``` 7. 读取数据并解码 ``` AVPacket packet; AVFrame *frame = av_frame_alloc(); while (av_read_frame(inputFormatCtx, &packet) == 0) { if (packet.stream_index == videoStreamIndex) { avcodec_send_packet(inputCodecCtx, &packet); while (avcodec_receive_frame(inputCodecCtx, frame) == 0) { // do something with the decoded frame, e.g. write to output file av_write_frame(outputFormatCtx, frame); } } av_packet_unref(&packet); } ``` 8. 写入输出文件尾 ``` av_write_trailer(outputFormatCtx); ``` 9. 释放资源 ``` avformat_close_input(&inputFormatCtx); avcodec_free_context(&inputCodecCtx); avformat_free_context(inputFormatCtx); avformat_free_context(outputFormatCtx); av_frame_free(&frame); ``` 这样,就可以使用FFmpeg解码B视频了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值