[fix](trx-frontend-http): handle stereo audio frame layouts
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -2676,6 +2676,37 @@ function configureRxStream(nextInfo) {
|
|||||||
audioStatus.textContent = "RX";
|
audioStatus.textContent = "RX";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractAudioFrameChannels(frame) {
|
||||||
|
const channels = Math.max(1, frame.numberOfChannels || 1);
|
||||||
|
const frames = Math.max(0, frame.numberOfFrames || 0);
|
||||||
|
const format = String(frame.format || "").toLowerCase();
|
||||||
|
const isPlanar = format.includes("planar");
|
||||||
|
|
||||||
|
if (!isPlanar) {
|
||||||
|
const interleaved = new Float32Array(frames * channels);
|
||||||
|
frame.copyTo(interleaved);
|
||||||
|
const out = Array.from({ length: channels }, () => new Float32Array(frames));
|
||||||
|
for (let i = 0; i < frames; i++) {
|
||||||
|
for (let ch = 0; ch < channels; ch++) {
|
||||||
|
out[ch][i] = interleaved[i * channels + ch];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
for (let ch = 0; ch < channels; ch++) {
|
||||||
|
let len = frames;
|
||||||
|
try {
|
||||||
|
len = Math.max(frames, Math.floor(frame.allocationSize({ planeIndex: ch }) / 4));
|
||||||
|
} catch (e) {}
|
||||||
|
const plane = new Float32Array(len);
|
||||||
|
frame.copyTo(plane, { planeIndex: ch });
|
||||||
|
out.push(plane.length === frames ? plane : plane.subarray(0, frames));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function startRxAudio() {
|
function startRxAudio() {
|
||||||
if (rxActive) { stopRxAudio(); return; }
|
if (rxActive) { stopRxAudio(); return; }
|
||||||
if (!hasWebCodecs) {
|
if (!hasWebCodecs) {
|
||||||
@@ -2721,30 +2752,26 @@ function startRxAudio() {
|
|||||||
const sampleRate = (streamInfo && streamInfo.sample_rate) || 48000;
|
const sampleRate = (streamInfo && streamInfo.sample_rate) || 48000;
|
||||||
opusDecoder = new AudioDecoder({
|
opusDecoder = new AudioDecoder({
|
||||||
output: (frame) => {
|
output: (frame) => {
|
||||||
|
const frameChannels = extractAudioFrameChannels(frame);
|
||||||
const forceMono = frame.numberOfChannels >= 2
|
const forceMono = frame.numberOfChannels >= 2
|
||||||
&& wfmAudioModeEl
|
&& wfmAudioModeEl
|
||||||
&& wfmAudioModeEl.value === "mono"
|
&& wfmAudioModeEl.value === "mono"
|
||||||
&& modeEl
|
&& modeEl
|
||||||
&& (modeEl.value || "").toUpperCase() === "WFM";
|
&& (modeEl.value || "").toUpperCase() === "WFM";
|
||||||
const outChannels = forceMono ? 1 : frame.numberOfChannels;
|
const outChannels = forceMono ? 1 : frameChannels.length;
|
||||||
const ab = audioCtx.createBuffer(outChannels, frame.numberOfFrames, frame.sampleRate);
|
const ab = audioCtx.createBuffer(outChannels, frame.numberOfFrames, frame.sampleRate);
|
||||||
if (forceMono) {
|
if (forceMono) {
|
||||||
// Mix all planes down to mono
|
|
||||||
const monoData = new Float32Array(frame.numberOfFrames);
|
const monoData = new Float32Array(frame.numberOfFrames);
|
||||||
for (let ch = 0; ch < frame.numberOfChannels; ch++) {
|
for (let ch = 0; ch < frameChannels.length; ch++) {
|
||||||
const plane = new Float32Array(frame.numberOfFrames);
|
const plane = frameChannels[ch];
|
||||||
frame.copyTo(plane, { planeIndex: ch });
|
|
||||||
for (let i = 0; i < frame.numberOfFrames; i++) monoData[i] += plane[i];
|
for (let i = 0; i < frame.numberOfFrames; i++) monoData[i] += plane[i];
|
||||||
}
|
}
|
||||||
const inv = 1 / frame.numberOfChannels;
|
const inv = 1 / Math.max(1, frameChannels.length);
|
||||||
for (let i = 0; i < frame.numberOfFrames; i++) monoData[i] *= inv;
|
for (let i = 0; i < frame.numberOfFrames; i++) monoData[i] *= inv;
|
||||||
ab.copyToChannel(monoData, 0);
|
ab.copyToChannel(monoData, 0);
|
||||||
} else {
|
} else {
|
||||||
// Copy each plane directly — AudioData uses planar layout (f32-planar)
|
for (let ch = 0; ch < frameChannels.length; ch++) {
|
||||||
for (let ch = 0; ch < frame.numberOfChannels; ch++) {
|
ab.copyToChannel(frameChannels[ch], ch);
|
||||||
const chData = new Float32Array(frame.numberOfFrames);
|
|
||||||
frame.copyTo(chData, { planeIndex: ch });
|
|
||||||
ab.copyToChannel(chData, ch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const src = audioCtx.createBufferSource();
|
const src = audioCtx.createBufferSource();
|
||||||
|
|||||||
Reference in New Issue
Block a user