FFmpeg学习之七(视音频流缓存)

缓存队列实现

源码下载

  1. 视频音频缓存队列实现源码demo

1.原理

初始化固定数量的结点装入空闲队列,当相机回调产生数据后,从空闲队列头部取出一个结点将产生的每一帧图像buffer装入,然后入队到工作队列的尾部,处理buffer的线程从工作队列的头部取出一个结点中的Buffer进行处理,处理完成后会将装有次buffer的结点中data置空并重新放入空闲队列的头部以供下次使用。

我们将空闲队列设计为头进头出,影响不大,因为我们每次只需要从空闲队列中取出一个空结点以供我们装入相机数据,所以没必要按照尾进头出的方式保证结点的顺序。
我们将工作队列设计为尾进头出,因为我们要确保从相机中捕获的数据是连续的,以便后期我们播放出来的画面也是连续的,所以工作队列必须保证尾进头出。
这样做我们相当于实现了用空闲队列当做缓冲队列,在正常情况下(fps=30,即每秒产生30帧数据,大约每33ms产生一帧数据),如果在33ms内对数据进行的操作可以正常完成,则工作队列会保持始终为0或1,但是如果长期工作或遇到某一帧数据处理较慢的情况(即处理时间大于33ms)则工作队列的长度会增加,而正因为我们使用了这样的队列会保护那一帧处理慢的数据在仍然能够正常处理完。
注意:这种情景仅用于短时间内仅有几帧数据处理较慢,如果比如1s内有20几帧数据都处理很慢则可能导致工作队列太长,则体现不出此队列的优势。

2.实现细节

2.1 结构体定义

  1. 节点
typedef struct KYLCustomQueueNode {
    void    *data;//结点中使用void *类型的data存放我们需要的sampleBuffer,使用index记录当前装入结点的sampleBuffer的索引,
                  //以便我们在取出结点时比较是否是按照顺序取出,结点中还装着同类型下一个结点的元素。
    size_t  size;  // data size 数据大小
    long    index;
    struct  KYLCustomQueueNode *next; //记录下一个节点指针
} KYLCustomQueueNode;

结点中使用void *类型的data存放我们需要的sampleBuffer,使用index记录当前装入结点的sampleBuffer的索引,以便我们在取出结点时比较是否是按照顺序取出,结点中还装着同类型下一个结点的元素。

  1. 队列类型
//队列中即为我们装载的结点数量,因为我们采用的是预先分配固定内存,
//所以工作队列与空闲队列的和始终不变(因为结点中的元素不在工作队列就在空闲队列)
typedef struct KYLCustomQueue {
    int size;
    KYLCustomQueueType type; //队列类型
    KYLCustomQueueNode *front; //队列头
    KYLCustomQueueNode *rear;  //队列尾
} KYLCustomQueue;

队列中即为我们装载的结点数量,因为我们采用的是预先分配固定内存,所以工作队列与空闲队列的和始终不变(因为结点中的元素不在工作队列就在空闲队列)

2.2 类定义

class KYLQueue
{
public:
    KYLCustomQueue *m_free_queue;//空闲队列
    KYLCustomQueue *m_work_queue;//工作队列
    
    KYLQueue();
    ~KYLQueue();
    
    // Queue Operation

    
    /**
     初始化队列

     @param queue 队列指针 KYLCustomQueue
     @param type 队列类型 KYLCustomQueueType
     */
    void InitQueue(KYLCustomQueue *queue,
                   KYLCustomQueueType type);

    
    /**
     入队

     @param queue 队列指针 KYLCustomQueue
     @param node 队列类型 KYLCustomQueueType
     */
    void EnQueue(KYLCustomQueue *queue,
                 KYLCustomQueueNode *node);

    
    /**
     出队

     @param queue 队列指针 KYLCustomQueue
     @return 出队的节点指针 KYLCustomQueueNode
     */
    KYLCustomQueueNode *DeQueue(KYLCustomQueue *queue);

    
    /**
     清空队列

     @param queue 队列指针KYLCustomQueue
     */
    void ClearKYLCustomQueue(KYLCustomQueue *queue);
    
    
    /**
     释放节点

     @param node 节点指针KYLCustomQueueNode
     */
    void FreeNode(KYLCustomQueueNode* node);
    
    
    /**
     重置队列,释放工作队列和空闲队列资源

     @param workQueue 工作队列指针 KYLCustomQueue
     @param freeQueue 空闲队列指针 KYLCustomQueue
     */
    void ResetFreeQueue(KYLCustomQueue *workQueue, KYLCustomQueue *freeQueue);
    
private:

