ffmpeg調用av_read_frame讀取實時視頻流返回AVERROR_EOF

自己所負責的模塊中使用到了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());
}

 

發佈了27 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章