FFmpeg相机花图问题解决方法
一、问题现象
使用FFmpeg进行进行相机rtsp连接获取相机码流并解码在本地显示,这个过程中有遇见相机花图等问题,排查后发现有花图。正常情况下图像为
出现花图的情况时图像如下:
二、解决方法
1、设置相机参数--将影响网络传输和解码依赖性的参数都调低
拿海康相机为例:网页登陆相机192.168.1.252 输入默认用户名:admin密码:12345之后,登陆相机,选择“配置”->音视频 设置参数
将图像质量调到最低、帧率调低(25修改为12或8)、码率上限(最好为2MB以下)
2、修改rtsp连接方式
rtsp传输有以下几种方式:
UDP传输:Transport:RTP/AVP
TCP传输:Transport:RTP/AVP/TCP
RAW UDP传输:Transport:RAW/RAW/UDP
ffmpeg提供udp和tcp的支持
udp方法如下:
- AVDictionary* options = NULL;
- av_dict_set(&options, "rtsp_transport", "udp", 0);
- avformat_open_input(&m_pRtspFmt, m_szRtspUrl, NULL, &options);
tcp方法如下:
- AVDictionary* options = NULL;
- av_dict_set(&options, "rtsp_transport", "tcp", 0);
- avformat_open_input(&m_pRtspFmt, m_szRtspUrl, NULL, &options);
将连接修改为tcp连接 则能减少花屏,但会有一定程度上的卡顿。
3、增加udp连接方式缓冲区减少丢包
打开ffmpeg源码(我的博客里有怎么编译源码 Windows下编译FFmpeg详解_ffmpeg windows编译_zhouyongku的博客-CSDN博客 )udp.c,可以做这样的实验 在方法2用udp传输的情况下 将udp.c中的UDP_MAX_PKT_SIZE 缩小10倍,再将编译好的ffmpeg库拿来用,则会发现花屏更加剧烈。于是将UDP_MAX_PKT_SIZE放大10倍,则基本上很难再出现花屏现象。
- #define UDP_TX_BUF_SIZE 32768
- #define UDP_MAX_PKT_SIZE (65536*10)
- #define UDP_HEADER_SIZE 8
4、多线程处理
测试过av_read_frame的耗时-发现在25帧/秒 4M比特流的情况下,av_read_frame耗时30~40ms,而解码也需要10ms左右,这就决定了1秒以内难以完成读取视频和解码视频的操作,即便是缓冲也不行,因为平均时间=25*(30~40+10)>1000ms。所以需要将读取rtsp码流作为一个线程,解码作为一个线程。下面是我封装好的存储rtsp线程码流的类
- /*******************************************************************
- * @版权信息:
- * @文件名称:PacketList.h
- * @摘 要:AVPacket列表管理类
- * @作 者:周勇
- * @当前版本:1.0.0
- * @日 期:2015年4月2日
- * @备 注:动态内存加载和释放
- *******************************************************************/
- #include "Common.h"
- class CPacketList
- {
- public:
- CPacketList();
- ~CPacketList();
- public:
- BOOL InputPacket(AVPacket *pktIn);
- AVPacketList* GetPacket();
- void FreeAllPacket();
- protected:
- AVPacketList *m_pHead; //列表头
- AVPacketList *m_pLast; //列表尾
- UINT m_nItemCnt; //共有多少未读项
- UINT m_nInputNum;
- UINT m_nOutputNum;
- CRITICAL_SECTION m_cs;
- };
- #include "PacketList.h"
- CPacketList::CPacketList()
- {
- m_pHead = NULL;
- m_pLast = NULL;
- m_nItemCnt = 0;
- m_nInputNum = 0;
- m_nOutputNum = 0;
- InitializeCriticalSection(&m_cs);
- }
- CPacketList::~CPacketList()
- {
- }
- BOOL CPacketList::InputPacket(AVPacket *pktIn)
- {
- BOOL bRet = TRUE;
- EnterCriticalSection(&m_cs);
- if (m_nItemCnt <= PKT_QUE_SIZE)
- {
- AVPacketList *pList = new AVPacketList;
- av_copy_packet(&pList->pkt, pktIn);
- pList->next = NULL;
- m_nItemCnt++;
- if (NULL == m_pHead)
- {
- m_pHead = pList;
- }
- if (m_pLast)
- {
- m_pLast->next = pList;
- }
- m_pLast = pList;
- m_nInputNum++;
- }
- else
- {
- //FreeAllPacket();
- bRet = FALSE;
- }
- LeaveCriticalSection(&m_cs);
- return bRet;
- }
- AVPacketList* CPacketList::GetPacket( )
- {
- AVPacketList *pPkt = NULL;
- int nPos = 0;
- EnterCriticalSection(&m_cs);
- if (m_nItemCnt&&m_pHead)
- {
- pPkt = m_pHead;
- if (pPkt == m_pLast)
- {
- m_pLast = NULL;
- }
- m_pHead = pPkt->next;
- m_nItemCnt--;
- m_nOutputNum++;
- }
- LeaveCriticalSection(&m_cs);
- return pPkt;
- }
- void CPacketList::FreeAllPacket()
- {
- EnterCriticalSection(&m_cs);
- AVPacketList *pNext = NULL;
- while (m_pHead)
- {
- pNext = m_pHead->next;
- av_free_packet(&m_pHead->pkt);
- delete m_pHead;
- m_pHead = pNext;
- }
- m_pLast = NULL;
- m_nItemCnt = 0;
- LeaveCriticalSection(&m_cs);
- }
用法:
- CPacketList m_pktList;
- //读取rtsp码流线程
- void CIPCamera::ReadStream()
- {
- AVPacket pkt;
- av_init_packet(&pkt);
- if (0 == av_read_frame(m_pRtspFmt, &pkt))
- {
- if (m_nInViStreamIdx == pkt.stream_index)
- {
- //将读取到的视频包存入队列
- if (!m_pktList.InputPacket(&pkt))
- {
- LOG(LOG_ERROR, "Channel[0x%x]CIPCamera::RreadStream to inputpacket failed of buffer buff!", this, m_nChannelID);
- }
- }
- }
- av_free_packet(&pkt);
- }
- //从视频包队列中取包进行解码
- void CIPCamera::DecodeStream()
- {
- AVPacketList *pList = NULL;
- AVPacket *pPkt = NULL;
- pList = m_pktList.GetPacket();
- if (pList)
- {
- pPkt = &pList->pkt;
- //读取到的是视频包
- if (m_nInViStreamIdx == pPkt->stream_index)
- {
- //解码
- if (DecodePacket(pPkt, m_pavfrm))
- {
- SendToUser();
- }
- }
- //释放packet
- av_free_packet(pPkt);
- //释放list
- delete pList;
- }
- }