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