Потоковое воспроизведение видео в браузере с помощью FFmpeg

01.05.2021 - Alexey Nurgaliev

Иногда возникает необходимость воспроизведения в браузере видео файлов, но использовать специальный видео хостинг (вроде YouTube) не всегда есть возможность (например, если файлы загружены пользователями).

Самый простой способ добавить видео на страницу, это использовать тег <video>:

<video src="/path/to/video.mp4">

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

По таблице совместимости на caniuse можно заметить, что для аудио лучше подходит MP3 - у него немного более широкая поддержка в браузерах. Но на практике использовать MP3 вместе с DASH (об этом пойдет речь ниже) может быть проблематично - например Video.js в некоторых случаях поддерживает воспроизведение только AAC.

Все перечисленные проблемы решает потоковое воспроизведение видео, если на стороне сервера файлы подготавливаются при помощи FFmpeg, а на стороне браузера используется плеер Video.js.

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

Пример встраивания видео на страницу при помощи тега video

Пример встраивания видео на страницу при помощи Video.js

Потоковое воспроизведение видео в браузере

Для потокового воспроизведения (streaming, стриминга) видео в браузере в разное время использовались разные решения. Раньше было популярно использование протоколов RTSP/RTMP. Встроенной их поддержки в браузерах не было (ее и сейчас нет), но существовали плееры на основе Flash. Сейчас использование Flash невозможно, поэтому RTSP/RTMP используются в основном для передачи видео между серверами, а в браузерах нужно использовать другие решения.

Специально для веба были разработаны протоколы передачи видео, использующие в качестве транспорта HTTP - стандартный протокол для всех браузеров. Здесь можно выделить HLS, который разработан и поддерживается Apple, и DASH, который разработан и поддерживается международной организацией MPEG. Оба протокола зарегистрированы как стандарты:

Еще есть возможность передачи видео через WebRTC, но это требует использования специального сервера для передачи данных (вроде Kurento).

Далее будет рассматриваться использование протокола DASH - на практике он показался более гибким и удобным.

DASH и полное перекодирование

Формат DASH поддерживает использование видео с адаптивным битрейтом, т.е. выбор во время воспроизведения видео, наиболее подходящего скорости соединения или возможностям устройства пользователя. Также поддерживается выбор из нескольких аудио-потоков.

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

FFmpeg поддерживает конвертирование видео в формат DASH, для этого можно воспользоваться примерно такой командой:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ffmpeg -hide_banner -y -i input.mp4 \
       -r 25 -c:v libx264 -pix_fmt yuv420p -preset veryfast -profile:v main \
       -keyint_min 250 -g 250 -sc_threshold 0 \
       -c:a aac -b:a 128k -ac 2 -ar 48000 \
       -map v:0 -filter:v:0 "scale=-2:360"  -b:v:0 800k  -maxrate:0 856k  -bufsize:0 1200k \
       -map v:0 -filter:v:1 "scale=-2:432"  -b:v:1 1400k -maxrate:1 1498k -bufsize:1 2100k \
       -map v:0 -filter:v:2 "scale=-2:540"  -b:v:2 2000k -maxrate:2 2140k -bufsize:2 3500k \
       -map v:0 -filter:v:3 "scale=-2:720"  -b:v:3 2800k -maxrate:3 2996k -bufsize:3 4200k \
       -map v:0 -filter:v:4 "scale=-2:1080" -b:v:4 5000k -maxrate:4 5350k -bufsize:4 7500k \
       -map 0:a \
       -init_seg_name "init-\$RepresentationID\$.\$ext\$" \
       -media_seg_name "chunk-\$RepresentationID\$-\$Number%05d\$.\$ext\$" \
       -dash_segment_type mp4 \
       -use_template 1 \
       -use_timeline 0 \
       -seg_duration 10 \
       -adaptation_sets "id=0,streams=v id=1,streams=a" \
       -f dash \
       dash.mpd

Параметры здесь указаны следующие:

В результате выполнения команды будет создано некоторое количество файлов (о них будет подробнее написано ниже). Для воспроизведения результата можно воспользоваться таким html-кодом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link href="https://vjs.zencdn.net/7.10.2/video-js.css" rel="stylesheet" />
    <title>DASH</title>
</head>
<body>

<video id="my-video" class="video-js" controls preload="auto" width="640" data-setup="{}">
    <source src="dash.mpd" type="application/dash+xml" />
    <p class="vjs-no-js">NO SIGNAL</p>
</video>

