如何跟踪音频播放位置?

我创建了一个线程,通过将其转换为字节数组来播放Java中的mp3文件。

我想知道我是否可以在播放mp3时跟踪当前的播放位置。

首先,我设置了我的音乐流:

try { AudioInputStream in = AudioSystem.getAudioInputStream(file); musicInputStream = AudioSystem.getAudioInputStream(MUSIC_FORMAT, in); DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, MUSIC_FORMAT); musicDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); musicDataLine.open(MUSIC_FORMAT); musicDataLine.start(); startMusicThread(); } catch(Exception e) { e.printStackTrace(); } 

接下来,我的音乐线程如下所示:

 private class MusicThread extends Thread { byte musicBuffer[] = new byte[BUFFER_SIZE]; public void run() { try { int musicCount = 0; while(writeOutput){ if(writeMusic && (musicCount = musicInputStream.read(musicBuffer, 0, musicBuffer.length)) > 0){ musicDataLine.write(musicBuffer, 0, musicCount); } } } catch (Exception e) { System.out.println("AudioStream Exception - Music Thread"+e); e.printStackTrace(); } } } 

我想到了一种可能性,用一个计时器创建另一个线程,慢慢地滴答,秒,秒,以显示mp3歌曲的剩余时间。 但这似乎不是一个好的解决方案。

