hi3519dv500OSD示例实现讲解
hi3519dv500OSD代码
sample_vio_osd.c
这个文件通过sample_vio直接调用相关函数直接完成OSD功能的实现,在它内部在完成具体实现某一种OSD的调用。
#include <stdint.h>
#include <stdio.h>
#include "sample_comm.h"
#include "securec.h"
#include "sample_vio_osd.h"
#include "sample_vio_osd_text.h"
#include "sample_vio_osd_corner.h"
static td_bool g_osd_started = TD_FALSE;
static sample_vio_osd_text_ctx_t g_text_ctx;
static sample_vio_osd_corner_ctx_t g_corner_ctx;
/*
* 功能:
* 初始化时间 OSD 的输入配置。
*
* 输入参数:
* app_stop_flag:
* 应用停止标志指针。时间 OSD 刷新线程会周期性检查该标志:
* 0 表示继续运行,非 0 表示请求停止。
* 可以传 TD_NULL,表示不通过该标志停止。
*
* 输出参数:
* config:
* 时间 OSD 配置结构体。函数负责填写全部字段。
*
* 返回值:
* 无。
*/
static td_void sample_vio_osd_text_config_init(
sample_vio_osd_text_config_t *config,
volatile sig_atomic_t *app_stop_flag)
{
(td_void)memset_s(config, sizeof(*config), 0, sizeof(*config));
config->rgn_handle = 0;
config->venc_dev = 0;
config->venc_chn = 0;
config->rgn_width = 640;
config->rgn_height = 72;
config->rgn_canvas_num = 2;
config->rgn_bg_color = 0x0000;
config->bmp_fg_color = 0xFFFF;
config->chn_pos_x = 24;
config->chn_pos_y = 984;
config->chn_fg_alpha = 255;
config->chn_bg_alpha = 0;
config->chn_layer = 0;
config->bmp_text_x = 18;
config->bmp_text_y = 24;
config->bmp_scale = 3;
config->refresh_ms = 1000;
config->app_stop_flag = app_stop_flag;
}
/*
* 功能:
* 初始化 CornerRect OSD 的输入配置。
*
* 输入参数:
* 无。
*
* 输出参数:
* config:
* CornerRect OSD 配置结构体。函数负责填写全部字段。
*
* 返回值:
* 无。
*/
static td_void sample_vio_osd_corner_config_init(
sample_vio_osd_corner_config_t *config)
{
(td_void)memset_s(config, sizeof(*config), 0, sizeof(*config));
config->rgn_handle = 140;
config->vpss_grp = 0;
config->vpss_chn = 0;
config->rgn_x = 100;
config->rgn_y = 100;
config->rgn_width = 400;
config->rgn_height = 300;
config->rgn_thick = 4;
config->rgn_hor_len = 40;
config->rgn_ver_len = 40;
config->chn_layer = 0;
config->rgn_color_rgb888 = 0x00FF00;
config->rgn_type = SAMPLE_VIO_OSD_CORNER_TYPE_CORNER;
}
td_s32 sample_vio_osd_start(volatile sig_atomic_t *app_stop_flag)
{
sample_vio_osd_text_config_t text_config;
sample_vio_osd_corner_config_t corner_config;
int32_t ret;
if (g_osd_started == TD_TRUE) {
return TD_SUCCESS;
}
sample_vio_osd_text_config_init(&text_config, app_stop_flag);
ret = sample_vio_osd_text_start(&text_config, &g_text_ctx);
if (ret != SAMPLE_VIO_OSD_TEXT_OK) {
printf("OSD: text start failed, ret=%d, sdk_error=%d.\n",
ret, g_text_ctx.last_error);
return TD_FAILURE;
}
sample_vio_osd_corner_config_init(&corner_config);
ret = sample_vio_osd_corner_start(&corner_config, &g_corner_ctx);
if (ret != SAMPLE_VIO_OSD_CORNER_OK) {
printf("CORNER start failed, ret=%d, sdk_error=%d; "
"time OSD continues.\n",
ret, g_corner_ctx.last_sdk_error);
}
g_osd_started = TD_TRUE;
return TD_SUCCESS;
}
td_void sample_vio_osd_stop(td_void)
{
int32_t text_ret;
int32_t corner_ret;
if (g_osd_started != TD_TRUE) {
return;
}
text_ret = sample_vio_osd_text_stop(&g_text_ctx);
if (text_ret != SAMPLE_VIO_OSD_TEXT_OK) {
printf("OSD: text stop failed, ret=%d, last_error=%d.\n",
text_ret, g_text_ctx.last_error);
}
corner_ret = sample_vio_osd_corner_stop(&g_corner_ctx);
if (corner_ret != SAMPLE_VIO_OSD_CORNER_OK) {
printf("CORNER stop failed, ret=%d, sdk_error=%d.\n",
corner_ret, g_corner_ctx.last_sdk_error);
}
g_osd_started = TD_FALSE;
}
代码讲解
static td_bool g_osd_started = TD_FALSE;
static sample_vio_osd_text_ctx_t g_text_ctx;
static sample_vio_osd_corner_ctx_t g_corner_ctx;
开始定义一个变量来判断整个OSD模块是否开启,随后定义两个参数分别保存时间和四角框的上下文信息。
static td_void sample_vio_osd_text_config_init(
sample_vio_osd_text_config_t *config,
volatile sig_atomic_t *app_stop_flag)
{
(td_void)memset_s(config, sizeof(*config), 0, sizeof(*config));
config->rgn_handle = 0;
config->venc_dev = 0;
config->venc_chn = 0;
config->rgn_width = 640;
config->rgn_height = 72;
config->rgn_canvas_num = 2;
config->rgn_bg_color = 0x0000;
config->bmp_fg_color = 0xFFFF;
config->chn_pos_x = 24;
config->chn_pos_y = 984;
config->chn_fg_alpha = 255;
config->chn_bg_alpha = 0;
config->chn_layer = 0;
config->bmp_text_x = 18;
config->bmp_text_y = 24;
config->bmp_scale = 3;
config->refresh_ms = 1000;
config->app_stop_flag = app_stop_flag;
}
这个函数是真实的初始化时间信息的函数,需要改变位置/样式就在这里改变相关参数。
sample_vio_osd_text_config_t/sample_vio_osd_corner_config_t
这个类型在时间对应的.h当中,保存的就是实现时间OSD需要用到的参数。
函数首先设置rgn_handle,这个相当于region的编号。随后设置venc_dev和venc_chn,venc_dev是venc设备0,这里只有这一个VENC所以只能选择0,venc_chn可选择0-15.
随后定义rgn_width和rgn_height,这个表示region的尺寸,随后在设置rgn_canvas_num,也就是canvas的个数,canvas就是缓冲区,只能设置为1/2,有刷新设置2,无刷新设置1。
接下来设置rgn_bg_color和bmp_fg_color,这里的前景和背景是bitmap自己的前景和region的背景,随后设置chn_pos_x和chn_pos_y这个对应region在画面上的坐标(左上点)。在设置chn_fg_alpha和chn_bg_alpha,这里的前景和背景指region的前景和背景,可以理解为这里的前景和背景往外退了一个层级。整个bmp为前景,其余region区域为背景。实际应用认为前面的前景背景表示显示什么颜色这里表示透明度即可。最后设置显示层级chn_layer。
bmp_text_x和bmp_text_y表示文字在region内部的位置,bmp_scale表示放大的倍数,因为默认是按照像素点7*5画的,不放大会很小。refresh_ms表示刷新时间,一秒刷新一次。app_stop_flag为应用停止指针,刷新线程时会检测应用状态。
static td_void sample_vio_osd_corner_config_init(
sample_vio_osd_corner_config_t *config)
{
(td_void)memset_s(config, sizeof(*config), 0, sizeof(*config));
config->rgn_handle = 140;
config->vpss_grp = 0;
config->vpss_chn = 0;
config->rgn_x = 100;
config->rgn_y = 100;
config->rgn_width = 400;
config->rgn_height = 300;
config->rgn_thick = 4;
config->rgn_hor_len = 40;
config->rgn_ver_len = 40;
config->chn_layer = 0;
config->rgn_color_rgb888 = 0x00FF00;
config->rgn_type = SAMPLE_VIO_OSD_CORNER_TYPE_CORNER;
}
首先设置rgn_handle,同样为region标号,类似id。vpss_grp,vpss_chn分别为组和通道组取值0-15,通道0-7,表示这个region要挂载到哪里。
rgn_x,rgn_y表示这个region在屏幕中的位置。rgn_width,rgn_height表示region的大小,rgn_thick表示线宽,rgn_hor_len,rgn_ver_len表示横向和纵向的长度。chn_layer表示显示的层级。rgn_color_rgb888表示画框颜色,rgn_type表示要画四角框还是整个方框。
td_s32 sample_vio_osd_start(volatile sig_atomic_t *app_stop_flag)
{
sample_vio_osd_text_config_t text_config;
sample_vio_osd_corner_config_t corner_config;
int32_t ret;
if (g_osd_started == TD_TRUE) {
return TD_SUCCESS;
}
sample_vio_osd_text_config_init(&text_config, app_stop_flag);
ret = sample_vio_osd_text_start(&text_config, &g_text_ctx);
if (ret != SAMPLE_VIO_OSD_TEXT_OK) {
printf("OSD: text start failed, ret=%d, sdk_error=%d.\n",
ret, g_text_ctx.last_error);
return TD_FAILURE;
}
sample_vio_osd_corner_config_init(&corner_config);
ret = sample_vio_osd_corner_start(&corner_config, &g_corner_ctx);
if (ret != SAMPLE_VIO_OSD_CORNER_OK) {
printf("CORNER start failed, ret=%d, sdk_error=%d; "
"time OSD continues.\n",
ret, g_corner_ctx.last_sdk_error);
}
g_osd_started = TD_TRUE;
return TD_SUCCESS;
}
外部就是通过调用这个函数来把OSD功能加载到视频流当中的,首先定义好两个需要初始化的变量,用于启动相关模块,随后一个防止重复启动的代码,然后两个逻辑完全相同的模块,都是先进行初始化,随后调用相关模块的启动函数进行启动,最后把状态变为已经启动。
td_void sample_vio_osd_stop(td_void)
{
int32_t text_ret;
int32_t corner_ret;
if (g_osd_started != TD_TRUE) {
return;
}
text_ret = sample_vio_osd_text_stop(&g_text_ctx);
if (text_ret != SAMPLE_VIO_OSD_TEXT_OK) {
printf("OSD: text stop failed, ret=%d, last_error=%d.\n",
text_ret, g_text_ctx.last_error);
}
corner_ret = sample_vio_osd_corner_stop(&g_corner_ctx);
if (corner_ret != SAMPLE_VIO_OSD_CORNER_OK) {
printf("CORNER stop failed, ret=%d, sdk_error=%d.\n",
corner_ret, g_corner_ctx.last_sdk_error);
}
g_osd_started = TD_FALSE;
}
这个是整个OSD的停止代码,先确定处于启动状态才需要停止,然后直接调用相关模块的停止函数,最后设置OSD参数为已经停止。
sample_vio_osd_corner.h
#ifndef SAMPLE_VIO_OSD_CORNER_H
#define SAMPLE_VIO_OSD_CORNER_H
#include <stdint.h>
/* 返回值 */
typedef enum {
SAMPLE_VIO_OSD_CORNER_OK = 0,
SAMPLE_VIO_OSD_CORNER_ERR_PARAM = -1,
SAMPLE_VIO_OSD_CORNER_ERR_STATE = -2,
SAMPLE_VIO_OSD_CORNER_ERR_CREATE = -3,
SAMPLE_VIO_OSD_CORNER_ERR_ATTACH = -4,
SAMPLE_VIO_OSD_CORNER_ERR_DETACH = -5,
SAMPLE_VIO_OSD_CORNER_ERR_DESTROY = -6
} sample_vio_osd_corner_result_t;
/* 角框类型 */
typedef enum {
SAMPLE_VIO_OSD_CORNER_TYPE_CORNER = 0,
SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE = 1
} sample_vio_osd_corner_type_t;
/*
* 输入配置参数。
* 所有坐标单位为像素,颜色为 RGB888:0xRRGGBB。
*/
typedef struct {
/* Region 参数 */
uint32_t rgn_handle; /* CORNER_RECT 可用 handle:140~151 */
int32_t rgn_x; /* 矩形左上角 X 坐标 */
int32_t rgn_y; /* 矩形左上角 Y 坐标 */
uint32_t rgn_width; /* 矩形宽度 */
uint32_t rgn_height; /* 矩形高度 */
uint32_t rgn_thick; /* 范围 2~16,必须为偶数 */
uint32_t rgn_hor_len; /* 横臂长度,2~8192 */
uint32_t rgn_ver_len; /* 竖臂长度,2~8192 */
uint32_t rgn_color_rgb888; /* 例如绿色:0x00FF00 */
sample_vio_osd_corner_type_t rgn_type;
/* 通道参数 */
int32_t vpss_grp; /* VPSS 组号 */
int32_t vpss_chn; /* VPSS 通道号 */
uint32_t chn_layer; /* 图层,范围 0~11 */
} sample_vio_osd_corner_config_t;
/*
* 输出上下文。
* 由调用者分配,start 成功时填充,stop 时使用。
*/
typedef struct {
uint32_t rgn_handle;
int32_t vpss_grp;
int32_t vpss_chn;
/* 最近一次 SDK 原始错误码,方便定位问题 */
int32_t last_sdk_error;
uint8_t started;
} sample_vio_osd_corner_ctx_t;
/*
* 输入:
* config - 角框配置,不能为 NULL
* out_ctx - 调用者提供的输出上下文,不能为 NULL
* 输出:
* out_ctx - start 成功后保存 Region handle、VPSS 通道和运行状态
* 返回:
* SAMPLE_VIO_OSD_CORNER_OK 或对应错误码
*/
int32_t sample_vio_osd_corner_start(
const sample_vio_osd_corner_config_t *config,
sample_vio_osd_corner_ctx_t *out_ctx);
/*
* 输入:
* ctx - 由 start 成功填充的上下文
* 输出:
* ctx->started 清零
* ctx->last_sdk_error 保存停止过程中的 SDK 错误
* 返回:
* SAMPLE_VIO_OSD_CORNER_OK 或对应错误码
*/
int32_t sample_vio_osd_corner_stop(sample_vio_osd_corner_ctx_t *ctx);
#endif /* SAMPLE_VIO_OSD_CORNER_H */
代码讲解
/* 返回值 */
typedef enum {
SAMPLE_VIO_OSD_CORNER_OK = 0,
SAMPLE_VIO_OSD_CORNER_ERR_PARAM = -1,
SAMPLE_VIO_OSD_CORNER_ERR_STATE = -2,
SAMPLE_VIO_OSD_CORNER_ERR_CREATE = -3,
SAMPLE_VIO_OSD_CORNER_ERR_ATTACH = -4,
SAMPLE_VIO_OSD_CORNER_ERR_DETACH = -5,
SAMPLE_VIO_OSD_CORNER_ERR_DESTROY = -6
} sample_vio_osd_corner_result_t;
/* 角框类型 */
typedef enum {
SAMPLE_VIO_OSD_CORNER_TYPE_CORNER = 0,
SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE = 1
} sample_vio_osd_corner_type_t;
首先有两个枚举类型,第一个表示如下图
|
错误码 |
|
SAMPLE_VIO_OSD_CORNER_OK |
|
SAMPLE_VIO_OSD_CORNER_ERR_PARAM |
|
SAMPLE_VIO_OSD_CORNER_ERR_STATE |
|
SAMPLE_VIO_OSD_CORNER_ERR_CREATE |
|
SAMPLE_VIO_OSD_CORNER_ERR_ATTACH |
|
SAMPLE_VIO_OSD_CORNER_ERR_DETACH |
|
SAMPLE_VIO_OSD_CORNER_ERR_DESTROY |
第二个枚举类型表示角框是四角的还是一个整框。
typedef struct {
/* Region 参数 */
uint32_t rgn_handle; /* CORNER_RECT 可用 handle:140~151 */
int32_t rgn_x; /* 矩形左上角 X 坐标 */
int32_t rgn_y; /* 矩形左上角 Y 坐标 */
uint32_t rgn_width; /* 矩形宽度 */
uint32_t rgn_height; /* 矩形高度 */
uint32_t rgn_thick; /* 范围 2~16,必须为偶数 */
uint32_t rgn_hor_len; /* 横臂长度,2~8192 */
uint32_t rgn_ver_len; /* 竖臂长度,2~8192 */
uint32_t rgn_color_rgb888; /* 例如绿色:0x00FF00 */
sample_vio_osd_corner_type_t rgn_type;
/* 通道参数 */
int32_t vpss_grp; /* VPSS 组号 */
int32_t vpss_chn; /* VPSS 通道号 */
uint32_t chn_layer; /* 图层,范围 0~11 */
} sample_vio_osd_corner_config_t;
这个就是刚才初始化配置的结构体变量,前面已经讲解
typedef struct {
uint32_t rgn_handle;
int32_t vpss_grp;
int32_t vpss_chn;
/* 最近一次 SDK 原始错误码,方便定位问题 */
int32_t last_sdk_error;
uint8_t started;
} sample_vio_osd_corner_ctx_t;
这个结构体保存了一些基本信息和上下文,前三个在停止当前模块需要使用,last_sdk_error表示最近一次的异常原因,started表示当前模块是否启动。
int32_t sample_vio_osd_corner_start(
const sample_vio_osd_corner_config_t *config,
sample_vio_osd_corner_ctx_t *out_ctx);
int32_t sample_vio_osd_corner_stop(sample_vio_osd_corner_ctx_t *ctx);
这两个是对启动和停止的声明。
sample_vio_osd_corner.c
#include <stdint.h>
#include "sample_comm.h"
#include "securec.h"
#include "sample_vio_osd_corner.h"
#define SAMPLE_VIO_OSD_CORNER_HANDLE_MIN 140U
#define SAMPLE_VIO_OSD_CORNER_HANDLE_MAX 151U
#define SAMPLE_VIO_OSD_CORNER_LAYER_MAX 11U
#define SAMPLE_VIO_OSD_CORNER_THICK_MIN 2U
#define SAMPLE_VIO_OSD_CORNER_THICK_MAX 16U
#define SAMPLE_VIO_OSD_CORNER_ARM_MIN 2U
#define SAMPLE_VIO_OSD_CORNER_ARM_MAX 8192U
#define SAMPLE_VIO_OSD_CORNER_RGB_MAX 0x00FFFFFFU
static td_bool sample_vio_osd_corner_config_valid(
const sample_vio_osd_corner_config_t *config)
{
if (config == TD_NULL) {
return TD_FALSE;
}
if (config->rgn_handle < SAMPLE_VIO_OSD_CORNER_HANDLE_MIN ||
config->rgn_handle > SAMPLE_VIO_OSD_CORNER_HANDLE_MAX) {
return TD_FALSE;
}
if (config->vpss_grp < 0 ||
config->vpss_chn < 0 ||
config->rgn_x < 0 ||
config->rgn_y < 0) {
return TD_FALSE;
}
if (config->rgn_width == 0 ||
config->rgn_height == 0 ||
config->rgn_thick < SAMPLE_VIO_OSD_CORNER_THICK_MIN ||
config->rgn_thick > SAMPLE_VIO_OSD_CORNER_THICK_MAX ||
(config->rgn_thick & 1U) != 0U) {
return TD_FALSE;
}
if (config->rgn_hor_len < SAMPLE_VIO_OSD_CORNER_ARM_MIN ||
config->rgn_hor_len > SAMPLE_VIO_OSD_CORNER_ARM_MAX ||
config->rgn_ver_len < SAMPLE_VIO_OSD_CORNER_ARM_MIN ||
config->rgn_ver_len > SAMPLE_VIO_OSD_CORNER_ARM_MAX) {
return TD_FALSE;
}
if (config->chn_layer > SAMPLE_VIO_OSD_CORNER_LAYER_MAX ||
config->rgn_color_rgb888 > SAMPLE_VIO_OSD_CORNER_RGB_MAX) {
return TD_FALSE;
}
if (config->rgn_type != SAMPLE_VIO_OSD_CORNER_TYPE_CORNER &&
config->rgn_type != SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE) {
return TD_FALSE;
}
return TD_TRUE;
}
static td_s32 sample_vio_osd_corner_get_sdk_type(
sample_vio_osd_corner_type_t type,
ot_corner_rect_type *sdk_type)
{
switch (type) {
case SAMPLE_VIO_OSD_CORNER_TYPE_CORNER:
*sdk_type = OT_CORNER_RECT_TYPE_CORNER;
return TD_SUCCESS;
case SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE:
*sdk_type = OT_CORNER_RECT_TYPE_FULL_LINE;
return TD_SUCCESS;
default:
return TD_FAILURE;
}
}
int32_t sample_vio_osd_corner_start(
const sample_vio_osd_corner_config_t *config,
sample_vio_osd_corner_ctx_t *out_ctx)
{
ot_rgn_attr region;
ot_rgn_chn_attr chn_attr;
ot_mpp_chn vpss_chn;
ot_corner_rect_type sdk_type;
td_s32 ret;
if (out_ctx == TD_NULL) {
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
if (out_ctx->started != 0U) {
return SAMPLE_VIO_OSD_CORNER_ERR_STATE;
}
(td_void)memset_s(out_ctx, sizeof(*out_ctx), 0, sizeof(*out_ctx));
if (sample_vio_osd_corner_config_valid(config) != TD_TRUE ||
sample_vio_osd_corner_get_sdk_type(config->rgn_type, &sdk_type) != TD_SUCCESS) {
out_ctx->last_sdk_error = SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
(td_void)memset_s(®ion, sizeof(region), 0, sizeof(region));
region.type = OT_RGN_CORNER_RECT;
ret = ss_mpi_rgn_create(config->rgn_handle, ®ion);
if (ret != TD_SUCCESS) {
out_ctx->last_sdk_error = ret;
return SAMPLE_VIO_OSD_CORNER_ERR_CREATE;
}
vpss_chn.mod_id = OT_ID_VPSS;
vpss_chn.dev_id = config->vpss_grp;
vpss_chn.chn_id = config->vpss_chn;
(td_void)memset_s(&chn_attr, sizeof(chn_attr), 0, sizeof(chn_attr));
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_CORNER_RECT;
chn_attr.attr.corner_rect_chn.corner_rect.rect.x = config->rgn_x;
chn_attr.attr.corner_rect_chn.corner_rect.rect.y = config->rgn_y;
chn_attr.attr.corner_rect_chn.corner_rect.rect.width = config->rgn_width;
chn_attr.attr.corner_rect_chn.corner_rect.rect.height = config->rgn_height;
chn_attr.attr.corner_rect_chn.corner_rect.thick = config->rgn_thick;
chn_attr.attr.corner_rect_chn.corner_rect.hor_len = config->rgn_hor_len;
chn_attr.attr.corner_rect_chn.corner_rect.ver_len = config->rgn_ver_len;
chn_attr.attr.corner_rect_chn.corner_rect_attr.corner_rect_type = sdk_type;
chn_attr.attr.corner_rect_chn.corner_rect_attr.color = config->rgn_color_rgb888;
chn_attr.attr.corner_rect_chn.layer = config->chn_layer;
ret = ss_mpi_rgn_attach_to_chn(config->rgn_handle, &vpss_chn, &chn_attr);
if (ret != TD_SUCCESS) {
out_ctx->last_sdk_error = ret;
(td_void)ss_mpi_rgn_destroy(config->rgn_handle);
return SAMPLE_VIO_OSD_CORNER_ERR_ATTACH;
}
out_ctx->rgn_handle = config->rgn_handle;
out_ctx->vpss_grp = config->vpss_grp;
out_ctx->vpss_chn = config->vpss_chn;
out_ctx->last_sdk_error = TD_SUCCESS;
out_ctx->started = 1U;
return SAMPLE_VIO_OSD_CORNER_OK;
}
int32_t sample_vio_osd_corner_stop(sample_vio_osd_corner_ctx_t *ctx)
{
ot_mpp_chn vpss_chn;
td_s32 ret;
int32_t result = SAMPLE_VIO_OSD_CORNER_OK;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
if (ctx->started == 0U) {
return SAMPLE_VIO_OSD_CORNER_OK;
}
vpss_chn.mod_id = OT_ID_VPSS;
vpss_chn.dev_id = ctx->vpss_grp;
vpss_chn.chn_id = ctx->vpss_chn;
ret = ss_mpi_rgn_detach_from_chn(ctx->rgn_handle, &vpss_chn);
if (ret != TD_SUCCESS) {
ctx->last_sdk_error = ret;
result = SAMPLE_VIO_OSD_CORNER_ERR_DETACH;
}
ret = ss_mpi_rgn_destroy(ctx->rgn_handle);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_CORNER_OK) {
ctx->last_sdk_error = ret;
result = SAMPLE_VIO_OSD_CORNER_ERR_DESTROY;
}
ctx->started = 0U;
if (result == SAMPLE_VIO_OSD_CORNER_OK) {
ctx->last_sdk_error = TD_SUCCESS;
}
return result;
}
代码讲解
#define SAMPLE_VIO_OSD_CORNER_HANDLE_MIN 140U
#define SAMPLE_VIO_OSD_CORNER_HANDLE_MAX 151U
#define SAMPLE_VIO_OSD_CORNER_LAYER_MAX 11U
#define SAMPLE_VIO_OSD_CORNER_THICK_MIN 2U
#define SAMPLE_VIO_OSD_CORNER_THICK_MAX 16U
#define SAMPLE_VIO_OSD_CORNER_ARM_MIN 2U
#define SAMPLE_VIO_OSD_CORNER_ARM_MAX 8192U
#define SAMPLE_VIO_OSD_CORNER_RGB_MAX 0x00FFFFFFU
这些宏定义是对传入参数的限制,这个限制的信源是官方SDK给的样例。
static td_bool sample_vio_osd_corner_config_valid(
const sample_vio_osd_corner_config_t *config)
{
if (config == TD_NULL) {
return TD_FALSE;
}
if (config->rgn_handle < SAMPLE_VIO_OSD_CORNER_HANDLE_MIN ||
config->rgn_handle > SAMPLE_VIO_OSD_CORNER_HANDLE_MAX) {
return TD_FALSE;
}
if (config->vpss_grp < 0 ||
config->vpss_chn < 0 ||
config->rgn_x < 0 ||
config->rgn_y < 0) {
return TD_FALSE;
}
if (config->rgn_width == 0 ||
config->rgn_height == 0 ||
config->rgn_thick < SAMPLE_VIO_OSD_CORNER_THICK_MIN ||
config->rgn_thick > SAMPLE_VIO_OSD_CORNER_THICK_MAX ||
(config->rgn_thick & 1U) != 0U) {
return TD_FALSE;
}
if (config->rgn_hor_len < SAMPLE_VIO_OSD_CORNER_ARM_MIN ||
config->rgn_hor_len > SAMPLE_VIO_OSD_CORNER_ARM_MAX ||
config->rgn_ver_len < SAMPLE_VIO_OSD_CORNER_ARM_MIN ||
config->rgn_ver_len > SAMPLE_VIO_OSD_CORNER_ARM_MAX) {
return TD_FALSE;
}
if (config->chn_layer > SAMPLE_VIO_OSD_CORNER_LAYER_MAX ||
config->rgn_color_rgb888 > SAMPLE_VIO_OSD_CORNER_RGB_MAX) {
return TD_FALSE;
}
if (config->rgn_type != SAMPLE_VIO_OSD_CORNER_TYPE_CORNER &&
config->rgn_type != SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE) {
return TD_FALSE;
}
return TD_TRUE;
}
这个函数的作用是对输出参数的合法性进行判断,确保使用合法的参数。
static td_s32 sample_vio_osd_corner_get_sdk_type(
sample_vio_osd_corner_type_t type,
ot_corner_rect_type *sdk_type)
{
switch (type) {
case SAMPLE_VIO_OSD_CORNER_TYPE_CORNER:
*sdk_type = OT_CORNER_RECT_TYPE_CORNER;
return TD_SUCCESS;
case SAMPLE_VIO_OSD_CORNER_TYPE_FULL_LINE:
*sdk_type = OT_CORNER_RECT_TYPE_FULL_LINE;
return TD_SUCCESS;
default:
return TD_FAILURE;
}
}
这个函数将角框类型从自定义的枚举类型转换为了SDK内部要求的类型。
int32_t sample_vio_osd_corner_start(
const sample_vio_osd_corner_config_t *config,
sample_vio_osd_corner_ctx_t *out_ctx)
{
ot_rgn_attr region;
ot_rgn_chn_attr chn_attr;
ot_mpp_chn vpss_chn;
ot_corner_rect_type sdk_type;
td_s32 ret;
if (out_ctx == TD_NULL) {
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
if (out_ctx->started != 0U) {
return SAMPLE_VIO_OSD_CORNER_ERR_STATE;
}
(td_void)memset_s(out_ctx, sizeof(*out_ctx), 0, sizeof(*out_ctx));
if (sample_vio_osd_corner_config_valid(config) != TD_TRUE ||
sample_vio_osd_corner_get_sdk_type(config->rgn_type, &sdk_type) != TD_SUCCESS) {
out_ctx->last_sdk_error = SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
(td_void)memset_s(®ion, sizeof(region), 0, sizeof(region));
region.type = OT_RGN_CORNER_RECT;
ret = ss_mpi_rgn_create(config->rgn_handle, ®ion);
if (ret != TD_SUCCESS) {
out_ctx->last_sdk_error = ret;
return SAMPLE_VIO_OSD_CORNER_ERR_CREATE;
}
vpss_chn.mod_id = OT_ID_VPSS;
vpss_chn.dev_id = config->vpss_grp;
vpss_chn.chn_id = config->vpss_chn;
(td_void)memset_s(&chn_attr, sizeof(chn_attr), 0, sizeof(chn_attr));
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_CORNER_RECT;
chn_attr.attr.corner_rect_chn.corner_rect.rect.x = config->rgn_x;
chn_attr.attr.corner_rect_chn.corner_rect.rect.y = config->rgn_y;
chn_attr.attr.corner_rect_chn.corner_rect.rect.width = config->rgn_width;
chn_attr.attr.corner_rect_chn.corner_rect.rect.height = config->rgn_height;
chn_attr.attr.corner_rect_chn.corner_rect.thick = config->rgn_thick;
chn_attr.attr.corner_rect_chn.corner_rect.hor_len = config->rgn_hor_len;
chn_attr.attr.corner_rect_chn.corner_rect.ver_len = config->rgn_ver_len;
chn_attr.attr.corner_rect_chn.corner_rect_attr.corner_rect_type = sdk_type;
chn_attr.attr.corner_rect_chn.corner_rect_attr.color = config->rgn_color_rgb888;
chn_attr.attr.corner_rect_chn.layer = config->chn_layer;
ret = ss_mpi_rgn_attach_to_chn(config->rgn_handle, &vpss_chn, &chn_attr);
if (ret != TD_SUCCESS) {
out_ctx->last_sdk_error = ret;
(td_void)ss_mpi_rgn_destroy(config->rgn_handle);
return SAMPLE_VIO_OSD_CORNER_ERR_ATTACH;
}
out_ctx->rgn_handle = config->rgn_handle;
out_ctx->vpss_grp = config->vpss_grp;
out_ctx->vpss_chn = config->vpss_chn;
out_ctx->last_sdk_error = TD_SUCCESS;
out_ctx->started = 1U;
return SAMPLE_VIO_OSD_CORNER_OK;
}
这个函数就是启动这个模块的代码。输入的参数一个就是配置的代码,一个是保存上下文的结构体,方便停止模块和查看状态。
函数进来先检验out_ctx参数合法性,和清零操作。随后在判断config有效并将角框格式转为SDK格式。随后对region设置OSD类型,在创建出region。随后设置vpss_chn,包含类型,组和通道,在对chn_attr赋值,将config参数在这里进行写入,包含region大小,位置,线宽度,线的长和高,角框类型,线的颜色,这里必须使用RGB888,以及层级设置。接下来这个函数将region显示到了视频当中。最后记录上下文信息到out_ctx。
int32_t sample_vio_osd_corner_stop(sample_vio_osd_corner_ctx_t *ctx)
{
ot_mpp_chn vpss_chn;
td_s32 ret;
int32_t result = SAMPLE_VIO_OSD_CORNER_OK;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_CORNER_ERR_PARAM;
}
if (ctx->started == 0U) {
return SAMPLE_VIO_OSD_CORNER_OK;
}
vpss_chn.mod_id = OT_ID_VPSS;
vpss_chn.dev_id = ctx->vpss_grp;
vpss_chn.chn_id = ctx->vpss_chn;
ret = ss_mpi_rgn_detach_from_chn(ctx->rgn_handle, &vpss_chn);
if (ret != TD_SUCCESS) {
ctx->last_sdk_error = ret;
result = SAMPLE_VIO_OSD_CORNER_ERR_DETACH;
}
ret = ss_mpi_rgn_destroy(ctx->rgn_handle);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_CORNER_OK) {
ctx->last_sdk_error = ret;
result = SAMPLE_VIO_OSD_CORNER_ERR_DESTROY;
}
ctx->started = 0U;
if (result == SAMPLE_VIO_OSD_CORNER_OK) {
ctx->last_sdk_error = TD_SUCCESS;
}
return result;
}
这是这个模块的停止代码,首先是对ctx参数进行检测,随后同样设置vpss_chn,但这次用于解绑region和通道,随后在销毁region,并将ctx参数重置以便下次使用。
sample_vio_osd_text.h
#ifndef SAMPLE_VIO_OSD_TEXT_H
#define SAMPLE_VIO_OSD_TEXT_H
#include <signal.h>
#include <stdint.h>
/* 返回值 */
typedef enum {
SAMPLE_VIO_OSD_TEXT_OK = 0,
SAMPLE_VIO_OSD_TEXT_ERR_PARAM = -1,
SAMPLE_VIO_OSD_TEXT_ERR_STATE = -2,
SAMPLE_VIO_OSD_TEXT_ERR_CREATE = -3,
SAMPLE_VIO_OSD_TEXT_ERR_ATTACH = -4,
SAMPLE_VIO_OSD_TEXT_ERR_MEMORY = -5,
SAMPLE_VIO_OSD_TEXT_ERR_RENDER = -6,
SAMPLE_VIO_OSD_TEXT_ERR_THREAD = -7,
SAMPLE_VIO_OSD_TEXT_ERR_DETACH = -8,
SAMPLE_VIO_OSD_TEXT_ERR_DESTROY = -9
} sample_vio_osd_text_result_t;
/*
* 时间 OSD 输入配置。
*
* 坐标单位:像素
* Alpha 范围:0~255
* 颜色格式:ARGB1555
*/
typedef struct {
/* Region 参数 */
uint32_t rgn_handle; /* VENC Overlay handle,范围 0~7 */
uint32_t rgn_width; /* Region 宽度,必须为偶数 */
uint32_t rgn_height; /* Region 高度,必须为偶数 */
uint32_t rgn_canvas_num; /* 1:单缓冲;2:双缓冲 */
uint16_t rgn_bg_color; /* Region 背景颜色,ARGB1555 */
/* 通道参数 */
int32_t venc_dev; /* VENC 设备号 */
int32_t venc_chn; /* VENC 通道号 */
int32_t chn_pos_x; /* Region 在视频画面中的 X 坐标 */
int32_t chn_pos_y; /* Region 在视频画面中的 Y 坐标 */
uint32_t chn_fg_alpha; /* 通道前景透明度 */
uint32_t chn_bg_alpha; /* 通道背景透明度 */
uint32_t chn_layer; /* VENC Overlay layer,范围 0~7 */
/* BMP 参数 */
uint16_t bmp_fg_color; /* 文字颜色,ARGB1555 */
uint32_t bmp_text_x; /* 时间文字在 Region 内的 X 坐标 */
uint32_t bmp_text_y; /* 时间文字在 Region 内的 Y 坐标 */
uint32_t bmp_scale; /* 字体放大倍数 */
/* 刷新周期,单位毫秒 */
uint32_t refresh_ms;
/*
* 可选的应用停止标志。
* 不使用时可传 NULL;非 NULL 时,该对象必须保持有效直到 stop 完成。
*/
const volatile sig_atomic_t *app_stop_flag;
} sample_vio_osd_text_config_t;
/*
* 时间 OSD 输出上下文。
* 调用者负责分配并初始化,建议定义成 static 或先清零。
*/
typedef struct {
uint32_t rgn_handle; /* 实际使用的 Region handle */
int32_t venc_dev; /* 实际使用的 VENC 设备号 */
int32_t venc_chn; /* 实际使用的 VENC 通道号 */
/* 最近一次错误码 */
int32_t last_error;
uint8_t started;
/* 模块内部数据,调用者不要访问或修改 */
void *internal;
} sample_vio_osd_text_ctx_t;
/*
* 输入:
* config - 时间 OSD 配置,不能为 NULL
* ctx - 调用者提供的输出上下文,不能为 NULL
* 输出:
* ctx - 成功后保存 Region handle、VENC 通道和运行状态
* 返回:
* SAMPLE_VIO_OSD_TEXT_OK 或对应错误码
*/
int32_t sample_vio_osd_text_start(
const sample_vio_osd_text_config_t *config,
sample_vio_osd_text_ctx_t *ctx);
/*
* 输入:
* ctx - 由 start 成功填充的上下文
* 输出:
* ctx->started 清零
* ctx->last_error 保存停止过程中的错误
* 返回:
* SAMPLE_VIO_OSD_TEXT_OK 或对应错误码
*/
int32_t sample_vio_osd_text_stop(sample_vio_osd_text_ctx_t *ctx);
#endif /* SAMPLE_VIO_OSD_TEXT_H */
代码讲解
typedef enum {
SAMPLE_VIO_OSD_TEXT_OK = 0,
SAMPLE_VIO_OSD_TEXT_ERR_PARAM = -1,
SAMPLE_VIO_OSD_TEXT_ERR_STATE = -2,
SAMPLE_VIO_OSD_TEXT_ERR_CREATE = -3,
SAMPLE_VIO_OSD_TEXT_ERR_ATTACH = -4,
SAMPLE_VIO_OSD_TEXT_ERR_MEMORY = -5,
SAMPLE_VIO_OSD_TEXT_ERR_RENDER = -6,
SAMPLE_VIO_OSD_TEXT_ERR_THREAD = -7,
SAMPLE_VIO_OSD_TEXT_ERR_DETACH = -8,
SAMPLE_VIO_OSD_TEXT_ERR_DESTROY = -9
} sample_vio_osd_text_result_t;
|
返回值 |
|
SAMPLE_VIO_OSD_TEXT_OK |
|
SAMPLE_VIO_OSD_TEXT_ERR_PARAM |
|
SAMPLE_VIO_OSD_TEXT_ERR_STATE |
|
SAMPLE_VIO_OSD_TEXT_ERR_CREATE |
|
SAMPLE_VIO_OSD_TEXT_ERR_ATTACH |
|
SAMPLE_VIO_OSD_TEXT_ERR_MEMORY |
|
SAMPLE_VIO_OSD_TEXT_ERR_RENDER |
|
SAMPLE_VIO_OSD_TEXT_ERR_THREAD |
|
SAMPLE_VIO_OSD_TEXT_ERR_DETACH |
|
SAMPLE_VIO_OSD_TEXT_ERR_DESTROY |
typedef struct {
/* Region 参数 */
uint32_t rgn_handle; /* VENC Overlay handle,范围 0~7 */
uint32_t rgn_width; /* Region 宽度,必须为偶数 */
uint32_t rgn_height; /* Region 高度,必须为偶数 */
uint32_t rgn_canvas_num; /* 1:单缓冲;2:双缓冲 */
uint16_t rgn_bg_color; /* Region 背景颜色,ARGB1555 */
/* 通道参数 */
int32_t venc_dev; /* VENC 设备号 */
int32_t venc_chn; /* VENC 通道号 */
int32_t chn_pos_x; /* Region 在视频画面中的 X 坐标 */
int32_t chn_pos_y; /* Region 在视频画面中的 Y 坐标 */
uint32_t chn_fg_alpha; /* 通道前景透明度 */
uint32_t chn_bg_alpha; /* 通道背景透明度 */
uint32_t chn_layer; /* VENC Overlay layer,范围 0~7 */
/* BMP 参数 */
uint16_t bmp_fg_color; /* 文字颜色,ARGB1555 */
uint32_t bmp_text_x; /* 时间文字在 Region 内的 X 坐标 */
uint32_t bmp_text_y; /* 时间文字在 Region 内的 Y 坐标 */
uint32_t bmp_scale; /* 字体放大倍数 */
/* 刷新周期,单位毫秒 */
uint32_t refresh_ms;
/*
* 可选的应用停止标志。
* 不使用时可传 NULL;非 NULL 时,该对象必须保持有效直到 stop 完成。
*/
const volatile sig_atomic_t *app_stop_flag;
} sample_vio_osd_text_config_t;
包含region参数,通道参数和BMP参数,以及刷新周期,和应用停止标志
typedef struct {
uint32_t rgn_handle; /* 实际使用的 Region handle */
int32_t venc_dev; /* 实际使用的 VENC 设备号 */
int32_t venc_chn; /* 实际使用的 VENC 通道号 */
/* 最近一次错误码 */
int32_t last_error;
uint8_t started;
/* 模块内部数据,调用者不要访问或修改 */
void *internal;
} sample_vio_osd_text_ctx_t;
用于存储上下文信息,以及停止时需要用到的参数。
int32_t sample_vio_osd_text_start(
const sample_vio_osd_text_config_t *config,
sample_vio_osd_text_ctx_t *ctx);
int32_t sample_vio_osd_text_stop(sample_vio_osd_text_ctx_t *ctx);
表示开启和关闭这个模块
sample_vio_osd_text.c
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include "sample_comm.h"
#include "securec.h"
#include "sample_vio_osd_text.h"
#define SAMPLE_VIO_OSD_TEXT_HANDLE_MAX 7U
#define SAMPLE_VIO_OSD_TEXT_ALPHA_MAX 255U
#define SAMPLE_VIO_OSD_TEXT_LAYER_MAX 7U
#define SAMPLE_VIO_OSD_TEXT_SCALE_MAX 8U
#define SAMPLE_VIO_OSD_TEXT_FONT_WIDTH 5U
#define SAMPLE_VIO_OSD_TEXT_FONT_HEIGHT 7U
#define SAMPLE_VIO_OSD_TEXT_FONT_STEP 6U
typedef struct {
pthread_t thread;
td_u8 *bmp_data;
size_t bmp_size;
sample_vio_osd_text_config_t config;
ot_mpp_chn venc_chn;
volatile td_bool running;
td_s32 last_sdk_error;
} sample_vio_osd_text_runtime_t;
static const td_u8 *sample_vio_osd_font(td_char ch)
{
static const td_u8 font_space[7] =
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const td_u8 font_colon[7] =
{0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00};
static const td_u8 font_dash[7] =
{0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00};
static const td_u8 font_digits[10][7] = {
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, /* 0 */
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* 1 */
{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}, /* 2 */
{0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E}, /* 3 */
{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, /* 4 */
{0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E}, /* 5 */
{0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E}, /* 6 */
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, /* 7 */
{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, /* 8 */
{0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E} /* 9 */
};
if (ch >= '0' && ch <= '9') {
return font_digits[ch - '0'];
}
switch (ch) {
case '-':
return font_dash;
case ':':
return font_colon;
default:
return font_space;
}
}
static td_bool sample_vio_osd_text_config_valid(
const sample_vio_osd_text_config_t *config)
{
if (config == TD_NULL) {
return TD_FALSE;
}
if (config->rgn_handle > SAMPLE_VIO_OSD_TEXT_HANDLE_MAX ||
config->rgn_width == 0 ||
config->rgn_height == 0 ||
(config->rgn_width & 1U) != 0U ||
(config->rgn_height & 1U) != 0U) {
return TD_FALSE;
}
if (config->rgn_canvas_num != 1U && config->rgn_canvas_num != 2U) {
return TD_FALSE;
}
if (config->venc_dev < 0 ||
config->venc_chn < 0 ||
config->chn_pos_x < 0 ||
config->chn_pos_y < 0) {
return TD_FALSE;
}
if (config->chn_fg_alpha > SAMPLE_VIO_OSD_TEXT_ALPHA_MAX ||
config->chn_bg_alpha > SAMPLE_VIO_OSD_TEXT_ALPHA_MAX ||
config->chn_layer > SAMPLE_VIO_OSD_TEXT_LAYER_MAX) {
return TD_FALSE;
}
if (config->bmp_text_x >= config->rgn_width ||
config->bmp_text_y >= config->rgn_height ||
config->bmp_scale == 0 ||
config->bmp_scale > SAMPLE_VIO_OSD_TEXT_SCALE_MAX ||
config->refresh_ms == 0) {
return TD_FALSE;
}
return TD_TRUE;
}
static void sample_vio_osd_draw_text(
sample_vio_osd_text_runtime_t *runtime,
const char *text)
{
const sample_vio_osd_text_config_t *config = &runtime->config;
uint32_t offset_x = config->bmp_text_x;
const char *cursor = text;
while (*cursor != '\0') {
const td_u8 *font = sample_vio_osd_font(*cursor);
uint32_t row;
uint32_t col;
for (row = 0; row < SAMPLE_VIO_OSD_TEXT_FONT_HEIGHT; row++) {
for (col = 0; col < SAMPLE_VIO_OSD_TEXT_FONT_WIDTH; col++) {
uint32_t py;
uint32_t px;
if ((font[row] &
(1U << (SAMPLE_VIO_OSD_TEXT_FONT_WIDTH - 1U - col))) == 0U) {
continue;
}
for (py = 0; py < config->bmp_scale; py++) {
for (px = 0; px < config->bmp_scale; px++) {
uint32_t draw_x = offset_x + col * config->bmp_scale + px;
uint32_t draw_y = config->bmp_text_y +
row * config->bmp_scale + py;
if (draw_x < config->rgn_width &&
draw_y < config->rgn_height) {
((td_u16 *)runtime->bmp_data)
[draw_y * config->rgn_width + draw_x] =
config->bmp_fg_color;
}
}
}
}
}
offset_x += SAMPLE_VIO_OSD_TEXT_FONT_STEP * config->bmp_scale;
cursor++;
if (offset_x + SAMPLE_VIO_OSD_TEXT_FONT_WIDTH * config->bmp_scale >=
config->rgn_width) {
break;
}
}
}
static td_s32 sample_vio_osd_render(
sample_vio_osd_text_runtime_t *runtime)
{
const sample_vio_osd_text_config_t *config = &runtime->config;
ot_bmp bmp;
time_t now;
struct tm local_time;
char text[64];
size_t i;
td_s32 ret;
for (i = 0; i < runtime->bmp_size / sizeof(td_u16); i++) {
((td_u16 *)runtime->bmp_data)[i] = config->rgn_bg_color;
}
now = time(TD_NULL);
(td_void)localtime_r(&now, &local_time);
ret = snprintf_s(
text,
sizeof(text),
sizeof(text) - 1U,
"%04d-%02d-%02d %02d:%02d:%02d",
local_time.tm_year + 1900,
local_time.tm_mon + 1,
local_time.tm_mday,
local_time.tm_hour,
local_time.tm_min,
local_time.tm_sec);
if (ret < 0) {
return TD_FAILURE;
}
sample_vio_osd_draw_text(runtime, text);
(td_void)memset_s(&bmp, sizeof(bmp), 0, sizeof(bmp));
bmp.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
bmp.width = config->rgn_width;
bmp.height = config->rgn_height;
bmp.data = runtime->bmp_data;
return ss_mpi_rgn_set_bmp(config->rgn_handle, &bmp);
}
static void sample_vio_osd_sleep_ms(uint32_t milliseconds)
{
struct timespec delay;
delay.tv_sec = milliseconds / 1000U;
delay.tv_nsec = (long)(milliseconds % 1000U) * 1000000L;
(td_void)nanosleep(&delay, TD_NULL);
}
static td_void *sample_vio_osd_thread(td_void *arg)
{
sample_vio_osd_text_runtime_t *runtime =
(sample_vio_osd_text_runtime_t *)arg;
const sample_vio_osd_text_config_t *config = &runtime->config;
while (runtime->running == TD_TRUE &&
(config->app_stop_flag == TD_NULL ||
*config->app_stop_flag == 0)) {
if (sample_vio_osd_render(runtime) != TD_SUCCESS) {
runtime->last_sdk_error = TD_FAILURE;
}
sample_vio_osd_sleep_ms(config->refresh_ms);
}
return TD_NULL;
}
int32_t sample_vio_osd_text_start(
const sample_vio_osd_text_config_t *config,
sample_vio_osd_text_ctx_t *ctx)
{
sample_vio_osd_text_runtime_t *runtime;
ot_rgn_attr region;
ot_rgn_chn_attr chn_attr;
td_bool region_created = TD_FALSE;
td_bool channel_attached = TD_FALSE;
int32_t result;
td_s32 ret;
int thread_ret;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
if (ctx->started != 0U) {
return SAMPLE_VIO_OSD_TEXT_ERR_STATE;
}
(td_void)memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx));
if (sample_vio_osd_text_config_valid(config) != TD_TRUE) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
runtime = (sample_vio_osd_text_runtime_t *)malloc(sizeof(*runtime));
if (runtime == TD_NULL) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
return SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
}
(td_void)memset_s(runtime, sizeof(*runtime), 0, sizeof(*runtime));
runtime->config = *config;
runtime->venc_chn.mod_id = OT_ID_VENC;
runtime->venc_chn.dev_id = config->venc_dev;
runtime->venc_chn.chn_id = config->venc_chn;
(td_void)memset_s(®ion, sizeof(region), 0, sizeof(region));
region.type = OT_RGN_OVERLAY;
region.attr.overlay.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
region.attr.overlay.size.width = config->rgn_width;
region.attr.overlay.size.height = config->rgn_height;
region.attr.overlay.bg_color = config->rgn_bg_color;
region.attr.overlay.canvas_num = config->rgn_canvas_num;
ret = ss_mpi_rgn_create(config->rgn_handle, ®ion);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_CREATE;
goto FAIL;
}
region_created = TD_TRUE;
runtime->bmp_size =
(size_t)config->rgn_width * config->rgn_height * sizeof(td_u16);
runtime->bmp_data = (td_u8 *)malloc(runtime->bmp_size);
if (runtime->bmp_data == TD_NULL) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
result = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
goto FAIL;
}
(td_void)memset_s(&chn_attr, sizeof(chn_attr), 0, sizeof(chn_attr));
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_OVERLAY;
chn_attr.attr.overlay_chn.point.x = config->chn_pos_x;
chn_attr.attr.overlay_chn.point.y = config->chn_pos_y;
chn_attr.attr.overlay_chn.fg_alpha = config->chn_fg_alpha;
chn_attr.attr.overlay_chn.bg_alpha = config->chn_bg_alpha;
chn_attr.attr.overlay_chn.layer = config->chn_layer;
chn_attr.attr.overlay_chn.dst = OT_RGN_ATTACH_JPEG_MAIN;
ret = ss_mpi_rgn_attach_to_chn(
config->rgn_handle, &runtime->venc_chn, &chn_attr);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_ATTACH;
goto FAIL;
}
channel_attached = TD_TRUE;
ret = sample_vio_osd_render(runtime);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_RENDER;
goto FAIL;
}
runtime->running = TD_TRUE;
thread_ret = pthread_create(
&runtime->thread, TD_NULL, sample_vio_osd_thread, runtime);
if (thread_ret != 0) {
runtime->running = TD_FALSE;
ctx->last_error = thread_ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_THREAD;
goto FAIL;
}
ctx->rgn_handle = config->rgn_handle;
ctx->venc_dev = config->venc_dev;
ctx->venc_chn = config->venc_chn;
ctx->last_error = SAMPLE_VIO_OSD_TEXT_OK;
ctx->started = 1U;
ctx->internal = runtime;
return SAMPLE_VIO_OSD_TEXT_OK;
FAIL:
if (channel_attached == TD_TRUE) {
(td_void)ss_mpi_rgn_detach_from_chn(
config->rgn_handle, &runtime->venc_chn);
}
if (region_created == TD_TRUE) {
(td_void)ss_mpi_rgn_destroy(config->rgn_handle);
}
free(runtime->bmp_data);
free(runtime);
return result;
}
int32_t sample_vio_osd_text_stop(sample_vio_osd_text_ctx_t *ctx)
{
sample_vio_osd_text_runtime_t *runtime;
td_s32 ret;
int32_t result = SAMPLE_VIO_OSD_TEXT_OK;
int thread_ret;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
if (ctx->started == 0U) {
return SAMPLE_VIO_OSD_TEXT_OK;
}
runtime = (sample_vio_osd_text_runtime_t *)ctx->internal;
runtime->running = TD_FALSE;
thread_ret = pthread_join(runtime->thread, TD_NULL);
if (thread_ret != 0) {
ctx->last_error = thread_ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_THREAD;
}
ret = ss_mpi_rgn_detach_from_chn(
ctx->rgn_handle, &runtime->venc_chn);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_DETACH;
}
ret = ss_mpi_rgn_destroy(ctx->rgn_handle);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_DESTROY;
}
free(runtime->bmp_data);
free(runtime);
ctx->rgn_handle = 0U;
ctx->started = 0U;
ctx->internal = TD_NULL;
if (result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_OK;
}
return result;
}
代码讲解
#define SAMPLE_VIO_OSD_TEXT_HANDLE_MAX 7U
#define SAMPLE_VIO_OSD_TEXT_ALPHA_MAX 255U
#define SAMPLE_VIO_OSD_TEXT_LAYER_MAX 7U
#define SAMPLE_VIO_OSD_TEXT_SCALE_MAX 8U
#define SAMPLE_VIO_OSD_TEXT_FONT_WIDTH 5U
#define SAMPLE_VIO_OSD_TEXT_FONT_HEIGHT 7U
#define SAMPLE_VIO_OSD_TEXT_FONT_STEP 6U
作为参数的要求,用于校验和使用
typedef struct {
pthread_t thread;
td_u8 *bmp_data;
size_t bmp_size;
sample_vio_osd_text_config_t config;
ot_mpp_chn venc_chn;
volatile td_bool running;
td_s32 last_sdk_error;
} sample_vio_osd_text_runtime_t;
|
字段 |
|
thread |
|
bmp_data |
|
bmp_size |
|
config |
|
venc_chn |
|
running |
|
last_sdk_error |
这个时这个模块内部存储上下文的地方
static const td_u8 *sample_vio_osd_font(td_char ch)
{
static const td_u8 font_space[7] =
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const td_u8 font_colon[7] =
{0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00};
static const td_u8 font_dash[7] =
{0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00};
static const td_u8 font_digits[10][7] = {
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, /* 0 */
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* 1 */
{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}, /* 2 */
{0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E}, /* 3 */
{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, /* 4 */
{0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E}, /* 5 */
{0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E}, /* 6 */
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, /* 7 */
{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, /* 8 */
{0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E} /* 9 */
};
if (ch >= '0' && ch <= '9') {
return font_digits[ch - '0'];
}
switch (ch) {
case '-':
return font_dash;
case ':':
return font_colon;
default:
return font_space;
}
}
这个函数时对时间进行转换,将时间抓换为像素的样式,但此时还不是ARGB1555类型。
static td_bool sample_vio_osd_text_config_valid(
const sample_vio_osd_text_config_t *config)
{
if (config == TD_NULL) {
return TD_FALSE;
}
if (config->rgn_handle > SAMPLE_VIO_OSD_TEXT_HANDLE_MAX ||
config->rgn_width == 0 ||
config->rgn_height == 0 ||
(config->rgn_width & 1U) != 0U ||
(config->rgn_height & 1U) != 0U) {
return TD_FALSE;
}
if (config->rgn_canvas_num != 1U && config->rgn_canvas_num != 2U) {
return TD_FALSE;
}
if (config->venc_dev < 0 ||
config->venc_chn < 0 ||
config->chn_pos_x < 0 ||
config->chn_pos_y < 0) {
return TD_FALSE;
}
if (config->chn_fg_alpha > SAMPLE_VIO_OSD_TEXT_ALPHA_MAX ||
config->chn_bg_alpha > SAMPLE_VIO_OSD_TEXT_ALPHA_MAX ||
config->chn_layer > SAMPLE_VIO_OSD_TEXT_LAYER_MAX) {
return TD_FALSE;
}
if (config->bmp_text_x >= config->rgn_width ||
config->bmp_text_y >= config->rgn_height ||
config->bmp_scale == 0 ||
config->bmp_scale > SAMPLE_VIO_OSD_TEXT_SCALE_MAX ||
config->refresh_ms == 0) {
return TD_FALSE;
}
return TD_TRUE;
}
检验参数合法性代码
static void sample_vio_osd_draw_text(
sample_vio_osd_text_runtime_t *runtime,
const char *text)
{
const sample_vio_osd_text_config_t *config = &runtime->config;
uint32_t offset_x = config->bmp_text_x;
const char *cursor = text;
while (*cursor != '\0') {
const td_u8 *font = sample_vio_osd_font(*cursor);
uint32_t row;
uint32_t col;
for (row = 0; row < SAMPLE_VIO_OSD_TEXT_FONT_HEIGHT; row++) {
for (col = 0; col < SAMPLE_VIO_OSD_TEXT_FONT_WIDTH; col++) {
uint32_t py;
uint32_t px;
if ((font[row] &
(1U << (SAMPLE_VIO_OSD_TEXT_FONT_WIDTH - 1U - col))) == 0U) {
continue;
}
for (py = 0; py < config->bmp_scale; py++) {
for (px = 0; px < config->bmp_scale; px++) {
uint32_t draw_x = offset_x + col * config->bmp_scale + px;
uint32_t draw_y = config->bmp_text_y +
row * config->bmp_scale + py;
if (draw_x < config->rgn_width &&
draw_y < config->rgn_height) {
((td_u16 *)runtime->bmp_data)
[draw_y * config->rgn_width + draw_x] =
config->bmp_fg_color;
}
}
}
}
}
offset_x += SAMPLE_VIO_OSD_TEXT_FONT_STEP * config->bmp_scale;
cursor++;
if (offset_x + SAMPLE_VIO_OSD_TEXT_FONT_WIDTH * config->bmp_scale >=
config->rgn_width) {
break;
}
}
}
这个代码是将字符串转换为ARGB1555格式,并且写入缓存文件的代码,是这里的核心代码。传入的参数一个是保存上下文的runtime,一个是文本text,这个文本激素要输出的样式。函数进来首先config得到配置参数,offset_x得到第一个字符在bmp内的横坐标,cursor是一个指针,指向要输出的文本。随后进入while循环,先将这个字符转化为对应的像素点格式,随后两个下标可以表明这个二维数组。在进入两层for循环就是要将对应像素点转换为ARGB1555格式。px和py是一个像素点放大倍数的x/y坐标,下来if判断当前像素点用不用处理,对于0直接跳过,对于1进行两层循环放大像素点随后将此像素点写入缓存,循环后,x坐标往后跳一个字符对应的大小,最后加了一个边界处理。
static td_s32 sample_vio_osd_render(
sample_vio_osd_text_runtime_t *runtime)
{
const sample_vio_osd_text_config_t *config = &runtime->config;
ot_bmp bmp;
time_t now;
struct tm local_time;
char text[64];
size_t i;
td_s32 ret;
for (i = 0; i < runtime->bmp_size / sizeof(td_u16); i++) {
((td_u16 *)runtime->bmp_data)[i] = config->rgn_bg_color;
}
now = time(TD_NULL);
(td_void)localtime_r(&now, &local_time);
ret = snprintf_s(
text,
sizeof(text),
sizeof(text) - 1U,
"%04d-%02d-%02d %02d:%02d:%02d",
local_time.tm_year + 1900,
local_time.tm_mon + 1,
local_time.tm_mday,
local_time.tm_hour,
local_time.tm_min,
local_time.tm_sec);
if (ret < 0) {
return TD_FAILURE;
}
sample_vio_osd_draw_text(runtime, text);
(td_void)memset_s(&bmp, sizeof(bmp), 0, sizeof(bmp));
bmp.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
bmp.width = config->rgn_width;
bmp.height = config->rgn_height;
bmp.data = runtime->bmp_data;
return ss_mpi_rgn_set_bmp(config->rgn_handle, &bmp);
}
这个函数作用是绘制bmp,首先将bmp的颜色全部设置为背景色,然后转化为当前时间,在转化为字符串,然后通过调用sample_vio_osd_draw_text,把字符串转化为ARGB1555格式并写入缓存,在设置格式宽高数据等,将bmp绘制到region上。
static void sample_vio_osd_sleep_ms(uint32_t milliseconds)
{
struct timespec delay;
delay.tv_sec = milliseconds / 1000U;
delay.tv_nsec = (long)(milliseconds % 1000U) * 1000000L;
(td_void)nanosleep(&delay, TD_NULL);
}
这时一个睡眠函数,我们实现每次睡眠一秒
static td_void *sample_vio_osd_thread(td_void *arg)
{
sample_vio_osd_text_runtime_t *runtime =
(sample_vio_osd_text_runtime_t *)arg;
const sample_vio_osd_text_config_t *config = &runtime->config;
while (runtime->running == TD_TRUE &&
(config->app_stop_flag == TD_NULL ||
*config->app_stop_flag == 0)) {
if (sample_vio_osd_render(runtime) != TD_SUCCESS) {
runtime->last_sdk_error = TD_FAILURE;
}
sample_vio_osd_sleep_ms(config->refresh_ms);
}
return TD_NULL;
}
这个函数是一个周期线程,每隔 refresh_ms 调用一次 render 更新时间 OSD,直到 runtime->running 被清零或外部停止标志变为非 0。
int32_t sample_vio_osd_text_start(
const sample_vio_osd_text_config_t *config,
sample_vio_osd_text_ctx_t *ctx)
{
sample_vio_osd_text_runtime_t *runtime;
ot_rgn_attr region;
ot_rgn_chn_attr chn_attr;
td_bool region_created = TD_FALSE;
td_bool channel_attached = TD_FALSE;
int32_t result;
td_s32 ret;
int thread_ret;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
if (ctx->started != 0U) {
return SAMPLE_VIO_OSD_TEXT_ERR_STATE;
}
(td_void)memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx));
if (sample_vio_osd_text_config_valid(config) != TD_TRUE) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
runtime = (sample_vio_osd_text_runtime_t *)malloc(sizeof(*runtime));
if (runtime == TD_NULL) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
return SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
}
(td_void)memset_s(runtime, sizeof(*runtime), 0, sizeof(*runtime));
runtime->config = *config;
runtime->venc_chn.mod_id = OT_ID_VENC;
runtime->venc_chn.dev_id = config->venc_dev;
runtime->venc_chn.chn_id = config->venc_chn;
(td_void)memset_s(®ion, sizeof(region), 0, sizeof(region));
region.type = OT_RGN_OVERLAY;
region.attr.overlay.pixel_format = OT_PIXEL_FORMAT_ARGB_1555;
region.attr.overlay.size.width = config->rgn_width;
region.attr.overlay.size.height = config->rgn_height;
region.attr.overlay.bg_color = config->rgn_bg_color;
region.attr.overlay.canvas_num = config->rgn_canvas_num;
ret = ss_mpi_rgn_create(config->rgn_handle, ®ion);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_CREATE;
goto FAIL;
}
region_created = TD_TRUE;
runtime->bmp_size =
(size_t)config->rgn_width * config->rgn_height * sizeof(td_u16);
runtime->bmp_data = (td_u8 *)malloc(runtime->bmp_size);
if (runtime->bmp_data == TD_NULL) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
result = SAMPLE_VIO_OSD_TEXT_ERR_MEMORY;
goto FAIL;
}
(td_void)memset_s(&chn_attr, sizeof(chn_attr), 0, sizeof(chn_attr));
chn_attr.is_show = TD_TRUE;
chn_attr.type = OT_RGN_OVERLAY;
chn_attr.attr.overlay_chn.point.x = config->chn_pos_x;
chn_attr.attr.overlay_chn.point.y = config->chn_pos_y;
chn_attr.attr.overlay_chn.fg_alpha = config->chn_fg_alpha;
chn_attr.attr.overlay_chn.bg_alpha = config->chn_bg_alpha;
chn_attr.attr.overlay_chn.layer = config->chn_layer;
chn_attr.attr.overlay_chn.dst = OT_RGN_ATTACH_JPEG_MAIN;
ret = ss_mpi_rgn_attach_to_chn(
config->rgn_handle, &runtime->venc_chn, &chn_attr);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_ATTACH;
goto FAIL;
}
channel_attached = TD_TRUE;
ret = sample_vio_osd_render(runtime);
if (ret != TD_SUCCESS) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_RENDER;
goto FAIL;
}
runtime->running = TD_TRUE;
thread_ret = pthread_create(
&runtime->thread, TD_NULL, sample_vio_osd_thread, runtime);
if (thread_ret != 0) {
runtime->running = TD_FALSE;
ctx->last_error = thread_ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_THREAD;
goto FAIL;
}
ctx->rgn_handle = config->rgn_handle;
ctx->venc_dev = config->venc_dev;
ctx->venc_chn = config->venc_chn;
ctx->last_error = SAMPLE_VIO_OSD_TEXT_OK;
ctx->started = 1U;
ctx->internal = runtime;
return SAMPLE_VIO_OSD_TEXT_OK;
FAIL:
if (channel_attached == TD_TRUE) {
(td_void)ss_mpi_rgn_detach_from_chn(
config->rgn_handle, &runtime->venc_chn);
}
if (region_created == TD_TRUE) {
(td_void)ss_mpi_rgn_destroy(config->rgn_handle);
}
free(runtime->bmp_data);
free(runtime);
return result;
}
这个函数是开始的函数,首先还是先检测合法性,随后创建临时上下文变量,保存相关参数,在创建region,和上面例子类似。然后申请出需要缓存的大小,创建chn_attr,设置相关参数,在进行绑定,在第一次调用sample_vio_osd_render,然后创建线程,周期刷新参数。最后还是保存上下文信息,如果出现失败要逐一进行释放资源。
int32_t sample_vio_osd_text_stop(sample_vio_osd_text_ctx_t *ctx)
{
sample_vio_osd_text_runtime_t *runtime;
td_s32 ret;
int32_t result = SAMPLE_VIO_OSD_TEXT_OK;
int thread_ret;
if (ctx == TD_NULL) {
return SAMPLE_VIO_OSD_TEXT_ERR_PARAM;
}
if (ctx->started == 0U) {
return SAMPLE_VIO_OSD_TEXT_OK;
}
runtime = (sample_vio_osd_text_runtime_t *)ctx->internal;
runtime->running = TD_FALSE;
thread_ret = pthread_join(runtime->thread, TD_NULL);
if (thread_ret != 0) {
ctx->last_error = thread_ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_THREAD;
}
ret = ss_mpi_rgn_detach_from_chn(
ctx->rgn_handle, &runtime->venc_chn);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_DETACH;
}
ret = ss_mpi_rgn_destroy(ctx->rgn_handle);
if (ret != TD_SUCCESS && result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = ret;
result = SAMPLE_VIO_OSD_TEXT_ERR_DESTROY;
}
free(runtime->bmp_data);
free(runtime);
ctx->rgn_handle = 0U;
ctx->started = 0U;
ctx->internal = TD_NULL;
if (result == SAMPLE_VIO_OSD_TEXT_OK) {
ctx->last_error = SAMPLE_VIO_OSD_TEXT_OK;
}
return result;
}
这是停止函数,先停止并等待刷新线程,再从 VENC 解绑、销毁 Region, 最后释放 bitmap 和 runtime,并把 ctx 恢复为未启动状态。
更多推荐

所有评论(0)