作品已经完成,先上源码:

https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip

全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程。

第一篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一)

第二篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)

以下是正文:

  在进行接收端程序开发前,首先要了解Orangpi Zero的声音设备。

  Orangpi可以通过ALSA(The Advanced Linux Sound Architecture )访问系统的声音设备。

一、查找并确定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中,使用以下命令即可看到声卡设备。

  1. 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”这篇文章,界面如下图所示。

  1. alsamixer

  设置之后,利用aplay命令测试一下能否播放音乐,如果TV接口播放音频正常,接下来就可以开始接收端的程序开发了。

  1. #播放测试音乐
  2. aplay test.wav

  测试alsa正常后,接下来介绍接收端程序中需要使用到的socket和pyalsaaduio模块。

二、socket模块

  socket模块使用比较简单,首先获取本机IP,然后初始化socket为UDP模式,并绑定IP地址和端口号,就可以开始接受数据包了。主要涉及的函数包括:

  1. #创建socket
  2. socket.socket([family[, type[, proto]]])
  3.  
  4. #连接远程地址
  5. socket.connect(address)
  6.  
  7. #绑定socket的IP地址和端口号
  8. socket.bind(address)
  9.  
  10. #从socket接收数据包
  11. socket.recvfrom(bufsize[, flags])
  12.  
  13. #关闭socket
  14. socket.close()

  socket模块的使用比较简单,网上有很多范例,这里不再详细说明。

三、pyalsaaudio模块

  pyalsaaudio(下载地址)是一个用于python中访问ALSA API的模块,利用这个模块,用户可以很轻松的在程序中访问Orangpi Zero的PCM和混音器设备,这个模块的使用说明和范例在这个链接地址里有。

  1、安装pyalsaaudio模块

  依次安装python对应版本的setuptools、python-dev、libasoud2-dev和pyalsaaudio包即可。其中python-dev包与所使用的python版本有关,可以使用python3 -V命令查看python版本,本作品armbian系统预装了python3.5,所以要安装python3.5-dev包。依次执行以下命令。

  (1)、安装python3-setuptools命令:

  1. apt-get install python3-setuptools

  (2)、安装python3.5-dev命令:

  1. apt-get install python3.5-dev

  (3)、安装libasoud2-dev命令:

  1. apt-get install libasound2-dev

  (4)最后,使用python的pip3命令安装pyalsaaudio模块:

  1. pip3 install pyalsaaudio

  (5)上一步中的pip3命令,是为了与python2区分的,armbian中预安装了python2和python3,作者使用的是python3,如果直接使用pip命令,系统就会给python2安装pyalsaaudio模块了,所以这里需要注意。如果提示没有pip3命令,那就需要使用以下命令安装pip3。安装之后就可以使用pip3命令操作第4步了。

  1. apt-get install python3-pip

