From e7d8e2b245491c0a8e008fb570037506d729ff04 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 May 2023 10:53:04 +0200 Subject: [PATCH] Fix audio transcoding copy --- server/helpers/ffmpeg/ffmpeg-options.ts | 2 +- server/initializers/constants.ts | 2 +- .../transcoding-quick-transcode.ts | 51 +--------------- server/tests/api/transcoding/transcoder.ts | 6 +- server/tests/helpers/core-utils.ts | 6 +- .../tests/peertube-runner/live-transcoding.ts | 2 +- .../peertube-runner/studio-transcoding.ts | 2 +- server/tests/shared/generate.ts | 4 +- shared/core-utils/videos/bitrate.ts | 12 ++-- .../ffmpeg-default-transcoding-profile.ts | 61 +++++++++++++++++-- 10 files changed, 76 insertions(+), 72 deletions(-) diff --git a/server/helpers/ffmpeg/ffmpeg-options.ts b/server/helpers/ffmpeg/ffmpeg-options.ts index db6350d39..64d7c4179 100644 --- a/server/helpers/ffmpeg/ffmpeg-options.ts +++ b/server/helpers/ffmpeg/ffmpeg-options.ts @@ -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), diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 6a757a0ff..adf24b73f 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -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 diff --git a/server/lib/transcoding/transcoding-quick-transcode.ts b/server/lib/transcoding/transcoding-quick-transcode.ts index b7f921890..53f12cd06 100644 --- a/server/lib/transcoding/transcoding-quick-transcode.ts +++ b/server/lib/transcoding/transcoding-quick-transcode.ts @@ -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 { 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 { - 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 { - 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 -} diff --git a/server/tests/api/transcoding/transcoder.ts b/server/tests/api/transcoding/transcoder.ts index fa78b58bb..8a0a7f6d2 100644 --- a/server/tests/api/transcoding/transcoder.ts +++ b/server/tests/api/transcoding/transcoder.ts @@ -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 diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts index de6ba4f82..cd2f07e4a 100644 --- a/server/tests/helpers/core-utils.ts +++ b/server/tests/helpers/core-utils.ts @@ -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) } }) }) diff --git a/server/tests/peertube-runner/live-transcoding.ts b/server/tests/peertube-runner/live-transcoding.ts index 1e94eabcd..31716d545 100644 --- a/server/tests/peertube-runner/live-transcoding.ts +++ b/server/tests/peertube-runner/live-transcoding.ts @@ -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' }) }) diff --git a/server/tests/peertube-runner/studio-transcoding.ts b/server/tests/peertube-runner/studio-transcoding.ts index cca905e2f..204836c4d 100644 --- a/server/tests/peertube-runner/studio-transcoding.ts +++ b/server/tests/peertube-runner/studio-transcoding.ts @@ -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' }) }) diff --git a/server/tests/shared/generate.ts b/server/tests/shared/generate.ts index b0c8dba66..3788b049f 100644 --- a/server/tests/shared/generate.ts +++ b/server/tests/shared/generate.ts @@ -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) } diff --git a/shared/core-utils/videos/bitrate.ts b/shared/core-utils/videos/bitrate.ts index 30d22df09..6be027826 100644 --- a/shared/core-utils/videos/bitrate.ts +++ b/shared/core-utils/videos/bitrate.ts @@ -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 } // --------------------------------------------------------------------------- diff --git a/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts index f7fc49465..8a3f32011 100644 --- a/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts +++ b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts @@ -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 { + 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 { + 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) } -- 2.41.0