    pthread_mutex_t free_queue_mutex; //互斥锁
    pthread_mutex_t work_queue_mutex;
};

因为涉及到异步操作,所以需要对结点的操作加锁,使用时需要先初始化队列,然后定义了入队,出队,清除队列中元素,释放结点,重置空闲队列等操作。

2.3 初始化队列

#pragma mark - Queue Size   设置队列的长度,不可过长
const int KYLCustomQueueSize = 3;
KYLQueue::KYLQueue(){
    m_free_queue = (KYLCustomQueue *)malloc(sizeof(struct KYLCustomQueue));
    m_work_queue = (KYLCustomQueue *)malloc(sizeof(struct KYLCustomQueue));
    
    InitQueue(m_free_queue, KYLCustomFreeQueue);
    InitQueue(m_work_queue, KYLCustomWorkQueue);
    
    for (int i = 0; i < KYLCustomQueueSize; i++) {
        KYLCustomQueueNode *node = (KYLCustomQueueNode *)malloc(sizeof(struct KYLCustomQueueNode));
        node->data = NULL;
        node->size = 0;
        node->index= 0;
        this->EnQueue(m_free_queue, node);
    }
    
    pthread_mutex_init(&free_queue_mutex, NULL);
    pthread_mutex_init(&work_queue_mutex, NULL);
    
    log4cplus_info(kModuleName, "%s: Init finish !",__func__);
}


假设空闲队列结点总数为3.首先为工作队列与空闲队列分配内存,其次对其分别进行初始化操作,具体过程可参考Demo,然后根据结点总数来为每个结点初始化分配内存,并将分配好内存的结点入队到空闲队列中。

注意:结点的重用,我们仅仅初始化几个固定数量的结点,因为处理数据量较大,没有必要让程序始终做malloc与free,为了优化我们这里的队列相当于一个静态链表,即结点的复用,因为当结点在工作队列中使用完成后会将其中的数据置空并重新入队到空闲队列中,所以结点的总数始终保持不变。

2.4 入队

如上所述,入队操作如果是空闲队列,则使用头进的方式,即始终让入队的结点在队列的头部,具体代码实现即让当前结点的next指向空闲队列的头结点,然后将当前结点变为空闲队列的头结点;如果入队操作是工作队列,则使用尾进的方式,并对结点的index赋值,以便我们在取出结点时可以打印Index是否连续,如果连续则说明入队时始终保持顺序入队。

代码如下:

/**
 入队
 
 @param queue 队列指针 KYLCustomQueue
 @param node 队列类型 KYLCustomQueueType
 */
void KYLQueue::EnQueue(KYLCustomQueue *queue, KYLCustomQueueNode *node) {
    if (queue == NULL) {
        log4cplus_debug(kModuleName, "%s: current queue is NULL",__func__);
        return;
    }
    
    if (node==NULL) {
        log4cplus_debug(kModuleName, "%s: current node is NULL",__func__);
        return;
    }
    
    node->next = NULL;
    
    if (KYLCustomFreeQueue == queue->type) {
        pthread_mutex_lock(&free_queue_mutex);
        
        if (queue->front == NULL) {
            queue->front = node;
            queue->rear  = node;
        }else {
            /*
             // tail in,head out
             freeQueue->rear->next = node;
             freeQueue->rear = node;
             */
            
            // head in,head out
            node->next = queue->front;
            queue->front = node;
        }
        queue->size += 1;
        log4cplus_debug(kModuleName, "%s: free queue size=%d",__func__,queue->size);
        pthread_mutex_unlock(&free_queue_mutex);
    }
    
    if (KYLCustomWorkQueue == queue->type) {
        pthread_mutex_lock(&work_queue_mutex);
        //TODO
        static long nodeIndex = 0;
        node->index=(++nodeIndex);
        if (queue->front == NULL) {
            queue->front = node;
            queue->rear  = node;
        }else {
            queue->rear->next   = node;
            queue->rear         = node;
        }
        queue->size += 1;
        log4cplus_debug(kModuleName, "%s: work queue size=%d",__func__,queue->size);
        pthread_mutex_unlock(&work_queue_mutex);
    }
}

2.5 出队

出队操作无论空闲队列还是工作队列都是从头出,即取出当前队列头结点中的数据。

代码如下:

/**
 出队
 
 @param queue 队列指针 KYLCustomQueue
 @return 出队的节点指针 KYLCustomQueueNode
 */
