内存优化说起来,做技术的都知道,减少拷贝次数,去掉不必要的内存空间申请,但是我们在项目的时候,难免要做数据储存,当然数据库是最大的内存使用表现;这里不讲数据库相关内容,主要讲讲流媒体线程中的内存优化;
一、 难题起源:项目内容,从摄像机取流,RTSP 协议,获取H264码流,解码本地显示,同时RTSP转RTMP 流,推流到服务器;
从整个的项目分析,需要用到缓存的地方有:RTSP 获取到一帧数据,H264 解码数据,YUV数据或者RGB数据就要多一个缓存,,RTSP 转RTMP数据,RTMP本地封包数据;
从 整个项目来讲:
1.申请的缓存越多,那么拷贝次数越多;
2.如果串行处理,效率还低,且会出现阻塞现象,因为RTSP 和RTMP的IO缓冲不同,接收包是一个独立事件,发送包也是一个独立事件;
3.如果缓冲区过大,靠储备数据来缓解机制,那么最后延迟也高,项目不达标;
4.如果视频的分辨率或者帧率增加,无疑又是另一种灾难;
宗上问题,提出解决方案:
- 采用多线程,让任务并行处理,不要串行处理事件;
- 尽可能减少拷贝,少申请内存空间;
针对上述方案,我的做法是:
1.RTSP 收取码流是一个独立的线程,接收数据IO的源头不能阻塞,这个是减少延迟的必要条件,如果源头数据收取慢了或者阻塞了,那后面的延迟必然高;
2.解码和显示在一个线程,解码是个很花力气的活儿,很多高级工程师,把延迟高推给解码慢了,从专业技术来讲,这个是不对的,很明显的推脱责任,延迟是和整个项目流程挂钩的,任何一个环节发生阻塞、滞后、或者什么其它的什么问题,都会导致延迟、卡顿等问题;想想,一根水管子,源头的水流断了、慢了、截留了、水龙头能出得了水吗?或者说出水的流量大吗?
3.H264帧数据封包FLV数据,发送数据在一个线程;
OK,上述的代码架构基本清晰了,别以为任务完成了,其实才刚刚开始;问题是,如果减少拷贝,且数据中的衔接是否得当,如何考察中间没有发生阻塞,或者说处理过程很慢?
的确,没有依据,或者说没有尺度是无法衡量每个模块之间的处理速度,这个是关键,别忘记,我们处理的是视频码流,视频是有GOP一说,帧率一说,没有错,就是这个数据,一秒多少帧,意味着,帧间隔时间是上限,处理时间只能小于这个平均时间(考虑I帧处理时间长的问题);
找到依据就好办了,评判代码的处理时间有了,只要模块代码处理时间小于这个时间,理论上来说,你写的流媒体代码效率都算高效的,另外,这个也是你找延迟问题的手段,别让那个冒牌货大牛往你身上泼脏水,让他解决他应该解决的问题;
言归正传,架构有了,减少拷贝如何做?这个问题很根据经验值来的,本人从事流媒体,吃过不少自认为大牛的人的暗亏,其实说实话,他们的确很厉害,代码写的也漂亮,但是我只认为他们的C C++功底很好,语法懂得很深,仅此而已。下面分享一下我的经验:
1.上面说了,模块间的划分是根据模块代码处理速度来的,判断依据是根据帧间隔来的,如果有音频,处理速度必然快,所以要等待,具体时间需要自己去算一下,也是根据视频的帧间隔来计算,主要是保证时间戳升序,播放时间间隔平稳或者说时间间隔误差平稳,那么音视频播放是没有为题的,处理音视频同步的方法也是基于这个来做的。我划分的模块依据都是根据实际各个模块的处理时间来划分的,因此,我个人觉得划分是应该没有问题的,如果读者有更好的划分方式,或者说有更好的架构,请留言,我会主动联系你,和你一起讨论一下。
2. OK 结构划分好了,接下来是减少拷贝,如何减少拷贝,这个还真跟个人能力有关系,数据穿梭,有了处理时间上的依据,内存在哪些C++类之间可以穿梭是需要考虑的,这个我就不细聊了,等会上一下代码,仅供参考,有好的方法,推荐给我,本人也是菜鸟,呵呵!
3. 上面说用到多线程,那么用多线程,用到的共享数据资源,一定马上会想到线程锁,其实,很多问题真的是我们的定式思维造成的,线程锁是不错的选择,但是有没有想过,加锁、解锁也是一种资源消耗,也是很耗时间的?那么有哪些解决方法呢?信号量、事件?这些都是选择,比锁的效率要高的吧?(这个不是万能的,复杂情况,还是要用线程锁);但是,我这里这种方案都没有用选择,学习过硬件解码的同学,如果只是学会了硬件接口调用,参数适配,这个就肤浅了,他的缓存机制有没有研究、多个芯片如何适配?这些才是精华。
4. fifo 机制,对用这个,这个就避免了用线程锁,且读写都自由,读的速度快与写的速度,但是,要保证,几个读的过程必须要写一次(周期),且读不能越界(崩溃)。
不多说,直接上代码,当然有更好的做法,节约资源性的做法,我这里只上一个简单的,复杂的或者更好的,需要自己去优化,我就不做过多的讲解:
类声明的H文件
#define MAXLEN 0x20000
#define BUFFERSIZE MAXLEN16
typedef struct strfifo
{
unsigned char buffer;
unsigned int write;
unsigned int read;
}str_fifo;
typedef struct datalen
{
int length;
}str_datalen;
class PacketHandler
{
public:
PacketHandler();
~PacketHandler();
void PfifoReset();
int PfifoPut(unsigned char* buffer, int len);
strfifo* PfifoGet();
void Pfifo_Init();
void Pfifo_free();
bool Pfifo_PCjudge();
public:
bool m_islittle;
strfifo* m_fifo;
};
实现C++文件
PacketHandler::PacketHandler() :m_fifo(0), m_islittle(false)
{
}
PacketHandler::~PacketHandler()
{
Pfifo_free();
}
void PacketHandler::PfifoReset()
{
if (m_fifo)
{
memset(m_fifo->buffer, 0, BUFFERSIZE);
m_fifo->read = 0;
m_fifo->write = 0;
}
}
strfifo* PacketHandler::PfifoGet()
{
return m_fifo;
}
bool PacketHandler::Pfifo_PCjudge()
{
int data = 0x11223344;
char* p;
p = (char*)&data;
if (*p == 0x44)
{
m_islittle = true;
return true;
}
else
{
m_islittle = false;
return false;
}
}
int PacketHandler::PfifoPut(unsigned char* buffer, int len)
{
//BUFFERSIZE
unsigned char buf[4];
unsigned int datalen = len;
int offset = (m_fifo->write & 0xf) * MAXLEN;
str_datalen strdat;
strdat.length = len;
int strlen = sizeof(str_datalen);
memcpy(m_fifo->buffer + offset, &strdat, strlen);
memcpy(m_fifo->buffer + offset + strlen, buffer, len);
m_fifo->write++;
return 0;
}
void PacketHandler::Pfifo_Init()
{
unsigned char* buf = (unsigned char*)malloc(BUFFERSIZE);
if (!m_fifo){ m_fifo = (strfifo*)malloc(sizeof(strfifo)); }
memset(buf, 0, BUFFERSIZE);
m_fifo->buffer = buf;
m_fifo->write = 0;
m_fifo->read = 0;
Pfifo_PCjudge();
}
void PacketHandler::Pfifo_free()
{
if (m_fifo)
{
free(m_fifo->buffer);
m_fifo->buffer = NULL;
m_fifo->write = 0;
m_fifo->read = 0;
free(m_fifo);
m_fifo = NULL;
}
}
上面是实现方法,线程中调用方法:m_ppfifo为 PacketHandler类指针
if (pstr->m_ppfifo->m_fifo->read >= pstr->m_ppfifo->m_fifo->write){ Sleep(1); continue; }
str_fifo* pfifo = pstr->m_ppfifo->PfifoGet();
str_datalen strdatalen;
int len = sizeof(str_datalen);
int offset = (pstr->m_ppfifo->m_fifo->read & 0xf)*MAXLEN;
memcpy(&strdatalen, pfifo->buffer + offset, sizeof(str_datalen));
if (strdatalen.length < 1){Sleep(1);continue;}
pstr->m_prtmp->PacketProcess(pfifo->buffer + offset + len, strdatalen.length, mtype);
pstr->m_ppfifo->m_fifo->read++; //这一句别忘记 ,否则数据一直不对,都不知道哪里找问题;
需要理解这个是什么意思,很重要。
OK ,我分享的思路和最后的内存优化方法,请大神们多多指正,另外,这个代码是可以优化的,本人也优化过,效率还能提高哦!