做品已經完成,先上源碼:php
https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.ziphtml
全文包含三篇,這是第三篇,主要講述接收端程序的原理和過程。python
第一篇:基於Orangpi Zero和Linux ALSA實現WIFI無線音箱(一)linux
第二篇:基於Orangpi Zero和Linux ALSA實現WIFI無線音箱(二)git
如下是正文:github
在進行接收端程序開發前,首先要了解Orangpi Zero的聲音設備。windows
Orangpi能夠經過ALSA(The Advanced Linux Sound Architecture )訪問系統的聲音設備。數組
1、查找並肯定Orangpi Zero的聲音設備服務器
要使用ALSA,首先就是要能正確找到聲音設備,做者在使用alsa的時候,嘗試過使用armbian官網(連接地址)上三個不一樣的鏡像,發現有些armbian鏡像有問題,不知道是什麼緣由,分別是:架構
(1)、基於Ubuntu Xenial的Armbian鏡像,版本號3.4.113(下載地址)
(2)、基於Ubuntu Xenial的Armbian鏡像,版本號4.14.14(下載地址)
(3)、基於Debian Stretch的Armbian鏡像,版本號4.14.14(下載地址)
這三個鏡像中,只有第一個能找到聲卡設備,其餘兩個都提示無聲卡設備。做者只能使用第一個鏡像。
在armbian中,使用如下命令便可看到聲卡設備。
aplay -l
如上圖所示,在OrangePi Zero中,共有兩個聲卡設備,一個card0是audiocodec,指的是板載的TV接口,另外一個card1是sndhdmi,指的是HDMI輸出接口,其中card0是默認聲卡設備,由於TV接口在開發板上有直接引出,並且只需3線(左聲道、右聲道、地),本做品直接使用TV接口做爲音頻輸出。硬件電路如圖以下所示。
若是使用aplay命令顯示出來的card0不是咱們想要的默認聲卡設備,那就要進行更改了,更改方法能夠參考「linux alsa音頻架構的配置與使用」這個文章。
此外,alsa還有一個虛擬的配置界面,alsamixer,利用它能夠方便的設置聲卡音量、配置聲卡、靜音等功能,相似windows桌面右下角的聲音管理器。要打開alsamixer,直接使用alsamixer命令便可,具體的使用方法,能夠參考「Linux下的音量控制器alsamixer」這篇文章,界面以下圖所示。
alsamixer
設置以後,利用aplay命令測試一下可否播放音樂,若是TV接口播放音頻正常,接下來就能夠開始接收端的程序開發了。
#播放測試音樂 aplay test.wav
測試alsa正常後,接下來介紹接收端程序中須要使用到的socket和pyalsaaduio模塊。
2、socket模塊
socket模塊使用比較簡單,首先獲取本機IP,而後初始化socket爲UDP模式,並綁定IP地址和端口號,就能夠開始接受數據包了。主要涉及的函數包括:
#建立socket socket.socket([family[, type[, proto]]]) #鏈接遠程地址 socket.connect(address) #綁定socket的IP地址和端口號 socket.bind(address) #從socket接收數據包 socket.recvfrom(bufsize[, flags]) #關閉socket socket.close()
socket模塊的使用比較簡單,網上有不少範例,這裏再也不詳細說明。
3、pyalsaaudio模塊
pyalsaaudio(下載地址)是一個用於python中訪問ALSA API的模塊,利用這個模塊,用戶能夠很輕鬆的在程序中訪問Orangpi Zero的PCM和混音器設備,這個模塊的使用說明和範例在這個連接地址裏有。
一、安裝pyalsaaudio模塊
依次安裝python對應版本的setuptools、python-dev、libasoud2-dev和pyalsaaudio包便可。其中python-dev包與所使用的python版本有關,可使用python3 -V命令查看python版本,本做品armbian系統預裝了python3.5,因此要安裝python3.5-dev包。依次執行如下命令。
(1)、安裝python3-setuptools命令:
apt-get install python3-setuptools
(2)、安裝python3.5-dev命令:
apt-get install python3.5-dev
(3)、安裝libasoud2-dev命令:
apt-get install libasound2-dev
(4)最後,使用python的pip3命令安裝pyalsaaudio模塊:
pip3 install pyalsaaudio
(5)上一步中的pip3命令,是爲了與python2區分的,armbian中預安裝了python2和python3,做者使用的是python3,若是直接使用pip命令,系統就會給python2安裝pyalsaaudio模塊了,因此這裏須要注意。若是提示沒有pip3命令,那就須要使用如下命令安裝pip3。安裝以後就可使用pip3命令操做第4步了。
apt-get install python3-pip
4、接收端程序設計
接收端比較簡單,在Python環境下直接使用socket和pyalsaaudio模塊便可快速實現數據包的接收和播放,主要使用的pyalsaaudio模塊函數以下。
#默認的構造函數 #系統初始化alsa device,系統默認按如下參數配置:PCM、44.1kHz、雙通道、週期大小32幀 #Sample format: PCM_FORMAT_S16_LE #Rate: 44100 Hz #Channels: 2 #Period size: 32 frames class alsaaudio.PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1) #設置採樣率,以Hz爲單位。 #典型值是8000(電話)、16000、44100(CD音質)、48000(DVD音質)、96000 PCM.setrate(rate) #設置週期大小,用戶程序每次處理音頻數據的幀數, #即用戶程序每次要寫入(播放)/讀取(錄音)的數據大小 #以幀爲單位,一幀就是一次採樣的字節數 PCM.setperiodsize(period) #寫入待播放的音頻數據。 #data的數據長度必須是幀大小的整數倍, 而且等於週期大小。 #若是小於週期大小,則實際不會播放,直到程序把數據按照週期大小徹底寫入。 PCM.write(data)
在《基於Orangpi Zero和Linux ALSA實現WIFI無線音箱(二)》中,做者設定了發送來的數據包前40個字節爲識別數據格式的包頭,真正的音頻數據是從第41字節開始。包頭數據的40個字節,實際就是C裏的WAVEFORMATEX結構體,包含採樣率、通道數、位深度信息,在python中,須要對這個結構體(數據包的開始的40字節)的數據進行解析讀取,這樣,才能正確設置pyalsaaudio的PCM類對象。
要實現上述功能,在C裏,能夠直接把數據包首地址強制轉換成WAVEFORMATEX結構體類型的指針,再訪問各個成員變量便可,但是在python裏,沒有地址和指針的概念,須要使用struct模塊中的pack和unpack函數。
struct模塊的pcak和unpcak函數是用來處理C結構數據的,經過它們能夠實現對字節數組的解釋。例如WAVEFORMATEX結構體的第2~3字節(以0開始)爲通道數,第4~7字節爲採樣率,unpack函數能夠把這些字節數組按照設定的要求進行轉換。兩個函數的詳細用法,能夠參考「Python中struct.pack()和struct.unpack()用法詳細說明」這篇文章。
最後,接收端程序設計的流程和源碼以下:
一、初始化socket
二、初始化PCM類對象
三、從socket接受數據(阻塞式)
四、解釋數據包頭
五、每隔1s判斷數據包頭指定的格式跟當前格式是否一致,若是不一致,則關閉PCM類對象並從新初始化
六、播放從第41字節開始的音頻數據
注意:程序中音頻格式只作了對採樣率的判斷,沒對位深度、通道數等信息的判斷,有興趣的讀者能夠自行添加。
import socket import alsaaudio import struct import time #函數:獲取IP地址 def GetHostIP(): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('1.1.1.1', 80)) ip = s.getsockname()[0] finally: s.close() return ip #如下是主程序 RecCount = 0 #默認的PCM音頻格式,參考C裏面的WAVEFORMATEX結構體 #格式標識wFormatTag = 0xfe #通道數nChannels = 2 #採樣率nSamplesPerSec = 48000Hz #波特率nAvgBytesPerSec = 192000 #塊對齊nBlockAlign = 4 #位深度wBitsPerSample = 16 list_pwfx = [65534, 2, 48000, 192000, 4, 16] Local_IP=GetHostIP() print('說明') print('1.本機ip:%s:12321'%(Local_IP)) print('2.默認按照48000Hz、雙通道、16位PCM格式播放') print('3.發送端發出的數據包前40個字節爲音頻格式信息,接收端(本程序)每隔1s會解釋一次包頭,讀取並自動修改播放器採樣率信息(如發生變化)') print('4.注意:接收端(本程序)只支持1102五、12000、44100、48000這4種採樣率的自動切換,不支持修改通道數、位深度等其餘信息的切換。') print('5.發送端若是在後臺(如Windows的音頻管理器)修改了採樣率,必須從新點擊‘啓動’按鈕,才能從新發生正確的音頻流') #初始化socket sss = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sss.bind((Local_IP, 12321)) #系統初始化alsa device,系統默認按如下參數配置 #Sample format: PCM_FORMAT_S16_LE #Rate: 44100 Hz #Channels: 2 #Period size: 32 frames device = alsaaudio.PCM() #修改默認採樣率爲48kHz device.setrate(list_pwfx[2]) #修改緩衝區大小(以幀爲單位,0.1s是4800幀) device.setperiodsize(list_pwfx[2]//10) Lasttime =time.time() while 1: #申請20k字節緩衝區 BytesRecv,ServerAddr = sss.recvfrom(20000) #這裏是爲了讓程序自動更改播放音頻的採樣率,若是距離上次設置採樣率的時刻大於1s, #則讀取數據包的頭40個字節,判斷服務器傳過來的數據採樣率有無變化,從新設置採樣率, #只支持在1102五、12000、44100和48000間切換 Nowtime = time.time() if (Nowtime-Lasttime) > 1 : Lasttime = Nowtime #解釋包頭(只取前16字節),具體請參考C裏面的WAVEFORMATEX結構體或文件開頭的說明 #注意struct.unpack返回值是一個元組 tuple_pwfx_temp = struct.unpack('HHLLHH',BytesRecv[:16]) #print(tuple_pwfx_temp) if tuple_pwfx_temp[2] != list_pwfx[2]: print('採樣率發生變化!') if tuple_pwfx_temp[2] in [11025,12000,44100,48000]: #把元組轉換爲列表,再賦值修改採樣率 list_pwfx[2] =list(tuple_pwfx_temp)[2] #關閉設備並從新初始化設備 device.close() device = alsaaudio.PCM() device.setrate(list_pwfx[2]) device.setperiodsize(list_pwfx[2]//10) print('採樣率正確,修改採樣率爲%s'%(list_pwfx[2])) else: print('採樣率錯誤!'%(list_pwfx[2])) #將socket接收到的數據送到device播放 #收到的數據包,第41字節開始纔是音頻數據 device.write(BytesRecv[40:]) print('RecCount=%s'%(RecCount),end='\r') RecCount+=1 device.close() sss.close()
同時運行發送端程序和接收端程序,在發送端打開音樂播放器,這個時候,OrangPi接的音箱應該能播放音樂了。
5、設置python腳本開機自啓動
好了,最後一步就是把這個python腳本設定成開機自啓動,這樣就不用每次登陸OrangPi Zero運行這個腳本,linux下實現python腳本開機自動啓動的方法也簡單,「Linux下Python腳本自啓動與定時任務詳解」這個文章有詳細介紹,修改一下系統配置文件便可。