自己所负责的模块中使用到了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的地方。最终发现,有两处比较可疑,第一个可疑之处如下所示:

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_实时视频流

上面的代码并没有对读取结果进行判断,只要不等于1,就认为文件结束。于是,修改此处代码,进行标记,查看调用结果。

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_ide_02

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_AVERROR_EOF_03

重新编译程序后运行,发现此处代码果然有问题,此时ret=-104,errno=104(Connection reset by peer),然而代码并没有对104错误进行处理。另一处可以代码如下:

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_实时视频流_04

上面的代码直接返回的ff_rtsp_read_reply结果,对此处代码进行标记。

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_实时视频_05

重新编译程序后运行,发现该处程序也被调用。

FFmpegFrameRecorder 录制音频 ffmpegframerecorder实时_实时视频流_06

本以为自己使用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());
}