<script src="https://vjs.zencdn.net/7.10.2/video.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/videojs-contrib-quality-levels@2.0.9/dist/videojs-contrib-quality-levels.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/videojs-hls-quality-selector@1.1.4/dist/videojs-hls-quality-selector.min.js"></script>
<script>
    const player = videojs('my-video');
    player.hlsQualitySelector();
</script>
</body>
</html>

Здесь используется плеер Video.js, который поддерживает формат DASH, с компонентом, позволяющим вручную выбирать качество видео. По умолчанию качество видео будет подстраиваться автоматически.

Таким образом решены все перечисленные ранее проблемы:

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

Пример встраивания видео в формате DASH на страницу

Список сгенерированных файлов

Файлы DASH

Здесь будет описываться только некоторая часть формата DASH для VOD (Video-on-Demand), для LIVE-трансляций файлы и описания могут быть другими.

Формат DASH определяет три типа файлов:

Файл описания dash.mpd, созданный FFmpeg с параметрами, описанными выше, будет выглядеть так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="utf-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="urn:mpeg:dash:schema:mpd:2011"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
     profiles="urn:mpeg:dash:profile:isoff-live:2011"
     type="static"
     mediaPresentationDuration="PT53.3S"
     maxSegmentDuration="PT10.0S"
     minBufferTime="PT20.0S">
  <ProgramInformation>
    <Title>sample</Title>
  </ProgramInformation>
  <ServiceDescription id="0">
  </ServiceDescription>
  <Period id="0" start="PT0.0S">
    <AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" 
                   frameRate="25/1" maxWidth="1920" maxHeight="1080" par="16:9" lang="und">
      <Representation id="0" mimeType="video/mp4" codecs="avc1.4d401e" bandwidth="800000" width="640" height="360" sar="1:1">
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
      <Representation id="1" mimeType="video/mp4" codecs="avc1.4d401e" bandwidth="1400000" width="768" height="432" sar="1:1">
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
      <Representation id="2" mimeType="video/mp4" codecs="avc1.4d401f" bandwidth="2000000" width="960" height="540" sar="1:1">
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
      <Representation id="3" mimeType="video/mp4" codecs="avc1.4d401f" bandwidth="2800000" width="1280" height="720" sar="1:1">
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
      <Representation id="4" mimeType="video/mp4" codecs="avc1.4d4028" bandwidth="5000000" width="1920" height="1080" sar="1:1">
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
    <AdaptationSet id="1" contentType="audio" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" lang="rus">
      <Representation id="5" mimeType="audio/mp4" codecs="mp4a.40.2" bandwidth="128000" audioSamplingRate="48000">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
        <SegmentTemplate timescale="1000000" duration="10000000" 
                         initialization="init-$RepresentationID$.m4s" 
                         media="chunk-$RepresentationID$-$Number%05d$.m4s" 
                         startNumber="1">
        </SegmentTemplate>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

Это файл XML, в котором представлены основные параметры видео и аудио - общая продолжительность, количество потоков, форматы и имена файлов сегментов. В файле можно выделить такие сведения:

Файлы init-0.m4sinit-5.m4s - это сегменты инициализации - они для каждого потока хранят информацию о кодеке (данных аудио и видео в них нет). Сегменты инициализации скачиваются перед началом воспроизведения потока.

Файлы вида chunk-0-00001.m4s - это сегменты с данными, они хранят данные для каждого потока видео или аудио. Формат этих файлов определяется выбранным типом контейнера (см. параметр dash_segment_type). В каждом файле хранится фрагмент видео или аудио длиной около seg_duration (в данном примере 10 секунд). Эти файлы по отдельности могут не воспроизводиться обычными плеерами, т.к. они могут не содержать сведения о кодеке (которые находятся в сегменте инициализации).

Проанализировав файлы и их содержимое можно предположить, что вместо многократного полного перекодирования исходного файла можно попробовать создавать нужные файлы по запросу. Т.е. содержимое файла mpd и сегментов инициализации известно заранее, и их можно быстро сгенерировать. А сегменты с данными можно попробовать получать перекодировав только часть исходного файла (длиной в один сегмент). Этот подход будет описан в следующих разделах.

Создание сегментов по запросу

