相册功能

更新时间:2026-02-12 09:23:04下载pdf

功能描述

涂鸦 IPC 开发框架在提供实时视频的记录和回放功能外,另外提供了一种 “回放” 文件的方式:相册功能,即将重要的文件(如:图片、MP4 视频)存放至指定位置,之后可通过 App 对应的入口查看、下载、删除这些文件。

相册功能

开发指导

目录和文件结构

App 查看相册内的文件时,需要向设备提供对应的相册名称。为降低开发和调试难度,当前版本的相册名称在设备 SDK 和 App 端是固定不变的。

设备端工作目录层级如下:

DCIM                                       // 涂鸦存储目录,位于 SD 卡的文件系统的顶层目录
    ├── ipc_emergency_record
    │    ├── aaa.jpg
    │    ├── bbb.mp4
    │    ├── ccc.mp4
    │    └── idx.bin   // 索引文件,请勿删除

当前相册命名只支持 ipc_emergency_record

非开发包提供的转码功能生成的 MP4 文件,需要在 App 查看和播放等交互流程中,自行实现 MP4 文件的解码和发送。

API 说明

功能初始化

/**
 * @brief initialize album storage
 * 
 * @param[in] album_info: album info
 * @param[in] path_info: storage mount path
 * @param[in]album_media_info: main video & audio stream info
 * 
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h 
 */
OPERATE_RET tuya_ipc_album_ss_init(IN TUYA_IPC_ALBUM_INFO_T* album_info, IN TUYA_IPC_ALBUM_WORKING_PATH_NAME_T* path_info, IPC_MEDIA_INFO_T *album_media_info);

开发包提供的相册文件存储方案并非必选,您也可以自行实现相册文件的保存和查询功能,具体实现细节请参考 注意事项 章节。

文件写入相册目录

/**
 * @brief write info of newly added file
 * 
 * @param[in] album_name: album name
 * @param[in] pInfo: newly added file info
 * 
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h 
 * 
 * @warning: not thread safe
 */
OPERATE_RET tuya_ipc_album_write_file_info(IN CHAR_T* album_name, IN ALBUM_FILE_INFO_T* pInfo);

需要将录像或图片文件提前移动或拷贝至相册目录,然后再调用此接口完成信息记录。

视频文件参数必须与主码流参数一致。

查询相册内的文件

/**
 * @brief get album index info by album name
 * 
 * @param[in] album_name: album name
 * @param[in] chan: ipc chan num, start from 0
 * @param[out] len: len of p_index_file (SS_ALBUM_INDEX_HEAD_T + SS_ALBUM_INDEX_ITEM_T)
 * @param[out] p_index_file: SS_ALBUM_INDEX_HEAD_T index header, followed SS_ALBUM_INDEX_ITEM_T
 * 
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h 
 */
OPERATE_RET tuya_ipc_album_query_by_name(IN CHAR_T* album_name, IN INT_T chan, OUT INT_T* len, OUT SS_ALBUM_INDEX_HEAD_T** p_index_file);

仅在 App 下发查询命令时调用。

下载状态设置

/**
 * @brief set album download status, start or cancel
 * 
 * @param[in] new_status: start or cancel download
 * @param[in] p_info: dowload file count and info
 * 
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h 
 */
OPERATE_RET tuya_ipc_album_set_download_status(IN SS_ALBUM_DOWNLOAD_STATUS_E new_status, IN SS_ALBUM_DOWNLOAD_START_INFO_T* p_info);

仅在 App 下发下载文件命令时调用。在 App 开始下载文件时,SDK 内部会开启新的线程发送文件数据。

删除相册内的文件

/**
 * @brief delete file info
 * 
 * @param[in] sessionId: p2p sesssion id
 * @param[in] album_name: album name
 * @param[in] cnt: file num to delete
 * @param[in] file_info_arr: file infos to delete
 * 
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h 
 */
OPERATE_RET tuya_ipc_album_delete_by_file_info(IN INT_T sessionId, IN CHAR_T* album_name, IN INT_T cnt, IN SS_FILE_PATH_T* file_info_arr);

仅在 App 下发删除文件命令时调用。

多目支持

对于带有多个图像传感器(多目)的设备,SDK 的使用方式与单目基本相同,只需要对若干参数进行修改即可。

多目设备需要对 PID 进行特殊配置,配置方式可联系对接人。

SDK 初始化

TUYA_IPC_ENV_VAR_T env;
memset(&env, 0, sizeof(TUYA_IPC_ENV_VAR_T));
....
env.image_sensor_count = 3;

ret = tuya_ipc_init_sdk(&env);

设备图像传感器的数量通过入参 image_sensor_count 设置。 计划将在 v3.10.22 之后的版本支持此特性

