ffplay считывает PCM данные из канала – при отсутствии данных пауза вместо продолжения поиска

Вопрос или проблема

Я использую 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)

Дополнительные рекомендации

  1. Обработка ошибок: Убедитесь, что вы добавили обработку ошибок при записи в stdin, чтобы избежать блокировок, когда ffmpeg ожидает данные.
  2. Оптимизация буферизации: Возможны корректировки параметров буферизации, основанные на вашем аудиосигнале и задержках.
  3. Тестирование временных интервальных задержек: Не забудьте протестировать систему с различными задержками, чтобы убедиться, что она работает должным образом, как при длительных паузах, так и при частых сегментах.

Заключение

С помощью приведенных выше шагов вы сможете преодолеть проблему с "поиском" в ffplay и обеспечить плавное воспроизведение аудио с приостановкой в моменты отсутствия данных. Это обеспечит эффективное использование аудиопотока и предотвратит потерю данных. Пожалуйста, протестируйте и адаптируйте предложенные решения согласно вашим требованиям.

Оцените материал
Добавить комментарий

Капча загружается...