国产成人高清亚洲,日韩无码一区二区,国产精品亚洲综合专区片高清久久久,欧美日韩国产区在线观看,sese在线,亞洲綜合久久精品無碼色欲,日韩亚洲av三级片

  • 方案介紹
    • 一、前言
    • 二、搭建視頻監(jiān)控流媒體服務器
    • 三、華為云IOT服務器部署過程
    • 四、Android手機APP開發(fā)
    • 五、STM32端代碼設計
    • 六、關于Android手機USB通信的問題
    • 七、總結
  • 附件下載
  • 相關推薦
申請入駐 產(chǎn)業(yè)圖譜

綠色再生·安卓4G智能遠程操作巡視機器人小車

06/03 10:17
637
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

更多詳細資料請聯(lián)系.docx

共1個文件

一、前言

1.1 項目介紹

【1】項目功能介紹

隨著物聯(lián)網(wǎng)技術移動通信技術的快速發(fā)展,遠程遙控設備在日常生活及工業(yè)應用中的普及度日益提高。無論是家用掃地機器人實現(xiàn)自主導航清掃,還是目前抖音平臺上展示的實景互動小車等創(chuàng)新應用,都體現(xiàn)了遠程控制和實時視頻監(jiān)控技術對現(xiàn)代智能設備的重要性。

本項目設計并實現(xiàn)一款基于STM32微控制器的遠程遙控安卓小車系統(tǒng)。該系統(tǒng)充分利用了淘汰下來的安卓舊手機作為車載信息處理單元,不僅實現(xiàn)了資源的有效再利用,還結合4G網(wǎng)絡技術以及先進的流媒體服務和物聯(lián)網(wǎng)技術,搭建起一套集遠程操控、實時視頻音頻傳輸于一體的高效解決方案。

項目的小車搭載了STM32主控板以精確控制四個電機的動作,通過L298N驅動芯片確保了底座移動的穩(wěn)定性和靈活性。同時,小車的動力源采用兩節(jié)18650鋰電池提供充足的電力支持。

車載的舊安卓手機通過USB線連接到STM32主控板上,接收并執(zhí)行來自遠端手機APP的指令。這款由Qt開發(fā)的Android APP能夠利用4G網(wǎng)絡實現(xiàn)實時在線,并通過攝像頭采集音視頻數(shù)據(jù),通過RTMP協(xié)議將這些數(shù)據(jù)推送到華為云ECS服務器上的NGINX流媒體服務器,從而實現(xiàn)高清流暢的遠程視頻監(jiān)控。

為了實現(xiàn)雙向交互和低延遲控制,整個系統(tǒng)還借助MQTT協(xié)議連接至華為云IOT服務器。另一臺安裝了同樣由Qt開發(fā)的Android手機APP的終端設備,可以通過該APP拉取小車端的實時音視頻流進行播放,并通過方向鍵菜單實現(xiàn)對小車的精準遠程操控。這種設計不僅極大地拓展了傳統(tǒng)遙控小車的功能性與實用性,還為其他類似應用場景提供了可借鑒的技術框架。

當前設計的這種基于4G網(wǎng)絡設計的遠程遙控巡檢小車的技術應用場景主要是: 安全防護、環(huán)境監(jiān)測、設備巡檢、物料搬運、應急救援這些地方。

(1)遠程監(jiān)控

  • 可應用于安全防護、環(huán)境監(jiān)測、農業(yè)監(jiān)控等領域,例如森林防火、農田灌溉管理、危險區(qū)域偵查等,通過實時視頻和音頻傳輸,工作人員可以在遠程位置對現(xiàn)場情況進行實時了解和操控。

(2)工業(yè)巡檢

  • 在工廠、倉庫或大型設施內部署此類小車,用于設備巡檢、物料搬運或生產(chǎn)流程監(jiān)控,尤其適合那些人員不易到達或者存在安全隱患的地方。

(3)應急救援

  • 在地震、火災等災害現(xiàn)場,遠程遙控小車可用于進入倒塌建筑內部搜尋生命跡象,或是攜帶傳感器測量有害氣體濃度等,為救援決策提供及時信息

下面是當前小車整體技術框架:

image-20240227102115213

小車的模型:

image-20240308094613156

本次設計里放在小車終端上的Android手機采用的是小米4C,一款普通的Android手機:

2015年上市的小米4C。 從本身價值來講,現(xiàn)在在某魚上差不多是200塊,本身就是一個完整的系統(tǒng)。性價比非常高。比去單獨買Linux開發(fā)板進行模型開發(fā)實驗來說,成本低低很多。 主流的Linux、Android開發(fā)板價格都比較貴的。

image-20240227102453689

image-20240227102527756

開發(fā)過程中,測試遙控效果:

image-20240227103342355

【2】設計實現(xiàn)的功能

(1)STM32主控板功能:

  • 控制4個電機:STM32通過L298N驅動芯片驅動4個電機,實現(xiàn)小車的前進、后退、左轉、右轉等動作。
  • 數(shù)據(jù)通信:STM32通過USB接口與安卓手機通信,接收手機APP發(fā)送的控制指令,并將小車的狀態(tài)信息(如電量、速度、位置等)發(fā)送回手機。
  • 電源管理:管理2節(jié)18650鋰電池的供電,確保電壓穩(wěn)定并監(jiān)控電池電量。

(2)安卓手機APP功能

  • 控制指令下發(fā):手機APP通過USB接口向STM32發(fā)送控制指令,控制小車的動作。
  • 視頻和音頻流獲取:APP從手機攝像頭麥克風獲取視頻和音頻流,并進行編碼處理。
  • 流媒體推流:通過RTMP協(xié)議將編碼后的視頻和音頻流推送到華為云ECS服務器+NGINX搭建的RTMP流媒體服務器。
  • MQTT連接:APP通過MQTT協(xié)議與華為云IOT服務器建立連接,實現(xiàn)雙向通信。

(3)華為云服務器功能:

  • RTMP流媒體服務:接收并轉發(fā)安卓手機APP推送的視頻和音頻流。
  • MQTT服務:作為MQTT消息代理,實現(xiàn)遠程手機與STM32主控板之間的通信。

(4)遠程Android手機APP功能:

  • 實時視頻和音頻播放:從華為云ECS服務器拉取視頻和音頻流,并實時顯示在APP界面上。
  • MQTT連接:與華為云IOT服務器建立連接,接收STM32主控板發(fā)送的小車狀態(tài)信息。
  • 遠程控制:提供方向鍵控制菜單,允許用戶遠程控制小車前進、后退、轉彎等動作。

【3】項目硬件模塊組成

(1)電源模塊

  • 電池組:采用兩節(jié)18650鋰電池作為供電源,它們具有高能量密度、體積小的特點,能夠為整個系統(tǒng)提供穩(wěn)定的直流電能。

(2)主控模塊

  • STM32微控制器:這是整個小車的核心控制單元,負責處理所有的邏輯運算和數(shù)據(jù)通信任務。通過編程實現(xiàn)對電機驅動、USB通信、網(wǎng)絡連接等功能的控制。

(3)電機驅動模塊

  • L298N驅動芯片:用于驅動底座上的四個電機,L298N是一個高性能的H橋電機驅動器,可以接收來自STM32的信號,轉換為足夠驅動電機工作的電流和電壓,并且支持電機正反轉及速度調節(jié)。

