0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

智能猫眼的实现是采用流媒体协议RTSP

OpenAtom OpenHarmony 来源:OpenAtom OpenHarmony 作者:OpenAtom OpenHarmony 2022-05-16 09:20 次阅读

前言

智能猫眼是一种家居安防产品。是安装在防盗门上的一种嵌入式设备,可以通过摄像头获取图像显示至手机应用中,这样老人或者小孩就可以看清门外的情况。

智能猫眼的实现是采用流媒体协议 RTSP。该协议定义了程序如何通过 IP 网络传送多媒体数据。RTSP 多用于安防摄像头、车载监控、网络直播等场景应用。本文档旨在讲解在 OpenAtom OpenHarmony(以下简称“OpenHarmony") 1.0.1 release 下将 Hi3518EV300 编码后的 H.265 视频格式(H.265 是一种视频编码格式,可以由 OpenHarmony 媒体子系统产生),通过 RTSP 传输显示到手机的应用中。

cc8c790e-d2bd-11ec-bce3-dac502259ad0.jpg

注: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 协议发送。

如下图所示:

ccbad592-d2bd-11ec-bce3-dac502259ad0.jpg

如何运行 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=GBctrl_interface=udpnetwork={    ssid="SMedia"    psk="12345678"}

设备启动后输入:


./bin/wpa_supplicant -iwlan0 -c/etc/wpa_supplicant.conf

输入 ifconfig 可查看到连接成功后的 IP 地址:

ccd7c4ae-d2bd-11ec-bce3-dac502259ad0.jpg

环形缓存区

在媒体子系统中,为了同步 RTSP Server 应用获取 H.265 数据须设计一个环形缓冲区。缓冲区总大小为 16*256K 长度的数组。put 为媒体子系统存放缓冲区的偏移值,get 为 RTSP Server(Hi3518EV300)线程获取缓冲区的偏移值,缓存区定义在文件 recorder_impl.h 下。


constexpr uint32_t RING_BUFF_MAX_CNT = 16;constexpruint32_tRING_BUFF_SIZE=256*1024;

具体实现如下:

初始情况下偏移值 put 与 get 的位置均在开头。

cd0b6048-d2bd-11ec-bce3-dac502259ad0.jpg

当 RTSP Server 启动后媒体子系统填充 buff,偏移值 put 向前移。

cd415892-d2bd-11ec-bce3-dac502259ad0.jpg

RTSP Server 通过偏移值 get 获取到视频编码数据后释放 buff,偏移值 get 向前移。

cd606f8e-d2bd-11ec-bce3-dac502259ad0.jpg

当 put 与 get 偏移超过 16 时重新置 1 因此形象地称为环形缓冲区,其中 get 永远在 put 后且间距不会超过 3 个 buff,实现是在 rtsp Server 中设置同步时间。

cd7e62f0-d2bd-11ec-bce3-dac502259ad0.jpg

代码实现逻辑:当 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)来结束流媒体会话。

其交互流程如下所示:

cd98e788-d2bd-11ec-bce3-dac502259ad0.jpg

在文件 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 抓取报文如下:

cdb83886-d2bd-11ec-bce3-dac502259ad0.jpg

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)   |                                               |   |-+-+-+-+-+-+-+-+                                               ||FUpayload|

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 抓取报文如下:

cdeaf3d4-d2bd-11ec-bce3-dac502259ad0.jpg

RTSP Client

RTSP Client 实现使用手机 APP”完美播放器“。

准备一台手机,在手机应用市场中搜索”完美播放器“并下载安装。

ce17673e-d2bd-11ec-bce3-dac502259ad0.jpg

打开菜单选择网址播放。

ce3e9674-d2bd-11ec-bce3-dac502259ad0.jpg

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

ce604102-d2bd-11ec-bce3-dac502259ad0.jpg

总结

丰富多样的 OpenHarmony 开发样例离不开广大合作伙伴和开发者的贡献,如果你也想把自己开发的样例分享出来,欢迎把样例提交到 OpenHarmony 知识体系 SIG 仓来,共建开发样例请参考如何共建开发样例。

审核编辑 :李倩



声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6790

    浏览量

    88723
  • 视频编码
    +关注

    关注

    2

    文章

    112

    浏览量

    21005
  • OpenHarmony
    +关注

    关注

    25

    文章

    3629

    浏览量

    16030

原文标题:基于OpenHarmony实现智能猫眼

