无法在Android上实现Gaplessaudio循环

我已经尝试了几乎所有的方法,但是我无法在循环单个音轨之间实现无间断的audio播放,持续时间为10-15秒。

我尝试过并失败的步骤:

  1. 不同的audio文件格式.mp3 .wav .ogg使用setLooping(true)

     MediaPlayer mp1 = MediaPlayer.create(MainActivity.this, R.raw.track1); mp1.setLooping(true); mp1.start(); 
  2. 创build两个setOnCompletionListener播放器并使用setOnCompletionListener一个接一个地循环不能没有间隙地循环。

  3. 使用setNextMediaPlayer(nextmp)一些如何工作,但只有两个循环是可能的。 在完成前两个循环后,我们必须准备并重新开始。

     mp1.start(); mp1.setNextMediaPlayer(mp2); 
  4. 更新: @Jeff Mixon的结果回答: Mediaplayer循环停止,出现Android错误 。 杰夫的Mixon工作正常,但只有10或20循环后,由于一些垃圾收集问题的媒体播放器立即停止离开日志下面张贴。 我真的有点在这里呆了两年。 提前致谢。

     E/MediaPlayer(24311): error (1, -38) E/MediaPlayer(23256): Error(1,-1007) E/MediaPlayer(23546): Error (1,-2147483648) 

Solutions Collecting From Web of "无法在Android上实现Gaplessaudio循环"

从我所做的testing中,这个解决scheme可以正常工作,超过150个循环,13秒160 kbps的MP3没有任何问题:

 public class LoopMediaPlayer { public static final String TAG = LoopMediaPlayer.class.getSimpleName(); private Context mContext = null; private int mResId = 0; private int mCounter = 1; private MediaPlayer mCurrentPlayer = null; private MediaPlayer mNextPlayer = null; public static LoopMediaPlayer create(Context context, int resId) { return new LoopMediaPlayer(context, resId); } private LoopMediaPlayer(Context context, int resId) { mContext = context; mResId = resId; mCurrentPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mCurrentPlayer.start(); } }); createNextMediaPlayer(); } private void createNextMediaPlayer() { mNextPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setNextMediaPlayer(mNextPlayer); mCurrentPlayer.setOnCompletionListener(onCompletionListener); } private MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.release(); mCurrentPlayer = mNextPlayer; createNextMediaPlayer(); Log.d(TAG, String.format("Loop #%d", ++mCounter)); } }; } 

要使用LoopMediaPlayer您只需调用:

 LoopMediaPlayer.create(context, R.raw.sample); 

丑陋的概念validation码,但你会明白:

 // Will need this in the callbacks final AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.sample); // Build and start first player final MediaPlayer player1 = MediaPlayer.create(this, R.raw.sample); player1.start(); // Ready second player final MediaPlayer player2 = MediaPlayer.create(this, R.raw.sample); player1.setNextMediaPlayer(player2); player1.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { // When player1 completes, we reset it, and set up player2 to go back to player1 when it's done mediaPlayer.reset(); try { mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mediaPlayer.prepare(); } catch (Exception e) { e.printStackTrace(); } player2.setNextMediaPlayer(player1); } }); player2.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { // Likewise, when player2 completes, we reset it and tell it player1 to user player2 after it's finished again mediaPlayer.reset(); try { mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mediaPlayer.prepare(); } catch (Exception e) { e.printStackTrace(); } player1.setNextMediaPlayer(player2); } }); // This loop repeats itself endlessly in this fashion without gaps 

这对我来说是一个API 19设备和一个5秒128 kbps的MP3。 循环中没有空白。

像这样的东西应该工作。 在res.raw目录中保存同一文件的两个副本。 请注意,这只是一个POC,而不是一个优化的代码。 我只是testing了这一点,它正在按预期工作。 让我知道你的想法。

 public class MainActivity extends Activity { MediaPlayer mp1; MediaPlayer mp2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mp1 = MediaPlayer.create(MainActivity.this, R.raw.demo); mp2 = MediaPlayer.create(MainActivity.this, R.raw.demo2); mp1.start(); Thread thread = new Thread(new Runnable() { @Override public void run() { int duration = mp1.getDuration(); while (mp1.isPlaying() || mp2.isPlaying()) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } duration = duration - 100; if (duration < 1000) { if (mp1.isPlaying()) { mp2.start(); mp1.reset(); mp1 = MediaPlayer.create(MainActivity.this, R.raw.demo); duration = mp2.getDuration(); } else { mp1.start(); mp2.reset(); mp2 = MediaPlayer.create(MainActivity.this, R.raw.demo2); duration = mp1.getDuration(); } } } } }); thread.start(); } } 

我build议你使用SoundPool API而不是MediaPlayer

从官方文档:

SoundPool类pipe理和播放应用程序的audio资源。

声音可以通过设置一个非零的循环值来循环。 值为-1会导致声音循环永久。 在这种情况下,应用程序必须显式调用stop()函数来停止声音。 任何其他非零值将导致声音重复指定的次数,例如,值为3将导致声音总共播放4次。

在这里看看如何使用SoundPool的实际例子。

至less从KitKat开始, Mattia Maestrini的答案 (对于这个问题)是我发现唯一解决scheme,它允许一个大的(> 1Mb未压缩的)audio样本的无间隙循环 。 我试过了:

  • .setLooping(true):给出了interloop噪声或暂停,即使完全修剪.WAV示例( Android中发布的错误 );
  • OGG格式 :无边框格式,比MP3好,但MediaPlayer仍然发出interloop文物; 和
  • SoundPool :可能适用于小声音样本,但大量样本会导致堆大小溢出 。

