Merge pull request #2532 from xinnan-tech/fix-test-page

update:修复测试工具页面声音卡顿问题
This commit is contained in:
欣南科技
2025-11-15 17:54:33 +08:00
committed by GitHub
2 changed files with 41 additions and 33 deletions
+33 -25
View File
@@ -48,8 +48,8 @@ export class StreamingContext {
convertInt16ToFloat32(int16Data) {
const float32Data = new Float32Array(int16Data.length);
for (let i = 0; i < int16Data.length; i++) {
// 将[-32768,32767]范围转换为[-1,1]
float32Data[i] = int16Data[i] / (int16Data[i] < 0 ? 0x8000 : 0x7FFF);
// 将[-32768,32767]范围转换为[-1,1],统一使用32768.0避免不对称失真
float32Data[i] = int16Data[i] / 32768.0;
}
return float32Data;
}
@@ -97,18 +97,26 @@ export class StreamingContext {
// 开始播放音频
async startPlaying() {
let scheduledEndTime = this.audioContext.currentTime; // 跟踪已调度音频的结束时间
while (true) {
// 如果累积了至少0.3秒的音频,开始播放
const minSamples = this.sampleRate * this.minAudioDuration * 3;
// 初始缓冲:等待足够的样本再开始播放
const minSamples = this.sampleRate * this.minAudioDuration * 2;
if (!this.playing && this.queue.length < minSamples) {
await this.getQueue(minSamples);
}
this.playing = true;
while (this.playing && this.queue.length) {
// 创建新的音频缓冲区
const minPlaySamples = Math.min(this.queue.length, this.sampleRate);
const currentSamples = this.queue.splice(0, minPlaySamples);
// 持续播放队列中的音频,每次播放一个小块
while (this.playing && this.queue.length > 0) {
// 每次播放120ms的音频(2个Opus包)
const playDuration = 0.12;
const targetSamples = Math.floor(this.sampleRate * playDuration);
const actualSamples = Math.min(this.queue.length, targetSamples);
if (actualSamples === 0) break;
const currentSamples = this.queue.splice(0, actualSamples);
const audioBuffer = this.audioContext.createBuffer(this.channels, currentSamples.length, this.sampleRate);
audioBuffer.copyToChannel(new Float32Array(currentSamples), 0);
@@ -116,28 +124,28 @@ export class StreamingContext {
this.source = this.audioContext.createBufferSource();
this.source.buffer = audioBuffer;
// 创建增益节点用于平滑过渡
const gainNode = this.audioContext.createGain();
// 精确调度播放时间
const currentTime = this.audioContext.currentTime;
const startTime = Math.max(scheduledEndTime, currentTime);
// 应用淡入淡出效果避免爆音
const fadeDuration = 0.02; // 20毫秒
gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(1, this.audioContext.currentTime + fadeDuration);
// 直接连接到输出
this.source.connect(this.audioContext.destination);
log(`调度播放 ${currentSamples.length} 个样本,约 ${(currentSamples.length / this.sampleRate).toFixed(2)}`, 'debug');
this.source.start(startTime);
// 更新下一个音频块的调度时间
const duration = audioBuffer.duration;
if (duration > fadeDuration * 2) {
gainNode.gain.setValueAtTime(1, this.audioContext.currentTime + duration - fadeDuration);
gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + duration);
scheduledEndTime = startTime + duration;
this.lastPlayTime = startTime;
// 如果队列中数据不足,等待新数据
if (this.queue.length < targetSamples) {
break;
}
// 连接节点并开始播放
this.source.connect(gainNode);
gainNode.connect(this.audioContext.destination);
this.lastPlayTime = this.audioContext.currentTime;
log(`开始播放 ${currentSamples.length} 个样本,约 ${(currentSamples.length / this.sampleRate).toFixed(2)}`, 'info');
this.source.start();
}
// 等待新数据
await this.getQueue(minSamples);
}
}
+8 -8
View File
@@ -445,8 +445,8 @@
let audioBufferQueue = []; // 存储接收到的音频包
let isAudioPlaying = false; // 是否正在播放音频
const BUFFER_THRESHOLD = 3; // 缓冲包数量阈值,至少累积3个包再开始播放
const MIN_AUDIO_DURATION = 0.1; // 最小音频长度(秒)小于这个长度的音频会被合并
const BUFFER_THRESHOLD = 6; // 缓冲包数量阈值,6个包(360ms)平衡延迟和流畅度
const MIN_AUDIO_DURATION = 0.12; // 最小音频长度(秒)对应2个Opus包(120ms)
let streamingContext = null; // 音频流上下文
const SAMPLE_RATE = 16000; // 采样率
const CHANNELS = 1; // 声道数
@@ -518,12 +518,12 @@
log(`预初始化Opus解码器失败: ${error.message}`, 'warning');
// 继续缓冲,我们会在播放时再次尝试初始化
});
const timeout = 300;
const timeout = 400;
while (true) {
// 每次数据空的时候等三条数据
// 初始缓冲:等待6个包,平衡延迟和流畅度
const packets = await queue.dequeue(
3, // 至少 3
timeout, // 最多等 300 ms
6, // 至少 6360ms缓冲
timeout, // 最多等 400 ms
(count) => { // 超时额外回调
log(`缓冲超时,当前缓冲包数: ${count},开始播放`, 'info');
}
@@ -532,9 +532,9 @@
log(`已缓冲 ${packets.length} 个音频包,开始播放`, 'info');
streamingContext.pushAudioBuffer(packets)
}
// 50毫秒里,有多少给多少
// 持续缓冲:快速获取数据,每30ms检查一次
while (true) {
const data = await queue.dequeue(99, 50)
const data = await queue.dequeue(99, 30)
if (data.length) {
streamingContext.pushAudioBuffer(data)
} else {