背景
- 摄像机设备需要支持远程控制(如分辨率、音量、网络配置等),并能够将设备状态(网络信息、运行状态、事件告警)实时上报给用户。这类能力本质上依赖于消息的发布/订阅机制。
- 传统方案通常使用 MQTT,但需要自建 MQTT 服务器,并在全球范围部署节点,运维成本和接入复杂度较高。相比之下,声网 RTM 具备与 MQTT 类似的实时消息能力,同时提供全球节点覆盖和完善的 SDK,使用户在使用时更加轻量、简洁、无感化运维。
《网络摄像机实战》系列文章,请读者前往阅读 👀
《网络摄像机实战①:Flutter 集成声网 Agora 打造实时音视频对讲》
《网络摄像机实战②:Flutter集成声网Agora图片抓拍与录像功能》
《网络摄像机实战③:设备端集成声网Agora打造实时音视频对讲功能》
《网络摄像机实战④:设备端集成声网Agora 打造远程控制设备或者异常报警推送功能》
《网络摄像机实战⑤:手机端 flutter集成声网Agora RTM打造远程控制设备功能》
《网络摄像机实战⑥:手机端 flutter集成亚马逊 AWS Kinesis Video Streams 音视频界面和控制》
《网络摄像机实战⑦:嵌入式端AWS KVS IoT SDK 库编译与测试全流程-君正/瑞芯微/全志(超详细指南)》
《网络摄像机实战⑧:嵌入式端接入 AWS KVS Producer 的设计要点与注意事项》
声网RTM介绍
实时消息(Real-Time Messaging,RTM)为开发者提供一整套低延时、高并发、可扩展、高可靠的实时消息及状态同步解决方案。RTM 负责管理应用程序实时通信层所需的基础设施。为方便用户开发与创新,RTM 在保障 99.95% 的 SLA 正常运行时间的同时,提供丰富的 App 及开放的第三方 API 扩展。
RTM的核心概念:
用户: 是指应用的实际使用者。 设备: 是指实际接入 RTM 网络的终端,包括但不限于手机、电脑、智能手表等 IoT 设备。 客户端: 是指运行在设备上的 RTM 对象实例。
基于IRtmEventHandler创建AgoraRtmService类,完成声网 RTM 的回调处理,以及核心处理类IRtmClient的实例
class AgoraRtmService : public IRtmEventHandler{
public:
~AgoraRtmService() override;
......
private:
RtmConfig m_cfg;
IRtmClient *p_rtm_client;
}
初始化 RTM,使用createAgoraRtmClient完成
int AgoraRtmService::startAgoraRtmService() {
m_cfg.appId = DEFAULT_APP_ID;
// m_cfg.userId = DEFAULT_USER_ACCOUNT;
// m_cfg.userId = nullptr;
m_cfg.userId = Device::getInstance().getDeviceGeneralInformation().getDeviceUuid();
m_cfg.eventHandler = this;
m_cfg.presenceTimeout = 30;
m_cfg.logConfig.level = RTM_LOG_LEVEL_INFO;
m_cfg.logConfig.filePath = DEFAULT_RTM_LOG_PATH;
m_cfg.useStringUserId = true;
int errorCode = -1;
int ret = 0;
uint64_t request_id = 0;
while(errorCode !=0)
{
p_rtm_client = createAgoraRtmClient(m_cfg, errorCode);
if (!p_rtm_client || errorCode != 0) {
cout << "client initialized error ret : " << ret << endl;
}
}
if(p_rtm_client)
{
p_rtm_client->login(RTC_USE_STRING_UID_ENABLE?DEFAULT_TOKEN:NULL,request_id);
}
return 0;
}
登录回调函数处理
注意: 如果登录成功才开始订阅主题 如果登录不成功会延时 1 秒再登录
void onLoginResult(const uint64_t requestId,
RTM_ERROR_CODE errorCode) override {
LOG(INFO) << " login result error code : " << errorCode << endl;
if(errorCode == RTM_ERROR_OK)
{
uint64_t request = 0;
if(p_rtm_client)
p_rtm_client->subscribe(Device::getInstance().getDeviceGeneralInformation().getDeviceUuid(), SubscribeOptions(), request);
}
else
{
uint64_t request_id = 0;
if(p_rtm_client)
{
p_rtm_client->login(RTC_USE_STRING_UID_ENABLE?DEFAULT_TOKEN:NULL, request_id);
}
sleep(1);
}
}
消息处理
int handleRobotMessage(const char *message,size_t message_size) {
LOG(INFO) << "robot control message : " << message << "length: " << message_size;
cJSON *messageJson = NULL;
cJSON *methodJson = NULL;
cJSON *responseJson = cJSON_CreateObject();;
messageJson = cJSON_Parse((char *) message);
methodJson = cJSON_GetObjectItem(messageJson, "method");
//假如methodJson为 NULL ,客户端发送了错误的method
if (!methodJson) {
cJSON *paramsJson = cJSON_CreateObject();;
cJSON_AddStringToObject(responseJson, "method", "unsupportedMethod");
cJSON_AddNumberToObject(responseJson, "status", MQTT_UNSUPPORTED_METHOND_STATUS);
cJSON_AddItemToObject(responseJson, "params", paramsJson);
cJSON_AddStringToObject(paramsJson, "unsupportedMethodParams", (char *) message);
char *json_data = cJSON_Print(responseJson);
// if ((rv = nng_send(g_rep_sock, json_data, strlen(json_data) + 1, 0)) != 0) {
// LOG(INFO) << "nng_send" << nng_strerror(rv);
// }
cJSON_Delete(responseJson);
cJSON_Delete(messageJson);
cJSON_free(json_data);
json_data = NULL;
return -1;
} else {
static MqttCommandHandle mqttCommandHandle;
cJSON *paramsJson = cJSON_GetObjectItem(messageJson, "params");
mqttCommandHandle.MqttCommandHandleFunction(methodJson->valuestring, paramsJson, responseJson);
char *json_data = cJSON_Print(responseJson);
// LOG(INFO) << "after MqttCommandHandleFunction json_data : " << json_data;
// LOG(INFO) << "after MqttCommandHandleFunction strlen(json_data) : " << strlen(json_data);
// if ((rv = nng_send(g_rep_sock, json_data, strlen(json_data) + 1, 0)) != 0) {
// LOG(INFO) << "nng_send" << nng_strerror(rv);
// }
cJSON_Delete(responseJson);
cJSON_Delete(messageJson);
cJSON_free(json_data);
json_data = NULL;
return 0;
}
}
通过onTokenPrivilegeWillExpire函数完成刷新 token
void onTokenPrivilegeWillExpire(const char* channelName) override{
cout << " rtm token privilege will expire: " <<channelName << endl;
if (DeviceConfig::device_config()->refresh_agora_token() == true) {
cout << " rtm refresh token success " << endl;
uint64_t request_id = 0;
p_rtm_client->renewToken((const char *)DeviceConfig::device_config()->agora_config()->token().c_str(),request_id);
} else {
cout << " rtm refresh token error " << endl;
}
}
参数设置截图
友情提示🔔
🙏 感谢你的阅读! 如果这篇文章对你有所启发,欢迎关注我 ⭐,欢迎点击 “打赏支持作者” 支持一下我,你的支持是我持续创作的最大动力! 我会持续分享更多关于 智能摄像头 📷、机器人项目、 🤖音视频 RTC 🎧、App 开发 📱、嵌入式开发 🔧 等方向的实战经验,让你更快落地、更少踩坑。 欢迎浏览我其他文章 📚,或许能解决你当前的难题。 如果你正好在做相关项目产品,也欢迎随时私信我,一起技术交流、一起搞事情! 🤝💬📞 联系微信/电话:13826173658