固定大小的环形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后,就实现了均匀丢帧渲染播放的效果。
- 固定大小的环形buf
- 环形缓冲buf的创建
- 【Linux】基于环形buf的多消费者多生产者问题
- 环形buf按照帧长读写
- 固定窗口大小的方法
- 固定大小方块的移动
- 创建固定大小的文件
- 布局 固定大小的布局
- 创建固定大小的list
- Linux:测试socket发送和接收时,缓冲区buf的大小
- 复制文件BUF大小、标准IO对执行速度的影响
- Android改变系统自带环形ProgressBar的大小
- textare的固定大小的属性
- textare的固定大小的设置
- 创建客户区域固定大小的窗口
- 固定单文档的窗口大小
- linux生成固定大小的文件
- 固定大小的线程池(2)
- unity mac 破解
- sublime vue stylus 高亮
- 初步使用ideaUI
- 容器的未来是怎么样的?
- 14个超级牛X的免费开源小工具!
- 固定大小的环形buf
- myeclipse 删除文件恢复
- Hbuilder日常踩坑之消息推送
- Android开发笔记: 圆角对话框
- 保存GIF到系统相册
- sinco.C自学笔记(变量分配地址时的结合性问题)
- 小米路由器3 opkg安装
- 初学者--- android Ijkplayer最简单的简单使用
- GPS、蓝牙、解析字符串等功能