前言
智能猫眼是一种家居安防产品。是安装在防盗门上的一种嵌入式设备,可以通过摄像头获取图像显示至手机应用中,这样老人或者小孩就可以看清门外的情况。
智能猫眼的实现是采用流媒体协议 RTSP。该协议定义了程序如何通过 IP 网络传送多媒体数据。RTSP 多用于安防摄像头、车载监控、网络直播等场景应用。本文档旨在讲解在 OpenAtom OpenHarmony(以下简称“OpenHarmony") 1.0.1 release 下将 Hi3518EV300 编码后的 H.265 视频格式(H.265 是一种视频编码格式,可以由 OpenHarmony 媒体子系统产生),通过 RTSP 传输显示到手机的应用中。
注:Hi3518EV300是 润和Hi3518 HiSpark IPC AI摄像头开发板套件
如上图片:Hi3518EV300 设备将捕获到的图像通过 RTSP 发送到手机应用中并显示出来。
开发流程
RTSP 采用 Server/Client 模式,在本样例场景中 Hi3518EV300为RTSP Server,手机应用为 RTSP Client。在 RTSP 体系结构包含 RTSP和RTP(实时传输协议)两种协议,其中 RTSP 协议用于建立连接与传输多媒体控制命令(开始、暂停、结束等),RTP 协议用来传输多媒体数据(音频、视频)。
RTSP Server 的实现分为如下几步:
●设置 Wi-Fi:将手机与 Hi3518EV300 在同一网络中;
●环形缓存区:将媒体子系统中编码出的 H.265 数据存入环形缓存中;
●RTSP:RTSP Server 通过 RTSP 与 RTSP Client 交互控制信息;
●RTP :RTSP Server 收到PLAY命令后从环形缓存中获取 H.265 数据并使用 RTP 协议发送。
如下图所示:
如何运行 RTSP Server 可以参考文章智能猫眼 3518 开发样例,下面根据该文章讲解 RTSP Server 的实现流程。
代码结构:
├── smart_door_viewer_3518
│ ├── BUILD.gn // 编译构建
│ ├── include
│ │ ├── camera_sample.h // 摄像头操作头文件
│ │ ├── rtp.h // rtp协议传输头文件
│ │ ├── rtsp_log.h // 打印调试头文件
│ │ └── rtsp_server.h // rtsp头文件
│ └── src
│ ├── camera_sample.cpp // 摄像头实现
│ ├── main.cpp // 主函数
│ ├── rtp.cpp // rtp协议实现
│ └── rtsp_server.cpp // rtsp协议实现
├── foundation
│ └── multimedia
│ └── media_lite
│ ├── frameworks
│ │ └── recorder_lite
│ │ ├── recorder.cpp //增加获取摄像头H.265数据实现类接口
│ │ ├── recorder_impl.cpp //增加获取摄像头H.265数据实现
│ │ └── recorder_impl.h //增加获取摄像头H.265数据实现定义
│ └── interfaces
│ └── kits
│ └── recorder_lite
│ └── recorder.h //增加应用层获取摄像头H.265数据实现类接口定义
设置Wi-Fi
设置 Wi-Fi 连接热点 ssid 为“Smedia”psk为“12345678”。
在文件 wpa_supplicant.conf 中修改如下:
country=GB
ctrl_interface=udp
network={
ssid="SMedia"
psk="12345678"
}
设备启动后输入:
./bin/wpa_supplicant -iwlan0 -c/etc/wpa_supplicant.conf
输入 ifconfig 可查看到连接成功后的 IP 地址:
环形缓存区
在媒体子系统中,为了同步 RTSP Server 应用获取 H.265 数据须设计一个环形缓冲区。缓冲区总大小为 16*256K 长度的数组。put 为媒体子系统存放缓冲区的偏移值,get 为 RTSP Server(Hi3518EV300)线程获取缓冲区的偏移值,缓存区定义在文件 recorder_impl.h 下。
constexpr uint32_t RING_BUFF_MAX_CNT = 16;
constexpr uint32_t RING_BUFF_SIZE = 256*1024;
具体实现如下:
初始情况下偏移值 put 与 get 的位置均在开头。
当 RTSP Server 启动后媒体子系统填充 buff,偏移值 put 向前移。
RTSP Server 通过偏移值 get 获取到视频编码数据后释放 buff,偏移值 get 向前移。
当 put 与 get 偏移超过 16 时重新置 1 因此形象地称为环形缓冲区,其中 get 永远在 put 后且间距不会超过 3 个 buff,实现是在 rtsp Server 中设置同步时间。
代码实现逻辑:当 RTSP Server 运行到 RTP 时才会往缓冲区存放数据(ringStatus 标志位设置为 true)。存入缓冲区的首帧是从关键帧(帧头为 0x40 与 0x01 与 startFramFlag 标志位为 true)开始,后续所有帧都会保存到缓冲区中(saveFlag 标志位设置为 true,startFramFlag 标志位为 false),在函数 VideoSourceProcess 下实现。
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 协议收发控制命令,其基本流程如下:
●OPTION:首先 Client 连接到 Server 并发送 OPTION 命令,Server 立刻返回所支持的命令(OPTION、DESCRIBE、SETUP、PLAY、TEARDOWN);
●DESCRIBE:Client 发送描述命令(DESCRIBE),Server 通过一个 SDP 描述来进行反馈,反馈信息包括流数量、媒体类型等信息;
●SETUP:Client 分析 SDP 描述,并为会话中发送建立命令(SETUP),告诉 Server 用于接收媒体数据的端口;
●PLAY:连接建立完成后,Client 发送一个播放命令(PLAY),Server 就开始在 UDP 上传送媒体流(RTP包)到 Client;
●TERADOWN:最后 Client 可发送一个终止命令(TERADOWN)来结束流媒体会话。
其交互流程如下所示:
在文件 rtsp_server.cpp 中,RTSP Server 收到 OPTION 后回复服务器提供的可用命令(OPTION、DESCRIBE、SETUP、PLAY、TEARDOWN)。
函数实现如下:
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 信息为会话名称和目的、会话持续时间、媒体类(音频、视频等)、传输协议(RTP/UDP/IP等)、媒体编码格式(H.264、H.265 等)、接收媒体的相关信息端口和格式等。)信息。
函数实现如下:
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。
函数实现如下:
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-",表示从开始播放,默认一直播放!随后发送视频流数据。
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 发送视频流数据,RTP 包分为 RtpHeader(Rtp 头)加 payload(负载数据),在文件 rtp.cpp 下的 UdpSendFrame 函数中。
RtpHeader
●csrcLen csrc 计数,在没有 RTP 混频器的情况下通常为 0
●extension 扩展名,必须为 0
●padding 填充位,不得使用填充,默认为 0
●version 版本号为 2
●payloadType 数据帧类型 96(H.265)
●marker 将一帧分片时区分头片
●seq 序列号为了以每片为单位
●timestamp 时间戳以每帧为单位
●ssrc 数据信源号
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 个字节,因此打包分为两种:
1.若 H.265 帧小于 1400 个字节时可放至一个 rtp 包中;
2.若 H.265 帧大于 1400 个字节时,则需要分片打包在多个 rtp 中;
当文件小于 1400 时直接放到 pyahload 中发送。
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 个字节时就必须进行分片封包处理。则要设置 PayloadHdr、FU(Fragmentation Units)、DONL 暂不涉及可以省略,其中 PayloadHdr 固定为 49。
0 1 2 3
0 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) | |
|-+-+-+-+-+-+-+-+ |
| FU payload |
FUheader 格式为:S 置 1 表示起始片,E 置 1 表示最后片,FuType 就是实际的 Nal type 类型。
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E| FuType |
+---------------+
函数中实现如下:
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 实现使用手机 APP”完美播放器“。
准备一台手机,在手机应用市场中搜索”完美播放器“并下载安装。
打开菜单选择网址播放。
输入 rtsp 播放地址,其中 ip 地址 10.42.0.54为Hi3518EV300中Wi-Fi 的地址。
总结
丰富多样的 OpenHarmony 开发样例离不开广大合作伙伴和开发者的贡献,如果你也想把自己开发的样例分享出来,欢迎把样例提交到 OpenHarmony 知识体系 SIG 仓来,共建开发样例请参考如何共建开发样例。
审核编辑 :李倩
全部0条评论
快来发表一下你的评论吧 !