本文的三位作者正陽、海洋、阿力,是來自不同公司的工程師,在Agora RTC Hack 上海站程式設計馬拉松上,開發了一款可實時視訊遠端看房的智慧小車。本文將從方案設計、硬體開發、Android 端開發、服務端部署,詳實分享他們的開發經歷。
歡迎訪問 RTC 開發者社群,與更多RTC開發者交流經驗。
創意構思
此前聽到多很多次“黑客馬拉松”這樣的活動,一群來自不同地方的人聚在一起,組隊、構思、開發,在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=" width: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 標準工作組組長等技術大咖們帶來的乾貨,還將有這些飽含創意與開發熱情的小專案。想與他們聊一聊,交個朋友麼?