(4)移動平臺模塊

  • 四個直流電機:直接安裝在小車底座上,通過L298N驅動進行精確的速度和方向控制,以實現(xiàn)小車前進、后退、左右轉彎等運動功能。

(5)通信模塊

  • USB接口:STM32主控板通過USB線與安卓手機物理連接,實現(xiàn)數(shù)據(jù)傳輸,接收來自手機APP的控制指令。
  • 4G模組:集成在安卓手機內部,插入SIM卡后可實現(xiàn)高速無線網(wǎng)絡連接,使小車能夠在遠程環(huán)境下通過互聯(lián)網(wǎng)與其他設備通信。

(6)多媒體采集模塊

  • 安卓手機攝像頭:用于捕捉實時視頻和音頻信息,是小車端環(huán)境感知的關鍵組件。

(7)云服務交互模塊

  • 華為云ECS服務器+NGINX RTMP流媒體服務器:小車端將采集到的音視頻流推送到華為云服務器上,通過RTMP協(xié)議實現(xiàn)實時音視頻的低延遲傳輸和分發(fā)。
  • 華為云IOT服務器:小車和遠端控制手機均通過MQTT協(xié)議與之建立連接,實現(xiàn)遠程數(shù)據(jù)交換和控制命令的下發(fā)。

【3】功能總結

(1)電機驅動與控制:通過STM32微控制器和L298N驅動芯片,實現(xiàn)對小車上四個電機的精確控制,包括前進、后退、左轉、右轉等動作,從而控制小車的移動方向和速度。

(2)無線通信與數(shù)據(jù)傳輸:STM32與安卓手機之間通過USB接口建立通信,實現(xiàn)控制指令的下發(fā)和小車狀態(tài)信息的上傳。同時,安卓手機通過4G網(wǎng)絡連接到華為云服務器,實現(xiàn)了遠程控制命令的遠程傳輸和視頻音頻流的推送。

(3)流媒體推流與播放:安卓手機APP能夠捕獲手機攝像頭和麥克風的視頻和音頻流,通過RTMP協(xié)議推送到華為云服務器。另一臺安卓手機APP則從服務器拉取這些流,實現(xiàn)實時播放,從而允許用戶遠程觀看小車的實時畫面和音頻。

(4)華為云服務器支持:華為云ECS服務器和NGINX搭建的RTMP流媒體服務器負責接收、轉發(fā)視頻和音頻流,確保流媒體的穩(wěn)定性和實時性。同時,華為云IOT服務器通過MQTT協(xié)議提供消息代理服務,實現(xiàn)遠程手機與STM32之間的雙向通信。

(5)用戶界面與交互設計:安卓手機APP提供了直觀的用戶界面,包括控制按鈕、狀態(tài)顯示、視頻播放器等,使用戶能夠方便地對小車進行控制、觀看視頻、監(jiān)聽音頻,以及監(jiān)控小車的狀態(tài)信息。

(6)遠程控制:通過結合STM32的電機控制、華為云服務器的數(shù)據(jù)處理和傳輸,以及安卓手機的用戶界面和交互設計,實現(xiàn)了從遠程手機到小車的遠程控制功能。用戶可以在遠離小車的地點,通過手機APP發(fā)出控制指令,實時觀察小車的動作和周圍環(huán)境。

1.2 設計思路

1.3 系統(tǒng)功能總結

自主供電與移動控制 采用2節(jié)18650鋰電池為小車提供電力供應;STM32微控制器結合L298N驅動芯片,精準控制4個電機動作,實現(xiàn)前進、后退、轉彎等移動功能
手機APP通信與指令傳輸 STM32通過USB線與安卓手機連接,接收并解析來自手機APP的控制指令,實現(xiàn)人機交互和遠程指令執(zhí)行
實時視頻音頻流傳輸 安卓手機利用4G網(wǎng)絡上網(wǎng),搭載Qt開發(fā)的Android APP采集攝像頭視頻和音頻數(shù)據(jù),并通過RTMP協(xié)議將音視頻流推送到華為云ECS服務器+NGINX搭建的流媒體服務器
物聯(lián)網(wǎng)(IoT)連接與遠程監(jiān)控 小車端及遠端控制手機均通過MQTT協(xié)議連接華為云IOT服務器,實現(xiàn)車輛狀態(tài)信息實時上傳及遠程音視頻流拉取顯示;遠端手機APP提供方向鍵菜單以遠程操控小車
數(shù)據(jù)交互與低延遲控制 利用MQTT協(xié)議確保在4G網(wǎng)絡環(huán)境下高效、低延遲的數(shù)據(jù)交互,實現(xiàn)對小車的實時遠程控制,提升整體系統(tǒng)的響應速度和操作體驗

二、搭建視頻監(jiān)控流媒體服務器

2.1 購買云服務器

如果之前沒有使用過華為云的ECS服務器,可以先申請試用1個月,了解服務器的基本使用然后再購買,不得不說提供這個試用服務還是非常不錯。

產(chǎn)品試用領取地址: https://activity.huaweicloud.com/free_test/index.html

image-20220611130453218

每天9:30開搶,每天限量100份,這個頁面不僅有云服務器可以領取試用,還有云數(shù)據(jù)庫、沙盒等其他產(chǎn)品。

image-20220611130511064

image-20220611130612334

我這里領取了 2核4G S6云服務器,這個服務器是配套華為自研25GE智能高速網(wǎng)卡,適用于網(wǎng)站和Web應用等中輕載企業(yè)應用。

image-20220611130700250

選擇配置的時候發(fā)現(xiàn)S6規(guī)格的已經(jīng)沒有了,來晚了。

image-20220611130956075

S6規(guī)格沒有了,就選擇了S3,2核,4GB的配置結算。

image-20220611131022494

頁面向下翻,下面選擇系統(tǒng)預裝的系統(tǒng),我這里選擇ubuntu 20.04,安裝NGINX,來搭建流媒體服務器。

image-20220611131123954

頁面繼續(xù)下翻,設置云服務器名稱,設置系統(tǒng)的root密碼。

image-20220611131148126

接著就可以繼續(xù)去支付了。

image-20220611131218881

image-20220611131250941

image-20220611131306457

購買后等待一段時間,系統(tǒng)資源就即可分配完成。

image-20220611141408922

2.2 登錄云服務器

云服務器的管理控制臺從這里進入: https://www.huaweicloud.com/product/ecs.html

在官網(wǎng)主頁,點擊產(chǎn)品,找到計算選項,就可以看到彈性云服務器ECS,點擊進去就可以看到管理控制臺的選項。

image-20220611151527835

image-20220611151444306

彈性云服務器的選項頁面可以看到剛才購買的云服務器,如果點擊進去提示該區(qū)域沒有可用的服務器,說明區(qū)域選擇的不對,在下面截圖紅色框框的位置可以看到可用的區(qū)域切換按鈕,切換之后就行了。

image-20220611151650793

點擊服務器右邊的更多,可以對服務器重裝系統(tǒng)、切換操作系統(tǒng)、關機、開機、重啟、重置密碼等操作。

image-20220611151918159

接下來先點擊登錄,了解一下登錄的流程,看看系統(tǒng)進去的效果。

image-20220611152037313

云服務器支持SSH協(xié)議遠程登錄,可以在瀏覽器上直接使用CloudShell方式或者VNC方式登錄,如果本身你自己也是使用Linux系統(tǒng),可以在Linux系統(tǒng)里通過ssh命令直接登錄,如果是在windows下可以使用SecureCRT登錄。

