基于STM32和W5500实现AirPlay音频播放

博客, 物聯網
※已刊登在“无线电”1月刊上  基于STM32和W5500实现AirPlay音频播放 作者:常席正,魏文龙   AirPlay是苹果公司推出的一套无线音视频解决方案,我们手里的iPhone、iPad甚至是Apple Watch等设备还有电脑上的iTunes都支持AirPlay。使用AirPlay可以方便的使移动设备的音频流,视频流可以投射到音箱和显示设备上,而无需蓝牙设备的配对过程。但是支持AirPlay功能的音响设备普遍都比较昂贵,而且家里的3.5毫米的插口的老音箱也没有利用起来,本着“喜新不厌旧,改造旧物发挥余热”的精神,我开始了新一轮的折腾。 我的想法是用嵌入式方案STM32+W5500的方式实现AirPlay协议,并使用I2S接口接PCM5102A音频模块来实现音频播放。于是马上上网查资料,发现成熟的方案还不太多,现有的方案都是在linux或者windows上运行的,精挑细选之后选择了https://github.com/juhovh/shAirPlay这个AirPlay开源项目作为参考,主要是该代码是用C语言实现移植到stm32比较方便。 在开始之前我们有必要先了解一下AirPlay, AirPlay是苹果公司收购airtunes后,在airtunes协议的基础上增加了视频,照片的传输,从而变为完整的AirPlay协议。AirPlay可以将iPhone 、iPad、iPod touch 等iOS 设备上的包括图片、音频、视频及镜像传输到支持AirPlay协议的设备中播放,实现随时随地的无线流媒体传输。在我们的这个项目中,我们只需要实现AirPlay协议中的音频流部分。AirPlay的实现过程中包含多个子协议,其中有的协议是完全标准的,有一部分协议苹果公司进行了一些修改,有的则是完全私有的。 Multicast DNS:用于发布服务,启动后,在iOS的控制中心菜单中就能看到支持AirPlay的设备列表; HTTP / RTSP / RTP:用于流媒体服务,传输音视频数据,进行播放控制等; NTP:网络时间协议,用于时间同步; FAirPlay DRM加密协议:用于进行数据加密,这个是完全私有的加密协议。 开始工作前我们需要进行一些前期准备,如下图: 图1 硬件框图及接线 iPhone用来播放音乐,并通过Airplay协议发送音频流。W5500EVB是WIZnet的W5500开发板,其中的W5500除了包含以太网的MAC和PHY外,还内置了硬件的TCP/IP协议栈,是目前比较常用的以太网方案。我们使用W5500EVB作为服务器接收并解码音频数据,开发板的操作可以参考http://www.w5500.com中的例程。PCM5102A音频模块可以将解码后的音频数据进行播放。经过分析后我们要实现AirPlay音频播放主要是实现以下三个方面: iPhone在网络中发现Airplay设备(W5500EVB)并建立连接; W5500EVB接收并解码音频数据; W5500EVB通过I2S接口将音频传送到PCM5102A音频模块; 接下来我们将分别实现这三个步骤: 1、发现Airplay设备并建立连接 AirPlay发现设备是基于mDNS协议(Multicast DNS)实现,iPhone与W5500EVB需要连入同一网络且W5500EVB要加入组播组224.0.0.251:5353才可以接收mDNS报文。W5500EVB收到iPhone发出的Querry查询报文后回复Response报文,报文的内容可以参考文档《Unofficial AirPlay Protocol Specification》(http://nto.github.io/AirPlay.html),下方为mDNS设备发现和设备注册代码: 1 uint8 mdns_query(uint8 s, uint8 * name,uint8* rname) 2 { 3     uint8 ip[4]; 4     uint16 len, port; 5     switch (getSn_SR(s)) { 6     case SOCK_CLOSED:/*打开SOCKET并加入组播组224.0.0.251*/ 7         setDIPR(s,DIP);/* 设置目标IP 224.0.0.251*/ 8         setDHAR(s,DHAR);/*设置目标MAC 01:00:5e:00:00:FB */ 9         setDPORT(s,DPORT);/*设置目标端口5353*/ 10         socket(s, Sn_MR_UDP, 5353,Sn_MR_MULTI);/*打开SOCKET并加入组播组*/ 11         break; 12     case SOCK_UDP: 13         if ((len = getSn_RX_RSR(s)) > 0) { 14             if (len > MAX_DNS_BUF_SIZE) { 15                 len = MAX_DNS_BUF_SIZE; 16             } 17             len = recvfrom(s, BUFPUB, len, ip, &port); 18             /*检查收到报文的flag确定报文是否为查询报文*/ 19             if ((BUFPUB[2]&0x80)==0) { 20                 len = mdns_makeresponse(0,name,rname,BUFPUB,MAX_DNS_BUF_SIZE); 21                 sendto(s, BUFPUB, len, DIP,DPORT); 22             } 23         } 24         break; 25…
Read More