STM32實現Airplay音樂播放器

AirPlay是蘋果公司推出的一套無線音樂解決方案,咱們手裏的iPhone、iPad甚至是Apple Watch等設備還有電腦上的iTunes都支持AirPlay,可是支持AirPlay功能的音響設備都是比較貴的,荷包扁扁的我天然是感受買那麼貴的音響實在是不合算。前兩天突發奇想,若是STM32能夠支持AirPlay的功能,那麼不就可讓我享受一把無線音樂的自由自在了嗎?因而立刻登錄github搜了一下,發現還真有解決方案不過基本上全部的方案都是在linux或者windows上運行的,精挑細選以後選擇了https://github.com/juhovh/shairplay這個AirPlay開源項目,主要是該代碼是用C語言實現移植到stm32比較方便。html

在開始以前咱們有必要先了解一下AirPlay, AirPlay是蘋果公司收購airtunes後升級airtunes的協議庫,在airtunes增長了視頻,照片的傳輸,完整的變爲airplay非開源功能,實現隨時隨地的家庭音樂無線流媒體傳輸。AirPlay能夠將iPhone 、iPad、iPod touch 等iOS 設備上的包括圖片、音頻、視頻及鏡像傳輸到支持AirPlay的設備中播放,AirPlay的實現過程當中包含多個協議,其中有的協議是徹底標準的, 有一部分協議進行了一些修改,有的則是徹底私有的。linux

    • Multicast DNS用於發佈服務, 啓動後, 在iOS的控制中心菜單中就能看到對應的設備;git

    • HTTP / RTSP / RTP  用於流媒體服務, 傳輸音視頻數據, 進行播放控制等;github

    • NTP 時間同步;算法

    • FairPlay DRM加密  徹底私有的加密協議。windows

咱們須要準備一部iphone手機並安裝網易雲音樂,W5500EVB開發板(stm32f103+W5500),PCM5102A音頻模塊。iPhone手機用來做爲客戶端搜索設備及發送音頻數據,W5500EVB是WIZnet的開發板具備以太網功能用來做爲服務器接收音頻數據,開發板的操做能夠參考www.w5500.com中的例程。PCM5102A音頻模塊是將解碼後的音頻數據進行播放。通過分析後咱們要實現Airplay音頻播放主要是實現如下三個方面:服務器

一、 iPhone在網絡中發現 W5500設備並創建鏈接網絡

二、 W5500EVB接收並解碼音頻數據app

三、 W5500EVB經過I2S接口將音頻傳送到PCM5102A音頻模塊less

一、發現設備並創建鏈接

