首先,我是Go和低级编程的新手,所以请耐心等待......:)
所以我要做的就是这个;阅读带有libsndfile binding的.wav文件,然后使用portaudio进行播放。
我找不到任何这方面的例子,显然我缺乏关于指针,流和缓冲区的基本知识来实现这一点。到目前为止,这是我对它的看法,我试图阅读文档和我能够找到的几个例子并将各个部分组合在一起。我想我能够打开文件和流,但我不知道如何连接这两个。
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)
}
我会非常感谢一个工作示例和初学者的一些提示/链接。如果您的解决方案涉及除上述两个库之外的其他库,那也没关系。
答案 0 :(得分:5)
考虑数据流:磁盘上.wav文件中的一堆位被程序读取并发送到操作系统,操作系统将它们移交给声卡,在声卡中它们被转换为驱动的模拟信号扬声器产生最终触及你耳朵的声波。
此流程对时间波动非常敏感。如果它在任何一点被抬起,你会在最后的声音中感觉到明显的,有时是刺耳的瑕疵。
通常情况下,操作系统/声卡都是可靠且经过良好测试的 - 大多数音频工件都是由我们的开发人员编写伪劣的应用程序代码引起的;)
PortAudio等图书馆通过处理一些线程概率黑魔法并使调度变得平易近人来帮助我们。基本上它说“好吧我打算启动这个音频流,每当我需要下一个样本数据时,每隔X毫秒,我就会回调你提供的任何功能。”
在这种情况下,您提供了一个用随机数据填充输出缓冲区的函数。要改为播放wave文件,您需要更改此回调函数。
但是!您不希望在回调中执行I / O.从磁盘读取一些字节可能需要几十毫秒,而portaudio需要现在 的样本数据,以便及时到达声卡。同样,您希望避免获取可能阻止音频回调的锁或任何其他操作。
对于这个例子,在启动流之前加载样本可能是最简单的,并使用类似的东西进行回调:
isample := 0
callback := func(out []int32) {
for i := 0; i < len(out); i++ {
out[i] = framesOut[(isample + i) % len(framesOut)]
}
isample += len(out)
}
请注意% len(framesOut)
将导致加载的32000个样本反复循环 - PortAudio将保持流运行,直到您告诉它停止。
实际上,你需要告诉它也要开始!打开之后打电话给stream.Start()
并在此之后加一个睡眠,否则你的程序可能会在它有机会玩之前退出。
最后,这还假设wave文件中的样本格式与您从PortAudio请求的样本格式相同。如果格式不匹配,你仍会听到一些声音,但它可能听起来不太好听!无论如何,样本格式都是一个完整的问题。
当然,预先加载所有的样本数据,这样你就可以在音频回调中引用它并不是一个很棒的方法,除非你超越了hello world的东西。通常,您使用环形缓冲区或类似的东西将样本数据传递给音频回调。
PortAudio提供另一个API(“阻止”API),为您完成此任务。对于portaudio-go,可以通过将切片传递给OpenStream
而不是函数来调用它。使用阻止API时,您可以通过以下方式将样本数据泵入流中:(a)填充您传递到OpenStream
的切片和(b)调用stream.Write()
。
这比我预期的要长得多,所以我最好留在那里。 HTH。