前言
智能貓眼是一種家居安防產(chǎn)品。是安裝在防盜門上的一種嵌入式設備,可以通過攝像頭獲取圖像顯示至手機應用中,這樣老人或者小孩就可以看清門外的情況。
智能貓眼的實現(xiàn)是采用流媒體協(xié)議 RTSP。該協(xié)議定義了程序如何通過 IP 網(wǎng)絡傳送多媒體數(shù)據(jù)。RTSP 多用于安防攝像頭、車載監(jiān)控、網(wǎng)絡直播等場景應用。本文檔旨在講解在 OpenAtom OpenHarmony(以下簡稱“OpenHarmony") 1.0.1 release 下將 Hi3518EV300 編碼后的 H.265 視頻格式(H.265 是一種視頻編碼格式,可以由 OpenHarmony 媒體子系統(tǒng)產(chǎn)生),通過 RTSP 傳輸顯示到手機的應用中。

如上圖片:Hi3518EV300 設備將捕獲到的圖像通過 RTSP 發(fā)送到手機應用中并顯示出來。
開發(fā)流程
RTSP 采用 Server/Client 模式,在本樣例場景中 Hi3518EV300為RTSP Server,手機應用為 RTSP Client。在 RTSP 體系結構包含 RTSP和RTP(實時傳輸協(xié)議)兩種協(xié)議,其中 RTSP 協(xié)議用于建立連接與傳輸多媒體控制命令(開始、暫停、結束等),RTP 協(xié)議用來傳輸多媒體數(shù)據(jù)(音頻、視頻)。
RTSP Server 的實現(xiàn)分為如下幾步:
●設置 Wi-Fi:將手機與 Hi3518EV300 在同一網(wǎng)絡中;
●環(huán)形緩存區(qū):將媒體子系統(tǒng)中編碼出的 H.265 數(shù)據(jù)存入環(huán)形緩存中;
●RTSP:RTSP Server 通過 RTSP 與 RTSP Client 交互控制信息;
●RTP :RTSP Server 收到PLAY命令后從環(huán)形緩存中獲取 H.265 數(shù)據(jù)并使用 RTP 協(xié)議發(fā)送。
如下圖所示:

如何運行 RTSP Server 可以參考文章智能貓眼 3518 開發(fā)樣例,下面根據(jù)該文章講解 RTSP Server 的實現(xiàn)流程。
代碼結構:
├── smart_door_viewer_3518│ ├── BUILD.gn // 編譯構建│ ├── include│ │ ├── camera_sample.h // 攝像頭操作頭文件│ │ ├── rtp.h // rtp協(xié)議傳輸頭文件│ │ ├── rtsp_log.h // 打印調試頭文件│ │ └── rtsp_server.h // rtsp頭文件│ └── src│ ├── camera_sample.cpp // 攝像頭實現(xiàn)│ ├── main.cpp // 主函數(shù)│ ├── rtp.cpp // rtp協(xié)議實現(xiàn)│ └── rtsp_server.cpp // rtsp協(xié)議實現(xiàn)├── foundation│ └── multimedia│ └── media_lite│ ├── frameworks│ │ └── recorder_lite│ │ ├── recorder.cpp //增加獲取攝像頭H.265數(shù)據(jù)實現(xiàn)類接口│ │ ├── recorder_impl.cpp //增加獲取攝像頭H.265數(shù)據(jù)實現(xiàn)│ │ └── recorder_impl.h //增加獲取攝像頭H.265數(shù)據(jù)實現(xiàn)定義│ └── interfaces│ └── kits│ └── recorder_lite│└──recorder.h//增加應用層獲取攝像頭H.265數(shù)據(jù)實現(xiàn)類接口定義
設置Wi-Fi
設置 Wi-Fi 連接熱點 ssid 為“Smedia”psk為“12345678”。
在文件 wpa_supplicant.conf 中修改如下:
country=GBctrl_interface=udpnetwork={ssid="SMedia"psk="12345678"}
設備啟動后輸入:
./bin/wpa_supplicant -iwlan0 -c/etc/wpa_supplicant.conf
輸入 ifconfig 可查看到連接成功后的 IP 地址:

環(huán)形緩存區(qū)
在媒體子系統(tǒng)中,為了同步 RTSP Server 應用獲取 H.265 數(shù)據(jù)須設計一個環(huán)形緩沖區(qū)。緩沖區(qū)總大小為 16*256K 長度的數(shù)組。put 為媒體子系統(tǒng)存放緩沖區(qū)的偏移值,get 為 RTSP Server(Hi3518EV300)線程獲取緩沖區(qū)的偏移值,緩存區(qū)定義在文件 recorder_impl.h 下。
constexpr uint32_t RING_BUFF_MAX_CNT = 16;constexpruint32_tRING_BUFF_SIZE=256*1024;
具體實現(xiàn)如下:
初始情況下偏移值 put 與 get 的位置均在開頭。

當 RTSP Server 啟動后媒體子系統(tǒng)填充 buff,偏移值 put 向前移。

RTSP Server 通過偏移值 get 獲取到視頻編碼數(shù)據(jù)后釋放 buff,偏移值 get 向前移。

當 put 與 get 偏移超過 16 時重新置 1 因此形象地稱為環(huán)形緩沖區(qū),其中 get 永遠在 put 后且間距不會超過 3 個 buff,實現(xiàn)是在 rtsp Server 中設置同步時間。

代碼實現(xiàn)邏輯:當 RTSP Server 運行到 RTP 時才會往緩沖區(qū)存放數(shù)據(jù)(ringStatus 標志位設置為 true)。存入緩沖區(qū)的首幀是從關鍵幀(幀頭為 0x40 與 0x01 與 startFramFlag 標志位為 true)開始,后續(xù)所有幀都會保存到緩沖區(qū)中(saveFlag 標志位設置為 true,startFramFlag 標志位為 false),在函數(shù) VideoSourceProcess 下實現(xiàn)。
if ((iNumber < RING_BUFF_MAX_CNT) && (ringStatus == true)) {if((startFramFlag == true) &&(buffer.dataAddr[4]==0x40)&& (buffer.dataAddr[5]==0x01)) {if (memcpy_s(ringFifo[iPut].buffer, RING_BUFF_SIZE, buffer.dataAddr, buffer.dataLen) != EOK) {MEDIA_INFO_LOG("[Error] memcpy_s");} else {ringFifo[iPut].size = buffer.dataLen;iPut = addring(iPut);iNumber++;startFramFlag = false;saveFlag = true;}} else {if(saveFlag == true) {if (memcpy_s(ringFifo[iPut].buffer, RING_BUFF_SIZE, buffer.dataAddr, buffer.dataLen) != EOK) {MEDIA_INFO_LOG("[Error] memcpy_s");} else {ringFifo[iPut].size = buffer.dataLen;iPut = addring(iPut);iNumber++;}}}}
RTSP
RTSP Server 與 RTSP Client 通過 RTSP 協(xié)議收發(fā)控制命令,其基本流程如下:
●OPTION:首先 Client 連接到 Server 并發(fā)送 OPTION 命令,Server 立刻返回所支持的命令(OPTION、DESCRIBE、SETUP、PLAY、TEARDOWN);
●DESCRIBE:Client 發(fā)送描述命令(DESCRIBE),Server 通過一個 SDP 描述來進行反饋,反饋信息包括流數(shù)量、媒體類型等信息;
●SETUP:Client 分析 SDP 描述,并為會話中發(fā)送建立命令(SETUP),告訴 Server 用于接收媒體數(shù)據(jù)的端口;
●PLAY:連接建立完成后,Client 發(fā)送一個播放命令(PLAY),Server 就開始在 UDP 上傳送媒體流(RTP包)到 Client;
●TERADOWN:最后 Client 可發(fā)送一個終止命令(TERADOWN)來結束流媒體會話。
其交互流程如下所示:

在文件 rtsp_server.cpp 中,RTSP Server 收到 OPTION 后回復服務器提供的可用命令(OPTION、DESCRIBE、SETUP、PLAY、TEARDOWN)。
函數(shù)實現(xiàn)如下:
static void RtspOptions(char* sendBuff, RtspClientInfo &rtspCliInfo){sprintf(sendBuff, "RTSP/1.0 200 OK ""CSeq: %d ""Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN "" ",rtspCliInfo.rtspCseq);}
RTSP Server 收到 DESCRIBE 后回復 SDP (SDP 信息為會話名稱和目的、會話持續(xù)時間、媒體類(音頻、視頻等)、傳輸協(xié)議(RTP/UDP/IP等)、媒體編碼格式(H.264、H.265 等)、接收媒體的相關信息端口和格式等。)信息。
函數(shù)實現(xiàn)如下:
static void RtspDescribe(char* sendBuff, RtspClientInfo &rtspCliInfo){char sdp[512];memset(sdp, 0, sizeof(sdp));sprintf(sdp, "v=0 ""o=- 973 1 IN IP4 192.168.1.103 ""t=0 0 ""a=control:* ""m=video 0 RTP/AVP 96 ""a=rtpmap:96 H265/90000 ""a=control:track0 ");sprintf(sendBuff, "RTSP/1.0 200 OK CSeq: %d ""Content-Base: %s ""Content-type: application/sdp ""Content-length: %d ""%s",rtspCliInfo.rtspCseq,"rtsp://192.168.1.127:8554/test.264",strlen(sdp),sdp);}
RTSP Server 收到 SETUP 后回復傳輸模式(采用 RTP 傳輸)、端口號信息準備 play。
函數(shù)實現(xiàn)如下:
static void RtspStep(char* sendBuff, RtspClientInfo &rtspCliInfo){sprintf(sendBuff,"RTSP/1.0 200 OK ""CSeq: %d ""Transport: RTP/AVP;unicast;client_port=55532-55532;""server_port=%d-%d ""Session: 66334873 "" ",rtspCliInfo.rtspCseq, rtspCliInfo.clientPort, rtspCliInfo.clientPort + 1);}
RTSP Server 收到 PLAY 后回復 Range 的值為"npt=0.0000-",表示從開始播放,默認一直播放!隨后發(fā)送視頻流數(shù)據(jù)。
static void RtspPlay(char* sendBuff, RtspClientInfo &rtspCliInfo){sprintf(sendBuff, "RTSP/1.0 200 OK ""CSeq: %d ""Range: npt=0.000- ""Session: 66334873; timeout=60 ",rtspCliInfo.rtspCseq);}
程序運行后使用 wireshark 抓取報文如下:

RTP
RTSP 會話進行到 PLAY 后就可啟動 RTP 發(fā)送視頻流數(shù)據(jù),RTP 包分為 RtpHeader(Rtp 頭)加 payload(負載數(shù)據(jù)),在文件 rtp.cpp 下的 UdpSendFrame 函數(shù)中。
RtpHeader
●csrcLen csrc 計數(shù),在沒有 RTP 混頻器的情況下通常為 0
●extension 擴展名,必須為 0
●padding 填充位,不得使用填充,默認為 0
●version 版本號為 2
●payloadType 數(shù)據(jù)幀類型 96(H.265)
●marker 將一幀分片時區(qū)分頭片
●seq 序列號為了以每片為單位
●timestamp 時間戳以每幀為單位
●ssrc 數(shù)據(jù)信源號
rtpPacket.rtpHeader.csrcLen = 0;rtpPacket.rtpHeader.extension = 0;rtpPacket.rtpHeader.padding = 0;rtpPacket.rtpHeader.version = 2;rtpPacket.rtpHeader.payloadType = 96;rtpPacket.rtpHeader.ssrc = 10;rtpPacket.rtpHeader.timestamp = timestamp;timestamp+=90000/25;
payload
RTP 包最大為 1400 個字節(jié),因此打包分為兩種:
1.若 H.265 幀小于 1400 個字節(jié)時可放至一個 rtp 包中;
2.若 H.265 幀大于 1400 個字節(jié)時,則需要分片打包在多個 rtp 中;
當文件小于 1400 時直接放到 pyahload 中發(fā)送。
if (s32NalBufSize <= RTP_MAX_PKT_SIZE) {if (memcpy_s(rtpPacket.payload, s32NalBufSize, pNalBuf, s32NalBufSize) != EOK){SAMPLE_INFO("memcpy_s");return -1;}rtpPacket.rtpHeader.marker = 1;rtpPacket.rtpHeader.seq = seq++;ret = UdpSendPacket(&rtpPacket, s32NalBufSize);sendBytes += ret;SAMPLE_INFO("sendBytes->%d", sendBytes);}
若 H.265 幀大于 1400 個字節(jié)時就必須進行分片封包處理。則要設置 PayloadHdr、FU(Fragmentation Units)、DONL 暫不涉及可以省略,其中 PayloadHdr 固定為 49。
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| PayloadHdr (Type=49) | FU header | DONL (cond) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|| DONL (cond) | ||-+-+-+-+-+-+-+-+ ||FUpayload|
FUheader 格式為:S 置 1 表示起始片,E 置 1 表示最后片,F(xiàn)uType 就是實際的 Nal type 類型。
+---------------+|0|1|2|3|4|5|6|7|+-+-+-+-+-+-+-+-+|S|E| FuType |+---------------+
函數(shù)中實現(xiàn)如下:
int pktNum = s32NalBufSize / RTP_MAX_PKT_SIZE; int remainPktSize = s32NalBufSize % RTP_MAX_PKT_SIZE; int i, pos, head_len; head_len = 2; pos = head_len; for(i = 0; i < pktNum; i++) { rtpPacket.rtpHeader.seq = seq++; rtpPacket.payload[0] = 49 << 1; rtpPacket.payload[1] = 1; rtpPacket.payload[2] = (naluType & 0x7E)>>1; if (i == 0) { rtpPacket.rtpHeader.marker = 1; rtpPacket.payload[2] |= 0x80; // start } else if (remainPktSize == 0 && i == (pktNum - 1)){ rtpPacket.rtpHeader.marker = 0; rtpPacket.payload[2] |= 0x40; // end } if (memcpy_s(rtpPacket.payload + head_len + 1, RTP_MAX_PKT_SIZE, pNalBuf+pos, RTP_MAX_PKT_SIZE) != EOK) { SAMPLE_INFO("memcpy_s"); return -1; } ret = UdpSendPacket(&rtpPacket, RTP_MAX_PKT_SIZE + head_len + 1); if (ret < 0) { SAMPLE_ERROR("rtpSendPacket is error"); goto cleanup; } sendBytes += ret; pos += RTP_MAX_PKT_SIZE; } if (remainPktSize > 0) { { rtpPacket.payload[0] = 49 << 1; rtpPacket.payload[1] = 1; rtpPacket.payload[2] = (naluType & 0x7E)>>1; rtpPacket.payload[2] |= 0x40; // end } if (memcpy_s(rtpPacket.payload + head_len + 1, remainPktSize, pNalBuf+pos, remainPktSize) != EOK) { SAMPLE_INFO("memcpy_s"); return -1; } rtpPacket.rtpHeader.seq = seq++; ret = UdpSendPacket(&rtpPacket, remainPktSize+head_len+1); if(ret < 0) { SAMPLE_ERROR("rtpSendPacket is error"); goto cleanup; } sendBytes += ret; }
程序運行后使用 wireshark 抓取報文如下:

RTSP Client
RTSP Client 實現(xiàn)使用手機 APP”完美播放器“。
準備一臺手機,在手機應用市場中搜索”完美播放器“并下載安裝。

打開菜單選擇網(wǎng)址播放。

輸入 rtsp 播放地址,其中 ip 地址 10.42.0.54為Hi3518EV300中Wi-Fi 的地址。

總結
豐富多樣的 OpenHarmony 開發(fā)樣例離不開廣大合作伙伴和開發(fā)者的貢獻,如果你也想把自己開發(fā)的樣例分享出來,歡迎把樣例提交到 OpenHarmony 知識體系 SIG 倉來,共建開發(fā)樣例請參考如何共建開發(fā)樣例。
審核編輯 :李倩
-
數(shù)據(jù)
+關注
關注
8文章
7335瀏覽量
94779 -
視頻編碼
+關注
關注
2文章
114瀏覽量
21597 -
OpenHarmony
+關注
關注
33文章
3952瀏覽量
21106
原文標題:基于OpenHarmony實現(xiàn)智能貓眼
文章出處:【微信號:gh_e4f28cfa3159,微信公眾號:OpenAtom OpenHarmony】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
長城汽車榮獲2025流媒體后視鏡產(chǎn)品技術創(chuàng)新獎
RK3576輕松搭建RTMP視頻推流,基于FFmpeg+Nginx協(xié)同
【嘉楠堪智K230開發(fā)板試用體驗】CanMV K230 RTSP推流
中偉視界:解密GB28181流媒體平臺,多模態(tài)AI的強大支撐
【BPI-CanMV-K230D-Zero開發(fā)板體驗】無線網(wǎng)絡攝像頭(RTSP 推流 1080P 60fps)
如何在米爾TI AM62開發(fā)板上部署流媒體服務實現(xiàn)監(jiān)控功能
如何部署流媒體服務實現(xiàn)監(jiān)控功能--基于米爾TI AM62x開發(fā)板
蔚來款新車型搭載遠峰科技超清流媒體內后視鏡
延遲低至30ms+ LLSM流媒體傳輸模塊低延遲方案推薦
微軟Build 2025大會:Copilot Studio升級,引領多智能體協(xié)作時代
用 樹莓派4 打造專屬流媒體控制臺!
LLSM——基于RK3588的低延遲低帶寬流媒體傳輸模塊
智能貓眼的實現(xiàn)是采用流媒體協(xié)議RTSP
評論