音视频环形缓冲区 介绍与实现

共 8157字,需浏览 17分钟

 ·

2022-07-24 13:01

一、什么是环形缓冲区

环形缓冲区(也称为循环缓冲区)是固定大小的缓冲区,工作原理就像内存是连续的且可循环的一样。在生成和使用内存时,不需将原来的数据全部重新清理掉,只要调整head/tail 指针即可。当添加数据时,head 指针前进。当使用数据时,tail 指针向前移动。当到达缓冲区的尾部时,指针又回到缓冲区的起始位置。

二、为什么使用环形缓冲区

环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在一个音视频处理的机制中,环形缓冲区就可以理解为数据码流的通道,每一个通道都对应着一个环形缓冲区,这样数据在读取和写入的时候都可以在这个缓冲区里循环进行,程序员可以根据自己需要的数据大小来决定自己使用的缓冲区大小。

环形缓冲区通常用作固定大小的队列。固定大小的队列对于嵌入式系统的开发非常友好,因为开发人员通常会尝试使用静态数据存储的方法而不是动态分配。

环形缓冲区对于数据写入和读出以不同速率发生的情况也是非常有用的结构:最新数据始终可用。如果读取数据的速度跟不上写入数据的速度,旧的数据将被新写入的数据覆盖。通过使用循环缓冲区,能够保证我们始终使用最新的数据。

三、代码实现

1. 环形缓存区结构体RING_BUFFER

typedef struct
{

    //缓存空间指针
    char*                           buffer;
    //缓存空间容量
    int                             size;
    //缓存读序号,范围:0 - (size -1)
    int                             rdIdx;
    //缓存写序号,范围:0 - (size -1)
    int                             wrIdx;
    //缓存满标志
    int                             isFull;
}RING_BUFFER;

2. 创建环形缓存区

voidringBufferCreate(int size)
{
    RING_BUFFER*    rb;
    if(NULL == (rb = malloc(sizeof(RING_BUFFER) + size)))
    {
        printf("内存不足!\n");
        return NULL;
    }
    memset(rb, 0sizeof(RING_BUFFER));
    rb->buffer = (char*)rb + sizeof(RING_BUFFER);
    rb->size = size;
    return (void*)rb;
}

3. 获取环形缓存区中有效数据大小

int ringBufferGetSize(RING_BUFFER* rb)
{
    if(rb->wrIdx == rb->rdIdx)
    {
        return 0;
    }
    if(rb->wrIdx > rb->rdIdx)
    {
        return (rb->wrIdx - rb->rdIdx);
    }
    return ((rb->size - rb->rdIdx) + rb->wrIdx);
}

4. 获取环形缓存区中空闲空间字节大小

int ringBufferFreeLen(RING_BUFFER* rb)
{
    return ((rb->size - 1) - __ringBufferGetSize(rb));
}

5. 写环形缓存区

int ringBufferWrite(void* handle, const char* ptr, int len)
{
    RING_BUFFER*    rb = (void*)handle;
    int          size;
    if(0 >= len)
    {
        printf("rb写参数[%lu]非法!\n", len);
        return 0;
    }
    //获取空闲空间大小
    size = ringBufferFreeLen(rb);
    if(size < len)
    {
        //空闲空间不足
        printf("rb写溢出!<%d, %d>\n", len, size);
        return 0;
    }
    //计算"写序号"后部还允许写入的数据字节数
    size = rb->size - rb->wrIdx;
    if(size >= len)
    {
        //当前待写入全部数据可一次写入后部
        memcpy(rb->buffer + rb->wrIdx, ptr, len);
        rb->wrIdx = (size > len) ? (rb->wrIdx + len) : 0;
        return len;
    }
    //先写一部分数据到后部
    memcpy(rb->buffer + rb->wrIdx, ptr, size);
    //再写剩余数据到前部
    memcpy(rb->buffer, ptr + size, len - size);
    rb->wrIdx = len - size;
    return len;
}

6. 读环形缓存区

int ringBufferRead(void* handle, char* ptr, int len)
{
    RING_BUFFER*    rb = (void*)handle;
    int         size;
    if(0 >= len)
    {
        printf("rb读参数[%lu]非法!\n", len);
        return 0;
    }
    //获取有效数据大小
    size = ringBufferGetSize(rb);
    if(0 == size)
    {
        return 0;//没有数据
    }
    if(0 != rb->isFull)
    {
        //之前已标记 缓存满,全清 缓存内容
        rb->rdIdx = rb->wrIdx;
        rb->isFull = 0;
        return 0;
    }
    if(size < len)
    {
        //有效数据不足,只能返回填充一部分
        len = size;
    }
    //计算"读序号"后部可读空间大小
    size = rb->size - rb->rdIdx;
    if(size >= len)
    {
        //当前待读出全部数据全存在于"读序号"后部
        memcpy(ptr, rb->buffer + rb->rdIdx, len);
        rb->rdIdx = (size > len) ? (rb->rdIdx + len) : 0;
        return len;
    }
    //先从"读序号"后部读出一部分数据
    memcpy(ptr, rb->buffer + rb->rdIdx, size);
    //再从前部读出剩余数据
    memcpy(ptr + size, rb->buffer, len - size);
    rb->rdIdx = len - size;
    return len;
}

7. 销毁环形缓存区

int ringBufferDestroy(void* handle)
{
    RING_BUFFER*    rb = (void*)handle;
    ctFree(rb);
    return 0;
}
来源:https://blog.csdn.net/u012478275/article/details/122101925

最后欢迎大家加入 音视频开发进阶 知识星球 ,这里有知识干货、编程答疑、开发教程,还有很多精彩分享。


更多内容可以在星球菜单中找到,随着时间推移,干货也会越来越多!!!


给出 10元 优惠券,涨价在即,目前还是白菜价,基本上提几个问题就回本,投资自己就是最好的投资!!!


加我微信 ezglumes ,拉你进技术交流群

推荐阅读:

音视频开发工作经验分享 || 视频版

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

Android NDK 免费视频在线学习!!!

你想要的音视频开发资料库来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

浏览 24
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报