Airplay發現設備是基於mdns協議實現,iPhone與W5500EVB須要連入同一網絡且W5500EVB要加入組播組224.0.0.251才能夠接收mdns報文。W5500EVB收到iphone發出的querry查詢報文後回覆response報文,報文的內容能夠參考文檔《Unofficial AirPlay Protocol Specification》(http://nto.github.io/AirPlay.html),下方爲MDNS設備發現代碼:

1uint8 dns_query(uint8 s, uint8 * name,uint8* rname)

 2 {

 3     uint8 ip[4];

 4     uint16 len, port;

 5     switch (getSn_SR(s)) {

 6     case SOCK_UDP:

 7         if ((len = getSn_RX_RSR(s)) > 0) {

 8             if (len > MAX_DNS_BUF_SIZE) {

 9                 len = MAX_DNS_BUF_SIZE;

10             }

11             len = recvfrom(s, BUFPUB, len, ip, &port);

12             len=dns_makequery(0,name,rname,BUFPUB,MAX_DNS_BUF_SIZE);

13             sendto(s, BUFPUB, len, DIP,DPORT);

14             len=dns_makeresponse(0,name,rname,BUFPUB,MAX_DNS_BUF_SIZE);

15             sendto(s, BUFPUB, len, DIP,DPORT);

16         }

17         break;

18     case SOCK_CLOSED:

19         setDIPR(s,DIP);/* 設置目標IP 224.0.0.251*/

20         setDHAR(s,DHAR);/*設置目標MAC 01:00:5e:00:00:FB*/

21         setDPORT(s,DPORT);/*設置目標端口5353*/

22         socket(s,Sn_MR_UDP, 5353,Sn_MR_MULTI);/*打開SOCKET加入組播組*/

23         break;

24     }

25     return DNS_RET_PROGRESS;

26 }

代碼中12行的dns_makequery()函數用來拼接查詢報文,代碼14行dns_makeresponse()函數用來拼接響應報文,咱們將代碼編譯下載到W5500EVB中運行,打開iPhone的選項列表點擊音樂選項會顯示以下圖所示的界面,點擊右上方標誌搜索同一網絡下的設備。界面以下圖所示:

圖1-1 iphone選項列表

此時iPhone向224.0.0.251組播組發送querry查詢報文,W5500EVB收到查詢報文後向224.0.0.251組播組發送response響應報文。W5500EVB發送的response響應報文中該報文中包含RAOP服務,該服務用於音頻流的投影。 RAOP從本質上來講是實時流協議,只不過增長了身份驗證請求-應答的步驟,RAOP服務用兩個信道實現流媒體:一個是用實時流協議的控制信道;另外一個是數據信道用來發送數據。RAOP服務名稱格式:MAC地址@設備名._raop._tcp.local。經過抓包工具抓取響應報文咱們能夠看到RAOP服務的相關信息。

 

圖1-2 RAOP服務報文

Service字段是服務名稱。Protocol服務的類別:_airplay是視頻服務(未用到),_raop是音頻服務。Name說明數據傳輸的協議,能夠經過TCP或者UDP傳輸。Port聲明瞭RTSP命令交互的端口號爲5005,客戶端能夠經過端口號與服務端創建鏈接。下圖中wiznet就是iPhone發現的支持AirPlay的設備(W5500EVB)。

圖1-3 iPhone發現設備

 

iPhone成功發現W500  EVB設備後就須要鏈接設備,此時咱們點擊列表中顯示的設備,鏈接成功後對應設備的後面會顯示對勾,以下圖所示:

 

圖1-4 iphone鏈接設備

上文介紹的iPhone發現設備設備的過程當中指定了RTSP是經過TCP進行通訊且端口號爲5005,因此咱們要建立一個端口號爲5005的TCP服務器來接收數據包,對RTSP數據包的解析是經過rtsp_parase_request()函數進行的以下方代碼20行所示。

1 void do_tcp_server(SOCKET s,uint16 localport)

 2 {

 3     uint16 len;

 4     uint8 send_buffer[1024];

 5     switch (getSn_SR(s)) {

 6     case SOCK_INIT:

 7         listen(s);

 8         break;

 9     case SOCK_ESTABLISHED:

10         if (getSn_IR(s) & Sn_IR_CON) {

11             setSn_IR(s, Sn_IR_CON);

12         }

13         len=getSn_RX_RSR(s);

14         if (len>0) {

15             memset(buffer,0,sizeof(buffer));

16             querry_flag=1;

17             recv(s,buffer,len);

18             memset(send_buffer,0,sizeof(send_buffer));

19             /*解析RTSP數據包並拼接響應數據*/

20             rtsp_parase_request((char*)buffer,(char*)send_buffer,s,len);

21             /*發送響應數據包*/

22             if (0==send(s,send_buffer,strlen(send_buffer))) {

23                 send(s,send_buffer,strlen(send_buffer));

24             }

25         }

26

27         break;

28     case SOCK_CLOSE_WAIT:

29         disconnect(s);

30         querry_flag=0;

31         break;

32     case SOCK_CLOSED:

33         querry_flag=0;

34         socket(s,Sn_MR_TCP,localport,Sn_MR_ND);

35         break;

36     }

37 }

    因爲蘋果的AirPlay協議爲了防止其餘未經蘋果容許的設備的接入,對傳輸的數據用非對稱性RSA加密算法進行加密,非對稱性的意思就是加密和解密用的不是同一份密鑰,RSA加密算法的密鑰分爲公鑰和私鑰,二者內容不一樣,用途也不一樣。公鑰用於加密,通常交給客戶端使用;私鑰用於解密,通常由服務器管理。iPhone中存有公鑰用來對iPhone輸出的數據流進行加密,接收端設備利用私鑰對接收的數據(音頻)流進行解密。W5500EVB是做爲服務器接收數據因此咱們只須要知道私鑰就能夠解析數據,咱們能夠直接百度網上已有大神破譯出的私鑰。RSA加密算法的實現能夠參考開源項目https://github.com/juhovh/shairplay工程中的RSA加密解密相關函數。

iPhone會先發送OPTIONS請求來肯定W5500EVB支持的方法,W5500EVB回覆支持的所有方法包含ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER等,方法具體含義可參考RTSP協議相關文檔。

iphone OPTIONS 請求報文:

OPTIONS * RTSP/1.0

CSeq: 0

DACP-ID: 4CB06073C86450D8

Active-Remote: 2937221397

User-Agent: AirPlay/373.9.1

圖1-5 OPTIONS請求報文

W5500EVB響應報文:

RTSP/1.0 200 OK

CSeq: 0

Apple-Jack-Status: connected; type=analog

Public:ANNOUNCE,SETUP,RECORD,PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER,SET_PARAMETER

圖1-6 OPTIONS響應報文

iphone收到W5500EVB的響應後,會向W5500EVB發送包含Apple-Challenge的OPTIONS數據包,Apple-Challenge後的參數是隨機生成且通過了RSA算法加密,W550EVB要將Apple-Challenge中的參數先進行base64解碼,解碼後的數據尾部添加W5500EVB的IP地址和MAC地址而後經過RSA私鑰加密後用base64編碼,W5500EVB將加密處理後的數據做爲Apple-Response的參數發送給iPhone,iPhone該數據進行驗證,數據正確則進行下一步,數據不正確則斷開鏈接。下圖爲包含Apple-Challenge的OPTIONS 數據包:

OPTIONS * RTSP/1.0

Apple-Challenge: UJPWMzMloBFr98cQQHX3OQ==

CSeq: 2

DACP-ID: 4CB06073C86450D8

Active-Remote: 2937221397

User-Agent: AirPlay/373.9.1

圖1-7 Apple-Challenge報文

 

接收到OPTIONS數據包後,截取Apple-Challenge相關數據,並進行解密代碼以下:

1if(strstr(rcv_buffer,"Apple-Challenge:")!=NULL)

 2 {

 3     rsakey_t *rsakey;

 4     rsakey = rsakey_init_pem(pemstr);

 5     if (!rsakey) {

 6         printf("Initializing RSA failed\n");

 7         return;

 8     }

 9     memset(response,0x00,1024);

10     /*獲取Apple-Challenge參數*/

11     mid(rcv_buffer,"Apple-Challenge: ","\r\n",CHALLENGE);

12     /*獲取加密Apple-Response*/

13     rsakey_sign(rsakey, response, sizeof(response), CHALLENGE,ipaddr, sizeof(ipaddr), hwaddr, sizeof(hwaddr));

14     mid(rcv_buffer,"CSeq: ","\r\n",CHALLENGE);

15     sprintf(send_buffer,"RTSP/1.0 200 OK\r\nCSeq: %s\r\nApple-Jack-Status:connected; type=analog\r\nApple-Response: %s\r\nPublic: ANNOUNCE, SETUP,RECORD,PAUSE, FLUSH, TEARDOWN, OPTIONS,SET_PARAMETER\r\n\r\n",CHALLENGE,response);

16 }

經過11行處的mid()函數來獲取Apple-Challenge後的參數而後14行處的rsakey_sign()函數對獲取數據進行加密解密,15行處完成對RTSP響應報文的拼接。拼接報文以下圖所示:

 

RTSP/1.0 200 OK

CSeq: 2

Apple-Jack-Status: connected; type=analog

Apple-Response:Dw5Jrbs1mhjks3YErCo1tSOUV8/G8pOOShS3dUocjWzDGQR6DfqiSEovks+G4nHmCw9BccjlpVHzzRUINYZenWhUy8zlGsVGNwuO4okfi86PjGp5VAS6RPeYbW/CpAPgrzpDsVCblSGt8kQbn+sWuku9WMfa4gYU82DgfmL3laphZlidEIZd8D6FwzAth4pbRdtL3N8GuM2kWGRSpT6FL4VGk326a58g0kUNqNDxHp0fTa4ijk8VORzkyKO9ByFeysmZqGDBurLuSvDoAs0c1zR9aHAIXfJkWd0Ii3WviC2F0+vEODcRgOh7gOvy/i5+OOTiUfvHiDFIqlhVCRnZ2g

Public:ANNOUNCE,SETUP,RECORD,PAUSE,FLUSH,TEARDOWN,OPTIONS,SET_PARAMETER

圖1-8 Apple-Response報文

 

iPhone收到W5500EVB的response後,對Apple-Response後的內容後進行解析校驗,校驗結果正確則設備鏈接成功能夠繼續發送數據不然斷開鏈接。

二、音頻數據接收與解碼

iPhone與W5500EVB創建鏈接成功後,就開始經過UDP協議發送音頻數據可是iPhone經過airplay傳輸的音數據都是加密過的,對於接收端來講,須要正確解密後才能對音視頻數據進行處理。音頻數據採用AES CBC128算法進行加密,該算法解密時需輸入參數rsaaeskey、aeskiv,這兩個參數經過解析iPhone發送ANNOUNCE請求來獲取, ANNOUNCE在傳輸的時候遵循了SDP協議。SDP協議用來描述媒體信息,下圖是ANNOUNCE請求報文

ANNOUNCE rtsp://192.168.1.150/1561243076001349804 RTSP/1.0

Content-Length: 652

Content-Type: application/sdp

CSeq: 3

DACP-ID: 4CB06073C86450D8

Active-Remote: 2937221397

User-Agent: AirPlay/373.9.1

 

v=0

o=AirTunes 1561243076001349804 0 IN IP4 192.168.1.100

s=AirTunes

i=Wenlong... iPhone

c=IN IP4 192.168.1.100

t=0 0

m=audio 0 RTP/AVP 96

a=rtpmap:96 AppleLossless

a=fmtp:96 352 0 16 40 10 14 2 255 0 0 44100

a=rsaaeskey:bx0eKFGbphzETu16PLtXyP8s2CDKHpjIclJCmChdw6b12YSEvzDR3jlQwTWQdRRRrr99cek6JzdE0pgv0TzAF++FK8g63la8H9ioEcLFq84zWT/7atIlPNFC7RELlQG5ff/yTXHJ7LkzxQF12DvzQzIPd8GMx5ik/rxnLObZ+GQAbB2xtW/By2JT5gapEMBsx8+t+0sZXNwA3GXrjcjF+h6+oAD37A3U04rR/iK+Pvzglvy/13ZOrXL1VJpTkE1O+TIflAzfl0BkBbtfd3lX/+Te+Og8+gXXe516Dg4/v1Veddj4HQYZ/vrxE/qYFGDZIFZUdmpBtmtVMqAYwt1n5w==

a=aesiv:UohAefAQLdnT4BIBimuhfg==

a=min-latency:11025

a=max-latency:88200

圖2-1 ANNOUNCE報文

W5500EVB解析收到ANNOUNCE請求包獲取rsaaeskey,aesiv並解碼。

1 void raop_announce(char *recv_buffer)

 2 {

 3     mid(recv_buffer,"Active-Remote: ","\r\n",remotestr);

 4     mid(recv_buffer,"rtpmap:","\r\n",rtpmapstr );

 5     mid(recv_buffer,"fmtp:","\r\n",fmtpstr);

 6     mid(recv_buffer,"rsaaeskey:","\r\n",rsaaeskeystr);

 7     mid(recv_buffer,"aesiv:","\r\n",aesivstr);

 8     /*解碼aeskey*/

 9     rsakey_decrypt(rsakey, aeskey, sizeof(aeskey), rsaaeskeystr);

10     /*解碼aesiv*/

11     rsakey_decode(rsakey, aesiv, sizeof(aesiv), aesivstr);

12     /*init alac*/

13     raop_buffer_init(&alac,fmtpstr);

14     return;

15 }

iPhone會繼續向W5500EVB發送SETUP數據包,數據包中包含timing_port 與control_port。timing_port 用來傳輸 AirPlay 的時間同步包,同時也能夠主動向iPhone請求當前的時間戳來校準流的時間戳。control_port是用來發送 resendTransmit Request 的端口,也就是當接收端發現收到的音樂流數據包中有丟失幀的時候,能夠經過 control port 發送 resendTransmit 的 request 給iPhone,iPhone收到後會將幀在 response 中補發回來。

SETUP rtsp://192.168.1.150/1561243076001349804 RTSP/1.0

Transport: RTP/AVP/UDP;unicast;mode=record;timing_port=55703;control_port=56616

CSeq: 4

DACP-ID: 4CB06073C86450D8

Active-Remote: 2937221397

User-Agent: AirPlay/373.9.1

圖2-2 SETUP報文

W5500EVB回覆的響應報文中的server_port, server port 用來傳輸音頻流數據包

RTSP/1.0 200 OK

CSeq: 4

Apple-Jack-Status: connected; type=analog

Transport: RTP/AVP/UDP;unicast;mode=record;timing_port=56461;events;control_port=51196;server_port=55641

Session:DEADBEEF

圖2-3 SETUP響應報文

 

   SETUP數據包肯定音頻流傳輸方式與傳輸端口號後,iPhone就開始發送音頻數據到W5500EVB指定的server_port 55641端口,W5500EVB接收音頻數據,經過解密過程後,咱們會獲得AAC編碼的音頻數據,播放器播放AAC數據還須要對其進行解碼,話很少說,直接經過部分代碼來講明音頻解密過程。

1 int  decode_audio_data(unsigned char *data, unsigned short

 2 datalen, int use_seqnum)

 3 {

 4     unsigned short seqnum;

 5     raop_buffer_entry_t entry;

 6     int encryptedlen;

 7     AES_CTX aes_ctx;

 8     int outputlen;

 9     /* Check packet data length is valid */

10     if (datalen < 12 || datalen > 1472) {

11         return -1;

12     }

13     /* Get correct seqnum for the packet */

14     if (use_seqnum) {

15         seqnum = (data[2] << 8) | data[3];

16     }

17     /* Update the raop_buffer entry header */

18     entry.flags = data[0];

19     entry.type = data[1];

20     entry.seqnum = seqnum;

21     entry.timestamp = (data[4] << 24) | (data[5] << 16) |

22                       (data[6] << 8) | data[7];

23     entry.ssrc = (data[8] << 24) | (data[9] << 16) |

24                  (data[10] << 8) | data[11];

25     entry.available = 1;

26     /* Decrypt audio data */

27     encryptedlen = (datalen-12)/16*16;

28     AES_set_key(&aes_ctx, aeskey, aesiv, AES_MODE_128);

29     AES_convert_key(&aes_ctx);

30     memset(packetbuf,0,sizeof(data));

31     AES_cbc_decrypt(&aes_ctx, &data[12], (uint8*)packetbuf,

32     encryptedlen);

33     memcpy(packetbuf+encryptedlen, &data[12+encryptedlen],

34     datalen-12-encryptedlen);

35     /* Decode ALAC audio data */

36     outputlen = audio_buffer_size;

37     alac_decode_frame(&alac, (uint8*)packetbuf ,audiobuf,

38     &outputlen);

39     entry.audio_buffer_len = outputlen;

40     return outputlen;

41 }

    在程序中W5500EVB經過UDP端口每收到數據包先會判斷數據包的長度是否小於12由於RTP的包頭爲12個字節,小於12字節就會直接丟棄掉,大於12字節且小於1472(UDP包的最大長度)就會經過31行AES_cbc_decrypt()函數的對數據解密而後把解密後的數據經過alac_decode_frame()函數轉換爲PCM5102A模塊可播放的數據並將數據存儲在audiobuf中等待發送給音頻模塊,返回可播放數據長度outputlen,該值在咱們初始化I2S的DMA功能時會用到。

三、音頻數據的播放

     音頻播放採用的是PCM5102A的DAC模塊,該模塊是經過I2S接口進行通訊,直接將解碼後的數據發送到PCM5102A模塊便可。爲了能與PCM512A模塊正常通訊要初始化W5500EVB的IIS接口,項目中中使用到的是I2S3接口,須要注意的是I2S3接口的時鐘腳PB3,該引腳默認爲JTAG的JTDO腳,初始化時須要禁止JTAG以使PB3可以做爲I2S的時鐘腳,初始化代碼以下所示:

 1 void I2S_Config(void)

 2 {

 3     I2S_InitTypeDef I2S_InitStructure;

 4     GPIO_InitTypeDef GPIO_InitStruct;

 5

 6     /*Init GPIO */

 7     RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);

 8     //SPI

 9     RCC_APB2PeriphClockCmd(

10     RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_

11     GPIOC|RCC_APB2Periph_AFIO, ENABLE);

12     GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);

