rpiplay实现树莓派AirPlay投屏器

发布于:2023-01-02 ⋅ 阅读:(927) ⋅ 点赞:(0)


尝试对rpiplay代码如何树莓派上AirPlay投屏实现的原理进行学习与解析,有不当的地方欢迎留言指正


在这里插入图片描述

(一) 参考代码和文档

在这里插入图片描述


(二)解析代码实现原理

在树莓派操作系统上(一个更适用于树莓派的内核为Linux的操作系统),网络协议的实现需要借助相应计算机网络编程实现。在操作系统上集成了许多基本计算机网络的协议接口,借助socket编程使用操作系统提供给程序员操作网络协议栈的接口来实现AirPlay一系列网络交互。对于Linux操作系统而言,套接字与文件并无区分,因此在实现过程中实际上是对“文件进行读写”操作。

1、发现模块

mDNSService即多播DNS服务,将会发送两类AirPlay规定的广播,RAOP广播(用作音频发现)和AirPlay广播(用作图片、视频发现)。
多播DNS服务需要预先安装libavahi-compat-libdnssd-dev,借助<dns_sd.h>头文件来调用实现Apple公司Bonjour的协议内容来注册AirPlay所需的相关服务。对于多播DNS中需要解决的三个部分,IP获取Addressing,名称解析Naming和服务发现Service Discovery。IP获取在本项目树莓派投屏器系统内将采用IPV4的格式,将依据服务器端的MAC地址进行转换生成符合DNS服务所需格式的IP数据,并检查是否会引起冲突。对于名称解析Naming, Bonjour将要求局域网内不能有重复的service名称和host名称,并将通过UDP广播的形式查询局域网内服务名称对应的IP地址。对于服务发现,Bonjour将要求每个进入局域网的支持Apple的Bonjour服务的设备广播自身的设备信息、IP、服务等等信息,并且利用查询查找局域网内是否有可用的服务、是否存在冲突以及所请求服务的主机的IP地址等等。为了能实现设备间的发现与通信,Bonjour统一服务注册格式。在AirPlay实现中,树莓派投屏器注册服务时将设定servername为‘_AirPlay._tcp’和‘_raop._tcp’的两个服务以供后续AirPlay发现与屏幕镜像数据传输。


就发现模块而言需要注册两个DNS服务,一个是RAOP,一个是AirPlay。
使用苹果官方已经直接提供的DNSServiceRegister来进行两个服务的注册,并且在DNS服务上Apple官方dns-sd.h的头文件内提供了更多相关接口来实现与支持。在注册中需要依据官方协议格式提供投屏服务注册所需要的参数。需要在注册时传入sdRef(DNSServiceRefDeallocate将后续依据该参数注销相应DNS服务);name用来指定注册服务的名称将使用MAC硬件地址进行转化为字符串进行定义;regtype与后续服务的发现与运行紧密相关,需要安装服务类型后加协议的固定Apple规范进行定义,如在本屏幕镜像中注册的两个服务就应定义为‘_raop._tcp’和‘_AirPlay._tcp’;port服务连接的端口;TXT记录中的各字段需要按照AirPlay协议内容中的标准格式进行定义,TXT 资源记录将会包含后续服务中可以接收的数据类型、加密类型、编解码信息等,并将作为服务注册的一部分。具体注册服务相应字段内容Apple官方并没有完全公布,借助了GitHub上相关开源参考文档可以查阅到已经被解开的相应字段以及对应含义功能的描述。

2、屏幕分享