Проще всего сгенерировать файл mpd и сегменты инициализации. Они не требуют полного кодирования исходного файла - достаточно перекодировать небольшой фрагмент (например секунду). Команда для кодирования будет во многом аналогична исходной:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ffmpeg -hide_banner -y \
       -ss 0 -to 1 \
       -i input.mp4 \
       -r 25 -c:v libx264 -pix_fmt yuv420p -preset veryfast -profile:v main \
       -keyint_min 250 -g 250 -sc_threshold 0 \
       -c:a aac -b:a 128k -ac 2 -ar 48000 \
       -map v:0 -filter:v:0 "scale=-2:360"  -b:v:0 800k  -maxrate:0 856k  -bufsize:0 1200k \
       -map v:0 -filter:v:1 "scale=-2:432"  -b:v:1 1400k -maxrate:1 1498k -bufsize:1 2100k \
       -map v:0 -filter:v:2 "scale=-2:540"  -b:v:2 2000k -maxrate:2 2140k -bufsize:2 3500k \
       -map v:0 -filter:v:3 "scale=-2:720"  -b:v:3 2800k -maxrate:3 2996k -bufsize:3 4200k \
       -map v:0 -filter:v:4 "scale=-2:1080" -b:v:4 5000k -maxrate:4 5350k -bufsize:4 7500k \
       -map 0:a \
       -init_seg_name "init-\$RepresentationID\$.\$ext\$" \
       -media_seg_name "chunk-\$RepresentationID\$-\$Number%05d\$.\$ext\$" \
       -dash_segment_type mp4 \
       -use_template 1 \
       -use_timeline 0 \
       -seg_duration 10 \
       -adaptation_sets "id=0,streams=v id=1,streams=a" \
       -f dash \
       dash.mpd

Добавились параметры -ss 0 -to 1 - они говорят, что нужно взять отрезок видео с 0 до 1 секунды (можно и просто указать -to 1, в этом случае начало будет неявно установлено на 0). Для этих параметров важно, располагаются они до -i или после. Если до, то поиск идет по исходному файлу (относительно быстро), а если после, то поиск идет по перекодированному файлу. Т.е. если нужно взять фрагмент через 30 секунд от начала, то в первом случае FFmpeg отступит 30 секунд в исходном файле и начнет кодирование, а во втором случае первые 30 секунд будут тоже перекодированы.

Получившийся файл dash.mpd будет аналогичен приведенному выше за одним исключением - mediaPresentationDuration (т.е. общая длительность видео) будет равно 1 секунде. Это значение нужно будет заменить на действительную продолжительность видео, которую можно получить при помощи программы ffprobe - ее применение будет рассмотрено в отдельном разделе.

Также будут созданы сегменты инициализации, их изменение не требуется. Сегменты данных содержат только одну секунду перекодированных потоков, поэтому их можно удалить.

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

Из-за особенностей кодировщика mp4 в FFmpeg и особенностей обработки сегментов mp4 в Video.js, использовать контейнер mp4 для сегментов не получится. Video.js может использовать контейнер MPEG-TS, хотя и с ограничениями на формат видео и аудио - выбор ограничен H.264 для видео и AAC для аудио - поэтому для кодирования аудио нельзя использовать mp3.

Возможность использовать MPEG-TS для сегментов данных - это особенность Video.js, другие плееры могут не поддерживать воспроизведение такого видео.

Следующие две проблемы связаны с кодированием аудио.

Если попробовать разделить аудио-поток на фрагменты ровно по 10 секунд и затем склеить их обратно без полного перекодирования (так же будет происходить склейка сегментов при воспроизведении), то можно будет заметить, что длина получившегося файла будет немного больше и на месте склеек появятся фрагменты длиной в несколько миллисекунд, не содержащие звука (см. изображение ниже). При воспроизведении эти фрагменты будут давать явно слышимые искажения.

Искажения звука

На рисунке пример склейки аудио фрагментов длинной в одну секунду, отмечены места появившихся искажений. Повторить эту склейку можно при помощи таких команд (а результат открыть, например, в audacity):

1
2
3
4
5
ffmpeg -hide_banner -i -ss 0 -to 1 -i input.mp4 -vn -c:a aac audio-1.aac
ffmpeg -hide_banner -i -ss 1 -to 2 -i input.mp4 -vn -c:a aac audio-2.aac
ffmpeg -hide_banner -i -ss 2 -to 3 -i input.mp4 -vn -c:a aac audio-3.aac
echo "file 'audio-1.aac'\nfile 'audio-2.aac'\nfile 'audio-3.aac'" > join.txt 
ffmpeg -hide_banner -i -f concat -i join.txt -c:a copy audio.aac

Первая проблема. Кодек AAC делит аудио-данные на фреймы, каждый фрейм содержит 1024 значения. При частоте дискретизации 48000 Гц, получается, что один фрейм содержит 1024 / 48000 = 0.02133333 секунд аудио. Количество фреймов в файле должно быть обязательно целым и, если данных в конце потока не хватает на полный фрейм, то он будет дополнен нулевыми значениями (т.е. отсутствием звука, тишиной). Получается, что в худшем случае неровное разделение на фреймы может давать примерно 0.021 лишних секунд к общей длине аудио-потока на каждую склейку, и это будет одним из источников искажений звука.