13     /*GPIO_Pin7 --> I2S_MCK*/

14     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;

15     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

16     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

17     GPIO_Init(GPIOC, &GPIO_InitStruct);

18     /*GPIO_Pin_15 -->I2S3_WS*/

19     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;

20     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

21     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

22     GPIO_Init(GPIOA, &GPIO_InitStruct);

23     /*GPIO_Pin_3 -->I2S3_CK

24       GPIO_Pin_5 -->I2S3_SD

25       */

26     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_5;

27     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

28     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

29     GPIO_Init(GPIOB, &GPIO_InitStruct);

30     /*Init IIS*/

31     SPI_I2S_DeInit(SPI3);

32     I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;

33     I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;

34     I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;

35     I2S_InitStructure.I2S_MCLKOutput=I2S_MCLKOutput_Disable;

36     I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_44k;

37     /*I2S clock steady state is low level */

38     I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;

39     I2S_Init(SPI3, &I2S_InitStructure);

40     I2S_Cmd(SPI3, ENABLE);

41 }

    代碼12行處經過調用GPIO_PinRemapConfig()函數禁用JTAG, 32行處模式配置爲主設備發送I2S_Mode_MasterTx,通訊標準設置爲I2S Philips標準I2S_Standard_Phillips,數據格式爲標準16位格式I2S_DataFormat_16b,採樣頻率設置爲44kHz I2S_AudioFreq_44k, I2S時鐘線空閒狀態的爲低電平。

   爲了提升數據的傳輸速度與效率,要打開IIS的DMA發送功能,每次發送SPI_I2S_DMAReq_Tx 請求後會將指定的buf0內的數據發送到SPI3的DR數據寄存器。我該函數是buf0即爲存儲音頻數據的audiobuf, 由於咱們的數據是按照16bit傳送audiobuf內的數據爲uint8型因此 num值爲audiobuf內的有效數據長度/2。