RTSP接收并回复RTSP请求,对RTSP请求进行处理开辟StreamBuffer来处理并读取后续接收的音频数据、启动播放等等工作。其中包括对于RTP数据传输的接收和RTSP接收音频控制的数据。RTSP模块将用于处理AirPlay过程中的各种数据传输的服务实现。Apple公司在进一步发展RTSP协议的基础上将该协议于AirPlay中应用为RAOP (Remote Audio Output Protocol)的服务内容。
按照RTSP协议的规定服务器端和客户端将具体交互以及功能如下。
首先客户端将会与服务器端进行POST请求的pair-setup和pair-verify的,这用于AirPlay的配对验证,其中会用到ed25519,SHA-512,AES等一系列加密算法,验证通过将进一步建立连接。之后,将会逐步进行POST请求的fp-setup的交互,而这部分也和FairPlay有关,即由Apple公司所研发的一项DRM(数字版权管理)方法。AirPlay传输过程中的流媒体数据将被该技术加密基于AES加密后传输,此加密技术将有助于在AirPlay传输过程中保障用户数据安全。完成了以上,服务器端就会再向客户端传递OPTIONS,而客户端则会反馈给服务器端所有可用的参数,在AirPlay中包括了SETUP、RECORD、FLUSH、TEARDOWN、OPTIONS、GET_PARAMETER、SET_PARAMETER等。然后,客户端将会通过GET/info请求媒体的描述文件,服务器端将回复媒体初始化信息,包括输入输出流媒体格式、视频分辨率与翻转、帧率参数等等信息。然后客户端和服务器端之间将建立连接,而客户端则发出创建连接的申请到服务器端,并着手准备传送音视频数据。在建立连接的请求中,请求信息将会包括流媒体传输模式、RTP和RTCP对应端口、会话属性等。服务器端接收申请后将创建会话,并返回所创建会话的标识符及其有关数据。建立连接以后,客户端就可以直接向服务器请求播放,而客户端则回复请求信息,客户端将进一步依据建立连接的信息与端口号发送流媒体数据。结束播放,客户端将向服务器端提出终止的申请,服务器端应答后即断开连接[15]。除了以上最基本的RTSP接入和传输的基本过程外,在与SET UP建立连接以后除了PLAY,还要根据不同的服务状态以及要求而有 GET_PARAMETER, SET_PARAMETER等交互过程。GET_PARAMETER和SET_PARAMETER命令用于调整音量大小。POST/feedback将每秒钟客户端就会给接收终端发送一次这个指令,保持连接状态。RECORD命令会再返回一些音频媒体相关的参数。TEARDOWN会结束流传输,同时释放与之相关的资源。每次开始推送音频和视频数据时会存在又一次SETUP的交互,其中对于音频与视频不同类型的数据传输而言内容有所不同,主要差异包括接口、数据类型和数据格式等。在与RTSP协议连接过程中,所采用的基于Client的信令的内容主要采用由一个叫plist的Apple定义的一个以xml形式的格式实现交互,需要借助相关方法进行解析。
请添加图片描述


在屏幕镜像分享上主要包括了视频数据的接收,音频数据的接收两类数据的处理。两者数据就传输方式上存在差异,在数据接收时处理也有所不同。视频数据是通过TCP传输,音频数据是通过UDP(即RTP)传输来实现的。
屏幕镜像是通过TCP长连接传输H.264编码的视频流来实现的。此流使用 128 字节标头进行打包。AAC-ELD 音频使用 AirTunes 协议发送。至于主时钟,它是使用NTP同步的。在音频与视频的屏幕镜像过程中,音视频内容将分开传输并最终在树莓派内通过时间戳来实现同步播放的效果。音频的UDP数据包将在buffer内按照UDP包内标识的顺序重新解码加载,并与镜像画面的视频数据一起依据传输的NTP时间戳协同播放。在接收过程中,屏幕镜像会直接将音频、视频数据暂存在对应定义的buffer内。 对于TCP传输的视频和UDP传输的音频数据包的不同的接收处理,以及解码播放的处理就是屏幕镜像的关键所在,也是本模块内致力解决的主要问题。同时对于UDP传输的其他控制相关数据包也要进行相应及时的响应,以达到控制媒体播放、控制音量等的效果。
在项目系统中使用socket编程来操作网络协议栈的接口来实现数据的接收以及端口的接听。在项目中主要使用bind()来为后续网络通讯分配地址和端口,bind()不分TCP、UDP均可使用。在UDP传输中使用recvfrom()接收UDP数据包,使用sendto()传输UDP数据包。在TCP传输中需要建立连接才能进一步传输交换数据,使用listen()先等待TCP连接请求,accept()来进一步允许TCP连接请求,使用send()和recv()的I/O函数来进行数据接收,同时对于TCP数据包需要对offset进行考虑处理,最后close()来断开TCP连接。在断开TCP连接时,可以进一步使用shutdown()来进一步更“智能”地断开连接,在使用shutdown()后socket会进入半断开状态即可接收输入,接完了可返回结束信号并自动关闭[10]。同时为了增加处理的效率,实现“实时传输”的效率,使用多线程进行进一步控制,涉及pthread_create ()和pthread_join()多线程操作、Mutex来控制同步等等。同时进一步借助select()来监视文件描述符,以更好地实现I/O复用[10]。
对于屏幕镜像raop的实现下,主要包含raop_ntp(时间同步)、raop_rtp(音频数据UDP接收与控制数据UDP接收)、raop_rtp_mirror(视频数据TCP接收)、fairplay(apple推出的加密)、pairing(保存各种密钥与签名)。

