首先,我是Go和低级编程世界的新手,所以请原谅我...:)
所以我想做的是用libsndfilebinding读取一个. 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)
}
我会非常感谢一个工作的例子和一些提示/链接的初学者。如果你有一个解决方案,涉及到其他库,而不是上面提到的两个,这也是可以的。
1条答案
按热度按时间gjmwrych1#
啊哈,音频节目!欢迎来到软实时计算的世界:)
想想数据流:磁盘上.wav文件中的一组位由程序读取并发送到操作系统,操作系统将其传递到声卡,在声卡中将其转换为模拟信号,驱动扬声器产生声波,最终到达您的耳朵。
这种流动对时间波动非常敏感。如果它在任何时候被举起,你会在最后的声音中察觉到明显的,有时是不和谐的伪影。
一般来说,操作系统/声卡是坚实的,经过良好的测试-大多数音频文物是由我们开发人员编写劣质的应用程序代码造成的;)
像PortAudio这样的库帮助我们解决了一些线程优先级的问题,并使调度变得容易。本质上,它说“好的,我要开始这个音频流,每隔X毫秒,当我需要下一位样本数据时,我会回调你提供的任何函数。”
在本例中,您提供了一个函数,该函数用随机数据填充输出缓冲区。要回放wave文件,您需要更改此回调函数。
但是!你不想在回调中做I/O。从磁盘上阅读一些字节可能需要几十毫秒,portaudio现在就需要样本数据,以便及时到达声卡。同样,您希望避免获取锁或任何其他可能在音频回调中阻塞的操作。
对于这个例子来说,在启动流之前加载样本可能是最简单的,并为回调使用如下内容:
请注意,
% len(framesOut)
将导致加载的32000个样本反复循环- PortAudio将保持流运行,直到您告诉它停止。实际上,你也需要告诉它开始!打开它之后,调用
stream.Start()
并在此之后添加一个睡眠,否则您的程序可能会在它有机会播放任何内容之前退出。最后,这里还假设wave文件中的示例格式与您从PortAudio请求的示例格式相同。如果格式不匹配,你仍然会听到一些声音,但它可能听起来不漂亮!无论如何,样本格式是一个完全不同的问题。
当然,预先加载所有示例数据以便在音频回调中引用它并不是一个很好的方法,除非你已经通过了hello world的东西。通常,您使用环形缓冲区或类似的东西将样本数据传递给音频回调。
PortAudio提供了另一个API(“阻塞”API)来为您完成此操作。对于portaudio-go,这是通过向
OpenStream
传递切片而不是函数来调用的。当使用阻塞API时,您可以通过(a)填充传入OpenStream
的切片和(b)调用stream.Write()
将样本数据泵入流。这比我想的要长得多,所以我最好把它留在那里。HTH。