aardio 文档

增量压缩音频播放

bass.streamPlayer 接收 MP3 等压缩音频的连续文件字节。它不需要完整文件、不依赖 TTS、HTTP 或 WebSocket,可以用于任意能够分块交付音频的生产者。

基本用法

import bass.streamPlayer;
var player = bass.streamPlayer();

// 收到生产者的一个编码音频块时:
// var ok,err = player.write(data);

// 生产者确认不会再发送任何数据时:
// player.finish();

// 生产者失败时(不把残缺音频当作正常结束):
// player.finish("错误信息");

// 用户打断或窗口关闭时:
player.close();

BASS 是闭源的第三方组件:非商业用途免费,商业用途需按 un4seen 官网 购买授权。只需播放 MP3(例如 TTS 语音)时,可改用免费的系统原生播放器 sys.mediaFoundation.streamingMp3Player,接口相同。

所有操作在创建播放器的线程执行,线程需要消息循环。wait(timeoutMs) 是带消息泵的便利方法;GUI 可以使用已有的 win.loopMessage()。

一个实例代表一个连续编码文件流,不是多个独立 MP3 文件的播放列表。它不是 PCM 样本推送器。

对象接口

方法/属性 语义
write(data) 接收字符串/buffer 字节;成功 true,失败 null,错误字符串;可重用传入的 buffer
finish() 结束输入,先送完排队数据,再通知解码器 EOF;不会立即停止声音;可重复调用
finish(err) 来源失败:立即停止并触发一次 onError;err 可以是错误字符串或含 message 的错误表
play() 开始或继续播放;可在尚无音频时调用
pause() 暂停播放,仍可接收输入,但仍受缓存上限限制
close() 立即停止并释放当前实例,可重复调用,不触发 onEnd
wait(timeoutMs) 等待实际播放完毕;超时不自动停止,需要时显式 close
state buffering、playing、paused、ended、closed、failed
done 已播完、关闭或失败
channel 格式识别后可访问 bass.channel;结束后为 null。不要自行 free
bufferedBytes aardio 层待送入 BASS 的编码字节数,不包括 BASS 自己的缓冲
receivedBytes 已收到的编码字节总数
error 失败信息

事件可在构造参数中给出,也可在第一次 write 之前设置:

空块 write("") 是无操作,不是 EOF。生产者错误时调用 finish(err) 或 close(),不要把不完整的音频当作正常完成。

缓冲选项

import bass.streamPlayer;
var player = bass.streamPlayer({
    initialBytes=2048;       // 初次尝试识别格式的字节数
    maxInitialBytes=65536;   // 初始头部识别预算
    maxBufferBytes=4194304;  // aardio 层待播队列上限,4 MiB
    volume=1;               // 本地播放音量 0..1
    autoPlay=true;
});
player.close();

初始数据不足以识别格式时,在预算内积累更多字节重试;短音频调用 finish 后会立即尝试识别。无法识别、缓冲超限或原生调用失败都明确返回错误,不静默丢块。

当 BASS 只能接收一部分输入时,剩余数据由播放器保留并通过短定时器继续提交。EOF 只在剩余数据全部接收后发送。每个流单独设置低延迟恢复与播放缓冲,不修改进程全局 BASS 缓冲设置。

内置解码器能处理的格式不等于所有文件都能从流中解码:依赖文件尾索引、过大的前置标签或额外插件的格式可能失败。此次重点验证的是 TTS 返回的 MP3。可通过提高初始识别预算处理合理的大头部,但不要取消内存上限。

已知问题:不要写入非音频数据

只把确认是音频的字节写入播放器。 写入 BASS 无法识别的数据(例如 HTTP 错误页、文本、格式错误的文件)时,BASS 会调用系统的 Media Foundation 继续探测格式。本机实测(32 位进程):推送流上的这一次探测耗时约 3 秒,最后返回 BASS_ERROR_FILEFORM(41),却占用约 1.9GB 进程地址空间且不再释放。32 位进程随后无法创建线程,出现“内存不够”、“创建线程时遇到错误”等与音频无关的故障。

同样的数据改用内存流 BASS_StreamCreateFile,或先禁用 BASS 的 Media Foundation,都不会出现。这是闭源 BASS 内部的问题,本库没有做规避。需要处理不可信数据时:

与原有 PCM 推送接口的区别

底层接口注意事项

openPush 成功返回 channel,consumedBytes。BASS 不一定消费所有初始数据;必须先补送未消费的初始部分,再送新到达的数据。失败返回 null,错误字符串,BASS错误码。

pushFile(data,length) 返回实际接收的字节数。返回 0 表示暂时没有空间,不代表失败;未接收字节必须保留。pushFile(null,0) 是 EOF,不是“空块”。

openPush 禁止 BASS_STREAM_AUTOFREE。必须在创建线程显式 free(),以保持初始原生回调的生命周期;不要在其他线程释放回调。高级播放器已经正确处理这些细节。

连接流式 TTS

播放器有 write/finish 方法,可以直接作为 web.edgeTextToSpeech 的输出对象:

import web.edgeTextToSpeech;
import bass.streamPlayer;

var player = bass.streamPlayer();
var task,err = web.edgeTextToSpeech().synthesize({text="收到前面的音频就开始播放。"}, player);
if(!task){player.close();return print(err.message);}

var ok,err = player.wait(120000); // 合成失败也会传给播放器
if(!ok) print(err);
task.cancel();
player.close();

验证

测试包括任意输入切分、原生部分写入、EOF、取消、空输入、缓存溢出,以及在只收到前缀时实际播放位置前进。不同输入切分在相同解码模式下生成完全一致的 PCM 数据。

Markdown 格式