媒体信息设置

单目设备,媒体信息只需设置一次即可。

OPERATE_RET tuya_ipc_media_adapter_set_media_info(INT_T device, INT_T channel, DEVICE_MEDIA_INFO_T media_info);

多目设备必须按照每个 sensor 输出的实际参数准确设置!调用方式参考:

INT_T i = 0;
for (i = 0; i < IPC_CHANNEL_NUM; i++) {
    .....
    tuya_ipc_media_adapter_set_media_info(0, i, &device_media_info[i]);    
}

ring buffer 初始化

同上,单目设备,ring buffer 初始化只需设置一次即可。

OPERATE_RET tuya_ipc_ring_buffer_init(INT_T device, INT_T channel, IPC_STREAM_E stream, RING_BUFFER_INIT_PARAM_T* pparam);

多目设备的调用方式参考:

INT_T i = 0;
for (i = 0; i < IPC_CHANNEL_NUM; i++) {
  for(ringbuffer_stream_type = E_IPC_STREAM_VIDEO_MAIN; ringbuffer_stream_type < E_IPC_STREAM_MAX; ringbuffer_stream_type++ ) {

      if(ringbuffer_stream_type == E_IPC_STREAM_AUDIO_MAIN)  {
        param.bitrate = audio_sample[E_IPC_STREAM_AUDIO_MAIN]* audio_databits[E_IPC_STREAM_AUDIO_MAIN]/1024;
        param.fps = audio_fps[E_IPC_STREAM_AUDIO_MAIN];
        param.max_buffer_seconds = 0;
        param.request_key_frame_cb = NULL;
        
        ret = tuya_ipc_ring_buffer_init(0, i, ringbuffer_stream_type, &param);
      } else {
        param.bitrate =  video_bitrate[ringbuffer_stream_type];
        param.fps =  video_fps[ringbuffer_stream_type];
        param.max_buffer_seconds = 0;
        param.request_key_frame_cb = NULL;
        
        ret = tuya_ipc_ring_buffer_init(0, i, ringbuffer_stream_type, &param);
      }
      
      if(ret != 0) {
        PR_ERR("init ring buffer fails. %d %d", ringbuffer_stream_type, ret);
        return OPRT_MALLOC_FAILED;
      }
    
      PR_DEBUG("init ring buffer success. channel:%d", ringbuffer_stream_type);
  }
}

视/音频帧数据写入

每个 sensor 输出的帧数据都需要写入到对应的 ring buffer,否则预览切换镜头后出现异常。

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);

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);

OPERATE_RET tuya_ipc_ring_buffer_close(RING_BUFFER_USER_HANDLE_T handle);

多目设备的调用方式参考:

STATIC RING_BUFFER_USER_HANDLE_T s_ring_buffer_handles[IPC_CHANNEL_NUM][E_IPC_STREAM_MAX] = {NULL,};

void * __frame_append_task(void *args)
{
  ....
    
  while(1){
    if (0 == tkl_venc_get_frame(0, 0, &frame)) {
      for (i = 0; i < IPC_CHANNEL_NUM; i++) {
        if (s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN] == NULL) {
          s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN] = tuya_ipc_ring_buffer_open(0, i, E_IPC_STREAM_VIDEO_MAIN, E_RBUF_WRITE);
        }

        if (s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN]) {
          tuya_ipc_ring_buffer_append_data(s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN], frame_buff,
                                           frame.used_size + naluSeiLen, frame.frametype, frame.pts);

        } else {
          PR_ERR("tuya_ipc_ring_buffer_open %d failed,channle:%d\n", i, E_IPC_STREAM_VIDEO_MAIN);
        }
      }
    } else {
      tkl_system_sleep(10);
    }
  }

  for (i = 0; i < IPC_CHANNEL_NUM; i++) {
    if (s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN] != NULL) {
      tuya_ipc_ring_buffer_close(s_ring_buffer_handles[i][E_IPC_STREAM_VIDEO_MAIN]);
    }
  }
  
  .....
}

注意事项

  • 可参考开发包中对应的 Demo 代码,重点查看 main.cty_sdk_sd_album.c 中的调用顺序。
  • 支持相册或者记录仪的设备,PID 需要特殊设置方可展示对应相册入口,PID 配置规则可联系对接人。

常见问题

非 SDK 码流存储文件(如 MP4),相册内如何查看和下载?

开发包提供了一种文件保存和查看方式,但这种方式并非必选项,您仍可以自行实现相册文件的保存和查看。

相册文件夹以及相册图片、视频文件的管理维护可自行设计实现,SDK 没有任何限制。