image-20220611160601965

其他登錄方式。

image-20220611152254866

最方便的登錄方式,直接在控制臺使用VNC在瀏覽器里登錄,點擊立即登錄。

image-20220611152727005

根據(jù)提示輸入用戶名,密碼后,按下回車鍵即可登錄。

用戶名直接輸入root,密碼就是剛才配置云服務器時,填入的root密碼。

注意: Linux下輸入密碼默認都是隱藏的,也就是在鍵盤上輸入密碼界面上是不會有反應的,自己按照正確的密碼輸入即可。

image-20220611152929951

用戶名、密碼輸入正確后,登錄成功。

可以點擊全屏模式,更好的操作。

image-20220611153239527

2.3 使用CloudShell登錄云服務器

在頁面上直接點擊CloudShell登錄按鈕。CloudShell方式比VNC方式方便、流暢多了,也支持命令的復制粘貼。

image-20220611160821704

image-20220611160729806

輸入密碼點擊連接。

image-20220611160944543

登錄成功進入終端。

image-20220611161046853

2.4 使用SecureCRT登錄云服務器

上面演示了兩種登錄方式,都是直接在瀏覽器里登錄,這種兩種方式比較來看,VNC方式效率最低,CloudShell相對來說要方便很多。一般我自己正常開發(fā)時,都是使用第三方工具來登錄的,如果本身自己開發(fā)環(huán)境的電腦就是Linux,MAC等,可以直接使用ssh命令登錄,這種方式操作流暢方便。如果在windows下,可以使用第三方軟件登錄。

我現(xiàn)在使用的系統(tǒng)是win10,在windows系統(tǒng)下有很多遠程終端軟件支持SSH登錄到Linux云服務器,我當前采用的使用SecureCRT 6.5來作為登錄終端,登錄云服務器。

注意: SecureCRT 6.5 登錄高版本Linux系統(tǒng)會出現(xiàn)Key exchange failed問題,導致登錄失敗,比如: 登錄ubuntu 20.04 系統(tǒng)。 出現(xiàn)這種問題需要對系統(tǒng)ssh配置文件進行添加配置。

添加配置的流程:

命令行輸入:
vim /etc/ssh/sshd_config

