Doorbell
1. 简介
本工程是UVC/DVP摄像头门锁的一个demo,支持端(BK7258设备)到端(手机APP端)的演示。
1.1 规格
- 硬件配置:
核心板,BK7258_QFN88_9X9_V3.2
显示转接板,BK7258_LCD_Interface_V3.0
麦克小板,BK_Module_Microphone_V1.1
喇叭小板,BK_Module_Speaker_V1.1
PSRAM 8M/16M
- 支持,UVC
参考外设,864 * 480 分辨率的UVC
参考外设,864 * 480 分辨率的DVP(gc2145)
支持,UAC
支持,TCP局域网图传
支持,UDP局域网图传
支持,尚云,P2P图传
- 支持,LCD RGB/MCU I8080显示
参考外设,ST7701SN,480 * 854 RGB LCD
RGB565/RGB888
- 支持,硬件/软件旋转
0°,90°,180°,270°
支持,板载喇叭
支持,麦克
- 支持,MJPEG硬件编解码
YUV422
- 支持,MJPEG软件解码
YUV420
支持,H264硬件编码
- 支持,OSD显示
ARGB888[PNG]
自定义字体
警告
请使用参考外设,进行demo工程的熟悉和学习。如果外设规格不一样,代码可能需要重新配置。
1.2 路径
<bk_avdk源代码路径>/projects/doorbell
2. 框架图
2.1 软件模块架构图
- 如下图所示,BK7258有多个CPU:
CP,运行WIFI/BLE,作为低功耗CPU。
AP,运行多媒体,作为多媒体高性能CPU。
Figure 1. software module architecture
UVC方案中,我们采用pipeline方式,来提高整体性能。
- UVC摄像头输出的图像可以分为两种,一种是YUV420 MJPEG,一种是YUV422 MJPEG。
软件会自动识别,并使用硬件解码器进行YUV422 MJPEG解码。而YUV420 MJPEG,则采用CPU1和CPU2进行软件解码。
硬件解码时,图像分辨率的宽需要是32的倍数,高的需要是16的倍数。
YUV像素排列分为,平面格式(planar)、打包格式(packed)、半平面格式(semi-planar)。硬件编码的数据,需要是packed格式。
- MJPEG HW Decoder,在pipeline模式中,由于H264的编码数据,需要基于MJPEG解码再编码。因此,本地显示和图传都会用到这个硬件模块。
关闭的时候,需要注意,显示和图传全部关闭的情况,才能关闭此模块。默认demo已经包含了这个逻辑。
- MJPEG SW Decoder,同一时间,不会两种解码器同时工作。
一旦图像确认是YUV420或者YUV422后,就决定了使用软件解码还是硬件解码。
- Rota HW 和Rota SW,同一时间,只会使用一种旋转模块。
Rota HW,支持RGB 565的图像输出,支持0°、90°、270°。
Rota SW,支持0°、90°、180°、270°。
如果需要使用RGB888输出,或者支持180°,满足其中一个条件,都需要切换成软件解码。
当前Rota HW和Rota SW,如何决策,由SDK软件决定。用户只需要在打开LCD时,将旋转角度和输出图像格式参数,输入给对应接口即可。
2.2 代码模块关系图
如下图所示,多媒体的接口,都定义在 media_app.h 和 aud_intf.h 中。
Figure 2. module relationship diagram
3. 配置
3.1 蓝牙与多媒体内存复用
为了进一步节约内存,默认工程中,多媒体的内存编解码内存和蓝牙的内存是复用,主要是采用以下两个宏。 如果希望并行使用两个模块,可自行关闭。关闭前请确认整体内存是否够用。
Marco
CPU
Value
CONFIG_BT_REUSE_MEDIA_MEMORY
CPU0 && CPU1
y
CONFIG_HARDWARE_ACC_SIZE
CPU0 && CPU1
0x1B000
为了解决实际使用过程中的内存复用冲突,需要在使用多媒体模块前,检查蓝牙的状态,关闭卸载蓝牙。
如果多媒体模块都已经关闭,想再次使用,需要再重新初始化蓝牙。请参考以下代码。
- CONFIG_BT_REUSE_MEDIA_MEM_SIZE:取值范围是,基于蓝牙硬件模块的需要最大内存和多媒体硬件编码需要的最大内存的两个值,取一个最大值,这个值与摄像头的分辨率相关。
一般蓝牙的硬件内存,需求比较小[实际统计,需要根据编译出来map程序来统计]。因为,一般都按照多媒体硬件的最大内存配置。
备注
CONFIG_HARDWARE_ACC_SIZE这个宏表示蓝牙占用的内存大小,当多媒体与蓝牙内存复用时,复用的长度CONFIG_HARDWARE_ACC_SIZE不是在传统的config文件中配置,而是在分区表中配置,
文件路径: ./projects/doorbell/partitions/bk7258/ram_regions.csv,简单的介绍如下,BK7258 SRAM加起来应该是640KB,PSRAM加起来应该与使用的型号相关,8M或16M。
name |
type |
offset |
size |
info |
|---|---|---|---|---|
AP_SPINLOCK |
SRAM |
0x28000000 |
0x010000 |
the size of ap spinlock use mem |
HARDWARE_ACC |
SRAM |
0x01b000 |
the size of BT(media) use mem |
|
AP_RAM |
SRAM |
0x043c00 |
the ap heap mem size |
|
CP_RAM |
SRAM |
0x030b00 |
the cp heap mem size |
|
PWR_MNG |
SRAM |
0x000100 |
the pwr mng mem size |
|
SWAP |
SRAM |
0x000800 |
the core exchange mem size |
|
PSRAM_MEM_SLAB_USER |
PSRAM |
0x60000000 |
0x019000 |
the user occur psram size |
PSRAM_MEM_SLAB_AUDIO |
PSRAM |
0x019000 |
the audio occur psram size |
|
PSRAM_MEM_SLAB_ENCODE |
PSRAM |
0x15E000 |
the encode occur psram size |
|
PSRAM_MEM_SLAB_DISPLAY |
PSRAM |
0x570000 |
the display occur psram size |
|
CP_PSRAM_HEAP |
PSRAM |
0x020000 |
the cp psram as heap size |
|
AP_PSRAM_HEAP |
PSRAM |
0x0C0000 |
the ap psram as heap size |
|
AP_PSRAM_SECTION |
PSRAM |
0x020000 |
the remaining psram size |
3.1.1 卸载蓝牙
#ifdef CONFIG_BT_REUSE_MEDIA_MEMORY
#if CONFIG_BLUETOOTH
bk_bluetooth_deinit();
#endif
#endif
3.1.2 初始化蓝牙
bk_bluetooth_init();
3.2 硬件解码内存配置说明
硬件加速器,需要使用一部分内存,这部分内存是根据实际的分辨率来优化。 默认配置参数,LCD是480 * 854的竖屏,Camera是864 * 480的MJPEG图像。
//Camera的输出分辨率,宽度,建议是32的倍数。当屏和Camera的默认配置小的时候,可以通过修改配置宏来优化内存。
#define IMAGE_MAX_WIDTH (864)
#define IMAGE_MAX_HEIGHT (480)
//启动缩放模块时需要关注这两组参数。默认建议,宽度需要比屏大一点。
#define DISPLAY_MAX_WIDTH (864)
#define DISPLAY_MAX_HEIGHT (480)
typedef struct {
#if SUPPORTED_IMAGE_MAX_720P
uint8_t decoder[DECODE_MAX_PIPELINE_LINE_SIZE * 2];
uint8_t scale[SCALE_MAX_PIPELINE_LINE_SIZE * 2];
uint8_t rotate[ROTATE_MAX_PIPELINE_LINE_SIZE * 2];
#else
uint8_t decoder[DECODE_MAX_PIPELINE_LINE_SIZE * 2];
uint8_t rotate[ROTATE_MAX_PIPELINE_LINE_SIZE * 2];
#endif
} mux_sram_buffer_t;
* 如果不需要旋转,旋转部分的内存可以省去。
* 缩放的分辨率需要注意。缩放后的分辨率,宽和高,都必须是8的倍数。
备注
当CONFIG_BT_REUSE_MEDIA_MEMORY宏打开时,这部分内存会与蓝牙的硬件内存复用,当输入camera图像的分辨率不为864 * 480, 上面的宏定义也需要同步修改,内存申请大没关系, 申请过小,就会导致内存被踩而发生异常。蓝牙与多媒体共用时,CONFIG_HARDWARE_ACC_SIZE的大小应与结构体“mux_sram_buffer_t”的大小保持一致。
4. 演示说明
请访问 APP使用文档 查看。
提示
如果您没有云账号权限,可以使用debug模式,设置局域网TCP图传方式。
5. 代码讲解
5.1 摄像头操作
已支持的外设,请参考 支持摄像头
5.1.1 UVC开关流程
如下图所示,uvc使能除了上层封装的接口都位于:./ap/components/multimedia中,用户调用的接口在“media_app.c”中,真正的功能代码都在:./ap/components/bk_uvc路径下。
Figure 3. uvc process flow chart
5.1.2 DVP开关流程
如下图所示,dvp使能除了上层封装的接口都位于:./ap/components/multimedia中,用户调用的接口在“media_app.c”中,真正的功能代码都在:./ap/components/bk_dvp路径下。 以及需要适配不同的dvp sensor的驱动代码位于:./ap/components/bk_peripheral/src/dvp中。
Figure 4. dvp process flow chart
5.1.3 打开摄像头
5.1.3.1 应用代码
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_camera_turn_on(camera_parameters_t *parameters)
{
...
//打开UVC摄像头
ret = media_app_camera_open(&db_device_info->video_handle, &device);
...
//检查是否需要重新生成IDR帧
ret = media_app_h264_regenerate_idr();
//设置本地显示旋转。
//需要注意的是:
// 1.MJPEG是YUV422 MJPEG时,仅本地显示会旋转。即,H264图像不会旋转。
// 2.MJPEG是YUV420 MJPEG时,旋转会在软件解码的时候做。即本地显示和H264编码的图像都是旋转后的数据。
media_app_set_rotate(rot_angle);
//检查是否需要打开H264 pipeline硬件编码加速器
ret = media_app_pipeline_h264_open();
...
}
5.1.3.2 接口代码
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_camera_open(camera_handle_t *handle, media_camera_device_t *device)
{
...
//卸载蓝牙
#ifdef CONFIG_BT_REUSE_MEDIA_MEMORY
#if CONFIG_BLUETOOTH
bk_bluetooth_deinit();
#endif
#endif
//投票启动PSRAM。投票的目的是,摄像头需要使用PSRAM进行数据存储。
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_JPEG_EN,PM_POWER_MODULE_STATE_ON);
//打开摄像头
ret = camera_open_handle(&media_device);
if (ret == BK_OK)
{
//打开成功,则通过camera的端口号,格式创建新节点
media_camera_node_t *node = bk_camera_handle_node_init(device->port, device->format);
if (node == NULL)
{
LOGE("%s, %d\n", __func__, __LINE__);
}
else
{
node->cam_handle = *handle;
}
}
...
}
5.1.4 关闭摄像头
5.1.4.1 应用代码
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_camera_turn_off(void)
{
...
//检查是否需要关闭H264编码
media_app_h264_pipeline_close();
//关闭所有打开的摄像头(支持多摄)
do
{
db_device_info->video_handle = bk_camera_handle_node_pop();
if (db_device_info->video_handle)
{
LOGD("%s, %d, %p\n", __func__, __LINE__, db_device_info->video_handle);
media_app_camera_close(&db_device_info->video_handle);
}
else
{
break;
}
}
while (1);
...
}
5.1.4.2 接口代码
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_camera_close(camera_handle_t *handle)
{
...
//关闭当前句柄对应的摄像头
ret = camera_close_handle(handle);
//从摄像头句柄链表中删除当前关闭的摄像头
if (ret == BK_OK)
{
bk_camera_handle_node_deinit(tmp);
}
//当关闭所有摄像头时,可以将psram投票下电,已到达低功耗的目的。
if (list_empty(&media_modules_state->cam_list))
{
LOGD("%s list_empty \n", __func__);
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_JPEG_EN,PM_POWER_MODULE_STATE_OFF);
}
...
}
警告
所有涉及到多媒体的操作,都需要注意低功耗的要求。即打开设备,必须关闭设备,否则无法让整个系统进入低功耗模式。
涉及到PSRAM投票的操作,打开和关闭,必须成对出现,否则会出现PSRAM无法关闭,功耗增加的问题。
可以参考低功耗章节
5.2 获取图像
当前AVDK针对外设出来的编码图像都是用链表(甚至是两级链表)的方式进行存储的。frame buffer的操作代码都位于:./ap/components/multimedia/comm/frame_buffer.c。 针对未编码的大图像(YUV/RGB)都是直接在PSRAM上申请大内存进行存储,具体代码:./ap/components/media_utils/src/psram_mem_slab.c。 下图展示了PSRAM上多媒体的使用与划分情况:
Figure 5. multimedia psram partition
下图为frame buffer与stream的结构图,每个摄像头都存在至少一个图像流节点stream_node,而每个流节点中存在多个frame_buffer,这些frame buffer有被根据状态分为ready和free, 具体如下:
Figure 6. stream frame buffer structure
根据上图中图像流结构,可知用户需要获取图像时,只需要从对应摄像头的图像流节点的ready链表中获取一帧图像即可,而且用完必须释放,否则会形成野指针,最终导致frame buffer个数泄漏 获取图像的基本代码流程如下:
Figure 7. frame buffer read chart
5.2.1 打开图传
5.2.1.1 应用代码
可以参考AVDK自带的图传功能,其内部调用的就是一个完整的读图,传输,释放的流程
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_video_transfer_turn_on(void)
{
...
if (db_device_info->camera_transfer_cb)
{
if (db_device_info->h264_transfer)
{
ret = bk_wifi_transfer_frame_open(db_device_info->camera_transfer_cb, IMAGE_H264);
}
else
{
ret = bk_wifi_transfer_frame_open(db_device_info->camera_transfer_cb, IMAGE_MJPEG);
}
}
...
}
5.2.1.2 接口代码
获取图像的方法预留两种:
第一种通过注册读取回调函数的方式,在回调函数中通知用户新的图像;参考接口:
media_app_register_read_frame_callback/media_app_unregister_read_frame_callback。第二种是复用storage方案,只不过在调用的时候:
media_app_storage_open,真正的storage方案此接口传入的形参为NULL,而需要获取图像时,形参为真正的回调函数,在回调函数中通知用户新的图像;参考接口:media_app_capture。上面图传方案应用的第一种方法。
//第一种在没有unregister之前会源源不断的通过回调返回读取到的新图像
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_register_read_frame_callback(image_format_t fmt, frame_cb_t cb)
{
...
//传入需要读取的图像类型fmt,确保当前存在这类图像类型码流,否则获取不到改类型图像;传入回调函数cb,图像会在回调函数中通知用户
ret = transfer_app_task_init(cb, fmt);
if (ret != BK_OK)
{
return ret;
}
...
}
//第二种
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_storage_open(frame_cb_t cb)
{
...
//传入回调函数cb,图像会在回调函数中通知用户
ret = storage_app_task_init(cb);
if (ret != BK_OK)
{
return ret;
}
...
}
//open之后,调用下面接口,会主动通过上面接口传入的回调通知用户,每调用一次,获取一张图像
bk_err_t media_app_capture(image_format_t format, char *name)
{
...
//传入需要读取的图像类型fmt,确保当前存在这类图像类型码流,否则获取不到该类型图像;传入name建议默认使用“unknow.jpg"通过接口参数检查
ret = storage_app_task_capture(format, name);
if (ret != BK_OK)
{
return ret;
}
...
}
//Path : ap/components/wifi_transfer/src/wifi_transfer.c
//Loaction : AP
bk_err_t bk_wifi_transfer_frame_open(const media_transfer_cb_t *cb, uint16_t img_format)
{
...
//注册读图回调
ret = media_app_register_read_frame_callback(img_format, wifi_transfer_read_frame_callback);
...
}
5.2.2 关闭图传
5.2.2.1 应用代码
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_video_transfer_turn_off(void)
{
...
ret = bk_wifi_transfer_frame_close();
...
}
5.2.2.2 接口代码
//Path : ap/components/wifi_transfer/src/wifi_transfer.c
//Loaction : AP
int doorbell_video_transfer_turn_off(void)
{
...
ret = media_app_unregister_read_frame_callback();
...
}
5.3 LCD显示
已支持的外设,请参考 支持外设
5.3.1 打开LCD
5.3.1.1 应用代码
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_display_turn_on(uint16_t id, uint16_t rotate, uint16_t fmt)
{
...
//设置显示的像素格式
if (fmt == 0)
{
media_app_lcd_fmt(PIXEL_FMT_RGB565_LE);
}
else if (fmt == 1)
{
media_app_lcd_fmt(PIXEL_FMT_RGB888);
}
//设置旋转的角度
media_app_pipline_set_rotate(rot_angle);
//根据摄像头类型打开对应的解码方案
if (current_device.type == UVC_CAMERA)
{
media_app_jdec_open(JPEGDEC_BY_LINE);
}
else if (current_device.type == DVP_CAMERA)
{
media_app_jdec_open(JPEGDEC_BY_FRAME);
}
//打开本地LCD显示
media_app_lcd_pipeline_open(&lcd_open);
...
}
5.3.1.2 接口代码
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_lcd_pipeline_open(void *lcd_open)
{
...
//使用LCD的时候可能需要使用PSRAM,投票使能PSRAM
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_LCD,PM_POWER_MODULE_STATE_ON);
//使能lcd显示线程
ret = lcd_display_open(config);
...
}
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t lcd_set_fmt(uint32_t fmt)
{
lcd_fmt = fmt;
LOGE("%s, fmt %x\n", __func__, lcd_fmt);
return BK_OK;
}
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_jdec_open(uint32_t dec_type)
{
int ret = BK_FAIL;
//使用decode的时候可能需要使用PSRAM,投票使能PSRAM
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_JPEG_DE, PM_POWER_MODULE_STATE_ON);
if (dec_type == JPEGDEC_BY_LINE)
{
ret = lcd_jdec_pipeline_open();
}
if (dec_type == JPEGDEC_BY_FRAME)
{
ret = img_service_open();
}
LOGI("%s complete %x\n", __func__, ret);
return ret;
}
5.3.2 关闭LCD
5.3.2.1 应用代码
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_display_turn_off(void)
{
...
//关闭本地LCD显示,关闭LCD相关的任务
media_app_jdec_close();
//关闭解码任务
media_app_lcd_disp_close();
...
}
5.3.2.2 接口代码
//Path : ap/components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_lcd_disp_close(void)
{
...
//lcd显示任务
ret = lcd_display_close();
//投票psram下电,保证低功耗目的
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_LCD, PM_POWER_MODULE_STATE_OFF);
...
}
//Path : components/multimedia/app/media_app.c
//Loaction : AP
bk_err_t media_app_jdec_close(void)
{
int ret = BK_FAIL;
//关闭整帧解码
ret = img_service_close();
if (ret != BK_OK)
{
LOGE("%s fail\n", __func__);
return ret;
}
//关闭按行解码
ret = lcd_jdec_pipeline_close();
if (ret != BK_OK)
{
LOGE("%s fail\n", __func__);
return ret;
}
//投票psram下电,保证低功耗目的
bk_pm_module_vote_psram_ctrl(PM_POWER_PSRAM_MODULE_NAME_VIDP_JPEG_DE, PM_POWER_MODULE_STATE_OFF);
LOGI("%s complete %x\n", __func__, ret);
return ret;
}
5.3.3 OSD显示
请参考 OSD视频叠加
5.4 Audio
5.4.1 打开UAC,板载MIC/SPEAKER
//Path : projects/doorbell/app/src/doorbell_udp_service.c
//Loaction : AP
int doorbell_audio_turn_on(audio_parameters_t *parameters)
{
...
//启用AEC
if (parameters->aec == 1)
{
aud_voc_setup.aec_enable = true;
}
else
{
aud_voc_setup.aec_enable = false;
}
//设置SPEAKER单端模式
ud_voc_setup.spk_mode = AUD_DAC_WORK_MODE_SIGNAL_END;
//启用UAC
if (parameters->uac == 1)
{
aud_voc_setup.mic_type = AUD_INTF_MIC_TYPE_UAC;
aud_voc_setup.spk_type = AUD_INTF_SPK_TYPE_UAC;
}
else //启动板载MIC和SPEAKER
{
aud_voc_setup.mic_type = AUD_INTF_MIC_TYPE_BOARD;
aud_voc_setup.spk_type = AUD_INTF_SPK_TYPE_BOARD;
}
if (aud_voc_setup.mic_type == AUD_INTF_MIC_TYPE_BOARD && aud_voc_setup.spk_type == AUD_INTF_SPK_TYPE_BOARD) {
aud_voc_setup.data_type = parameters->rmt_recoder_fmt - 1;
}
//设置采样率
switch (parameters->rmt_recorder_sample_rate)
{
case DB_SAMPLE_RARE_8K:
aud_voc_setup.samp_rate = 8000;
break;
case DB_SAMPLE_RARE_16K:
aud_voc_setup.samp_rate = 16000;
break;
default:
aud_voc_setup.samp_rate = 8000;
break;
}
//注册MIC数据回调
aud_intf_drv_setup.aud_intf_tx_mic_data = doorbell_udp_voice_send_callback;
...
}
5.4.2 获取上行MIC数据
//Path : projects/doorbell/app/src/doorbell_udp_service.c
//Loaction : AP
//注册MIC回调
aud_intf_drv_setup.aud_intf_tx_mic_data = doorbell_udp_voice_send_callback;
ret = bk_aud_intf_drv_init(&aud_intf_drv_setup);
int doorbell_udp_voice_send_callback(unsigned char *data, unsigned int len)
{
...
//通常实现的回调是往WIFI方向传输。
return db_device_info->audio_transfer_cb->send(buffer, len, &retry_cnt);
}
5.4.3 播放下行SPEAKER数据
//Path : projects/doorbell/app/src/doorbell_udp_service.c
//Loaction : AP
void doorbell_audio_data_callback(uint8_t *data, uint32_t length)
{
...
//往SPEAKER送数据
ret = bk_aud_intf_write_spk_data(data, length);
...
}
5.4.4 AEC/降噪处理
请参考 AEC 调试
5.4.5 关闭UAC,板载MIC/SPEAKER
//Path : projects/doorbell/ap/src/doorbell_devices.c
//Loaction : AP
int doorbell_audio_turn_off(void)
{
...
bk_aud_intf_voc_stop();
bk_aud_intf_voc_deinit();
/* deinit aud_tras task */
aud_work_mode = AUD_INTF_WORK_MODE_NULL;
bk_aud_intf_set_mode(aud_work_mode);
bk_aud_intf_drv_deinit();
...
}
5.5 H264编解码
请参考 H264编码