App 端查看、播放文件功能需要按照信令交互流程执行:

  • 查询相册文件 MEDIA_STREAM_ALBUM_QUERY
    如果自行实现相册文件保存功能,则需要在收到此命令后,转换相册名称,并查询返回相册内所有的文件信息。返回的数据结构必须使用 C2C_ALBUM_INDEX_HEAD ,此数据的内存空间需要由 tal_malloc 动态申请,SDK 内部在使用此空间后自动释放。

  • 下载文件 MEDIA_STREAM_ALBUM_DOWNLOAD_START
    在收到此命令后,您需要自行实现文件数据发送功能,建议新开线程发送帧数据。文件发送必须使用以下接口。

    OPERATE_RET tuya_imm_p2p_app_download_data(IN CONST CHAR_T *dev_id, IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, IN CONST void * pHead, IN CONST CHAR_T * pData);
    

    其中:

    • 入参 dev_idNULL
    • client 为接收信令时携带的参数 channel
    • type 固定使用 TUYA_DOWNLOAD_ALBUM
    • pHead 数据类型为 C2C_DOWNLOAD_ALBUM_HEAD
    • pData 为需要发送的数据 Buffer。

    建议分片发送文件,并在发送最后一个分片时,将 pHeadfileEnd 置为 1。在所有文件下载完成后,需要清空 pHead 并将 fileIndex 改为 -1 表示下载结束。

  • 下载终止 MEDIA_STREAM_ALBUM_DOWNLOAD_CANCEL:接收到此信令后,需要立即终止下载线程,并释放申请的资源。

  • 删除文件 MEDIA_STREAM_ALBUM_DELETE:收到此信令后,需要删除相册内的指定文件,删除后调用以下接口上报状态。

    OPERATE_RET tuya_imm_p2p_delete_video_finish(IN CONST CHAR_T *dev_id,  IN CONST UINT_T client, TUYA_DOWNLOAD_DATA_TYPE type, INT_T success);
    

    其中:

    • 入参 dev_idNULL
    • client 为接收信令时携带的参数 channel
    • type 固定使用 TUYA_DOWNLOAD_ALBUM
    • success 在失败时值为 0,成功则为 1
  • 相册视频在线播放 MEDIA_STREAM_ALBUM_PLAY_CTRL:此控制信令较为复杂,包含:

    • 播放开始:TY_CMD_IO_CTRL_ALBUM_PLAY_START
    • 播放停止:TY_CMD_IO_CTRL_ALBUM_PLAY_STOP
    • 播放暂停:TY_CMD_IO_CTRL_ALBUM_PLAY_PAUSE
    • 播放重新启动:TY_CMD_IO_CTRL_ALBUM_PLAY_RESUME

    以上功能需要逐个实现。播放开始后,需要自行实现文件解析和发送。发送帧数据时需要使用 tuya_imm_p2p_app_album_play_send_data,播放完成后需要使用 tuya_imm_p2p_album_play_send_finish 同步进度。调用过程请参考 Demo 。

是否有循环覆盖功能?

因相册的文件写入由开发者实现,因此相册文件夹的循环覆盖功能应由开发者实现。建议开发者考虑新增空间管理功能,避免存储卡存满后导致的文件写入异常等问题。在删除旧文件时,需要同步调用tuya_ipc_album_delete_by_file_info 清空索引文件的记录,避免查找时的异常。

如自行开发文件存储的功能,即不使用 tuya_ipc_album_write_file_info 写入文件等数据,那么就无需调用 tuya_ipc_album_delete_by_file_info 。但仍然需要自行实现循环覆盖!

是否支持多目设备的录像存储与回放?

SDK 暂不不支持多目设备的录像存储和回放,开发者需要自行设计和实现多目的录像存储与回放。
但需要注意的:与单目设备相比,多目的设备回放时,需要关注 MEDIA_STREAM_PLAYBACK_QUERY_DAY_TS 信令的 ipcChan 参数,此参数表示需要查询来自哪一目的录像,此后播放录像控制信令仍需要沿用此参数;原因为:回放的前提是查询某天有哪些录像片段,所以播放控制信令中不再带有 ipcChan 参数,需要开发者保留此参数。

开启多目后,是否会占用更多内存?

需要占用更多的内存。具体大小与 sensor 输出的视/音频的码率和 ring buffer 的缓存长度有关。
例如:当支持3目的设备,每个 sensor 输出的主码流码率为 1.5Mbps、子码流 0.5Mbps、ring buffer 默认 10s,则 ring buf 总共占用内存为:((1.5 / 8 * 1024) * 10 + (0.5 / 8 * 1024) * 10) * 3 = 7680 KB。 开发者可根据实际情况,决定是否保留子码流以节约内存。