| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Switch
- Machine Learning
- nodejs
- rabbitmq
- deep learning
- React
- docker
- php
- For
- 기초 수학
- mariadb
- python
- Node
- Babel
- webpack
- Backbone.js
- SQL
- linux
- Redis
- fastapi
- nginx
- node.js
- CentOS
- 블레이드 템플릿
- javascript
- NCP
- AWS
- Redux
- Go
- laravel
- Today
- Total
개발일기
Node.js - ffmpeg으로 mp4파일을 hls로 변환하기 본문

1. HLS란?
HLS는 HTTP기반의 적응형 스트리밍 프로토콜이며 미디어 파일을 일정한 세그먼트(segment) 단위로 나눠서 전송한다. 적응형 스트리밍이란 사용자의 네트워크 환경에 맞는 화질을 자동으로 선택하여 전송하는 것을 의미한다. 즉 사용자의 네트워크 속도가 느릴 경우 화질을 낮춰 전송하고 네트워크 속도가 괜찮을 경우 좋은 화질로 미디어 데이터를 전송하는 것을 의미한다.
2. 코덱과 비트레이트
- Video Codec: 영상을 인코딩 하거나 디코딩하는 기술로 영상 데이터를 저장하거나 전송하기에 적합한 형태로 압축을 하고 재생 시 다시 원래 형태로 복원하는 역할을 한다. 비디오 코덱의 종류에는 libx264, libx265, mpeg4 등이 있다.
- Audio Codec: Audio Codec은 Video Codec과 다르게 소리를 인코딩하거나 디코딩한다. 오디오 코덱의 종류에는 MP3, AAC, FLAC 등이 있다.
- Bitrate: 1초당 몇 비트의 데이터를 사용할지를 의미한다. 비트레이트가 높으면 파일 크기가 크며 고화질이다. 반대로 비트레이트가 낮으면 파일 크기가 작으며 저화질이다.
- Constant Bitrate(CBR): 고정 비트레이트로 처음부터 끝까지 고정된 비트레이트를 사용하여 압축을 진행한다. 고정된 비트로 인해 전송이 안정적이지만 화면 전환이 많은 구간에는 충분한 비트가 할당되지 않아 해당 부분 한정으로 품질이 낮아질 수 있다.
- Variable Bitrate(VBR): 가변 비트레이트로 CBR과 다르게 비트레이트가 매번 변하며 이로 인해 CBR의 단점을 보완할 수 있다. 전체적인 품질은 VBR이 더 우수하다.
3. ffmpeg 옵션
const ffmpegOptions = [
'-loglevel', 'info',
'-i', path.join(__dirname, `${mp4DirName}/SampleVideo_1280x720_30mb.mp4`),
'-filter_complex', // 여러 스트림을 동시에 처리하기 위해 사용
'[0:v]split=3[v1][v2][v3];' + // 비디오 스트림을 3개로 분할
'[v1]scale=1280:720[vout1];' + // 첫 번째 분할된 스트림을 1280x720 크기로 지정
'[v2]scale=854:480[vout2];' + // 두 번째 분할된 스트림을 854x480 크기로 지정
'[v3]scale=640:360[vout3]', // 세 번째 분할된 스트림을 640x360 크기로 지정
'-map', '[vout1]', '-map', '0:a', // 매핑
'-map', '[vout2]', '-map', '0:a',
'-map', '[vout3]', '-map', '0:a',
'-c:v', 'libx264', // 영상 코덱
'-g', '24', // GOP 크기 설정
'-keyint_min', '24', // 최소 키프레임 간격
'-b:v:0', '2500k', // 영상 비트레이트
'-b:v:1', '1500k',
'-b:v:2', '800k',
'-c:a', 'aac', // 오디오 코덱
'-b:a', '128k', // 오디오 비트레이트
'-hls_time', '5', // segment 간격
'-hls_list_size', '0', // m3u8 파일에 포함할 ts파일 개수
'-hls_segment_filename', `${targetDirName}/%v/segments_%03d.ts`, // ts 파일 이름 형식
'-master_pl_name', 'master.m3u8', // master 파일 이름
'-var_stream_map', 'v:0,a:0,name:720p v:1,a:1,name:480p v:2,a:2,name:360p', // 스트림 매핑
`${targetDirName}/%v/playlist.m3u8`
]
- -filter_complex: 여러 스트림을 동시에 처리하거나 입력 파일의 미디어 스트림을 여러 개 분할하여 변환할 때 사용한다.
- '[0:v]split=3[v1][v2][v3];’: [0:v]는 입력 파일의 첫 번째 영상 스트림을 의미한다. split으로 입력 파일의 영상을 3개의 스트림으로 분할하여 각각 v1, v2, v3로 이름을 지정한다. 여기서 v는 video를 의미한다.
- [v1]scale=1280:720[vout1]…: 분할된 스트림의 해상도를 지정한다. width, height 순으로 나열하며 720p, 480p, 360p로 지정한다. vout1, vout2, vout3은 filter_complex로 분할되고 해상도가 지정된 최종 영상 스트림의 이름을 의미한다. vout1, vout2, vout3을 -map에 사용하여 매핑을 진행한다.
- -map: ffmpeg에서 어떤 입력 스트림을 출력해 포함시킬지 정의한다. 0:a는 입력 파일의 첫 번째 오디오 스트림을 의미한다. 0:v와 같이 0은 첫 번째 스트림을 의미하며 a는 audio를 의미한다. 각 영상마다 오디오가 필요하므로 ‘-map’: ‘[vout1]’, ‘-map’, ‘0:a’, ‘-map’: ‘[vout2’], ‘-map’], ‘0:a’ … 처럼 3개의 영상 스트림과 오디오 스트림을 출력에 포함되도록 지정한다.
- '-c:v', 'libx264': 비디오의 코덱을 libx264로 설정한다.
- '-b:v:0', '2500k': 첫 번째 영상 스트림의 비트레이트를 2500k로 설정한다. 2번째 세번째 영상 스트림은 해상도에 따라 비트레이트를 조절한다.
- '-c:a', 'aac': 음성 스트림의 코덱을 aac로 설정한다.
- '-b:a', '128k': 음성 스트림의 비트레이트를 128k로 설정한다. 음성 스트림은 하나이기 때문에 a:0, a:1이 아닌 a로 입력한다.
- -g: 키프레임부터 다음 키프레임까지의 최대 프레임 수를 지정한다.
- -keyint_min: 키프레임들 사이의 최소 프레임 수를 지정한다. 보통 keyint_min과 -g는 같은 값으로 지정한다. 단위는 fps로 24면 24프레임을 의미한다.
- '-hls_time', '5': 생성될 hls segment의 길이를 지정한다. 단위는 초로 5로 지정하면 5초를 의미한다. keyint_min과 g값으로 인해 hls_time은 5로 지정했지만 각 segment의 길이가 달라질 수 있다.
- '-hls_list_size', '0’: m3u8플레이 리스트에 포함시킬 hls segment의 개수를 지정한다. 0으로 지정하면 모든 hls segment를 m3u8에 포함하여 저장한다. 5로 지정하면 최근 생성된 5개의 hls segment만 m3u8에 포함하여 저장하게 된다. 0이 아닌 다른 숫자를 지정하는 경우는 보통 라이브 스트리밍을 할 때 다른 숫자로 지정하게 된다.
- '-var_stream_map’: -map에서 매핑되어 출력 스트림에 포함된 스트림들을 그룹화한다. 위 코드를 보면 v:0과 a:0을 720p로 v:1과 a:0을 480p로 그룹 지정하였다. 이렇게 지정된 이름은 %v로 사용할 수 있다.
- '-hls_segment_filename’: 생성되는 hls segment의 파일 명을 지정한다.
- '-master_pl_name’: 마스터 플레이리스트의 파일 명을 지정한다. 지정된 이름으로 m3u8파일이 생성된다.
- ${targetDirName}/%v/playlist.m3u8: 분할되어 생성되는 각 해상도별 플레이리스트 파일 명을 지정한다.
4. 변환 코드
const spawn = require('child_process').spawn;
const path = require('path');
const fs = require('fs');
const mp4DirName = 'original_videos'
const targetDirName = 'streaming_videos/SampleVideo_1280x720_30mb'
if (!fs.existsSync(path.join(__dirname, targetDirName))) {
fs.mkdirSync(path.join(__dirname, targetDirName), { recursive: true });
}
const ffmpegOptions = [
'-loglevel', 'info',
'-i', path.join(__dirname, `${mp4DirName}/SampleVideo_1280x720_30mb.mp4`),
'-filter_complex', // 여러 스트림을 동시에 처리하기 위해 사용
'[0:v]split=3[v1][v2][v3];' + // 비디오 스트림을 3개로 분할
'[v1]scale=1280:720[vout1];' + // 첫 번째 분할된 스트림을 1280x720 크기로 지정
'[v2]scale=854:480[vout2];' + // 두 번째 분할된 스트림을 854x480 크기로 지정
'[v3]scale=640:360[vout3]', // 세 번째 분할된 스트림을 640x360 크기로 지정
'-map', '[vout1]', '-map', '0:a', // 매핑
'-map', '[vout2]', '-map', '0:a',
'-map', '[vout3]', '-map', '0:a',
'-c:v', 'libx264', // 영상 코덱
'-g', '24', // GOP 크기 설정
'-keyint_min', '24', // 최소 키프레임 간격
'-b:v:0', '2500k', // 영상 비트레이트
'-b:v:1', '1500k',
'-b:v:2', '800k',
'-c:a', 'aac', // 오디오 코덱
'-b:a', '128k', // 오디오 비트레이트
'-hls_time', '5', // segment 간격
'-hls_list_size', '0', // m3u8 파일에 포함할 ts파일 개수
'-hls_segment_filename', `${targetDirName}/%v/segments_%03d.ts`, // ts 파일 이름 형식
'-master_pl_name', 'master.m3u8', // master 파일 이름
'-var_stream_map', 'v:0,a:0,name:720p v:1,a:1,name:480p v:2,a:2,name:360p', // 스트림 매핑
`${targetDirName}/%v/playlist.m3u8`
]
const converter = spawn('ffmpeg', ffmpegOptions)
converter.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
})
converter.stderr.on('end', () => {
console.log('End converting video')
})
위의 코드를 실행하면 streaming_videos라는 폴더가 생성되고 그 하위 폴더에 360p, 480p과 720p 해상도별 폴더가 생성된다. 적응형 스트리밍을 위해 master.m3u8은 각 플레이리스트의 정보를 담고 있으며 각각의 폴더는 ts segment과 playlist.m3u8을 생성한다.

참고 사이트:
ffmpeg로 hls 만들기, 옵션정리
ffmpeg 프로그램을 다운받고 영상을 hls 전용 스트리밍 영상 파일로 변환 및 ts (각각의 세그먼트 파일) 들을 추출 한 뒤에 웹에 적용해볼 수 있습니다. ie 11 이상으로 지원하고, 각각 영상 조각 파일
frontdev.tistory.com
https://video.stackexchange.com/questions/37648/ffmpeg-hls-time-and-hls-init-time-not-working
ffmpeg: -hls_time and -hls_init_time not working
Source video: h264 mp4, 45 seconds, 30 fps, keyint 30, min keyint 16 I am trying to create an m3u8 playlist where the first chunk is smaller (2 seconds) and the rest are 10 seconds. when using this
video.stackexchange.com
hls_time: Invalid chars on ffmpeg version 3 · Issue #347 · bramp/ffmpeg-cli-wrapper
hls_time: Invalid chars on ffmpeg version 3 · Issue #347 · bramp/ffmpeg-cli-wrapper
Describe the bug HlsOutputBuilder is incompatible with the hls format used in ffmpeg version 3. FFmpeg switched from second based value (eg. 120) to the format we use (2:00, hh:mm:ss) in version 4....
github.com
https://ffmpeg.org/documentation.html
Documentation
The following documentation is regenerated nightly, and corresponds to the newest FFmpeg revision. Consult your locally installed documentation for older versions. API Documentation Doxygen documentation for current trunk (regenerated nightly); documentati
ffmpeg.org
'Javascript > Node.js' 카테고리의 다른 글
| Node.js - ffprobe로 미디어 파일 분석하기 (0) | 2025.08.06 |
|---|---|
| Node.js - Socket.io의 room관련 메서드 (0) | 2025.05.04 |
| Node.js - Socket.io의 기본 이벤트와 메서드 (0) | 2025.05.03 |
| Node.js - Yaml to Object (0) | 2025.05.02 |
| Node.js - PM2로 프로세스 관리하기 (0) | 2023.04.02 |