初始化项目
This commit is contained in:
80
utils/audioUtils.ts
Normal file
80
utils/audioUtils.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
export const base64ToUint8Array = (base64: string) => {
|
||||
const binaryString = atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export const uint8ArrayToBase64 = (bytes: Uint8Array) => {
|
||||
let binary = '';
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
};
|
||||
|
||||
export const triggerDownload = (blob: Blob, filename: string) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export const createWavFileFromPcm = (pcmData: Uint8Array, sampleRate: number = 24000, numChannels: number = 1): Blob => {
|
||||
const header = new ArrayBuffer(44);
|
||||
const view = new DataView(header);
|
||||
|
||||
const writeString = (view: DataView, offset: number, string: string) => {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
writeString(view, 0, 'RIFF');
|
||||
view.setUint32(4, 36 + pcmData.length, true);
|
||||
writeString(view, 8, 'WAVE');
|
||||
writeString(view, 12, 'fmt ');
|
||||
view.setUint32(16, 16, true);
|
||||
view.setUint16(20, 1, true);
|
||||
view.setUint16(22, numChannels, true);
|
||||
view.setUint32(24, sampleRate, true);
|
||||
view.setUint32(28, sampleRate * numChannels * 2, true);
|
||||
view.setUint16(32, numChannels * 2, true);
|
||||
view.setUint16(34, 16, true);
|
||||
writeString(view, 36, 'data');
|
||||
view.setUint32(40, pcmData.length, true);
|
||||
|
||||
return new Blob([view, pcmData], { type: 'audio/wav' });
|
||||
};
|
||||
|
||||
export const processAndDownloadAudio = (base64Data: string, filename: string) => {
|
||||
try {
|
||||
// Check for RIFF header (WAV)
|
||||
// Some base64 strings might have newlines, strip them if necessary,
|
||||
// but generally atob handles it or we assume clean base64.
|
||||
const binaryString = atob(base64Data.substring(0, 50).replace(/\s/g, ''));
|
||||
const isWav = binaryString.startsWith('RIFF');
|
||||
|
||||
if (isWav) {
|
||||
const bytes = base64ToUint8Array(base64Data);
|
||||
const blob = new Blob([bytes], { type: 'audio/wav' });
|
||||
triggerDownload(blob, filename);
|
||||
} else {
|
||||
// Assume Raw PCM 24kHz 16-bit Mono (Gemini Flash TTS default)
|
||||
const bytes = base64ToUint8Array(base64Data);
|
||||
const blob = createWavFileFromPcm(bytes, 24000, 1);
|
||||
triggerDownload(blob, filename);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error downloading audio", e);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user