클라우드 Mac에서 검증하는 iOS CMTime 정밀도와 구간 경계

클라우드 Mac에서 검증하는 iOS CMTime 정밀도와 구간 경계

클라우드 Mac에서 검증하는 iOS CMTime 정밀도와 구간 경계

클립이 바뀌는 지점에서 자막이 순간적으로 깜빡이거나, 잘라 낸 영상에 한 프레임이 더 들어간다면 플레이어부터 수정하지 마세요. 문제가 발생한 시점을 클라우드 Mac으로 가져와 독립 Swift 스크립트로 계산을 검사한 뒤, 앱으로 돌아가 디코딩 결과를 확인하세요. 이렇게 하면 ‘시간 계산 오류’와 ‘재생 위치가 정확히 맞지 않는 문제’를 구분할 수 있고, 전체 미디어를 반복해서 업로드할 필요도 없습니다.

초 단위로 바꾸기 전에 시간 처리 규칙부터 정하기

CMTime은 정수 분자 value와 시간 척도 timescale로 시간을 표현하며, 유효성, 반올림 여부, epoch 정보도 함께 담습니다. 1/30초는 정확히 표현할 수 있지만, 정수 밀리초로 변환하면 더 이상 정확하지 않습니다. seconds를 소수점 셋째 자리까지 표시하는 것은 화면 표시 방식일 뿐, 두 시간이 같은지 판단하는 기준이 될 수 없습니다.

먼저 저장소에 네 가지 규칙을 명시하세요. 입력은 수치형 시간이어야 하고, 같은 타임라인에서 계산에 사용하는 시간은 epoch가 같아야 합니다. 클립은 시작 시점과 길이로 표현하며, 종료 시점은 해당 클립에 포함하지 않습니다. 길이는 음수일 수 없습니다. 길이가 0인 클립은 비즈니스 규칙에 따라 거부하거나 빈 클립으로 취급합니다.

시간 척도는 프레임 레이트가 아닙니다. 영상 프레임 레이트, 오디오 샘플 레이트, 비즈니스 인터페이스의 시간 단위를 모두 정수로 표현한다는 이유만으로 섞어 써서는 안 됩니다.

이 예제에서는 epoch가 0인 일반적인 타임라인만 다룹니다. 재생 회차를 넘나들거나 다른 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

모든 검사를 통과하면 스크립트는 아무 출력 없이 종료되고, 검사에 실패하면 0이 아닌 종료 상태를 반환합니다. 파이프라인에서는 로그에 특정 단어가 있는지로 성공 여부를 판단하지 말고, 이 종료 상태를 그대로 사용하세요. 코드에서도 최적화 빌드에서 제거될 수 있는 assert 대신 명시적인 검사를 사용합니다.

정밀도 손실은 인터페이스 경계에 한정하기

같은 수치도 여러 형태로 표현할 수 있으므로, 시간을 비교할 때는 두 value를 비교하지 말고 CMTimeCompare를 사용하세요. 더할 때도 분자를 직접 더하지 말고 CMTimeAdd를 사용합니다. 시간 척도와 epoch가 같고 오버플로 처리 정책이 명확할 때만 정수 연산이 동등한 결과를 낼 수 있습니다.

시간 척도를 변환할 때는 반올림 방식을 명시해야 합니다. 잘라 낼 구간의 시작점을 더 이른 시점으로 옮길지, 더 늦은 시점으로 옮길지에 따라 경계 프레임의 보존 여부가 달라집니다. 제품이 결정해야 할 동작을 기본값에 맡기지 마세요. 스크립트의 3분의 1초 변환은 검증용 사례일 뿐, 모든 미디어의 시간 단위를 밀리초로 통일하라는 권장이 아닙니다.

hasBeenRounded는 결과에 반올림 흔적이 있다는 뜻이지, 오차의 크기를 알려 주는 값은 아닙니다. 앞선 연산에서 이 플래그를 물려받았을 수도 있습니다. 오차 한도를 관리해야 한다면 원래 시간을 보존하고, 변환 전후의 차이를 계산해 비즈니스상 허용되는 값과 비교하세요. 플래그가 있다고 무조건 실패로 처리해서도 안 되고, 플래그가 없다고 입력 검사를 생략해서도 안 됩니다.

