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(); } }