四、连线和数据传输
主要使用的API: IOTCAPIs+AVAPIs

图:客户端连线和数据传输流程图
(一)连线
连线需先通过IOTC_Get_SessionID获取空闲会话ID(SID),再调用IOTC_Connect_ByUIDEx与设备建立连接。该接口支持密钥认证,同时兼容局域网和广域网连接场景。
// 获取空闲会话ID(用于后续建立连接)
int tmpSID = IOTC_Get_SessionID();
if (tmpSID < 0) {
printf("[%s:%d]IOTC_Get_SessionID failed ret[%d]\n",
__FUNCTION__, __LINE__, tmpSID);
pthread_exit(0);
}
printf("[%s:%d]IOTC_Get_SessionID tmpSID[%d]\n",
__FUNCTION__, __LINE__, tmpSID);
// 初始化连接配置参数结构体
IOTCConnectInput connect_input;
memset(&connect_input, 0, sizeof(connect_input)); // 清空结构体避免脏数据
// 配置核心连接参数
connect_input.cb = sizeof(connect_input); // 结构体大小(固定赋值,确保版本兼容)
connect_input.authentication_type = AUTHENTICATE_BY_KEY; // 采用密钥认证方式
connect_input.timeout = 20; // 连线超时时间(单位:秒)
// 复制认证密钥(需与设备端配置一致)
memcpy(connect_input.auth_key,
gDemoConfig.iotc_auth_key,
IOTC_AUTH_KEY_LENGTH);
// 建立IOTC连接(通过设备UID连接,返回最终会话ID)
int SID = IOTC_Connect_ByUIDEx(uid, tmpSID, &connect_input);
// 连接结果判断
if (SID >= 0) {
printf("[%s:%d]IOTC_Connect_ByUIDEx success, SID[%d]\n",
__FUNCTION__, __LINE__, SID);
} else {
printf("[%s:%d]IOTC_Connect_ByUIDEx failed ret[%d]\n",
__FUNCTION__, __LINE__, SID);
// 常见错误码处理:-90(密钥错误)、-42(网络不可达)、-19(连接超时)
PrintErrHandling(SID);
pthread_exit(0);
}
其他参考API:
1. IOTC_Connect_Stop_BySID:主动终止指定SID的连接
2. IOTC_Session_Close:关闭会话并释放资源
(二)创建传输通道
IOTCAPIs提供的是不可靠传输通道,AVAPIs基于此封装了可靠传输通道,专门用于音视频数据和信令的稳定传输。通过avClientStartEx创建通道时,需配置认证方式、安全加密模式、重传策略等核心参数。
// 初始化AV客户端配置结构体(输入/输出)
AVClientStartInConfig avClientInConfig;
AVClientStartOutConfig avClientOutConfig;
// 清空输入配置结构体,避免脏数据影响
memset(&avClientInConfig, 0, sizeof(avClientInConfig));
// -------------------------- 核心配置参数 --------------------------
avClientInConfig.cb = sizeof(AVClientStartInConfig); // 结构体大小(固定赋值)
avClientInConfig.iotc_channel_id = 0; // IOTC通道ID(范围0-31,默认0为主通道)
avClientInConfig.iotc_session_id = SID; // 关联的IOTC会话ID(上一步连线返回值)
avClientInConfig.timeout_sec = 30; // 通道创建超时时间(单位:秒)
avClientInConfig.resend = 1; // 重传模式(1=开启重传,0=关闭重传,建议开启)
// -------------------------- 安全加密模式配置 --------------------------
#if ENABLE_DTLS
// 启用DTLS加密(推荐,安全性更高,适用于敏感数据传输)
avClientInConfig.security_mode = AV_SECURITY_DTLS;
#else
// 使用TUTK SIMPLE加密(兼容性优先,适用于普通场景)
avClientInConfig.security_mode = AV_SECURITY_SIMPLE;
#endif
// -------------------------- 认证方式配置 --------------------------
#if AUTH_BY_TOKEN
// Token认证(支持更长的验证数据,适用于复杂权限控制)
avClientInConfig.account_or_identity = gDemoConfig.av_identity; // 客户端身份标识
avClientInConfig.password_or_token = gDemoConfig.av_token; // 认证Token
avClientInConfig.auth_type = AV_AUTH_TOKEN; // 认证类型标记
#else
// 密码认证(验证数据较短,适用于简单场景)
avClientInConfig.account_or_identity = gDemoConfig.av_account; // 客户端账号
avClientInConfig.password_or_token = gDemoConfig.av_password; // 认证密码
avClientInConfig.auth_type = AV_AUTH_PASSWORD; // 认证类型标记
#endif
// -------------------------- 输出参数配置 --------------------------
avClientOutConfig.cb = sizeof(AVClientStartOutConfig); // 结构体大小(固定赋值)
// 创建AV传输通道(返回通道索引avIndex,后续数据操作基于此索引)
int avIndex = avClientStartEx(&avClientInConfig, &avClientOutConfig);
// 通道创建结果判断
if (avIndex < 0) {
printf("[%s:%d]avClientStartEx failed ret[%d]\n",
__FUNCTION__, __LINE__, avIndex);
IOTC_Session_Close(SID); // 通道创建失败,关闭对应的IOTC会话
pthread_exit(0);
}
其他参考API:
1. avClientStop:关闭AV通道并释放资源
2. avClientExit:中止avClientStartEx
(三)数据传输
AVAPIs针对不同数据类型提供专用传输API对,确保数据传输的高效性和可靠性。IO控制指令支持自定义格式,也可参考公版IO Command定义。
- 图像数据:
avSendFrameData(发送) / avRecvFrameData2(接收,支持帧信息解析) - 音频数据:
avSendAudioData(发送) / avRecvAudioData(接收) - IO控制指令:
avSendIOCtrl(发送) / avRecvIOCtrl(接收)
1. 接收视频
客户端需先发送视频启动指令(IOTYPE_USER_IPCAM_START),再创建独立线程接收视频数据。视频解码需从I帧开始,中途丢帧后需等待下一个I帧才能恢复正常显示。
// 发送视频流启动指令(通知设备开始推送视频)
SMsgAVIoctrlAVStream ioMsg;
memset(&ioMsg, 0, sizeof(SMsgAVIoctrlAVStream)); // 清空指令结构体
int ret = avSendIOCtrl(
avIndex,
IOTYPE_USER_IPCAM_START, // 视频启动指令(标准指令定义)
(char*)&ioMsg,
sizeof(SMsgAVIoctrlAVStream)
);
if (ret < 0) {
printf("start_ipcam_stream failed[%d]\n", ret);
avClientStop(avIndex);
IOTC_Session_Close(SID);
return 0;
}
// 初始化视频接收相关变量
unsigned int frmNo; // 帧序号
int outBufSize = 0; // 缓冲区实际使用大小
int outFrmSize = 0; // 帧数据实际大小
int outFrmInfoSize = 0; // 帧信息实际大小
bool need_key_frame = true; // 是否需要等待I帧(初始状态需等待I帧)
const int VIDEO_BUF_SIZE = 1024 * 1024; // 视频缓冲区大小(1MB,根据实际分辨率调整)
char* video_buf = new char[VIDEO_BUF_SIZE]; // 视频数据缓冲区
FRAMEINFO_t frameInfo; // 帧信息结构体(包含帧类型、分辨率等)
// 循环接收视频数据(线程主循环)
while (gProcessRun) {
// 接收视频帧数据(avRecvFrameData2支持解析帧信息,推荐使用)
int ret = avRecvFrameData2(
avIndex,
video_buf,
VIDEO_BUF_SIZE,
&outBufSize,
&outFrmSize,
(char*)&frameInfo,
sizeof(FRAMEINFO_t),
&outFrmInfoSize,
&frmNo
);
// 处理接收结果
if (ret <= 0) {
// 数据未准备好(正常情况,短暂等待后重试)
if (ret == AV_ER_DATA_NOREADY) {
usleep(5 * 1000); // 5ms延迟,降低CPU占用
continue;
}
// 帧丢失或帧数据不完整(需标记等待I帧)
else if (ret == AV_ER_LOSED_THIS_FRAME || ret == AV_ER_INCOMPLETE_FRAME) {
printf("Recv video, Lost video frame NO[%d]\n", frmNo);
need_key_frame = true; // 标记需要等待下一个I帧
continue;
}
// 缓冲区大小不足(需增大缓冲区)
else if (ret == AV_ER_BUFPARA_MAXSIZE_INSUFF) {
printf("Video buffer is too small to store frame[%d]\n", frmNo);
need_key_frame = true;
// 可选:动态调整缓冲区大小(需重新分配内存)
// resizeVideoBuffer(&video_buf, VIDEO_BUF_SIZE * 2);
continue;
}
// 其他错误(通道关闭或网络异常,退出循环)
else {
printf("Recv video error, code[%d], break loop\n", ret);
break;
}
}
// 检查是否需要等待I帧
if (need_key_frame) {
// 判断当前帧是否为I帧(IPC_FRAME_FLAG_IFRAME)
if (frameInfo.flags == IPC_FRAME_FLAG_IFRAME) {
need_key_frame = false; // 已收到I帧,解除等待标记
printf("Recv I-Frame, frame NO[%d]\n", frmNo);
} else {
continue; // 未收到I帧,跳过当前非I帧
}
}
// 视频数据处理(提交到解码队列,由解码线程处理)
// 注意:解码和显示需在独立线程中执行,避免阻塞接收线程
push_video_frame_to_decode_queue(video_buf, outFrmSize, &frameInfo);
}
// 释放资源
delete[] video_buf;
video_buf = NULL;
avClientStop(avIndex);
IOTC_Session_Close(SID);
2. 接收音频
音频接收流程与视频类似:先发送音频启动指令,再创建独立线程接收音频数据。音频具有线性特性,少量丢帧可通过填充上一帧数据避免杂音。
// 发送音频流启动指令(通知设备开始推送音频)
SMsgAVIoctrlAVStream ioMsg;
memset(&ioMsg, 0, sizeof(SMsgAVIoctrlAVStream));
int ret = avSendIOCtrl(
avIndex,
IOTYPE_USER_IPCAM_AUDIOSTART, // 音频启动指令(标准指令定义)
(char*)&ioMsg,
sizeof(SMsgAVIoctrlAVStream)
);
if (ret < 0) {
printf("start_ipcam_audio_stream failed[%d]\n", ret);
avClientStop(avIndex);
IOTC_Session_Close(SID);
return 0;
}
// 初始化音频接收相关变量
const int AUDIO_BUF_SIZE = 4096; // 音频缓冲区大小(根据采样率调整)
char audio_buf[AUDIO_BUF_SIZE]; // 音频数据缓冲区
char last_audio_frame[AUDIO_BUF_SIZE]; // 上一帧音频数据(用于填充丢帧)
int last_audio_size = 0; // 上一帧音频数据大小
FRAMEINFO_t frameInfo; // 帧信息结构体
unsigned int frmNo; // 帧序号
// 循环接收音频数据(线程主循环)
while (gProcessRun) {
// 接收音频帧数据
int ret = avRecvAudioData(
avIndex,
audio_buf,
AUDIO_BUF_SIZE,
(char*)&frameInfo,
sizeof(FRAMEINFO_t),
&frmNo
);
// 处理接收结果
if (ret <= 5="" if="" ret="=" else="" recv="" lost="" audio="" frame="" ifdef="" _enable_fill_last_frame_="" last_audio_size=""> 0) {
push_audio_frame_to_decode_queue(last_audio_frame, last_audio_size);
}
#else
// 不启用填充,直接跳过丢帧
continue;
#endif
continue;
}
// 其他错误(退出循环)
else {
printf("Recv audio error, code[%d], break loop\n", ret);
break;
}
}
// 保存当前帧数据(用于后续丢帧填充)
memcpy(last_audio_frame, audio_buf, ret);
last_audio_size = ret;
// 提交音频数据到解码队列(解码和播放在独立线程执行)
push_audio_frame_to_decode_queue(audio_buf, ret);
}
// 释放资源(音频缓冲区为栈内存,无需手动释放)
avClientStop(avIndex);
IOTC_Session_Close(SID);
重要提示
1. 视频和音频接收需分别在独立线程中执行,避免数据收发互相阻塞,影响实时性。
2. 接收线程仅负责数据接收和预处理,解码和播放需在另一独立线程中执行,防止解码耗时导致丢帧。
3. 视频解码必须从I帧开始,非I帧无法独立解码;中途丢帧后,需等待下一个I帧才能恢复正常显示。
4. 缓冲区大小需根据实际分辨率和码率调整,过小会导致帧丢失,过大会浪费内存(建议1080P视频缓冲区不小于2MB)。
(四)对讲