경계 사례로 구간 판정 확인하기

서로 인접한 두 클립을 각각 [0, 1)과 [1, 2)로 구성하세요. 시간이 정확히 1초라면 두 번째 구간에만 속해야 합니다. 이 규칙을 자막 전환, 클립 선택, 드래그 스냅에 공통으로 적용하면 두 구간이 동시에 활성화되는 일을 막을 수 있습니다.

각 비즈니스 구간에서는 최소한 시작점, 종료점 직전의 유효한 샘플 시점, 종료점, 종료점 바로 다음 샘플 시점을 검사하세요. 샘플 간격은 실제 트랙이나 비즈니스에서 요구하는 정밀도를 기준으로 정해야 합니다. 무조건 1밀리초를 빼고 이를 ‘이전 프레임’으로 간주해서는 안 됩니다. 특히 가변 프레임 레이트 미디어는 전체 길이를 프레임 수로 나눠 모든 경계를 추정할 수 없습니다.

음수 시간이라고 해서 반드시 무효한 것은 아닙니다. 오프셋을 나타낼 수도 있기 때문입니다. 일괄적으로 0으로 잘라 내는 대신, 허용 여부를 인터페이스에서 결정해야 합니다. 길이를 알 수 없는 경우에도 0초로 바꿔 계산을 계속해서는 안 됩니다. 아직 로드되지 않은 미디어가 빈 클립으로 잘못 판정될 수 있습니다.

회귀 테스트에 통합하되 재생 검증은 별도로 수행하기

스크립트를 저장소의 도구 디렉터리에 넣고, 실행 시점의 시스템 버전과 xcodebuild -version 출력을 기록해 실패가 발생한 환경을 추적할 수 있게 하세요. 그런 다음 같은 사례를 프로젝트 테스트로 옮겨, 프로덕션 코드의 시간 변환 함수와 구간 판정 함수를 직접 호출합니다. 프레임워크 함수만 검사하면 비즈니스 계층에서 시간을 다시 부동소수점으로 바꾸는 문제를 발견할 수 없습니다.

커밋 전에 다음을 확인하세요. 수치형 시간과 epoch를 검증했는지, 음수 길이가 없는지, 시간 척도 변환이 명확한 진입점에 모여 있는지, 반올림 방향을 테스트했는지, 인접 구간에 중복으로 속하는 시점이 없는지 확인합니다. 무효 시간, 무한 시간, 미정 시간은 오류 처리 분기로 보내야 합니다.

마지막으로 짧은 미디어를 사용해 플레이어에 요청한 위치, 실제로 반환된 위치, 최종 표시 프레임을 확인하세요. 탐색 허용 오차, 키프레임, 디코딩 스케줄링 모두 도달 위치에 영향을 주므로 계산 검사를 통과했다고 화면까지 정확한 것은 아닙니다. 먼저 시간 처리 규칙을 안정시킨 다음 재생 동작을 조사하면 문제를 추적할 범위를 훨씬 좁힐 수 있습니다.

자주 묻는 질문

모든 시간을 밀리초로 바꾸면 오차가 없어지나요?

아닙니다. 초당 30프레임의 한 프레임은 1/30초이므로 정수 밀리초로 정확히 표현할 수 없습니다. 원본 시간 척도를 유지하고 변환 지점에서 반올림 방향과 허용 오차를 검사하세요.

iOS 시뮬레이터를 실행해야 하나요?

CMTime 연산과 구간 검사는 macOS SDK가 준비된 Mac에서 xcrun swift로 실행할 수 있습니다. 실제 탐색 위치, 디코딩 프레임과 음영상 동기화는 별도 앱 테스트가 필요합니다.

나만의 Mac으로 워크플로를 확장하세요

기본 macOS 환경에서 빌드, 테스트, 실험을 진행하세요. 사양과 이용 기간을 비교한 뒤 적합한 물리 노드를 선택할 수 있습니다.

클라우드 Mac 대여하기 Mac 요금제 보기

다음 빌드는 클라우드 Mac에서

가상 머신이 아닌 전용 물리 머신입니다. 프로젝트 일정에 맞춰 사양과 이용 기간을 선택하세요.