]>
Commit | Line | Data |
---|---|---|
c729caf6 C |
1 | import { FfmpegCommand, FilterSpecification } from 'fluent-ffmpeg' |
2 | import { join } from 'path' | |
3 | import { VIDEO_LIVE } from '@server/initializers/constants' | |
f443a746 | 4 | import { AvailableEncoders, LiveVideoLatencyMode } from '@shared/models' |
c729caf6 C |
5 | import { logger, loggerTagsFactory } from '../logger' |
6 | import { buildStreamSuffix, getFFmpeg, getScaleFilter, StreamType } from './ffmpeg-commons' | |
7 | import { getEncoderBuilderResult } from './ffmpeg-encoders' | |
8 | import { addDefaultEncoderGlobalParams, addDefaultEncoderParams, applyEncoderOptions } from './ffmpeg-presets' | |
9 | import { computeFPS } from './ffprobe-utils' | |
10 | ||
11 | const lTags = loggerTagsFactory('ffmpeg') | |
12 | ||
13 | async function getLiveTranscodingCommand (options: { | |
14 | inputUrl: string | |
15 | ||
16 | outPath: string | |
17 | masterPlaylistName: string | |
f443a746 | 18 | latencyMode: LiveVideoLatencyMode |
c729caf6 C |
19 | |
20 | resolutions: number[] | |
21 | ||
22 | // Input information | |
23 | fps: number | |
24 | bitrate: number | |
25 | ratio: number | |
26 | ||
27 | availableEncoders: AvailableEncoders | |
28 | profile: string | |
29 | }) { | |
f443a746 | 30 | const { inputUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio, latencyMode } = options |
c729caf6 C |
31 | |
32 | const command = getFFmpeg(inputUrl, 'live') | |
33 | ||
34 | const varStreamMap: string[] = [] | |
35 | ||
36 | const complexFilter: FilterSpecification[] = [ | |
37 | { | |
38 | inputs: '[v:0]', | |
39 | filter: 'split', | |
40 | options: resolutions.length, | |
41 | outputs: resolutions.map(r => `vtemp${r}`) | |
42 | } | |
43 | ] | |
44 | ||
45 | command.outputOption('-sc_threshold 0') | |
46 | ||
47 | addDefaultEncoderGlobalParams(command) | |
48 | ||
49 | for (let i = 0; i < resolutions.length; i++) { | |
50 | const resolution = resolutions[i] | |
51 | const resolutionFPS = computeFPS(fps, resolution) | |
52 | ||
53 | const baseEncoderBuilderParams = { | |
54 | input: inputUrl, | |
55 | ||
56 | availableEncoders, | |
57 | profile, | |
58 | ||
59 | canCopyAudio: true, | |
60 | canCopyVideo: true, | |
61 | ||
62 | inputBitrate: bitrate, | |
63 | inputRatio: ratio, | |
64 | ||
65 | resolution, | |
66 | fps: resolutionFPS, | |
67 | ||
68 | streamNum: i, | |
69 | videoType: 'live' as 'live' | |
70 | } | |
71 | ||
72 | { | |
73 | const streamType: StreamType = 'video' | |
74 | const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType }) | |
75 | if (!builderResult) { | |
76 | throw new Error('No available live video encoder found') | |
77 | } | |
78 | ||
79 | command.outputOption(`-map [vout${resolution}]`) | |
80 | ||
81 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) | |
82 | ||
83 | logger.debug( | |
84 | 'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile, | |
85 | { builderResult, fps: resolutionFPS, resolution, ...lTags() } | |
86 | ) | |
87 | ||
88 | command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) | |
89 | applyEncoderOptions(command, builderResult.result) | |
90 | ||
91 | complexFilter.push({ | |
92 | inputs: `vtemp${resolution}`, | |
93 | filter: getScaleFilter(builderResult.result), | |
94 | options: `w=-2:h=${resolution}`, | |
95 | outputs: `vout${resolution}` | |
96 | }) | |
97 | } | |
98 | ||
99 | { | |
100 | const streamType: StreamType = 'audio' | |
101 | const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType }) | |
102 | if (!builderResult) { | |
103 | throw new Error('No available live audio encoder found') | |
104 | } | |
105 | ||
106 | command.outputOption('-map a:0') | |
107 | ||
108 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) | |
109 | ||
110 | logger.debug( | |
111 | 'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile, | |
112 | { builderResult, fps: resolutionFPS, resolution, ...lTags() } | |
113 | ) | |
114 | ||
115 | command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`) | |
116 | applyEncoderOptions(command, builderResult.result) | |
117 | } | |
118 | ||
119 | varStreamMap.push(`v:${i},a:${i}`) | |
120 | } | |
121 | ||
122 | command.complexFilter(complexFilter) | |
123 | ||
f443a746 | 124 | addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode }) |
c729caf6 C |
125 | |
126 | command.outputOption('-var_stream_map', varStreamMap.join(' ')) | |
127 | ||
128 | return command | |
129 | } | |
130 | ||
f443a746 C |
131 | function getLiveMuxingCommand (options: { |
132 | inputUrl: string | |
133 | outPath: string | |
134 | masterPlaylistName: string | |
135 | latencyMode: LiveVideoLatencyMode | |
136 | }) { | |
137 | const { inputUrl, outPath, masterPlaylistName, latencyMode } = options | |
138 | ||
c729caf6 C |
139 | const command = getFFmpeg(inputUrl, 'live') |
140 | ||
141 | command.outputOption('-c:v copy') | |
142 | command.outputOption('-c:a copy') | |
143 | command.outputOption('-map 0:a?') | |
144 | command.outputOption('-map 0:v?') | |
145 | ||
f443a746 | 146 | addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode }) |
c729caf6 C |
147 | |
148 | return command | |
149 | } | |
150 | ||
f443a746 C |
151 | function getLiveSegmentTime (latencyMode: LiveVideoLatencyMode) { |
152 | if (latencyMode === LiveVideoLatencyMode.SMALL_LATENCY) { | |
153 | return VIDEO_LIVE.SEGMENT_TIME_SECONDS.SMALL_LATENCY | |
154 | } | |
155 | ||
156 | return VIDEO_LIVE.SEGMENT_TIME_SECONDS.DEFAULT_LATENCY | |
157 | } | |
158 | ||
c729caf6 C |
159 | // --------------------------------------------------------------------------- |
160 | ||
161 | export { | |
f443a746 C |
162 | getLiveSegmentTime, |
163 | ||
c729caf6 C |
164 | getLiveTranscodingCommand, |
165 | getLiveMuxingCommand | |
166 | } | |
167 | ||
168 | // --------------------------------------------------------------------------- | |
169 | ||
f443a746 C |
170 | function addDefaultLiveHLSParams (options: { |
171 | command: FfmpegCommand | |
172 | outPath: string | |
173 | masterPlaylistName: string | |
174 | latencyMode: LiveVideoLatencyMode | |
175 | }) { | |
176 | const { command, outPath, masterPlaylistName, latencyMode } = options | |
177 | ||
178 | command.outputOption('-hls_time ' + getLiveSegmentTime(latencyMode)) | |
c729caf6 C |
179 | command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE) |
180 | command.outputOption('-hls_flags delete_segments+independent_segments') | |
181 | command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`) | |
182 | command.outputOption('-master_pl_name ' + masterPlaylistName) | |
183 | command.outputOption(`-f hls`) | |
184 | ||
185 | command.output(join(outPath, '%v.m3u8')) | |
186 | } |