libGDX/Android:如何循环背景音乐而不产生可怕的间隙?

libGDX/Android:如何循环背景音乐而不产生可怕的间隙?,android,audio,libgdx,background-music,Android,Audio,Libgdx,Background Music,我正在使用libGDX,但我面临的问题是,背景音乐无法在各种Android设备上完美循环(例如运行棒棒糖的Nexus7)。每当轨道循环(即从末端跳到起点)时,可听到明显的间隙。现在我想知道背景音乐如何能在没有令人不安的间隙的情况下循环播放 我已经尝试过各种方法,如: 确保轨迹中的采样数是轨迹采样率的精确倍数(如本文某处所述) 各种音频格式,如.ogg、.m4a、.mp3和.wav(.ogg似乎是目前选择的解决方案,但不幸的是,它在我的情况下不起作用) 使用带setLooping(true)的A

我正在使用libGDX,但我面临的问题是,背景音乐无法在各种Android设备上完美循环(例如运行棒棒糖的Nexus7)。每当轨道循环(即从末端跳到起点)时,可听到明显的间隙。现在我想知道背景音乐如何能在没有令人不安的间隙的情况下循环播放

我已经尝试过各种方法,如:

  • 确保轨迹中的采样数是轨迹采样率的精确倍数(如本文某处所述)
  • 各种音频格式,如.ogg、.m4a、.mp3和.wav(.ogg似乎是目前选择的解决方案,但不幸的是,它在我的情况下不起作用)
  • 使用带setLooping(true)的Androids MediaPlayer代替libGDX音乐类
  • 已使用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(); }
        }
    
你知道这段代码有什么问题吗

仅用于记录:
结果证明这是无法解决的。最后,我们在文件中多次循环播放背景音乐。这样,差距就不那么频繁了。这不是问题的真正解决办法,但却是我们能找到的最好的解决办法。

这是一个老问题,但我会给出我的解决方案,以防有人遇到同样的问题

该解决方案需要使用音频扩展(已弃用,但效果很好),如果您无法在线找到链接,则我正在使用的JAR也需要一些外部存储空间

摘要如下

  • 使用解码器(ogg的VorbisDecoder类或mp3的MPG123解码器)提取原始音乐数据,并将其保存到外部存储器(您可以检查它是否已经存在,以便只需提取一次,因为这需要一些时间)
  • 使用刚刚保存到外部存储器的文件创建一个RandomAccessFile
  • 播放时,将RandomAccessFile指针设置为文件中的正确位置并读取数据段
  • 使用AudioDevice类播放上述数据段
  • 这里有一些代码

    提取音乐文件并将其保存到外部存储器,文件是内部音乐文件的文件句柄,这是一个ogg,这就是我们使用Vorbis解码器的原因

        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();
    
    创建一个缓冲区,这样我们就可以将从文件中读取的字节转换为一个短数组,该数组将馈送到音频设备

    public byte[] rafbufferBytes=new byte[length*2];
    public short[] rafbuffer=new short[length];
    public ShortBuffer sBuffer=ByteBuffer.wrap(rafbufferBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
    
        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();
    
    当我们想要播放这个文件时,我们会创建一个音频设备和一个新的线程,在这里我们不断地从raf文件中读取并将其提供给音频设备

    public byte[] rafbufferBytes=new byte[length*2];
    public short[] rafbuffer=new short[length];
    public ShortBuffer sBuffer=ByteBuffer.wrap(rafbufferBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
    
        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();
    

    看看这个:ExoPlayer。请看这里的重复问题:哇,2021年,这个问题得到了投票。真令人惊讶。好吧,2015年libGDX和RoboVM很有趣,但到2021年,你和Godot的关系会更好:你是如何得出这是无法解决的结论的?有什么特别的,或者只是因为你尝试了所有的东西,但都没有效果?Android bug tracker中有一个问题-是的,我尝试了所有可能的选项来循环背景音乐,然后放弃了。谢谢你的帮助。我没有试过,但无论如何我会接受你的回答。抱歉耽搁了五年半;-)