libGDX/Android: 如何循环背景音乐而没有可怕的间隙?
libGDX/Android: How to loop background music without the dreaded gap?
我正在使用 libGDX 并面临背景音乐无法在各种 Android 设备(例如 Nexus 7 运行 Lollipop)上完美循环的问题。每当曲目循环(即从结尾跳到开头)时,都能听到明显的间隙。现在我想知道如何循环播放背景音乐而没有令人不安的间隙?
我已经尝试过各种方法,例如:
- 确保轨道中的样本数量是轨道采样率的精确倍数(如 SO 中某处所述)。
- 各种音频格式,如 .ogg、.m4a、.mp3 和 .wav(.ogg 似乎是 SO 的首选解决方案,但不幸的是,它不适用于我的情况)。
- 使用 Androids MediaPlayer with setLooping(true) 而不是 libGDX Music class。
使用了 Androids MediaPlayer.setNextMediaPlayer()。代码如下所示,它播放两个音轨,中间没有间隙,但不幸的是,一旦第二个 MediaPlayer 结束,第一个就不会再次开始!
/* initialization */
afd = context.getAssets().openFd(filename);
firstBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
firstBackgroundMusic.prepare();
firstBackgroundMusic.setOnCompletionListener(this);
secondBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
secondBackgroundMusic.prepare();
secondBackgroundMusic.setOnCompletionListener(this);
firstBackgroundMusic.setNextMediaPlayer(secondBackgroundMusic);
secondBackgroundMusic.setNextMediaPlayer(firstBackgroundMusic);
firstBackgroundMusic.start();
@Override
public void onCompletion(MediaPlayer mp) {
mp.stop();
try {
mp.prepare();
} catch (IOException e) { e.printStackTrace(); }
}
知道代码片段有什么问题吗?
仅作记录:
调出来是无解的。最后,我们在文件中多次循环播放背景音乐。这样,差距出现的频率就会降低。这不是问题的真正解决方案,而是我们能找到的最佳解决方法。
这是一个老问题,但我会给出我的解决方案,以防有人遇到同样的问题。
该解决方案需要使用音频扩展(已弃用但工作正常),如果您无法在网上找到 link here 是我正在使用的罐子,还需要一些外部存储 space.
摘要如下
- 使用解码器(VorbisDecoder class for ogg 或 Mpg123Decoder for mp3)提取原始音乐数据并将它们保存到外部存储(您可以检查它是否已经存在,以便它只需要提取一次,因为它需要一些时间)。
- 使用刚刚保存到外部存储的文件创建一个 RandomAccessFile
- 播放时将 RandomAccessFile 指针设置为文件中的正确位置并读取数据段
- 用AudioDevice播放上面的数据段class
这是一些代码
提取音乐文件并保存到外部存储,file是内部音乐文件的FileHandle,这里是一个ogg,这就是我们使用VorbisDecoder的原因
FileHandle external=Gdx.files.external("data/com.package.name/music/"+file.name());
file.copyTo(external);
VorbisDecoder decoder = new VorbisDecoder(external);
FileHandle extreactedDataFile=Gdx.files.external("data/com.package.name/music/"+file.nameWithoutExtension()+".mdata");
if(extreactedDataFile.exists())extreactedDataFile.delete();
ShortBuffer sbuffer=ByteBuffer.wrap(shortBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
while(true){
if(LogoScreen.shouldBreakMusicLoad)break;
int num=decoder.readSamples(samples, 0,samples.length);
sbuffer.put(samples,0,num);
sbuffer.position(0);
extreactedDataFile.writeBytes(shortBytes,0,num*2, true);
if(num<=0)break;
}
external.delete();
创建一个指向我们刚刚创建的文件的 RandomAccessFile
if(extreactedDataFile.exists()){
try {
raf=new RandomAccessFile(Gdx.files.external(extreactedDataFile.path()).file(), "r");
raf.seek(0);
} catch (Exception e) {
e.printStackTrace();
}
}
创建一个缓冲区,以便我们可以将从文件读取的字节转换为一个短数组,该数组被提供给 AudioDevice
public byte[] rafbufferBytes=new byte[length*2];
public short[] rafbuffer=new short[length];
public ShortBuffer sBuffer=ByteBuffer.wrap(rafbufferBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
当我们想要播放文件时,我们创建一个 AudioDevice 和一个新线程,我们不断地读取 raf 文件并将其提供给 AudioDevice
device = Gdx.audio.newAudioDevice((int)rate/*the hrz of the music e.g 44100*/,isthemusicMONO?);
currentBytes=0;//set the file to the beggining
playbackThread = new Thread(new Runnable() {
@Override
public synchronized void run() {
while (playing) {
if(raf!=null){
int length=raf.read(rafbufferBytes);
if(length<=0){
ocl.onCompletion(DecodedMusic.this);
length=raf.read(rafbufferBytes);
}
sBuffer.get(rafbuffer);
sBuffer.position(0);
if(length>20){
try{
device.writeSamples(rafbuffer,0,length/2);
fft.spectrum(rafbuffer, spectrum);
currentBytes+=length;
}catch(com.badlogic.gdx.utils.GdxRuntimeException ex){
ex.printStackTrace();
device = Gdx.audio.newAudioDevice((int)(rate),MusicPlayer.mono);
}
}
}
}
}
});
playbackThread.setDaemon(true);
playbackThread.start();
而当我们想在某个位置寻找时
public void seek(float pos){
currentBytes=(int) (rate*pos);
try {
raf.seek(currentBytes*4);
} catch (IOException e) {
e.printStackTrace();
}
}
我正在使用 libGDX 并面临背景音乐无法在各种 Android 设备(例如 Nexus 7 运行 Lollipop)上完美循环的问题。每当曲目循环(即从结尾跳到开头)时,都能听到明显的间隙。现在我想知道如何循环播放背景音乐而没有令人不安的间隙?
我已经尝试过各种方法,例如:
- 确保轨道中的样本数量是轨道采样率的精确倍数(如 SO 中某处所述)。
- 各种音频格式,如 .ogg、.m4a、.mp3 和 .wav(.ogg 似乎是 SO 的首选解决方案,但不幸的是,它不适用于我的情况)。
- 使用 Androids MediaPlayer with setLooping(true) 而不是 libGDX Music class。
使用了 Androids MediaPlayer.setNextMediaPlayer()。代码如下所示,它播放两个音轨,中间没有间隙,但不幸的是,一旦第二个 MediaPlayer 结束,第一个就不会再次开始!
/* initialization */ afd = context.getAssets().openFd(filename); firstBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); firstBackgroundMusic.prepare(); firstBackgroundMusic.setOnCompletionListener(this); secondBackgroundMusic.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); secondBackgroundMusic.prepare(); secondBackgroundMusic.setOnCompletionListener(this); firstBackgroundMusic.setNextMediaPlayer(secondBackgroundMusic); secondBackgroundMusic.setNextMediaPlayer(firstBackgroundMusic); firstBackgroundMusic.start(); @Override public void onCompletion(MediaPlayer mp) { mp.stop(); try { mp.prepare(); } catch (IOException e) { e.printStackTrace(); } }
知道代码片段有什么问题吗?
仅作记录: 调出来是无解的。最后,我们在文件中多次循环播放背景音乐。这样,差距出现的频率就会降低。这不是问题的真正解决方案,而是我们能找到的最佳解决方法。
这是一个老问题,但我会给出我的解决方案,以防有人遇到同样的问题。
该解决方案需要使用音频扩展(已弃用但工作正常),如果您无法在网上找到 link here 是我正在使用的罐子,还需要一些外部存储 space.
摘要如下
- 使用解码器(VorbisDecoder class for ogg 或 Mpg123Decoder for mp3)提取原始音乐数据并将它们保存到外部存储(您可以检查它是否已经存在,以便它只需要提取一次,因为它需要一些时间)。
- 使用刚刚保存到外部存储的文件创建一个 RandomAccessFile
- 播放时将 RandomAccessFile 指针设置为文件中的正确位置并读取数据段
- 用AudioDevice播放上面的数据段class
这是一些代码
提取音乐文件并保存到外部存储,file是内部音乐文件的FileHandle,这里是一个ogg,这就是我们使用VorbisDecoder的原因
FileHandle external=Gdx.files.external("data/com.package.name/music/"+file.name());
file.copyTo(external);
VorbisDecoder decoder = new VorbisDecoder(external);
FileHandle extreactedDataFile=Gdx.files.external("data/com.package.name/music/"+file.nameWithoutExtension()+".mdata");
if(extreactedDataFile.exists())extreactedDataFile.delete();
ShortBuffer sbuffer=ByteBuffer.wrap(shortBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
while(true){
if(LogoScreen.shouldBreakMusicLoad)break;
int num=decoder.readSamples(samples, 0,samples.length);
sbuffer.put(samples,0,num);
sbuffer.position(0);
extreactedDataFile.writeBytes(shortBytes,0,num*2, true);
if(num<=0)break;
}
external.delete();
创建一个指向我们刚刚创建的文件的 RandomAccessFile
if(extreactedDataFile.exists()){
try {
raf=new RandomAccessFile(Gdx.files.external(extreactedDataFile.path()).file(), "r");
raf.seek(0);
} catch (Exception e) {
e.printStackTrace();
}
}
创建一个缓冲区,以便我们可以将从文件读取的字节转换为一个短数组,该数组被提供给 AudioDevice
public byte[] rafbufferBytes=new byte[length*2];
public short[] rafbuffer=new short[length];
public ShortBuffer sBuffer=ByteBuffer.wrap(rafbufferBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
当我们想要播放文件时,我们创建一个 AudioDevice 和一个新线程,我们不断地读取 raf 文件并将其提供给 AudioDevice
device = Gdx.audio.newAudioDevice((int)rate/*the hrz of the music e.g 44100*/,isthemusicMONO?);
currentBytes=0;//set the file to the beggining
playbackThread = new Thread(new Runnable() {
@Override
public synchronized void run() {
while (playing) {
if(raf!=null){
int length=raf.read(rafbufferBytes);
if(length<=0){
ocl.onCompletion(DecodedMusic.this);
length=raf.read(rafbufferBytes);
}
sBuffer.get(rafbuffer);
sBuffer.position(0);
if(length>20){
try{
device.writeSamples(rafbuffer,0,length/2);
fft.spectrum(rafbuffer, spectrum);
currentBytes+=length;
}catch(com.badlogic.gdx.utils.GdxRuntimeException ex){
ex.printStackTrace();
device = Gdx.audio.newAudioDevice((int)(rate),MusicPlayer.mono);
}
}
}
}
}
});
playbackThread.setDaemon(true);
playbackThread.start();
而当我们想在某个位置寻找时
public void seek(float pos){
currentBytes=(int) (rate*pos);
try {
raf.seek(currentBytes*4);
} catch (IOException e) {
e.printStackTrace();
}
}