录像本地存储

更新时间:2025-04-02 09:03:52下载pdf

本地存储是智能摄像机 IPC 的基础功能。该功能负责管理 SD 卡,将音视频和图片等数据按特定目录和文件结构存储到 SD 卡上的通用文件系统,并支持数据的检索、回放、下载、删除等操作。

功能描述

本地存储提供将实时流视频存储到本地 SD 卡的能力,可设置 事件录制模式全时段录像模式。SD 卡储存空间达到上限后,采用循环覆盖的写入模式,并提供按时间索引的视频回放接口。

开发指导

运行环境

  • 开发框架

    IPC 开发框架的通用功能

  • 关联组件

    svc_local_storage

SD 卡管理

SD 卡管理包括热插拔监测、格式化、状态维护等。为保证足够读写性能,建议选用主流品牌、class10 以上的 SD 卡。

  • 热插拔监测:

    • SD 卡插入时,识别文件系统并挂载,以供数据的读写。
    • SD 卡拔出时,停止数据的录像、回放等操作,并卸载文件系统。
  • SD 卡状态:包含卡不存在、卡正常工作、卡异常。

    卡异常的主要导致原因有无文件系统、文件系统不支持、文件系统损坏、坏卡等。

  • SD 卡格式化:由用户在手机 App 上触发,会清空卡数据、使卡处于正常工作的状态。SD 卡格式化的流程图如下:

    DPSDKSD 卡pb_stop_all, set mode none to SDKformatingget status errstop writingget status err periodicallyformat ok, make status okget status okwhen status ok, SDK will get mode from cfgstart writingDPSDKSD 卡

目录和文件结构

IPC 本地存储,采用通用文件系统,以私有格式文件存储音视频和索引、JPG 格式存储图片,按多级目录、特定文件名组织文件以加速检索。

目录分为录像、本地相册 2 大类,业务上彼此独立。具体如下:

DCIM                                       // 涂鸦存储目录,位于 SD 卡的文件系统的顶层目录
    ├── CHAN0                              // 单通道 IPC 或多通道 IPC 的第一个通道的录像存储目录
    │   └── 2022
    │       └── 10
    │           └── 14
    │               ├── 1665732067_0014    // 事件文件夹,记录 utc 时间开始的录像,时长 14 秒
    │               │   ├── 0000.media     // 音视频文件,采用涂鸦私有格式,从第 0 秒开始
    │               │   ├── 0010.media     // 从第 10 秒
    │               │   ├── 1665732067.jpg // 事件封面图
    │               │   └── .info          // 录像视频格式、事件类型
    │               └── day_idx.bin        // .info 文件的索引
    ├── CHAN1
    │           ...                        //多通道 IPC 的第二个通道的录像存储目录,内部结构同

录像

录像记录

  • 录像模式有 3 种:

    • 不录像(任何时候不录像)

    • 连续录像(7*24 小时持续录像)

    • 事件录像(仅在有事件发生时录像,支持预录)

      预录,是指事件发生前 N 秒的视频,也能存到 SD 卡。N 最大值受限于 ringBuff 时长,例如 ringBuff 有 10 秒,N 最大可为 9 秒。

  • 循环覆盖,存储空间不足 500 MB 时,程序自动删除时间最老的录像,为新录像留出存储空间。每次仅删除一个事件文件夹。

录像检索

  • 按月历检索,检索该月哪些天有录像。
  • 进度条检索,检索 SD 卡中某天的录像时间的分布。

录像回放

读取 SD 卡中的音视频数据,按照一定速率、有选择地传输给客户端做显示。

  • 录像回放,支持回放状态控制、进度条跳转、倍速等特性。
  • 回放状态控制,有开始、暂停、恢复、结束。
  • 倍速回放,加快或减慢数据读取和显示的速度,支持的倍速有 0.5、2、4、8 倍。

录像下载

读取 SD 卡中的音视频数据,尽量快地传给 App 等客户端。

