6.3 구간 복원(Partial Restore) - 필요한 바이트만 읽는 방법
1시간짜리 파일에서 1분만 필요할 때 전체를 복원하지 않고 구간만 읽는 구간 복원의 원리, 키프레임·GOP 경계와 인덱스로 읽을 바이트 범위를 계산하는 실험, 지원 포맷 확인의 중요성을 설명합니다.
#왜 구간 복원이 필요한가
아카이브에서 1시간짜리 영상(원본 XDCAM 50Mbps 기준 약 22GB)이 필요한 구간은 단 1분일 수 있습니다. 전체를 테이프에서 읽어 전송하고 저장하면 시간과 네트워크, 스토리지를 낭비합니다.
| 방식 | 읽는 양 | 시간 |
|---|---|---|
| 전체 복원 | 22GB | 수십 분 |
| 구간 복원 | 약 0.4GB (1분) | 수 분 |
구간 복원(Partial Restore, Partial File Restore, Partial Retrieve 등으로도 부릅니다)은 요청된 구간만 읽어서 새 파일로 만들어 주는 기능입니다. 뉴스 보도처럼 "그때 그 장면 10초"를 빨리 가져와야 하는 현장에서 아카이브 솔루션을 고르는 핵심 기준이 되기도 합니다.
#원리: 시간 → 바이트 위치
구간 복원의 핵심은 "3초부터 5초까지" 같은 시간 구간을 "파일의 몇 번째 바이트부터 몇 번째 바이트까지"로 바꾸는 것입니다. 이를 위해 프레임 위치를 알려 주는 인덱스가 필요합니다.
- MXF: 파일 안에 Index Table이 있습니다(2장). 프레임 번호에 해당하는 바이트 오프셋을 알려 줍니다.
- MP4/MOV:
moov의 샘플 테이블(stts,stsz,stco)이 같은 역할을 합니다. - MPEG-TS: 전체 인덱스가 없어, 별도로 스캔해서 위치 인덱스를 만들어 두어야 합니다.
아카이브할 때 이 위치 정보를 미리 추출해서 DB에 저장해 두면, 복원할 때 테이프를 앞뒤로 감지 않고 필요한 위치로 바로 이동할 수 있습니다.
#키프레임 경계 문제
위치만 알아서는 부족합니다. 2장에서 설명한 것처럼 P/B 프레임은 앞의 I 프레임이 있어야 디코딩됩니다. 그러므로 3초 지점의 프레임을 쓰려면 그 이전의 가장 가까운 I 프레임부터 읽어야 하고, 구간의 끝도 다음 I 프레임 직전까지 읽어야 완결된 GOP가 됩니다.
요청 구간: [3.00 ─────────── 5.00]
GOP 경계: | 2.80 | 3.20 | 3.60 | 4.00 | 4.40 | 4.80 | 5.20 |
실제 읽기: [2.80 ─────────────────────────────── 5.20 직전]
#실험: 읽을 바이트 범위 계산
아래 명령으로 만든 10초짜리 MPEG-2 4:2:2 50Mbps MXF(GOP N=12, B프레임 2장, 29.97fps, 약 62.3MB)에서 FFprobe의 패킷 정보(pos, size, 키프레임 플래그)를 이용해 읽어야 하는 범위를 계산했습니다.
ffmpeg -f lavfi -i testsrc2=size=1920x1080:rate=30000/1001 -t 10 -c:v mpeg2video -pix_fmt yuv422p \
-b:v 50M -minrate 50M -maxrate 50M -bufsize 17M -g 12 -bf 2 -flags +ildct+ilme -top 1 -f mxf xd.mxf
import json
import subprocess
import sys
def packets(path):
cmd = ['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-show_entries',
'packet=pts_time,pos,size,flags', '-of', 'json', path]
out = subprocess.run(cmd, capture_output=True, text=True, check=True).stdout
pk = [p for p in json.loads(out)['packets'] if p.get('pos') not in (None, 'N/A')]
for p in pk:
p['pts'] = float(p['pts_time'])
p['pos'] = int(p['pos'])
p['size'] = int(p['size'])
p['key'] = 'K' in p['flags']
return sorted(pk, key=lambda p: p['pos'])
def byte_range(pk, t_in, t_out):
keys = [i for i, p in enumerate(pk) if p['key']]
start_i = max(i for i in keys if pk[i]['pts'] <= t_in) # t_in 이하의 마지막 키프레임
after = [i for i in keys if pk[i]['pts'] > t_out]
end_i = (after[0] - 1) if after else len(pk) - 1 # t_out 이후 첫 키프레임 직전
start = pk[start_i]['pos']
end = pk[end_i]['pos'] + pk[end_i]['size']
return start, end, pk[start_i]['pts'], pk[end_i]['pts']
실행 결과는 다음과 같습니다.
키프레임 위치(pts): 0.0, 0.4, 0.801, ... 2.803, 3.203, 3.604, ... (12프레임 ≈ 0.4초 간격)
요청 구간 : 3.00s ~ 5.00s (2.00초)
읽기 시작 : pts 2.803s 키프레임부터, 다음 키프레임(5.205s) 직전까지
바이트 범위 : 16,646,144 ~ 31,722,654 (15,076,510 bytes)
비디오 스트림 전체 대비: 24.3%
요청 구간 : 3.50s ~ 3.60s (0.10초)
읽기 시작 : pts 3.203s 키프레임부터, 다음 키프레임(3.604s) 직전까지
바이트 범위 : 19,159,040 ~ 21,671,070 (2,512,030 bytes)
비디오 스트림 전체 대비: 4.0%
해석합니다.
- 2초 구간 요청은 비디오 데이터의 24.3%를 읽습니다. 10초 중 2초면 20%이지만, 앞뒤를 GOP 경계에 맞추느라 실제로는 약 2.4초(2.803~5.205초)를 읽습니다.
- 0.1초(3프레임) 구간을 요청해도 완결된 GOP를 읽어야 하므로 약 0.4초 분량인 2.5MB(4.0%)가 필요했습니다. 요청 길이보다 GOP 단위로 올림됩니다. 요청이 아주 짧아도 최소 1개 GOP분은 읽습니다.
- Open GOP에 주의하세요. 이 파일에서는 각 키프레임 바로 뒤에 오는 B프레임 2장의 표시 시각(pts)이 키프레임보다 앞섭니다(0.4초 키프레임 뒤에 pts 0.33, 0.37인 프레임). 이 프레임들은 앞 GOP의 마지막 P프레임을 참조하므로, 키프레임부터만 잘라 내면 앞 2프레임을 정상적으로 디코딩할 수 없습니다. 구간 복원 기능은 이런 프레임을 버리거나 앞 GOP 일부를 함께 읽어야 합니다.
- 이 계산에서 오디오와 부가 데이터는 제외했습니다. 실제 복원은 영상, 오디오, 타임코드를 모두 포함한 새 파일을 만들어야 합니다(MXF라면 헤더 메타데이터와 인덱스도 새로 생성).
#지원 포맷 확인이 중요한 이유
구간 복원을 처음 도입하는 프로젝트에서 흔한 실수는 "구간 추출 지원"이라는 말만 믿는 것입니다. 실제로는 다음 중 일부만 지원하는 경우가 많습니다.
| 확인 항목 | 질문 |
|---|---|
| 컨테이너 | MXF OP1a? OP-Atom? MOV? MP4? |
| 코덱·프로파일 | XDCAM HD(35/50Mbps), D-10(IMX), DNxHD, ProRes, AVC-Intra, H.264 ... |
| 해상도·프레임레이트 | HD만? UHD도? 인터레이스? |
| 오디오 구성 | 모노 N트랙? 몇 채널까지? |
| 결과 파일 | 원본과 같은 포맷으로 생성? 타임코드는 유지? |
| 프레임 정확도 | GOP 경계 단위? 프레임 단위(재인코딩 포함)? |
RFP(제안요청서)에는 필요한 코덱·컨테이너·프로파일 이름을 구체적으로 적고, 도입 전에 실제 소재 샘플로 구간 복원 테스트를 해야 합니다. 새로운 코덱 규격이 방송 현장에 도입된 시점에는 해당 제품이 아직 지원하지 않을 수 있습니다.
#구간 복원 설계 체크리스트
- 아카이브 시 인덱스 추출: 프레임 위치 정보를 추출해 DB 또는 별도 파일로 보관한다.
- 요청 단위: 타임코드 in/out을 받아 프록시와 같은 타임코드 체계로 변환한다(1장, 2장).
- GOP 정책: 정확한 프레임 컷이 필요하면 경계 GOP를 재인코딩할지(스마트 렌더링) 결정한다.
- 결과 검증: 복원된 파일이 의도한 구간인지(시작 타임코드, 길이) 분석 도구로 확인한다(3장).
- 여유(핸들): 편집을 위해 구간 앞뒤로 몇 초의 여유를 포함하는 옵션을 제공한다.
#정리
- 구간 복원은 시간 구간을 바이트 범위로 바꾸어 필요한 만큼만 읽는 기능이며, 대용량 아카이브에서 시간·비용을 크게 줄입니다.
- 키프레임/GOP 경계 때문에 요청보다 조금 더 읽고(실험에서 0.1초 요청에 약 2.5MB, 4.0%), 읽은 데이터를 유효한 파일로 재구성하는 로직은 포맷마다 별도 구현이 필요합니다.
- 제품 선택과 RFP에서는 지원 코덱·컨테이너를 명시하고 실제 샘플로 테스트하세요.