Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,10 @@ transcoding:
web_videos:
enabled: false

# Apply dynamic loudness normalization when transcoding audio
audio_loudnorm:
enabled: false

# /!\ Requires ffmpeg >= 4.1
# Generate HLS playlists and fragmented MP4 files. Better playback than with Web Videos:
# * Resolution change is smoother
Expand Down Expand Up @@ -726,6 +730,10 @@ live:
# Available in core PeerTube: 'default'
profile: 'default'

# Apply dynamic loudness normalization when transcoding audio in live
audio_loudnorm:
enabled: false

resolutions:
0p: false # Audio only
144p: false
Expand Down
21 changes: 20 additions & 1 deletion packages/ffmpeg/src/ffmpeg-command-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface FFmpegCommandWrapperOptions {
logger: SimpleLogger
lTags?: { tags: string[] }

audioLoudnorm?: boolean

updateJobProgress?: (progress?: number) => void
onEnd?: () => void
onError?: (err: Error) => void
Expand All @@ -39,6 +41,8 @@ export class FFmpegCommandWrapper {
private readonly logger: SimpleLogger
private readonly lTags: { tags: string[] }

private readonly audioLoudnorm: boolean

private readonly updateJobProgress: (progress?: number) => void
private readonly onEnd?: () => void
private readonly onError?: (err: Error) => void
Expand All @@ -54,6 +58,8 @@ export class FFmpegCommandWrapper {
this.logger = options.logger
this.lTags = options.lTags || { tags: [] }

this.audioLoudnorm = options.audioLoudnorm === true

this.updateJobProgress = options.updateJobProgress

this.onEnd = options.onEnd
Expand All @@ -72,6 +78,10 @@ export class FFmpegCommandWrapper {
return this.command
}

isAudioLoudnormEnabled () {
return this.audioLoudnorm === true
}

// ---------------------------------------------------------------------------

debugLog (msg: string, meta: any = {}) {
Expand Down Expand Up @@ -182,6 +192,11 @@ export class FFmpegCommandWrapper {

const { streamType, videoType } = options

// Force re-encode audio if loudnorm is enabled
if (streamType === 'audio' && this.audioLoudnorm) {
options.canCopyAudio = false
}

const encodersToTry = this.availableEncoders.encodersToTry[videoType][streamType]
const encoders = this.availableEncoders.available[videoType]

Expand Down Expand Up @@ -210,6 +225,9 @@ export class FFmpegCommandWrapper {
}
}

// propagate audioLoudnorm flag to the profile builder
;(options as any).audioLoudnorm = this.audioLoudnorm

const result = await builder(
pick(options, [
'input',
Expand All @@ -220,7 +238,8 @@ export class FFmpegCommandWrapper {
'inputProbe',
'fps',
'inputRatio',
'streamNum'
'streamNum',
'audioLoudnorm'
])
)

Expand Down
26 changes: 21 additions & 5 deletions packages/ffmpeg/src/ffmpeg-default-transcoding-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import { EncoderOptionsBuilder, EncoderOptionsBuilderParams } from '@peertube/peertube-models'
import { FfprobeData } from 'fluent-ffmpeg'

const LOUDNORM_FILTER = 'loudnorm=I=-14:TP=-1:LRA=11:linear=false'

const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => {
const { fps, inputRatio, inputBitrate, resolution } = options

Expand Down Expand Up @@ -40,7 +42,7 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
}
}

const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio, inputProbe }) => {
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio, inputProbe, audioLoudnorm }) => {
if (canCopyAudio && await canDoQuickAudioTranscode(input, inputProbe)) {
return { copy: true, outputOptions: [] }
}
Expand All @@ -57,15 +59,29 @@ const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNu
// Force stereo as it causes some issues with HLS playback in Chrome
const base = [ '-channel_layout', 'stereo' ]

const opts = base.slice()

if (audioLoudnorm) {
opts.push(buildStreamSuffix('-filter:a', streamNum), LOUDNORM_FILTER)
}

if (bitrate !== -1) {
return { outputOptions: base.concat([ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ]) }
return { outputOptions: opts.concat([ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ]) }
}

return { outputOptions: base }
return { outputOptions: opts }
}

const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => {
return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] }
const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum, audioLoudnorm }) => {
const outputOptions: string[] = []

if (audioLoudnorm) {
outputOptions.push(buildStreamSuffix('-filter:a', streamNum), LOUDNORM_FILTER)
}

outputOptions.push(buildStreamSuffix('-q:a', streamNum), '5')

return { outputOptions }
}