录像删除

按天删除,由用户在 App 上触发,删除某一天的录像。

录像格式

私有格式存储

在使能存储,选择连续录像或事件录像后,内部自动将音视频流记录到存储卡中。默认的存储格式为涂鸦私有格式,因此在存储卡中保存的录像文件不能被常规播放器打开并播放。只能通过涂鸦 App 回放中观看。满足了一定的安全等级要求。

存储加密

为满足更高的安全等级要求,在私有格式的基础上增加了存储数据的加密功能,可通过相关 API 开启或关闭。打开加密后,在保存码流数据过程中,将对数据进行加密操作,然后再写入到存储卡。
加密后的录像只可以在录像时使用的设备中回放观看,存储卡放入其他设备后,将不能正常回放加密录像。如:在 A 设备中录制的加密录像,在 B 设备中将无法正常回放观看。

  • 加密过程会占用额外的 CPU 和内存资源,因不同芯片平台算力不同,暂无法给出统一的 CPU 占用量,使用前需要单独测试并评估影响。
  • 加密需要从云端获取加密 KEY,KEY 由云端管理,因此设备配网后才可以正常运行加密。
  • 加密 KEY 如出现异常,将会更换,更换后将无法正常回放更换 KEY 之前的加密录像。

MP4 格式存储

MP4 文件格式是数码多媒体容器格式,广泛用于存储视频、音频、字幕以及照片等数据。MP4,全称为MPEG-4 Part 14,是基于 ISO/IEC 14496-12 标准的一种格式。它是一种高度兼容的视频格式,支持多种编码标准。
当前最新版本的 SDK 支持的 MP4 视频编码包括 H.264 和 H.265,而音频支持 PCM 和 G711 两种。

开启 MP4 存储后,与私有格式录像不再兼容。同时不再支持录像加密功能。

数据结构

初始化参数

typedef struct {
    CHAR_T base_path[SS_BASE_PATH_LEN];  // 视频存储的根路径
    UINT_T max_event_per_day;   // 每天的最大事件数,此限制应基于 Soc 能力,查询并在可接受的时间内返回播放信息。如果事件数超过此限制,将拒绝查询消息
    SS_SD_STATUS_CHANGED_CB  sd_status_changed_cb;  // SD 卡状态变化通知回调 
    INT_T stor_format;  //0:默认值,私有格式存储;1:MP4 格式存储(不支持加密)
    INT_T encrypt_mode;  //  1:使能加密
    INT_T security_level;   //  加密等级,可不填写
    UINT_T  skills;  //  0 表示支持所有能力,参考 TUYA_IPC_SKILL_BASIC | TUYA_IPC_SKILL_DOWNLOAD
    TUYA_IPC_ALBUM_INFO_T album_info;    // 相册功能初始化参数
    INT_T max_event_duration;  // 事件最大时长,0 为默认值 10 分钟
    UINT_T write_card_period_s;  // 录像数据写入周期,默认为 0 表示 6s 写入一次数据
    TUYA_IPC_STORAGE_WORKING_PATH_NAME_T path_name;  // 录像存储自定义路径命名
    TUYA_IPC_AOV_STORAGE_INFO_T aov_info;  // AOV 存储参数
} TUYA_IPC_STORAGE_VAR_T;

回调参数

SD 状态回调通知。

typedef OPERATE_RET (*SS_SD_STATUS_CHANGED_CB)(SD_STATUS_E status);

参数说明

typedef enum {
    SD_STATUS_UNKNOWN = 0,
    SD_STATUS_NORMAL,
    SD_STATUS_ABNORMAL,
    SD_STATUS_LACK_SPACE,
    SD_STATUS_FORMATING,
    SD_STATUS_NOT_EXIST,
    SD_STATUS_MAX
} SD_STATUS_E;

API 说明

初始化通道

初始化存储通道,该操作会执行缓存空间的创建和初始化,初始化成功会返回 OPRT_OK