Решить эту проблему можно подбирая длину сегмента, кратную длине фрейма, т.е.

Вторая проблема. Кодек AAC всегда добавляет в начало файла один пустой фрейм. Т.е. при частоте дискретизации 48000 Гц каждый сегмент будет начинаться с примерно 0.021 секунд тишины. При склейке аудио без перекодирования эти фреймы сохраняются и тоже являются источником искажений звука. Средствами FFmpeg отключить создание пустого фрейма нельзя, вырезать ровно один фрейм из аудио тоже не получается.

Решить проблему можно, если сначала перенести начало сегмента на AudioOverhead секунд раньше, а затем вырезать фрагмент начиная с AudioOverhead секунды без перекодирования (т.е. указав -c:a copy). Если не было перекодирования, то пустой фрейм не создается и сегмент будет начинаться сразу со звука. Значение AudioOverhead нужно подобрать кратным длине фрейма AAC, чтобы не получить неполные фреймы.

Пустой фрейм в начале файла - особенность не только AAC, но и других кодеков, кодирующих с потерями, например MP3.

Так как теперь длина сегмента не ровно 10 секунд, то при разделении видео на сегменты может оказаться, что раздел пройдет по кадру (т.е. в сегменте будет не целое число кадров). В худшем случае может получиться, что при склейке один кадр либо пропадет, либо продублируется. Решить проблему можно, подбирая длину сегмента, кратную частоте кадров, т.е.

Длина видео и аудио сегментов не совпадает, поэтому итоговая длина сегмента должна быть кратна и длительности аудио фрейма, и длительности кадра видео. Для частоты кадров видео, равной 25, и частоте дискретизации аудио, равной 48000, длина фрейма будет равна 0.32 секунды. Вывод этого значения будет рассмотрен в отдельном разделе.

Теперь можно определить время начала и окончания сегментов:

Команда для кодирования одного сегмента видео будет выглядеть примерно так:

1
2
3
4
5
6
7
8
9
10
11
12
ffmpeg -hide_banner -y \
       -ss VideoSegmentStart \
       -to VideoSegmentEnd \
       -i input.mp4 \
       -copyts -start_at_zero \
       -an \
       -r 25 -c:v libx264 -pix_fmt yuv420p -preset veryfast -profile:v main \
       -keyint_min 250 -g 250 -sc_threshold 0 \
       -map v:0 -filter:v:0 "scale=-2:360" -b:v:0 800k -maxrate:0 856k -bufsize:0 1200k \
       -f mpegts \
       -muxdelay 0 -muxpreload 0 \
       chunk-video.m4s

Здесь добавились новые параметры:

Выбор параметров кодирования видео зависит от номера потока, для которого нужно сгенерировать сегмент.

Для кодирования аудио нужно две команды. Первая перекодирует фрагмент аудио-потока исходного файла в AAC:

1
2
3
4
5
6
7
8
9
10
11
ffmpeg -hide_banner -y \
       -ss AudioSegmentStart \
       -to AudioSegmentEnd \
       -i input.mp4 \
       -copyts -start_at_zero \
       -vn \
       -map a:0 \
       -c:a aac -b:a 128k -ac 2 -ar 48000 \
       -f mpegts \
       -muxdelay 0 -muxpreload 0 \
       chunk-audio.tmp.m4s

Вторая уберет пустой первый фрейм:

1
2
3
4
5
6
7
8
ffmpeg -hide_banner -y \
       -ss AudioSegmentTrimStart \
       -i chunk-audio.tmp.m4s \
       -c:a copy -vn \
       -copyts \
       -f mpegts \
       -muxdelay 0 -muxpreload 0 \
       chunk-audio.m4s

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

Комментарий по производительности. Если перекодированные сегменты данных сохранять и при повторном запросе вместо перекодирования отдавать ранее сохраненный сегмент, то для воспроизведения всего файла потребуется столько же вычислений, сколько и для полного перекодирования. Отличие только в том, что вычисления будут выполняться не все сразу, а будут распределены по времени. При полном перекодировании нагрузка будет более предсказуемой - пока обработка файла не завершится, он не будет доступен для воспроизведения. При перекодировании по запросу долгого ожидания доступности файла не будет, можно начинать воспроизведение, как только будет готов первый сегмент, но в таком случае сложно предсказать, успеет ли завершиться перекодирование следующего сегмента за время воспроизведения текущего.