1)RTSP协议

对于使用RTSP协议的实现上,conn_request在建立连接前,需要进行多轮的数据交互,对不同的RTSP消息格式要进行解析并回应相关数据参数,使用raop_handler内对RTSP协议交互过程中涉及的方法进行不同处理操作进行定义,在交互中回应请求时还需使用plist的相应数据格式对于参数进行赋值加入,同时借助llhttp来便捷解析HTTP。在准备工作中有一个监听来自raop服务所属端口的http请求的线程(称其为http监听线程),因此第一个RTSP请求就是先发送到这个端口中,在被http监听线程监听到fd改变后,该线程会进行accept等动作,建立socket连接。 连接建立后,RTSP请求正式一个一个地进入。
在该项目中使用http_request和http_response内预先进行基本的HTTP解析操作,以供之后网络内交互使用。并在httpd中定义关于连接、端口与进程的控制,以供之后建立连接接收数据使用。raop_ntp中建立UDP Socket来对本地和远程计算机的时间进行接收与处理,以便后续时间同步。netutils_init_socket来依据传输协议类型、ip类型使用socket()和setsockopt()来对socket进行自定义相关参数的创建与初始化,并使用bind()来进行相关服务对应套接字的分配。
在整个屏幕镜像实现RTSP的过程中会有多次setup的交互,推送音频和视频数据的时候就会分别进行setup,此时依据不同的数据类型会包含不同的数据编码、类型、格式的交互信息。当数据传输为视频的时候,相应的处理内容将转到raop_rtp_mirror内的定义来进行TCP传输类型的接收。当数据传输为音频时,则转为raop_rtp内相关UDP传输的处理中。

2)UDP音频数据

raop_rtp服务在初始化时会创建UDP传输类型的csock、dsock两个socket套接字。并使用select()来对这两个端口进行监听。在监听到数据时,使用recvfrom()来对UDP数据包进行接收。判断收到的包的类型,并且依据UDP包在协议中规定的格式提取不同字段的数据。csock端口用来负责控制与时间同步的数据包,依据数据包内不同位段将分别获取到本地NTP和远程的NTP以及RTP时间戳数据。对收到的音频UDP数据包使用buffer进行enqueue接收,并且借助dequeue来将数据输入到视频播放处理器中,使用AAC对应pts控制进度,实现与视频画面播放的同步。
在这里插入图片描述

3)TCP视频数据

在视频SETUP请求后会进一步对于mirror视频数据进行TCP长连接的数据传输前的连接建立。RTSP交互handler中视频SETUP调用的raop_rtp_start_mirror()将对raop_rtp_mirror进一步使用bind()的raop_rtp_init_mirror_sockets()来对socket初始化,并使用listen()进行监听。在使用select()接收到相关连接请求变动后,使用accept()来对TCP连接请求进行接收,并对TCP连接使用setsockopt()来对超时时间、长连接Keep Alive模式参数等进行设定。建立完连接后使用select()继续进行非阻塞的监事,检查到数据传递后使用recv()的I/O函数来对收到的TCP数据包进行接收。流媒体包头部会包含有效负载payload的大小、格式等参数,并会表明该TCP数据包的类型。TCP数据包会包含视频比特流、编解码数据信息和心跳三种类型。需要在payload界定的参数范围内读入数据,并依据payload_type所示类型对不同数据进行相应处理。
在这里插入图片描述

