本文的三位作者正阳、海洋、阿力,是来自不同公司的工程师,将 Agora SDK 与智能小车结合,开发了一款可实时视频远程看房的创新性项目。本文将从方案设计到具体实现,详实分享他们的开发经验。三人也凭借该项目,在6月30日的Agora RTC Hack 上海站编程马拉松获得大奖。
创意构思
此前听到多很多次“黑客马拉松”这样的活动,一群来自不同地方的人聚在一起,组队、构思、开发,在48小时内做出产品雏形。我们三人抱着去听听别人的创意,重在参与的想法参加了这次比赛。对于想要做的东西,在比赛前也只是有一个大概的方向:
- 构思的方向依据我们擅长的部分来组合拼接,这就得说到我的两位给力队友海洋、阿力;海洋是嵌入式软件工程师,汽车电子方向,写个驱动做个小车手到擒来。阿力是后端工程师,具备处理服务器端和前端页面的能力。
- 于是队伍有了嵌入式和云端两部分的能力,技术构思的方向是云端为嵌入式赋能。希望有一个小车,小车可以传递回视频图像,视频图像可以实时传给多个用户,用户在得到授权之后,实现远程对小车的操控。
方案设计与分工
出于这样的构思,实现架构如上图所示。现在有了一个基本架构,也清楚了我们要实现的功能,接下来就是分工了。考虑到我们各有所长,分工如下:
分工 | 人物 | 实现目标1 | 实现目标2 |
---|---|---|---|
小车驱动与摄像头端 | 海洋 | 使用 Android系统,完成摄像头驱动 | 在 Android系统中加入驱动,解析运动控制信号,实现小车运动 |
车载APP与用户APP | 正阳 | 将摄像头采集的视频传递出去,解析服务器信令,给小车发送运动信号 | 用户可以通过手机查看小车视频,并控制小车 |
服务器端与web前端 | 阿力 | 将视频传输服务器挂到公网上,用户可以用输入网址的方式查看小车视频 | 提供web前端,用户可以通过网页控制小车 |
从硬件开发开始
小车采用了是4轮伺服电机驱动,搭配有视频采集模块、伺服电机驱动模块、STM32控制模块和摄像头云台模块,安装后整体效果图如下:
用户在远程操控小车各种动作之前,需要小车通过wifi连接到互联网。用户可以通过上位机(Android APP 或网页前端)控制小车前后左右移动或控制云台调整摄像头方向。
视频采集模块包含有wifi模块,可以连接到wifi热点为视频传输提供网络基础。也提供HDMI接口与显示器连接,方便用户调试。摄像头通过USB的方式与视频采集模块连接,我们采用免驱动的天敏6602型号摄像头,分辨率可以达到640*480,并能够自动调焦。
STM32控制模块采用 Arduino 接口与伺服电机驱动模块连接,STM32模块负责控制电机、云台信号的产生,并由伺服电机驱动模块直接驱动电机工作。伺服电机输入电压为6~12V,直流驱动。
工作原理
小车上的视频采集模块采用了定制的 Android 系统,提供网络连接、指令转发和视频流采集、传输功能。当上位机通过远程服务连接到小车后,上位机可以请求到当前小车摄像头上的视频信息;同时,视频采集模块也将上位机上传来的控制信号解析为指定格式和功能的协议数据,并通过串口发送到 STM32控制模块。
小车上的STM32控制模块在接收到相关控制信号后,调整输出脉冲信号的占空比,由驱动板转换输出电平后直接控制伺服电机或云台模块做出相应的动作,从而完成上位机用户想要的操控功能。
控制信号协议
对于只需要实现简单的小车控制的话,我们只需要实现通过串口向 STM32控制模块发送控制信号即可,简单的控制信号协议如下:
命令类型 | 包头 | 类型 | 命令 | 数据 | 包尾 |
---|---|---|---|---|---|
停止 | FF | 00 | 00 | 00 | FF |
前进 | FF | 00 | 01 | 00 | FF |
后退 | FF | 00 | 02 | 00 | FF |
左转 | FF | 00 | 03 | 00 | FF |
右转 | FF | 00 | 04 | 00 | FF |
云台上下 | FF | 01 | 01 | 角度值 | FF |
云台左右 | FF | 01 | 02 | 角度值 | FF |
Android SDK定制
开发工具
为了实现我们想要的实时视频与小车的远程控制功能,我们需要采用声网的视频SDK,并运行在 Android 开发板上。开发板,我们选用了Firefly的RK3128平台,采用Cortex-A7架构四核1.3GHz处理器、Mali-400MP2 GPU,板载千兆以太网口、2.4GHz Wi-Fi和蓝牙4.0,支持Android与Ubuntu双系统。
定制串口驱动
为了实现RK3128对小车的控制,我们需要实现 RK3128通过 USB 转串口模块与STM32控制模块通信。因此我们首先要重新配置RK3128内核,使得RK3128支持 USB转串口驱动程序。
首先下载完RK3128 Android SDK并先验证文件MD5值:
md5sum /path/to/fireprime_android5.1_git_20180510.tar.gz
fce0e6d65549939167923260142b2c1e fireprime_android5.1_git_20180510.tar.gz
确认无误后解压:
mkdir -p ~/proj/fireprime
cd ~/proj/fireprime
tar xvf /path/to/fireprime_android5.1_git_20180510.tar.gz
git reset --hard
git remote add bitbucket https://bitbucket.org/T-Firefly/firenow-lollipop.git
git pull bitbucket fireprime:fireprime
配置并编译内核:
cd ~/proj/fireprime/kernel
make rk3128-fireprime_defconfig
make menuconfig
make -j8 rk3128-fireprime.img
其中make menuconfig
这一步需要勾选上Device Drivers ---> USB support ---> USB Serial Converyer support ---> USB Serial Console device support / USB Generic Serial Driver,并勾选上 CP210x / CH341 / FTDI / PL2303 等常用串口工具设备。
编译Android系统:
cd ~/proj/fireprime
. build.sh
make -j8
./mkimage.sh
最后编译完成后烧录分区镜像,并插入USB转串口工具查看系统dmesg
是否出现以下log信息:
[ 2213.003173] usb 1-1.3: new full-speed USB device number 6 using rockchip_ehct
[ 2213.113759] usb 1-1.3: New USB device found, idVendor=10c4, idProduct=ea60
[ 2213.113839] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumbe3
[ 2213.113883] usb 1-1.3: Product: CP2102 USB to UART Bridge Controller
[ 2213.113921] usb 1-1.3: Manufacturer: Silicon Labs
[ 2213.113956] usb 1-1.3: SerialNumber: 0001
[ 2213.120813] cp210x 1-1.3:1.0: cp210x converter detected
[ 2213.209852] usb 1-1.3: reset full-speed USB device number 6 using rockchip_et
[ 2213.320161] usb 1-1.3: cp210x converter now attached to ttyUSB0
出现串口设备附着到 ttyUSBx,即说明定制串口驱动成功。
以上为全编译Android SDK的方法,需要编译 Android 系统,相较于仅编译内核而言比较费时。我们可以在上述make menuconfig
时将需要的串口驱动程序勾选为M,通过make modules
的方法,将驱动编译成.ko
文件,然后在 Android系统开机时自动加载驱动程序:
首先将.ko
驱动程序文件复制到 Android 文件系统内
adb shell
su
mount -o remount ,rw /
mkdir /modules
chmod 777 /modules
chown -R nobody:nobody /modules
exit
exit
adb push ./xxxx.ko /modules
编写启动运行脚本/data/serial.sh
#!/system/bin/sh
insmod /modules/xxxx.ko
mknod /dev/ttyUSB c 240 0
修改init.rc
并添加运行自己的脚本
service serial /system/bin/sh /data/serial.sh
user root
oneshot
在 App 端实现视频传输
视频传输和信令传输的部分,我们通过声网 Agora SDK 来实现。由于涉及到与嵌入式开发板的结合,我们主要参考的是声网在 Github 提供的各种案例中的抓娃娃机 demo。示例代码中的结构图如下:
示例代码有视频传输的部分,控制信令需要自己完成。
两个APP | 视频 | 控制信号 |
---|---|---|
小车端的 Android App | 发送视频信号 | 接受控制信号 |
用户手机端 Android App | 接受视频信号 | 发送控制信号 |
声网SDK简要使用方法如下:
- 首先申请 AppID
AndroidAPP中在res/values/strings_config.xml加入如下内容,将agora_app_id进行配置
<resources>
<string name="agora_app_id">1a486ee31a30xxxxxxxxxx</string>
</resources>
- 将.jar文件拷贝到libs/中
因为用到信令和视频传输两部分,需要两个.jar 文件分别为agora-rtc-sdk.jar和agora-sig-sdk.jar
- 在src/main/jniLibs加入armeabi-v7a与其中的.so文件
并在build.gradle中确定拥有如下描述:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
就此,使用示例代码可以顺利开启视频传输功能。
用信令让 App 控制小车
信令的具体使用方法见Agora 的参考,这里不进行详尽描述。主要使用的函数如下:
// 初始化信令 SDK
m_agoraAPI = AgoraAPIOnlySignal.getInstance(context, appID);
// 登录 Agora 信令系统
m_agoraAPI.login2(appId, account, token, uid, deviceID, retry_time_in_s, retry_count)
//////////////点对点测试/////////////
// 发送点对点消息
m_agoraAPI.messageInstantSend(account, uid, msg, msgID)
// 设置对端收到消息回调(
m_agoraAPI.onMessageInstantReceive(account, uid, msg){
//code there
}
/////////////频道测试///////////////
// 加入频道
m_agoraAPI.channelJoin(channelName)
// 发送频道消息
m_agoraAPI.messageChannelSend(channelName, msg, msgID)
// 设置对端接收到频道消息回调
m_agoraAPI.onMessageChannelReceive(channelID, account, uid, msg) {
// code there
}
//////////////////////////////////
// 退出 Agora 信令系统
m_agoraAPI.logout()
Android App 操作串口
对于小车端的 Android App 得到信令之后需要串口发送数据。因此如何实现 Android App 操作串口。这里简述两种方案:
- 采用 Android 系统给出的架构进行处理, Android 带有串口demo代码,名称为 SerialPort。这里注意两点,此处的代码依赖于 JNI 工具和 NDK,如果没有完整安装在使用项目代码的时候会出现问题。另外,串口操作不方便使用 Android 模拟器进行测试,对于没有串口的设备,在开启串口的动作时,会报错并可能导致程序退出崩溃。
- 选择使用 Android 代码发送 shell 命令的方式,直接模拟linux的shell控制代码,示例 echo ‘aa’ > /dev/ttyUSB0 将aa发送到串口ttyUSB0,这样做的好处时代码本身简单,串口直接调用底层。
对于短时间实现功能来说,方案2是更容易实现的方法,这里需要非常注意的一点,需要重新编译android的framework层给app赋予root权限
当信令解析完成,串口调试通过,就可以实现远程控制小车的行进了。
最后:服务器端的部署
为了实现用户可以方便通过手机或者电脑在线实时看房,我们需要通过web端连接小车的Android App端,获取实时传输过来的视频内容。在我们的设想中,用户可以通过远程控制小车,这样可以方便用户了解房屋各个方面的情况。综上所述,我们需要实现如下两个功能:
- 具有视频连接功能
- 具有远程遥控功能
幸运的是,通过声网提供的服务,我们可以很便捷的搭建这两个服务。
在本项目中,我们使用声网的视频SDK实现网页端和小车APP端的视频连接,通过信令SDK发送消息,去控制小车的前后左右行走和摄像头上下左右摆动。
罗列一下我们使用到的工具:
实现视频连接和发送消息
先在页面上引入视频和信令的 SDK。然后我们先来实现视频连接。
// 创建 AgoraRTC 实例并加入频道
const client = AgoraRTC.CreateClient({mode:"interop"})
client.init(appId, function () {
console.log("AgoraRTC client initialized");
client.join(channel_key, CHANNEL_NAME, null, function (uid) {
console.log("User " + uid + " join channel successfully")
console.log(new Date().toLocaleTimeString())
// do something
}
}
订阅远端的视频流并播放。
let stream = AgoraRTC.creatStream(merge(defaultConfig.config))
localStream.init(() =>{
client.on('stream-added', function (evt) {
var stream = evt.stream;
console.log("New stream added: " + stream.getId());
console.log("Subscribe ", stream);
client.subscribe(stream, function (err) {
console.log("Subscribe stream failed", err);
});
});
client.on('stream-subscribed', function (evt) {
var stream = evt.stream;
console.log("Subscribe remote stream successfully: " + stream.getId());
if ($('div#video #agora_remote' + stream.getId()).length === 0) {
$('div#video').append('<div id="agora_remote' + stream.getId() + '" style=" 810px;height:607px;"></div>');
}
stream.play('agora_remote' + stream.getId());
});
})
实现发送信息。
// 创建信令的对象
const signal = Signal(appId)
// 在实验条件下,不设置token
const token = '_no_need_token'
// 登录
const session = signal.login(account, token)
session.onLoginSuccess = (uid) => {
//发送消息给指定的账号
signal.sendMessage(reciveAcount, message)
}
完成上述的步骤之后,与小车端设置相同的appId和token(如有必要),设定好对应的参数,我们就可以远程控制小车并获取视频了。
Hacker 们用实际行动,说明了 RTC 技术不仅仅可应用于娱乐、社交、教育等领域,还可以迸发出更多新的创意。就在近期,Agora RTC Hack 还在全球其它城市火热进行中。有个人,也有来自创业公司的团队参赛并获奖。我们将邀请其中几支来自世界各地的获奖团队参加到9月7日 - 8日举行的 RTC 2018 实时互联网大会。现场不仅有 Google WebRTC 产品经理、华为多媒体实验室首席科学家、西北工业大学智能声学与临境通信中心首席科学家、Twitch 首席研发工程师、AVS 标准工作组组长等技术大咖们带来的干货,还将有这些饱含创意与开发热情的小项目。想与他们聊一聊,交个朋友么?点击这里报名,现场约起来吧!