如何跟踪音频播放位置? [英] How to keep track of audio playback position?

查看:279
本文介绍了如何跟踪音频播放位置?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个线程通过将其转换为字节数组发挥Java的一个mp3文件。

I created a thread to play an mp3 file in Java by converting it to an array of bytes.

我想知道如果我能保持当前的播放位置的轨道作为MP3正在播放。

I'm wondering if I can keep track of the current play position as the mp3 is being played.

首先,我建立了我的音乐流像这样:

First, I set up my music stream like so:

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();
    }

接下来,我的音乐主题是这样的:

Next, my music thread looks like this:

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歌曲。但是,这似乎并不像所有好的解决方案。


I thought of one possibility, to create another thread with a timer that slowly ticks down, second by second, to show the remaining amount of time for the mp3 song. But that doesn't seem like a good solution at all.

推荐答案

INT musicCount (返回从 AudioInputStream.read值(.. 。))告诉你读取的字节数,所以用,你可以做一个小的计算总是找出你流的地方。 (的DataLine 有一些方法做一些数学题的你,但他们不能总是使用......见下文)。

Your int musicCount (the return value from AudioInputStream.read(...)) tells you the number of bytes read, so with that you can do a small computation to figure out your place in the stream always. (DataLine has some methods to do some of the math for you but they can't always be used...see below.)

int musicCount = 0;
int totalBytes = 0;

while ( loop stuff ) {
    // accumulate it
    // and do whatever you need with it
    totalBytes += musicCount;

    musicDataLine.write(...);
}

要得到逝去的秒数,你可以做以下的事情:

To get the number of seconds elapsed, you can do the following things:

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;

所以你刚才得到的经过时间每个循环,并把它无论你需要它去。注意,这个精度种类取决于你的缓冲区的大小。较小的缓冲器,更准确

So you'd just get the elapsed time each loop and put it wherever you need it to go. Note that the accuracy of this kind of depends on the size of your buffer. The smaller the buffer, the more accurate.

此外, 有一些方法来查询这个给你(但你可能必须改变你在做什么很多)。

Also, Clip has some methods to query this for you (but you'd probably have to change what you're doing a lot).

这些方法(<一个href=\"http://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/DataLine.html#getLongFramePosition--\"相对=nofollow> GET(长)FramePosition / <一个href=\"http://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/DataLine.html#getMicrosecondPosition--\"相对=nofollow> getMicrosecondPosition )从的DataLine ,所以你也可以给他们打电话在的SourceDataLine ,以及如果你不想自己做数学题。的然而的,基本上你需要做一个新行每个文件你玩,所以这取决于你如何使用就行了。 (我个人宁愿只是做了自己的分工要求,因为该行是一种不透明的。)

These methods (get(Long)FramePosition/getMicrosecondPosition) are inherited from DataLine, so you can also call them on the SourceDataLine as well if you don't want to do the math yourself. However, you basically need to make a new line for every file you play, so it depends on how you're using the line. (Personally I'd rather just do the division myself since asking the line is kind of opaque.)

BTW:

musicDataLine.open(MUSIC_FORMAT);

您应该打开用自己的缓冲区大小指定的行,使用 (AudioFormat的,INT) 超载​​。 SourceDataLine.write(...)时,其内部缓冲区已满,只有块,这样,如果它是从你的字节数组大小不同,有时你的循环阻塞,其他时间是只是打转。

You should open the line with your own buffer size specified, using the (AudioFormat, int) overload. SourceDataLine.write(...) only blocks when its internal buffer is full, so if it's a different size from your byte array, sometimes your loop is blocking, other times it's just spinning.

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<AudioPlayer> thePlayerQueue = new ArrayDeque<AudioPlayer>();

    @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);
    }
}

有关剪辑,你就必须像一个Swing计时器(或其他方线程),并常常但查询它:

For Clip, you'd just have something like a Swing timer (or other side-thread) and query it however often:

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


相关阅读:


Related:

  • How to calculate the level/amplitude/db of audio signal in java?
  • How to make waveform rendering more interesting?

这篇关于如何跟踪音频播放位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