环境
Android Studio Bumblebee, Mac mini 2014
问题描述
需要实现功能为:将 MP3 转成 PCM 并通过 UDP 传出去。
任务可分解为:
- 将 MP3 转成 PCM
- 将 PCM 通过 AudioTrack 播放以确保转换的 PCM 文件正常
- 将 PCM 通过 UDP 传出去。
问题分析
注:写文章引用来源一直力求引用原创,但下面很多引文没找到原创文献
1 将 MP3 转成 PCM
面向搜索引擎编程,Java 直接有转换用的 API: AudioSystem。转换方式参照 CSDN 博文。
这里有点要说明,如果直接使用,会提示找不到 javax.sound.sampled.AudioSystem
, 原因是 Android 自带的包覆盖掉了 JRE 里面的文件。
解决方法是将 JRE 中包添加到 Android 醒目的 ‘build.gradle’ 的依赖里面。-- 此处博文忘记引文地址了。
implementation files('/Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/rt.jar')
问题是,我使用AudioSystem.write()
转换数据,总是提示 could not write audio file: file type not supported
, 问题可能是 输出文件的 AudioFomat
设置有问题,但尝试了许久,没找到问题点。
不能总吊在一棵树上。此计不通,立马考虑其他方法。直接在外面将 MP3 转成 PCM。在网站即时工具箱 中将 MP3 转成 PCM. 该页面有很简单的转换设置项。转换后出来的文件,使用 AudioTrack
播放,出来的是滋滋滋的声音,网上是说可能是大小端的问题。但网页转换没有这个设置项,没法验证。办法二宣告失败。
网上另一种途径是使用 FFMPEG 将 MP3 转换成 PCM。把我树莓派搬出来。参考博文mp3文件转pcm文件,生成了 单通道-8k采样率 的 PCM 文件。但使用 ffplay
播放时报错 ‘could not initialize sdl - displayindex must be in the range 0 - -1
。首先安装 SDL。安装完成后,播放 PCM 仍旧有相同报错。貌似调试 SDL 也得大费周章。
2 使用 AudioTrack 播放以验证 PCM 正常
鉴于调试 SDL 也是不小的工程,遂停止使用 SDL 播放的尝试,直接在 Android 里面使用 AudioTrack 播放,正常,谢天谢地。
AudioTrack udioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_DEFAULT,
AudioFormat.ENCODING_PCM_16BIT, 15000, AudioTrack.MODE_STREAM);
binding.play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
pcmAudioTrack.play();
InputStream inputStream = null;
byte[] data = new byte[5000];
AssetManager assetManager = getAssets();
try {
inputStream = assetManager.open("dujia.pcm");
int length = 0;
while((length = inputStream.read(data))!= -1) {
pcmAudioTrack.write(data, 0, length);
SystemClock.sleep(100);
}
pcmAudioTrack.stop();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
});
3 将 PCM 通过 UDP 传出去
这里是与上一步类似,将 PCM 文件以流的方式读入并按照接收端定义的格式打包后通过 UDP 传出去。具体过程非重点,略过不表。
这里要强调两点:
-
为查看下通过 UDP 发出的包是否正确,需要比对下 PCM 二进制数据。在 Mac 系统中,可以在终端中使用
xxd
命令打开 PCM 文件。具体使用方式参见博文Linux xxd命令详解_angelasp的博客-CSDN博客_linux xxd. -
在获取连接到本机 WIFI 热点的设备 IP 时,使用方法参见博文Android获取实时连接热点的设备IP。代码如下:
public static ArrayList<String> getConnectedIP() {
ArrayList<String> connectedIP = new ArrayList<>();
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/net/arp"));
String line = bufferedReader.readLine();
while ((line = bufferedReader.readLine()) != null) {
String[] splitted = line.split(" +");
if (splitted.length >= 4) {
String ip = splitted[0];
connectedIP.add(ip);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return connectedIP;
}
需要注意两点:一是在 Android 10 及以上手机中可能提示没有权限,换低版本系统手机即可。二是在 Android Studio 终端 或者 Mac 终端 使用同样命令获取到设备IP:
adb shell
cat /proc/net/arp