Pydub глюки звука при разделении / присоединении mp3
Я экспериментирую с pydub, который мне очень нравится, однако у меня возникла проблема при разбиении / присоединении файла mp3.
Мне нужно создать серию небольших фрагментов аудио на сервере, которые будут последовательно посылаться в веб-браузер и воспроизводиться через <audio/>
элемент. Мне нужно, чтобы воспроизведение звука было "плавным" без слышимых соединений между отдельными частями. На данный момент, однако, соединения между отдельными битами аудио довольно очевидны, иногда возникает короткое молчание, а иногда странный сбой звука.
В своем доказательстве кода концепции я взял один большой mp3 и разделил его на 1-секундные куски следующим образом:
song = AudioSegment.from_mp3('my.mp3')
song_pos = 0
while song_pos < 100:
p1 = song_pos * 1000
p2 = p1 + 1000
segment = song[p1:p2] # 1 second of audio
output = StringIO.StringIO()
segment.export(output, format="mp3")
client_data = output.getvalue() # send this to client
song_pos += 1
Значения client_data передаются в браузер через долгоживущее http-соединение:
socket.send("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: audio/mp3\r\n\r\n")
а затем для каждого нового куска аудио
socket.send(client_data)
Может кто-нибудь объяснить глюки, которые я слышу, и предложить способ их устранения?
3 ответа
Обновление моего комментария до ответа:
Основная проблема заключается в том, что кодеки MP3, используемые ffmpeg, добавляют тишину к концу закодированного звука (и ваш подход заключается в создании нескольких отдельных аудиофайлов).
Если возможно, используйте формат без потерь, например, wave, а затем уменьшите размер файла с помощью gzip или аналогичного. Вы также можете использовать сжатие звука без потерь (например, flac), но это, вероятно, зависит от того, как работает кодер.
У меня нет окончательного объяснения слышимых артефактов, которые вы слышите, но может случиться так, что вы разделяете звук в точке, где сигнал не равен нулю. Если звук начинается с сэмпла со значением 100 (например), это будет звучать как звук с цифровым треском. Сжатие MP3 также может изменить звук, особенно на более низких скоростях. Если это проблема, появление 1 мс устранит всплеск без заметного слышимого "исчезновения" (хотя потенциально может привести к появлению других артефактов) - более длительное появление (например, 20 или 50 мс позволит избежать странных артефактов в частотной области, но приведет к заметному "исчезать".
Если вы хотите проделать немного больше (кодирование) работы, вы можете найти "пересечение нуля" (в основном, место, где сигнал находится в нулевой точке, естественно) и разделить аудио там.
Вероятно, лучший подход, если это возможно:
Кодируйте весь сигнал как один сжатый файл и отправьте байты (этого одного файла) клиенту порциями для воспроизведения в виде единого потока. Если вы используете постоянное битрейтное кодирование mp3 (CBR), вы можете почти идеально отправлять фрагменты длиной в 1 секунду, просто считая байты. Например, с CBR 256 Кбит / с, просто отправьте 256 КБ за раз.
Таким образом, я могу быть совершенно неправ, я обычно не связываюсь с аудио файлами, но это может быть проблемой индексации. пытаться,
p2 = p1 + 1001
но вам может понадобиться инвертировать процесс объединения, чтобы он работал. Если только вы не добавите дополнительную миллисекунду в конце.
Единственное, что я мог бы подумать, это артефакт в потоке, который появляется, когда вы конвертируете байты в строку. Попробуйте использовать конечную точку AudioSegment(). Raw_data для байтового представления аудио.
Звук - это форма волны, и вы соединяете две волны, которые не совпадают по фазе; так что вы получаете функцию шага, и это делает поп.
Я не знаком с этим программным обеспечением, но кодифицируя предложения Нильса Вернера, вы можете попробовать:
song = AudioSegment.from_mp3('my.mp3')
song_pos = 0
# begin with a millisecond of blank
segment = AudioSegment.silent(duration=1)
# append all your pieces to it
while song_pos < 100:
p1 = song_pos * 1000
p2 = p1 + 1000
#append an item to your segment with several milliseconds of crossfade
segment.append(song[p1:p2], crossfade=50)
song_pos += 1
# then pass it on to your client outside of your loop
output = StringIO.StringIO()
segment.export(output, format="mp3")
client_data = output.getvalue() # send this to client
в зависимости от того, насколько низка / высока частота того, к чему вы присоединяетесь, вам нужно отрегулировать время перехода, чтобы смешаться; низкая частота потребует большего затухания.