摄像头若每秒采集25帧,也就是平均40ms采集一帧,我们将摄像头数据通过网络发送给客户端时,会遇到一个问题就是网络是不稳定的,特别是外网状态下。为了不让网络传输的不稳定性影响到采集过程,我们可以用一个线程专门每40ms采集一帧,而另外一个线程专门对采集的数据进行发送。
两个线程的速率并不是一样的,为了让两个不同任务的线程均工作最合理,我们在两个线程之前加入了缓存的概念,也就是生产者 采集线程采集帧后,将数据暂存到缓存中,而消费者 网络传输线程从缓存中取数据并消费。这种缓存过程就是对内存的应用过程。
首先讨论C语言的内存分配函数
c语言 malloc函数开辟内存。
比如
int *p;
p=(int *)malloc (100*sizeof(int));
它开辟100个int单元,即400字节。
通过malloc分配内存后,就可以对内存块进行使用,当使用完内存后,必须采用free函数对内存进行释放。
if(NULL != p)
free(p)
对使用的内存不进行释放操作,就会产生内存泄露。
下面描述在数据缓存中,最常用的两种缓存方式。
1.队列
#include<stdio.h>
#include<malloc.h>
typedef struct Node //定义节点
{
int data;
Node *PNEXT;
}NODE,*PNODE;
typedef struct Quene //定义队列
{
PNODE First;
PNODE Last;
}QUENE,*PQUENE;
//初始化
void init(PQUENE pQ)
{
pQ->First = (PNODE)malloc(sizeof(NODE));
pQ->Last = pQ->First;
pQ->Last->PNEXT = NULL;
}
//进队
void EnQuene(PQUENE pQ,int val)
{
PNODE newnode = (PNODE)malloc(sizeof(NODE));
newnode->data = val;
pQ->Last->PNEXT = newnode;
pQ->Last = newnode;
newnode->PNEXT = NULL;
}
//判断队列是否为空
int ifEmpty(PQUENE pQ)
{
if(pQ->First == pQ->Last)
return 1;
else
return 0;
}
//出队
int DeQuene(PQUENE pQ)
{
if(ifEmpty(pQ))
{
printf("队列已空,出队失败");
return 0;
}
else
{
PNODE p;
p = pQ->First->PNEXT;
pQ->First->PNEXT = p->PNEXT;
int a = p->data;
free(p);
return a;
}
}
//队列数据遍历
void traverse(PQUENE pQ)
{
PNODE p = pQ->First->PNEXT;
printf("当前队列内容:");
while(p->PNEXT != NULL)
{
printf("%d ",p->data);
p = p->PNEXT;
}
printf("%d ",p->data);
printf("\n");
}
//销毁队列
void destory(PQUENE pQ)
{
while(pQ->First != pQ->Last)
{
PNODE p = pQ->First;
pQ->First = p->PNEXT;
free(p);
}
free(pQ->First);
}
//主程序
int main()
{
QUENE quene;
init(&quene);
EnQuene(&quene,1);
printf("入队1\n");
traverse(&quene);
EnQuene(&quene,2);
printf("入队2\n");
traverse(&quene);
EnQuene(&quene,3);
printf("入队3\n");
traverse(&quene);
int a = DeQuene(&quene);
printf("%d值已出列\n",a);
traverse(&quene);
destory(&quene);
return 0;
}
输出结果
入队1
当前队列内容:1
入队2
当前队列内容:1 2
入队3
当前队列内容:1 2 3
1值已出列
当前队列内容:2 3
2.环形缓存
环形缓冲区(也称为循环缓冲区)是固定大小的缓冲区,工作原理就像内存是连续的且可循环的一样。在生成和使用内存时,不需将原来的数据全部重新清理掉,只要调整head/tail 指针即可。当添加数据时,head 指针前进。当使用数据时,tail 指针向前移动。当到达缓冲区的尾部时,指针又回到缓冲区的起始位置。
环形缓存区的原理和使用并没有普通的链表队列直观,为什么在视频场合推荐环形缓存区,原因是普通队列的出队,入列操作存在malloc和free操作,malloc的频繁操作会引起内存的另一个问题,内存碎片。环形缓存区只会在初始化时malloc一次内存,相比较链表队列而言,有效的避免内存泄露。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static unsigned short validlen; //已使用的数据长度
static unsigned char* pHead = NULL; //环形缓冲区首地址
static unsigned char* pTail = NULL; //环形缓存区尾地址
static unsigned char* pValid = NULL; //已使用缓冲区的首地址
static unsigned char* pValidTail = NULL; //已使用缓冲区的尾地址
int g_buffer_size = 5;
void InitRingBuffer(unsigned short size)
{
if(pHead == NULL)
{
pHead = (char*) malloc(size);
}
pValid = pValidTail = pHead;
pTail = pHead + size;
g_buffer_size = size;
validlen = 0;
}
//释放环形缓冲区
void releaseRingbuffer(void)
{
if(pHead != NULL)
free(pHead);
pHead = NULL;
}
int writeRingbuffer(unsigned char* buffer, unsigned int len)
{
unsigned short len1, len2, movelen;
if(len > g_buffer_size)
{
return -1;
}
if(pHead == NULL)
{
return -2;
}
assert(buffer);
//将要写入的数据copy到pVaildTaill处
if(pValidTail+len > pTail)
{
len1 = pTail - pValidTail; //放到尾部
len2 = len - len1; //尾部放不下的位置,从头开始放
memcpy(pValidTail, buffer, len1);
memcpy(pHead, buffer + len1, len2);
pValidTail = pHead + len2; //新的有效数据
}else
{
memcpy(pValidTail, buffer, len);
pValidTail += len; //新的有效数据区结尾
}
//重新计算已使用区的起始位置
if(validlen + len > g_buffer_size)
{
movelen = validlen + len - g_buffer_size;
if(pValid + movelen > pTail) //需要分成两段
{
len1 = pTail - pValid;
len2 = movelen - len1;
pValid = pHead + len2;
}else
{
pValid += movelen;
}
validlen = g_buffer_size;
}else
{
validlen += len;
}
return 0;
}
int readRingbuffer(unsigned char* buffer, unsigned int len)
{
unsigned short len1, len2;
if(pHead == NULL)
{
return -1;
}
assert(buffer);
if(len > validlen)
{
len = validlen;
}
if(validlen == 0)
{
return 0;
}
if(pValid + len > pTail) //需要分成两段copy
{
len1 = pTail - pValid;
len2 = len - len1;
memcpy(buffer, pValid, len1); //第一段
memcpy(buffer + len1, pHead, len2); //第二段 ,绕到整个存储区的开头
pValid = pHead + len2; //更新已使用缓冲区的起始
}else
{
memcpy(buffer, pValid, len);
pValid += len; //更新已使用缓冲区的起始
}
validlen -= len; //更新已使用缓冲区的长度
return len;
}
unsigned int getRingbufferValidLen(void)
{
return validlen;
}
int main()
{
unsigned int readLen,i;
unsigned char readBuffer[5] = {0};
//unsigned char buffer[25] = {0};
InitRingBuffer(25);//初始化环形缓冲区
unsigned char buffer1[5] = "1234";
printf("第1次写4字节:%s\n", buffer1);
writeRingbuffer(buffer1, strlen(buffer1));
unsigned char buffer2[5] = "abc";
printf("第2次写3字节:%s\n", buffer2);
writeRingbuffer(buffer2, strlen(buffer2));
unsigned char buffer3[5] = "====";
printf("第3次写4字节:%s\n", buffer3);
writeRingbuffer(buffer3, strlen(buffer3));
printf("===================================\n");
memset(readBuffer, 0, sizeof(readBuffer));
readLen = readRingbuffer(readBuffer,4);
printf("第1次读4字节:%s\n", readBuffer);
memset(readBuffer, 0, sizeof(readBuffer));
readLen = readRingbuffer(readBuffer,3);
printf("第2次读3字节:%s\n", readBuffer);
memset(readBuffer, 0, sizeof(readBuffer));
readLen = readRingbuffer(readBuffer,4);
printf("第3次读4字节:%s\n", readBuffer);
releaseRingbuffer();
return 0;
}
运行结果
第1次写4字节:1234
第2次写3字节:abc
第3次写4字节:====
===================================
第1次读4字节:1234
第2次读3字节:abc
第3次读4字节:====