FFmpeg: выравнивание видео и аудио при захвате рабочего стола

Я использую встроенный в FFmpeg gdigrab и dshow для записи дисплея и звука моей системы. Я кодирую несжатое видео H.264/AVC и несжатое аудио PCM/WAV.

У меня были две проблемы с синхронизацией аудио/видео:

  1. Видео и аудио не выровнены (видео и аудио не запускаются одновременно).
  2. Дрейф звука (звук постепенно теряет синхронизацию с видео).

Первоначально я использовал одну команду для захвата/кодирования. Что-то вроде следующего:

ffmpeg -hide_banner -rtbufsize 1000M -f gdigrab -framerate 60 -draw_mouse 0 \
  -i "title=<window_name>" -f dshow -i audio="<sys_audio/mic>" -c:v libx264 \
  -preset ultrafast -qp 0 -x264opts keyint=1 -c:a pcm_s16le -ac 1 "<out_file>.mkv"

Но мне удалось решить мою вторую проблему (дрейф) путем захвата видео и аудио в отдельных процессах FFmpeg. Обратите внимание, что я использую UNIX-подобную оболочку ( MSYS2 с BASH). Ниже приведен пример сценария оболочки, который я запускаю:

# capture audio & get PID
ffmpeg -hide_banner -rtbufsize 500M -f dshow -i audio="<sys_audio/mic>" \
  -c:a pcm_s16le -ac 1 "<out_file>-audio.wav" & APID=$!

# capture video
ffmpeg -hide_banner -rtbufsize 1000M -f gdigrab -framerate 60 -draw_mouse 0 \
  -i "<window_name>" -c:v libx264 -preset ultrafast -qp 0 -x264opts keyint=1 \
  "<out_file>-video.mkv"

# get exit code of video process
VIDRET=$?

# send interrupt signal to audio process after video process exits
kill -s SIGINT ${APID}

# mux video & audio if video process exited okay
if [ "${VIDRET}" -eq "0" ]; then
    ffmpeg -hide_banner -i "${OUTVID}" -i "${OUTAUD}" -map 0:0 -map 1:0 \
      -c copy "<out_file>.mkv"

    # delete temp video & audio streams if muxing succeeded
    if [ "$?" -eq "0" ]; then
        rm "${OUTVID}" "${OUTAUD}"
    fi
fi

Таким образом, оставшаяся проблема заключается в том, что видео- и аудиоданные не начинают правильно выравниваться. Звук обычно где-то между 0 мс-300 мс опережает видео.

Это можно легко решить, снова запустив выходной файл через FFmpeg или отдельную программу, такую ​​как Avidemux , для настройки задержки звука. Это можно сделать без перекодирования в Avidemux (насчет FFmpeg не уверен).

Однако я бы предпочел решить эту проблему в процессе/скрипте захвата, чтобы избежать дополнительного шага выравнивания данных вручную.

Недавно я выполнил очистку системы, удалив ненужные файлы, убедившись, что фрагментация на моем диске низкая, и отключил ненужные фоновые процессы. Но выравнивание аудио/видео по-прежнему часто не работает.

Итак, наконец, к простому и простому вопросу: есть ли способ заставить два процесса FFmpeg начать захват одновременно, чтобы получить видео и аудио как можно ближе к синхронизации? Могу ли я использовать системные часы в качестве начального значения?

Или есть лучший метод, чем я использую прямо сейчас? Например, один процесс FFmpeg, который будет захватывать оба, сохраняя при этом выравнивание данных и избегая дрейфа звука.

Мне кажется, что проблема просто в том, что аудиопроцесс запускается раньше видеопроцесса. Я слышал о передаче команд FFmpeg, но не уверен, как правильно это сделать. Я нашел некоторую информацию о трубопроводе, которую я пытаюсь выяснить:

Информация о FFmpeg: Официальная статическая 64-битная сборка от Zeranoe .

ffmpeg version N-92511-g0279cb4f69 Copyright (c) 2000-2018 the FFmpeg developers
    built with gcc 8.2.1 (GCC) 20181017
    configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig
        --enable-gnutls --enable-iconv --enable-libass --enable-libbluray
        --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb
        --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine
        --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame
        --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264
        --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib
        --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc
        --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom
        --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va
        --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth

Это мои системные характеристики:

-- Редактировать --

Я понял команду pipe, но результат тот же. Аудио чуть впереди:

ffmpeg -hide_banner -rtbufsize 500M -f dshow -ac 1 -i audio="<sys_audio/mic>" \
  -c:a pcm_s16le -f s16le pipe: | ffmpeg -y -hide_banner -rtbufsize 1500M -f gdigrab \
  -thread_queue_size 128 -framerate 60 -draw_mouse 0 -i title="<window_name>" -f s16le \
  -thread_queue_size 128 -i pipe: -map 0:0 -map 1:0 -c:v libx264 -preset ultrafast -qp 0 \
  -x264opts keyint=1 -c:a copy "<out_file>.mkv"

Ответы (2)

После того, как вы некоторое время делали то, что вы описываете, с отдельными файлами, я недавно начал использовать вместо этого встроенные потоки. Уже поздно, и я устал, но вы должны увидеть, как это работает, и я могу уточнить позже, если вам нужно.

используя отдельные выходные файлы, просто чтобы вы могли увидеть, как я это настроил... для справки.

ffmpeg -hide_banner \
    -re \
    $videoDecodeMode \
    -f pulse \
        -i $audioOut \
    -f pulse \
        -i $audioMic \
    -f x11grab \
        -r $frameRate \
        -s $resolution \
        -i :0.0 \
        -g 60 \
    $videoEncodeMode \
    -map 2:0 \
        -metadata handler="video:1920x1080@60" \
        "${exportName}" \
    -map 0:0 \
        -metadata title="audio:out" \
        "${exportName%.*}_audioOut.wav" \
    -map 1:0 \
        -metadata title="audio:microphone" \
        "${exportName%.*}_audioMic.wav" </dev/null &

использование встроенных потоков

ffmpeg -hide_banner \
    -re \
    $videoDecodeMode \
    -f pulse \
        -thread_queue_size 1024 \
        -i $audioOut \
    -f pulse \
        -thread_queue_size 1024 \
        -i $audioMic \
    -f x11grab \
        -r $frameRate \
        -s $resolution \
        -thread_queue_size 1024 \
        -i :0.0 \
    -g 60 \
    -map 2:v \
    -map 0:a \
    -map 1:a \
    -c:a aac -ac 2 -b:a 128k -r:a 48000 \
    -metadata:s:v:0 handler="video:1920x1080@60" \
    -metadata:s:a:2 handler="audio:out" \
    -metadata:s:a:3 handler="audio:microphone" \
    $videoEncodeMode \
    "${exportName}"  </dev/null &

встроенные потоки, компандированный микрофон, чтобы немного решить мою проблему с линейным шумом, и некоторое микширование потоков, чтобы объединить вывод и микрофон в их собственный поток.

ffmpeg -hide_banner \
    -re \
    $videoDecodeMode \
    -f pulse \
        -thread_queue_size 1024 \
        -i $audioOut \
    -f pulse \
        -thread_queue_size 1024 \
        -i $audioMic \
    -f x11grab \
        -r $frameRate \
        -s $resolution \
        -thread_queue_size 1024 \
        -i :0.0 \
    -g 60 \
    -filter_complex \
        "[1:a]aformat=channel_layouts=stereo,asplit=2[micOrig][micNew]\
        ;[micNew]\
            compand=0:0.2:-26/-900|-16/-16|0/-10|10/-900:6:0:0:0\
        [micCleaned]\
        ;[0:a][micCleaned]amix=inputs=2:duration=first[allAudio]" \
    -map 2:v -map 0:a \
    -map [allAudio] -map [micOrig] \
    -c:a aac -ac 2 -b:a 128k -r:a 48000 \
    -metadata:s:v:0 handler="video:1920x1080@60" \
    -metadata:s:a:2 handler="audio:out" \
    -metadata:s:a:1 handler="audio:combined" \
    -metadata:s:a:3 handler="audio:microphone" \
    $videoEncodeMode \
    "${exportName}"  </dev/null &

Если это Windows, то почему бы не использовать dshow вместо gdigrab?

Пожалуйста, посмотрите https://github.com/rdp/screen-capture-recorder-to-video-windows-free

Если и видео, и аудио будут передаваться через dshow, вы можете использовать ffmpeg следующим образом:

ffmpeg -f dshow -i video="VIDEO_DEVICE":audio="FIRST_AUDIO_DEVICE" -f dshow -i audio="ANOTHER_AUDIO_DEVICE" ...