四、接收端程序设计

  接收端比较简单,在Python环境下直接使用socket和pyalsaaudio模块即可快速实现数据包的接收和播放,主要使用的pyalsaaudio模块函数如下。

  1. #默认的构造函数
  2. #系统初始化alsa device,系统默认按以下参数配置:PCM、44.1kHz、双通道、周期大小32帧
  3. #Sample format: PCM_FORMAT_S16_LE
  4. #Rate: 44100 Hz
  5. #Channels: 2
  6. #Period size: 32 frames
  7. class alsaaudio.PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1)
  8.  
  9. #设置采样率,以Hz为单位。
  10. #典型值是8000(电话)、16000、44100(CD音质)、48000(DVD音质)、96000
  11. PCM.setrate(rate)
  12.  
  13. #设置周期大小,用户程序每次处理音频数据的帧数,
  14. #即用户程序每次要写入(播放)/读取(录音)的数据大小
  15. #以帧为单位,一帧就是一次采样的字节数
  16. PCM.setperiodsize(period)
  17.  
  18. #写入待播放的音频数据。
  19. #data的数据长度必须是帧大小的整数倍, 并且等于周期大小。
  20. #如果小于周期大小,则实际不会播放,直到程序把数据按照周期大小完全写入。
  21. 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()用法详细说明”这篇文章。

  最后,接收端程序设计的流程和源码如下:

  1、初始化socket

  2、初始化PCM类对象

  3、从socket接受数据(阻塞式)

  4、解释数据包头

  5、每隔1s判断数据包头指定的格式跟当前格式是否一致,如果不一致,则关闭PCM类对象并重新初始化

  6、播放从第41字节开始的音频数据

  注意:程序中音频格式只做了对采样率的判断,没对位深度、通道数等信息的判断,有兴趣的读者可以自行添加。

  1. import socket
  2. import alsaaudio
  3. import struct
  4. import time
  5.  
  6. #函数:获取IP地址
  7. def GetHostIP():
  8. try:
  9. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  10. s.connect(('1.1.1.1', 80))
  11. ip = s.getsockname()[0]
  12. finally:
  13. s.close()
  14. return ip
  15.  
  16. #以下是主程序
  17. RecCount = 0
  18. #默认的PCM音频格式,参考C里面的WAVEFORMATEX结构体
  19. #格式标识wFormatTag = 0xfe
  20. #通道数nChannels = 2
  21. #采样率nSamplesPerSec = 48000Hz
  22. #波特率nAvgBytesPerSec = 192000
  23. #块对齐nBlockAlign = 4
  24. #位深度wBitsPerSample = 16
  25. list_pwfx = [65534, 2, 48000, 192000, 4, 16]
  26.  
  27. Local_IP=GetHostIP()
  28. print('说明')
  29. print('1.本机ip:%s:12321'%(Local_IP))
  30. print('2.默认按照48000Hz、双通道、16位PCM格式播放')
  31. print('3.发送端发出的数据包前40个字节为音频格式信息,接收端(本程序)每隔1s会解释一次包头,读取并自动修改播放器采样率信息(如发生变化)')
  32. print('4.注意:接收端(本程序)只支持11025、12000、44100、48000这4种采样率的自动切换,不支持修改通道数、位深度等其他信息的切换。')
  33. print('5.发送端如果在后台(如Windows的音频管理器)修改了采样率,必须重新点击‘启动’按钮,才能重新发生正确的音频流')
  34.  
  35. #初始化socket
  36. sss = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
  37. sss.bind((Local_IP, 12321))
  38.  
  39. #系统初始化alsa device,系统默认按以下参数配置
  40. #Sample format: PCM_FORMAT_S16_LE
  41. #Rate: 44100 Hz
  42. #Channels: 2
  43. #Period size: 32 frames
  44. device = alsaaudio.PCM()
  45.  
  46. #修改默认采样率为48kHz
  47. device.setrate(list_pwfx[2])
  48. #修改缓冲区大小(以帧为单位,0.1s是4800帧)
  49. device.setperiodsize(list_pwfx[2]//10)
  50.  
  51. Lasttime =time.time()
  52.  
  53. while 1:
  54.  
  55. #申请20k字节缓冲区
  56. BytesRecv,ServerAddr = sss.recvfrom(20000)
  57.  
  58. #这里是为了让程序自动更改播放音频的采样率,如果距离上次设置采样率的时刻大于1s,
  59. #则读取数据包的头40个字节,判断服务器传过来的数据采样率有无变化,重新设置采样率,
  60. #只支持在11025、12000、44100和48000间切换
  61. Nowtime = time.time()
  62. if (Nowtime-Lasttime) > 1 :
  63.  
  64. Lasttime = Nowtime
  65.  
  66. #解释包头(只取前16字节),具体请参考C里面的WAVEFORMATEX结构体或文件开头的说明
  67. #注意struct.unpack返回值是一个元组
  68. tuple_pwfx_temp = struct.unpack('HHLLHH',BytesRecv[:16])
  69. #print(tuple_pwfx_temp)
  70. if tuple_pwfx_temp[2] != list_pwfx[2]:
  71. print('采样率发生变化!')
  72. if tuple_pwfx_temp[2] in [11025,12000,44100,48000]:
  73. #把元组转换为列表,再赋值修改采样率
  74. list_pwfx[2] =list(tuple_pwfx_temp)[2]
  75.  
  76. #关闭设备并重新初始化设备
  77. device.close()
  78. device = alsaaudio.PCM()
  79. device.setrate(list_pwfx[2])
  80. device.setperiodsize(list_pwfx[2]//10)
  81.  
  82. print('采样率正确,修改采样率为%s'%(list_pwfx[2]))
  83. else:
  84. print('采样率错误!'%(list_pwfx[2]))
  85.  
  86. #将socket接收到的数据送到device播放
  87. #收到的数据包,第41字节开始才是音频数据
  88. device.write(BytesRecv[40:])
  89.  
  90. print('RecCount=%s'%(RecCount),end='\r')
  91. RecCount+=1
  92.  
  93. device.close()
  94. sss.close()

  同时运行发送端程序和接收端程序,在发送端打开音乐播放器,这个时候,OrangPi接的音箱应该能播放音乐了。

五、设置python脚本开机自启动

  好了,最后一步就是把这个python脚本设定成开机自启动,这样就不用每次登录OrangPi Zero运行这个脚本,linux下实现python脚本开机自动启动的方法也简单,“Linux下Python脚本自启动与定时任务详解”这个文章有详细介绍,修改一下系统配置文件即可。

基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)的更多相关文章

  1. 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一)

    作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第一篇,作为前言和概述. 第二篇:基于Oran ...

  2. 基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)

    作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第二篇,主要讲述发送端程序的原理和过程. 第一 ...

  3. 基于Linux ALSA音频驱动的wav文件解析及播放程序 2012

    本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重 ...

  4. 菜鸟学习物联网---辨析基于Andriod 5.1,Linux,Windows10开发Dragon Board 410c板

    点击打开链接 诸位亲最近怎么样?刚过完年上班是不是很不情愿?自古做事者,不唯有坚韧不拔之志,亦或有超世之才.所以,诸位好好加油.今天小编想给大家系统性总结一下Dragon Board 410c板基于A ...

  5. Kali Linux下破解WIFI密码挂载usb无线网卡的方法

    Kali Linux下破解WIFI密码挂载usb无线网卡的方法 时间:2014-10-12    来源:服务器之家    投稿:root 首先我要说的是,wifi密码的破解不是想象中的那么容易,目前还 ...

  6. 基于VMware的虚拟Linux集群搭建-lvs+keepalived

    基于VMware的虚拟Linux集群搭建-lvs+keepalived 本文通过keepalived实现lvsserver的的双机热备和真实server之间的负载均衡.这方面的blog挺多,可是每一个 ...

  7. [原创]基于Zynq AXI-Bram Standalone & Linux 例程

    基于Zynq AXI-Bram Standalone & Linux 例程 待添加完善中

  8. [原创]基于Zynq AXI-GPIO Standalone & Linux 例程

    基于Zynq AXI-GPIO Standalone & Linux 例程 待添加完善中

  9. Linux学习笔记——基于鸟哥的Linux私房菜

    Linux学习笔记--基于鸟哥的Linux私房菜 ***** ARM与嵌入式linux的入门建议 (1) 学习基本的裸机编程:ARM7或ARM9,理解硬件架构和控制原理 (这一步是绝对的根基) (2) ...