文章出处:【微信号:gh_e4f28cfa3159,微信公众号:OpenAtom OpenHarmony】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    流媒体后视镜市场份额连续6年称霸全国,新产品即将上市

    日前,来自佐思汽研的《2024年全球及中国电子后视镜行业研究报告》新鲜出炉。 数据显示,2024年1-4月,流媒体后视镜装配量完成15.9万辆,同比增长33.8%。从供应商层面来看,远峰科技仍处于
    的头像 发表于 09-29 09:50 224次阅读
    <b class='flag-5'>流媒体</b>后视镜市场份额连续6年称霸全国,新产品即将上市

    ElfBoard技术贴|如何在ELF 1开发板上搭建流媒体服务器

    流媒体服务器是一种专门用于传输实时数据流的服务器软件,广泛用于视频直播、视频会议、音频播放等应用场景。在嵌入式开发领域,将流媒体服务器部署到开发板上可以实现诸如视频监控、实时数据传输等功能。本文将介绍如何利用nginx和其rtm
    的头像 发表于 08-20 14:48 483次阅读
    ElfBoard技术贴|如何在ELF 1开发板上搭建<b class='flag-5'>流媒体</b>服务器

    贸泽开售AMD / Xilinx Alveo MA35D媒体加速器 为流媒体、游戏、远程医疗和在线学习应用提供支持

    。   AMD / Xilinx Alveo MA35D 媒体加速器采用基于特定应用集成电路的视频处理单元,专为高密度、超低延迟流媒体而设计。每款器件(每张卡 2个)
    发表于 07-12 10:44 524次阅读

    亚马逊拟收购印度流媒体MX Player部分资产

    近日,亚马逊与印度知名视频流媒体服务MX Player达成了一项引人注目的收购协议。据悉,亚马逊将收购MX Player的部分资产,而此次交易的估值不到1亿美元,远低于市场对该公司的预期。
    的头像 发表于 06-07 15:56 477次阅读

    智能后视镜定制_行车记录仪|流媒体ADAS辅助驾驶定制开发方案

    行车记录仪方案采用了联发科MTK6761四核高性能处理器,从根本上解决了过去死机问题,大幅度提升了视频压缩效率和画面稳定性,带来更加可靠和清晰的行车记录体验。采用1080P流媒体后视系统,搭载安卓11系统,大幅升级系统流畅度,使
    的头像 发表于 05-27 19:51 409次阅读
    <b class='flag-5'>智能</b>后视镜定制_行车记录仪|<b class='flag-5'>流媒体</b>ADAS辅助驾驶定制开发方案

    PLC设备通过智能网关采用HTTP协议JSON文件对接MES、ERP等系统平台

    运行。 本文例采用西门子S7-1500PLC(IP:192.168.2.11)与IGT-DSER智能网关以太网通讯,实现HTTP协议JSON文件通讯。先用过
    发表于 05-13 12:04

    【RTC程序设计:实时音视频权威指南】信令与媒体协

    简单的rtc系统,至少包含了九个基本信令,从登录信令开始,登入系统后可以发布媒体流,也可以订阅其他用户的媒体流,用户在房间内的操作都是通过信令来实现的。 在信令传输内容中最重要的载体就是
    发表于 04-29 17:24

    OpenHarmony鸿蒙南向开发案例:【智能猫眼(基于Hi3518开发板)】

    基于Hi3518开发板,使用开源OpenHarmony开发的RTSP协议流媒体应用。达到将Hi3518开发板中摄像头获取的数据通过RTSP协议
    的头像 发表于 04-22 15:46 1846次阅读
    OpenHarmony鸿蒙南向开发案例:【<b class='flag-5'>智能</b><b class='flag-5'>猫眼</b>(基于Hi3518开发板)】

    OpenHarmony鸿蒙南向开发案例:【智能猫眼(基于3516开发板)】

    基于Hi3516开发板,使用开源OpenHarmony开发的RTSP协议流媒体应用。达到将Hi3516开发板中摄像头获取的数据通过RTSP协议
    的头像 发表于 04-19 22:01 521次阅读
    OpenHarmony鸿蒙南向开发案例:【<b class='flag-5'>智能</b><b class='flag-5'>猫眼</b>(基于3516开发板)】

    车载智能后视镜_流媒体云镜_行车记录仪主板方案定制

    流媒体后视镜搭载联发科MT6761处理器,采用12nm工艺制造,集成四核A53 CPU主频达2.0GHz。这一高速处理器确保了后视镜的快速响应和顺畅运行,画面质量不打折,无论白天黑夜都能清晰捕捉影像。流媒体云镜运行安卓11.0系
    的头像 发表于 04-08 20:01 679次阅读
    车载<b class='flag-5'>智能</b>后视镜_<b class='flag-5'>流媒体</b>云镜_行车记录仪主板方案定制

    音视频解码生成与流媒体传输的结合

    音视频解码生成与流媒体传输是现代数字媒体技术中两个不可或缺的部分,它们的结合为用户提供了高质量、实时性的多媒体体验。 1. 解码生成与流媒体传输的关系 解码生成是
    的头像 发表于 02-21 14:36 346次阅读

    编解码一体机在流媒体传输中的核心作用

    传输带宽的需求,还能降低存储空间的使用。 实时传输:编解码一体机支持实时传输协议,能够实现音视频流的实时传输,保证流媒体服务的实时性和流畅性。 协议转换:编解码一体机能够
    的头像 发表于 01-31 14:20 385次阅读
    编解码一体机在<b class='flag-5'>流媒体</b>传输中的核心作用

    智能猫眼门铃,雷达感应模组应用

    智能猫眼门铃初期时,产品功能相对单一,包括门铃和猫眼两个部分。门铃的基本功能是在室外有人呼叫时,室内可以接到信息。随着技术的进步,电子猫眼经历了几个发展阶段:首先是带屏显示,可拍照报警
    的头像 发表于 12-12 16:34 415次阅读
    <b class='flag-5'>智能</b><b class='flag-5'>猫眼</b>门铃,雷达感应模组应用

    毫米波雷达模组在智能猫眼门铃中的应用

    智能猫眼门铃初期时,产品功能相对单一,包括门铃和猫眼两个部分。门铃的基本功能是在室外有人呼叫时,室内可以接到信息。随着技术的进步,电子猫眼经历了几个发展阶段:首先是带屏显示,可拍照报警
    的头像 发表于 12-11 11:29 839次阅读
    毫米波雷达模组在<b class='flag-5'>智能</b><b class='flag-5'>猫眼</b>门铃中的应用

    一个电路板从射频设计到实现是经过什么步骤?

    一个电路板从设计到实现是经过什么步骤?
    的头像 发表于 11-14 10:02 769次阅读
    一个电路板从射频设计到<b class='flag-5'>实现是</b>经过什么步骤?