通过在我的项目中简单地包括Maestrini的LoopMediaPlayer类,然后用LoopMediaPlayer.create()调用replace我的MediaPlayer.create() LoopMediaPlayer.create()调用,我可以确保我的.OGG示例是无缝循环的。 LoopMediaPlayer因此是一个非常实用和透明的解决scheme。

但是,这种透明度引发了一个问题:一旦我交换我的MediaPlayer调用LoopMediaPlayer调用,我的实例如何调用MediaPlayer方法,如。 isPlaying.pause.setVolume以下是我对这个问题的解决scheme 。 也许可以通过比我自己更懂Java的人来改进(我欢迎他们的意见),但到目前为止,我发现这是一个可靠的解决scheme。

我对Maestrini的课程所作的唯一修改(除了Lint推荐的一些调整之外)在下面代码的最后标记了。 其余我包括上下文。 我的补充是通过在LoopMediaPlayer调用它们,在mCurrentPlayer实现MediaPlayer几种方法。

警告:虽然我在下面实现了几个有用的MediaPlayer方法,但是我没有实现所有这些方法。 因此,如果您期望例如调用.attachAuxEffect ,则需要将其作为LoopMediaPlayer一种方法添加到我所添加的行中。 确保复制这些方法的原始接口(即参数,抛出和返回):

 public class LoopMediaPlayer { private static final String TAG = LoopMediaPlayer.class.getSimpleName(); private Context mContext = null; private int mResId = 0; private int mCounter = 1; private MediaPlayer mCurrentPlayer = null; private MediaPlayer mNextPlayer = null; public static LoopMediaPlayer create(Context context, int resId) { return new LoopMediaPlayer(context, resId); } private LoopMediaPlayer(Context context, int resId) { mContext = context; mResId = resId; mCurrentPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mCurrentPlayer.start(); } }); createNextMediaPlayer(); } private void createNextMediaPlayer() { mNextPlayer = MediaPlayer.create(mContext, mResId); mCurrentPlayer.setNextMediaPlayer(mNextPlayer); mCurrentPlayer.setOnCompletionListener(onCompletionListener); } private final MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.release(); mCurrentPlayer = mNextPlayer; createNextMediaPlayer(); Log.d(TAG, String.format("Loop #%d", ++mCounter)); } }; // code-read additions: public boolean isPlaying() throws IllegalStateException { return mCurrentPlayer.isPlaying(); } public void setVolume(float leftVolume, float rightVolume) { mCurrentPlayer.setVolume(leftVolume, rightVolume); } public void start() throws IllegalStateException { mCurrentPlayer.start(); } public void stop() throws IllegalStateException { mCurrentPlayer.stop(); } public void pause() throws IllegalStateException { mCurrentPlayer.pause(); } public void release() { mCurrentPlayer.release(); mNextPlayer.release(); } public void reset() { mCurrentPlayer.reset(); } } 

出于某种原因,我发现我的“OnCompletion”事件在试图循环一个8秒的OGG文件时总是了几秒钟。 对于任何遇到此类延迟的人,请尝试以下操作。

在以前的解决scheme中推荐使用“nextMediaPlayer”强制排队 ,只需将延迟的Runnable发布到MediaPlayers的Handler,并避免在onCompletion事件中循环

我的160kbps 8秒OGG,最小API 16对我来说完美无瑕。

在您的活动/服务的某个地方,创build一个HandlerThread&Handler

 private HandlerThread SongLooperThread = new HandlerThread("SongLooperThread"); private Handler SongLooperHandler; public void startSongLooperThread(){ SongLooperThread.start(); Looper looper = SongLooperThread.getLooper(); SongLooperHandler = new Handler(looper){ @Override public void handleMessage(Message msg){ //do whatever... } } } public void stopSongLooperThread(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){ SongLooperThread.quit(); } else { SongLooperThread.quitSafely(); } }` 

开始线程 ,声明和设置您的MediaPlayers

 @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); startSongLooperThread(); activeSongResID = R.raw.some_loop; activeMP = MediaPlayer.create(getApplicationContext(), activeSongResID); activeSongMilliseconds = activeMP.getDuration(); queuedMP = MediaPlayer.create(getApplicationContext(),activeSongResID); } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); stopSongLooperThread(); activeMP.release(); queuedMP.release(); activeMP = null; queuedMP = null; } 

…创build一个交换 MediaPlayers的方法

 private void swapActivePlayers(){ Log.v("SongLooperService","MediaPlayer swap started...."); queuedMP.start(); //Immediately get the Duration of the current track, then queue the next swap. activeSongMilliseconds = queuedMP.getDuration(); SongLooperHandler.postDelayed(timedQueue,activeSongMilliseconds); Log.v("SongLooperService","Next call queued..."); activeMP.release(); //Swap your active and queued MPs... Log.v("SongLooperService","MediaPlayers swapping...."); MediaPlayer temp = activeMP; activeMP = queuedMP; queuedMP = temp; //Prepare your now invalid queuedMP... queuedMP = MediaPlayer.create(getApplicationContext(),activeSongResID); Log.v("SongLooperService","MediaPlayer swapped."); } 

…创buildRunnables发布到您的线程…

 private Runnable startMP = new Runnable(){ public void run(){ activeMP.start(); SongLooperHandler.postDelayed(timedQueue,activeSongMilliseconds); } }; private Runnable timedQueue = new Runnable(){ public void run(){ swapActivePlayers(); } }; 

在您的服务的onStartCommand()或您的活动中的某个地方,启动MediaPlayer …

 ... SongLooperHandler.post(startMP); ...