如何在Golang中实现原生音频播放
如何在Golang中实现原生音频播放
大家好,是否有可能在Go语言中像C++函数PlaySound(audiofile, NULL, SND_SYNC)那样简单地播放音乐?
我正在开发一个CLI应用程序,并且希望播放音乐(mp3/wav格式),而不需要用户下载外部依赖。
func main() {
fmt.Println("hello world")
}
4 回复
我也遇到了同样的问题,但现在我已经解决了。
好的,我找到了一个效果很好的方法:
package main
import (
"fmt"
"io"
"log"
"os"
"github.com/hajimehoshi/oto"
"github.com/hajimehoshi/go-mp3"
)
func run() error {
f, err := os.Open("AudioFile.mp3")
if err != nil {
return err
}
defer f.Close()
d, err := mp3.NewDecoder(f)
if err != nil {
return err
}
c, err := oto.NewContext(d.SampleRate(), 2, 2, 8192)
if err != nil {
return err
}
defer c.Close()
p := c.NewPlayer()
defer p.Close()
fmt.Printf("Length: %d[bytes]\n", d.Length())
if _, err := io.Copy(p, d); err != nil {
return err
}
return nil
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
在Go中实现原生音频播放需要使用操作系统级别的音频API。以下是几种方法:
1. 使用Windows原生API(仅Windows)
package main
import (
"fmt"
"syscall"
"unsafe"
)
const (
SND_SYNC = 0x0000
SND_ASYNC = 0x0001
SND_FILENAME = 0x00020000
)
var (
winmm = syscall.NewLazyDLL("winmm.dll")
playSound = winmm.NewProc("PlaySoundW")
)
func PlaySound(file string, flags uint32) error {
filePtr, _ := syscall.UTF16PtrFromString(file)
ret, _, _ := playSound.Call(
uintptr(unsafe.Pointer(filePtr)),
0,
uintptr(flags|SND_FILENAME),
)
if ret == 0 {
return fmt.Errorf("failed to play sound")
}
return nil
}
func main() {
err := PlaySound("audio.wav", SND_SYNC)
if err != nil {
fmt.Println("Error:", err)
}
}
2. 跨平台解决方案(使用beep库)
虽然需要导入外部库,但beep是纯Go实现:
package main
import (
"fmt"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/wav"
"os"
)
func playAudio(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
var streamer beep.StreamSeekCloser
var format beep.Format
// 根据文件扩展名选择解码器
if len(filename) > 4 && filename[len(filename)-4:] == ".mp3" {
streamer, format, err = mp3.Decode(f)
} else if len(filename) > 4 && filename[len(filename)-4:] == ".wav" {
streamer, format, err = wav.Decode(f)
} else {
return fmt.Errorf("unsupported format")
}
if err != nil {
return err
}
defer streamer.Close()
done := make(chan bool)
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
speaker.Play(beep.Seq(streamer, beep.Callback(func() {
done <- true
})))
<-done
return nil
}
func main() {
err := playAudio("music.mp3")
if err != nil {
fmt.Println("Error:", err)
}
}
3. 使用系统命令(最简单但依赖外部播放器)
package main
import (
"fmt"
"os/exec"
"runtime"
)
func PlaySound(file string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "start", "/wait", file)
case "darwin":
cmd = exec.Command("afplay", file)
case "linux":
cmd = exec.Command("aplay", file)
// 或者使用mpg123 for mp3: cmd = exec.Command("mpg123", file)
default:
return fmt.Errorf("unsupported platform")
}
return cmd.Run()
}
func main() {
err := PlaySound("audio.wav")
if err != nil {
fmt.Println("Error:", err)
}
}
4. 纯Go的PCM音频播放(仅WAV)
package main
import (
"encoding/binary"
"fmt"
"os"
"github.com/gordonklaus/portaudio"
)
type WavHeader struct {
ChunkID [4]byte
ChunkSize uint32
Format [4]byte
Subchunk1ID [4]byte
Subchunk1Size uint32
AudioFormat uint16
NumChannels uint16
SampleRate uint32
ByteRate uint32
BlockAlign uint16
BitsPerSample uint16
}
func PlayWav(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
var header WavHeader
if err := binary.Read(file, binary.LittleEndian, &header); err != nil {
return err
}
// 跳过可能的额外头信息
file.Seek(int64(header.ChunkSize+8)-int64(binary.Size(header)), 0)
portaudio.Initialize()
defer portaudio.Terminate()
stream, err := portaudio.OpenDefaultStream(
0,
int(header.NumChannels),
float64(header.SampleRate),
portaudio.FramesPerBufferUnspecified,
func(out []float32) {
// 读取并播放音频数据
},
)
if err != nil {
return err
}
defer stream.Close()
stream.Start()
defer stream.Stop()
// 读取和播放音频数据
buffer := make([]byte, 4096)
for {
n, err := file.Read(buffer)
if n == 0 || err != nil {
break
}
// 转换和播放buffer
}
return nil
}
func main() {
err := PlayWav("audio.wav")
if err != nil {
fmt.Println("Error:", err)
}
}
第一种方法最接近C++的PlaySound函数,但仅限Windows。如果追求跨平台且不想依赖外部库,需要实现不同操作系统的音频API调用。

