使用Java增加/减少AudioInputStream的音频播放速度[英] Increase/decrease audio play speed of AudioInputStream with Java

问题描述

使用Java进入复杂的音频世界,我正在使用这个 库,基本上我在github上得到了改进和发表.

图书馆的主类是 streamplayer 和代码有注释,是直接理解.


问题是它支持许多功能外,除了速度增加/减少音频速度.让我们说在你改变视频速度时,请说出YouTube.

我没有CLUE如何实现这样的功能.我的意思是,在将音频写入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 .

推荐答案

简短答案:

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

膨胀答案:

加速语音比听起来更复杂.只需调整样品即可增加采样率会导致扬声器听起来像花栗鼠一样.基本上有两种良好的方案,用于线性加速我已经收听的语音:基于固定帧的方案,如WSOLA,像Picola这样的俯仰同步方案,其被Sonic用于高达2x的速度.我听取的另一个方案是基于FFT的,并且应该避免使用这些实现.我听到了谣言,即基于FFT的可以很好地完成,但没有开源版本我知道是可以使用的最后一次检查,可能是2014年.

我必须发明一个新的速度速度超过2倍,因为皮多拉只是丢弃整个音高周期,只要你不会连续下降两个音高周期即可运行.为了更快地超过2x,Sonic在每个输入间距周期的样本的一部分中混合,从每个输入间距保持一些频率信息.这适用于大多数讲话,虽然匈牙利语等一些语言似乎具有言论的部分,所以甚至皮多拉发动了一些音素.但是,您可以在没有占用音素的情况下放弃一个音频的一般规则似乎在大部分时间内工作.

俯仰 - 同步方案专注于一个扬声器,通常会使扬声器更清晰,而不是固定帧方案,以牺牲非语音声音为代价.然而,对于大多数扬声器,在固定帧方案上的俯仰同步方案的改进难以听到小于约1.5倍的速度.这是因为当只有一个扬声器时,WSOLA等固定帧算法基本上模拟了像上皮的音调同步码,并且每帧需要丢弃一个不超过一个音高周期.如果WSOLA调整扬声器,则数学在这种情况下基本上是相同的.例如,如果能够及时选择+/-一帧的声音段,那么50ms固定帧将允许WSOLA为大多数具有基本间距> 100 Hz的扬声器模拟皮比拉.然而,一个具有深入发言权的男性,使用这些设置将与WSOLA屠杀.此外,部分语音,例如在句子的末尾,当参数未得到最佳调整时,我们的基本间距显着下降也可以被WSOLA屠杀.此外,WSOLA通常落在大于2倍的速度,在那里,在这里,它开始连续下降多个音调周期.

在正面,WSOLA将使大多数声音包括音乐可以理解,如果不是高保真.采用非谐波多声音声音并改变速度而不会引入大量失真,与WSOLA和Picola等重叠和添加(OLA)方案是不可能的. 做这件事很好,需要分开不同的声音,独立改变它们的速度,并将结果混合在一起.但是,大多数音乐都足够谐波,可以用WSOLA发出正常.

事实证明,WSOLA的质量差> 2倍是一个原因,人们很少听高于2倍.人们根本不喜欢它.一旦听到了从WSOLA切换到Android上的Sonic样算法,他们就可以将支持的速度范围从2x到3x增加到3倍.在过去的几年里,我还没有在iOS上听,但截至2014年,iOS上的Acomible.com是悲惨的,以便在3倍的速度听,因为他们使用内置的iOS WSOLA库.从那以后他们可能会修复它.

其他推荐答案

查看您链接的库,它看起来并不是一个专门为此播放速度问题而开始的好地方;您是否有任何原因不使用 audiot rack ?它似乎支持您需要的一切.

编辑1: audiot rack是Android特定的,但OP的问题是基于桌面javase;我将在这里留下来以供将来参考.

1.使用 audiotrack 调整播放速度(Android)

感谢另一个帖子的答案(
),有一个类发布,它使用内置的 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 (这里).

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)

这只是加快播放的一个示例,并不是如何这样做的唯一算法,而是一个开始的算法.一旦您计算了加速缓冲区,您可以将其写入您的音频输出,数据将在您选择应用的任何缩放时播放.

慢向下扫描音频,通过根据需要的插值添加当前缓冲值之间的数据点来应用相反的.

请注意,调整播放速度时,通常值得低通滤波,以避免不必要的伪像.


如你所说,第二次尝试是一个更具有挑战性的任务,因为它需要你自己实施这样的功能,所以我可能首先使用但是认为这是值得一提的.

本文地址:https://www.itbaoku.cn/post/978637.html