export function getDefaultAvailableEncoders () {
Expand Down
2 changes: 1 addition & 1 deletion packages/ffmpeg/src/ffmpeg-vod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class FFmpegVOD {

const videoPath = this.getHLSVideoPath(options)

if (options.copyCodecs) {
if (options.copyCodecs && !this.commandWrapper.isAudioLoudnormEnabled()) {
presetCopy(this.commandWrapper, {
withAudio: !options.separatedAudio || !options.resolution,
withVideo: !options.separatedAudio || !!options.resolution
Expand Down
8 changes: 8 additions & 0 deletions packages/models/src/server/custom-config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ export interface CustomConfig {
enabled: boolean
splitAudioAndVideo: boolean
}

audioLoudnorm?: {
enabled: boolean
}
}

live: {
Expand Down Expand Up @@ -228,6 +232,10 @@ export interface CustomConfig {
fps: {
max: number
}

audioLoudnorm?: {
enabled: boolean
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export type EncoderOptionsBuilderParams = {

// For lives
streamNum?: number

// Flag to enforce audio loudness normalization
audioLoudnorm?: boolean
}

export type EncoderOptionsBuilder = (params: EncoderOptionsBuilderParams) => Promise<EncoderOptions> | EncoderOptions
Expand Down
6 changes: 6 additions & 0 deletions server/core/controllers/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ function customConfig (): CustomConfig {
originalFile: {
keep: CONFIG.TRANSCODING.ORIGINAL_FILE.KEEP
},
audioLoudnorm: {
enabled: CONFIG.TRANSCODING.AUDIO_LOUDNORM.ENABLED
},
remoteRunners: {
enabled: CONFIG.TRANSCODING.REMOTE_RUNNERS.ENABLED
},
Expand Down Expand Up @@ -467,6 +470,9 @@ function customConfig (): CustomConfig {
remoteRunners: {
enabled: CONFIG.LIVE.TRANSCODING.REMOTE_RUNNERS.ENABLED
},
audioLoudnorm: {
enabled: CONFIG.LIVE.TRANSCODING.AUDIO_LOUDNORM.ENABLED
},
threads: CONFIG.LIVE.TRANSCODING.THREADS,
profile: CONFIG.LIVE.TRANSCODING.PROFILE,
resolutions: {
Expand Down
7 changes: 6 additions & 1 deletion server/core/helpers/ffmpeg/ffmpeg-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ export function getFFmpegCommandWrapperOptions (type: CommandType, availableEnco
warn: logger.warn.bind(logger),
error: logger.error.bind(logger)
},
lTags: { tags: [ 'ffmpeg' ] }
lTags: { tags: [ 'ffmpeg' ] },
audioLoudnorm: type === 'vod'
? CONFIG.TRANSCODING.AUDIO_LOUDNORM.ENABLED
: type === 'live'
? CONFIG.LIVE.TRANSCODING.AUDIO_LOUDNORM.ENABLED
: false
}
}

Expand Down
12 changes: 12 additions & 0 deletions server/core/initializers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@ const CONFIG = {
get PROFILE () {
return config.get<string>('transcoding.profile')
},

AUDIO_LOUDNORM: {
get ENABLED () {
return config.get<boolean>('transcoding.audio_loudnorm.enabled')
}
},
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () {
return config.get<boolean>('transcoding.always_transcode_original_resolution')
},
Expand Down Expand Up @@ -706,6 +712,12 @@ const CONFIG = {
return config.get<string>('live.transcoding.profile')
},

AUDIO_LOUDNORM: {
get ENABLED () {
return config.get<boolean>('live.transcoding.audio_loudnorm.enabled')
}
},

get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () {
return config.get<boolean>('live.transcoding.always_transcode_original_resolution')
},
Expand Down
Loading