【问题标题】:Increase/decrease audio play speed of AudioInputStream with Java使用 Java 增加/减少 AudioInputStream 的音频播放速度
【发布时间】:2018-07-02 02:06:08
【问题描述】:

使用 Java 进入复杂的音频世界我正在使用 this library ,基本上我对其进行了改进并发布在 Github 上。

库的主类是StreamPlayer,代码有cmets,简单易懂。


问题在于它支持许多功能除了速度增加/减少音频速度。假设就像 YouTube 在您更改视频速度时所做的那样。

我不知道如何实现这样的功能。我的意思是,将音频写入targetFormat 的采样率时该怎么办?我每次都必须一次又一次地重新启动音频....

AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sourceFormat.getSampleRate()*2, nSampleSizeInBits, sourceFormat.getChannels(),
                nSampleSizeInBits / 8 * sourceFormat.getChannels(), sourceFormat.getSampleRate(), false);

播放音频的代码是:

/**
 * Main loop.
 *
 * Player Status == STOPPED || SEEKING = End of Thread + Freeing Audio Resources.<br>
 * Player Status == PLAYING = Audio stream data sent to Audio line.<br>
 * Player Status == PAUSED = Waiting for another status.
 */
@Override
public Void call() {
    //  int readBytes = 1
    //  byte[] abData = new byte[EXTERNAL_BUFFER_SIZE]
    int nBytesRead = 0;
    int audioDataLength = EXTERNAL_BUFFER_SIZE;
    ByteBuffer audioDataBuffer = ByteBuffer.allocate(audioDataLength);
    audioDataBuffer.order(ByteOrder.LITTLE_ENDIAN);

    // Lock stream while playing.
    synchronized (audioLock) {
        // Main play/pause loop.
        while ( ( nBytesRead != -1 ) && status != Status.STOPPED && status != Status.SEEKING && status != Status.NOT_SPECIFIED) {
            try {
                //Playing?
                if (status == Status.PLAYING) {

                    // System.out.println("Inside Stream Player Run method")
                    int toRead = audioDataLength;
                    int totalRead = 0;

                    // Reads up a specified maximum number of bytes from audio stream   
                    //wtf i have written here xaxaxoaxoao omg //to fix! cause it is complicated
                    for (; toRead > 0
                            && ( nBytesRead = audioInputStream.read(audioDataBuffer.array(), totalRead, toRead) ) != -1; toRead -= nBytesRead, totalRead += nBytesRead)

                        // Check for under run
                        if (sourceDataLine.available() >= sourceDataLine.getBufferSize())
                            logger.info(() -> "Underrun> Available=" + sourceDataLine.available() + " , SourceDataLineBuffer=" + sourceDataLine.getBufferSize());

                    //Check if anything has been read
                    if (totalRead > 0) {
                        trimBuffer = audioDataBuffer.array();
                        if (totalRead < trimBuffer.length) {
                            trimBuffer = new byte[totalRead];
                            //Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array
                            // The number of components copied is equal to the length argument. 
                            System.arraycopy(audioDataBuffer.array(), 0, trimBuffer, 0, totalRead);
                        }

                        //Writes audio data to the mixer via this source data line
                        sourceDataLine.write(trimBuffer, 0, totalRead);

                        // Compute position in bytes in encoded stream.
                        int nEncodedBytes = getEncodedStreamPosition();

                        // Notify all registered Listeners
                        listeners.forEach(listener -> {
                            if (audioInputStream instanceof PropertiesContainer) {
                                // Pass audio parameters such as instant
                                // bit rate, ...
                                listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), trimBuffer, ( (PropertiesContainer) audioInputStream ).properties());
                            } else
                                // Pass audio parameters
                                listener.progress(nEncodedBytes, sourceDataLine.getMicrosecondPosition(), trimBuffer, emptyMap);
                        });

                    }

                } else if (status == Status.PAUSED) {

                    //Flush and stop the source data line 
                    if (sourceDataLine != null && sourceDataLine.isRunning()) {
                        sourceDataLine.flush();
                        sourceDataLine.stop();
                    }
                    try {
                        while (status == Status.PAUSED) {
                            Thread.sleep(50);
                        }
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                        logger.warning("Thread cannot sleep.\n" + ex);
                    }
                }
            } catch (IOException ex) {
                logger.log(Level.WARNING, "\"Decoder Exception: \" ", ex);
                status = Status.STOPPED;
                generateEvent(Status.STOPPED, getEncodedStreamPosition(), null);
            }
        }

        // Free audio resources.
        if (sourceDataLine != null) {
            sourceDataLine.drain();
            sourceDataLine.stop();
            sourceDataLine.close();
            sourceDataLine = null;
        }

        // Close stream.
        closeStream();

        // Notification of "End Of Media"
        if (nBytesRead == -1)
            generateEvent(Status.EOM, AudioSystem.NOT_SPECIFIED, null);

    }
    //Generate Event
    status = Status.STOPPED;
    generateEvent(Status.STOPPED, AudioSystem.NOT_SPECIFIED, null);

    //Log
    logger.info("Decoding thread completed");

    return null;
}

