FFmpeg: alinear video y audio de captura de escritorio

Estoy usando gdigrab y dshow integrados de FFmpeg para grabar la pantalla y el audio de mi sistema. Estoy codificando a video H.264/AVC sin comprimir y audio PCM/WAV sin comprimir.

Había tenido dos problemas con la sincronización de audio/video:

  1. El video y el audio no están alineados (el video y el audio no comienzan al mismo tiempo).
  2. Deriva de audio (el audio se desincroniza progresivamente con el video).

Originalmente estaba usando un solo comando para capturar/codificar. Algo como lo siguiente:

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"

Pero pude resolver mi segundo problema (la deriva) capturando video y audio en procesos FFmpeg separados. Tenga en cuenta que estoy usando un shell tipo UNIX ( MSYS2 con BASH). El siguiente es un ejemplo de un script de shell que estoy ejecutando:

# 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

Entonces, el problema que queda es que los datos de video y audio no comienzan alineados correctamente. El audio generalmente está entre 0 y 300 ms por delante del video.

Se puede resolver fácilmente ejecutando el archivo de salida a través de FFmpeg nuevamente, o un programa separado como Avidemux , para ajustar el retraso de audio. Esto se puede hacer sin volver a codificar en Avidemux (no estoy seguro acerca de FFmpeg).

Sin embargo, preferiría resolver este problema dentro del proceso/secuencia de comandos de captura para poder evitar el paso adicional de alinear los datos manualmente.

Recientemente realicé una limpieza del sistema eliminando archivos basura, asegurándome de que la fragmentación en mi disco sea baja y desactivando los procesos en segundo plano innecesarios. Pero la alineación de audio/video todavía suele estar desactivada.

Entonces, finalmente, a la pregunta simple y simple: ¿Hay alguna manera de hacer que los dos procesos FFmpeg comiencen a capturar al mismo tiempo para que el video y el audio estén lo más sincronizados posible? ¿Puedo usar el reloj del sistema como semilla?

O, ¿hay un método mejor que el que estoy usando en este momento? Por ejemplo, un solo proceso FFmpeg que capturará ambos mientras mantiene los datos alineados y evita la deriva de audio.

Me parece que el problema es simplemente que el proceso de audio comienza antes que el proceso de video. Escuché sobre canalizar comandos FFmpeg, pero no estoy seguro de la forma correcta de hacerlo. He encontrado información sobre tuberías que estoy tratando de averiguar:

Información de FFmpeg: versión estática oficial de 64 bits de 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

Estas son las especificaciones de mi sistema:

-- Editar --

Descubrí un comando de canalización, pero el resultado es el mismo. El audio está ligeramente por delante:

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"

Respuestas (2)

Después de hacer lo que describe con archivos separados por un tiempo, recientemente comencé a usar secuencias incrustadas en su lugar. Es tarde y estoy cansada, pero deberías poder ver cómo funciona, y puedo dar más detalles más tarde si me necesitas.

usando archivos de salida separados, solo para que pueda ver cómo lo configuré ... como referencia.

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 &

usando secuencias incrustadas

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 &

flujos incrustados, micrófono combinado para limpiar un poco mi problema de ruido de línea, y algunas mezclas de flujo para poner la salida y el micrófono juntos en su propio flujo

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 &

Si es Windows, ¿por qué no usar dshow en lugar de gdigrab?

Consulte https://github.com/rdp/screen-capture-recorder-to-video-windows-free

Si tanto el video como el audio fueran a través de dshow, entonces podría usar ffmpeg así:

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