学习ffmpeg时候遇到的一个死锁问题,六个队列互相等待造成的悲剧!
前情提要:
需求是实现一个修改流媒体文件封装格式的程序,然后中间还要穿插过滤对原视频进行一系列修改,旋转,裁剪啥的,最后重新将音频流和视频流封装成指定格式的文件。计划是,创建六个线程,每个线程都各司其职,然后中间的编解码数据使用队列进行传递。像这个样子。。。。
首先俺来解释一下,这六个线程和这六个队列的作用,队列名字起的太长了,这里就用首字母来代替啦。
- Demuxer: 解复用线程,用于接收流媒体文件如mp4, flv…, 将流媒体文件的音频流和视频流分别解码出Packet,然后将Packet送入DVP(DemuxerVideoPacketQueue)队列, 便于后面的模块可以从这个队列中获取Packet数据。
- VideoDecoder: 视频解码线程,这个线程只管两件事,从预先设置好的Packet队列(这里就是指的DVP)中读取Packet数据,然后将其解码成Frame,再送到预先设定好的Frame队列中(这里指的是VF)。
- VideoEncoder: 视频编码线程,这个线程也只管两件事,和上面说的VideoDecoder功能正好相反,从预先设定好的Frame队列(VF)中获取Frame,再将Frame按照指定编码格式编码成Packet,再传给预先设定好的Packet队列(MVP)。
- AudioDecoder: 音频解码线程,和上面的VideoDecoder线程一样,一个是解码视频一个是解码音频。
- AudioEncoder: 音频编码线程。
- Muxer: 复用线程,作用是将得到的音频流和视频流的Packet按照一定的顺序,封装进预先设定的流媒体文件中。
然后说一下这个队列的特性, 这里所有的队列都是阻塞队列,获取的时候如果队列中没东西可以拿了,那就会阻塞,同样新增的时候,如果达到了实现设定的最大长度的情况下,也会阻塞。
问题分析:
然后Demuxer中,由于解复用的速度较快,不需要等待任何人,因此在这里对队列设定了一个输出Packet队列长度不能超过设定长度的机制,每次都会先检查,当前队列长度是多少,如果>=设定的最大长度,那就会睡眠一段时间(问题就出现在这里,由于Demuxer管理两个队列,因此任何一个队列长度大于等于最大长度都会睡眠)。
然后Muxer中,也是一样的,Muxer会从实现设定好的音频队列和视频队列中取Packet,如果音频或视频的任意一个Packet队列没有数据的话也会阻塞。
然后问题来了,如下图。
从Demuxer逆时针方向看哇。。。。
从上面可以看到,六个队列互相等待,谁都动不了,分析了半天,总结出原因就是因为,Demuxer和Muxer设计的不合理,例如Muxer模块,为什么视频数据获取到了不能直接消费还需要等待音频数据呢,Demuxer也一样,视频队列是满了,但是音频队列没有满啊,可以先执行插入音频数据的逻辑,再回来处理视频的。
Demuxer和Muxer都分别等待了他们本不该等待的逻辑,导致了最后的死锁。
解决方案:
抓耳挠腮了两个多小时,从打印日志分析,到得出结论之后,下面就是解决了。既然原因是Demuxer和Muxer都等待了他们原本不该等待的逻辑,那就修改这部分代码就好了。
Demuxer将原先逻辑修改为,只有当两个队列同时满了,才睡眠一段时间, 如下:
Muxer将原本的peek阻塞等待修改为非阻塞等待,如下:
peekWithoutBlock
方法不同于peek
方法如果没有数据可以取,那就返回null值,后续的逻辑会根据null值进行特殊处理。
最后阻塞问题解决,舒服啦~