KYLCustomQueueNode* KYLQueue::DeQueue(KYLCustomQueue *queue) {
    if (queue == NULL) {
        log4cplus_debug(kModuleName, "%s: current queue is NULL",__func__);
        return NULL;
    }
    
    const char *type = queue->type == KYLCustomWorkQueue ? "work queue" : "free queue";
    pthread_mutex_t *queue_mutex = ((queue->type == KYLCustomWorkQueue) ? &work_queue_mutex : &free_queue_mutex);
    KYLCustomQueueNode *element = NULL;
    
    pthread_mutex_lock(queue_mutex);
    element = queue->front;
    if(element == NULL) {
        pthread_mutex_unlock(queue_mutex);
        log4cplus_debug(kModuleName, "%s: The node is NULL",__func__);
        return NULL;
    }
    
    queue->front = queue->front->next;
    queue->size -= 1;
    pthread_mutex_unlock(queue_mutex);
    
    log4cplus_debug(kModuleName, "%s: type=%s size=%d",__func__,type,queue->size);
    return element;
}

注意:该结点为空与该结点中的数据为空不可混为一谈,如果该结点为空则说明没有从队列中取出结点,即空结点没有内存地址,而结点中的数据则为node->data,在本Demo中为相机产生的每一帧sampleBuffer数据。

2.6 重置空闲队列数据

当我们将执行一些中断操作,例如从本View跳转到其他View,或进入后台等操作,我们需要将工作队列中的结点均置空然后重新放回空闲队列,这样可以保证我们最初申请的结点还均有效可用,保证结点不会丢失。

代码如下:

/**
 重置队列,释放工作队列和空闲队列资源
 
 @param workQueue 工作队列指针 KYLCustomQueue
 @param freeQueue 空闲队列指针 KYLCustomQueue
 */
void KYLQueue::ResetFreeQueue(KYLCustomQueue *workQueue, KYLCustomQueue *freeQueue) {
    if (workQueue == NULL) {
        log4cplus_debug(kModuleName, "%s: The WorkQueue is NULL",__func__);
        return;
    }
    
    if (freeQueue == NULL) {
        log4cplus_debug(kModuleName, "%s: The FreeQueue is NULL",__func__);
        return;
    }
    
    int workQueueSize = workQueue->size;
    if (workQueueSize > 0) {
        for (int i = 0; i < workQueueSize; i++) {
            KYLCustomQueueNode *node = DeQueue(workQueue);
            CFRelease(node->data);
            node->data = NULL;
            EnQueue(freeQueue, node);
        }
    }
    log4cplus_info(kModuleName, "%s: ResetFreeQueue : The work queue size is %d, free queue size is %d",__func__,workQueue->size, freeQueue->size);
}

3.完整代码

  1. 头文件
//
//  KYLQueue.h
//  yuvShowKYLDemo
//
//  Created by yulu kong on 2019/7/31.
//  Copyright © 2019 yulu kong. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN


typedef enum {
    KYLCustomWorkQueue,
    KYLCustomFreeQueue
} KYLCustomQueueType;

typedef struct KYLCustomQueueNode {
    void    *data;//结点中使用void *类型的data存放我们需要的sampleBuffer,使用index记录当前装入结点的sampleBuffer的索引,
                  //以便我们在取出结点时比较是否是按照顺序取出,结点中还装着同类型下一个结点的元素。
    size_t  size;  // data size 数据大小
    long    index;
    struct  KYLCustomQueueNode *next; //记录下一个节点指针
} KYLCustomQueueNode;

//队列中即为我们装载的结点数量,因为我们采用的是预先分配固定内存,
//所以工作队列与空闲队列的和始终不变(因为结点中的元素不在工作队列就在空闲队列)
typedef struct KYLCustomQueue {
    int size;
    KYLCustomQueueType type; //队列类型
    KYLCustomQueueNode *front; //队列头
    KYLCustomQueueNode *rear;  //队列尾
} KYLCustomQueue;

class KYLQueue
{
public:
    KYLCustomQueue *m_free_queue;//空闲队列
    KYLCustomQueue *m_work_queue;//工作队列
    
    KYLQueue();
    ~KYLQueue();
    
    // Queue Operation

    
    /**
     初始化队列

     @param queue 队列指针 KYLCustomQueue
     @param type 队列类型 KYLCustomQueueType
     */
    void InitQueue(KYLCustomQueue *queue,
                   KYLCustomQueueType type);

    
    /**
     入队

     @param queue 队列指针 KYLCustomQueue
     @param node 队列类型 KYLCustomQueueType
     */
    void EnQueue(KYLCustomQueue *queue,
                 KYLCustomQueueNode *node);

    
    /**
     出队

     @param queue 队列指针 KYLCustomQueue
     @return 出队的节点指针 KYLCustomQueueNode
     */
    KYLCustomQueueNode *DeQueue(KYLCustomQueue *queue);

    
    /**
     清空队列

     @param queue 队列指针KYLCustomQueue
     */
    void ClearKYLCustomQueue(KYLCustomQueue *queue);
    
    
    /**
     释放节点

     @param node 节点指针KYLCustomQueueNode
     */
    void FreeNode(KYLCustomQueueNode* node);
    
    
    /**
     重置队列,释放工作队列和空闲队列资源

     @param workQueue 工作队列指针 KYLCustomQueue
     @param freeQueue 空闲队列指针 KYLCustomQueue
     */
    void ResetFreeQueue(KYLCustomQueue *workQueue, KYLCustomQueue *freeQueue);
    
private:

    pthread_mutex_t free_queue_mutex; //互斥锁
    pthread_mutex_t work_queue_mutex;
};


NS_ASSUME_NONNULL_END

  1. 实现文件
//
//  KYLQueue.m
//  yuvShowKYLDemo
//
//  Created by yulu kong on 2019/7/31.
//  Copyright © 2019 yulu kong. All rights reserved.
//

/*******************************************************************************************************************
 
 我们将空闲队列设计为头进头出,影响不大,因为我们每次只需要从空闲队列中取出一个空结点以供我们装入相机数据,所以没必要按照尾进头出的方式保证结点的顺序。
 我们将工作队列设计为尾进头出,因为我们要确保从相机中捕获的数据是连续的,以便后期我们播放出来的画面也是连续的,所以工作队列必须保证尾进头出。
 这样做我们相当于实现了用空闲队列当做缓冲队列,在正常情况
 (fps=30,即每秒产生30帧数据,大约每33ms产生一帧数据),如果在33ms内对数据进行的操作可以正常完成,则工作队列会保持始终为0或1,
 但是如果长期工作或遇到某一帧数据处理较慢的情况(即处理时间大于33ms)则工作队列的长度会增加,
 而正因为我们使用了这样的队列会保护那一帧处理慢的数据在仍然能够正常处理完。
 这种情景仅用于短时间内仅有几帧数据处理较慢,如果比如1s内有20几帧数据都处理很慢则可能导致工作队列太长,则体现不出此队列的优势。
 ********************************************************************************************************************/


#import "KYLQueue.h"
#import <pthread.h>
#include "log4cplus.h"

#pragma mark - Queue Size   设置队列的长度,不可过长
const int KYLCustomQueueSize = 3;

const static char *kModuleName = "KYLQueueProcess";

#pragma mark - Init

//构造函数
KYLQueue::KYLQueue(){
    m_free_queue = (KYLCustomQueue *)malloc(sizeof(struct KYLCustomQueue));
    m_work_queue = (KYLCustomQueue *)malloc(sizeof(struct KYLCustomQueue));
    
    InitQueue(m_free_queue, KYLCustomFreeQueue);
    InitQueue(m_work_queue, KYLCustomWorkQueue);
    
    for (int i = 0; i < KYLCustomQueueSize; i++) {
        KYLCustomQueueNode *node = (KYLCustomQueueNode *)malloc(sizeof(struct KYLCustomQueueNode));
        node->data = NULL;
        node->size = 0;
        node->index= 0;
        this->EnQueue(m_free_queue, node);
    }
    
    pthread_mutex_init(&free_queue_mutex, NULL);
    pthread_mutex_init(&work_queue_mutex, NULL);
    
    log4cplus_info(kModuleName, "%s: Init finish !",__func__);
}

/**
 初始化队列
 
 @param queue 队列指针 KYLCustomQueue
 @param type 队列类型 KYLCustomQueueType
 */

void KYLQueue::InitQueue(KYLCustomQueue *queue, KYLCustomQueueType type) {
    if (queue != NULL) {
        queue->type  = type;
        queue->size  = 0;
        queue->front = 0;
        queue->rear  = 0;
    }
}

#pragma mark - Main Operation

/**
 入队
 
 @param queue 队列指针 KYLCustomQueue
 @param node 队列类型 KYLCustomQueueType
 */
