自己所负责的模块中使用到了ffmpeg,一直都很正常。但最近碰到了个奇怪的问题,使用av_read_frame连续读取摄像头实时视频流,运行一段时间后,该函数会返回AVERROR_EOF,代码如下:
void MediaSource::DataProvider::_RecvThread(void)
{
INFO_LOG(m_LogHandler, "recv thread ENTER, url : %s", m_URLStr.c_str());
const std::list<AVPacket>::size_type MAX_CACHED_FRAMES = ConfigParser::GetInstance().GetDecodingConf().CacheFrameNum;
while (m_RunningFlag) {
AVPacketWithTimestamp pkt;
int ret = av_read_frame(m_AVFormatContext, &pkt.pkt);
if (ret != 0) {
if (ret == AVERROR_EOF) {
m_EOFProcFunc();
INFO_LOG(m_LogHandler, "media recv finished, url : %s", m_URLStr.c_str());
break;
}
m_OfflineProcFunc();
INFO_LOG(m_LogHandler, "media recv offline, url : %s", m_URLStr.c_str());
break;
}
else {
if (pkt.pkt.stream_index != m_VideoStreamID) {
av_packet_unref(&pkt.pkt);
continue;
}
auto now = std::chrono::high_resolution_clock::now();
time_t timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
pkt.sec = timestamp / 1000;
pkt.msec = timestamp % 1000;
std::lock_guard<std::mutex> lk(m_PacketMutex);
m_PacketList.push_back(pkt);
m_PacketListNotEmptyCond.notify_one();
}
std::unique_lock<std::mutex> lk(m_PacketMutex);
if (m_PacketList.size() >= MAX_CACHED_FRAMES) {
while (m_RunningFlag && m_PacketList.size() >= MAX_CACHED_FRAMES)
m_PacketListNotFullCond.wait_for(lk, std::chrono::milliseconds(1000));
}
}
INFO_LOG(m_LogHandler, "recv thread LEAVE, url : %s", m_URLStr.c_str());
}
如果读取视频文件,该函数返回AVERROR_EOF是件很正常的事,但读取实时视频流是件绝对不可能的事,因为实时视频是不可能结束的(关闭摄像头除外)。于是查看libavformat中rtsp.c的代码,究其原理,看自己是否调用有误,最后发现其中有五处返回AVERROR_EOF的地方,但也没有看出什么来。没办法,只能进行抓包进行分析。最终发现,摄像头在rtsp信令通道上发型了RST指令(why?不知道),导致视频流中断。那么问题又来了,既然连接断开,无论调用recv,还是send,都会返回一个负值,怎么ffmpeg会返回AVERROR_EOF?最终还是得回到ffmpeg代码。
于是,又仔细地看了几遍返回AVERROR_EOF的地方。最终发现,有两处比较可疑,第一个可疑之处如下所示:
上面的代码并没有对读取结果进行判断,只要不等于1,就认为文件结束。于是,修改此处代码,进行标记,查看调用结果。
重新编译程序后运行,发现此处代码果然有问题,此时ret=-104,errno=104(Connection reset by peer),然而代码并没有对104错误进行处理。另一处可以代码如下:
上面的代码直接返回的ff_rtsp_read_reply结果,对此处代码进行标记。
重新编译程序后运行,发现该处程序也被调用。
本以为自己使用ffmpeg版本(3.3)较低,才会存在该问题,但去github查看了最新版本的ffmpeg代码,仍旧如此。为了解决该问题,只好在判断返回值的同时,判断一下errno,是否是视频流真正结束,最后代码如下:
void MediaSource::DataProvider::_RecvThread(void)
{
INFO_LOG(m_LogHandler, "recv thread ENTER, url : %s", m_URLStr.c_str());
const std::list<AVPacket>::size_type MAX_CACHED_FRAMES = ConfigParser::GetInstance().GetDecodingConf().CacheFrameNum;
while (m_RunningFlag) {
AVPacketWithTimestamp pkt;
int ret = av_read_frame(m_AVFormatContext, &pkt.pkt);
if (ret != 0) {
if (ret == AVERROR_EOF && errno == 0) { //增加对errno的判断,判断文件结束是否是由网络异常造成的假象
m_EOFProcFunc();
INFO_LOG(m_LogHandler, "media recv finished, url : %s", m_URLStr.c_str());
break;
}
m_OfflineProcFunc();
INFO_LOG(m_LogHandler, "media recv offline, url : %s, errno = %d", m_URLStr.c_str(), errno);
break;
}
else {
if (pkt.pkt.stream_index != m_VideoStreamID) {
av_packet_unref(&pkt.pkt);
continue;
}
auto now = std::chrono::high_resolution_clock::now();
time_t timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
pkt.sec = timestamp / 1000;
pkt.msec = timestamp % 1000;
std::lock_guard<std::mutex> lk(m_PacketMutex);
m_PacketList.push_back(pkt);
m_PacketListNotEmptyCond.notify_one();
}
std::unique_lock<std::mutex> lk(m_PacketMutex);
if (m_PacketList.size() >= MAX_CACHED_FRAMES) {
while (m_RunningFlag && m_PacketList.size() >= MAX_CACHED_FRAMES)
m_PacketListNotFullCond.wait_for(lk, std::chrono::milliseconds(1000));
}
}
INFO_LOG(m_LogHandler, "recv thread LEAVE, url : %s", m_URLStr.c_str());
}