在文件最后添加:
KexAlgorithms [email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1

保存退出。
    
重啟ssh服務
service ssh restart

如果不想這么麻煩的去修改配置文件,那么最簡單的辦法就是: 切換操作系統(tǒng),換一個低版本的,比如:ubuntu18.04 即可。

在云服務器的控制臺,找到自己的服務器,然后選擇切換操作系統(tǒng)。

image-20220611155915095

根據(jù)界面上的引導流程,切換即可。更換新的系統(tǒng)需要1~4分鐘時間,稍微等待一下即可。

image-20220611160010714

如果要使用遠程SSH協(xié)議方式登錄云服務器,需要具備以下幾個前提條件。

[1]彈性云服務器狀態(tài)為“運行中”。
[2]彈性云服務器已經(jīng)綁定彈性公網(wǎng)IP,綁定方式請參見綁定彈性公網(wǎng)IP。
[3]所在安全組入方向已開放22端口,配置方式請參見配置安全組規(guī)則。
[4]使用的登錄工具(如PuTTY,SecureCRT`)與待登錄的彈性云服務器之間網(wǎng)絡連通。例如,默認的22端口沒有被防火墻屏蔽。

但是這些配置不用擔心,在購買服務器后,根據(jù)引導一套走完,上面的這些配置都已經(jīng)默認配置好了,不用自己再去單獨配置。

系統(tǒng)切換成功后,打開SecureCRT 6.5軟件,進行登錄。SecureCRT 6.5整體而言還是挺好用的。

如果自己沒有``SecureCRT,自己下載一個即可。當然,不一定非要使用SecureCRT`,其他還有很多SSH遠程登錄工具,喜歡哪個使用哪個。

下面介紹``SecureCRT `登錄的流程。

image-20220611160216821

選擇新建會話。

image-20220611161730570

選擇SSH2協(xié)議。

image-20220611161757991

主機名就填服務器的公網(wǎng)IP地址,端口號默認是22,用戶名填root。

image-20220611161831158

自己云服務器的公網(wǎng)IP地址,在控制臺可以看到。

image-20220611161945275

軟件點擊下一步之后,可以填充描述信息,備注這個鏈接是干什么的。

image-20220611162046191

選擇剛才新建的協(xié)議端口點擊連接。

image-20220611162148248

云服務器連接上之后,軟件界面會彈出對話框填充用戶名、密碼。

image-20220611162228021

登錄成功的效果如下。

image-20220611162246059

軟件可以配置一些選項,讓界面符合Linux終端配色,可以修改字體大小、字體編碼等等。

image-20220611162407116

image-20220611162522751

配置后的效果。

image-20220611162543936

2.5 安裝NFS服務器

為了方便向服務器上拷貝文件,可以采用NFS服務器、或者FTP服務器 這些方式。 我本地有一臺ubuntu 18.04 系統(tǒng)筆記本,我這里采用NFS方式拷貝文件上去。

(1)安裝NFS服務器

root@ecs-348470:~# sudo apt-get install nfs-kernel-server

(2)創(chuàng)建一個work目錄方便當做共享目錄使用

root@ecs-348470:~# mkdir work

(3)編寫NFS配置文件

NFS 服務的配置文件為/etc/exports,如果系統(tǒng)沒有默認值,這個文件就不一定會存在,可以使用 vim 手動建立,然后在文件里面寫入配置內容。

/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure)    

image-20220611182221690

配置文件里參數(shù)的含義:

image-20220611180438643

image-20220611180457992

image-20220611180909866

image-20220611180933623

(4)NFS服務器相關指令

/etc/init.d/nfs-kernel-server start #啟動 NFS 服務
ufw disable     #關閉防火墻
/etc/init.d/nfs-kernel-server restart  #重啟NFS服務
exportfs -arv   #共享路徑生效

(5)本地客戶機掛載服務器的目錄

wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/

(6)設置華為云服務器的安全策略

需要把華為云服務器的端口號開放出來,不然其他客戶端是無法訪問服務器的。

我這里比較粗暴直接,直接將所有端口號,所有IP地址都開放出來了。

image-20220611185514553

image-20220611185254027

image-20220611185204400

**(7)本地客戶機掛載服務器測試 **

掛載指令:

sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/

image-20220611185744008

2.6 安裝NGINX(配置RTMP服務)

(1)下載編譯時需要依賴的一些工具

root@ecs-348470:~# sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev

image-20220611170102729

(2)下載NGINX編譯需要的軟件包

root@ecs-348470:~# mkdir nginx      
root@ecs-348470:~# cd nginx/
root@ecs-348470:~# wget http://nginx.org/download/nginx-1.10.3.tar.gz
root@ecs-348470:~# wget http://zlib.net/zlib-1.2.11.tar.gz
root@ecs-348470:~# wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz
root@ecs-348470:~# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz
root@ecs-348470:~# wget https://github.com/arut/nginx-rtmp-module/archive/master.zip

image-20220611190538685

(3)下載后的文件全部解壓

root@ecs-348470:~# tar xvf openssl-1.0.2k.tar.gz
root@ecs-348470:~# tar xvf nginx-rtmp-module-master.tar.gz
root@ecs-348470:~# tar xvf nginx-1.8.1.tar.gz
root@ecs-348470:~# tar xvf pcre-8.40.tar.gz
root@ecs-348470:~# tar xvf zlib-1.2.11.tar.gz

image-20220611190905353

(4)配置NGINX源碼,生成Makefile文件

root@ecs-348470:~# cd nginx-1.8.1/
root@ecs-348470:~# ./configure --prefix=/usr/local/nginx --with-debug --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 --with-openssl=../openssl-1.0.2k --add-module=../nginx-rtmp-module-master

執(zhí)行完上一步之后,打開objs/Makefile文件,找到-Werror選項并刪除。

(5)編譯并安裝NGINX

 root@ecs-348470:~/nginx/nginx-1.8.1# make && make install

安裝之后NGINX的配置文件存放路徑:

/usr/local/nginx/nginx:主程序

(6)查看NGINX的版本號

root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.8.1

(5)在配置文件里加入RTMP服務器的配置

root@ecs-348470:~# vim /usr/local/nginx/conf/nginx.conf 
打開文件后,在文件最后加入以下配置:

rtmp {  
    server {  
        listen 8888;   
        application live {  
            live on;  
			record all;
    		record_unique on;
    		record_path "./video";  #視頻緩存的路徑
    		record_suffix -%Y-%m-%d-%H_%M_%S.flv;
        	}
         } 		 
}

這樣配置之后,服務器收到RTMP流會在NGINX的當前目錄下,創(chuàng)建一個video目錄用來緩存視頻。

客戶端向服務器推流之后,服務器就會緩存視頻到設置的目錄。

(5)檢查配置文件是否正確

root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

(6)NGINX常用的3個命令

sudo service nginx start
sudo service nginx stop
sudo service nginx restart

(7)啟動NGINX服務器

sudo service nginx start

2.7 攝像頭推流音視頻到服務器

為了模擬攝像頭實時監(jiān)控推流,我這使用QT+FFMPEG編寫了一個小軟件,在windows下運行,推流本地筆記本電腦的數(shù)據(jù)到服務器。軟件運行之后,將本地音頻、視頻編碼之后通過RTMP協(xié)議推流到NGINX服務器。

軟件運行效果:

image-20220611200146361

推流工具運行過程中效果。

image-20220611200428359

2.8 編寫本地RTMP流播放器

在上面通過推流客戶端模擬攝像頭,已經(jīng)將本地的攝像頭數(shù)據(jù)實時推流到服務器了,那么還差一個播放器,為了方便能夠在任何有網(wǎng)的地方實時查看攝像頭的音頻和圖像,還需要編寫一個RTMP流媒體播放器。

我這里的播放器內核是采用libvlc開發(fā)的,使用QT作為GUI框架,開發(fā)了一個流媒體播放器,可以實時拉取服務器上的流數(shù)據(jù),并且還支持回放。因為服務器上的NGINX配置了自動保存的參數(shù),可以將推上去的流按時間段保存下來。

輸入服務器地址之后就可以拉取流進行播放。

image-20220611201255709

點擊獲取回放列表,可以查看服務器上保存的歷史視頻回放播放。

image-20220611201443927

三、華為云IOT服務器部署過程

在華為云IOT平臺上,需要進行設備接入、數(shù)據(jù)模型定義、規(guī)則引擎配置和應用開發(fā)等四個核心模塊的開發(fā)。其中,設備接入模塊包括設備注冊、獲取設備證書、建立連接等步驟,以保障設備與云平臺之間的安全通信;數(shù)據(jù)模型定義模塊需要根據(jù)實際需求定義相應的數(shù)據(jù)模型,包括上傳數(shù)據(jù)格式、設備屬性和服務等。規(guī)則引擎配置模塊需要完成實時消息推送、遠程控制和告警等功能。應用開發(fā)模塊則是將完整的智能井蓋系統(tǒng)進行打包,為用戶提供統(tǒng)一的操作接口。

華為云官網(wǎng): https://www.huaweicloud.com/

打開官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 設備接入IoTDA

image-20221204193824815

3.1 物聯(lián)網(wǎng)平臺介紹

華為云物聯(lián)網(wǎng)平臺(IoT 設備接入云服務)提供海量設備的接入和管理能力,將物理設備聯(lián)接到云,支撐設備數(shù)據(jù)采集上云和云端下發(fā)命令給設備進行遠程控制,配合華為云其他產(chǎn)品,幫助我們快速構筑物聯(lián)網(wǎng)解決方案。

使用物聯(lián)網(wǎng)平臺構建一個完整的物聯(lián)網(wǎng)解決方案主要包括3部分:物聯(lián)網(wǎng)平臺、業(yè)務應用和設備。

物聯(lián)網(wǎng)平臺作為連接業(yè)務應用和設備的中間層,屏蔽了各種復雜的設備接口,實現(xiàn)設備的快速接入;同時提供強大的開放能力,支撐行業(yè)用戶構建各種物聯(lián)網(wǎng)解決方案。

設備可以通過固網(wǎng)、2G/3G/4G/5G、NB-IoT、Wifi等多種網(wǎng)絡接入物聯(lián)網(wǎng)平臺,并使用LWM2M/CoAP、MQTT、HTTPS協(xié)議將業(yè)務數(shù)據(jù)上報到平臺,平臺也可以將控制命令下發(fā)給設備。

業(yè)務應用通過調用物聯(lián)網(wǎng)平臺提供的API,實現(xiàn)設備數(shù)據(jù)采集、命令下發(fā)、設備管理等業(yè)務場景。

img

3.2 開通物聯(lián)網(wǎng)服務

地址: https://www.huaweicloud.com/product/iothub.html

image-20221204194233414

開通標準版免費單元。

image-20230420181306316

image-20230420181322092

開通之后,點擊總覽,查看接入信息。 我們當前設備準備采用MQTT協(xié)議接入華為云平臺,這里可以看到MQTT協(xié)議的地址和端口號等信息。

image-20230423111235524

總結:

端口號:   MQTT (1883)| MQTTS (8883)	
接入地址: a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com

根據(jù)域名地址得到IP地址信息:

Microsoft Windows [版本 10.0.19044.2846]
(c) Microsoft Corporation。保留所有權利。

C:Users11266>ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com

正在 Ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com [121.36.42.100] 具有 32 字節(jié)的數(shù)據(jù):
來自 121.36.42.100 的回復: 字節(jié)=32 時間=37ms TTL=31
來自 121.36.42.100 的回復: 字節(jié)=32 時間=37ms TTL=31
來自 121.36.42.100 的回復: 字節(jié)=32 時間=36ms TTL=31
來自 121.36.42.100 的回復: 字節(jié)=32 時間=37ms TTL=31

121.36.42.100 的 Ping 統(tǒng)計信息:
    數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
    最短 = 36ms,最長 = 37ms,平均 = 36ms

C:Users11266>

image-20230423111213624

MQTT協(xié)議接入端口號有兩個,1883是非加密端口,8883是證書加密端口,單片機無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺。

3.3 創(chuàng)建產(chǎn)品

(1)創(chuàng)建產(chǎn)品

點擊右上角創(chuàng)建產(chǎn)品。

image-20230420181503524

(2)填寫產(chǎn)品信息

根據(jù)自己產(chǎn)品名字填寫,設備類型選擇自定義類型。

(3)添加自定義模型

產(chǎn)品創(chuàng)建完成之后,點擊進入產(chǎn)品詳情頁面,翻到最下面可以看到模型定義。

image-20230420181615129

模型簡單來說: 就是存放設備上傳到云平臺的數(shù)據(jù)。比如:環(huán)境溫度、環(huán)境濕度、環(huán)境煙霧濃度、火焰檢測狀態(tài)圖等等,這些我們都可以單獨創(chuàng)建一個模型保存。

3.4 添加設備

產(chǎn)品是屬于上層的抽象模型,接下來在產(chǎn)品模型下添加實際的設備。添加的設備最終需要與真實的設備關聯(lián)在一起,完成數(shù)據(jù)交互。

(1)注冊設備

點擊右上角注冊設備。

image-20230421091842025

(2)根據(jù)自己的設備填寫

在彈出的對話框里填寫自己設備的信息。根據(jù)自己設備詳細情況填寫。

(3)保存設備信息

創(chuàng)建完畢之后,點擊保存并關閉,得到創(chuàng)建的設備密匙信息。該信息在后續(xù)生成MQTT三元組的時候需要使用。

比如我當前設備的信息如下:

{
    "device_id": "64000697352830580e48df07_dev1",
    "secret": "12345678"
}

3.5 MQTT協(xié)議主題訂閱與發(fā)布

(1)MQTT協(xié)議介紹

當前的設備是采用MQTT協(xié)議與華為云平臺進行通信。

MQTT是一個物聯(lián)網(wǎng)傳輸協(xié)議,它被設計用于輕量級的發(fā)布/訂閱式消息傳輸,旨在為低帶寬和不穩(wěn)定的網(wǎng)絡環(huán)境中的物聯(lián)網(wǎng)設備提供可靠的網(wǎng)絡服務。MQTT是專門針對物聯(lián)網(wǎng)開發(fā)的輕量級傳輸協(xié)議。MQTT協(xié)議針對低帶寬網(wǎng)絡,低計算能力的設備,做了特殊的優(yōu)化,使得其能適應各種物聯(lián)網(wǎng)應用場景。目前MQTT擁有各種平臺和設備上的客戶端,已經(jīng)形成了初步的生態(tài)系統(tǒng)。

MQTT是一種消息隊列協(xié)議,使用發(fā)布/訂閱消息模式,提供一對多的消息發(fā)布,解除應用程序耦合,相對于其他協(xié)議,開發(fā)更簡單;MQTT協(xié)議是工作在TCP/IP協(xié)議上;由TCP/IP協(xié)議提供穩(wěn)定的網(wǎng)絡連接;所以,只要具備TCP協(xié)議棧的網(wǎng)絡設備都可以使用MQTT協(xié)議。 本次設備采用的ESP8266就具備TCP協(xié)議棧,能夠建立TCP連接,所以,配合STM32代碼里封裝的MQTT協(xié)議,就可以與華為云平臺完成通信。

華為云的MQTT協(xié)議接入幫助文檔在這里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

img

業(yè)務流程:

img

(2)華為云平臺MQTT協(xié)議使用限制

描述 限制
支持的MQTT協(xié)議版本 3.1.1
與標準MQTT協(xié)議的區(qū)別 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg
MQTTS支持的安全等級 采用TCP通道基礎 + TLS協(xié)議(最高TLSv1.3版本)
單帳號每秒最大MQTT連接請求數(shù) 無限制
單個設備每分鐘支持的最大MQTT連接數(shù) 1
單個MQTT連接每秒的吞吐量,即帶寬,包含直連設備和網(wǎng)關 3KB/s
MQTT單個發(fā)布消息最大長度,超過此大小的發(fā)布請求將被直接拒絕 1MB
MQTT連接心跳時間建議值 心跳時間限定為30至1200秒,推薦設置為120秒
產(chǎn)品是否支持自定義Topic 支持
消息發(fā)布與訂閱 設備只能對自己的Topic進行消息發(fā)布與訂閱
每個訂閱請求的最大訂閱數(shù) 無限制

(3)主題訂閱格式

幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

image-20221207153310037

對于設備而言,一般會訂閱平臺下發(fā)消息給設備 這個主題。

設備想接收平臺下發(fā)的消息,就需要訂閱平臺下發(fā)消息給設備 的主題,訂閱后,平臺下發(fā)消息給設備,設備就會收到消息。

比如:我創(chuàng)建的設備信息如下

以當前設備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
    
最終的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/messages/down

(4)主題發(fā)布格式

對于設備來說,主題發(fā)布表示向云平臺上傳數(shù)據(jù),將最新的傳感器數(shù)據(jù),設備狀態(tài)上傳到云平臺。

這個操作稱為:屬性上報。

幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html

image-20221207153637391

根據(jù)幫助文檔的介紹, 當前設備發(fā)布主題,上報屬性的格式總結如下:

發(fā)布的主題格式:
$oc/devices/{device_id}/sys/properties/report
 
最終的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
發(fā)布主題時,需要上傳數(shù)據(jù),這個數(shù)據(jù)格式是JSON格式。

上傳的JSON數(shù)據(jù)格式如下:

{
  "services": [
    {
      "service_id": <填服務ID>,
      "properties": {
        "<填屬性名稱1>": <填屬性值>,
        "<填屬性名稱2>": <填屬性值>,
        ..........
      }
    }
  ]
}
根據(jù)JSON格式,一次可以上傳多個屬性字段。 這個JSON格式里的,服務ID,屬性字段名稱,屬性值類型,在前面創(chuàng)建產(chǎn)品的時候就已經(jīng)介紹了,不記得可以翻到前面去查看。

//Up, Down, Left, Right, Stop
    
根據(jù)這個格式,組合一次上傳的屬性數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}

3.6 MQTT三元組

MQTT協(xié)議登錄需要填用戶ID,設備ID,設備密碼等信息,就像我們平時登錄QQ,微信一樣要輸入賬號密碼才能登錄。MQTT協(xié)議登錄的這3個參數(shù),一般稱為MQTT三元組。

接下來介紹,華為云平臺的MQTT三元組參數(shù)如何得到。

(1)MQTT服務器地址

要登錄MQTT服務器,首先記得先知道服務器的地址是多少,端口是多少。

幫助文檔地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home

image-20230411141412090

MQTT協(xié)議的端口支持1883和8883,它們的區(qū)別是:8883 是加密端口更加安全。但是單片機上使用比較困難,所以當前的設備是采用1883端口進連接的。

根據(jù)上面的域名和端口號,得到下面的IP地址和端口號信息: 如果設備支持填寫域名可以直接填域名,不支持就直接填寫IP地址。 (IP地址就是域名解析得到的)

華為云的MQTT服務器地址:117.78.5.125
華為云的MQTT端口號:1883

(2)生成MQTT三元組

華為云提供了一個在線工具,用來生成MQTT鑒權三元組: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打開這個工具,填入設備的信息(也就是剛才創(chuàng)建完設備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。

下面是打開的頁面:

image-20221207154917230

填入設備的信息: (上面兩行就是設備創(chuàng)建完成之后保存得到的)

直接得到三元組信息。

image-20230302140101884

image-20230302140153743

得到三元組之后,設備端通過MQTT協(xié)議登錄鑒權的時候,填入?yún)?shù)即可。

DeviceId      64000697352830580e48df07_dev1
DeviceSecret  12345678
ClientId      64000697352830580e48df07_dev1_0_0_2023030206
Username      64000697352830580e48df07_dev1
Password      a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449

3.7 模擬設備登錄測試

經(jīng)過上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設備來登錄平臺。測試與服務器通信是否正常。

(1)填入登錄信息

打開MQTT客戶端軟件,對號填入相關信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發(fā)布主題。

image-20230302142915034

(2)打開網(wǎng)頁查看

完成上面的操作之后,打開華為云網(wǎng)頁后臺,可以看到設備已經(jīng)在線了。

到此,云平臺的部署已經(jīng)完成,設備已經(jīng)可以正常上傳數(shù)據(jù)了。

(3)MQTT登錄測試參數(shù)總結

IP地址:117.78.5.125
端口號:1883
DeviceId      64000697352830580e48df07_dev1
DeviceSecret  12345678
ClientId      64000697352830580e48df07_dev1_0_0_2023030206
Username      64000697352830580e48df07_dev1
Password      a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449
訂閱主題:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down
發(fā)布主題:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
發(fā)布的消息:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}

四、Android手機APP開發(fā)

4.1 開發(fā)環(huán)境介紹

在當前項目中,用于遠程遙控安卓小車的Android手機APP是基于Qt框架開發(fā)的。Qt是一個功能強大且高度靈活的跨平臺應用程序開發(fā)框架,特別適合構建具有豐富圖形用戶界面(GUI)的應用程序,同時也支持開發(fā)非GUI程序。在開發(fā)這款遠程遙控APP時,Qt的優(yōu)勢在于其跨平臺性,使得同一份代碼可以輕松部署在不同操作系統(tǒng)平臺上,包括Android。

Android開發(fā)必備的工具鏈包括:Java JDK  、Android SDK 、Android NDK。

NDK下載地址:https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip
SDK下載: https://www.androiddevtools.cn/
JDK下載地址:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html

image-20240308101952890

image-20240308102004096

4.2 ffmpeg介紹

說起ffmpeg,只要是搞音視頻相關的開發(fā)應該都是聽過的。FFmpeg提供了非常先進的音頻/視頻編解碼庫,并且支持跨平臺。

現(xiàn)在互聯(lián)網(wǎng)上ffmpeg相關的文章、教程也非常的多,ffmpeg本身主要是用來對視頻、音頻進行解碼、編碼,對音視頻進行處理。

其中主要是解碼和編碼。 解碼的應用主要是視頻播放器制作、音樂播放器制作,解碼視頻文件得到視頻畫面再渲染顯示出來就是播放器的基本模型了。 編碼主要是用于視頻錄制保存,就是將攝像頭的畫面或者屏幕的畫面編碼后寫入文件保存為視頻,比如:行車記錄儀錄制視頻,監(jiān)控攝像頭錄制視頻等等。 當然也可以編碼推流到服務器,現(xiàn)在的直播平臺、智能家居里的視頻監(jiān)控、智能安防攝像頭都是這樣的應用。

在本項目里,通過ffmpeg技術將手機采集的視頻圖像編碼后,推流到搭建好的流媒體服務器,實現(xiàn)遠程監(jiān)控。

4.3 Linux下編譯安裝ffmpeg

(1)安裝依賴項:

sudo apt-get update
sudo apt-get install build-essential nasm yasm cmake libx264-dev libx265-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libssl-dev

(2)下載FFmpeg源碼:

wget https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.gz
tar -zxvf ffmpeg-x.y.z.tar.gz
cd ffmpeg-x.y.z

注意替換 4.2.2 為實際的版本號。

(3)配置編譯選項:

./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-nonfree

如果需要其他編碼器或功能,可以根據(jù)需要添加或修改配置選項。

(4)編譯和安裝:

make -j$(nproc)
sudo make install

-j$(nproc) 表示使用多個CPU核心進行并行編譯,可以根據(jù)實際情況調整。

(5)完成后,可以通過運行以下命令來驗證FFmpeg是否正確安裝:

ffmpeg -version

這樣就完成了在Linux下編譯FFmpeg源碼的過程。

4.4 Qt攝像頭采集

下面代碼實現(xiàn),通過子線程采集攝像頭畫面,并通過信號槽機制將圖像傳遞給主線程顯示。

// mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QCamera>
#include <QCameraViewfinder>
#include <QCameraImageCapture>
#include <QThread>

class CameraThread;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_start_clicked();
    void on_pushButton_stop_clicked();
    void onNewImageAvailable(const QImage &image);

private:
    Ui::MainWindow *ui;
    QCamera *camera;
    QCameraViewfinder *viewfinder;
    QCameraImageCapture *imageCapture;
    CameraThread *cameraThread;
};

#endif // MAINWINDOW_H


// mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>

class CameraThread : public QThread
{
    Q_OBJECT

public:
    explicit CameraThread(QObject *parent = nullptr);

signals:
    void newImageAvailable(const QImage &image);

protected:
    void run() override;

private:
    QCamera *camera;
    QCameraImageCapture *imageCapture;
};

CameraThread::CameraThread(QObject *parent) : QThread(parent)
{
    camera = new QCamera(this);
    imageCapture = new QCameraImageCapture(camera, this);
}

void CameraThread::run()
{
    camera->setCaptureMode(QCamera::CaptureStillImage);
    camera->start();

    connect(imageCapture, &QCameraImageCapture::imageCaptured, this, [&](int id, const QImage &preview) {
        emit newImageAvailable(preview);
    });

    exec();
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    camera = new QCamera(this);
    viewfinder = new QCameraViewfinder(this);
    imageCapture = new QCameraImageCapture(camera, this);
    cameraThread = new CameraThread(this);

    cameraThread->start();

    connect(cameraThread, &CameraThread::newImageAvailable, this, &MainWindow::onNewImageAvailable);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_start_clicked()
{
    camera->setViewfinder(viewfinder);
    camera->start();
    ui->verticalLayout->addWidget(viewfinder);
}

void MainWindow::on_pushButton_stop_clicked()
{
    camera->stop();
    ui->verticalLayout->removeWidget(viewfinder);
    viewfinder->deleteLater();
}

void MainWindow::onNewImageAvailable(const QImage &image)
{
    // 在這里處理接收到的圖像,例如將其顯示在 QLabel 上
    ui->label_image->setPixmap(QPixmap::fromImage(image));
}

// main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

(1)CameraThread 類繼承自 QThread,在子線程中負責采集攝像頭畫面。在 run() 方法中,首先創(chuàng)建了一個 QCamera 對象和一個 QCameraImageCapture 對象,然后設置攝像頭的捕獲模式為靜態(tài)圖像,啟動攝像頭。通過連接 imageCaptureimageCaptured 信號到 lambda 函數(shù),當捕獲到新圖像時,將圖像通過自定義信號 newImageAvailable 發(fā)送出去。

(2)MainWindow 類是程序的主窗口,其中包含了攝像頭的視圖控件、開始按鈕、停止按鈕以及用于顯示圖像的標簽。在構造函數(shù)中,創(chuàng)建了攝像頭對象、視圖控件對象、圖像捕獲對象,并創(chuàng)建了一個 CameraThread 對象作為子線程來處理攝像頭畫面的采集。

(3)當用戶點擊開始按鈕時,調用 on_pushButton_start_clicked() 槽函數(shù),將攝像頭視圖控件添加到界面上并啟動攝像頭,開始顯示攝像頭畫面。

(4)當用戶點擊停止按鈕時,調用 on_pushButton_stop_clicked() 槽函數(shù),停止攝像頭捕獲并移除視圖控件。

(5)當子線程采集到新的圖像時,通過 onNewImageAvailable() 槽函數(shù)接收到圖像,并在標簽上顯示該圖像。

(6)main.cpp 文件是程序的入口,創(chuàng)建了 QApplication 對象和 MainWindow 對象,并執(zhí)行主事件循環(huán)。

4.6 ffmpeg視頻編碼推流代碼

使用Qt(C++)結合FFmpeg庫來采集攝像頭畫面,進行編碼,并通過子線程將視頻推送到RTMP流媒體服務器。

// mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

extern "C" {
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

class VideoCaptureThread;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_start_clicked();
    void on_pushButton_stop_clicked();
    void onNewFrameAvailable(const QImage &frame);

private:
    Ui::MainWindow *ui;
    VideoCaptureThread *videoCaptureThread;
};

#endif // MAINWINDOW_H


// mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

class VideoCaptureThread : public QThread
{
    Q_OBJECT

public:
    explicit VideoCaptureThread(QObject *parent = nullptr);
    ~VideoCaptureThread();

protected:
    void run() override;

signals:
    void newFrameAvailable(const QImage &frame);

private:
    AVFormatContext *formatContext;
    AVCodecContext *codecContext;
    AVStream *videoStream;
    SwsContext *swsContext;
    bool stop;
};

VideoCaptureThread::VideoCaptureThread(QObject *parent) : QThread(parent), stop(false)
{
    avformat_network_init();

    formatContext = avformat_alloc_context();
    AVInputFormat *inputFormat = av_find_input_format("dshow");
    avformat_open_input(&formatContext, "video=YourCameraDevice", inputFormat, NULL);

    // 省略了初始化視頻編碼器的部分,需要根據(jù)實際情況添加

    swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
                                codecContext->width, codecContext->height, AV_PIX_FMT_RGB32,
                                SWS_BICUBIC, NULL, NULL, NULL);
}

VideoCaptureThread::~VideoCaptureThread()
{
    stop = true;
    wait();

    sws_freeContext(swsContext);
    avcodec_free_context(&codecContext);
    avformat_close_input(&formatContext);
    avformat_free_context(formatContext);
}

void VideoCaptureThread::run()
{
    while (!stop) {
        AVPacket packet;
        av_init_packet(&packet);
        if (av_read_frame(formatContext, &packet) >= 0) {
            // 省略了視頻編碼的部分,需要根據(jù)實際情況添加

            QImage frameImage(codecContext->width, codecContext->height, QImage::Format_RGB32);
            sws_scale(swsContext, codecContext->coded_frame->data, codecContext->coded_frame->linesize,
                      0, codecContext->height, reinterpret_cast<uint8_t **>(frameImage.bits()), frameImage.bytesPerLine());

            emit newFrameAvailable(frameImage);
        }
        av_packet_unref(&packet);
    }
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    videoCaptureThread = new VideoCaptureThread(this);
    connect(videoCaptureThread, &VideoCaptureThread::newFrameAvailable, this, &MainWindow::onNewFrameAvailable);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_start_clicked()
{
    videoCaptureThread->start();
}

void MainWindow::on_pushButton_stop_clicked()
{
    videoCaptureThread->quit();
}

void MainWindow::onNewFrameAvailable(const QImage &frame)
{
    // 將QImage轉換為AVFrame
    AVFrame *avFrame = av_frame_alloc();
    avFrame->width = frame.width();
    avFrame->height = frame.height();
    avFrame->format = AV_PIX_FMT_RGB32;
    av_frame_get_buffer(avFrame, 0);
    for (int y = 0; y < frame.height(); ++y) {
        memcpy(avFrame->data[0] + y * avFrame->linesize[0], frame.scanLine(y), frame.width() * 4);
    }

    // 編碼視頻幀
    AVPacket packet;
    av_init_packet(&packet);
    int ret = avcodec_send_frame(codecContext, avFrame);
    if (ret < 0) {
        qDebug() << "Failed to send frame to encoder";
        return;
    }
    while (ret >= 0) {
        ret = avcodec_receive_packet(codecContext, &packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            qDebug() << "Error during encoding";
            return;
        }

        // 推送到RTMP服務器
        RTMP *rtmp = RTMP_Alloc();
        RTMP_Init(rtmp);
        if (!RTMP_SetupURL(rtmp, "rtmp://your_rtmp_server_url")) {
            qDebug() << "Failed to set RTMP URL";
            RTMP_Close(rtmp);
            RTMP_Free(rtmp);
            return;
        }
        RTMP_EnableWrite(rtmp);
        if (!RTMP_Connect(rtmp, NULL)) {
            qDebug() << "Failed to connect to RTMP server";
            RTMP_Close(rtmp);
            RTMP_Free(rtmp);
            return;
        }
        if (!RTMP_ConnectStream(rtmp, 0)) {
            qDebug() << "Failed to connect to RTMP stream";
            RTMP_Close(rtmp);
            RTMP_Free(rtmp);
            return;
        }

        packet.stream_index = videoStream->index;
        ret = RTMP_SendPacket(rtmp, reinterpret_cast<char*>(packet.data), packet.size, TRUE);
        if (ret < 0) {
            qDebug() << "Failed to send packet to RTMP server";
            RTMP_Close(rtmp);
            RTMP_Free(rtmp);
            return;
        }

        av_packet_unref(&packet);
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }

    av_frame_free(&avFrame);
}

代碼中創(chuàng)建了一個 VideoCaptureThread 類作為子線程,負責采集攝像頭畫面并進行視頻編碼。在 run() 方法中,利用FFmpeg庫讀取攝像頭畫面,進行視頻編碼,并通過自定義信號 newFrameAvailable 發(fā)送每一幀圖像。主界面中的 MainWindow 類負責開始和停止視頻采集線程,并處理接收到的視頻幀,可以在 onNewFrameAvailable() 槽函數(shù)中將視頻幀編碼為RTMP流并推送到服務器。將QImage轉換為AVFrame,使用avcodec_send_frameavcodec_receive_packet函數(shù)對視頻幀進行編碼。創(chuàng)建一個RTMP連接,并將編碼后的視頻包發(fā)送到RTMP服務器。

五、STM32端代碼設計

STM32端的代碼主要是控制小車的移動,代碼比較少。

5.1 STM32小車底座驅動代碼

#include "stm32f10x.h"

#define MOTOR1_PIN1 GPIO_Pin_0
#define MOTOR1_PIN2 GPIO_Pin_1
#define MOTOR2_PIN1 GPIO_Pin_2
#define MOTOR2_PIN2 GPIO_Pin_3
#define MOTOR3_PIN1 GPIO_Pin_4
#define MOTOR3_PIN2 GPIO_Pin_5
#define MOTOR4_PIN1 GPIO_Pin_6
#define MOTOR4_PIN2 GPIO_Pin_7

void delay_ms(uint32_t ms) {
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 7200; j++);
}

void motor_init() {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void forward() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
    GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
}

void backward() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
    GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
}

void left() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
    GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
}

void right() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
    GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
}

int main(void) {
    motor_init();
    
    while (1) {
        forward();
        delay_ms(1000);
        
        backward();
        delay_ms(1000);
        
        left();
        delay_ms(1000);
        
        right();
        delay_ms(1000);
    }
}

5.2 小車控制代碼

#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>

#define MOTOR1_PIN1 GPIO_Pin_0
#define MOTOR1_PIN2 GPIO_Pin_1
#define MOTOR2_PIN1 GPIO_Pin_2
#define MOTOR2_PIN2 GPIO_Pin_3
#define MOTOR3_PIN1 GPIO_Pin_4
#define MOTOR3_PIN2 GPIO_Pin_5
#define MOTOR4_PIN1 GPIO_Pin_6
#define MOTOR4_PIN2 GPIO_Pin_7

void delay_ms(uint32_t ms) {
    uint32_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 7200; j++);
}

void motor_init() {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void forward() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
    GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
}

void backward() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
    GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
}

void left() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
    GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
}

void right() {
    GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
    GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
}

void usart_init() {
    USART_InitTypeDef USART_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void usart_send(USART_TypeDef* USARTx, uint8_t data) {
    while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
    USART_SendData(USARTx, data);
}

uint8_t usart_receive(USART_TypeDef* USARTx) {
    while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);
    return USART_ReceiveData(USARTx);
}

void usart_puts(USART_TypeDef* USARTx, char* str) {
    while (*str) {
        usart_send(USARTx, *str++);
    }
}

void control(char* cmd) {
    if (strcmp(cmd, "forward") == 0) {
        forward();
        usart_puts(USART1, "OKn");
    } else if (strcmp(cmd, "backward") == 0) {
        backward();
        usart_puts(USART1, "OKn");
    } else if (strcmp(cmd, "left") == 0) {
        left();
        usart_puts(USART1, "OKn");
    } else if (strcmp(cmd, "right") == 0) {
        right();
        usart_puts(USART1, "OKn");
    } else {
        usart_puts(USART1, "Invalid commandn");
    }
}

int main(void) {
    motor_init();
    usart_init();
    
    while (1) {
        char cmd[10];
        memset(cmd, 0, sizeof(cmd));
        int i = 0;
        while (1) {
            char c = usart_receive(USART1);
            if (c == 'r' || c == 'n') {
                break;
            }
            cmd[i++] = c;
        }
        control(cmd);
    }
}

六、關于Android手機USB通信的問題

在Qt中開發(fā)Android手機APP并利用USB線進行串口通信,需要啟用權限。

(1)添加權限:在AndroidManifest.xml文件中添加USB權限,并在Qt項目中的Android配置文件中聲明需要的權限。例如,在AndroidManifest.xml中添加以下代碼:

<uses-permission android:name="android.permission.USB_PERMISSION" />

(2)檢測USB連接:通過Qt的Android JNI接口(Java Native Interface)來檢測USB設備的插拔狀態(tài),并獲取USB設備的信息。

(3)打開和關閉USB串口:使用Qt的QSerialPort類來打開和關閉USB串口,并進行數(shù)據(jù)的讀寫操作??梢酝ㄟ^檢測到的USB設備路徑來打開對應的串口。

(4)處理串口數(shù)據(jù):接收到的串口數(shù)據(jù)可以通過信號槽機制或者其他方式傳遞給界面進行顯示或進一步處理。

下面是測試的代碼:

#include <QSerialPort>
#include <QSerialPortInfo>

void detectUsbDevices() {
    QList<QSerialPortInfo> usbDevices = QSerialPortInfo::availablePorts();
    
    foreach (const QSerialPortInfo &info, usbDevices) {
        qDebug() << "USB Device Name: " << info.portName();
        qDebug() << "Description: " << info.description();
    }
}

void openUsbSerialPort(const QString &portName) {
    QSerialPort serialPort;
    serialPort.setPortName(portName);
    serialPort.setBaudRate(QSerialPort::Baud9600);
    
    if (serialPort.open(QIODevice::ReadWrite)) {
        qDebug() << "USB Serial Port opened successfully!";
        
        // Read or write data here
        
        serialPort.close();
    } else {
        qDebug() << "Failed to open USB Serial Port!";
    }
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    // Detect USB devices
    detectUsbDevices();
    
    // Open USB serial port
    openUsbSerialPort("/dev/ttyUSB0"); // Replace with the actual port name
    
    return app.exec();
}

七、總結

本文詳細介紹了一款創(chuàng)新且環(huán)保的基于4G網(wǎng)絡設計的遠程遙控安卓小車系統(tǒng)的開發(fā)與實現(xiàn)過程。該項目巧妙地將被淘汰的安卓舊手機升級轉化為車載信息處理單元,賦予其新的生命力,同時融入先進的4G網(wǎng)絡技術、流媒體服務以及物聯(lián)網(wǎng)技術,成功打造出一個集遠程操控與實時音視頻傳輸功能于一身的高效率解決方案。

智能小車以其獨特的設計思路和強大的功能特性,展現(xiàn)出廣泛的應用潛力。無論是作為教育科研領域的實踐平臺,還是在遠程監(jiān)控、工業(yè)巡檢、應急救援、無人駕駛技術驗證,甚至智能家居與物流等方面,均能發(fā)揮重要作用,顯著提升了工作效率,降低了人力成本,并有效保障了作業(yè)的安全性。

該項目積極響應可持續(xù)發(fā)展號召,通過資源循環(huán)利用,成功展示了科技如何助力環(huán)保,彰顯了技術創(chuàng)新的社會價值。展望未來,隨著5G網(wǎng)絡技術的廣泛應用,這款基于4G網(wǎng)絡的遠程遙控安卓小車將進一步優(yōu)化性能,拓展應用場景,為社會各領域帶來更加智能化、便捷化的技術服務。

  • 更多詳細資料請聯(lián)系.docx
    下載
意法半導體

意法半導體

意法半導體(ST)集團于1987年6月成立,是由意大利的SGS微電子公司和法國Thomson半導體公司合并而成。1998年5月,SGS-THOMSON Microelectronics將公司名稱改為意法半導體有限公司。意法半導體是世界最大的半導體公司之一,公司銷售收入在半導體工業(yè)五大高速增長市場之間分布均衡(五大市場占2007年銷售收入的百分比):通信(35%),消費(17%),計算機(16%),汽車(16%),工業(yè)(16%)。 據(jù)最新的工業(yè)統(tǒng)計數(shù)據(jù),意法半導體是全球第五大半導體廠商,在很多市場居世界領先水平。例如,意法半導體是世界第一大專用模擬芯片和電源轉換芯片制造商,世界第一大工業(yè)半導體和機頂盒芯片供應商,而且在分立器件、手機相機模塊和車用集成電路領域居世界前列.

意法半導體(ST)集團于1987年6月成立,是由意大利的SGS微電子公司和法國Thomson半導體公司合并而成。1998年5月,SGS-THOMSON Microelectronics將公司名稱改為意法半導體有限公司。意法半導體是世界最大的半導體公司之一,公司銷售收入在半導體工業(yè)五大高速增長市場之間分布均衡(五大市場占2007年銷售收入的百分比):通信(35%),消費(17%),計算機(16%),汽車(16%),工業(yè)(16%)。 據(jù)最新的工業(yè)統(tǒng)計數(shù)據(jù),意法半導體是全球第五大半導體廠商,在很多市場居世界領先水平。例如,意法半導體是世界第一大專用模擬芯片和電源轉換芯片制造商,世界第一大工業(yè)半導體和機頂盒芯片供應商,而且在分立器件、手機相機模塊和車用集成電路領域居世界前列.收起

查看更多

相關推薦