Увеличение / уменьшение скорости воспроизведения аудио AudioInputStream с Java
Попадая в сложный мир аудио с использованием 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);
Код воспроизведения аудио:
/**
* 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;
}
Не стесняйтесь скачать и проверить библиотеку в одиночку, если хотите.:) Мне нужна помощь в этом... Ссылка на библиотеку.
2 ответа
Короткий ответ:
Для ускорения разговора с одним человеком, используйте мою реализацию Sonic.java на Java для моего алгоритма Sonic. Пример того, как использовать это в Main.Java. Версия этого же алгоритма на языке C используется Android AudioTrack. Для ускорения музыки или фильмов найдите библиотеку на основе WSOLA.
Раздутый ответ:
Ускорение речи сложнее, чем кажется. Простое увеличение частоты дискретизации без регулировки сэмплов приведет к тому, что колонки будут звучать как бурундуки. По сути, есть две хорошие схемы линейного ускорения речи, которые я слушал: схемы на основе фиксированных кадров, такие как WSOLA, и схемы с синхронным высотой звука, такие как PICOLA, которые используются Sonic для скоростей до 2X. Еще одна схема, которую я слушал, основана на FFT, и IMO этих реализаций следует избегать. Я слышал слухи о том, что на основе FFT можно сделать хорошо, но ни одна версия с открытым исходным кодом, о которой я знаю, не была пригодна для использования в последний раз, когда я проверял, вероятно, в 2014 году.
Мне пришлось изобрести новый алгоритм для скоростей, превышающих 2X, поскольку PICOLA просто отбрасывает целые периоды основного тона, который работает хорошо, если вы не отбрасываете два периода основного тона подряд. Для скорости, превышающей 2х, Sonic смешивает порции сэмплов из каждого периода основного тона, сохраняя некоторую информацию о частоте каждого из них. Это хорошо работает для большинства речи, хотя некоторые языки, такие как венгерский, имеют такие короткие части речи, что даже PICOLA портит некоторые фонемы. Тем не менее, общее правило, что вы можете отбросить один шаг без искажения фонем, в большинстве случаев работает хорошо.
Схемы с синхронным питчем фокусируются на одном динамике и, как правило, делают этот динамик более четким, чем схемы с фиксированным кадром, за счет распечатки неречевых звуков. Тем не менее, улучшение синхронных схем основного тона по сравнению со схемами с фиксированным кадром трудно услышать на скоростях, меньших, чем приблизительно 1,5, для большинства динамиков. Это связано с тем, что алгоритмы с фиксированным кадром, такие как WSOLA, в основном эмулируют синхронные схемы основного тона, такие как PICOLA, когда имеется только один динамик, и для каждого кадра необходимо отбрасывать не более одного периода основного тона. В этом случае математика работает в основном так же, если WSOLA хорошо настроена на динамик. Например, если он способен выбрать звуковой сегмент +/- один кадр за раз, то фиксированный кадр 50 мс позволит WSOLA эмулировать PICOLA для большинства громкоговорителей с основной высотой тона> 100 Гц. Тем не менее, мужчина с глубоким голосом, скажем, 95 Гц, будет убит WSOLA с использованием этих настроек. Кроме того, части речи, например, в конце предложения, где значительно снижается наша основная высота звука, также могут быть разделены WSOLA, когда параметры не оптимально настроены. Кроме того, WSOLA обычно разваливается для скоростей, превышающих 2X, где, как и PICOLA, он начинает сбрасывать несколько периодов основного тона подряд.
С положительной стороны, WSOLA сделает большинство звуков, включая музыку, понятными, если не высококачественными. Принимать негармоничные многоголосные звуки и изменять скорость без существенных искажений невозможно с помощью схем перекрытия и добавления (OLA), таких как WSOLA и PICOLA. Чтобы сделать это хорошо, потребуется разделить разные голоса, изменить их скорости независимо и смешать результаты вместе. Тем не менее, большая часть музыки достаточно гармонична, чтобы звучать нормально с WSOLA.
Оказывается, плохое качество WSOLA при> 2X является одной из причин, по которой люди редко слушают на более высоких скоростях, чем 2X. Людям просто не нравится это. Когда Audible.com переключился с WSOLA на алгоритм, подобный Sonic, на Android, они смогли увеличить поддерживаемый диапазон скоростей с 2X до 3X. Я не слушал iOS в последние несколько лет, но с 2014 года Audible.com на iOS было жалко слушать с 3-кратной скоростью, так как они использовали встроенную iOS WSOLA библиотеку. Они, вероятно, исправили это с тех пор.
Глядя на библиотеку, которую вы связали, не кажется, что это хорошее место, чтобы начать именно с этой проблемы скорости воспроизведения; есть ли причина, по которой вы не используете AudioTrack? Кажется, чтобы поддержать все, что вам нужно.
РЕДАКТИРОВАТЬ 1: AudioTrack специфичен для Android, но вопрос OP основан на десктопном javaSE; Я оставлю это здесь для дальнейшего использования.
1. Использование AudioTrack и настройка скорости воспроизведения (Android)
Благодаря ответу на другой пост SO ( здесь), опубликован класс, который использует встроенный 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)
Это всего лишь один пример ускорения воспроизведения и не единственный алгоритм, позволяющий это сделать, а тот, с которого нужно начать. После того, как вы рассчитали свой буфер ускорения, вы можете записать его на свой аудиовыход, и данные будут воспроизводиться с любым масштабированием, который вы выберете.
Чтобы замедлить звук, примените противоположное, добавив точки данных между текущими значениями буфера с интерполяцией по желанию.
Обратите внимание, что при настройке скорости воспроизведения часто стоит фильтровать нижние частоты на максимальной частоте, чтобы избежать ненужных артефактов.
Как вы можете видеть, вторая попытка является гораздо более сложной задачей, так как требует самостоятельной реализации такой функциональности, поэтому я, вероятно, использовал бы первую, но подумал, что стоит упомянуть вторую.