使用 Java 增加/减少 AudioInputStream 的音频播放速度

GOX*_*LUS 1 java audio audioinputstream

使用 Java 进入复杂的音频世界我正在使用这个库,它基本上是我在 Github 上改进和发布的。

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


问题是它支持除速度增加/降低音频速度之外的许多功能假设当您更改视频速度时,就像 YouTube 所做的那样。

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

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

播放音频的代码是:

/**
 * 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;
}
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以随意下载并单独查看图书馆。:) 我需要一些帮助...图书馆链接

小智 5

简短的回答:

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

臃肿的答案:

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

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

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

从积极的方面来说,WSOLA 将使包括音乐在内的大多数声音变得易于理解,即使不是高保真度。对于 WSOLA 和 PICOLA 等重叠加法 (OLA) 方案,不可能在不引入实质性失真的情况下获取非谐波多声部声音并改变速度。要做到这一点,需要将不同的声音分开,独立地改变它们的速度,并将结果混合在一起。然而,大多数音乐都足够和谐,可以用 WSOLA 听起来不错。

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