my网站域名,镇江网站推广,丰台网站制作,兰州网站设计厂家需求
开始录音——监听录音数据——结束录音
在监听录音数据过程中#xff1a;客户端每100ms给前端传输一次数据#xff08;pcm数据转成base64#xff09;#xff0c;前端需要将base64片段解码、合并、添加WAV头、转成File、上传到 OSS之后将 url 给到服务端处理。
{num…需求
开始录音——监听录音数据——结束录音
在监听录音数据过程中客户端每100ms给前端传输一次数据pcm数据转成base64前端需要将base64片段解码、合并、添加WAV头、转成File、上传到 OSS之后将 url 给到服务端处理。
{numberOfChannels: 1, // 声道数// sampleRate: 16000, // 采样率sampleRate: 44100, // 更改采样率为 44100 HzbitsPerChannel: 16, // 位深format: PCM,
}概念
pcm是原始音频mac上可以使用audacity软件播放pcm原始音频文件 base64编码将二进制编码成文本格式 atob 将二进制转为 unicode 字符序列charCodeAt 获取每个字符的unicode编码 Uint8Array 是包含8位一个字节的无符号整数序列用于处理二进制数据 ArrayBuffer 在内存中分配一段连续的空间存储二进制数据如数字、图像、音频文件等 new Blob([wavHeader, pcmData], { type: ‘audio/wav’ }); 给PCM数据添加wav头信息 Blob 是浏览器内部生成的二进制数据包括数据和类型信息 File 是 Blob 的子类除了数据和类型信息还包括文件名和最后修改时间通常表示用户从本地文件系统选择的文件
将base64片段转为WAV文件
/*** 将base64片段转为WAV文件* param base64Segments* returns*/
export function base64ToAudio(base64Segments) {// 合并PCM数据const pcmData mergeBase64SegmentsIntoPCM(base64Segments);// 创建WAV头const dataLength pcmData.length;const wavHeader createWavHeader(dataLength, 44100);// 合并WAV文件头和PCM数据const blob new Blob([wavHeader, pcmData], { type: audio/wav });const file new File([blob], output.wav, { type: audio/wav });return file;
}将一系列Base64编码的音频段合并成一个PCM数据流
/*** 将一系列Base64编码的音频段合并成一个PCM数据流* param segments 包含Base64编码音频段的数组* returns*/
function mergeBase64SegmentsIntoPCM(segments) {let mergedData new Uint8Array();segments.forEach((base64Segment) {const binarySegment atob(base64Segment);const binaryArray new Uint8Array(binarySegment.length);for (let i 0; i binarySegment.length; i) {binaryArray[i] binarySegment.charCodeAt(i);}mergedData mergeArrays(mergedData, binaryArray);});// 合并后的PCM数据return mergedData;
}合并两个TypedArray类型化数组 /*** 合并两个TypedArray类型化数组* param segments* returns*/
function mergeArrays(a, b) {// 类型化数组确保类型一致const c new a.constructor(a.length b.length);// 类型化数组的set方法直接在底层内存中操作不需要逐个元素拷贝效率高c.set(a, 0);// 保障合并后的数组在内存中是连续的提高访问速度c.set(b, a.length);return c;
}创建一个WAV文件的头部信息
/*** 创建一个WAV文件的头部信息* 包含了RIFF格式标识、文件大小、WAVE标识、格式子块fmt的ID和大小、音频格式、* 声道数、采样率、字节率、块对齐、每样本位数以及数据子块data的ID和大小* param dataSize 文件大小* param sampleRate 采样率* returns*/
function createWavHeader(dataSize, sampleRate) {// 创建一个大小为44字节的ArrayBuffer用于存储WAV文件头const buffer new ArrayBuffer(44);// 创建一个DataView用于操作buffer中的数据const view new DataView(buffer);view.setUint32(0, 0x52494646, false); // 设置Chunk ID为RIFFview.setUint32(4, dataSize 36, true); // 设置文件大小不包括前8个字节view.setUint32(8, 0x57415645, false); // 设置格式标识为WAVEview.setUint32(12, 0x666d7420, false); // 设置第一个子块ID为fmt view.setUint32(16, 16, true); // 设置第一个子块大小为16字节view.setUint16(20, 1, true); // 设置音频格式为PCM1表示PCMview.setUint16(22, 1, true); // 设置声道数单声道为1view.setUint32(24, sampleRate, true); // 设置采样率view.setUint32(28, sampleRate * 2, true); // 设置字节率采样率 * 每帧字节数view.setUint16(32, 2, true); // 设置每帧字节数块对齐view.setUint16(34, 16, true); // 设置每样本位数view.setUint32(36, 0x64617461, false); // 设置第二个子块ID为dataview.setUint32(40, dataSize, true); // 设置第二个子块大小即音频数据大小// 返回填充了WAV文件头信息的bufferreturn buffer;
}
异步获取音频文件的时长
/*** 异步获取音频文件的时长* param file 音频文件* returns 返回音频的时长秒*/
export const getAudioDuration async (file) {try {const audio new Audio(URL.createObjectURL(file));await new Promise((resolve) (audio.onloadedmetadata resolve));const { duration } audio;return duration;} catch (error) {console.error(获取音频时长时发生错误:, error);return 0;}
};将文件上传到oss
export const uploadFile (data: UploadTokenData, file: File) {console.log(uploadFile开始了, data, , file);const bodyFormData new FormData();const url ${data.host}/${data.dir}${file.name};bodyFormData.append(OSSAccessKeyId, data.accessId);bodyFormData.append(policy, data.policy);bodyFormData.append(signature, data.signature);bodyFormData.append(key, ${data.dir}${file.name});bodyFormData.append(dir, data.dir);bodyFormData.append(success_action_status, 200);bodyFormData.append(file, file);console.log(uploadFile上传的url: , url);return new Promise((resolve, reject) {const xhr new XMLHttpRequest();xhr.onerror function error(e) {console.log(upload error, e);reject(e);};xhr.onload async () {// allow success when 2xx status see https://github.com/react-component/upload/issues/34if (xhr.status 200 || xhr.status 300) {reject(上传异常);}console.log(upload success);resolve({...data,ossUrl: url,});};xhr.open(post, data.host, true);xhr.setRequestHeader(X-Requested-With, XMLHttpRequest);xhr.send(bodyFormData);});
};