/**
 * @brief 初始化本地存储
 *
 * @param[in] p_storage_var: pointer
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_init(IN TUYA_IPC_STORAGE_VAR_T *p_storage_var);

释放本地存储

执行反初始化,释放缓存资源。

/**
 * @brief uninit tuya stream storage, free used resource, memory e.g.
 *
 * @param VOID
 *
 * @return VOID
 */
VOID tuya_ipc_ss_uninit(VOID);

判断是否启动了本地存储

/**
 * @brief if stream storage is inited
 *
 * @param VOID
 *
 * @return TRUE/FALSE
 */
BOOL_T tuya_ipc_ss_is_inited(VOID);

设置本地存储模式

/**
 * @brief set write mode of stream storage
 *
 * @param[in] write_mode
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_ipc_ss_set_write_mode(IN CONST STREAM_STORAGE_WRITE_MODE_E write_mode);

获取本地存储模式

/**
* @brief get current stream storage write mode
*
* @param VOID
*
* @return STREAM_STORAGE_WRITE_MODE_E
*/
STREAM_STORAGE_WRITE_MODE_E tuya_ipc_ss_get_write_mode(VOID);

判断是否启用本地储存写入模式

/**
 * @brief if stream storage write mode is enabled
 *
 * @param VOID
 *
 * @return TRUE/FALSE
 */
BOOL_T tuya_ipc_ss_write_mode_is_enabled(VOID);

按通道启动录像存储

