generated from tailored/router-template
add funasr demo
This commit is contained in:
144
src/recorder/index.ts
Normal file
144
src/recorder/index.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import assert from 'assert';
|
||||
import { logDebug, logInfo } from '../logger/index.ts';
|
||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
||||
import recorders from './recorders/index.ts';
|
||||
import Stream from 'stream';
|
||||
|
||||
export type RecordingOptions = {
|
||||
/* 采样率,默认为16000 */
|
||||
sampleRate?: number;
|
||||
/* 声道数,默认为1 */
|
||||
channels?: number;
|
||||
/* 是否压缩音频,默认为false */
|
||||
compress?: boolean;
|
||||
/* 录音开始的音量阈值,默认为0.5 */
|
||||
threshold?: number;
|
||||
/* 开始录音的音量阈值 */
|
||||
thresholdStart?: number;
|
||||
/* 结束录音的音量阈值 */
|
||||
thresholdEnd?: number;
|
||||
/* 录音结束的静默时间,默认为'1.0'秒 */
|
||||
silence?: string;
|
||||
/* 使用的录音器,默认为'sox' */
|
||||
recorder?: string;
|
||||
/* 是否在静默时结束录音,默认为false */
|
||||
endOnSilence?: boolean;
|
||||
/* 音频类型,默认为'wav' */
|
||||
audioType?: string;
|
||||
};
|
||||
/**
|
||||
* node-record-lpcm16
|
||||
* https://github.com/gillesdemey/node-record-lpcm16
|
||||
*/
|
||||
export class Recording {
|
||||
options: RecordingOptions;
|
||||
cmd: string;
|
||||
args: string[];
|
||||
cmdOptions: any;
|
||||
process: ChildProcessWithoutNullStreams;
|
||||
_stream: Stream.Readable;
|
||||
|
||||
constructor(options?: RecordingOptions) {
|
||||
const defaults = {
|
||||
sampleRate: 16000,
|
||||
channels: 1,
|
||||
compress: false,
|
||||
threshold: 0.5,
|
||||
thresholdStart: null,
|
||||
thresholdEnd: null,
|
||||
silence: '1.0',
|
||||
recorder: 'sox',
|
||||
endOnSilence: false,
|
||||
audioType: 'wav',
|
||||
};
|
||||
|
||||
this.options = Object.assign(defaults, options);
|
||||
|
||||
const recorder = recorders[this.options.recorder];
|
||||
if (!recorder) {
|
||||
throw new Error(`No such recorder found: ${this.options.recorder}`);
|
||||
}
|
||||
const { cmd, args, spawnOptions = {} } = recorder(this.options);
|
||||
|
||||
this.cmd = cmd;
|
||||
this.args = args;
|
||||
this.cmdOptions = Object.assign({ encoding: 'binary', stdio: 'pipe' }, spawnOptions);
|
||||
|
||||
logDebug(`Started recording`);
|
||||
logDebug('options', this.options);
|
||||
logDebug(` ${this.cmd} ${this.args.join(' ')}`);
|
||||
|
||||
return this.start();
|
||||
}
|
||||
|
||||
start() {
|
||||
const { cmd, args, cmdOptions } = this;
|
||||
|
||||
const cp = spawn(cmd, args, cmdOptions);
|
||||
const rec = cp.stdout;
|
||||
const err = cp.stderr;
|
||||
|
||||
this.process = cp; // expose child process
|
||||
this._stream = rec; // expose output stream
|
||||
|
||||
cp.on('close', (code) => {
|
||||
if (code === 0) return;
|
||||
rec.emit(
|
||||
'error',
|
||||
`${this.cmd} has exited with error code ${code}.
|
||||
|
||||
Enable debugging with the environment variable DEBUG=record.`,
|
||||
);
|
||||
});
|
||||
|
||||
err.on('data', (chunk) => {
|
||||
logDebug(`STDERR: ${chunk}`);
|
||||
});
|
||||
|
||||
rec.on('data', (chunk) => {
|
||||
logDebug(`Recording ${chunk.length} bytes`);
|
||||
});
|
||||
|
||||
rec.on('end', () => {
|
||||
logDebug('Recording ended');
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
assert(this.process, 'Recording not yet started');
|
||||
|
||||
this.process.kill();
|
||||
}
|
||||
|
||||
pause() {
|
||||
assert(this.process, 'Recording not yet started');
|
||||
|
||||
this.process.kill('SIGSTOP');
|
||||
this._stream.pause();
|
||||
logDebug('Paused recording');
|
||||
}
|
||||
|
||||
resume() {
|
||||
assert(this.process, 'Recording not yet started');
|
||||
|
||||
this.process.kill('SIGCONT');
|
||||
this._stream.resume();
|
||||
logDebug('Resumed recording');
|
||||
}
|
||||
|
||||
isPaused() {
|
||||
assert(this.process, 'Recording not yet started');
|
||||
|
||||
return this._stream.isPaused();
|
||||
}
|
||||
|
||||
stream() {
|
||||
assert(this._stream, 'Recording not yet started');
|
||||
|
||||
return this._stream;
|
||||
}
|
||||
}
|
||||
|
||||
export const record = (...args) => new Recording(...args);
|
||||
23
src/recorder/recorders/arecord.ts
Normal file
23
src/recorder/recorders/arecord.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// On some systems (RasPi), arecord is the prefered recording binary
|
||||
export default (options: any) => {
|
||||
const cmd = 'arecord';
|
||||
|
||||
const args = [
|
||||
'-q', // show no progress
|
||||
'-r',
|
||||
options.sampleRate, // sample rate
|
||||
'-c',
|
||||
options.channels, // channels
|
||||
'-t',
|
||||
options.audioType, // audio type
|
||||
'-f',
|
||||
'S16_LE', // Sample format
|
||||
'-', // pipe
|
||||
];
|
||||
|
||||
if (options.device) {
|
||||
args.unshift('-D', options.device);
|
||||
}
|
||||
|
||||
return { cmd, args };
|
||||
};
|
||||
11
src/recorder/recorders/index.ts
Normal file
11
src/recorder/recorders/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import path from 'path';
|
||||
|
||||
import sox from './sox.ts';
|
||||
import rec from './rec.ts';
|
||||
import arecord from './arecord.ts';
|
||||
|
||||
export default {
|
||||
sox,
|
||||
rec,
|
||||
arecord,
|
||||
};
|
||||
32
src/recorder/recorders/rec.ts
Normal file
32
src/recorder/recorders/rec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default (options: any) => {
|
||||
const cmd = 'rec';
|
||||
|
||||
let args = [
|
||||
'-q', // show no progress
|
||||
'-r',
|
||||
options.sampleRate, // sample rate
|
||||
'-c',
|
||||
options.channels, // channels
|
||||
'-e',
|
||||
'signed-integer', // sample encoding
|
||||
'-b',
|
||||
'16', // precision (bits)
|
||||
'-t',
|
||||
options.audioType, // audio type
|
||||
'-', // pipe
|
||||
];
|
||||
|
||||
if (options.endOnSilence) {
|
||||
args = args.concat([
|
||||
'silence',
|
||||
'1',
|
||||
'0.1',
|
||||
options.thresholdStart || options.threshold + '%',
|
||||
'1',
|
||||
options.silence,
|
||||
options.thresholdEnd || options.threshold + '%',
|
||||
]);
|
||||
}
|
||||
|
||||
return { cmd, args };
|
||||
};
|
||||
39
src/recorder/recorders/sox.ts
Normal file
39
src/recorder/recorders/sox.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export default (options: any) => {
|
||||
const cmd = 'sox';
|
||||
|
||||
let args = [
|
||||
'--default-device',
|
||||
'--no-show-progress', // show no progress
|
||||
'--rate',
|
||||
options.sampleRate, // sample rate
|
||||
'--channels',
|
||||
options.channels, // channels
|
||||
'--encoding',
|
||||
'signed-integer', // sample encoding
|
||||
'--bits',
|
||||
'16', // precision (bits)
|
||||
'--type',
|
||||
options.audioType, // audio type
|
||||
'-', // pipe
|
||||
];
|
||||
|
||||
if (options.endOnSilence) {
|
||||
args = args.concat([
|
||||
'silence',
|
||||
'1',
|
||||
'0.1',
|
||||
options.thresholdStart || options.threshold + '%',
|
||||
'1',
|
||||
options.silence,
|
||||
options.thresholdEnd || options.threshold + '%',
|
||||
]);
|
||||
}
|
||||
|
||||
const spawnOptions: any = {};
|
||||
|
||||
if (options.device) {
|
||||
spawnOptions.env = { ...process.env, AUDIODEV: options.device };
|
||||
}
|
||||
|
||||
return { cmd, args, spawnOptions };
|
||||
};
|
||||
Reference in New Issue
Block a user