duanliaolan6178 2015-02-12 23:21
浏览 231

如何在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.

  • 写回答

1条回答 默认 最新

  • drmlxgmqn18198265 2015-05-31 17:22
    关注

    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.

    评论

报告相同问题?

悬赏问题

  • ¥15 matlab实现基于主成分变换的图像融合。
  • ¥15 对于相关问题的求解与代码
  • ¥15 ubuntu子系统密码忘记
  • ¥15 信号傅里叶变换在matlab上遇到的小问题请求帮助
  • ¥15 保护模式-系统加载-段寄存器
  • ¥15 电脑桌面设定一个区域禁止鼠标操作
  • ¥15 求NPF226060磁芯的详细资料
  • ¥15 使用R语言marginaleffects包进行边际效应图绘制
  • ¥20 usb设备兼容性问题
  • ¥15 错误(10048): “调用exui内部功能”库命令的参数“参数4”不能接受空数据。怎么解决啊