1背景
上一篇16中处理到了视频帧的存储,这一篇从视频帧存储开始。
为什么需要处理视频帧的存储
因为处理到着色器渲染才发现没有视频帧可以渲染,所以才从前面start开始找哪里处理视频帧,然后整理了一下调用的顺序。
- 线程start中调用了run
- 线程run中执行了doTask
- 子类hffplayer的dotask中解析并给data赋值
- hframe在open的时候指向了data
- push_frame(&hframe);
- HVideoPlayer中使用HFrameBuf
- HFrameBuf使用了HRingBuf
- HRingBuf继承了HBuf
2步骤
2.1 HRingBuf
在hv库的base下有个hbuf.h的头文件
class HRingBuf : public HBuf {
public:
HRingBuf() : HBuf() {_head = _tail = _size = 0;}
HRingBuf(size_t cap) : HBuf(cap) {_head = _tail = _size = 0;}
virtual ~HRingBuf() {}
char* alloc(size_t len) {
char* ret = NULL;
if (_head < _tail || _size == 0) {
// [_tail, this->len) && [0, _head)
if (this->len - _tail >= len) {
ret = base + _tail;
_tail += len;
if (_tail == this->len) _tail = 0;
}
else if (_head >= len) {
ret = base;
_tail = len;
}
}
else {
// [_tail, _head)
if (_head - _tail >= len) {
ret = base + _tail;
_tail += len;
}
}
_size += ret ? len : 0;
return ret;
}
void free(size_t len) {
_size -= len;
if (len <= this->len - _head) {
_head += len;
if (_head == this->len) _head = 0;
}
else {
_head = len;
}
}
void clear() {_head = _tail = _size = 0;}
size_t size() {return _size;}
private:
size_t _head;
size_t _tail;
size_t _size;
};
#endif
这个HRingBuf类的实现是一个环形缓冲区,旨在高效管理动态内存的分配和释放,尤其适用于需要连续内存块的场景。 环形缓冲区结构:
_head :指向待释放数据的起始位置(消费者指针)。 _tail :指向可分配空间的起始位置(生产者指针)。 _size :当前缓冲区中已占用的字节数,用于快速判断剩余空间。
连续内存分配: alloc 方法优先分配连续的内存块,确保调用者可以方便操作数据,无需处理分段。当剩余空间不连续时,可能无法分配即使总空间足够,这要求调用者处理分配失败的情况。
下面这个只处理了尾部连续或者头部连续。
class RingBuffer {
public:
RingBuffer(size_t cap) : capacity(cap), size(0), head(0), tail(0) {
data = new char[cap];
}
~RingBuffer() {
delete[] data;
}
// 分配连续内存(生产者)
char* alloc(size_t len) {
if (len == 0 || len > available()) return nullptr;
size_t contiguous_after_tail = capacity - tail;
if (contiguous_after_tail >= len) {
// 尾部有足够连续空间
char* ret = data + tail;
tail = (tail + len) % capacity;
size += len;
return ret;
} else {
// 检查头部是否有足够空间
if (head >= len) {
char* ret = data;
tail = len;
size += len;
return ret;
}
}
return nullptr;
}
// 释放数据(消费者)
void free(size_t len) {
if (len == 0 || len > size) return;
size_t contiguous_after_head = capacity - head;
if (len <= contiguous_after_head) {
head += len;
} else {
head = len - contiguous_after_head;
}
head %= capacity;
size -= len;
}
// 剩余可用空间
size_t available() const {
return capacity - size;
}
// 当前数据量
size_t current_size() const {
return size;
}
private:
char* data; // 缓冲区指针
size_t capacity; // 总容量
size_t size; // 已用空间
size_t head; // 读取位置
size_t tail; // 写入位置
};
正常情况下没必要处理一部分在头部,一部分在尾部的数据,毕竟分段会增加调用复杂度。
- 根据数据类型选择策略 分块友好型数据(如文本日志、传感器数据) → 支持分段分配,通过 alloc 返回多块信息。 连续依赖型数据(如TCP封包、音频帧) → 强制连续分配,若空间不足则等待或报错。