1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 01:39:37 +02:00

Improve audio transcoding

ffmpeg aac encoder is not very good so prefer to keep the same bitrate
as mp3
we also use the max available bitrate with a flac input that has an
unknown bitrate
This commit is contained in:
Chocobozzz 2025-07-24 16:49:28 +02:00
parent 445866967f
commit 29a88c0dde
No known key found for this signature in database
GPG key ID: 583A612D890159BE
2 changed files with 28 additions and 56 deletions

View file

@ -2,7 +2,7 @@ import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, getMinTheoretic
import {
buildStreamSuffix,
getAudioStream,
getMaxAudioBitrate,
getMaxAudioKBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
@ -42,7 +42,7 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio, inputProbe }) => {
if (canCopyAudio && await canDoQuickAudioTranscode(input, inputProbe)) {
return { copy: true, outputOptions: [ ] }
return { copy: true, outputOptions: [] }
}
const parsedAudio = await getAudioStream(input, inputProbe)
@ -52,7 +52,7 @@ const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNu
const audioCodecName = parsedAudio.audioStream['codec_name']
const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate)
const bitrate = getMaxAudioKBitrate(audioCodecName, parsedAudio.bitrate)
// Force stereo as it causes some issues with HLS playback in Chrome
const base = [ '-channel_layout', 'stereo' ]
@ -116,7 +116,7 @@ export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeDat
const audioBitrate = parsedAudio.bitrate
if (!audioBitrate) return false
const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
const maxAudioBitrate = getMaxAudioKBitrate('aac', audioBitrate)
if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
const channelLayout = parsedAudio.audioStream['channel_layout']

View file

@ -6,7 +6,7 @@ import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'
* Helpers to run ffprobe and extract data from the JSON output
*/
function ffprobePromise (path: string) {
export function ffprobePromise (path: string) {
return new Promise<FfprobeData>((res, rej) => {
ffmpeg.ffprobe(path, [ '-show_chapters' ], (err, data) => {
if (err) return rej(err)
@ -62,7 +62,7 @@ const imageCodecs = new Set([
'xwd'
])
async function isAudioFile (path: string, existingProbe?: FfprobeData) {
export async function isAudioFile (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) return true
@ -71,13 +71,13 @@ async function isAudioFile (path: string, existingProbe?: FfprobeData) {
return false
}
async function hasAudioStream (path: string, existingProbe?: FfprobeData) {
export async function hasAudioStream (path: string, existingProbe?: FfprobeData) {
const { audioStream } = await getAudioStream(path, existingProbe)
return !!audioStream
}
async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
export async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
const data = existingProbe || await ffprobePromise(videoPath)
if (Array.isArray(data.streams)) {
@ -95,45 +95,34 @@ async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
return { absolutePath: data.format.filename }
}
function getMaxAudioBitrate (type: string, bitrate: number) {
export function getMaxAudioKBitrate (type: string, bitrate: number) {
const maxKBitrate = 384
const kToBits = (kbits: number) => kbits * 1000
// If we did not manage to get the bitrate, use an average value
if (!bitrate) return 256
if (!bitrate) {
// We expect uploader wants a high quality audio if the input is in FLAC format
if (type === 'flac') return maxKBitrate
return 256
}
const kBitrate = Math.round(bitrate / 1000)
if (type === 'aac') {
switch (true) {
case bitrate > kToBits(maxKBitrate):
return maxKBitrate
if (kBitrate > maxKBitrate) return maxKBitrate
default:
return -1 // we interpret it as a signal to copy the audio stream as is
}
// We interpret it as a signal to copy the audio stream as is
return -1
}
/*
a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
That's why, when using aac, we can go to lower kbit/sec. The equivalences
made here are not made to be accurate, especially with good mp3 encoders.
*/
switch (true) {
case bitrate <= kToBits(192):
return 128
case bitrate <= kToBits(384):
return 256
default:
return maxKBitrate
}
return Math.min(maxKBitrate, kBitrate)
}
// ---------------------------------------------------------------------------
// Video
// ---------------------------------------------------------------------------
async function getVideoStreamDimensionsInfo (path: string, existingProbe?: FfprobeData) {
export async function getVideoStreamDimensionsInfo (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) {
return {
@ -164,7 +153,7 @@ async function getVideoStreamDimensionsInfo (path: string, existingProbe?: Ffpro
}
}
async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
export async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) return 0
@ -182,7 +171,7 @@ async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
return 0
}
async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
export async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
const metadata = existingProbe || await ffprobePromise(path)
let bitrate = metadata.format.bit_rate
@ -197,19 +186,19 @@ async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData)
return undefined
}
async function getVideoStreamDuration (path: string, existingProbe?: FfprobeData) {
export async function getVideoStreamDuration (path: string, existingProbe?: FfprobeData) {
const metadata = existingProbe || await ffprobePromise(path)
return Math.round(metadata.format.duration)
}
async function getVideoStream (path: string, existingProbe?: FfprobeData) {
export async function getVideoStream (path: string, existingProbe?: FfprobeData) {
const metadata = existingProbe || await ffprobePromise(path)
return metadata.streams.find(s => s.codec_type === 'video')
}
async function hasVideoStream (path: string, existingProbe?: FfprobeData) {
export async function hasVideoStream (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe)
return !!videoStream
@ -219,7 +208,7 @@ async function hasVideoStream (path: string, existingProbe?: FfprobeData) {
// Chapters
// ---------------------------------------------------------------------------
async function getChaptersFromContainer (options: {
export async function getChaptersFromContainer (options: {
path: string
maxTitleLength: number
ffprobe?: FfprobeData
@ -236,20 +225,3 @@ async function getChaptersFromContainer (options: {
title: (c['TAG:title'] || '').slice(0, maxTitleLength)
}))
}
// ---------------------------------------------------------------------------
export {
ffprobePromise,
getAudioStream,
getChaptersFromContainer,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamDuration,
getVideoStreamFPS,
hasAudioStream,
hasVideoStream,
isAudioFile
}