Несколько аудио-потоков

Формат DASH позволяет включать в видео несколько аудио-потоков, а Video.js позволяет во время воспроизведения их переключать. Для этого каждый поток должен располагаться в отдельной секции <AdaptationSet>. Иначе, если все потоки собрать в одну секцию, то плеер это воспримет как варианты одного потока и возможности переключения не будет.

Кодируются дополнительные аудио-потоки так же, номер потока нужно выбирать в параметре -map a:0.

Подготовка субтитров

Формат DASH поддерживает субтитры к видео, но FFmpeg эту функциональность при кодировании DASH не поддерживает. Плеер Video.js самостоятельно поддерживает отображение субтитров, для этого их нужно перекодировать в формат WebVTT и передать в параметре конфигурации tracks.

Перекодировать можно с помощью FFmpeg:

1
2
3
4
ffmpeg -hide_banner -y \
       -i input.mp4 \
       -map s:0 -c:s webvtt \
       subtitles.webvtt

В параметрах Video.js субтитры добавляются следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
{
  ...
  tracks: [
    {
        src: '/ссылка/на/файл/субтитров.webvtt',
        kind: 'subtitles',
        srclang: 'код языка субтитров',
        label: 'отображаемое в интерфейсе название'
    } 
  ],
  ...
}

Получение информации о видео файле с помощью ffprobe

Для получения разнообразной информации о видео файле: длительности, о количестве, видах и свойствах потоков, можно использовать программу ffprobe, она обычно устанавливается вместе с FFmpeg. Получить информацию о потоках в формате, удобном для программного разбора, можно такой командой:

1
2
3
4
ffprobe -loglevel error \
        -show_entries streams \
        -of default=noprint_wrappers=1:nokey=0 \
        input.mp4

Будет выведена примерно такая информация:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=Main
codec_type=video
codec_tag_string=avc1
codec_tag=0x31637661
width=1920
height=1080
coded_width=1920
coded_height=1080
closed_captions=0
has_b_frames=2
sample_aspect_ratio=1:1
display_aspect_ratio=16:9
pix_fmt=yuv420p
level=40
color_range=tv
color_space=bt709
color_transfer=bt709
color_primaries=bt709
chroma_location=left
field_order=unknown
refs=1
is_avc=true
nal_length_size=4
id=N/A
r_frame_rate=24000/1001
avg_frame_rate=38340000/1599097
time_base=1/90000
start_pts=0
start_time=0.000000
duration_ts=4797291
duration=53.303233
bit_rate=1692919
max_bit_rate=N/A
bits_per_raw_sample=8
nb_frames=1278
nb_read_frames=N/A
nb_read_packets=N/A
...

Информация о каждом потоке начинается со строки index=. Тип потока определяется по строке codec_type=.

Также нужно обратить внимание, что частота кадров определяется в виде дроби. r_frame_rate - это частота кадров из метаданных потока, а avg_frame_rate - это частота, полученная делением количества кадров на длительность потока.

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

1
2
3
4
ffprobe -loglevel error \
        -show_entries format=duration \
        -of default=noprint_wrappers=1:nokey=0 \
        input.mp4

Результатом будет длительность в секундах:

duration=53.376000

Вывод общей длины фрейма для аудио и видео

Нужно определить такое минимальное количество секунд, в которое помещается целое количество кадров видео и фреймов аудио (AAC). Видео рассматривается с частотой кадров 25, аудио с частотой дискретизации 48000.

Длительность одного кадра видео будет равна:

\[VideoFrameDuration = \frac{1}{25} = \frac{1}{5^2}\]

Рассмотрим длительность фрейма аудио:

\[AudioFrameDuration = \frac{1024}{48000} = \frac{2^{10}}{2^7 \cdot 3 \cdot 5^3} = \frac{2^3}{3 \cdot 5^3} = \frac{2^3}{3 \cdot 5} \cdot \frac{1}{5^2} = \frac{2^3}{3 \cdot 5} \cdot VideoFrameDuration\]

Чтобы получить целое число, нужно умножить AudioFrameDuration на 15, получится:

\[15 \cdot AudioFrameDuration = 15 \cdot \frac{2^3}{3 \cdot 5} \cdot VideoFrameDuration = 8 \cdot VideoFrameDuration\]

Т.е. длительность пятнадцати фреймов аудио равна длительности восьми кадров видео. В итоге минимальная длительность фрейма для аудио и видео равна:

\[8 \cdot VideoFrameDuration = 8 \cdot \frac{1}{25} = 0.32\]

Ссылки

Примеры

Лицензия Creative Commons
Code More Team - GitHub