Fix audio transcoding copy

This commit is contained in:
Chocobozzz 2023-05-05 10:53:04 +02:00 committed by Chocobozzz
parent dd3f99434c
commit e7d8e2b245
10 changed files with 76 additions and 72 deletions

View File

@ -11,7 +11,7 @@ export function getFFmpegCommandWrapperOptions (type: CommandType, availableEnco
availableEncoders,
profile: getProfile(type),
niceness: FFMPEG_NICE[type],
niceness: FFMPEG_NICE[type.toUpperCase()],
tmpDirectory: CONFIG.STORAGE.TMP_DIR,
threads: getThreads(type),

View File

@ -469,7 +469,7 @@ const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
DISLIKE: 'dislike'
}
const FFMPEG_NICE: { [ id: string ]: number } = {
const FFMPEG_NICE = {
// parent process defaults to niceness = 0
// reminder: lower = higher priority, max value is 19, lowest is -20
LIVE: 5, // prioritize over VOD and THUMBNAIL

View File

@ -1,16 +1,6 @@
import { FfprobeData } from 'fluent-ffmpeg'
import { CONFIG } from '@server/initializers/config'
import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
import { getMaxBitrate } from '@shared/core-utils'
import {
ffprobePromise,
getAudioStream,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamFPS
} from '@shared/ffmpeg'
import { canDoQuickAudioTranscode, canDoQuickVideoTranscode, ffprobePromise } from '@shared/ffmpeg'
export async function canDoQuickTranscode (path: string, existingProbe?: FfprobeData): Promise<boolean> {
if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
@ -20,42 +10,3 @@ export async function canDoQuickTranscode (path: string, existingProbe?: Ffprobe
return await canDoQuickVideoTranscode(path, probe) &&
await canDoQuickAudioTranscode(path, probe)
}
export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
const parsedAudio = await getAudioStream(path, probe)
if (!parsedAudio.audioStream) return true
if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
const audioBitrate = parsedAudio.bitrate
if (!audioBitrate) return false
const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
const channelLayout = parsedAudio.audioStream['channel_layout']
// Causes playback issues with Chrome
if (!channelLayout || channelLayout === 'unknown' || channelLayout === 'quad') return false
return true
}
export async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
const videoStream = await getVideoStream(path, probe)
const fps = await getVideoStreamFPS(path, probe)
const bitRate = await getVideoStreamBitrate(path, probe)
const resolutionData = await getVideoStreamDimensionsInfo(path, probe)
// If ffprobe did not manage to guess the bitrate
if (!bitRate) return false
// check video params
if (!videoStream) return false
if (videoStream['codec_name'] !== 'h264') return false
if (videoStream['pix_fmt'] !== 'yuv420p') return false
if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false
return true
}

View File

@ -3,7 +3,7 @@
import { expect } from 'chai'
import { canDoQuickTranscode } from '@server/lib/transcoding/transcoding-quick-transcode'
import { checkWebTorrentWorks, generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared'
import { buildAbsoluteFixturePath, getAllFiles, getMaxBitrate, getMinLimitBitrate, omit } from '@shared/core-utils'
import { buildAbsoluteFixturePath, getAllFiles, getMaxTheoreticalBitrate, getMinTheoreticalBitrate, omit } from '@shared/core-utils'
import {
ffprobePromise,
getAudioStream,
@ -564,7 +564,7 @@ describe('Test video transcoding', function () {
expect(resolution).to.equal(resolution)
const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
expect(bitrate).to.be.below(maxBitrate)
}
}
@ -611,7 +611,7 @@ describe('Test video transcoding', function () {
const bitrate = await getVideoStreamBitrate(path)
const inputBitrate = 60_000
const limit = getMinLimitBitrate({ fps: 10, ratio: 1, resolution: r })
const limit = getMinTheoreticalBitrate({ fps: 10, ratio: 1, resolution: r })
let belowValue = Math.max(inputBitrate, limit)
belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise

View File

@ -3,7 +3,7 @@
import { expect } from 'chai'
import { snakeCase } from 'lodash'
import validator from 'validator'
import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils'
import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate } from '@shared/core-utils'
import { VideoResolution } from '@shared/models'
import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils'
@ -128,7 +128,7 @@ describe('Bitrate', function () {
]
for (const test of tests) {
expect(getMaxBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
expect(getMaxTheoreticalBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
}
})
@ -144,7 +144,7 @@ describe('Bitrate', function () {
]
for (const test of tests) {
expect(getAverageBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
expect(getAverageTheoreticalBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
}
})
})

View File

@ -145,7 +145,7 @@ describe('Test Live transcoding in peertube-runner program', function () {
const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
peertubeRunner = new PeerTubeRunnerProcess()
await peertubeRunner.runServer({ hideLogs: false })
await peertubeRunner.runServer()
await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' })
})

View File

@ -75,7 +75,7 @@ describe('Test studio transcoding in peertube-runner program', function () {
const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
peertubeRunner = new PeerTubeRunnerProcess()
await peertubeRunner.runServer({ hideLogs: false })
await peertubeRunner.runServer()
await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' })
})

View File

@ -2,7 +2,7 @@ import { expect } from 'chai'
import ffmpeg from 'fluent-ffmpeg'
import { ensureDir, pathExists } from 'fs-extra'
import { dirname } from 'path'
import { buildAbsoluteFixturePath, getMaxBitrate } from '@shared/core-utils'
import { buildAbsoluteFixturePath, getMaxTheoreticalBitrate } from '@shared/core-utils'
import { getVideoStreamBitrate, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@shared/ffmpeg'
async function ensureHasTooBigBitrate (fixturePath: string) {
@ -10,7 +10,7 @@ async function ensureHasTooBigBitrate (fixturePath: string) {
const dataResolution = await getVideoStreamDimensionsInfo(fixturePath)
const fps = await getVideoStreamFPS(fixturePath)
const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
expect(bitrate).to.be.above(maxBitrate)
}

View File

@ -40,7 +40,7 @@ const maxBitPerPixel: BitPerPixel = {
[VideoResolution.H_4K]: 0.14
}
function getAverageBitrate (options: {
function getAverageTheoreticalBitrate (options: {
resolution: VideoResolution
ratio: number
fps: number
@ -51,7 +51,7 @@ function getAverageBitrate (options: {
return targetBitrate
}
function getMaxBitrate (options: {
function getMaxTheoreticalBitrate (options: {
resolution: VideoResolution
ratio: number
fps: number
@ -62,7 +62,7 @@ function getMaxBitrate (options: {
return targetBitrate
}
function getMinLimitBitrate (options: {
function getMinTheoreticalBitrate (options: {
resolution: VideoResolution
ratio: number
fps: number
@ -76,9 +76,9 @@ function getMinLimitBitrate (options: {
// ---------------------------------------------------------------------------
export {
getAverageBitrate,
getMaxBitrate,
getMinLimitBitrate
getAverageTheoreticalBitrate,
getMaxTheoreticalBitrate,
getMinTheoreticalBitrate
}
// ---------------------------------------------------------------------------

View File

@ -1,5 +1,15 @@
import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils'
import { buildStreamSuffix, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/ffmpeg'
import { FfprobeData } from 'fluent-ffmpeg'
import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, getMinTheoreticalBitrate } from '@shared/core-utils'
import {
buildStreamSuffix,
ffprobePromise,
getAudioStream,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamFPS
} from '@shared/ffmpeg'
import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models'
const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => {
@ -34,6 +44,10 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => {
const probe = await ffprobePromise(input)
if (canCopyAudio && await canDoQuickAudioTranscode(input, probe)) {
return { copy: true, outputOptions: [ ] }
}
const parsedAudio = await getAudioStream(input, probe)
// We try to reduce the ceiling bitrate by making rough matches of bitrates
@ -95,6 +109,45 @@ export function getDefaultEncodersToTry () {
}
}
export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
const parsedAudio = await getAudioStream(path, probe)
if (!parsedAudio.audioStream) return true
if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
const audioBitrate = parsedAudio.bitrate
if (!audioBitrate) return false
const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
const channelLayout = parsedAudio.audioStream['channel_layout']
// Causes playback issues with Chrome
if (!channelLayout || channelLayout === 'unknown' || channelLayout === 'quad') return false
return true
}
export async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
const videoStream = await getVideoStream(path, probe)
const fps = await getVideoStreamFPS(path, probe)
const bitRate = await getVideoStreamBitrate(path, probe)
const resolutionData = await getVideoStreamDimensionsInfo(path, probe)
// If ffprobe did not manage to guess the bitrate
if (!bitRate) return false
// check video params
if (!videoStream) return false
if (videoStream['codec_name'] !== 'h264') return false
if (videoStream['pix_fmt'] !== 'yuv420p') return false
if (fps < 2 || fps > 65) return false
if (bitRate > getMaxTheoreticalBitrate({ ...resolutionData, fps })) return false
return true
}
// ---------------------------------------------------------------------------
function getTargetBitrate (options: {
@ -105,8 +158,8 @@ function getTargetBitrate (options: {
}) {
const { inputBitrate, resolution, ratio, fps } = options
const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio }))
const limit = getMinLimitBitrate({ resolution, fps, ratio })
const capped = capBitrate(inputBitrate, getAverageTheoreticalBitrate({ resolution, fps, ratio }))
const limit = getMinTheoreticalBitrate({ resolution, fps, ratio })
return Math.max(limit, capped)
}