如何在Golang中实现原生音频播放

如何在Golang中实现原生音频播放 大家好,是否有可能在Go语言中像C++函数PlaySound(audiofile, NULL, SND_SYNC)那样简单地播放音乐? 我正在开发一个CLI应用程序,并且希望播放音乐(mp3/wav格式),而不需要用户下载外部依赖。

func main() {
    fmt.Println("hello world")
}
4 回复

很高兴你解决了问题

更多关于如何在Golang中实现原生音频播放的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我也遇到了同样的问题,但现在我已经解决了。

好的,我找到了一个效果很好的方法:

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调用。

回到顶部