perf: avoid silk encoding blocking the main event loop (#527)

This commit is contained in:
pk5ls20 2024-11-15 20:39:18 +08:00 committed by GitHub
parent 74b4d9bf49
commit e44595334a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 16 deletions

View File

@ -48,9 +48,10 @@
},
"dependencies": {
"express": "^5.0.0",
"fluent-ffmpeg": "^2.1.2",
"qrcode-terminal": "^0.12.0",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0",
"qrcode-terminal": "^0.12.0",
"fluent-ffmpeg": "^2.1.2"
"piscina": "^4.7.0"
}
}
}

View File

@ -0,0 +1,9 @@
import { encode } from "silk-wasm";
export interface EncodeArgs {
input: ArrayBufferView | ArrayBuffer
sampleRate: number
}
export default async ({ input, sampleRate }: EncodeArgs) => {
return await encode(input, sampleRate);
};

View File

@ -1,13 +1,19 @@
import Piscina from 'piscina';
import fsPromise from 'fs/promises';
import path from 'node:path';
import { randomUUID } from 'crypto';
import { spawn } from 'node:child_process';
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
import { LogWrapper } from './log';
import { EncodeArgs } from "@/common/audio-worker";
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
const EXIT_CODES = [0, 255];
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
const FFMPEG_PATH = process.env.FFMPEG_PATH ?? 'ffmpeg';
const piscina = new Piscina<EncodeArgs, EncodeResult>({
filename: new URL('./audio-worker.mjs', import.meta.url).href,
});
async function guessDuration(pttPath: string, logger: LogWrapper) {
const pttFileInfo = await fsPromise.stat(pttPath);
@ -41,8 +47,11 @@ async function convert(filePath: string, pcmPath: string, logger: LogWrapper): P
}
async function handleWavFile(
file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper
): Promise<{input: Buffer, sampleRate: number}> {
file: Buffer,
filePath: string,
pcmPath: string,
logger: LogWrapper
): Promise<{ input: Buffer; sampleRate: number }> {
const { fmt } = getWavFileInfo(file);
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
@ -60,8 +69,8 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
const { input, sampleRate } = isWav(file)
? (await handleWavFile(file, filePath, pcmPath, logger))
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
const silk = await encode(input, sampleRate);
await fsPromise.writeFile(pttPath, silk.data);
const silk = await piscina.run({ input: input, sampleRate: sampleRate });
await fsPromise.writeFile(pttPath, Buffer.from(silk.data));
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
return {
converted: true,
@ -86,4 +95,4 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
logger.logError.bind(logger)('convert silk failed', error.stack);
return {};
}
}
}

View File

@ -3,12 +3,14 @@ import { defineConfig, PluginOption, UserConfig } from 'vite';
import { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import { builtinModules } from 'module';
//依赖排除
const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal', 'fluent-ffmpeg'];
const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal', 'fluent-ffmpeg', 'piscina'];
const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat();
function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };
}
let startScripts: string[] | undefined = undefined;
if (process.env.NAPCAT_BUILDSYS == 'linux') {
startScripts = [];
@ -17,6 +19,7 @@ if (process.env.NAPCAT_BUILDSYS == 'linux') {
} else {
startScripts = ['./script/KillQQ.bat'];
}
const FrameworkBaseConfigPlugin: PluginOption[] = [
cp({
targets: [
@ -66,9 +69,12 @@ const ShellBaseConfig = () => defineConfig({
target: 'esnext',
minify: false,
lib: {
entry: 'src/shell/napcat.ts',
entry: {
'napcat': 'src/shell/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
},
formats: ['es'],
fileName: () => 'napcat.mjs',
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
@ -90,9 +96,12 @@ const FrameworkBaseConfig = () => defineConfig({
target: 'esnext',
minify: false,
lib: {
entry: 'src/framework/napcat.ts',
entry: {
'napcat': 'src/framework/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
},
formats: ['es'],
fileName: () => 'napcat.mjs',
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],