固定大小的环形buf

来源:互联网 发布:淘宝产品摄影报价 编辑:程序博客网 时间:2024/05/27 19:27

需求分析

最近在做项目时,对解码后的yuv数据需要做缓存,界面线程按照可配置的帧率,设定定时器去从缓存中获取YUV数据然后渲染播放。注意的是,因为界面是多画面监控网格,最多需要16画面,而视频分辨率都是1080p,甚至4k,在低性能的机器上根本无法带动。所以需要可配置FPS去播放,比如25帧的YUV,实际只渲染15帧或者10帧,只在某个全屏时才按照实际FPS去渲染播放。那么问题来了,25帧去播放哪15帧呢,如果只播放前15帧,丢掉后10帧,监控画面出现时间段跳跃肯定是不行的,为了解决这个问题,只好自己写了一个环形buf,采用后来的帧自动覆盖还未播放的帧,实现任意配置FPS的目的。

源代码

#ifndef HRINGBUFFER_H#define HRINGBUFFER_H#include <malloc.h>#include <stdio.h>#include <string.h>#define CAN_WRITE   0#define CAN_READ    1#define DISCARD_WHEN_NO_CAN_WRITE   0/** * @note: use in multi thread, please lock for read and write. * ***/class HRingBuffer{public:    HRingBuffer(int size, int num = 10){        _size = size;        _num = num;        long long total = (1+size)*num; // 1 for flag : CAN_WRITE or CAN_READ        _ptr = (char*)malloc(total);        memset(_ptr, 0, total); // init CAN_WRITE        read_index = 0;        write_index = 0;    }    ~HRingBuffer(){        if (_ptr){            free(_ptr);            _ptr = NULL;        }    }    char* read(){        char* ret = get(read_index);        if (*ret == CAN_READ){            read_index = (read_index + 1)%_num;            *ret = CAN_WRITE;            return ret+1;        }        return NULL;    }    char* write(){        char* ret = get(write_index);        if (*ret == CAN_READ){            if (DISCARD_WHEN_NO_CAN_WRITE){                return NULL;            }            // edge out read_index            read_index = (read_index+1)%_num;            // qDebug("edge out read_index");        }        write_index = (write_index+1)%_num;        *ret = CAN_READ;        return ret+1;    }    char* get(int index){        if (index < 0 || index >= _num)            return NULL;        return _ptr + index*(1+_size);    }private:    int _size;    int _num;    char* _ptr;    int read_index;    int write_index;};#endif // HRINGBUFFER_H

讲解

这个buf虽然使用类HRingBuffer进行封装,内部实际是c的实现,非常的高效,完全是指针偏移操作。

_size用来指定每段缓存的大小,比如1080p的yuv数据,每帧的缓存大小为1920*1080*3/2
_num指定缓存的数目,比如缓存10帧
_ptr指向开辟缓存的首地址
read_index代表当前读索引
write_index代表当前写索引

在构造函数中,根据给定的size和num,我们使用malloc开辟缓存区,使用_ptr保存首地址

注意的是,我使用了每帧缓存的第一个字节代表标识,用来记录当前帧缓存是可读还是可写

所以总的缓存大小是(1+size)*num

初始化将所有字节置0,memset(_ptr, 0, total)
因为我定义的0代表可写

#define CAN_WRITE   0#define CAN_READ    1

开始时read_index和write_index自然都是0

在get方法中,通过索引获取到每段内存的首地址

return _ptr + index*(1+_size);

对外提供的接口是read和write,返回代表可读和可写的内存首地址

char* read(){    char* ret = get(read_index);    if (*ret == CAN_READ){        read_index = (read_index + 1)%_num;        *ret = CAN_WRITE;        return ret+1;    }    return NULL;}

通过get方法获取到当前读索引代表的首地址,判断第一个字节是否是可读,是的话就返回ret+1并将read_index 读索引+1,采用取余%即可实现环形buf的自动从尾索引跳到头索引,标志置为可写,不是的话说明缓存中没有可读的帧数据,返回NULL,所以使用时需要对返回值进行非NULL判断

char* write(){    char* ret = get(write_index);    if (*ret == CAN_READ){        if (DISCARD_WHEN_NO_CAN_WRITE){            return NULL;        }        // edge out read_index        read_index = (read_index+1)%_num;        qDebug("edge out read_index");    }    write_index = (write_index+1)%_num;    *ret = CAN_READ;    return ret+1;}

write方法和get类似,不同的是我设置了一个策略,定义了一个宏DISCARD_WHEN_NO_CAN_WRITE,表示当没有可写(缓存里全部塞满了)时是否丢弃,如果是直接返回NULL,代表没有可写内存,如果不是我们需要将可读的内存给覆盖掉,所以将read_index +1,write_index +1,标志位置为可读。

说明

可以看到是读写返回的都是内存指针,都没有上锁,如果是用在单线程中完全不会冲突,如果用来多线程中,肯定是要自己上锁的。调用形式如下:

// 读线程mutex.lock();char* p = pBuf->read();if (p){    // use p    }mutex.unlock();// 写线程mutex.lock();char* p = pBuf->write();if (p){    // use p    }mutex.unlock();

在use p的地方只做内存的拷入和拷出操作,不用担心锁的太久。

采用这个环形buf后,就实现了均匀丢帧渲染播放的效果。

原创粉丝点击