1 void I2S2_TX_DMA_Init(u8* buf0,u16 num)

 2 {

 3   NVIC_InitTypeDef   NVIC_InitStructure;

 4   DMA_InitTypeDef  DMA_InitStructure;

 5   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

 6   DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&SPI3->DR);

 7   DMA_InitStructure.DMA_MemoryBaseAddr = (u32)buf0;

 8   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

 9   DMA_InitStructure.DMA_BufferSize = num;

10   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

11   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

12   DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;

13   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

14   DMA_InitStructure.DMA_Mode = DMA_Mode_Circular   ;

15   DMA_InitStructure.DMA_Priority = DMA_Priority_High;

16   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

17   DMA_Init(DMA2_Channel2, &DMA_InitStructure);

18   DMA_Cmd(DMA2_Channel2, ENABLE);

19   SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE);

20 }

    音頻流的處理過程爲經過UDP接收音頻數據包,而後對收到的數據包進行解碼,並將解碼後的數據存儲到audiobuf,經過I2S3的DMA功能將數據發送到PCM5102A模塊,代碼以下所示:

1 void do_raop(uint8 s)

 2 {

 3     int outputlen;

 4     uint8 ip[4];

 5     uint16 len, port;

 6     switch (getSn_SR(s)) {

 7     case SOCK_UDP:

 8         if ((len = getSn_RX_RSR(s)) > 0) {

 9             /*接收音頻數據*/

10             recvfrom(s,buffer,len,ip,&port);

11             /*解碼收到的音頻數據*/

12             outputlen=decode_audio_data(buffer, len ,1);

13             /*配置DMA*/

14             I2S2_TX_DMA_Init((uint8*)audiobuf,outputlen/2);

15         }

16         break;

17     case SOCK_CLOSED:

18         socket(s, Sn_MR_UDP,55641,0);

19         break;

20     }

21 }

代碼完成後就要進行硬件鏈接,因爲W5500EVB的SPI2口用來與W5500進行通訊因此咱們只能選擇I2S3接口,I2S,W5500EVB與PCM5102A模塊鏈接示意圖以下所示:

 

圖3-1硬件鏈接圖

 

將編譯好的程序下載到W5500EVB,將耳機插入PCM5102A模塊,用iPhone手機搜索並鏈接W5500EVB設備,點擊播放音樂就能夠用耳機聽音樂了。

本文的項目中只是簡單的實現了經過AirPlay播放音樂,因爲時間匆忙項目功能還能夠繼續優化例如對各個音樂播放器的兼容性問題,QQ音樂、網易音樂等實現都不太同樣本文項目中用的是網易雲音樂;音樂播放過程當中的音量設置問題,;音樂播放過程當中的噪音問題,因爲手上只有帶STM32F103 的W5500EVB開發板,f103芯片在運行加密解密時會比較慢RAM空間也比較小等。你們若是想要作的話建議選用處理速度快一些的芯片。

相關文章
相關標籤/搜索