先从实际问题开始讨论, 有个视频采集线程,采集到一帧视频数据, 这帧数据要给存储模块(线程)、RTSP模块,RTMP模块使用。 像这样的情况,内存的申请是在采集线程, 但释放就不能是在采集线程了,所以要实现,谁最后使用,谁释放。如图所示:
需求明确,但是代码怎么实现呢? 答案是用到智能指针实现!
智能指针又是怎么实现呢? 智能指针使用引用技术实现, 当指针传递时,引用加1,当指针使用结束,引用减1,如果引用数等于0,则析构对象,释放内存 。
在上面的例子中,采集模块new出来对象,对象的引用计数为1,对象的指针传给存储模块时,引用计数加1;传给RTSP模块时,引用计数加1;传给RTMP模块时,引用计数加1, 如果几个模块都没有使用结束,此时对象的引用计数为4。采集模块使用结束,引用减1;存储模块使用结束,引用减1;RTSP模块使用结束,引用减1;RRTMP模块使用结束,引用减1, 此时引用计数为0, 析构对象,释放内存。
引用计数的智能指针的具体实现请看:
智能指针在boost里有现成的, 也就是shared_ptr。 怎么使用boost:: shared_ptr来实现我们想要的功能呢? 直接看代码可能更容易理解:
AVData.h
#ifndef AV_DATA__H
#define AV_DATA__H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <boost/shared_ptr.hpp>
typedef enum _AVDataType
{
AVDataType_Audio = 1,
AVDataType_Video,
AVDataType_Both
}AVDataType;
class AVData
{
public:
AVData();
AVData(int i);
~AVData();
void TestFunc();
int SetAVData(AVDataType type, const char* buffer, const int size);
char* GetAVData();
int GetAVDataSize();
private:
char* m_pBuffer;
int m_iBufferSize;
int m_iCount;
};
typedef boost::shared_ptr<AVData> AVDataPtr;
#endif // AV_DATA__H
</pre>AVDataPool.h<p></p><pre name="code" class="cpp">#ifndef AV_DATA_POOL__H
#define AV_DATA_POOL__H
#include "AVData.h"
#include <boost/thread.hpp>
typedef std::list<AVDataPtr> AVDataList;
class AVDataPool
{
public:
AVDataPool();
AVDataPool(int max);
~AVDataPool();
void PutAVDataPool(AVDataPtr ptr);
void GetAVDataPool(AVDataPtr &ptr);
void Flush();
private:
AVDataList m_List;
boost::mutex m_Mutex;
int m_iMaxCount;
};
#endif // AV_DATA_POOL__H
</pre>main<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <list>
#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include "AVData.h"
#include "AVDataPool.h"
void my_handler(int sig);
void ThreadStoreVideo(void *p);
void ThreadRtsp(void *p);
void ThreadRtmp(void *p);
int global_value_exit = 0;
class ThreadParams
{
public:
ThreadParams()
:m_pAVDataPool(NULL)
{
}
~ThreadParams()
{
if (m_pAVDataPool)
{
delete m_pAVDataPool;
m_pAVDataPool = NULL;
}
}
void InitThreadParams(int count)
{
m_pAVDataPool = new AVDataPool(count);
}
public:
AVDataPool *m_pAVDataPool;
};
int main(int argc, char* argv[])
{
printf("shared_ptr test begin\n");
struct sigaction sigIntHandler; // 接收Ctrl + C 结束
sigIntHandler.sa_handler = my_handler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
ThreadParams *store_params = new ThreadParams();
store_params->InitThreadParams(3);
ThreadParams *rtsp_params = new ThreadParams();
rtsp_params->InitThreadParams(4);
ThreadParams *rtmp_params = new ThreadParams();
rtmp_params->InitThreadParams(5);
boost::thread thread_store(ThreadStoreVideo, store_params);
boost::thread thread_rtsp(ThreadRtsp, rtsp_params);
boost::thread thread_rtmp(ThreadRtmp, rtmp_params);
int i = 0;
char *buffer = (char*)malloc(4096);
int size;
char data = 'a';
while(!global_value_exit)
{
if (data > 'z')
{
data = 'a';
}
AVDataPtr pAVData(new AVData(i++));
size = 1024 + rand()%1024; // 生成随机数 1024~2048
memset(buffer, data, size);
pAVData->SetAVData(AVDataType_Both, buffer, size);
data++;
store_params->m_pAVDataPool->PutAVDataPool(pAVData);
rtsp_params->m_pAVDataPool->PutAVDataPool(pAVData);
rtmp_params->m_pAVDataPool->PutAVDataPool(pAVData);
usleep(200000);
}
thread_store.join();
thread_rtsp.join();
thread_rtmp.join();
if (buffer)
{
free(buffer);
buffer = NULL;
}
return 0;
}
void ThreadStoreVideo(void *p)
{
ThreadParams *params = (ThreadParams*)p;
while(!global_value_exit)
{
AVDataPtr ptr;
params->m_pAVDataPool->GetAVDataPool(ptr);
if (ptr)
{
//ptr->TestFunc();
char *buffer = ptr->GetAVData();
printf("%s : %c\n", __func__, *buffer);
}
usleep(15000);
}
}
void ThreadRtsp(void *p)
{
ThreadParams *params = (ThreadParams*)p;
while(!global_value_exit)
{
AVDataPtr ptr;
params->m_pAVDataPool->GetAVDataPool(ptr);
if (ptr)
{
//ptr->TestFunc();
char *buffer = ptr->GetAVData();
printf("%s : %c\n", __func__, *buffer);
}
usleep(15000);
}
}
void ThreadRtmp(void *p)
{
ThreadParams *params = (ThreadParams*)p;
while(!global_value_exit)
{
AVDataPtr ptr;
params->m_pAVDataPool->GetAVDataPool(ptr);
if (ptr)
{
//ptr->TestFunc();
char *buffer = ptr->GetAVData();
printf("%s : %c\n", __func__, *buffer);
}
usleep(15000);
}
}
void my_handler(int sig)
{
if (sig == SIGINT)
{
printf("Caught signal %d\n", sig);
global_value_exit = 1;
}
}
以上就是C++内存管理的一种方式, 当然C++内存管理方式多种多样。 还可以在一开始的时候,申请一大块内存, 然后再通过修改 uint8_t *rptr, *wptr来改变读写内存的位置, 来实现数据的写入和读出。如果代码可以参考ffmpeg libavutil/fifo.c,实现原理如下图所示:
哪种实现性能更优呢? 第二种方法看似更好一些, 一次申请了一大块内存, 不用跟系统频繁申请释放内存, 但是别忘了第二种方法要加锁。没做过性能测试, 不知道哪种方法更好些, 目前开发的系统使用的是第一种方法。 欢迎做过比较测试的朋友告诉我哈