mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-18 17:48:52 +08:00
201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
import { logger } from '@/core/common/logger';
|
||
import { OggVorbisDecoderWebWorker } from '@wasm-audio-decoders/ogg-vorbis';
|
||
import { OggOpusDecoderWebWorker } from 'ogg-opus-decoder';
|
||
import { AudioType, isAudioSupport } from './support';
|
||
import type { AudioPlayer } from './player';
|
||
|
||
const fileSignatures: [AudioType, number[]][] = [
|
||
[AudioType.Mp3, [0x49, 0x44, 0x33]],
|
||
[AudioType.Ogg, [0x4f, 0x67, 0x67, 0x53]],
|
||
[AudioType.Wav, [52, 0x49, 0x46, 0x46]],
|
||
[AudioType.Flac, [0x66, 0x4c, 0x61, 0x43]],
|
||
[AudioType.Aac, [0xff, 0xf1]],
|
||
[AudioType.Aac, [0xff, 0xf9]]
|
||
];
|
||
const oggHeaders: [AudioType, number[]][] = [
|
||
[AudioType.Opus, [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64]]
|
||
];
|
||
|
||
export function checkAudioType(data: Uint8Array) {
|
||
let audioType: AudioType | '' = '';
|
||
// 检查头文件获取音频类型,仅检查前256个字节
|
||
const toCheck = data.slice(0, 256);
|
||
for (const [type, value] of fileSignatures) {
|
||
if (value.every((v, i) => toCheck[i] === v)) {
|
||
audioType = type;
|
||
break;
|
||
}
|
||
}
|
||
if (audioType === AudioType.Ogg) {
|
||
// 如果是ogg的话,进一步判断是不是opus
|
||
for (const [key, value] of oggHeaders) {
|
||
const has = toCheck.some((_, i) => {
|
||
return value.every((v, ii) => toCheck[i + ii] === v);
|
||
});
|
||
if (has) {
|
||
audioType = key;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return audioType;
|
||
}
|
||
|
||
export interface IAudioDecodeError {
|
||
/** 错误信息 */
|
||
message: string;
|
||
}
|
||
|
||
export interface IAudioDecodeData {
|
||
/** 每个声道的音频信息 */
|
||
channelData: Float32Array[];
|
||
/** 已经被解码的 PCM 采样数 */
|
||
samplesDecoded: number;
|
||
/** 音频采样率 */
|
||
sampleRate: number;
|
||
/** 解码错误信息 */
|
||
errors: IAudioDecodeError[];
|
||
}
|
||
|
||
export abstract class AudioDecoder {
|
||
static readonly decoderMap: Map<AudioType, new () => AudioDecoder> =
|
||
new Map();
|
||
|
||
/**
|
||
* 注册一个解码器
|
||
* @param type 要注册的解码器允许解码的类型
|
||
* @param decoder 解码器对象
|
||
*/
|
||
static registerDecoder(type: AudioType, decoder: new () => AudioDecoder) {
|
||
if (this.decoderMap.has(type)) {
|
||
logger.warn(47, type);
|
||
return;
|
||
}
|
||
this.decoderMap.set(type, decoder);
|
||
}
|
||
|
||
/**
|
||
* 解码音频数据
|
||
* @param data 音频文件数据
|
||
* @param player AudioPlayer实例
|
||
*/
|
||
static async decodeAudioData(data: Uint8Array, player: AudioPlayer) {
|
||
// 检查头文件获取音频类型,仅检查前256个字节
|
||
const toCheck = data.slice(0, 256);
|
||
const type = checkAudioType(data);
|
||
if (type === '') {
|
||
logger.error(
|
||
25,
|
||
[...toCheck]
|
||
.map(v => v.toString().padStart(2, '0'))
|
||
.join(' ')
|
||
.toUpperCase()
|
||
);
|
||
return null;
|
||
}
|
||
if (isAudioSupport(type)) {
|
||
if (data.buffer instanceof ArrayBuffer) {
|
||
return player.ac.decodeAudioData(data.buffer);
|
||
} else {
|
||
return null;
|
||
}
|
||
} else {
|
||
const Decoder = this.decoderMap.get(type);
|
||
if (!Decoder) {
|
||
return null;
|
||
} else {
|
||
const decoder = new Decoder();
|
||
await decoder.create();
|
||
const decodedData = await decoder.decode(data);
|
||
if (!decodedData) return null;
|
||
const buffer = player.ac.createBuffer(
|
||
decodedData.channelData.length,
|
||
decodedData.channelData[0].length,
|
||
decodedData.sampleRate
|
||
);
|
||
decodedData.channelData.forEach((v, i) => {
|
||
buffer.copyToChannel(v, i);
|
||
});
|
||
return buffer;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建音频解码器
|
||
*/
|
||
abstract create(): Promise<void>;
|
||
|
||
/**
|
||
* 摧毁这个解码器
|
||
*/
|
||
abstract destroy(): void;
|
||
|
||
/**
|
||
* 解码流数据
|
||
* @param data 流数据
|
||
*/
|
||
abstract decode(data: Uint8Array): Promise<IAudioDecodeData | undefined>;
|
||
|
||
/**
|
||
* 解码整个文件
|
||
* @param data 文件数据
|
||
*/
|
||
abstract decodeAll(data: Uint8Array): Promise<IAudioDecodeData | undefined>;
|
||
|
||
/**
|
||
* 当音频解码完成后,会调用此函数,需要返回之前还未解析或未返回的音频数据。调用后,该解码器将不会被再次使用
|
||
*/
|
||
abstract flush(): Promise<IAudioDecodeData | undefined>;
|
||
}
|
||
|
||
export class VorbisDecoder implements AudioDecoder {
|
||
decoder?: OggVorbisDecoderWebWorker;
|
||
|
||
async create(): Promise<void> {
|
||
this.decoder = new OggVorbisDecoderWebWorker();
|
||
await this.decoder.ready;
|
||
}
|
||
|
||
destroy(): void {
|
||
this.decoder?.free();
|
||
}
|
||
|
||
async decode(data: Uint8Array): Promise<IAudioDecodeData | undefined> {
|
||
return this.decoder?.decode(data);
|
||
}
|
||
|
||
async decodeAll(data: Uint8Array): Promise<IAudioDecodeData | undefined> {
|
||
return this.decoder?.decodeFile(data);
|
||
}
|
||
|
||
async flush(): Promise<IAudioDecodeData | undefined> {
|
||
return this.decoder?.flush();
|
||
}
|
||
}
|
||
|
||
export class OpusDecoder implements AudioDecoder {
|
||
decoder?: OggOpusDecoderWebWorker;
|
||
|
||
async create(): Promise<void> {
|
||
this.decoder = new OggOpusDecoderWebWorker();
|
||
await this.decoder.ready;
|
||
}
|
||
|
||
destroy(): void {
|
||
this.decoder?.free();
|
||
}
|
||
|
||
async decode(data: Uint8Array): Promise<IAudioDecodeData | undefined> {
|
||
return this.decoder?.decode(data);
|
||
}
|
||
|
||
async decodeAll(data: Uint8Array): Promise<IAudioDecodeData | undefined> {
|
||
return this.decoder?.decodeFile(data);
|
||
}
|
||
|
||
async flush(): Promise<IAudioDecodeData | undefined> {
|
||
return await this.decoder?.flush();
|
||
}
|
||
}
|