void KYLQueue::EnQueue(KYLCustomQueue *queue, KYLCustomQueueNode *node) {
    if (queue == NULL) {
        log4cplus_debug(kModuleName, "%s: current queue is NULL",__func__);
        return;
    }
    
    if (node==NULL) {
        log4cplus_debug(kModuleName, "%s: current node is NULL",__func__);
        return;
    }
    
    node->next = NULL;
    
    if (KYLCustomFreeQueue == queue->type) {
        pthread_mutex_lock(&free_queue_mutex);
        
        if (queue->front == NULL) {
            queue->front = node;
            queue->rear  = node;
        }else {
            /*
             // tail in,head out
             freeQueue->rear->next = node;
             freeQueue->rear = node;
             */
            
            // head in,head out
            node->next = queue->front;
            queue->front = node;
        }
        queue->size += 1;
        log4cplus_debug(kModuleName, "%s: free queue size=%d",__func__,queue->size);
        pthread_mutex_unlock(&free_queue_mutex);
    }
    
    if (KYLCustomWorkQueue == queue->type) {
        pthread_mutex_lock(&work_queue_mutex);
        //TODO
        static long nodeIndex = 0;
        node->index=(++nodeIndex);
        if (queue->front == NULL) {
            queue->front = node;
            queue->rear  = node;
        }else {
            queue->rear->next   = node;
            queue->rear         = node;
        }
        queue->size += 1;
        log4cplus_debug(kModuleName, "%s: work queue size=%d",__func__,queue->size);
        pthread_mutex_unlock(&work_queue_mutex);
    }
}

/**
 出队
 
 @param queue 队列指针 KYLCustomQueue
 @return 出队的节点指针 KYLCustomQueueNode
 */
KYLCustomQueueNode* KYLQueue::DeQueue(KYLCustomQueue *queue) {
    if (queue == NULL) {
        log4cplus_debug(kModuleName, "%s: current queue is NULL",__func__);
        return NULL;
    }
    
    const char *type = queue->type == KYLCustomWorkQueue ? "work queue" : "free queue";
    pthread_mutex_t *queue_mutex = ((queue->type == KYLCustomWorkQueue) ? &work_queue_mutex : &free_queue_mutex);
    KYLCustomQueueNode *element = NULL;
    
    pthread_mutex_lock(queue_mutex);
    element = queue->front;
    if(element == NULL) {
        pthread_mutex_unlock(queue_mutex);
        log4cplus_debug(kModuleName, "%s: The node is NULL",__func__);
        return NULL;
    }
    
    queue->front = queue->front->next;
    queue->size -= 1;
    pthread_mutex_unlock(queue_mutex);
    
    log4cplus_debug(kModuleName, "%s: type=%s size=%d",__func__,type,queue->size);
    return element;
}

/**
 重置队列,释放工作队列和空闲队列资源
 
 @param workQueue 工作队列指针 KYLCustomQueue
 @param freeQueue 空闲队列指针 KYLCustomQueue
 */
void KYLQueue::ResetFreeQueue(KYLCustomQueue *workQueue, KYLCustomQueue *freeQueue) {
    if (workQueue == NULL) {
        log4cplus_debug(kModuleName, "%s: The WorkQueue is NULL",__func__);
        return;
    }
    
    if (freeQueue == NULL) {
        log4cplus_debug(kModuleName, "%s: The FreeQueue is NULL",__func__);
        return;
    }
    
    int workQueueSize = workQueue->size;
    if (workQueueSize > 0) {
        for (int i = 0; i < workQueueSize; i++) {
            KYLCustomQueueNode *node = DeQueue(workQueue);
            CFRelease(node->data);
            node->data = NULL;
            EnQueue(freeQueue, node);
        }
    }
    log4cplus_info(kModuleName, "%s: ResetFreeQueue : The work queue size is %d, free queue size is %d",__func__,workQueue->size, freeQueue->size);
}

/**
 清空队列
 
 @param queue 队列指针KYLCustomQueue
 */
void KYLQueue::ClearKYLCustomQueue(KYLCustomQueue *queue) {
    while (queue->size) {
        KYLCustomQueueNode *node = this->DeQueue(queue);
        this->FreeNode(node);
    }
    
    log4cplus_info(kModuleName, "%s: Clear KYLQueue queue",__func__);
}

/**
 释放节点
 
 @param node 节点指针KYLCustomQueueNode
 */
void KYLQueue::FreeNode(KYLCustomQueueNode* node) {
    if(node != NULL){
        free(node->data);
        free(node);
    }
}

4.调用缓存队列实例

  1. 将samplebuffer放入空闲队列
    设置相机代理后,在 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 方法中将samplebuffer装入空闲队列
    代码如下:
#pragma mark ------------------AVCaptureVideoDataOutputSampleBufferDelegate--------------------------------
// Called whenever an AVCaptureVideoDataOutput instance outputs a new video frame. 每产生一帧视频帧时调用一次
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    //    if(!CMSampleBufferDataIsReady(sampleBuffer)) {
    //        NSLog( @"sample buffer is not ready. Skipping sample" );
    //        return;
    //    }
    
    //     将相机产生的Samplebuffer入队
    [self addBufferToWorkQueueWithSampleBuffer:sampleBuffer];
    
}

- (void)addBufferToWorkQueueWithSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    KYLCustomQueueNode *node = _captureBufferQueue->DeQueue(_captureBufferQueue->m_free_queue);
    if (node == NULL) {
        log4cplus_debug(kModuleName, "Data in , the node is NULL !");
        return;
    }
    CFRetain(sampleBuffer);
    node->data = sampleBuffer;
    _captureBufferQueue->EnQueue(_captureBufferQueue->m_work_queue, node);
    
    log4cplus_debug(kModuleName, "Data in ,  work size = %d, free size = %d !",_captureBufferQueue->m_work_queue->size, _captureBufferQueue->m_free_queue->size);
}

注意:因为相机回调中捕捉的sampleBuffer是有生命周期的所以需要手动CFRetain一下使我们队列中的结点持有它。

  1. 开启一条线程处理队列中的Buffer

使用pthread创建一条线程,每隔10ms取一次数据,我们可以在此对取到的数据进行我们想要的操作,操作完成后再将清空释放sampleBuffer再将其装入空闲队列供我们循环使用。

代码如下:

#pragma mark - 处理Samplebuffer的线程
- (void)handleCacheThread {
    while (true) {
        // 从队列取出在相机回调中放入队列的线程
        KYLCustomQueueNode *node = _captureBufferQueue->DeQueue(_captureBufferQueue->m_work_queue);
        if (node == NULL) {
            log4cplus_debug(kModuleName, "Data node is NULL");
            usleep(10*1000);
            continue;
        }
        
        CMSampleBufferRef sampleBuffer     = (CMSampleBufferRef)node->data;
        // 打印结点的index,如果连续则说明在相机回调中放入的samplebuffer是连续的
        log4cplus_debug(kModuleName, "Test index : %ld",node->index);
        
        /* 可在此处理从队列中拿到的Buffer,用完后记得释放内存并将结点重新放回空闲队列
         * ........
         */
        
        CFRelease(sampleBuffer);
        node->data = NULL;
        _captureBufferQueue->EnQueue(_captureBufferQueue->m_free_queue, node);
    }
}

void * startCropTask(void *param) {
    pthread_setname_np("TVUCropThread");
    KYLTestBufferController *obj = (__bridge_transfer KYLTestBufferController *)param;
    [obj handleCacheThread];
    
    return NULL;
}

循环缓存队列实现

原理

实现细节

完整代码

  • 头文件:
//
//  KYLCircleBuf.h
//  yuvShowKYLDemo
//
//  Created by yulu kong on 2019/7/27.
//  Copyright © 2019 yulu kong. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef struct tag_AV_VIDEO_BUF_HEAD
{
    unsigned int head; /* Õ∑£¨±ÿ–ε»”⁄0xFF00FF */
    unsigned int timestamp; // ±º‰¥¡£¨»Áπ˚ «¬ºœÒ£¨‘ÚÃÓ¬ºœÒµƒ ±º‰¥¡£¨»Áπ˚ « µ ± ”∆µ£¨‘ÚŒ™0
    unsigned int len;    /*≥§∂»*/
    unsigned int frametype;
}AV_VIDEO_BUF_HEAD;

typedef struct tag_stAVStreamHead
{
    unsigned int nCodecID;     // refer to SEP2P_ENUM_AV_CODECID
    char   nParameter;     // Video: refer to SEP2P_ENUM_VIDEO_FRAME.   Audio:(samplerate << 2) | (databits << 1) | (channel), samplerate refer to SEP2P_ENUM_AUDIO_SAMPLERATE; databits refer to SEP2P_ENUM_AUDIO_DATABITS; channel refer to SEP2P_ENUM_AUDIO_CHANNEL
    char   nLivePlayback;// Video: 0:live video or audio;  1:playback video or audio
    char   reserve1[2];
    unsigned int  nStreamDataLen;    // Stream data size after following struct 'STREAM_HEAD'
    unsigned int  nTimestamp;        // Timestamp of the frame, in milliseconds
    unsigned char  nNumConnected;    // amount that app connected this device for M,X series when nCodecID is AV_CODECID_VIDEO...
    unsigned char  nNumLiveView;    // amount that app is at liveview UI for M,X series when nCodecID is AV_CODECID_VIDEO...
    char   reserve2[2];
    unsigned int  nPlaybackID;        // reserve2[2,5] -> nPlaybackID, modified on 20141201
}AV_STREAM_HEAD;


