miniaudio 生态实验项目 osaudio:用阻塞式读写模型重新设计底层音频 API
发布时间:2026/9/28 3:21:24来源:尧图网络
音频处理【免费下载链接】miniaudioAudio playback and capture library written in C, in a single source file.项目地址https://gitcode.com/gh_mirrors/mi/miniaudio点击查看免费下载导读本文围绕 miniaudio 仓库中extras/osaudio目录下的实验项目展开讲解 osaudio 的设计动机、API 骨架与参考实现它尝试用操作系统的视角重新设计底层音频 API以单线程阻塞式 read/write 模型取代 miniaudio 惯用的回调模型用更少的函数覆盖设备枚举、打开关闭、读写、配置查询与事件通知等底层能力。读完本文你将掌握 osaudio 的全部核心概念与完整函数签名理解 osaudio.h 中每个结构体与常量的含义并能在参考实现 osaudio_miniaudio.c 之上动手编译、运行最简单的播放程序。一、项目定位一次操作系统音频 API的设计实验extras/osaudio/README.md开宗明义地说明这只是一个小型实验用来探索如果为操作系统如 WASAPI、ALSA 层面设计音频 API我会怎么做。项目名 osaudio 意为Operating System Audio也可以解读为 Open Source Audio。设计动机源于作者即 miniaudio 的作者多年处理各平台音频 API 后的观察——平台音频 API 普遍极其复杂而作者希望证明完整灵活与简单易用并不互斥。与 miniaudio 的关键差异在于维度miniaudioosaudio数据传递模型回调模型data callback阻塞式 read/write 模型线程控制权由库的内部音频线程驱动应用完全掌控音频线程设计定位跨平台、跨后端的通用库面向OS 级 API的设想附带参考实现阻塞模型带来的直接收益是更灵活应用可以对音频线程拥有完全控制权也让 osaudio 更容易运行在单线程系统上。同时项目文件结构极简——本质上就是一个头文件 参考实现头文件 osaudio.h 本身无任何依赖参考实现 osaudio_miniaudio.c 内部以 miniaudio 为后端。二、底层音频方案的六个必备能力osaudio.h的注释将完整底层音频方案归纳为六项能力这也是 osaudio API 设计的骨架枚举系统上连接的设备osaudio_enumerate()打开与关闭到设备的连接osaudio_open()/osaudio_close()启动与停止设备由 read/write 隐式启动由 drain/flush 停止也可用osaudio_pause()/osaudio_resume()向设备写入、从设备读取音频数据osaudio_write()/osaudio_read()查询设备的数据配置osaudio_get_info()以及在osaudio_open()返回时回填配置在设备被停止、被重新路由等事件发生时通知应用notification回调。可见 osaudio 的 API 面非常收敛全部对外函数仅有enumerate、config_init、open、close、write、read、drain、flush、pause、resume、get_avail、get_info共 12 个。三、快速上手编译与最小播放示例README 明确给出了编译方式直接编译osaudio_miniaudio.c然后把osaudio.h当作普通头文件使用。头文件零依赖参考实现当然依赖 miniaudio——如果源码不在仓库根目录下需要按实际情况调整osaudio_miniaudio.c中的 include 路径该文件默认通过#include ../../miniaudio.h引用仓库根目录的 miniaudio.h。README 中的最小示例默认设备播放#include osaudio.h ... osaudio_t audio; osaudio_config_t config; osaudio_config_init(config, OSAUDIO_OUTPUT); config.format OSAUDIO_FORMAT_F32; config.channels 2; config.rate 48000; osaudio_open(audio, config); osaudio_write(audio, myAudioData, frameCount); // 阻塞直到所有数据写入设备 osaudio_close(audio);整个播放流程只涉及四个调用初始化配置 → 打开设备 → 阻塞写数据 → 关闭。README 作者将其与 Core Audio、PipeWire 等 API 对比并直言没有谁的 API 能在简单播放这件事上比这更干净。四、API 全景数据结构与常量定义osaudio.h是 osaudio 的完整文档所在以下内容全部出自该头文件行号见 osaudio.h。4.1 结果码L260-L269常量值含义OSAUDIO_SUCCESS0成功OSAUDIO_ERROR-1通用错误OSAUDIO_INVALID_ARGS-2非法参数OSAUDIO_INVALID_OPERATION-3非法操作OSAUDIO_OUT_OF_MEMORY-4内存不足OSAUDIO_FORMAT_NOT_SUPPORTED-101请求的格式不受支持由osaudio_open()返回OSAUDIO_XRUN-102发生欠载/过载可由osaudio_read()/osaudio_write()返回OSAUDIO_DEVICE_STOPPED-103设备已停止osaudio_drain()对未运行设备调用会返回此码4.2 方向、格式与声道L271-L341方向不可组合双向需使用两个独立对象OSAUDIO_INPUT1、OSAUDIO_OUTPUT2。采样格式全部为本机字节序、交织存储OSAUDIO_FORMAT_UNKNOWN0、OSAUDIO_FORMAT_F321、OSAUDIO_FORMAT_U82、OSAUDIO_FORMAT_S163、OSAUDIO_FORMAT_S244紧密打包、OSAUDIO_FORMAT_S325。头文件明确声明永远不会支持大小端格式。声道位置从OSAUDIO_CHANNEL_NONE0、MONO1、FL/FR/FC/LFE/BL/BR等标准环绕声道到TC、TFL/TFC/TFR、TBL/TBC/TBR顶置声道再到AUX0~AUX31共 32 个辅助声道上限为OSAUDIO_MAX_CHANNELS 64。4.3 通知类型与标志位L343-L353通知类型OSAUDIO_NOTIFICATION_STARTED0响应 write/read 启动、STOPPED1响应 drain/flush 停止、REROUTED2设备被重路由非所有实现支持、INTERRUPTION_BEGIN3/INTERRUPTION_END4如来电打断。标志位OSAUDIO_FLAG_NO_REROUTING1要求实现尽量禁用自动重路由仅是提示实现可以忽略OSAUDIO_FLAG_REPORT_XRUN2要求实现通过中止 read/write 并返回OSAUDIO_XRUN来上报欠载/过载。4.4 核心结构体L355-L405struct osaudio_id_t { /* 256 字节唯一标识设备未用字节必须清零可整体复制以便序列化保存 */ char data[256]; }; struct osaudio_config_t { osaudio_id_t* device_id; /* NULL 表示使用默认设备非 NULL 时自动路由将被禁用 */ osaudio_direction_t direction; /* OSAUDIO_INPUT / OSAUDIO_OUTPUT不可组合 */ osaudio_format_t format; /* OSAUDIO_FORMAT_* */ unsigned int channels; unsigned int rate; /* 每秒采样数 */ osaudio_channel_t channel_map[OSAUDIO_MAX_CHANNELS]; /* 全部置 0 表示默认排布 */ unsigned int buffer_size; /* 以帧为单位0 表示系统默认 */ unsigned int flags; /* OSAUDIO_FLAG_* 组合 */ void (* notification)(void* user_data, const osaudio_notification_t* notification); /* 永不在音频线程中调用 */ void* user_data; }; struct osaudio_info_t { osaudio_id_t id; char name[256]; osaudio_direction_t direction; unsigned int config_count; osaudio_config_t* configs; /* 打开后含一项内容与 osaudio_open() 回填的配置一致 */ };设备 ID 的设计值得注意它是 256 字节数组足以容纳各平台不同的设备 ID 表示且可以复制适合保存到配置文件中做序列化/反序列化。五、设备枚举与打开默认设备与指定设备5.1 枚举设备osaudio_enumerate()返回osaudio_info_t数组必须用free()释放。osaudio.h中给出的判别示例unsigned int count; osaudio_info_t* info; osaudio_enumerate(count, info); for (int i 0; i count; i) { if (info[i].direction OSAUDIO_OUTPUT) { printf(Output device: %s\n, info[i].name); } else { printf(Input device: %s\n, info[i].name); } }如果只想打开默认设备完全可以跳过枚举。5.2 打开默认设备osaudio_config_t中device_id传NULL即表示系统默认设备README 示例int result; osaudio_t audio; osaudio_config_t config; osaudio_config_init(config, OSAUDIO_OUTPUT); config.format OSAUDIO_FORMAT_F32; config.channels 2; config.rate 48000; result osaudio_open(audio, config); if (result ! OSAUDIO_SUCCESS) { printf(Failed to open device.); return -1; } ... osaudio_close(audio);5.3 打开指定设备枚举后取目标设备的id填入配置README 示例int result; osaudio_t audio; osaudio_config_t config; unsigned int infoCount; osaudio_info_t* info; result osaudio_enumerate(infoCount, info); if (result ! OSAUDIO_SUCCESS) { printf(Failed to enumerate devices.\n); return -1; } // ... 遍历 info 数组用 direction 成员区分输入/输出找到目标设备 ... osaudio_config_init(config, OSAUDIO_OUTPUT); config.id info[indexOfYourChosenDevice].id; config.format OSAUDIO_FORMAT_F32; config.channels 2; config.rate 48000; osaudio_open(audio, config); ... osaudio_close(audio); free(info); // 枚举数组必须用 free() 释放5.4 使用设备原生配置osaudio_config_init()会把结构体清零并把 direction 设为指定值——零值即使用设备原生格式/声道/采样率。README 给出两种等价写法osaudio_config_init(config, OSAUDIO_OUTPUT); config.format OSAUDIO_FORMAT_UNKNOWN; // 等价于不设置 config.channels 0; config.rate 0;与直接osaudio_config_init(config, OSAUDIO_OUTPUT);完全相同。此时实现负责在设备原生配置与请求配置之间做数据转换若无法转换则返回OSAUDIO_FORMAT_NOT_SUPPORTED调用方可决定改用原生配置自行转换或放弃。osaudio_open()返回时传入的 config 会被回填为设备的实际配置包括 format、channels、rate以及channel_map——声道映射用于确定数据中声道排列顺序头文件特别强调实现不会做自动声道映射转换这需要调用方手动处理。若想在打开前就获知原生配置可以通过枚举拿到configs数组第一项即含格式/声道/采样率信息。六、数据读写阻塞模型的核心6.1 写入与读取osaudio_write(audio, data, frame_count)阻塞直到指定帧数全部写入或设备被关闭osaudio_read(audio, data, frame_count)同理从设备读取指定帧数。设备由这两个函数自动启动当frame_count 0且未处于暂停状态时见 osaudio.h。README 的写入示例int result osaudio_write(audio, myAudioData, myAudioDataFrameCount); if (result OSAUDIO_SUCCESS) { printf(Successfully wrote %d frames of audio data.\n, myAudioDataFrameCount); } else { printf(Failed to write audio data.\n); }注意事项读写进行中不能调用osaudio_close()同一时刻只能有一个线程调用osaudio_write()或osaudio_read()多线程写入需自行加同步。6.2 非阻塞查询get_avail由于读写是阻塞的osaudio_get_avail()用于获知当前还有多少帧可写/可读而不会阻塞unsigned int framesAvailable osaudio_get_avail(audio); if (result 0) { printf(There are %d frames available for writing.\n, framesAvailable); } else { printf(There are no frames available for writing.\n); }6.3 打断阻塞操作osaudio_flush()可以立即中止任何正在进行的阻塞 read/write不阻塞、立即返回随后这些挂起的读写会立刻返回。七、生命周期管理drain / flush / pause / resumeosaudio_drain()阻塞直到所有挂起的读写完成。之后若再次调用 read/write设备会恢复正常工作。禁止在暂停状态下调用否则缓冲永远不会被消费drain 永不返回否则返回OSAUDIO_DEVICE_STOPPED。osaudio_flush()立即清空挂起的读写不阻塞。osaudio_pause()/osaudio_resume()显式暂停/恢复。暂停触发OSAUDIO_NOTIFICATION_STOPPED通知恢复触发OSAUDIO_NOTIFICATION_STARTED通知。README 专门提醒一旦使用osaudio_pause()osaudio_drain()将永不返回——因为暂停导致设备处于停止状态缓冲不再被消费自然永远无法排空。暂停的另一种简陋做法是直接 drain/flush 后不再做任何读写操作让设备保持在停止状态。八、线程安全模型osaudio.h明确了三条线程安全规则除这些以外一切线程安全osaudio_open()进行期间不得调用任何其他函数尚未获得有效的osaudio_t对象任何其他函数进行期间不得调用osaudio_close()销毁正在使用中的对象本就是糟糕用法osaudio_write()与osaudio_read()同一时刻只能由一个线程调用否则音频数据必然混乱。以上规则仅针对同一个osaudio_t对象。可以同时打开多个 osaudio 对象并在不同线程上并发调用不同对象的任意函数。九、参考实现源码解析miniaudio 后端如何工作osaudio_miniaudio.c 是官方参考实现内部用 miniaudio 驱动其核心机制可以完整印证上文的所有 API 语义。9.1 编译裁剪与内部结构文件开头通过宏裁剪 miniaudio 功能MA_NO_DECODING、MA_NO_ENCODING、MA_NO_RESOURCE_MANAGER、MA_NO_NODE_GRAPH、MA_NO_ENGINE、MA_NO_GENERATION并将MA_API定义为static实现单文件/unity 编译L20-L30若想自行提供 miniaudio 实现如参与 unity build可定义OSAUDIO_NO_MINIAUDIO_IMPLEMENTATION。每个设备对象L32-L45持有ma_device、ma_pcm_rb环形缓冲、ma_semaphore音频线程释放、read/write 等待、若干原子标志isActive/isPaused/isFlushed/xrunDetected、用于启停设备的activateLock自旋锁以及用于 drain 与 read/write 互斥的drainLock互斥锁。9.2 阻塞语义的落地环形缓冲 信号量播放方向的数据通路是应用线程osaudio_write()把数据写入环形缓冲miniaudio 的 data callbackosaudio_data_callback_playback从缓冲读取并送出每处理一整个 chunk 就释放一次信号量写满缓冲后应用线程在信号量上等待。若 callback 侧无数据可用则填充静音并标记xrunDetected欠载。捕获方向完全对称osaudio_data_callback_capture。osaudio_write()L680-L735的实现是一个循环只要frame_count 0就尽量向环形缓冲写入缓冲满则ma_semaphore_wait()等待若等待中被osaudio_flush()/osaudio_drain()置为非激活则中止循环返回。若启用了OSAUDIO_FLAG_REPORT_XRUN且检测到 xrun则清标志并返回OSAUDIO_XRUN。9.3 启动 / 停止 / drain / flush启动集中在一个osaudio_activate()函数L658-L678持有activateLock若未激活则置isActive必要时先重置环形缓冲flush 语义再调用ma_device_start()。osaudio_drain()L792-L842捕获方向先停设备再置非激活并释放信号量然后等drainLock循环等待缓冲排空后停止播放设备——这与头文件drain 阻塞直到所有挂起读写完成的语义一致。osaudio_flush()L844-L876先ma_device_stop()停设备置非激活并释放信号量唤醒阻塞中的 read/write置isFlushed标志真正的缓冲清空延迟到下一次osaudio_activate()时执行。9.4 细节行为buffer_size为 0 时默认按10ms周期配置L558-L562环形缓冲大小为buffer_size * 2L625periodCount 2。OSAUDIO_FLAG_NO_REROUTING映射到 miniaudio 的 WASAPInoAutoStreamRouting选项L567-L569。全局上下文通过引用计数管理osaudio_ref_context/osaudio_unref_context首次使用时用哑设备探测后端osaudio_determine_miniaudio_backend。通知回调经由 miniaudio 的notificationCallback桥接osaudio_nofication_callback并把ma_device_notification_type_*一一映射到OSAUDIO_NOTIFICATION_*。十、另一份实现DOS Sound Blaster 后端除 miniaudio 后端外仓库还提供了一份面向 DOS 的参考实现 osaudio_dos_soundblaster.c约 1141 行仅供 DOS 编译作者用 OpenWatcom v2.0 测试过。它通过BLASTER环境变量读取基端口、IRQ 与 DMA 通道缺省时回退到 8-bit 通道 1 / 16-bit 通道 5一次只允许初始化一个设备。该实现支持的能力组合清晰地示范了原生配置枚举的含义L76-L89格式OSAUDIO_FORMAT_S16、OSAUDIO_FORMAT_U8声道数立体声2、单声道1采样率44100、22050、11025、24000、12000、8000 Hz。osaudio_enumerate()固定报告默认播放设备 默认捕获设备两个条目设备名为 Sound Blaster按上述格式/声道/采样率笛卡尔积枚举出全部原生配置组合。这正好呼应 README 中实现需要自行在设备原生配置与请求配置之间做数据转换的分工——在这份实现里你只能从列出的原生组合中选择。同时它演示了OSAUDIO_FAR远指针宏在__MSDOS__/_MSDOS/__DOS__下定义为farosaudio.h的实际用途。十一、随附测试验证与范例extras/osaudio/tests/下有两个可直接参考的测试程序osaudio_sine.c完整演示了枚举设备 → 打开默认播放设备 → 生成 220Hz 正弦波 → 阻塞写出一秒音频 → 关闭的流程同时示范了osaudio_get_info()打印设备名与实际协商出的格式/采样率/声道数并按0xFFFF帧分块写入对应写函数入参上限以及 U8/S16 两种格式的生成逻辑。osaudio_deviceio.c示范了三种模式——用 miniaudio 解码音频文件后通过osaudio_write()播放MODE_PLAYBACK、用独立输入/输出对象组成的全双工回环MODE_DUPLEXosaudio_read()与osaudio_write()配对使用并演示OSAUDIO_FLAG_REPORT_XRUN标志下如何区分并容忍OSAUDIO_XRUN返回码。这两个测试还展示了头文件中没有单独画出的关键实践读写数据前以osaudio_get_info(audio)-configs[0]或回填后的config为准来分配缓冲与计算帧字节数而不是盲目相信请求值。十二、结语设计取舍与开放讨论从 README 与osaudio.h的反馈征集可以看出该项目仍处于抛砖引玉的实验阶段作者明确想听取三类意见支持的格式是否足够并强调某块硬件上的原生格式不足以成为加入格式的理由大小端永不支持、声道位置是否足够、以及任何一般性批评。它刻意把 API 收敛到 12 个函数、单线程阻塞模型与配置可协商、失败即报错的语义上与 miniaudio 的回调模型互为镜像。对想研究底层音频 API 到底可以简单到什么程度的开发者来说extras/osaudio 目录README、头文件、miniaudio 与 DOS 两份实现、两个测试是一份非常精悍的参考读物。赞分享音频处理【免费下载链接】miniaudioAudio playback and capture library written in C, in a single source file.项目地址https://gitcode.com/gh_mirrors/mi/miniaudio点击查看免费下载相关推荐CPython wave 模块实战指南WAV 音频文件的读写、格式参数与底层实现解析CPython wave 模块实战指南WAV 音频文件的读写、格式参数与底层实现解析 本文基于 CPython 官方库参考手册中 wave 模块的文档 Do编程语言语言运行时解释器标准库Mantle Swift重写纯Swift模型层设计思路Mantle Swift重写纯Swift模型层设计思路 你还在为Objective C模型层的繁琐映射和类型转换烦恼吗本文将带你用Swift重构Mantle移动开发RxJava 阻塞式 Observable 操作符完全指南blockingFirst 到 blockingIterable 的用法与底层实现RxJava 阻塞式 Observable 操作符完全指南blockingFirst 到 blockingIterable 的用法与底层实现 本文以 RxJa后端异步编程上一篇如何快速获取网盘直链下载LinkSwift 网盘直链下载助手完整使用指南下一篇DLSS Swapper实战指南一站式游戏性能优化解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网