Commit 853a9784 authored by martin hou's avatar martin hou

fix: 增加双声道合并为单声道的实现

parent 458a8c08
...@@ -461,27 +461,47 @@ export function Home() { ...@@ -461,27 +461,47 @@ export function Home() {
Logger.info('jensen', 'bluetooth', 'Clear Paired Devices: ' + JSON.stringify(rst)); Logger.info('jensen', 'bluetooth', 'Clear Paired Devices: ' + JSON.stringify(rst));
} }
const rms = (u8: Uint8Array) => { /**
* 计算双声道PCM数据的RMS和单声道PCM数据
* @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)];
let sumL = 0; let sumL = 0;
let sumR = 0; let sumR = 0;
const frameCount = (u8.length - 8) / 4; const mono8 = new Int8Array(frameCount);
for (let i = 8; i < u8.length; i+=4) { let j = 0;
for (let i = 8; i + 3 < u8.length; i += 4, j++) {
const a = u8[i + 0] & 0xff; const a = u8[i + 0] & 0xff;
const b = u8[i + 1] & 0xff; const b = u8[i + 1] & 0xff;
const c = u8[i + 2] & 0xff; const c = u8[i + 2] & 0xff;
const d = u8[i + 3] & 0xff; const d = u8[i + 3] & 0xff;
let l = (b << 8 | a) & 0xffff; // 小端 16-bit
let r = (d << 8 | c) & 0xffff; let l = (b << 8) | a;
if (l & 0x8000) l = (l & 0x7fff) - 0x8000; let r = (d << 8) | c;
if (r & 0x8000) r = (r & 0x7fff) - 0x8000; // 还原为有符号 16-bit
// console.log('live, l', l, 'r', r); if (l & 0x8000) l = l - 0x10000;
if (r & 0x8000) r = r - 0x10000;
const nl = l / 32768; const nl = l / 32768;
const nr = r / 32768; const nr = r / 32768;
sumL += nl * nl; sumL += nl * nl;
sumR += nr * nr; 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;
} }
// console.log('live, sumL', sumL, 'sumR', sumR); const leftRms = Math.sqrt(sumL / frameCount);
return [Math.sqrt(sumL / frameCount), Math.sqrt(sumR / frameCount)]; const rightRms = Math.sqrt(sumR / frameCount);
return [leftRms, rightRms, mono8];
} }
const blocksRef = useRef<Uint8Array[]>([]); const blocksRef = useRef<Uint8Array[]>([]);
...@@ -493,6 +513,7 @@ export function Home() { ...@@ -493,6 +513,7 @@ export function Home() {
if (live?.data == null) return; if (live?.data == null) return;
setLiveStates('live: ' + live.data.length + ' rest: ' + live.rest + ' muted: ' + live.muted); 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))
blocksRef.current.push(live.data.slice(8));
const [rms1, rms2] = rms(live.data); const [rms1, rms2] = rms(live.data);
// console.log('live, rms1', rms1, 'rms2', rms2); // console.log('live, rms1', rms1, 'rms2', rms2);
setRmsLeft(Math.floor(rms1 * 4 * 400)); setRmsLeft(Math.floor(rms1 * 4 * 400));
...@@ -525,6 +546,45 @@ export function Home() { ...@@ -525,6 +546,45 @@ export function Home() {
} }
console.log('live stopped'); console.log('live stopped');
// 将blocks拼接成一个Uint8Array,并下载到本地 // 将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);
let offsetMono = 0;
for (const arr of monoChunks) {
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 totalLen = blocksRef.current.reduce((sum, b) => sum + b.length, 0);
const data = new Uint8Array(totalLen); const data = new Uint8Array(totalLen);
let offset = 0; let offset = 0;
...@@ -533,7 +593,6 @@ export function Home() { ...@@ -533,7 +593,6 @@ export function Home() {
offset += b.length; offset += b.length;
} }
// 清空缓存 // 清空缓存
/*
blocksRef.current = []; blocksRef.current = [];
let blob = new Blob([data], { type: 'audio/pcm' }); let blob = new Blob([data], { type: 'audio/pcm' });
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
......
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