你的int musicCount (来自AudioInputStream.read(...)的返回值)告诉你读取的字节数,所以你可以做一个小的计算来总是找出你在流中的位置。 ( DataLine有一些方法可以为你做一些数学运算,但不能总是使用它们……见下文。)

 int musicCount = 0; int totalBytes = 0; while ( loop stuff ) { // accumulate it // and do whatever you need with it totalBytes += musicCount; musicDataLine.write(...); } 

要获得经过的秒数,您可以执行以下操作:

 AudioFormat fmt = musicInputStream.getFormat(); long framesRead = totalBytes / fmt.getFrameSize(); long totalFrames = musicInputStream.getFrameLength(); double totalSeconds = (double) totalFrames / fmt.getSampleRate(); double elapsedSeconds = ((double) framesRead / (double) totalFrames) * totalSeconds; 

因此,您只需获取每个循环所用的时间,并将其放在您需要的任何地方。 请注意,此类精度取决于缓冲区的大小。 缓冲区越小,越准确。

另外, Clip有一些方法可以为你查询(但你可能不得不改变你正在做的事情)。

这些方法( get(Long)FramePosition / getMicrosecondPosition )inheritance自DataLine ,因此如果您不想自己进行数学运算,也可以在SourceDataLine上调用它们。 但是 ,您基本上需要为您播放的每个文件创建一个新行,因此这取决于您使用该行的方式。 (就个人而言,我更愿意自己做分工,因为要求线条是不透明的。)


BTW:

 musicDataLine.open(MUSIC_FORMAT); 

您应该使用(AudioFormat, int)重载打开指定自己的缓冲区大小的行。 SourceDataLine.write(...)仅在其内部缓冲区已满时阻塞,因此如果它与字节数组的大小不同,有时您的循环会阻塞,有时它只是旋转。


MCVE的好措施:

SimplePlaybackProgress

 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.sound.sampled.*; public class SimplePlaybackProgress extends WindowAdapter implements Runnable, ActionListener { class AudioPlayer extends Thread { volatile boolean shouldPlay = true; final int bufferSize; final AudioFormat fmt; final AudioInputStream audioIn; final SourceDataLine audioOut; final long frameSize; final long totalFrames; final double sampleRate; AudioPlayer(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException { audioIn = AudioSystem.getAudioInputStream(file); fmt = audioIn.getFormat(); bufferSize = fmt.getFrameSize() * 8192; frameSize = fmt.getFrameSize(); totalFrames = audioIn.getFrameLength(); sampleRate = fmt.getSampleRate(); try { audioOut = AudioSystem.getSourceDataLine(audioIn.getFormat()); audioOut.open(fmt, bufferSize); } catch (LineUnavailableException x) { try { audioIn.close(); } catch(IOException suppressed) { // Java 7+ // x.addSuppressed(suppressed); } throw x; } } @Override public void run() { final byte[] buffer = new byte[bufferSize]; long framePosition = 0; try { audioOut.start(); while (shouldPlay) { int bytesRead = audioIn.read(buffer); if (bytesRead < 0) { break; } int bytesWritten = audioOut.write(buffer, 0, bytesRead); if (bytesWritten != bytesRead) { // shouldn't happen throw new RuntimeException(String.format( "read: %d, wrote: %d", bytesWritten, bytesRead)); } framePosition += bytesRead / frameSize; // or // framePosition = audioOut.getLongFramePosition(); updateProgressBar(framePosition); } audioOut.drain(); audioOut.stop(); } catch (Throwable x) { showErrorMessage(x); } finally { updateProgressBar(0); try { audioIn.close(); } catch (IOException x) { showErrorMessage(x); } audioOut.close(); } } void updateProgressBar( final long framePosition) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { double fractionalProgress = (double) framePosition / (double) totalFrames; int progressValue = (int) Math.round( fractionalProgress * theProgressBar.getMaximum()); theProgressBar.setValue(progressValue); int secondsElapsed = (int) Math.round( (double) framePosition / sampleRate); int minutes = secondsElapsed / 60; int seconds = secondsElapsed % 60; theProgressBar.setString(String.format( "%d:%02d", minutes, seconds)); } }); } void stopPlaybackAndDrain() throws InterruptedException { shouldPlay = false; this.join(); } } /* * */ public static void main(String[] args) { SwingUtilities.invokeLater(new SimplePlaybackProgress()); } JFrame theFrame; JButton theButton; JProgressBar theProgressBar; // this should only ever have 1 thing in it... // multithreaded code with poor behavior just bugs me, // even for improbable cases, so the queue makes it more robust final Queue thePlayerQueue = new ArrayDeque(); @Override public void run() { theFrame = new JFrame("Playback Progress"); theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); theButton = new JButton("Open"); theProgressBar = new JProgressBar( SwingConstants.HORIZONTAL, 0, 1000); theProgressBar.setStringPainted(true); theProgressBar.setString("0:00"); Container contentPane = theFrame.getContentPane(); ((JPanel) contentPane).setBorder( BorderFactory.createEmptyBorder(8, 8, 8, 8)); contentPane.add(theButton, BorderLayout.WEST); contentPane.add(theProgressBar, BorderLayout.CENTER); theFrame.pack(); theFrame.setResizable(false); theFrame.setLocationRelativeTo(null); theFrame.setVisible(true); theButton.addActionListener(this); theFrame.addWindowListener(this); } @Override public void actionPerformed(ActionEvent ae) { JFileChooser dialog = new JFileChooser(); int option = dialog.showOpenDialog(theFrame); if (option == JFileChooser.APPROVE_OPTION) { File file = dialog.getSelectedFile(); try { enqueueNewPlayer(new AudioPlayer(file)); } catch (UnsupportedAudioFileException x) { // ew, Java 6 showErrorMessage(x); // } catch (IOException x) { // showErrorMessage(x); // } catch (LineUnavailableException x) { // showErrorMessage(x); // } // } } @Override public void windowClosing(WindowEvent we) { stopEverything(); } void enqueueNewPlayer(final AudioPlayer newPlayer) { // stopPlaybackAndDrain calls join // so we want to do it off the EDT new Thread() { @Override public void run() { synchronized (thePlayerQueue) { stopEverything(); newPlayer.start(); thePlayerQueue.add(newPlayer); } } }.start(); } void stopEverything() { synchronized (thePlayerQueue) { while (!thePlayerQueue.isEmpty()) { try { thePlayerQueue.remove().stopPlaybackAndDrain(); } catch (InterruptedException x) { // shouldn't happen showErrorMessage(x); } } } } void showErrorMessage(Throwable x) { x.printStackTrace(System.out); String errorMsg = String.format( "%s:%n\"%s\"", x.getClass().getSimpleName(), x.getMessage()); JOptionPane.showMessageDialog(theFrame, errorMsg); } } 

对于Clip ,你只需要一个类似Swing计时器(或其他边线程)的东西,然后经常查询它:

 new javax.swing.Timer(100, new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { long usPosition = theClip.getMicrosecondPosition(); // put it somewhere } }).start(); 

有关:

  • 如何计算java中音频信号的电平/幅度/ db?
  • 如何使波形渲染更有趣?