aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-08-06 10:39:40 +0200
committerChocobozzz <me@florianbigard.com>2021-08-06 10:39:40 +0200
commitc826f34a45757b324a20f71665b44ed10e6953b5 (patch)
treeff29bdff8b4519bbdbbcd3aa0d68521ce2b06ff5
parent421ff4618da64f0849353383f690a014024c40da (diff)
downloadPeerTube-c826f34a45757b324a20f71665b44ed10e6953b5.tar.gz
PeerTube-c826f34a45757b324a20f71665b44ed10e6953b5.tar.zst
PeerTube-c826f34a45757b324a20f71665b44ed10e6953b5.zip
Limit live bitrate
-rw-r--r--server/helpers/ffmpeg-utils.ts21
-rw-r--r--server/helpers/ffprobe-utils.ts13
-rw-r--r--server/lib/live/live-manager.ts28
-rw-r--r--server/lib/live/shared/muxing-session.ts5
-rw-r--r--server/lib/transcoding/video-transcoding-profiles.ts33
-rw-r--r--server/tests/api/check-params/live.ts4
-rw-r--r--server/tests/api/live/live.ts40
-rw-r--r--shared/extra-utils/videos/live-command.ts5
-rw-r--r--shared/extra-utils/videos/live.ts23
-rw-r--r--shared/models/videos/video-transcoding.model.ts1
10 files changed, 122 insertions, 51 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 61c8a6db2..7f84a049f 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -6,7 +6,7 @@ import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants'
6import { AvailableEncoders, EncoderOptions, EncoderOptionsBuilder, EncoderProfile, VideoResolution } from '../../shared/models/videos' 6import { AvailableEncoders, EncoderOptions, EncoderOptionsBuilder, EncoderProfile, VideoResolution } from '../../shared/models/videos'
7import { CONFIG } from '../initializers/config' 7import { CONFIG } from '../initializers/config'
8import { execPromise, promisify0 } from './core-utils' 8import { execPromise, promisify0 } from './core-utils'
9import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils' 9import { computeFPS, ffprobePromise, getAudioStream, getVideoFileBitrate, getVideoFileFPS } from './ffprobe-utils'
10import { processImage } from './image-utils' 10import { processImage } from './image-utils'
11import { logger } from './logger' 11import { logger } from './logger'
12 12
@@ -218,11 +218,12 @@ async function getLiveTranscodingCommand (options: {
218 218
219 resolutions: number[] 219 resolutions: number[]
220 fps: number 220 fps: number
221 bitrate: number
221 222
222 availableEncoders: AvailableEncoders 223 availableEncoders: AvailableEncoders
223 profile: string 224 profile: string
224}) { 225}) {
225 const { rtmpUrl, outPath, resolutions, fps, availableEncoders, profile, masterPlaylistName } = options 226 const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName } = options
226 const input = rtmpUrl 227 const input = rtmpUrl
227 228
228 const command = getFFmpeg(input, 'live') 229 const command = getFFmpeg(input, 'live')
@@ -253,6 +254,7 @@ async function getLiveTranscodingCommand (options: {
253 profile, 254 profile,
254 255
255 fps: resolutionFPS, 256 fps: resolutionFPS,
257 inputBitrate: bitrate,
256 resolution, 258 resolution,
257 streamNum: i, 259 streamNum: i,
258 videoType: 'live' as 'live' 260 videoType: 'live' as 'live'
@@ -260,7 +262,7 @@ async function getLiveTranscodingCommand (options: {
260 262
261 { 263 {
262 const streamType: StreamType = 'video' 264 const streamType: StreamType = 'video'
263 const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType })) 265 const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
264 if (!builderResult) { 266 if (!builderResult) {
265 throw new Error('No available live video encoder found') 267 throw new Error('No available live video encoder found')
266 } 268 }
@@ -284,7 +286,7 @@ async function getLiveTranscodingCommand (options: {
284 286
285 { 287 {
286 const streamType: StreamType = 'audio' 288 const streamType: StreamType = 'audio'
287 const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType })) 289 const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
288 if (!builderResult) { 290 if (!builderResult) {
289 throw new Error('No available live audio encoder found') 291 throw new Error('No available live audio encoder found')
290 } 292 }
@@ -510,10 +512,11 @@ async function getEncoderBuilderResult (options: {
510 videoType: 'vod' | 'live' 512 videoType: 'vod' | 'live'
511 513
512 resolution: number 514 resolution: number
515 inputBitrate: number
513 fps?: number 516 fps?: number
514 streamNum?: number 517 streamNum?: number
515}) { 518}) {
516 const { availableEncoders, input, profile, resolution, streamType, fps, streamNum, videoType } = options 519 const { availableEncoders, input, profile, resolution, streamType, fps, inputBitrate, streamNum, videoType } = options
517 520
518 const encodersToTry = availableEncoders.encodersToTry[videoType][streamType] 521 const encodersToTry = availableEncoders.encodersToTry[videoType][streamType]
519 const encoders = availableEncoders.available[videoType] 522 const encoders = availableEncoders.available[videoType]
@@ -543,7 +546,7 @@ async function getEncoderBuilderResult (options: {
543 } 546 }
544 } 547 }
545 548
546 const result = await builder({ input, resolution, fps, streamNum }) 549 const result = await builder({ input, resolution, inputBitrate, fps, streamNum })
547 550
548 return { 551 return {
549 result, 552 result,
@@ -573,8 +576,11 @@ async function presetVideo (options: {
573 576
574 addDefaultEncoderGlobalParams({ command }) 577 addDefaultEncoderGlobalParams({ command })
575 578
579 const probe = await ffprobePromise(input)
580
576 // Audio encoder 581 // Audio encoder
577 const parsedAudio = await getAudioStream(input) 582 const parsedAudio = await getAudioStream(input, probe)
583 const bitrate = await getVideoFileBitrate(input, probe)
578 584
579 let streamsToProcess: StreamType[] = [ 'audio', 'video' ] 585 let streamsToProcess: StreamType[] = [ 'audio', 'video' ]
580 586
@@ -593,6 +599,7 @@ async function presetVideo (options: {
593 availableEncoders, 599 availableEncoders,
594 profile, 600 profile,
595 fps, 601 fps,
602 inputBitrate: bitrate,
596 videoType: 'vod' as 'vod' 603 videoType: 'vod' as 'vod'
597 }) 604 })
598 605
diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts
index ef2aa3f89..bc87e49b1 100644
--- a/server/helpers/ffprobe-utils.ts
+++ b/server/helpers/ffprobe-utils.ts
@@ -175,10 +175,19 @@ async function getMetadataFromFile (path: string, existingProbe?: ffmpeg.Ffprobe
175 return new VideoFileMetadata(metadata) 175 return new VideoFileMetadata(metadata)
176} 176}
177 177
178async function getVideoFileBitrate (path: string, existingProbe?: ffmpeg.FfprobeData) { 178async function getVideoFileBitrate (path: string, existingProbe?: ffmpeg.FfprobeData): Promise<number> {
179 const metadata = await getMetadataFromFile(path, existingProbe) 179 const metadata = await getMetadataFromFile(path, existingProbe)
180 180
181 return metadata.format.bit_rate as number 181 let bitrate = metadata.format.bit_rate as number
182 if (bitrate && !isNaN(bitrate)) return bitrate
183
184 const videoStream = await getVideoStreamFromFile(path, existingProbe)
185 if (!videoStream) return undefined
186
187 bitrate = videoStream?.bit_rate
188 if (bitrate && !isNaN(bitrate)) return bitrate
189
190 return undefined
182} 191}
183 192
184async function getDurationFromVideoFile (path: string, existingProbe?: ffmpeg.FfprobeData) { 193async function getDurationFromVideoFile (path: string, existingProbe?: ffmpeg.FfprobeData) {
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts
index f106d69fb..b19ecef6f 100644
--- a/server/lib/live/live-manager.ts
+++ b/server/lib/live/live-manager.ts
@@ -1,7 +1,13 @@
1 1
2import { createServer, Server } from 'net' 2import { createServer, Server } from 'net'
3import { isTestInstance } from '@server/helpers/core-utils' 3import { isTestInstance } from '@server/helpers/core-utils'
4import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' 4import {
5 computeResolutionsToTranscode,
6 ffprobePromise,
7 getVideoFileBitrate,
8 getVideoFileFPS,
9 getVideoFileResolution
10} from '@server/helpers/ffprobe-utils'
5import { logger, loggerTagsFactory } from '@server/helpers/logger' 11import { logger, loggerTagsFactory } from '@server/helpers/logger'
6import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' 12import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
7import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME } from '@server/initializers/constants' 13import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME } from '@server/initializers/constants'
@@ -193,11 +199,20 @@ class LiveManager {
193 199
194 const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath 200 const rtmpUrl = 'rtmp://127.0.0.1:' + config.rtmp.port + streamPath
195 201
196 const [ { videoFileResolution }, fps ] = await Promise.all([ 202 const now = Date.now()
197 getVideoFileResolution(rtmpUrl), 203 const probe = await ffprobePromise(rtmpUrl)
198 getVideoFileFPS(rtmpUrl) 204
205 const [ { videoFileResolution }, fps, bitrate ] = await Promise.all([
206 getVideoFileResolution(rtmpUrl, probe),
207 getVideoFileFPS(rtmpUrl, probe),
208 getVideoFileBitrate(rtmpUrl, probe)
199 ]) 209 ])
200 210
211 logger.info(
212 '%s probing took %d ms (bitrate: %d, fps: %d, resolution: %d)',
213 rtmpUrl, Date.now() - now, bitrate, fps, videoFileResolution, lTags(sessionId, video.uuid)
214 )
215
201 const allResolutions = this.buildAllResolutionsToTranscode(videoFileResolution) 216 const allResolutions = this.buildAllResolutionsToTranscode(videoFileResolution)
202 217
203 logger.info( 218 logger.info(
@@ -213,6 +228,7 @@ class LiveManager {
213 streamingPlaylist, 228 streamingPlaylist,
214 rtmpUrl, 229 rtmpUrl,
215 fps, 230 fps,
231 bitrate,
216 allResolutions 232 allResolutions
217 }) 233 })
218 } 234 }
@@ -223,9 +239,10 @@ class LiveManager {
223 streamingPlaylist: MStreamingPlaylistVideo 239 streamingPlaylist: MStreamingPlaylistVideo
224 rtmpUrl: string 240 rtmpUrl: string
225 fps: number 241 fps: number
242 bitrate: number
226 allResolutions: number[] 243 allResolutions: number[]
227 }) { 244 }) {
228 const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, rtmpUrl } = options 245 const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, bitrate, rtmpUrl } = options
229 const videoUUID = videoLive.Video.uuid 246 const videoUUID = videoLive.Video.uuid
230 const localLTags = lTags(sessionId, videoUUID) 247 const localLTags = lTags(sessionId, videoUUID)
231 248
@@ -239,6 +256,7 @@ class LiveManager {
239 videoLive, 256 videoLive,
240 streamingPlaylist, 257 streamingPlaylist,
241 rtmpUrl, 258 rtmpUrl,
259 bitrate,
242 fps, 260 fps,
243 allResolutions 261 allResolutions
244 }) 262 })
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts
index 709d6c615..62708b14b 100644
--- a/server/lib/live/shared/muxing-session.ts
+++ b/server/lib/live/shared/muxing-session.ts
@@ -54,6 +54,7 @@ class MuxingSession extends EventEmitter {
54 private readonly streamingPlaylist: MStreamingPlaylistVideo 54 private readonly streamingPlaylist: MStreamingPlaylistVideo
55 private readonly rtmpUrl: string 55 private readonly rtmpUrl: string
56 private readonly fps: number 56 private readonly fps: number
57 private readonly bitrate: number
57 private readonly allResolutions: number[] 58 private readonly allResolutions: number[]
58 59
59 private readonly videoId: number 60 private readonly videoId: number
@@ -83,6 +84,7 @@ class MuxingSession extends EventEmitter {
83 streamingPlaylist: MStreamingPlaylistVideo 84 streamingPlaylist: MStreamingPlaylistVideo
84 rtmpUrl: string 85 rtmpUrl: string
85 fps: number 86 fps: number
87 bitrate: number
86 allResolutions: number[] 88 allResolutions: number[]
87 }) { 89 }) {
88 super() 90 super()
@@ -94,6 +96,7 @@ class MuxingSession extends EventEmitter {
94 this.streamingPlaylist = options.streamingPlaylist 96 this.streamingPlaylist = options.streamingPlaylist
95 this.rtmpUrl = options.rtmpUrl 97 this.rtmpUrl = options.rtmpUrl
96 this.fps = options.fps 98 this.fps = options.fps
99 this.bitrate = options.bitrate
97 this.allResolutions = options.allResolutions 100 this.allResolutions = options.allResolutions
98 101
99 this.videoId = this.videoLive.Video.id 102 this.videoId = this.videoLive.Video.id
@@ -118,6 +121,8 @@ class MuxingSession extends EventEmitter {
118 121
119 resolutions: this.allResolutions, 122 resolutions: this.allResolutions,
120 fps: this.fps, 123 fps: this.fps,
124 bitrate: this.bitrate,
125
121 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 126 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
122 profile: CONFIG.LIVE.TRANSCODING.PROFILE 127 profile: CONFIG.LIVE.TRANSCODING.PROFILE
123 }) 128 })
diff --git a/server/lib/transcoding/video-transcoding-profiles.ts b/server/lib/transcoding/video-transcoding-profiles.ts
index c5ea72a5f..2309f38d4 100644
--- a/server/lib/transcoding/video-transcoding-profiles.ts
+++ b/server/lib/transcoding/video-transcoding-profiles.ts
@@ -1,14 +1,7 @@
1import { logger } from '@server/helpers/logger' 1import { logger } from '@server/helpers/logger'
2import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../../shared/models/videos' 2import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../../shared/models/videos'
3import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils' 3import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils'
4import { 4import { canDoQuickAudioTranscode, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '../../helpers/ffprobe-utils'
5 canDoQuickAudioTranscode,
6 ffprobePromise,
7 getAudioStream,
8 getMaxAudioBitrate,
9 getVideoFileBitrate,
10 getVideoStreamFromFile
11} from '../../helpers/ffprobe-utils'
12import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' 5import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants'
13 6
14/** 7/**
@@ -22,8 +15,8 @@ import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants'
22// * https://slhck.info/video/2017/03/01/rate-control.html 15// * https://slhck.info/video/2017/03/01/rate-control.html
23// * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate 16// * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
24 17
25const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ input, resolution, fps }) => { 18const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ inputBitrate, resolution, fps }) => {
26 const targetBitrate = await buildTargetBitrate({ input, resolution, fps }) 19 const targetBitrate = buildTargetBitrate({ inputBitrate, resolution, fps })
27 if (!targetBitrate) return { outputOptions: [ ] } 20 if (!targetBitrate) return { outputOptions: [ ] }
28 21
29 return { 22 return {
@@ -36,8 +29,8 @@ const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ input, reso
36 } 29 }
37} 30}
38 31
39const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = async ({ resolution, fps, streamNum }) => { 32const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = async ({ resolution, fps, inputBitrate, streamNum }) => {
40 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) 33 const targetBitrate = buildTargetBitrate({ inputBitrate, resolution, fps })
41 34
42 return { 35 return {
43 outputOptions: [ 36 outputOptions: [
@@ -237,20 +230,16 @@ export {
237} 230}
238 231
239// --------------------------------------------------------------------------- 232// ---------------------------------------------------------------------------
240async function buildTargetBitrate (options: { 233
241 input: string 234function buildTargetBitrate (options: {
235 inputBitrate: number
242 resolution: VideoResolution 236 resolution: VideoResolution
243 fps: number 237 fps: number
244}) { 238}) {
245 const { input, resolution, fps } = options 239 const { inputBitrate, resolution, fps } = options
246 const probe = await ffprobePromise(input)
247
248 const videoStream = await getVideoStreamFromFile(input, probe)
249 if (!videoStream) return undefined
250 240
251 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) 241 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)
242 if (!inputBitrate) return targetBitrate
252 243
253 // Don't transcode to an higher bitrate than the original file 244 return Math.min(targetBitrate, inputBitrate)
254 const fileBitrate = await getVideoFileBitrate(input, probe)
255 return Math.min(targetBitrate, fileBitrate)
256} 245}
diff --git a/server/tests/api/check-params/live.ts b/server/tests/api/check-params/live.ts
index 700b4724d..8e1d655d4 100644
--- a/server/tests/api/check-params/live.ts
+++ b/server/tests/api/check-params/live.ts
@@ -417,7 +417,7 @@ describe('Test video lives API validator', function () {
417 417
418 const live = await command.get({ videoId: video.id }) 418 const live = await command.get({ videoId: video.id })
419 419
420 const ffmpegCommand = sendRTMPStream(live.rtmpUrl, live.streamKey) 420 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
421 421
422 await command.waitUntilPublished({ videoId: video.id }) 422 await command.waitUntilPublished({ videoId: video.id })
423 await command.update({ videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) 423 await command.update({ videoId: video.id, fields: {}, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
@@ -430,7 +430,7 @@ describe('Test video lives API validator', function () {
430 430
431 const live = await command.get({ videoId: video.id }) 431 const live = await command.get({ videoId: video.id })
432 432
433 const ffmpegCommand = sendRTMPStream(live.rtmpUrl, live.streamKey) 433 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
434 434
435 await command.waitUntilPublished({ videoId: video.id }) 435 await command.waitUntilPublished({ videoId: video.id })
436 436
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index d555cff19..4095cdb1c 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -3,7 +3,7 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' 6import { ffprobePromise, getVideoFileBitrate, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils'
7import { 7import {
8 checkLiveCleanupAfterSave, 8 checkLiveCleanupAfterSave,
9 checkLiveSegmentHash, 9 checkLiveSegmentHash,
@@ -302,21 +302,21 @@ describe('Test live', function () {
302 302
303 liveVideo = await createLiveWrapper() 303 liveVideo = await createLiveWrapper()
304 304
305 const command = sendRTMPStream(rtmpUrl + '/bad-live', liveVideo.streamKey) 305 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/bad-live', streamKey: liveVideo.streamKey })
306 await testFfmpegStreamError(command, true) 306 await testFfmpegStreamError(command, true)
307 }) 307 })
308 308
309 it('Should not allow a stream without the appropriate stream key', async function () { 309 it('Should not allow a stream without the appropriate stream key', async function () {
310 this.timeout(60000) 310 this.timeout(60000)
311 311
312 const command = sendRTMPStream(rtmpUrl + '/live', 'bad-stream-key') 312 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: 'bad-stream-key' })
313 await testFfmpegStreamError(command, true) 313 await testFfmpegStreamError(command, true)
314 }) 314 })
315 315
316 it('Should succeed with the correct params', async function () { 316 it('Should succeed with the correct params', async function () {
317 this.timeout(60000) 317 this.timeout(60000)
318 318
319 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) 319 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
320 await testFfmpegStreamError(command, false) 320 await testFfmpegStreamError(command, false)
321 }) 321 })
322 322
@@ -340,7 +340,7 @@ describe('Test live', function () {
340 340
341 await servers[0].blacklist.add({ videoId: liveVideo.uuid }) 341 await servers[0].blacklist.add({ videoId: liveVideo.uuid })
342 342
343 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) 343 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
344 await testFfmpegStreamError(command, true) 344 await testFfmpegStreamError(command, true)
345 }) 345 })
346 346
@@ -351,7 +351,7 @@ describe('Test live', function () {
351 351
352 await servers[0].videos.remove({ id: liveVideo.uuid }) 352 await servers[0].videos.remove({ id: liveVideo.uuid })
353 353
354 const command = sendRTMPStream(rtmpUrl + '/live', liveVideo.streamKey) 354 const command = sendRTMPStream({ rtmpBaseUrl: rtmpUrl + '/live', streamKey: liveVideo.streamKey })
355 await testFfmpegStreamError(command, true) 355 await testFfmpegStreamError(command, true)
356 }) 356 })
357 }) 357 })
@@ -468,6 +468,34 @@ describe('Test live', function () {
468 await stopFfmpeg(ffmpegCommand) 468 await stopFfmpeg(ffmpegCommand)
469 }) 469 })
470 470
471 it('Should correctly set the appropriate bitrate depending on the input', async function () {
472 this.timeout(120000)
473
474 liveVideoId = await createLiveWrapper(false)
475
476 const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({
477 videoId: liveVideoId,
478 fixtureName: 'video_short.mp4',
479 copyCodecs: true
480 })
481 await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
482 await waitJobs(servers)
483
484 const video = await servers[0].videos.get({ id: liveVideoId })
485
486 const masterPlaylist = video.streamingPlaylists[0].playlistUrl
487 const probe = await ffprobePromise(masterPlaylist)
488
489 const bitrates = probe.streams.map(s => parseInt(s.tags.variant_bitrate))
490 for (const bitrate of bitrates) {
491 expect(bitrate).to.exist
492 expect(isNaN(bitrate)).to.be.false
493 expect(bitrate).to.be.below(61_000_000) // video_short.mp4 bitrate
494 }
495
496 await stopFfmpeg(ffmpegCommand)
497 })
498
471 it('Should enable transcoding with some resolutions and correctly save them', async function () { 499 it('Should enable transcoding with some resolutions and correctly save them', async function () {
472 this.timeout(200000) 500 this.timeout(200000)
473 501
diff --git a/shared/extra-utils/videos/live-command.ts b/shared/extra-utils/videos/live-command.ts
index bf9486a05..81ae458e0 100644
--- a/shared/extra-utils/videos/live-command.ts
+++ b/shared/extra-utils/videos/live-command.ts
@@ -68,11 +68,12 @@ export class LiveCommand extends AbstractCommand {
68 async sendRTMPStreamInVideo (options: OverrideCommandOptions & { 68 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
69 videoId: number | string 69 videoId: number | string
70 fixtureName?: string 70 fixtureName?: string
71 copyCodecs?: boolean
71 }) { 72 }) {
72 const { videoId, fixtureName } = options 73 const { videoId, fixtureName, copyCodecs } = options
73 const videoLive = await this.get({ videoId }) 74 const videoLive = await this.get({ videoId })
74 75
75 return sendRTMPStream(videoLive.rtmpUrl, videoLive.streamKey, fixtureName) 76 return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs })
76 } 77 }
77 78
78 async runAndTestStreamError (options: OverrideCommandOptions & { 79 async runAndTestStreamError (options: OverrideCommandOptions & {
diff --git a/shared/extra-utils/videos/live.ts b/shared/extra-utils/videos/live.ts
index 94f5f5b59..6aa405b19 100644
--- a/shared/extra-utils/videos/live.ts
+++ b/shared/extra-utils/videos/live.ts
@@ -7,16 +7,29 @@ import { join } from 'path'
7import { buildAbsoluteFixturePath, wait } from '../miscs' 7import { buildAbsoluteFixturePath, wait } from '../miscs'
8import { PeerTubeServer } from '../server/server' 8import { PeerTubeServer } from '../server/server'
9 9
10function sendRTMPStream (rtmpBaseUrl: string, streamKey: string, fixtureName = 'video_short.mp4') { 10function sendRTMPStream (options: {
11 rtmpBaseUrl: string
12 streamKey: string
13 fixtureName?: string // default video_short.mp4
14 copyCodecs?: boolean // default false
15}) {
16 const { rtmpBaseUrl, streamKey, fixtureName = 'video_short.mp4', copyCodecs = false } = options
17
11 const fixture = buildAbsoluteFixturePath(fixtureName) 18 const fixture = buildAbsoluteFixturePath(fixtureName)
12 19
13 const command = ffmpeg(fixture) 20 const command = ffmpeg(fixture)
14 command.inputOption('-stream_loop -1') 21 command.inputOption('-stream_loop -1')
15 command.inputOption('-re') 22 command.inputOption('-re')
16 command.outputOption('-c:v libx264') 23
17 command.outputOption('-g 50') 24 if (copyCodecs) {
18 command.outputOption('-keyint_min 2') 25 command.outputOption('-c:v libx264')
19 command.outputOption('-r 60') 26 command.outputOption('-g 50')
27 command.outputOption('-keyint_min 2')
28 command.outputOption('-r 60')
29 } else {
30 command.outputOption('-c copy')
31 }
32
20 command.outputOption('-f flv') 33 command.outputOption('-f flv')
21 34
22 const rtmpUrl = rtmpBaseUrl + '/' + streamKey 35 const rtmpUrl = rtmpBaseUrl + '/' + streamKey
diff --git a/shared/models/videos/video-transcoding.model.ts b/shared/models/videos/video-transcoding.model.ts
index 3f2382ce8..f1fe4609b 100644
--- a/shared/models/videos/video-transcoding.model.ts
+++ b/shared/models/videos/video-transcoding.model.ts
@@ -5,6 +5,7 @@ import { VideoResolution } from './video-resolution.enum'
5export type EncoderOptionsBuilder = (params: { 5export type EncoderOptionsBuilder = (params: {
6 input: string 6 input: string
7 resolution: VideoResolution 7 resolution: VideoResolution
8 inputBitrate: number
8 fps?: number 9 fps?: number
9 streamNum?: number 10 streamNum?: number
10}) => Promise<EncoderOptions> | EncoderOptions 11}) => Promise<EncoderOptions> | EncoderOptions