/**
 * @brief start event stream storage by channel
 *
 * @param[in] channel: which channel, 0 for ipc
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_start_event(IN INT_T channel);

按通道停止录像存储

/**
 * @brief stop event stream storage by channel
 *
 * @param[in] channel: which channel, 0 for ipc
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_stop_event(IN INT_T channel);

预记录时间设置

/**
 * @brief set pre-record time, invoke this API only if needed, or it is 2 seconds by default.
 *        Should be invoked once after init and before start.
 *
 * @param[in] pre_record_second: time in second
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_set_pre_record_time(IN UINT_T pre_record_second);

最大事件时长设置

/**
 * @brief set max event duration, invoke this API only if needed, or it is 600 seconds by default.
 *        Should be invoked onece after init and before start.
 *
 * @param[in] max_event_duration: time in second
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_set_max_event_duration(IN UINT_T max_event_duration);

开始回放

该接口在 MEDIA_STREAM_PLAYBACK_START_TS P2P 事件上启用,详细请参考 Demo 应用。

/**
 * @brief start a new playback
 *
 * @param[in] pb_idx: the playback and search index, used for simultaneous search from different clients
 * @param[in] event_cb: callback function of playback event
 * @param[in] video_cb: callback function of getting playback video data
 * @param[in] audio_cb: callback function of getting playback audio data
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_pb_start(IN UINT_T pb_idx, IN SS_PB_EVENT_CB event_cb, IN SS_PB_GET_MEDIA_CB video_cb, IN SS_PB_GET_MEDIA_CB audio_cb);

回调参数

typedef VOID (*SS_PB_EVENT_CB)(IN UINT_T pb_idx, IN SS_PB_EVENT_E pb_event, IN PVOID_T args);

回放事件回调

typedef VOID (*SS_PB_GET_MEDIA_CB)(IN UINT_T pb_idx, IN CONST MEDIA_FRAME_T *p_frame);

音视频接收回调参数

参数 说明
pb_idx 回放和查询索引,针对不同的客户端在同一时间进行查询
event_cb 回放事件回调
video_cb 视频数据回调
audio_cb 音频数据回调

停止回放

停止指定通道的回放。

/**
 * @brief stop an ongoing playback
 *
 * @param[in] pb_idx: playback/query index, for different client do query in the same time
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_pb_stop(IN UINT_T pb_idx);

录像加密

  • 在调用 tuya_ipc_ss_init 初始化本地存储时,设置加密使能参数。
  • 动态开启/关闭加密。更改模式后,将会停止原有的录像进程,然后再启动新的录像。
/**
 * @brief set the encryption mode
 *
 * @param[in] encrypt_enable: turn encryption on or off
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tuya_ipc_ss_set_encrypt_mode(IN CONST BOOL_T encrypt_enable)

MP4录像使能

本地存储 tuya_ipc_ss_init 初始化的入参 stor_format 参数填入 1 ,即可开启 MP4 格式存储。

开启后存储的路径与私有格式的路径不同,因此回放时不再支持原有私有格式的录像。

注意事项

  • 录像的目录,按年、月、日、事件文件夹、音视频文件的多级组织。
  • 音视频文件,单文件最多存储 10s 录像。
  • 事件文件夹,是对多个时间连续的音视频文件的包裹,最多包含 600 秒录像。
  • 事件录像模式下,一个事件文件夹一般对应一个事件持续期间的录像。
  • 连续录像模式下,事件文件夹多为 600 秒。

常见问题

内存在什么时候申请与释放,占用多大的内存?

在首次识别到正常的 SD 卡后,会申请录像缓存内存,在本存模组反初始化时释放。
内存大小:N 秒 *(1 秒视频主码流大小+1 秒音频主码流大小),N 为可配置,默认为 6。
对于多通道录像,每通道各一份内存。

SD 卡存储的视频文件是什么格式?

音视频文件采用涂鸦私有格式存储音视频数据,以 .media 作为后缀,后续版本会新增对 MP4 格式的支持。

本地存储模块是否支持自定义的格式和逻辑?

涂鸦提供了标准化的存储实现,如果开发者需要有自定义的存储逻辑,如不同的目录结构、不同的文件大小和格式、不同的文件读写方案等,可以选择不启用该模块,使用自有的存储方案程序。但是需要按照标准协议对接录像回放、下载等功能。

本地存储录像中间跳帧

  • 确保设备本机时间连续无跳变。
  • 确保音视频帧率稳定。
  • 对于低功耗产品,SD 卡挂载成功时长尽量在所设置的预录时长之内。
  • SD 卡容量越大,获取第一帧后创建事件录像文件夹的时间越长,会影响第二帧与第一帧的连续性,导致中间丢帧。

有事件上报但是没有本地录像

事件产生时调用 tuya_ipc_ss_start_event 后,不能立即调用 tuya_ipc_ss_stop_event

调用 tuya_ipc_ss_start_event 时,如果本地初始化未完成,内部会缓存事件标记,然后等初始化完成后,根据事件标记再开始录像。但是,如果在此期间调用了 tuya_ipc_ss_stop_event, 标记会被清除,最终事件录像将不会产生。

本地存储初始化失败

调用 tuya_ipc_ss_start_eventtuya_ipc_ss_set_write_mode 后日志中出现 not init 报错,说明存储模块的目录初始化还没有完成。

此现象大部分原因和上层 SD_STATUS_E tuya_ipc_sd_get_status(VOID) 的实现有关。SDK 内部会调用 tuya_ipc_sd_get_status,在返回 SD_STATUS_NORMAL 后,存储目录初始化才能完成。

App 回放界面不显示录像的下载和删除按钮

回放界面的 下载删除 按钮需要通过 tuya_ipc_upload_skills 上传对应能力位才可开启。能力标记位可通过 tuya_ipc_ss_init 的入参 skills 按位设置。对应值在 tuya_ipc_stream_storage.h 中可找到,检索 TUYA_IPC_SKILL_DOWNLOAD 即可。但需要注意:

  • tuya_ipc_ss_init 需要在调用 tuya_ipc_upload_skills 之前进行,否则存储的能力标记无法上传。
  • tuya_ipc_upload_skills 需要在联网后调用,否则能力标记无法上传。

不用 SDK 提供的存储,需要注意什么?

不调用 tuya_ipc_ss_init 即可。如果需要开启回放的下载、删除等功能,需要单独上传对应的能力标记位。可调用 tuya_ipc_skill_enable 单独设置能力标记,如下所示:

TUYA_IPC_SKILL_PARAM_U skill_param = { .value = TUYA_IPC_SKILL_BASIC | TUYA_IPC_SKILL_DOWNLOAD | TUYA_IPC_SKILL_DELETE_BY_DAY};
tuya_ipc_skill_enable(TUYA_IPC_SKILL_LOCALSTG, &skill_param);

非 SDK 存储如何实现回放?

如自行实现了存储码流等功能,同时需要回放功能,可参考 Demo 中对应的信令处理流程。回放交互有以下信令:

  • 月份检索:MEDIA_STREAM_PLAYBACK_QUERY_MONTH_SIMPLIFY,返回值参考 Demo 的实现。
  • 具体某一天检索:MEDIA_STREAM_PLAYBACK_QUERY_DAY_TSMEDIA_STREAM_PLAYBACK_QUERY_DAY_TS_WITH_ENCRYPT,返回值类型参考 Demo 的实现。
  • 开始回放:MEDIA_STREAM_PLAYBACK_START_TS,该信令接收后,需要立即返回,需要实现定位新的播放位置和发送音视频的操作。
    • 其中发送操作需要单独在另外的线程中执行,线程创建、销毁和资源管理需要自行实现。
    • 逐帧发送,回放发送音视频帧接口可参考 Demo 中调用 tuya_ipc_media_playback_send_video_frame_with_encrypttuya_ipc_media_playback_send_audio_frame_with_encrypt 的上下文。
    • 定位播放位置功能需要自行实现,并配合发送线程。
  • 回放暂停:MEDIA_STREAM_PLAYBACK_PAUSE
  • 回放再开始:MEDIA_STREAM_PLAYBACK_RESUME
  • 回放静音:MEDIA_STREAM_PLAYBACK_MUTE
  • 回放非静音:MEDIA_STREAM_PLAYBACK_UNMUTE
  • 回放倍速设置:MEDIA_STREAM_PLAYBACK_SET_SPEED,需要配合发送线程,且大于 4 倍速的回放建议只发送 I 帧。
  • 回放结束:MEDIA_STREAM_PLAYBACK_STOP
  • 下载开始:MEDIA_STREAM_DOWNLOAD_START
    • 参考开始回放信令处理,发送接口需要改为 tuya_imm_p2p_app_download_data
    • 下载发送结束后,需要调用 tuya_imm_p2p_app_download_status 设置发送完成标记。
    • 注意下载线程创建、销毁和资源管理。
  • 下载结束:MEDIA_STREAM_DOWNLOAD_STOP
  • 下载暂停:MEDIA_STREAM_DOWNLOAD_PAUSE
  • 下载再开始:MEDIA_STREAM_DOWNLOAD_RESUME
  • 下载取消:MEDIA_STREAM_DOWNLOAD_CANCLE

APP回放黑屏?

此现象与回放音视频数据发送的参数有关。 需要特别注意的是,在 MP4 方案中,使用 tuya_ipc_media_playback_send_video_frame_with_encrypt 发送视频帧数据时,接口入参中视频帧的编码参数 media.video.video_codecmedia.video.video_widthmedia.video.video_height 需要自行设置后 APP 端才能正常解析。

录像循环覆盖不生效?

此问题大多和上层实现的 VOID tuya_ipc_sd_get_capacity(UINT_T *p_total, UINT_T *p_used, UINT_T *p_free) 的有关。
此接口会在 SDK 中调用,实时获取卡剩余容量,并进入对应的策略。SDK 中录像循环覆盖的逻辑有以下两种:1. 卡剩余空间小于 512MB ,会进入循环删除覆盖的逻辑 2.剩余空间小于20MB,停止任何录像。