对于有些场景下,我们需要使用帧缓冲区来显示图像,实际上我们可以用一个循环缓冲区标识当前可用的帧,如果多个帧缓冲区的地址是不连续的,那么还需要一个链表结构来保存下一个帧缓冲区的地址。那有没有什么更简单的实现呢?
1 使用场景
比如显示一个摄像头的图像,它是一帧一帧显示的。假设有三个缓冲区framebuffer[3][WIDTH*HEIGHT*BPP]
,将缓冲区填充完待显示的图像后,交由LCD控制器来显示,在显示完后有一个中断回调函数指示图像成功绘制到LCD中,此时就可以标记这缓冲区为未使用。
在这个过程中间,可能LCD还没有绘制完上一帧,下一帧摄像头图像就来了,所以现在就将新的摄像头帧保存在下一个帧缓冲区中,显示下一帧的图像。这样显示出来的摄像头图像才是流畅的。有时摄像头的图像来得很快,3个缓冲区就用满了,就需要等待中断回调函数中释放缓冲区。
所以下面就来介绍一种简单的帧缓冲区管理机制。
2 原理
前面有提到,如果帧缓冲区的地址不是连续的,就还需要记录各个帧缓冲区的地址,类似一个链表的操作。对于没有使用的帧缓冲区来说,我们就可以用帧缓冲区自身的内存来保存这个地址,然后用一个指针变量s_fbList
来指向最新的缓冲区。每次有新的帧缓冲区加入时,将新的帧缓冲区的前4个字节赋值为前一个帧缓冲区的地址。整个数据结构与栈有些类似,实现思路如下所示:
所以这就要求了每个帧缓冲区的大小必须大于4字节(在32位机器上),也就是能保存一个指针的地址。
3 C语言实现
代码如下所示,可以看到代码十分精简,就两个函数:一个获取帧缓冲区的函数APP_GetFrameBuffer
和一个增加帧缓冲区的函数APP_GetFrameBuffer
。
static void *volatile s_fbList = NULL;
static void *APP_GetFrameBuffer(void)
{
void *fb;
fb = s_fbList;
if (NULL != fb)
{
s_fbList = *(void **)fb;
}
return fb;
}
static void APP_PutFrameBuffer(void *fb)
{
*(void **)fb = s_fbList;
s_fbList = fb;
}
我们只要在程序初始化的时候初始化几个帧缓冲区,然后在使用时调用APP_GetFrameBuffer
获取一个帧缓冲区,最后在LCD的中断回调函数中调用APP_PutFrameBuffer
释放刚刚获取的帧缓冲区即可。
注意这里二维指针的使用,比如在APP_PutFrameBuffer
中,如果直接将fb = s_fbList
的话,似乎和*(void **)fb = s_fbList
一样都是改变fb地址处的值。但这样实际上是把函数创建的临时指针变量指向的地址改变了。所以如果要改指针所指向地址里面的值的话,需要先强制转化为二维指针,再访问里面的内容。
以这个思路的话,假设LCD显示的速度足够快,那么可能在初始化时,我们最早调用APP_PutFrameBuffer
加入的帧缓冲区0可能永远都用不上。
4 总结
平时写代码的过程中经常会遇到一些有趣的数据结构,比如这个帧缓冲区的机制,我看到就这几行代码很是好奇里面的原理。当然,我不去理解里面的原理也可以,因为这段代码可以正常运行,这也不会影响我项目的进度。但如果你花时间来理解里面的原理,掌握不同的数据结构,以后在你写代码的过程中也会有更多的选择和思路。