class KYLCircleBuf
{
public:
    KYLCircleBuf();
    ~KYLCircleBuf();
    
    bool Create(int size);
    void Release();
    int Read(void* buf, int size);
    int  ReadByPeer(void* buf, int size);
    int Write(void* buf, int size);
    int GetStock();
    void Reset();
    bool IsBufCreateSucceed(){return m_bCreateBufSucceed;}
    char * ReadOneFrame(int &len);
    char* ReadOneFrame1(int &len, AV_VIDEO_BUF_HEAD & videobufhead);
    char* ReadOneFrame2(int &len, AV_STREAM_HEAD & videobufhead);
    
private:
    int Read1(void* buf, int size);
    
    
protected:
    char* m_pBuf;
    int m_nSize;
    int m_nStock;
    int m_nReadPos;
    int m_nWritePos;
    
    int  m_nTimeout;
    
    NSCondition *m_Lock;
    
private:
    
    int m_n;
    bool m_bCreateBufSucceed;
    
};



NS_ASSUME_NONNULL_END

  • 实现文件:
//
//  KYLCircleBuf.m
//  yuvShowKYLDemo
//
//  Created by yulu kong on 2019/7/27.
//  Copyright © 2019 yulu kong. All rights reserved.
//

#import "KYLCircleBuf.h"

KYLCircleBuf::KYLCircleBuf()
{
    m_pBuf = NULL;
    m_nSize = 0;
    m_nStock= 0;
    m_nWritePos= 0;
    m_nReadPos = 0;
    m_bCreateBufSucceed = false;
    m_Lock = [[NSCondition alloc] init];
}

KYLCircleBuf::~KYLCircleBuf()
{
    Release();
    
    //[m_Lock release];
    m_Lock = nil;
}

bool KYLCircleBuf::Create(int size)
{
    if (size <= 0)
    {
        m_bCreateBufSucceed = false;
        return false;
    }
    if (m_pBuf != NULL)
    {
        delete[] m_pBuf;
        m_pBuf = NULL;
    }
    m_pBuf = new char[size];
    if(m_pBuf == NULL)
    {
        m_bCreateBufSucceed = false;
        return false;
    }
    m_nSize = size;
    m_nStock = 0;
    m_nWritePos = 0;
    m_nReadPos = 0;
    m_bCreateBufSucceed = true;
    return true;
}

void KYLCircleBuf::Release()
{
    [m_Lock lock];
    
    m_bCreateBufSucceed = false;
    if (m_pBuf == NULL)
    {
        [m_Lock unlock];
        return;
    }
    if(m_pBuf != NULL)
    {
        delete[] m_pBuf;
        m_pBuf = NULL;
    }
    m_nSize = 0;
    m_nStock = 0;
    m_nReadPos = 0;
    m_nWritePos = 0;
    [m_Lock unlock];
}

