Вопрос или проблема
Я использую ffplay (и подозреваю, что ffmpeg также необходим где-то в цепочке) для воспроизведения аудио из поток байтов, созданного скриптом на Python, который может генерировать байты (сырые PCM16LE, один канал, 24 кГц) в произвольные интервалы времени.
Моя первая попытка заключалась в том, чтобы просто передать байты в подсистему ffplay
:
ffplay = subprocess.Popen(
["ffplay", "-nodisp", "-f", "s16le", "-ar", "24000", "-acodec", "pcm_s16le", "-i", "-"],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
# ...
for segment in audio_iter:
ffplay.stdin.write(segment.raw_data)
Однако что происходит: когда итератор аудио временно истощается, ffplay продолжает “искать” вперед во времени. Позже аудиопоток может выдавать больше байтов, которые я ожидал воспроизвести сразу, но вместо этого ничего не воспроизводится, пока аудиобайты не “достигнут” того места, куда голова воспроизведения перемотала в ffplay, что приводит к потере некоторого аудио.
Я проверил документацию ffplay и не нашел никакого поведения, связанного с автопаузой, когда поток данных exhausted. Я подозреваю, что может потребоваться использовать ffmpeg, чтобы “заполнить” пустые данные, когда никакие байты не доступны из потока Python, но здесь я теряюсь. Есть ли какой-либо способ сделать это с помощью ffmpeg, передавая данные в ffplay? Моя текущая попытка выглядит так, но имеет ту же проблему.
ffmpeg = subprocess.Popen(
["ffmpeg", "-f", "s16le", "-ar", "24000", "-ac", "1", "-i", "pipe:", "-f", "wav", "pipe:"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
ffplay = subprocess.Popen(
["ffplay", "-nodisp", "-i", "-"],
stdin=ffmpeg.stdout,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
# ...
for segment in audio_iter:
ffmpeg.stdin.write(segment.raw_data)
Эти два вопроса казались актуальными, но я также не смог заставить их правильно выводить (входящее аудио в итоге испортилось – я подозреваю, так как сырое PCM аудио не в реальном времени).
https://www.reddit.com/r/ffmpeg/comments/148o6jb/inserting_silence_into_stream_when_stream_is/
Заполнить пробелы в потоковом аудио тишиной в FFMPEG
Ответ или решение
Для решения проблемы с воспроизведением аудиоданных через ffplay
, когда данные поступают из Python-скрипта в виде сырого PCM16LE, необходимо рассмотреть несколько аспектов, связанных с обработкой потока и временными задержками между сегментами аудио. Приведу подробное решение, которое поможет добиться необходимого поведения — при отсутствии данных ffplay
должен приостанавливать воспроизведение, а не продолжать "прокручивать" вперед.
Проблема
Как вы уже заметили, когда аудиоитедрат неожиданно перестает выдавать данные, ffplay
продолжает воспроизведение, что приводит к наложению звуков и потерям звуковых сегментов. Это случается потому, что ffplay
и ffmpeg
работают с буферизацией, и при отсутствии новых данных, playback engine начинает искать следующий фрагмент, который его просто отсутствует.
Решение
Для обеспечения корректного воспроизведения, вам нужно использовать ffmpeg
для обработки входящего потока и добавления тишины в те моменты, когда данных нет. Ниже приведен пример реализации, который включает в себя использование ffmpeg
для добавления тишины в пустые места вашей аудиопотока.
1. Настройка ffmpeg
для добавления тишины
Вы можете использовать ffmpeg
с опцией -f
для формата PCM, и добавить фильтрацию на основе временных интервалов, чтобы заполнить пустоты. Непосредственно в вашей команде ffmpeg
следует написать:
ffmpeg = subprocess.Popen(
[
"ffmpeg",
"-f", "s16le", # Формат входящего потока
"-ar", "24000", # Частота дискретизации
"-ac", "1", # Количество каналов
"-i", "pipe:", # Входной поток из Python
"-af", "apad", # Добавить тишину при отсутствии данных
"-f", "wav", # Вывод в формате WAV
"pipe:"
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
2. Изменение командной строки для ffplay
Команда для запуска ffplay
останется похожей, но теперь вы будете использовать stdout от ffmpeg
как вход для ffplay
:
ffplay = subprocess.Popen(
[
"ffplay",
"-nodisp",
"-i", "-",
],
stdin=ffmpeg.stdout,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
3. Подача данных
Как вы уже делали, данные передаются через stdin:
for segment in audio_iter:
ffmpeg.stdin.write(segment.raw_data)
Дополнительные рекомендации
- Обработка ошибок: Убедитесь, что вы добавили обработку ошибок при записи в stdin, чтобы избежать блокировок, когда
ffmpeg
ожидает данные. - Оптимизация буферизации: Возможны корректировки параметров буферизации, основанные на вашем аудиосигнале и задержках.
- Тестирование временных интервальных задержек: Не забудьте протестировать систему с различными задержками, чтобы убедиться, что она работает должным образом, как при длительных паузах, так и при частых сегментах.
Заключение
С помощью приведенных выше шагов вы сможете преодолеть проблему с "поиском" в ffplay
и обеспечить плавное воспроизведение аудио с приостановкой в моменты отсутствия данных. Это обеспечит эффективное использование аудиопотока и предотвратит потерю данных. Пожалуйста, протестируйте и адаптируйте предложенные решения согласно вашим требованиям.