如何在Go中使用portaudio和sndfile播放wav文件

First off, I'm new to the world of Go and lower level programming, so bear with me... :)

So what I'm trying to do is this; read a .wav-file with the libsndfile binding for Go and play it with portaudio.

I cannot find any examples for this, and clearly I lack basic knowledge about pointers, streams and buffers to make this happen. Here is my take on it so far, I've tried to read the docs and the few examples I've been able to find and put the pieces together. I think I'm able to open the file and the stream but I don't get how to connect the two.

package main

import (
    "code.google.com/p/portaudio-go/portaudio"
    "fmt"
    "github.com/mkb218/gosndfile/sndfile"
    "math/rand"
)

func main() {
    portaudio.Initialize()
    defer portaudio.Terminate()

    // Open file with sndfile
    var i sndfile.Info
    file, fileErr := sndfile.Open("hello.wav", sndfile.Read, &i)
    fmt.Println("File: ", file, fileErr)

    // Open portaudio stream
    h, err := portaudio.DefaultHostApi()
    stream, err := portaudio.OpenStream(portaudio.HighLatencyParameters(nil, h.DefaultOutputDevice), func(out []int32) {
        for i := range out {
            out[i] = int32(rand.Uint32())
        }
    })
    defer stream.Close()
    fmt.Println("Stream: ", stream, err)

    // Play portaudio stream
    // ....
    framesOut := make([]int32, 32000)
    data, err := file.ReadFrames(framesOut)
    fmt.Println("Data: ", data, err)
}

I would be ever so grateful for a working example and some tips/links for beginners. If you have a solution that involves other libraries than the two mentioned above, that's ok too.

Aha, audio programming! Welcome to the world of soft-realtime computing :)

Think about the flow of data: a bunch of bits in a .wav file on disk are read by your program and sent to the operating system which hands them off to a sound card where they are converted to an analog signal that drives speakers generating the sound waves that finally reach your ears.

This flow is very sensitive to time fluctuations. If it is held up at any point you will perceive noticeable and sometimes jarring artifacts in the final sound.

Generally the OS/sound card are solid and well tested - most audio artifacts are caused by us developers writing shoddy application code ;)

Libraries such as PortAudio help us out by taking care of some of the thread proirity black magic and making the scheduling approachable. Essentially it says "ok I'm going to start this audio stream, and every X milliseconds when I need the next bit of sample data I'll callback whatever function you provide."

In this case you've provided a function that fills the output buffer with random data. To playback the wave file instead, you need to change this callback function.

But! You don't want to be doing I/O in the callback. Reading some bytes off disk could take tens of milliseconds, and portaudio needs that sample data now so that it gets to the sound card in time. Similarly, you want to avoid acquiring locks or any other operation that could potentially block in the audio callback.

For this example it's probably simplest to load the samples before starting the stream, and use something like this for the callback:

isample := 0 callback := func(out []int32) { for i := 0; i < len(out); i++ { out[i] = framesOut[(isample + i) % len(framesOut)] } isample += len(out) }

Note that % len(framesOut) will cause the loaded 32000 samples to loop over and over - PortAudio will keep the stream running until you tell it to stop.

Actually, you need to tell it to start too! After opening it call stream.Start() and add a sleep after that or your program is likely to exit before it gets a chance to play anything.

Finally, this also assumes that the sample format in the wave file is the same as the sample format you requested from PortAudio. If the formats don't match you will still hear something, but it probably won't sound pretty! Anyway sample formats are a whole 'nother question.

Of course loading all your sample data up front so you can refer to it within the audio callback isn't a fantastic approach except once you get past hello world stuff. Generally you use a ring-buffer or something similar to pass sample data to the audio callback.

PortAudio provides another API (the "blocking" API) that does this for you. For portaudio-go, this is invoked by passing a slice into OpenStream instead of a function. When using the blocking API you pump sample data into the stream by (a) filling the slice you passed into OpenStream and (b) calling stream.Write().

This is much longer than I intended so I better leave it there. HTH.