char* KYLCircleBuf::ReadOneFrame1(int &len, AV_VIDEO_BUF_HEAD & videobufhead)
{
    [m_Lock lock];
    len = 0;
    if(m_nStock == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    char *pbuf = NULL;
    AV_VIDEO_BUF_HEAD videohead;
    int nRet = Read1((char*)&videohead, sizeof(AV_VIDEO_BUF_HEAD));
    if(nRet == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    pbuf = new char[videohead.len] ;
    nRet = Read1((char*)pbuf, videohead.len);
    if(nRet == 0)
    {
        delete []pbuf;
        pbuf = NULL;
        [m_Lock unlock];
        return NULL;
    }
    memcpy((char*)&videobufhead, (char*)&videohead, sizeof(AV_VIDEO_BUF_HEAD));
    len = videohead.len;
    [m_Lock unlock];
    return pbuf;
}

char* KYLCircleBuf::ReadOneFrame2(int &len, AV_STREAM_HEAD & streambufhead)
{
    [m_Lock lock];
    len = 0;
    if(m_nStock == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    char *pbuf = NULL;
    AV_STREAM_HEAD streamhead;
    int nRet = Read1((char*)&streamhead, sizeof(AV_STREAM_HEAD));
    if(nRet == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    pbuf = new char[streamhead.nStreamDataLen] ;
    nRet = Read1((char*)pbuf, streamhead.nStreamDataLen);
    if(nRet == 0)
    {
        delete []pbuf;
        pbuf = NULL;
        [m_Lock unlock];
        return NULL;
    }
    memcpy((char*)&streambufhead, (char*)&streamhead, sizeof(AV_STREAM_HEAD));
    len = streamhead.nStreamDataLen;
    [m_Lock unlock];
    return pbuf;
    
}

char* KYLCircleBuf::ReadOneFrame(int &len)
{
    [m_Lock lock];
    len = 0;
    if(m_nStock == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    char *pbuf = NULL;
    AV_VIDEO_BUF_HEAD videohead;
    int nRet = Read1((char*)&videohead, sizeof(AV_VIDEO_BUF_HEAD));
    if(nRet == 0)
    {
        [m_Lock unlock];
        return NULL;
    }
    pbuf = new char[videohead.len] ;
    if(pbuf == NULL)
    {
        [m_Lock unlock];
        return NULL;
    }
    nRet = Read1((char*)pbuf, videohead.len);
    if(nRet == 0)
    {
        delete []pbuf;
        pbuf = NULL;
        [m_Lock unlock];
        return NULL;
    }
    len = videohead.len;
    [m_Lock unlock];
    return pbuf;
    
}

int KYLCircleBuf::Read1(void* buf, int size)
{
    if (m_nStock < size)
    {
        return 0;
    }
    int left = 0;
    int offs = m_nWritePos - m_nReadPos;
    if (offs > 0)
    {
        memcpy(buf, &m_pBuf[m_nReadPos], size);
        m_nReadPos += size;
    }
    else
    {
        offs = m_nSize - m_nReadPos;
        if (offs > size)
        {
            memcpy(buf, &m_pBuf[m_nReadPos], size);
            m_nReadPos += size;
        }
        else
        {
            memcpy(buf, &m_pBuf[m_nReadPos], offs);
            left = size - offs;
            memcpy(&((char*)buf)[offs], m_pBuf, left);
            m_nReadPos = left;
        }
    }
    m_nStock -= size;
    return size;
}


//从缓存中读数据,当缓存数据不够读则读取失败,返回为0
int KYLCircleBuf::Read(void* buf, int size)
{
    //Lock the buffer
    [m_Lock lock];
    if (m_nStock < size)
    {
        [m_Lock unlock];
        return 0;
    }
    int left = 0;
    int offs = m_nWritePos - m_nReadPos;
    if (offs > 0)
    {
        memcpy(buf, &m_pBuf[m_nReadPos], size);
        m_nReadPos += size;
    }
    else
    {
        offs = m_nSize - m_nReadPos;
        if (offs > size)
        {
            memcpy(buf, &m_pBuf[m_nReadPos], size);
            m_nReadPos += size;
        }
        else
        {
            memcpy(buf, &m_pBuf[m_nReadPos], offs);
            left = size - offs;
            memcpy(&((char*)buf)[offs], m_pBuf, left);
            m_nReadPos = left;
        }
    }
    m_nStock -= size;
    [m_Lock unlock];
    return size;
}

int  KYLCircleBuf::ReadByPeer(void* buf, int size)
{
    //Lock the buffer
    [m_Lock lock];
    if(m_nStock < size)
    {
        [m_Lock unlock];
        return 0;
    }
    int left = 0;
    int offs = m_nWritePos - m_nReadPos;
    if(offs > 0)
    {
        memcpy(buf, &m_pBuf[m_nReadPos], size);
    }else{
        offs = m_nSize - m_nReadPos;
        if(offs > size)
        {
            memcpy(buf, &m_pBuf[m_nReadPos], size);
        }else{
            memcpy(buf, &m_pBuf[m_nReadPos], offs);
            left = size - offs;
            memcpy(&((char*)buf)[offs], m_pBuf, left);
            //m_nReadPos = left;
        }
    }
    [m_Lock unlock];
    return size;
}


int KYLCircleBuf::Write(void* buf, int size)
{
    //Lock the buffer
    [m_Lock lock];
    // the buffer is full
    if (m_nStock + size > m_nSize)
    {
        [m_Lock unlock];
        return 0;
    }
    int left = 0;
    int offs = m_nSize - m_nWritePos;
    if (offs > size)
    {
        memcpy(&m_pBuf[m_nWritePos], buf, size);
        m_nWritePos += size;
    }
    else
    {
        memcpy(&m_pBuf[m_nWritePos], buf, offs);
        left = size - offs;
        memcpy(m_pBuf, &((char*)buf)[offs], left);
        m_nWritePos = left;
    }
    
    m_nStock += size;
    [m_Lock unlock];
    return size;
    
}

int KYLCircleBuf::GetStock()
{
    int n;
    [m_Lock lock];
    n =  m_nStock;
    [m_Lock unlock];
    
    return n;
}

void KYLCircleBuf::Reset()
{
    [m_Lock lock];
    m_nReadPos = 0;
    m_nWritePos = 0;
    m_nStock = 0;
    [m_Lock unlock];
}

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值