|
|
@@ -0,0 +1,112 @@
|
|
|
+const mediaFileToWavFile = async (mediaObj, options = {}) => {
|
|
|
+ let _file = null
|
|
|
+ // if (mediaObj.url) {
|
|
|
+ // let res = await getFile(mediaObj.url)
|
|
|
+ // if (res.data === 1) {
|
|
|
+ // console.log('获取媒体文件失败')
|
|
|
+ // return ''
|
|
|
+ // }
|
|
|
+ // _file = new File([res.data], mediaObj.name, { type: mediaObj.type })
|
|
|
+ // } else {
|
|
|
+ _file = mediaObj
|
|
|
+ // }
|
|
|
+
|
|
|
+ // 添加参数配置
|
|
|
+ const { sampleRate = 22050, optimize = false } = options
|
|
|
+
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ try {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = async (e) => {
|
|
|
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
|
+ const buffer = await audioContext.decodeAudioData(e.target.result);
|
|
|
+
|
|
|
+ let targetBuffer = buffer;
|
|
|
+
|
|
|
+ // 如果启用优化或指定采样率
|
|
|
+ if (optimize || sampleRate !== buffer.sampleRate) {
|
|
|
+ const targetSampleRate = Math.min(sampleRate, buffer.sampleRate);
|
|
|
+
|
|
|
+ const offlineAudioContext = new OfflineAudioContext({
|
|
|
+ numberOfChannels: buffer.numberOfChannels,
|
|
|
+ length: Math.floor(buffer.length * targetSampleRate / buffer.sampleRate),
|
|
|
+ sampleRate: targetSampleRate
|
|
|
+ });
|
|
|
+
|
|
|
+ const source = offlineAudioContext.createBufferSource();
|
|
|
+ source.buffer = buffer;
|
|
|
+ source.connect(offlineAudioContext.destination);
|
|
|
+ source.start();
|
|
|
+
|
|
|
+ targetBuffer = await offlineAudioContext.startRendering();
|
|
|
+ }
|
|
|
+
|
|
|
+ const wavBlob = bufferToWav(targetBuffer);
|
|
|
+ const fileName = mediaObj.name ?
|
|
|
+ mediaObj.name.replace(/\.[^/.]+$/, "") + '.wav' :
|
|
|
+ 'audio.wav';
|
|
|
+ const audioFile = new File([wavBlob], fileName, { type: 'audio/wav' });
|
|
|
+
|
|
|
+ console.log(`转换成功,采样率: ${targetBuffer.sampleRate}Hz,文件大小: ${(audioFile.size / 1024 / 1024).toFixed(2)}MB`)
|
|
|
+ resolve(audioFile)
|
|
|
+ }
|
|
|
+ reader.readAsArrayBuffer(_file);
|
|
|
+ } catch (error) {
|
|
|
+ console.log('音频提取失败', error)
|
|
|
+ resolve('')
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function bufferToWav(buffer) {
|
|
|
+ const numChannels = buffer.numberOfChannels;
|
|
|
+ const sampleRate = buffer.sampleRate;
|
|
|
+ const format = 1; // PCM
|
|
|
+ const bitDepth = 16;
|
|
|
+
|
|
|
+ const bytesPerSample = bitDepth / 8;
|
|
|
+ const blockAlign = numChannels * bytesPerSample;
|
|
|
+ const byteRate = sampleRate * blockAlign;
|
|
|
+ const dataSize = buffer.length * blockAlign;
|
|
|
+
|
|
|
+ const bufferLength = 44 + dataSize;
|
|
|
+ const arrayBuffer = new ArrayBuffer(bufferLength);
|
|
|
+ const view = new DataView(arrayBuffer);
|
|
|
+
|
|
|
+ // WAV 头部
|
|
|
+ writeString(view, 0, 'RIFF');
|
|
|
+ view.setUint32(4, bufferLength - 8, true);
|
|
|
+ writeString(view, 8, 'WAVE');
|
|
|
+ writeString(view, 12, 'fmt ');
|
|
|
+ view.setUint32(16, 16, true); // fmt chunk size
|
|
|
+ view.setUint16(20, format, true);
|
|
|
+ view.setUint16(22, numChannels, true);
|
|
|
+ view.setUint32(24, sampleRate, true);
|
|
|
+ view.setUint32(28, byteRate, true);
|
|
|
+ view.setUint16(32, blockAlign, true);
|
|
|
+ view.setUint16(34, bitDepth, true);
|
|
|
+ writeString(view, 36, 'data');
|
|
|
+ view.setUint32(40, dataSize, true);
|
|
|
+
|
|
|
+ // 写入 PCM 数据
|
|
|
+ let offset = 44;
|
|
|
+ for (let i = 0; i < buffer.length; i++) {
|
|
|
+ for (let channel = 0; channel < numChannels; channel++) {
|
|
|
+ const sample = Math.max(-1, Math.min(1, buffer.getChannelData(channel)[i]));
|
|
|
+ view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
|
|
|
+ offset += 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Blob([arrayBuffer], { type: 'audio/wav' });
|
|
|
+}
|
|
|
+
|
|
|
+function writeString(view, offset, string) {
|
|
|
+ for (let i = 0; i < string.length; i++) {
|
|
|
+ view.setUint8(offset + i, string.charCodeAt(i));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export {
|
|
|
+ mediaFileToWavFile
|
|
|
+}
|