Java Android:音频曲目(流模式)在写入之间切换
我的主要目标是能够在局域网中将音频从一个设备流到另一个设备。我计划通过将mp3文件读入一个字节[](我已经开始工作)并将其作为udp数据包发送到第二台设备并在那里播放(我告诉您,以防这已经是错误的方法)。我目前一直在玩字节数组。我使用mp3中的Java Android:音频曲目(流模式)在写入之间切换,java,android,multithreading,audio,audiotrack,Java,Android,Multithreading,Audio,Audiotrack,我的主要目标是能够在局域网中将音频从一个设备流到另一个设备。我计划通过将mp3文件读入一个字节[](我已经开始工作)并将其作为udp数据包发送到第二台设备并在那里播放(我告诉您,以防这已经是错误的方法)。我目前一直在玩字节数组。我使用mp3中的解码器(path、startMs、durationMs)功能读取文件。目前,我能够听到音频,但在每次滴答声(我阅读文件的部分)后,我都会听到一些ms的“无”音,这会导致不良的听力体验。我认为这与缓冲区大小有关,并试着对其进行一些调整,但这并没有真正改变什么
解码器(path、startMs、durationMs)
功能读取文件。目前,我能够听到音频,但在每次滴答声(我阅读文件的部分)后,我都会听到一些ms的“无”音,这会导致不良的听力体验。我认为这与缓冲区大小有关,并试着对其进行一些调整,但这并没有真正改变什么,也没有添加音轨。我还考虑将每个for()-循环放在不同的线程中,但这根本不起作用(这是有道理的)。我也尝试过先读取文件,然后将字节[]放入Arraylist,因为这可能是由于文件读取速度慢而导致的问题,但仍然是相同的体验。了解Log.e(“DEBUG”,“Length”+data.Length”)也可能有所帮助
只在每个勾号上显示,这意味着每次勾号上都只会发生写入操作(这可能就是问题所在)。我怎样才能摆脱歌曲中的这些空白部分
以下是单击按钮时执行的代码:
song.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Thread thrd = new Thread(new Runnable() {
@Override
public void run() {
try {
int tick = 1000;
int max = 9000;
int sampleRate = 44100;
int bufSize = AudioTrack.getMinBufferSize(sampleRate*4, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
byte[] data = decode(path, 0, tick);
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
44100, AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, bufSize,
AudioTrack.MODE_STREAM, AudioTrack.WRITE_NON_BLOCKING);
track.play();
track.write(data, 0, data.length);
Log.e("DEBUG", "Length " + data.length);
for(int i = tick; i < max; i+=tick) {
data = decode(path, i, tick);
track.write(data, 0, data.length);
Log.e("DEBUG", "Length " + data.length);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
thrd.start();
}
});
我认为decode函数不是问题所在,因为返回的字节[]似乎相当好。也许阅读过程可以像以后一样优化,当我真的流式播放音频并总是阅读大约10毫秒的部分时,总是打开和关闭文件可能是一个问题。导致这一问题的根本原因是:您使用的decode()
函数不是专门为其设计的。即使出现“代码>引号())/代码>将允许您以随机访问方式解码MP3流的任何部分,但实际上,返回的音频的前几毫秒始终是静默的,无论是从歌曲的开头开始,还是从中间开始。这种沉默造成了“鸿沟”。显然,decode()
函数更多的是用于在随机位置重新开始播放,例如由于用户“seek”
decode()
的行为是这样的,因为为了解码第N个压缩数据块,解码器需要块N-1和块N。对应于块N的解压缩数据将是良好的,但块N-1的数据将具有这种“淡入”声音。这是.mp3解码器的一个通用功能,我知道AAC也有这种情况。同时,解码块N+1、N+2、N+3等没有问题,因为在每种情况下,解码器已经具有前一块
一种解决方案是更改decode()
函数:
private Decoder decoder;
private float totalMs;
private Bitstream bitstream;
private InputStream inputStream;
//call this once, when it is time to start a new song:
private void startNewSong(String path) throws IOException
{
decoder = new Decoder();
totalMs = 0;
File file = new File(path);
inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
bitstream = new Bitstream(inputStream);
}
private byte[] decode(String path, int startMs, int maxMs)
throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);
try {
boolean done = false;
while (! done) {
Header frameHeader = bitstream.readFrame();
if (frameHeader == null) {
done = true;
inputStream.close(); //Note this change. Now, the song is done. You can also clean up the decoder here.
} else {
totalMs += frameHeader.ms_per_frame();
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
if (output.getSampleFrequency() != 44100
|| output.getChannelCount() != 2) {
Log.w("ERROR", "mono or non-44100 MP3 not supported");
}
short[] pcm = output.getBuffer();
for (short s : pcm) {
outStream.write(s & 0xff);
outStream.write((s >> 8) & 0xff);
}
if (totalMs >= (startMs + maxMs)) {
done = true;
}
}
bitstream.closeFrame();
}
} catch (BitstreamException e) {
throw new IOException("Bitstream error: " + e);
} catch (DecoderException e) {
Log.w("ERROR", "Decoder error", e);
}
return outStream.toByteArray();
}
这段代码有点粗糙,需要改进。但是一般的方法是,每次使用一个新的方法来解码更多的歌曲,而不是随机访问,decode()
;再多读一点文件,再向解码器发送几个块。由于解码器(和位流
)状态在每次调用decode()
之间保持不变,因此无需搜索块N-1
UDP和流媒体
UDP方法的有效性取决于很多因素。您可能需要寻找其他问题来解决这个问题。UDP可以方便地广播到给定子网上的多个设备,但它不能帮助您确保按顺序接收数据包,或者根本不能。您可能需要TCP。还考虑是否要传输已编码的mp3块(那些由代码> >比特流.Read Frror()/代码>返回的)或解压缩音频块。还要考虑如何处理网络延迟、断开连接和缓冲。这里有许多困难的设计决策,每个选择都有利弊。祝你好运。请添加你的decode()
函数的实现。@greeble31谢谢你的反馈,decode()现在包括在内了!注意你正在使用的AudioTrack
构造器;您的最后一个参数实际上是不匹配的。你所有的写操作都被阻塞了。为了使您选择的线程方案能够工作,它们必须是。您假设write()
准确返回请求的字节数,因此您不希望有任何“短写入”。顺便说一下,您的写入需要是阻塞的,而不是非非阻塞的。这就是我第二条评论的意思。或者,每次你从decode()
得到一个结果时,你可以将它转换成一个方波,用这个模式替换数组的内容,端到端重复,覆盖整个数组:[127,127,127,127,127,127,127,0,0,0,0,0]
。这将产生一个连续的、高调的音调,没有任何间隙。如果在你将音频转换成9秒长的方波后,仍然存在间隙,那么我肯定错了。您可以使用for
循环来完成它:如果(((i/16)%2)==0)数据[i]=127;else数据[i]=0代码>。
private Decoder decoder;
private float totalMs;
private Bitstream bitstream;
private InputStream inputStream;
//call this once, when it is time to start a new song:
private void startNewSong(String path) throws IOException
{
decoder = new Decoder();
totalMs = 0;
File file = new File(path);
inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
bitstream = new Bitstream(inputStream);
}
private byte[] decode(String path, int startMs, int maxMs)
throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);
try {
boolean done = false;
while (! done) {
Header frameHeader = bitstream.readFrame();
if (frameHeader == null) {
done = true;
inputStream.close(); //Note this change. Now, the song is done. You can also clean up the decoder here.
} else {
totalMs += frameHeader.ms_per_frame();
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
if (output.getSampleFrequency() != 44100
|| output.getChannelCount() != 2) {
Log.w("ERROR", "mono or non-44100 MP3 not supported");
}
short[] pcm = output.getBuffer();
for (short s : pcm) {
outStream.write(s & 0xff);
outStream.write((s >> 8) & 0xff);
}
if (totalMs >= (startMs + maxMs)) {
done = true;
}
}
bitstream.closeFrame();
}
} catch (BitstreamException e) {
throw new IOException("Bitstream error: " + e);
} catch (DecoderException e) {
Log.w("ERROR", "Decoder error", e);
}
return outStream.toByteArray();
}