对于视频数据,先是依据包内头部的ntp时间戳数据进行截取,并对视频数据接收后在buffer内进行对应解码操作。借助h264的pts进行音画同步。
对于编解码信息,对应AirPlay固定格式的位置将相应数据传给音频数据h264相应固定参数赋值,为后续将视频h264数据传给编解码器正常渲染提供参数。
在这里插入图片描述

3、音视频渲染

流媒体是指Internet上使用流式传输技术的连续时基媒体。当前在Internet上传输音频和视频等信息主要有两种方式:下载和流式传输两种方式。
而AirPlay协议投屏中使用流式传输技术来实现流媒体屏幕镜像的效果。使用流式传输可以一边下载一边观看流媒体内容。由于Internet是基于分组传输的,所以接收端所收到的数据包往往有延迟和乱序(流式传输构建在UDP上)。要实现流式传输,就是要从降低延迟和恢复数据包时序入手。在发送端,为降低延迟,往往对传输数据进行预处理(降低质量和高效压缩)。在接收端为了恢复时序,采用了接收缓冲;而为了实现媒体的流畅播放,则采用了播放缓冲。使用接收缓冲,可以将接收到的数据包缓存起来,然后根据数据包的封装信息(如包序号和时戳等),将乱序的包重新排序,最后将重新排序了的数据包放入播放缓冲播放。由于网络不可能很理想,并且对数据包排序需要处理时耗,所得到排序好的数据包的时间间隔是不等的。如果不用播放缓冲,那么播放节目会很卡,这叫时延抖动。相反,使用播放缓冲,在开始播放时,花费几十秒钟先将播放缓冲填满(例如PPLIVE),可以有效地消除时延抖动,从而在不太损失实时性的前提下实现流媒体的顺畅播放。接收缓冲时,涉及流媒体数据包的封装信息(包序号和时戳等),其具体体现在RTP封装中。
在该模块的实现上主要是借助树莓派上已有的音视频渲染的库来实现。通过AirPlay协议对应播放显示参数位数据的接收与赋值,借助OpenMAX和fdk-aac的一系列相关函数来实现缓冲接收数据后的解码与播放显示的功能。


要使镜像投屏能在树莓派HDMI外接显示屏上正常显示播放,项目系统中需要进行视频渲染器和音频渲染器的配置。
就流媒体的处理与开发上主要分为,数据接收缓存部分、压缩数据的解压decode部分以及媒体数据帧的显示部分。在实现过程中,缓存部分需要借助现有库对加密数据进行解密;解压音视频流部分,视频使用OpenMAX下接口完成,音频借助fdk-aac内对ACC解码的接口完成;显示播放部分,音视频内容均借助OpenMAX下组件实现,在播放过程中音画同步则同时借助传入数据的时间戳数据来实现。
对于视频流数据,h264格式的视频数据分包TCP传至树莓派投屏器时将会包含三种类型的数据包。TCP数据包将包含视频比特流、编解码器数据和心跳三种类型,其中心跳类型包不包含有效数据。不同类型在TCP标头将在协议指定位置拥有不同的赋值,通过对值的区分将分别处理不同种类的视频流数据。对于编解码器数据的数据包内容,将依据AirPlay协议内规定的内容对不同位所表示解码含义的数据进行读取与解码器参数的赋值。对于视频比特流的数据包内容先调用AirPlay内加密所对应的解密接口进行处理后加入视频接收缓冲队列。通过调用相应OpenMAX内的库,将解码参数交予解码器进行定义,将视频流数据进行解码后渲染显示。
对于音频数据,ACC格式的数据分包RTP传至树莓派投屏器。对于音频数据加入音频接收缓冲队列,并对包内数据在按编号加入缓冲队列前进行加密数据解密的操作。并进一步将音频接收缓冲队列数据交至音频解码与渲染的音频播放缓冲队列。在音频播放缓冲队列,先是需要借助fdk-aac内aacDecoder的相应接口调用实现音频数据的解码,并对解码得到的音频数据通过OpenMAX相应组件来实现播放。
在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看