
图 1:基于AVAPIs的文件下载整体流程示意图
一、IO交互流程
文件下载的核心交互通过
avSendIOCtrl/avRecvIOCtrl 接口完成,分为文件列表查询、下载请求、响应三个关键阶段,最终触发通道创建和数据传输。- 阶段1:文件列表查询
- APP 调用
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_REQ),传入查询条件(时间范围、文件类型等,对应stFileListReq结构体); - 设备端通过
avRecvIOCtrl接收请求,查询符合条件的文件列表,通过avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_RESP)回复(对应stFileListResp结构体)。
- APP 调用
- 阶段2:下载请求发起
- 用户在APP端选中待下载文件,APP 调用
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_REQ),传入待下载文件列表(对应stFileDownloadReq结构体)。
- 用户在APP端选中待下载文件,APP 调用
- 阶段3:下载响应确认
- 设备端接收下载请求后,根据硬件性能(如CPU、带宽)和软件资源,自定义分配传输通道数;
- 设备端通过
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP)回复APP,包含分配的IOTC通道ID和传输协议类型(此处为AVAPIs),对应stFileDownloadResp结构体。
- 阶段4:通道创建与数据传输
- APP 和设备端根据响应中的
IOTC通道ID,分别创建对应的AV通道; - 通道创建成功后,设备端开始发送文件数据,APP端接收并存储数据。
- APP 和设备端根据响应中的
- 阶段5:通道销毁
- 所有文件下载完成(检测到
endFlag=1),APP 和设备端分别销毁AV通道,释放资源。
- 所有文件下载完成(检测到
二、AV通道的创建和销毁
通道创建需基于已建立的P2P会话(SID)和分配的IOTC通道ID,APP端与设备端的创建逻辑对称,且必须开启
resend=1(重传模式)确保数据可靠传输。2.1 APP端(客户端)通道操作
APP端作为文件接收方,通过
avClientStartEx 创建AV客户端通道,avClientStop 销毁通道。int createDataChannelOfClientForDownload(int sid,int iotc_channel_id)
{
AVClientStartInConfig in;
AVClientStartOutConfig out;
clearMemory(&in,sizeof(in));
clearMemory(&out,sizeof(out));
in.cb = sizeof(in);
in.iotc_session_id = sid; // P2P会话ID(已建立的会话)
in.iotc_channel_id = iotc_channel_id; // 设备分配的IOTC通道ID
in.timeout_sec = 30; // 连接超时时间(30秒)
in.resend = 1; //此处务必要开启resend模式(确保数据可靠传输)
in.security_mode = AV_SECURITY_AUTO; // 自动选择安全模式
if(isSDKV4){
in.auth_type = AV_AUTH_NEBULA; // SDK v4+ 认证类型(NEBULA)
in.account_or_identity = ""; // 身份标识(v4+ 无需填写)
in.password_or_token = ""; // 令牌(v4+ 无需填写)
} else {
in.auth_type = AV_AUTH_PASSWORD;// SDK v3 认证类型(密码认证)
in.account_or_identity = "camera account"; // 设备账号
in.password_or_token = "camera password"; // 设备密码
}
in.sync_recv_data = 0; // 0=异步接收数据(推荐)
in.dtls_cipher_suites = nullptr; // DTLS加密套件(默认NULL)
out.cb = sizeof(out);
return avClientStartEx(&in,&out); // 创建通道,返回AV通道索引(avIndex)
}关键说明:resend必须设为1,否则丢包时无法重传导致文件损坏;SDK版本需区分认证类型,避免认证失败。
void destoryDataChannelOfClient(int avIndex)
{
if(avIndex < 0)
return ;
avClientStop(avIndex); // 销毁AV通道,释放资源
}2.2 设备端(服务端)通道操作
设备端作为文件发送方,通过
avServStartEx 创建AV服务端通道,avServStop 销毁通道。int createDataChannelOfDeviceForDownload(int sid,int iotc_channel_id)
{
AVServStartInConfig avStartInConfig;
AVServStartOutConfig avStartOutConfig;
clearMemory(&avStartInConfig,sizeof(AVServStartInConfig));
clearMemory(&avStartOutConfig,sizeof(avStartOutConfig));
avStartInConfig.cb = sizeof(AVServStartInConfig);
avStartInConfig.iotc_session_id = sid; // P2P会话ID
avStartInConfig.iotc_channel_id = iotc_channel_id; // 分配的IOTC通道ID
avStartInConfig.timeout_sec = 30; // 超时时间(30秒)
avStartInConfig.password_auth = ExPasswordAuthCallBackFn; // 密码认证回调(v3版本用)
avStartInConfig.server_type = 0; // 服务端类型(默认0)
avStartInConfig.resend = 1; // 必须开启重传模式
avStartInConfig.security_mode = AV_SECURITY_DTLS; // 启用DTLS加密(安全传输)
//avStartInConfig.json_request = ExJsonRequest; // JSON请求回调(可选)
avStartOutConfig.cb = sizeof(AVServStartOutConfig);
return avServStartEx(&avStartInConfig, &avStartOutConfig); // 创建通道,返回AV通道索引
}关键说明:security_mode设为AV_SECURITY_DTLS启用加密传输,保护文件数据安全;password_auth回调用于v3版本的密码校验。
void destoryDataChannelOfDevice(int avIndex)
{
if(avIndex < 0)
return ;
avServStop(avIndex); // 销毁AV通道,释放资源
}三、数据传输流程
数据传输依赖
avSendFrameData(设备端发送)和 avRecvFrameData2(APP端接收)接口,文件信息通过 FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t 结构体携带(定义参考《文件传输通用定义》),传输单元为“文件信息+二进制数据”。3.1 设备端(发送方)数据发送
设备端按文件分帧读取二进制数据,搭配文件信息帧头发送,支持多文件连续传输,处理丢包重传(错误码-20006)。
#define MAX_BUFFER_SIZE 10243 // 单次发送最大缓冲区大小(可根据实际调整)
// 从文件读取二进制数据(工具函数)
int readBinaryDataFromFile(file f,char* buffer,size_t bufferSize)
{
return f.read(buffer,bufferSize);
}
// 发送文件列表(核心函数)
void sendFileList(void* arg)
{
int avIndex = createDataChannelOfDeviceForDownload(sid,iotc_channel_id);
if(avIndex < 0){
printf("createDataChannelOfDeviceForDownload failed,error code:%d\n",avIndex);
return ;
}
foreach(auto f,files){ // 遍历待发送文件列表
// 打开文件
f.open();
// 初始化文件信息帧头
FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t frmInfo = {0};
strcpy(frmInfo.fileName, f.name().c_str()); // 文件名(含扩展名)
frmInfo.fileSize = f.size(); // 文件总大小(字节)
while(1){
char buffer[MAX_BUFFER_SIZE] = {0};
// 读取文件数据
int readSize = readBinaryDataFromFile(f,buffer,MAX_BUFFER_SIZE);
if(readSize < MAX_BUFFER_SIZE){
if(readSize <= 0)
break; // 读取完毕,退出循环
// 若为文件列表中最后一个文件,设置结束标志
if(f.isLastFileOfList()){
frmInfo.endFlag = 1;
}
}
frmInfo.frmSize = readSize; // 当前帧数据大小
int ret = 0;
// 发送数据:遇-20006(丢包)则重传
do{
ret = avSendFrameData(
avIndex, // AV通道索引
buffer, // 二进制数据缓冲区
readSize, // 数据长度
(const void*)&frmInfo, // 文件信息帧头
sizeof(FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t) // 帧头长度
);
if(ret < 0){
if(ret == -20006){ // 丢包错误,等待后重传
msleep(20);
} else { // 其他错误,退出发送
break;
}
}
} while(ret == -20006);
// 异常退出:关闭文件并销毁通道
if(ret < 0){
printf("avSendFrameData error :%d\n",ret);
f.close();
goto LAB_END;
}
// 最后一帧发送完成,退出当前文件循环
if(frmInfo.endFlag){
break;
}
}
f.close(); // 关闭当前文件
}
// 等待重传缓冲区清空(避免数据残留)
int i_count = 0;
while(i_count++ < 10*300){ // 最多等待30秒
float userate = avResendBufUsageRate(avIndex); // 获取重传缓冲区使用率
if(userate > 0.0){
msleep(100);
} else {
break; // 缓冲区为空,可安全销毁通道
}
}
LAB_END:
destoryDataChannelOfDevice(avIndex); // 销毁通道
}3.2 APP端(接收方)数据接收
APP端循环接收数据,解析帧头信息,处理文件切换(新文件打开/旧文件关闭),验证结束标志,完成后关闭文件和通道。
#define MAX_BUFFER_SIZE 300*1024 // 接收缓冲区大小(建议大于发送端缓冲区)
void recvDataAndSaveToLocalFile(void *arg)
{
// 创建接收通道
int avIndex = createDataChannelOfClientForDownload(sid,iotc_channel_id);
if(avIndex < 0){
printf("createDataChannelOfClientForDownload failed\n");
return;
}
char buffer[MAX_BUFFER_SIZE];
QString fileName;
uint fileSize;
uint dataSize;
int endFlag;
FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t frmInfo;
int tmpInt1, tmpInt2, tmpInt3, tmpInt4;
uint frmNo;
QFile f; // 本地文件对象(Qt示例,其他平台可替换为对应文件操作)
// 循环读取数据
while(1){
int ret = avRecvFrameData2(
avIndex, // AV通道索引
buffer, // 接收缓冲区
MAX_BUFFER_SIZE, // 缓冲区最大长度
&tmpInt1, // 预留参数(忽略)
&tmpInt2, // 预留参数(忽略)
(char*)&frmInfo, // 接收文件信息帧头
sizeof(FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t), // 帧头长度
&tmpInt3, // 预留参数(忽略)
&frmNo // 帧序号(忽略)
);
if(ret < 0){
if(ret == AV_ER_DATA_NOREADY){ // 暂无数据,短暂等待
msleep(5);
continue;
} else if(ret == AV_ER_INCOMPLETE_FRAME || ret == AV_ER_LOSED_THIS_FRAME){
printf("lost data, frameNo:%d\n", frmNo); // 帧不完整/丢失,继续接收
continue;
} else { // 其他错误(如通道断开),退出循环
printf("avRecvFrameData2 return error:%d\n",ret);
break;
}
}
// 处理文件切换:未打开文件→打开新文件;文件名变化→关闭旧文件,打开新文件
if(!f.isOpen()){
fileName = frmInfo.fileName;
f.setFileName(fileName);
if(!f.open(QIODevice::WriteOnly)){ // 以只写模式打开文件
printf("open file failed:%s\n", fileName.toUtf8().data());
break;
}
} else {
if(fileName != frmInfo.fileName){ // 检测到新文件
f.close(); // 关闭旧文件
fileName = frmInfo.fileName;
f.setFileName(fileName);
if(!f.open(QIODevice::WriteOnly)){
printf("open file failed:%s\n", fileName.toUtf8().data());
break;
}
}
}
// 写入本地文件(ret为实际接收的数据长度)
f.write(buffer, ret);
// 检测结束标志:最后一个文件的最后一包,退出循环
if(frmInfo.endFlag){
printf("file download complete, fileName:%s\n", fileName.toUtf8().data());
break;
}
}
// 关闭本地文件
if(f.isOpen()){
f.close();
}
// 销毁通道,释放资源
destoryDataChannelOfClient(avIndex);
}四、结束标志的判断
结束标志通过
FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t 结构体的 endFlag 字段判断,规则如下:endFlag = 0:非最后一个文件的数据包,或最后一个文件的非最后一包数据;endFlag = 1:仅当所有待下载文件中,最后一个文件的最后一包数据 才设为1,用于标识整个下载流程结束。
注意:多文件连续传输时,前序文件的最后一包
endFlag 仍为0,仅最后一个文件的最后一包设为1,避免APP端误判提前关闭通道。关键注意事项
1. 重传模式强制开启:resend=1 是文件传输可靠性的核心,未开启会导致丢包后文件损坏,不可省略;
2. 通道创建顺序:必须先完成IO交互(设备返回IOTC通道ID),再创建AV通道,避免通道绑定失败;
3. 缓冲区大小规划:发送端与接收端的缓冲区大小需匹配(接收端建议更大),避免数据溢出或接收不完整;
4. 错误处理:重点处理 -20006(丢包重传)、AV_ER_DATA_NOREADY(暂无数据)等常见错误,提升稳定性;
5. 资源释放:下载完成或异常退出时,需确保“文件关闭→通道销毁”的顺序,避免文件占用或资源泄漏;
6. 版本兼容性:SDK v3与v4的认证类型不同(AV_AUTH_PASSWORD vs AV_AUTH_NEBULA),需根据实际版本适配。
