更新时间:2023-08-09 08:09:35下载pdf
涂鸦结合视频帧的编码特性(H.264/265 的 I 帧、P 帧)以及音频帧数据特点,基于环形缓冲区(Ring Buffer)的设计原理,实现了高效无锁、多进多出的 音视频环形缓冲区。
环形缓冲区也称为圆形缓冲区(Circular Buffer)或循环缓冲区(Cyclic Buffer),能够在固定大小的内存内,存放头尾循环读写的数据结构。
生产者将数据帧按序写入 Buffer 中,当到达内存尾部时跳转到头部覆盖老数据。
一个或者多个消费者按序读取数据帧,并确保其数据是正确未被覆盖,以及相互之间不会产生干扰。
涂鸦音视频环形缓冲区具有以下特点:
svc_ring_buffer
音视频数据帧在 Ring Buffer 中统一按节点存储管理。
typedef struct
{
UINT_T index; // 节点数据索引,循环使用
MEDIA_FRAME_TYPE_E type; // 数据帧类型
UCHAR_T *raw_data; // 原始数据指针
UINT_T size; // 原始数据大小
UINT64_T pts; // 呈现时间戳
UINT64_T timestamp; // 解码时间戳
UINT_T seq_no; // 帧序列号,持续增加不循环
UCHAR_T *extra_data; // 附加数据,暂无使用
UINT_T extra_size; // 附加数据大小,暂无使
UINT_T seq_sync; // 用于同步音频/视频的全局序列号
} RING_BUFFER_NODE_T;
typedef struct
{
UINT_T bitrate; // 码率(上限)大小,单位 kb
UINT_T fps; // 帧率
UINT_T max_buffer_seconds; // 最大缓存时长
FUNC_REQUEST_KEY_FRAME_CB request_key_frame_cb; // 关键帧视频编回调接口,默认为空
} RING_BUFFER_INIT_PARAM_T;
参数说明
参数 | 说明 |
---|---|
max_buffer_seconds |
表示最大缓存时长,建议值为大于 1 个 GOP(Group Of Picture)长度,小于 10 秒。当设置为 0 时内部默认使用 10 秒。 |
typedef VOID (*FUNC_REQUEST_KEY_FRAME_CB)(INT_T device, INT_T channel, IPC_STREAM_E stream);
该回调用于触发外部编码器快速产生一个视频编码关键帧(I 帧)。
参数说明
参数 | 说明 |
---|---|
device |
子设备号,仅用于 XVR 监视器等产品。对于不包含子设备的 IPC 单品,值为 0 。 |
channel |
视频通道号,用于双目等多镜头多传感器产品,从 0 开始编号。 |
stream |
码流通道号,用于区分同一视频通道输出的高清、标清等不同数据码流。 |
针对特定设备、特定视频通道的特定码流,执行初始化。该操作会执行缓存空间的创建与节点的初始化。
/** @brief initialize one ring buffer for one channel(one device)
* @param[in] device device number
* @param[in] channel channel number in device
* @param[in] stream stream number in channel
* @param[in] pparam initialize parameter
* @return error code
* - OPRT_OK init success
* - Others init failed
*/
OPERATE_RET tuya_ipc_ring_buffer_init(INT_T device, INT_T channel, IPC_STREAM_E stream, RING_BUFFER_INIT_PARAM_T* pparam);
针对特定设备、特定视频通道的特定码流,执行反初始化,释放缓存空间与节点资源。
/** @brief uninitialize one ring buffer for one channel(one device)
* @param[in] device device number
* @param[in] channel channel number in device
* @param[in] stream stream number in channel
* @return error code
* - OPRT_OK uninit success
* - Others uninit failed
*/
OPERATE_RET tuya_ipc_ring_buffer_uninit(INT_T device, INT_T channel, IPC_STREAM_E stream);
以数据生产者(写模式)或数据消费者(读模式),创建特定码流缓存的用户句柄。该句柄将用于后续的数据操作。
/** @brief open a new session for read/write
* @param[in] device device number
* @param[in] channel channel number in device
* @param[in] stream stream number in channel
* @param[in] open_type open type
* @return user handle
*/
RING_BUFFER_USER_HANDLE_T tuya_ipc_ring_buffer_open(INT_T device, INT_T channel, IPC_STREAM_E stream, RBUF_OPEN_TYPE_E open_type);
关闭用户句柄对应的缓存。
/** @brief close session
* @warning open/close should be called in pair like file
* @param[in] handle user handle return by open
* @return error code
* - OPRT_OK success
* - Others failed
*/
OPERATE_RET tuya_ipc_ring_buffer_close(RING_BUFFER_USER_HANDLE_T handle);
往特定的码流缓存中新增一个数据帧。
/** @brief append new frame into a ring buffer
* @param[in] handle user handle return by open
* @param[in] addr the address of the data to append
* @param[in] size size of the data
* @param[in] type media type
* @param[in] pts time stamp in us
* @return error code
* - OPRT_OK success
* - Others failed
*/
OPERATE_RET tuya_ipc_ring_buffer_append_data(RING_BUFFER_USER_HANDLE_T handle, UCHAR_T *addr, UINT_T size, MEDIA_FRAME_TYPE_E type, UINT64_T pts);
往特定的码流缓存中新增一个数据帧,并指定时间戳。
/** @brief append new frame into ring buffer with custom timestamp
* @param[in] handle user handle return by open
* @param[in] addr the address of the data to append
* @param[in] size size of the data
* @param[in] type media type
* @param[in] pts time stamp in us
* @param[in] timestamp time stamp in ms
* @return error code
* - OPRT_OK success
* - Others failed
*/
OPERATE_RET tuya_ipc_ring_buffer_append_data_with_timestamp(RING_BUFFER_USER_HANDLE_T handle, UCHAR_T *addr, UINT_T size, MEDIA_FRAME_TYPE_E type, UINT64_T pts, UINT64_T timestamp);
根据用户句柄,从特定设备、特定视频通道的特定码流缓存中获取一帧数据。接口内部会自动维护一个位置与状态信息,数据消费者只需根据业务需求重复调用该接口,即可按序获得新的数据。
当获取数据的速度较慢导致数据过老而被循环覆盖时,接口内部会自动跳转到最新的符合编码特性的数据帧。
/** @brief get next frame from ring buffer, will jump to the latest frame if delayed too much time
* @param[in] handle user handle return by open
* @param[in] is_retry whether retry to get the last frame
* @return the ring buffer node or NULL if there is no newer frame in ring buffer
*/
RING_BUFFER_NODE_T *tuya_ipc_ring_buffer_get_frame(RING_BUFFER_USER_HANDLE_T handle, BOOL_T is_retry);
在音频码流通道中,从最新的数据帧往前查找第 frame_num
个帧。在视频码流通道中,会从第 frame_num
帧起继续往前查找直到数据帧类型为关键帧 I 帧。该接口仅用于获得对应的数据帧节点。
/** @brief start with the newest frame, find previous frame by count frame_num.
for video channel(device), it will keep find previous frame until it's I frame.
* @param[in] handle user handle returned by open
* @param[in] frame_num count frame num backwards
* @return the ring buffer node or NULL if there is no newer frame in ring buffer
*/
RING_BUFFER_NODE_T *tuya_ipc_ring_buffer_find_pre_by_frame(RING_BUFFER_USER_HANDLE_T handle, UINT_T frame_num);
在音频码流通道中,从最新的数据帧往前查找第 frame_num
个帧。在视频码流通道中,会从第 frame_num
帧起继续往前查找直到数据帧类型为关键帧 I 帧。
该接口在获得对应的预录帧节点后,会将对应的用户句柄的状态更新到该节点,即此时再调用 tuya_ipc_ring_buffer_get_frame
,获得该预录帧的后一帧。
/** @brief start with the newest frame, find previous frame by count frame_num, and update&anchor user(of userIndex) to this frame node.
for video channel(device), it will keep find previous frame until it's I frame.
* @param[in] handle user handle returned by open
* @param[in] frame_num count frame num backwards
* @return the ring buffer node or NULL if there is no newer frame in ring buffer
*/
RING_BUFFER_NODE_T *tuya_ipc_ring_buffer_get_pre_frame(RING_BUFFER_USER_HANDLE_T handle, UINT_T frame_num);
Ring Buffer 在初始化时即申请连续的内存。这样设计的考虑,是为了减少在运行过程中,占用的内存动态变化导致的 OOM(Out of memory,内存溢出)等问题。每路码流占用的内存大小为,最大缓存时长 max_buffer_seconds
+1 对应码率 bitrate 的乘积。
在 IPC 的应用场景中,Ring Buffer 是复用于实时预览、本地录像存储、云存储等所有业务的。如果缓存时长较短,则在云存储上传录像、本地录像写磁盘、实时预览网络抖动的情况下,极易触发跳帧策略,影响用户体验。
为了防止异常场景出现,最大视频帧数据为根据设定的码率同比例变化,对应 1.5Mbps 数据,单帧上限为 280KB。
当数据消费者落后生产者太多,以至于将发生数据被覆盖,包括节点信息覆盖和内存空间覆盖时,自动触发跳帧策略。
视频帧会跳到最新的关键 I 帧,音频会跳到最新的帧。
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