안녕하세요. 볼드나인 Backend 개발자 최송이입니다.
최근 저는 이지스토리지 시스템 내에서 영상을 녹화하고, 녹화된 영상을 조회할 수 있는 기능을 개발하고 있습니다.
이 과정에서 네트워크 부하를 최소화하기 위해 영상을 여러 청크로 분할하여 서버에 순차적으로 전송한 후, 모든 청크가 도착하면 이를 병합하여 최종 파일로 저장하는 방식을 채택했습니다.
1.
클라이언트에서 영상을 여러 개의 청크로 분할해서 순차적으로 서버에 전송
2.
서버에선 청크를 받아서 임시 저장 후
3.
모든 청크가 전송되면 클라이언트에서 서버에 병합 요청
4.
서버에선 저장된 청크 순서대로 병합
5.
최종 병합된 파일 업로드
동영상 컨테이너(파일 형식)으로는 Chrome 브라우저에서 MediaRecorder API를 사용해 영상을 녹화하면 기본적으로 저장되는 파일 형식이고, 웹 환경에 최적화되기도 한 WebM(.webm)을 선택했습니다.
물론 MediaRecorder의 mimeType 옵션을 통해 다른 포맷도 시도해볼 수 있지만 MP4(.mp4)와 같은 일반적인 형식도 브라우저에 따라 지원되지 않는 경우가 많습니다.
이번 글에서는 영상을 다룰 때 많이 사용되는 FFmpeg로 영상 처리 작업을 했던 경험을 공유하려고 합니다.
코덱이란?
먼저 코덱에 대해 알아보겠습니다.
코덱(Codec)은 "코더(coder) + 디코더(decoder)"의 합성어로, 비디오 및 오디오 데이터를 압축하고 해독하는 알고리즘입니다. WebM 파일에서 지원하는 주요 코덱은 다음과 같습니다:
•
VP8, VP9: Google이 개발한 오픈소스 비디오 코덱
•
AV1: 차세대 오픈소스 비디오 코덱(최근 WebM에서 지원)
•
H.264: WebM에서는 공식 지원하지 않음(하지만 일부 변환 과정에서 포함될 수 있음)
•
Opus, Vorbis: WebM에서 사용되는 오디오 코덱
FFmpeg란?
FFmpeg은 다양한 멀티미디어 포맷을 다룰 때, 빠지지 않는 오픈소스 툴입니다. 비디오 및 오디오 변환, 스트리밍, 분할, 편집, 병합 등 다양한 기능을 제공하며, WebM과 같은 포맷을 다룰 때도 필수적인 도구입니다.
•
비디오/오디오 변환: 다양한 포맷 간 변환 가능
•
코덱 조작: 특정 코덱을 유지하거나 변경 가능
•
파일 병합 및 분할: 여러 개의 파일을 하나로 병합하거나 분할 가능
아래 세부 라이브러리를 활용할 수도 있습니다.
•
libavutil: FFmpeg 개발 시 필요한 다양한 유틸리티
•
libavcodec: 오디오/비디오의 인코더/디코더
•
libavformat: 오디오/비디어 컨테이너 포맷의 muxer/demuxer
•
libavdevice: 다양한 입/출력 장치(Device)와의 인터페이스를 제공
•
libavfilter: 인코더와 디코더 사이에서 오디오/비디오를 변경하고 검사
•
libswscale: 비디오의 image scaling, color-space, pixel-format 변환
•
libswresample: 오디오 리샘플링(audio resampling)
처음에는 스트리밍 방식으로 병합을 시도했지만, 영상 병합시 예상치 못한 문제(코덱 불일치, 재생 오류 등)가 발생하여 FFmpeg를 사용하게 되었습니다.
FFmpeg는 어떻게 동작할까?
간단히 말하자면 (1)Demuliplexer - 동영상 파일을 해체하는 과정, (2)Decoder - 압축을 푸는 과정, (3)Filter - 편집하는 과정, (4)Encoder - 다시 압축하는 과정, (5)Multiplexer - 해체했던 파일을 다시 원래대로 되돌리는 과정으로 진행됩니다.
(1) Demultiplexer - 컨테이너 해체
: 멀티미디어 파일에서 오디오/비디오 스트림을 분리하는 단계
•
컨테이너 포맷(WebM 등) 내부에는 비디오 스트림과 오디오 스트림이 포함되어 있습니다.
•
FFmpeg는 먼저 컨테이너를 해체(Demuxing) 하여 각각의 스트림을 추출합니다. (참고로 스트림은 압축된 상태 (Encoded Data) 유지)
(2) Decoder - 원본 데이터 복원
: 압축된 데이터를 원래의 비디오/오디오 프레임으로 변환하는 단계
•
대부분의 미디어 파일은 압축된 상태(Encoded Data) 로 저장되기 때문에 FFmpeg는 비디오/오디오 코덱을 이용해 압축을 해제(Decoding) 하여 원본 프레임(Decoded Frames)으로 복원합니다. (원본 코덱이 손실되거나, 변환이 필요할 수도 있음)
(3) Filter - 영상 및 오디오 처리
: 디코딩된 원본 프레임을 변환 및 편집하는 단계
•
디코딩된 데이터에서 필요하다면 영상 보정, 해상도 변경, 자막 추가, 오디오 조정 등의 필터 작업을 수행할 수 있습니다.
(4) Encoder - 다시 압축
: 필터링된 프레임을 다시 인코딩하여 압축하는 단계
•
원본 비디오 프레임을 특정 코덱을 사용하여 압축(Encoding) 합니다.
•
인코딩 과정에서 원본과 다른 코덱을 선택할 수 있으며 CPU를 많이 사용하는 작업입니다.
(5) Multiplexer - 최종 파일 생성
: 인코딩된 비디오와 오디오를 하나의 파일로 합치는 단계
•
비디오/오디오 스트림을 조합하여 새로운 컨테이너 파일을 생성하는 과정입니다.
•
컨테이너 포맷을 선택하여 저장할 수 있습니다.
FFmpeg로 영상 분할, 병합하기
영상 파일을 일정한 길이(예: 10초)로 분할하려면 -segment_time 옵션을 사용합니다.
ffmpeg -i input.webm -c copy -map 0 -segment_time 10 -f segment -reset_timestamps 1 output_%03d.webm
Shell
복사
•
i input.webm → 입력 파일 지정
•
c copy → 재인코딩 없이 원본 코덱 유지 (VP8/VP9 + Opus/Vorbis), 속도 빠름
•
map 0 → 모든 스트림(비디오, 오디오 등) 유지
•
segment_time 10 → 10초 단위로 분할
•
f segment → FFmpeg의 segment 포맷 사용
•
reset_timestamps 1 → 각 조각의 타임스탬프를 0초부터 다시 설정
•
output_%03d.webm → 분할된 파일이 output_000.webm, output_001.webm... 형식으로 저장됨
FFmpeg에서는 여러 개의 영상 파일을 하나의 영상으로 병합할 수 있습니다.
•
모든 파일이 같은 코덱과 포맷을 사용한다면, 인코딩 없이 바로 병합할 수 있습니다!
1.
파일 목록 생성: file_list.txt라는 파일을 만들고, 병합할 파일을 나열합니다.
file 'output_000.webm'
file 'output_001.webm'
file 'output_002.webm'
Plain Text
복사
2.
FFmpeg 명령어 실행
ffmpeg -f concat -safe 0 -i file_list.txt -c copy merged.webm
Shell
복사
•
f concat → FFmpeg의 concat 포맷 사용
•
safe 0 → 안전 모드 비활성화 (경로 문제 방지)
•
i file_list.txt → 병합할 파일 목록 입력
•
c copy → 인코딩 없이 원본 그대로 복사 (빠름)
•
merged.webm → 병합된 영상 파일
모든 파일이 같은 코덱이 아닐 때 사용하는 방법으로, 다시 인코딩해야 해서 처리 속도가 느려질 수 있습니다.
ffmpeg -i "concat:output_000.webm|output_001.webm|output_002.webm" -c:v libvpx-vp9 -b:v 1M -c:a libopus merged.webm
Shell
복사
•
"concat:output_000.webm|output_001.webm|output_002.webm" → 병합할 파일을 |로 연결
•
c:v libvpx-vp9 → 비디오 코덱을 VP9으로 변환
•
b:v 1M → 비디오 비트레이트 설정 (1Mbps)
•
c:a libopus → 오디오 코덱을 Opus로 변환
•
merged.webm → 병합된 WebM 파일 생성
추가적으로, 만약 Node.js 에서 fluent-ffmpeg 라이브러리를 사용하여 가독성을 향상시킬 수도 있습니다.
const fs = require('fs');
const ffmpeg = require('fluent-ffmpeg');
// 병합할 파일 목록 생성
const fileList = ['output_000.webm', 'output_001.webm', 'output_002.webm'];
fs.writeFileSync('file_list.txt', fileList.map(f => `file '${f}'`).join('\n'));
// FFmpeg 실행
ffmpeg()
.input('file_list.txt')
.inputOptions('-f concat', '-safe 0')
.outputOptions('-c copy') // 코덱을 유지하므로 불필요한 인코딩 방지
.output('merged.webm')
.on('end', () => console.log('WebM 영상 병합 완료!'))
.run();
JavaScript
복사
지금까지 FFmpeg로 영상을 분할하고 병합하는 작업에 대해 알아보았습니다.
제가 소개해 드린 것보다 FFmpeg로 할 수 있는 작업이 훨씬 많아서, 혹시 궁금하시다면 직접 시도해 보시는 것도 추천해 드립니다!
긴 글 읽어주셔서 감사합니다. 