Go beep 怎样把两个音频流混合播放
Go beep 混合两个音频流的核心是先把音频解码成 beep.Streamer,再让它们进入同一个输出采样率,最后交给 beep.Mix 同时播放。两个文件长度不一样时,混音流会在较长的输入结束后才结束;如果播放中还要追加音频,则应改用可动态管理的 beep.Mixer。
官方地址:https://pkg.go.dev/github.com/gopxl/beep
先分清一次性混音和动态混音
beep.Mix(s1, s2) 适合“已有两个有限音频,现在一起播放”的场景。它把多个 Streamer 叠加到一个输出流里,适合背景音乐加提示音、两段旁白同时播放等任务。
beep.Mixer 更像一个长期存在的混音容器,可以在运行期间调用 Add 追加流,也可以调用 Clear 清空当前流。它本身不会因为暂时没有输入而结束,而是持续输出静音,因此不能直接拿它替代一次性播放的结束信号。

把两个 MP3 解码为 Streamer
MP3 解码器接收 io.ReadCloser,返回可定位的流、音频格式和错误。返回的流负责释放解码资源,所以成功解码后要关闭返回的 StreamSeekCloser,不要在解码后立刻关闭传入文件。
package main
import (
"fmt"
"os"
"github.com/gopxl/beep"
"github.com/gopxl/beep/mp3"
"github.com/gopxl/beep/speaker"
)
// openMP3 只负责打开并解码,调用方负责关闭返回的流。
func openMP3(path string) (beep.StreamSeekCloser, beep.Format, error) {
file, err := os.Open(path)
if err != nil {
return nil, beep.Format{}, fmt.Errorf("打开 %s: %w", path, err)
}
// mp3.Decode 会接管读取过程;释放资源时关闭返回的 StreamSeekCloser。
stream, format, err := mp3.Decode(file)
if err != nil {
// 解码失败时返回的流不可用,此时关闭原文件避免句柄泄漏。
_ = file.Close()
return nil, beep.Format{}, fmt.Errorf("解码 %s: %w", path, err)
}
return stream, format, nil
}
func main() {
first, firstFormat, err := openMP3("voice.mp3")
if err != nil {
panic(err)
}
defer first.Close()
second, secondFormat, err := openMP3("music.mp3")
if err != nil {
panic(err)
}
defer second.Close()
fmt.Println("音频格式:", firstFormat, secondFormat)
_ = speaker.Play // 下一节使用 speaker 初始化和播放
}
这里保留两个 Format 是为了后面选择输出采样率。不要只看文件扩展名判断格式,真正可用于播放的采样率和声道信息来自解码结果。
采样率不同,先用 Resample 统一
混音器接收的是采样样本,不会自动替你解决不同采样率造成的速度和音高问题。如果第一个文件是 44100 Hz、第二个文件是 48000 Hz,可以把第二个流重采样到第一个格式的采样率,再进入混音器。
targetRate := firstFormat.SampleRate
// quality=3 适合实时播放;质量越高,CPU 开销通常越大。
secondForMix := beep.Resample(
3,
secondFormat.SampleRate,
targetRate,
second,
)
// Mix 只负责把同一输出格式的两个 Streamer 叠加。
mixed := beep.Mix(first, secondForMix)
// bufferSize 以样本数表示;较大更稳定,较小延迟更低。
if err := speaker.Init(targetRate, targetRate.N(time.Second/10)); err != nil {
panic(err)
}
speaker.PlayAndWait(mixed)
上面的片段需要额外导入 time。如果两个解码结果的声道数或精度也不一致,不要把它们当成完全相同的格式直接混合;工程上应先把输入约束到 beep 支持的双声道输出,或在进入 beep 前完成转换。
用 beep.Mix 让两个有限流同时播放
下面给出一个可以直接放进命令行播放器的完整骨架。它把第一个流作为目标采样率,第二个流经过重采样后再混合;PlayAndWait 会等待混合流播放完毕,主函数退出前两个返回流也会被关闭。
package main
import (
"fmt"
"os"
"time"
"github.com/gopxl/beep"
"github.com/gopxl/beep/mp3"
"github.com/gopxl/beep/speaker"
)
func decode(path string) (beep.StreamSeekCloser, beep.Format, error) {
file, err := os.Open(path)
if err != nil {
return nil, beep.Format{}, err
}
// Decode 返回的流拥有后续读取和关闭所需的资源。
stream, format, err := mp3.Decode(file)
if err != nil {
_ = file.Close()
return nil, beep.Format{}, err
}
return stream, format, nil
}
func main() {
voice, voiceFormat, err := decode("voice.mp3")
if err != nil {
panic(fmt.Errorf("读取人声: %w", err))
}
defer voice.Close()
music, musicFormat, err := decode("music.mp3")
if err != nil {
panic(fmt.Errorf("读取背景音乐: %w", err))
}
defer music.Close()
// 统一采样率,否则不同输入可能出现速度或音高异常。
music = beep.Resample(3, musicFormat.SampleRate, voiceFormat.SampleRate, music)
mixed := beep.Mix(voice, music)
// 缓冲区取约 100 毫秒,兼顾稳定性和播放响应。
if err := speaker.Init(voiceFormat.SampleRate, voiceFormat.SampleRate.N(100*time.Millisecond)); err != nil {
panic(fmt.Errorf("初始化扬声器: %w", err))
}
// 有限流播放完成后再退出,避免主函数提前结束。
speaker.PlayAndWait(mixed)
}
播放期间还要加音频时,换成 Mixer
例如提示音需要在背景音乐播放期间随时加入,应该让 Mixer 持续存在。修改当前播放集合时要短暂锁住 speaker,否则播放线程可能同时读取正在变化的流集合。
mixer := &beep.Mixer{}
// 先把背景音乐放入动态混音器,再启动持续播放。
mixer.Add(background)
speaker.Play(mixer)
// 事件到来时只在修改集合的瞬间加锁,避免产生播放卡顿。
speaker.Lock()
mixer.Add(notification)
speaker.Unlock()
// 需要停止当前所有输入时清空 Mixer;Mixer 自身仍会输出静音。
speaker.Lock()
mixer.Clear()
speaker.Unlock()
Mixer 的特点是空集合时仍然输出静音,所以它适合常驻播放管线,不适合用“等待它自然结束”来判断一段有限音频的完成。动态场景需要自行维护退出条件,或者把有限流交给一次性的 beep.Mix。

