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, availableEncoders,
profile: getProfile(type), profile: getProfile(type),
niceness: FFMPEG_NICE[type], niceness: FFMPEG_NICE[type.toUpperCase()],
tmpDirectory: CONFIG.STORAGE.TMP_DIR, tmpDirectory: CONFIG.STORAGE.TMP_DIR,
threads: getThreads(type), threads: getThreads(type),

View File

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

View File

@ -1,16 +1,6 @@
import { FfprobeData } from 'fluent-ffmpeg' import { FfprobeData } from 'fluent-ffmpeg'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants' import { canDoQuickAudioTranscode, canDoQuickVideoTranscode, ffprobePromise } from '@shared/ffmpeg'
import { getMaxBitrate } from '@shared/core-utils'
import {
ffprobePromise,
getAudioStream,
getMaxAudioBitrate,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamFPS
} from '@shared/ffmpeg'
export async function canDoQuickTranscode (path: string, existingProbe?: FfprobeData): Promise<boolean> { export async function canDoQuickTranscode (path: string, existingProbe?: FfprobeData): Promise<boolean> {
if (CONFIG.TRANSCODING.PROFILE !== 'default') return false if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
@ -20,42 +10,3 @@ export async function canDoQuickTranscode (path: string, existingProbe?: Ffprobe
return await canDoQuickVideoTranscode(path, probe) && return await canDoQuickVideoTranscode(path, probe) &&
await canDoQuickAudioTranscode(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 { expect } from 'chai'
import { canDoQuickTranscode } from '@server/lib/transcoding/transcoding-quick-transcode' import { canDoQuickTranscode } from '@server/lib/transcoding/transcoding-quick-transcode'
import { checkWebTorrentWorks, generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared' 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 { import {
ffprobePromise, ffprobePromise,
getAudioStream, getAudioStream,
@ -564,7 +564,7 @@ describe('Test video transcoding', function () {
expect(resolution).to.equal(resolution) expect(resolution).to.equal(resolution)
const maxBitrate = getMaxBitrate({ ...dataResolution, fps }) const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
expect(bitrate).to.be.below(maxBitrate) expect(bitrate).to.be.below(maxBitrate)
} }
} }
@ -611,7 +611,7 @@ describe('Test video transcoding', function () {
const bitrate = await getVideoStreamBitrate(path) const bitrate = await getVideoStreamBitrate(path)
const inputBitrate = 60_000 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) let belowValue = Math.max(inputBitrate, limit)
belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise 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 { expect } from 'chai'
import { snakeCase } from 'lodash' import { snakeCase } from 'lodash'
import validator from 'validator' import validator from 'validator'
import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils' import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate } from '@shared/core-utils'
import { VideoResolution } from '@shared/models' import { VideoResolution } from '@shared/models'
import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils' import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils'
@ -128,7 +128,7 @@ describe('Bitrate', function () {
] ]
for (const test of tests) { 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) { 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() const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
peertubeRunner = new PeerTubeRunnerProcess() peertubeRunner = new PeerTubeRunnerProcess()
await peertubeRunner.runServer({ hideLogs: false }) await peertubeRunner.runServer()
await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' }) 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() const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
peertubeRunner = new PeerTubeRunnerProcess() peertubeRunner = new PeerTubeRunnerProcess()
await peertubeRunner.runServer({ hideLogs: false }) await peertubeRunner.runServer()
await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' }) 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 ffmpeg from 'fluent-ffmpeg'
import { ensureDir, pathExists } from 'fs-extra' import { ensureDir, pathExists } from 'fs-extra'
import { dirname } from 'path' 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' import { getVideoStreamBitrate, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@shared/ffmpeg'
async function ensureHasTooBigBitrate (fixturePath: string) { async function ensureHasTooBigBitrate (fixturePath: string) {
@ -10,7 +10,7 @@ async function ensureHasTooBigBitrate (fixturePath: string) {
const dataResolution = await getVideoStreamDimensionsInfo(fixturePath) const dataResolution = await getVideoStreamDimensionsInfo(fixturePath)
const fps = await getVideoStreamFPS(fixturePath) const fps = await getVideoStreamFPS(fixturePath)
const maxBitrate = getMaxBitrate({ ...dataResolution, fps }) const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
expect(bitrate).to.be.above(maxBitrate) expect(bitrate).to.be.above(maxBitrate)
} }

View File

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

View File

@ -1,5 +1,15 @@
import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' import { FfprobeData } from 'fluent-ffmpeg'
import { buildStreamSuffix, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/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' import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models'
const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => {
@ -34,6 +44,10 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => { const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => {
const probe = await ffprobePromise(input) const probe = await ffprobePromise(input)
if (canCopyAudio && await canDoQuickAudioTranscode(input, probe)) {
return { copy: true, outputOptions: [ ] }
}
const parsedAudio = await getAudioStream(input, probe) const parsedAudio = await getAudioStream(input, probe)
// We try to reduce the ceiling bitrate by making rough matches of bitrates // 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: { function getTargetBitrate (options: {
@ -105,8 +158,8 @@ function getTargetBitrate (options: {
}) { }) {
const { inputBitrate, resolution, ratio, fps } = options const { inputBitrate, resolution, ratio, fps } = options
const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio })) const capped = capBitrate(inputBitrate, getAverageTheoreticalBitrate({ resolution, fps, ratio }))
const limit = getMinLimitBitrate({ resolution, fps, ratio }) const limit = getMinTheoreticalBitrate({ resolution, fps, ratio })
return Math.max(limit, capped) return Math.max(limit, capped)
} }