Проверка временной шкалы iOS: точность и границы CMTime

Проверка временной шкалы iOS: точность и границы CMTime

Проверка временной шкалы iOS: точность и границы CMTime

Субтитры на мгновение мигают на стыке фрагментов, а после обрезки остаётся лишний кадр? Не спешите менять плеер. Перенесите проблемные временные метки на облачный Mac, проверьте вычисления отдельным скриптом Swift, а затем вернитесь в приложение и проверьте результат декодирования. Так можно отделить ошибку в расчёте времени от неточного позиционирования при воспроизведении, не загружая весь исходный материал заново при каждой проверке.

Сначала определите правила работы со временем, а не переводите его в секунды

CMTime представляет время с помощью целочисленного числителя value и масштаба времени timescale, а также хранит сведения о допустимости значения, состоянии округления и epoch. Значение 1/30 секунды можно представить точно, но при переводе в целое число миллисекунд точность теряется. Форматирование seconds с тремя знаками после запятой — лишь способ отображения, а не основание считать два значения времени равными.

Сначала зафиксируйте в репозитории четыре правила: на вход принимаются только числовые значения времени; значения, участвующие в вычислениях на одной временной шкале, должны иметь одинаковый epoch; фрагмент задаётся началом и длительностью; конечная точка не входит в текущий фрагмент. Длительность не может быть отрицательной. Фрагменты нулевой длительности следует либо отклонять, либо считать пустыми — в зависимости от бизнес-правил.

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

В этом примере рассматривается только обычная временная шкала с нулевым epoch. Для данных из разных циклов воспроизведения или с другими номерами эпох сначала нужно определить правила отображения на общую шкалу. Нельзя просто взять числитель и знаменатель и заново создать значение времени.

Запустите минимальную проверку на облачном Mac

Подготовьте рабочее окружение командной строки Xcode. Выполните xcrun --find swift и xcrun --sdk macosx --show-sdk-path, чтобы убедиться, что компилятор и SDK доступны. Сохраните следующий код в timeline-check.swift. Он использует только системные фреймворки и не требует загрузки медиафайлов.

import CoreMedia
import Foundation

func check(_ condition: Bool, _ name: String) {
    if !condition {
        fatalError("Check failed: \(name)")
    }
}

let frame = CMTime(value: 1, timescale: 30)
let sameFrame = CMTime(value: 20, timescale: 600)

check(frame.isNumeric, "numeric-input")
check(frame.epoch == 0, "expected-epoch")
check(CMTimeCompare(frame, sameFrame) == 0, "rational-equality")

let sum = CMTimeAdd(frame, frame)
check(
    CMTimeCompare(sum, CMTime(value: 1, timescale: 15)) == 0,
    "exact-addition"
)

let third = CMTime(value: 1, timescale: 3)
let exact = CMTimeConvertScale(
    third,
    timescale: 600,
    method: .roundHalfAwayFromZero
)
check(exact.value == 200, "exact-conversion")
check(!exact.hasBeenRounded, "exact-rounding-flag")

let rounded = CMTimeConvertScale(
    third,
    timescale: 1000,
    method: .roundHalfAwayFromZero
)
check(rounded.value == 333, "rounded-value")
check(rounded.hasBeenRounded, "rounded-flag")

let segment = CMTimeRange(
    start: CMTime(value: 0, timescale: 30),
    duration: CMTime(value: 30, timescale: 30)
)
check(CMTimeRangeContainsTime(segment, time: .zero), "start-included")
check(
    CMTimeRangeContainsTime(
        segment,
        time: CMTime(value: 29, timescale: 30)
    ),
    "last-frame-start-included"
)
check(
    !CMTimeRangeContainsTime(segment, time: CMTimeRangeGetEnd(segment)),
    "end-excluded"
)
check(!CMTime.invalid.isNumeric, "invalid-rejected")
check(!CMTime.indefinite.isNumeric, "indefinite-rejected")
check(!CMTime.positiveInfinity.isNumeric, "infinity-rejected")
xcrun swift timeline-check.swift

Если все проверки пройдены, скрипт завершится без вывода. При нарушении проверяемого условия он вернёт ненулевой код завершения. В конвейере используйте непосредственно этот код, а не наличие определённого слова в журнале. В коде применяются явные проверки вместо assert, который может быть удалён при оптимизированной сборке.

Ограничьте потерю точности границами интерфейсов

Одно и то же числовое значение может иметь разные представления. Поэтому для сравнения времени используйте CMTimeCompare, а не сравнивайте два поля value. Для сложения используйте CMTimeAdd, а не складывайте числители напрямую. Целочисленные операции могут быть эквивалентны только при одинаковом масштабе времени, совпадающей эпохе и явно заданной политике обработки переполнения.

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

hasBeenRounded указывает на наличие следов округления в результате, но не сообщает величину погрешности. Этот флаг также может быть унаследован от предыдущих вычислений. Если нужно контролировать допустимую погрешность, сохраните исходное время, вычислите разницу до и после преобразования и сравните её с допустимым для задачи значением. Не следует считать любой установленный флаг ошибкой, а отсутствие флага — поводом пропустить проверку входных данных.

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

Задайте два соседних фрагмента интервалами [0, 1) и [1, 2). Время, равное ровно одной секунде, должно попадать только во второй фрагмент. Тогда переключение субтитров, выбор фрагментов и привязка при перетаскивании смогут использовать единые правила, не активируя оба фрагмента одновременно.

Для каждого прикладного интервала проверьте как минимум начало, последнюю допустимую точку дискретизации перед концом, сам конец и первую точку дискретизации после него. Шаг между точками должен определяться реальной дорожкой или требуемой в задаче точностью. Нельзя просто вычесть одну миллисекунду и назвать результат «предыдущим кадром». Для материала с переменной частотой кадров тем более нельзя рассчитывать все границы, разделив общую длительность на число кадров.

Отрицательное время само по себе не является недопустимым: оно может обозначать смещение. Разрешать ли такие значения, должен определять интерфейс, а не общее правило замены всех отрицательных значений нулём. Неизвестную длительность также нельзя превращать в ноль секунд и продолжать вычисления: иначе ещё не загруженный материал будет ошибочно признан пустым фрагментом.

Включите проверки в регрессионные тесты, но не заменяйте ими проверку воспроизведения

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

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

В завершение проверьте на коротком материале позицию, запрошенную у плеера, фактически возвращённую позицию и итоговый показанный кадр. Допуск при позиционировании, ключевые кадры и планирование декодирования влияют на то, где действительно окажется воспроизведение. Успешная проверка арифметики не гарантирует точности изображения. Сначала закрепите правила работы со временем, а затем исследуйте поведение плеера — так область поиска станет значительно уже.

Часто задаваемые вопросы

Устранит ли перевод всего времени в миллисекунды ошибки?

Нет. При 30 кадрах в секунду кадр длится 1/30 секунды, что нельзя точно выразить целым числом миллисекунд. Сохраняйте исходный масштаб и проверяйте направление округления при преобразовании.

Нужен ли симулятор iOS для этих проверок?

Вычисления CMTime можно проверить через xcrun swift на Mac с доступным macOS SDK. Поиск позиции в плеере, декодирование кадров и синхронизацию звука с видео проверяют отдельно в приложении.

Подключите выделенный Mac к своему рабочему процессу

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

Арендовать облачный Mac Сравнить конфигурации

Передайте следующую сборку облачному Mac

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