Commit 34e1e94a authored by martin hou's avatar martin hou

fix: 降采样到16k

parent 97d374b0
......@@ -484,15 +484,20 @@ export function Home() {
* @param u8 - 双声道PCM数据
* @returns - 左声道RMS,右声道RMS,单声道PCM数据
*/
const rms = (u8: Uint8Array): [number, number, Int8Array] => {
if (!u8 || u8.byteLength <= 8) return [0, 0, new Int8Array(0)];
const frameCount = Math.floor((u8.length - 8) / 4); // 每帧: L(2B)+R(2B)
if (frameCount <= 0) return [0, 0, new Int8Array(0)];
const rms = (u8: Uint8Array): [number, number, Uint8Array] => {
if (!u8 || u8.byteLength <= 8) return [0, 0, new Uint8Array(0)];
// 每帧: L(2B)+R(2B),总帧数(48k立体声)
const totalFrames = Math.floor((u8.length - 8) / 4);
if (totalFrames <= 0) return [0, 0, new Uint8Array(0)];
// 48k -> 16k,降采样因子为3
const downFactor = 3;
const outFrames = Math.floor((totalFrames + downFactor - 1) / downFactor) * 2; // 向上取整
let sumL = 0;
let sumR = 0;
const mono8 = new Int8Array(frameCount);
let j = 0;
for (let i = 8; i + 3 < u8.length; i += 4, j++) {
const mono8 = new Uint8Array(outFrames);
let j = 0, k = 0;
for (let i = 8; i + 3 < u8.length; i += 4, j++)
{
const a = u8[i + 0] & 0xff;
const b = u8[i + 1] & 0xff;
const c = u8[i + 2] & 0xff;
......@@ -509,16 +514,20 @@ export function Home() {
sumR += nr * nr;
// 双声道合成为单声道(16-bit),再压缩为 Int8
// C = A + B - (A * B / 32767)
let m = l + r - Math.floor((l * r) / 32767);
if (m > 32767) m = 32767;
else if (m < -32768) m = -32768;
let m8 = m >> 8; // 16-bit 转 8-bit(有符号算术右移)
if (m8 > 127) m8 = 127;
else if (m8 < -128) m8 = -128;
mono8[j] = m8;
if (j % downFactor == 0)
{
let m = l + r - Math.floor((l * r) / 32767);
if (m > 32767) m = 32767;
else if (m < -32768) m = -32768;
let H = (m >> 8) & 0xff;
let L = m & 0xff;
mono8[k++] = (L << 8);
mono8[k++] = H;
}
}
const leftRms = Math.sqrt(sumL / frameCount);
const rightRms = Math.sqrt(sumR / frameCount);
const leftRms = Math.sqrt(sumL / totalFrames);
const rightRms = Math.sqrt(sumR / totalFrames);
// 返回实际长度的视图,避免尾部未填充元素
return [leftRms, rightRms, mono8];
}
......@@ -531,8 +540,10 @@ export function Home() {
if (live?.data == null) return;
setLiveStates('live: ' + live.data.length + ' rest: ' + live.rest + ' muted: ' + live.muted);
// 仅计算,不保留引用;如需录音导出再使用 blocksRef.current.push(live.data.slice(8))
blocksRef.current.push(live.data.slice(8));
const [rms1, rms2] = rms(live.data);
// blocksRef.current.push(live.data.slice(8));
const [rms1, rms2, mono16k] = rms(live.data);
// 基于切片内容复制,避免使用 .buffer 造成长度/偏移误用
blocksRef.current.push(new Uint8Array(mono16k));
// console.log('live, rms1', rms1, 'rms2', rms2);
setRmsLeft(Math.floor(rms1 * 4 * 400));
setRmsRight(Math.floor(rms2 * 4 * 400));
......@@ -550,7 +561,7 @@ export function Home() {
window.clearTimeout(liveTimeoutRef.current);
}
// 立即触发一次,并在回调末尾调度下一次
scheduleLiveTick();
liveTimeoutRef.current = window.setTimeout(scheduleLiveTick, 100);
}
const stopLive = async () => {
......@@ -563,63 +574,22 @@ export function Home() {
liveTimeoutRef.current = null;
}
console.log('live stopped');
// 将blocks拼接成一个Uint8Array,并下载到本地
// 将blocksRef.current的双声道PCM合并为单声道
// 每个块都是(interleaved) 2通道,每个通道16bit(2字节),小端
// 合并算法: C = A + B - (A * B / 32767)
let monoChunks: Int16Array[] = [];
for (const block of blocksRef.current) {
// 每帧4字节: L(2字节) R(2字节)
let view = new DataView(block.buffer, block.byteOffset, block.byteLength);
let sampleCount = block.length / 4;
let mono = new Int16Array(sampleCount);
for (let i = 0; i < sampleCount; ++i) {
let offset = i * 4;
let l = view.getInt16(offset, true);
let r = view.getInt16(offset + 2, true);
// C = A + B - (A * B / 32767)
let c = l + r - Math.floor((l * r) / 32767);
// 限定在16bit
if (c > 32767) c = 32767;
if (c < -32768) c = -32768;
mono[i] = c;
}
monoChunks.push(mono);
}
// 合并所有Int16Array为单一Int16Array
const totalSamples = monoChunks.reduce((sum, arr) => sum + arr.length, 0);
const monoAll = new Int16Array(totalSamples);
// 将blocksRef.current合并为单一Uint8Array
const totalSamples = blocksRef.current.reduce((sum, arr) => sum + arr.length, 0);
const monoAll = new Uint8Array(totalSamples);
let offsetMono = 0;
for (const arr of monoChunks) {
for (const arr of blocksRef.current) {
monoAll.set(arr, offsetMono);
offsetMono += arr.length;
}
// e.g. 可导出为 .pcm 文件
let monoBlob = new Blob([monoAll.buffer], { type: 'audio/pcm' });
let monoUrl = URL.createObjectURL(monoBlob);
let monoA = document.createElement('a');
monoA.href = monoUrl;
monoA.download = 'live_mono.pcm';
monoA.click();
URL.revokeObjectURL(monoUrl);
/*
const totalLen = blocksRef.current.reduce((sum, b) => sum + b.length, 0);
const data = new Uint8Array(totalLen);
let offset = 0;
for (const b of blocksRef.current) {
data.set(b, offset);
offset += b.length;
}
// 清空缓存
blocksRef.current = [];
let blob = new Blob([data], { type: 'audio/pcm' });
let blob = new Blob([monoAll.buffer], { type: 'audio/pcm' });
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'live.pcm';
a.click();
URL.revokeObjectURL(url);
*/
}
return (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment