Рез всегда точный. Раньше начало куска притягивалось к ближайшему опорному кадру, а звук брался из непритянутой точки: по журналам сборки средняя притяжка 0.24 с, худшая 0.99 с, и на длинном куске этот сдвиг держится все его минуты. Плата за точность — доли секунды перекодирования на рез. Второй вид реза (Shift+S) стал тем же самым и убран. Разбор заголовка MP4 не учитывал список правок (elst): метки опорных кадров уезжали на задержку показа, на пробе два кадра. Сверка быстрого пути с ffprobe теперь совпадает точно, в том числе на записи с B-кадрами. Куску заказывается точное число кадров, а не только время: при копировании ffmpeg отмеряет конец по времени декодирования, и у записи с B-кадрами кусок выходил длиннее. Склейке длительность каждого куска задаётся явно. Последний пакет скопированного куска показывается позже прочих, контейнер считал его в длительность, и время картинки растягивалось — 0.05 с на кусок. Звука в кусках картинки больше нет: он не используется, а в MPEG-TS ещё и сбивает выравнивание склейки на 36 мс. Границы кусков ложатся на кадровую сетку: картинка выходит целым числом кадров, звук набирается до отсчёта, и доли кадра разводили их в стороны. Разогрев кодировщика звука компенсируется: сжатый AAC начинается со служебных отсчётов, MP4 помечает их пропускаемыми, Matroska — нет, и звук ехал на 21.3 мс позже. Величина меряется пробным сжатием. Продолжение прерванной сборки чинится заодно: готовность проверяется у каждой части, а не у куска целиком. При точном резе частей две, и вторая при продолжении терялась. В журнал сборки добавлена проверка синхронности: сколько кадров вышло и сколько просил монтаж, с вердиктом и худшей частью. Замер (tests/syncprobe.sh, исходник со вспышкой и щелчком на каждой секунде): было до 0.6 с и росло по ходу ролика, стало 0.02-0.04 с без накопления при точности измерения 0.017 с.
27 lines
1.2 KiB
Python
27 lines
1.2 KiB
Python
#!/usr/bin/env python3
|
||
"""Времена щелчков в звуковой дорожке — по самим отсчётам.
|
||
|
||
Читает сырой звук (16 бит, моно, 48000) со стандартного ввода и печатает
|
||
время начала каждого громкого куска. Отсчёты, а не окна анализа: любой
|
||
оконный способ уплывает по времени, потому что окно двигается кратно
|
||
своему размеру, а не секунде, и на замере синхронности это выглядит как
|
||
накапливающийся рассинхрон, которого нет.
|
||
"""
|
||
import sys
|
||
import struct
|
||
|
||
RATE = 48000
|
||
THRESHOLD = 3000 # из 32767: щелчок заметно громче тишины
|
||
GAP = int(RATE * 0.5) # полсекунды тишины разделяет соседние щелчки
|
||
|
||
data = sys.stdin.buffer.read()
|
||
count = len(data) // 2
|
||
samples = struct.unpack('<%dh' % count, data[:count * 2])
|
||
|
||
last_loud = -GAP * 2
|
||
for i, value in enumerate(samples):
|
||
if abs(value) < THRESHOLD:
|
||
continue
|
||
if i - last_loud > GAP:
|
||
print('%.6f' % (i / RATE))
|
||
last_loud = i
|