最容易踩到的四个边界
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 混合后速度或音调异常 | 输入采样率不同 | 以输出采样率为目标调用 beep.Resample |
| 程序立刻退出 | 只调用了 speaker.Play,没有等待 | 有限流用 speaker.PlayAndWait 或自行等待 |
| 追加提示音时偶发杂音 | 并发修改 Mixer | 用 speaker.Lock 和 Unlock 包住最短修改区间 |
| 文件句柄持续增加 | 只关闭了传入文件,未关闭解码返回流 | 成功解码后对 StreamSeekCloser 执行 defer Close |
扬声器缓冲区也需要按场景取舍:更大的缓冲区通常更稳定但延迟更高,更小的缓冲区响应更快但更容易受到系统调度影响。先用约 100 毫秒作为起点,再按实际交互需求调整。
结论与使用清单
两个音频文件已经存在且只需同时播放时,使用“解码 → 统一采样率 → beep.Mix → speaker.PlayAndWait”这条短路径;播放过程中会不断加入或清除提示音时,使用“常驻 beep.Mixer → 锁定 speaker → 修改输入集合”的方式。无论选择哪条路径,都要把解码返回的流当成资源管理对象,并确认输出采样率和声道约束。
- 能否从两个解码结果中确定目标采样率?
- 第二个音频是否需要
beep.Resample? - 这是有限播放还是运行期间持续追加的场景?
- 是否在退出前等待了有限流播放完成?
- 是否在关闭前释放了每个
StreamSeekCloser?
连锁门店的GLN与营业执照编号能互相替代吗
- 上一篇
- 连锁门店的GLN与营业执照编号能互相替代吗
- 下一篇
- Go beep 怎样给音频流添加暂停控制
-
- Golang · Go教程 | 19分钟前 |
- Go flac 怎样根据采样编号定位FLAC内容
- 257浏览 收藏
-
- Golang · Go教程 | 21分钟前 |
- Go flac 怎样按音频帧遍历解码样本
- 168浏览 收藏
-
- Golang · Go教程 | 24分钟前 |
- Go flac 怎样读取FLAC的STREAMINFO元数据
- 323浏览 收藏
-
- Golang · Go教程 | 44分钟前 |
- Go go-wav 怎样把整数音频缓冲区编码成WAV
- 495浏览 收藏
-
- Golang · Go教程 | 44分钟前 |
- Go go-wav 怎样分批读取WAV的整数PCM缓冲区
- 179浏览 收藏
-
- Golang · Go教程 | 45分钟前 |
- Go go-wav 怎样读取WAV文件的采样格式
- 463浏览 收藏
-
- Golang · Go教程 | 1小时前 | Go io.Seeker PCM go-mp3 Decoder.Seek
- Go go-mp3 怎样从可定位输入跳到PCM字节位置
- 310浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go go-mp3 怎样按解码采样率计算音频时长
- 240浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go go-mp3 怎样从MP3读取解码后的PCM字节
- 380浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go beep 怎样把有限音频流包装为循环流
- 326浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go beep 怎样给音频流添加暂停控制
- 278浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go malgo 怎样在结束播放时释放设备和上下文
- 452浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 804次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 795次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 883次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 853次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 683次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