如果需要,请随时下载并单独查看库。 :) 我需要一些帮助...Library link

【问题讨论】:

    标签: java audio audioinputstream


    【解决方案1】:

    简答:

    为了加快一个人说话的速度,请使用我的 Sonic 算法的 Sonic.java 本机 Java 实现。 Main.Java 中有一个如何使用它的示例。 Android 的 AudioTrack 使用相同算法的 C 语言版本。要加快音乐或电影的速度,请查找基于 WSOLA 的库。

    臃肿的答案:

    加快语音速度比听起来更复杂。简单地增加采样率而不调整样本会导致扬声器听起来像花栗鼠。我听过的基本上有两种线性加速语音的好方案:像 WSOLA 这样的基于固定帧的方案,以及像 PICOLA 这样的音高同步方案,Sonic 使用它来实现高达 2 倍的速度。我听过的另一种方案是基于 FFT 的,IMO 应该避免这些实现。我听说基于 FFT 可以很好地完成的传言,但我知道的开源版本在我上次检查时没有可用的版本,可能是在 2014 年。

    我不得不发明一种新的算法来实现超过 2X 的速度,因为 PICOLA 只是降低了整个音高周期,只要您不连续丢掉两个音高周期,这种算法就可以很好地工作。对于超过 2X 的速度,Sonic 会混合来自每个输入音高周期的部分样本,同时保留每个输入音高周期的一些频率信息。这适用于大多数语音,尽管某些语言(如匈牙利语)的词性似乎很短,以至于 PICOLA 也会破坏一些音素。但是,在不破坏音素的情况下,您可以降低一个音高周期的一般规则似乎在大多数情况下都很好用。

    音高同步方案专注于一个扬声器,并且通常会使该扬声器比固定框架方案更清晰,但会牺牲非语音声音。然而,对于大多数扬声器来说,在低于约 1.5X 的速度下很难听到音高同步方案相对于固定帧方案的改进。这是因为像 WSOLA 这样的固定帧算法基本上模拟像 PICOLA 这样的音高同步方案,当只有一个扬声器并且每帧不超过一个音高周期需要丢弃时。如果 WSOLA 很好地适应了扬声器,那么在这种情况下,数学原理基本相同。例如,如果它能够及时选择 +/- 一帧的声音片段,那么 50 毫秒的固定帧将允许 WSOLA 为大多数基本音高 > 100 Hz 的扬声器模拟 PICOLA。但是,使用这些设置的 WSOLA 会杀死一个具有 95 Hz 深沉声音的男性。此外,当参数未进行最佳调整时,WSOLA 也可能会扼杀部分语音,例如在句子结尾处,我们的基本音高会显着下降。此外,当速度超过 2X 时,WSOLA 通常会崩溃,而像 PICOLA 一样,它会开始连续下降多个音高周期。

    从积极的方面来说,WSOLA 将使包括音乐在内的大多数声音即使不是高保真度也可以理解。采用 WSOLA 和 PICOLA 之类的重叠相加 (OLA) 方案,在不引入大量失真的情况下采用非谐波多声部声音并改变速度是不可能的。 做好这件事需要分离不同的声音,独立改变它们的速度,并将结果混合在一起。但是,大多数音乐都足够和声,在 WSOLA 中听起来不错。

    事实证明,2X 以上的 WSOLA 质量差是人们很少以高于 2X 的速度收听的原因之一。人们根本不喜欢它。一旦 Audible.com 在 Android 上从 WSOLA 切换到类似 Sonic 的算法,他们就能够将支持的速度范围从 2X 增加到 3X。过去几年我没有在 iOS 上听过,但截至 2014 年,iOS 上的 Audible.com 以 3X 的速度收听很痛苦,因为他们使用了内置的 iOS WSOLA 库。从那以后他们可能已经修复了它。

    【讨论】:

    • 嘿 :) 谢谢我昨天找到了 Skny,虽然我不知道如何将它添加到项目类路径中,我的意思是我只看到了。除 Main.java 之外的 c 文件
    • 我已经使用 Sonic 大约半年了,用于收听有声读物和 youtube 视频,非常感谢您的工作!我使用 VLC 进行播放,它使用 wsola,多年来我被限制在 2.5 倍左右。
    • 如何将播放的音频保存到.wav 文件?
    • 基于我开发的库和使用StreamPlayer编写java代码太难了。
    【解决方案2】:

    查看您链接的库,这似乎不是专门针对此播放速度问题开始的好地方;你有什么理由不使用AudioTrack?它似乎支持你需要的一切。

    编辑 1: AudioTrack 是特定于 Android 的,但 OP 的问题是基于桌面 javaSE 的;我只会把它留在这里以供将来参考。

    1。使用AudioTrack 并调整播放速度(Android)

    感谢另一个 SO 帖子 (here) 上的回答,发布了一个类,它使用内置的 AudioTrack 来处理播放期间的速度调整。

    public class AudioActivity extends Activity {
    AudioTrack audio = new AudioTrack(AudioManager.STREAM_MUSIC,
            44100,
            AudioFormat.CHANNEL_OUT_STEREO,
            AudioFormat.ENCODING_PCM_16BIT,
            intSize, //size of pcm file to read in bytes
            AudioTrack.MODE_STATIC);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            //read track from file
            File file = new File(getFilesDir(), fileName);
    
            int size = (int) file.length();
            byte[] data = new byte[size];
    
            try {
                FileInputStream fileInputStream = new FileInputStream(file);
                fileInputStream.read(data, 0, size);
                fileInputStream.close();
    
                audio.write(data, 0, data.length);
            } catch (IOException e) {}
        }
    
        //change playback speed by factor
        void changeSpeed(double factor) {
            audio.setPlaybackRate((int) (audio.getPlaybackRate() * factor));
        }
    }
    

    这只是使用一个文件在一个写入命令中流式传输整个文件,但您可以进行其他调整(setPlayBackRate 方法是您需要的主要部分)。

    2。应用您自己的播放速度调整

    调整播放速度的原理有两种:

    • 调整采样率
    • 更改单位时间的样本数

    由于您使用的是初始采样率(因为我假设您必须在调整采样率时重置库并停止音频?),您必须调整每单位时间的采样数。

    例如,要加快音频缓冲区的播放速度,您可以使用此伪代码(Python 风格),感谢Coobird (here)。

    original_samples = [0, 0.1, 0.2, 0.3, 0.4, 0.5]
    
    def faster(samples):
        new_samples = []
        for i = 0 to samples.length:
            if i is even:
                new_samples.add(0.5 * (samples[i] + samples[i+1]))
        return new_samples
    
    faster_samples = faster(original_samples)
    

    这只是加快播放速度的一个例子,并不是唯一的算法,而是一个开始的算法。计算出加速缓冲区后,您可以将其写入音频输出,数据将以您选择应用的任何缩放比例播放。

    要减慢音频速度,请通过在当前缓冲区值之间添加数据点并根据需要使用插值来应用相反的方法。

    请注意,在调整播放速度时,通常需要以所需的最大频率进行低通滤波,以避免不必要的伪影。


    如您所见,第二次尝试是一项更具挑战性的任务,因为它需要您自己实现此类功能,所以我可能会使用第一次,但认为值得一提的是第二次。

    【讨论】:

    • 这太棒了,虽然我在计算机上使用 JavaSE,而不是在 Android 上使用 Java。
    • @GOXR3PLUS,抱歉!我的坏我没有意识到。我已经更新了我的问题,提到 #1 是 Android 特定的解决方案。我还环顾四周,了解如何在桌面应用程序上执行此操作;似乎没有直接的答案。就像我的回答(#2)一样,我认为你必须自己做一些 DSP,而不是重新设置采样率。上面我给出了一个关于如何手动实现它的解决方案,但也许可以查看像 TarsosDSP 这样的库。否则我相信你会发现很多其他的 DSP 库!
    • 在 JavaSE 中太难了,我找到的在线示例是从 0 到 0,如果您找到任何具体的内容,请告诉我,我现在正在搜索 1 年 :)
    猜你喜欢
    • 2014-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    相关资源
    最近更新 更多