随机推荐

  1. neo4j-rest-client使用摘要

    1.使用它的原因,与django搭配的最好的neomodel目前只支持到v2.2,我已给官方发了issue,官方也回复了,马上修改并发布(老外对开源项目的负责态度让人感动) 2.这个库的文档中大概描述 ...

  2. ZAB协议(Zookeeper atomic Broadcast)

    一.简语: ZAB协议是Paxos算法的经典实现 二.ZAB协议的两种模式: 崩溃恢复: 1.每个server都有一张选票(myid,zxid),选票投给自己 2.收集所有server的投票 3.比较 ...

  3. Ocelot中文文档-路由

    Ocelot的主要功能是接管进入的http请求并把它们转发给下游服务.目前是以另一个http请求的形式(将来可能是任何传输机制). Ocelot将路由一个请求到另一个请求描述为ReRoute.为了在O ...

  4. JQuery(三)-- AJAX的深入理解以及JQuery的使用

    HTTP HTTP http: 超文本传输协议.特点:  简单.快速.灵活.无状态.无连接 URL: 统一资源定位符. 组成:协议名://主机IP:端口号/项目资源地址?传递参数的键值对#锚点 ①ip ...

  5. 16.git命令汇总

  6. Flask入门之自定义过滤器(匹配器)

    1.  动态路由的匹配器? 不知道这种叫啥名,啥用法,暂且叫做匹配器吧. Flask自带的匹配器可以说有四种吧(保守数字,就我学到的) 动态路由本身,可以传任何参数字符串或者数字,如:<user ...

  7. cnblog 模板 SimpleMemory 个性化设置代码备份

    /页面顶部作者名/ blogTitle h1 { font-size: 50px; margin-top: 0px; } /页面简介/ blogTitle h2 { letter-spacing: 1 ...

  8. Robot Framework之测试用例分层实战

    1.1  测试用例的第一层(交互层) 1. 创建项目资源(Resource). 操作步骤: 点”项目名称”->右键,选New Resource,在弹窗Name 输入框输入资源名称 mykeywo ...

  9. .NET开发微信小程序-上传图片到服务器

    1.上传图片分为几种: a:上传图片到本地(永久保存) b:上传图片到本地(临时保存) c:上传图片到服务器 a和b在小程序的api文档里面有.直接说C:上传图片到服务器 前端代码: /* 上传图片到 ...

  10. DX11 Without DirectX SDK--03 渲染一个立方体

    回到 DirectX11--使用Windows SDK来进行开发 一个立方体有8个顶点,然而绘制一个立方体需要画12个三角形,如果按照前面的方法绘制的话,则需要提供36个顶点,而且这里面的顶点数据会重 ...