英文:
How can I play a wav file in Go with portaudio and sndfile
问题
首先,我是你的中文翻译助手,我会帮你翻译代码部分。以下是你要翻译的内容:
首先,我对Go语言和低级编程还不熟悉,所以请谅解...
我想要做的是这样的:使用Go语言的libsndfile绑定库和portaudio来读取一个.wav文件并播放它。
我找不到任何关于这方面的示例,显然我对指针、流和缓冲区的基本知识缺乏,无法实现这个目标。以下是我目前的尝试,我试图阅读文档和找到的少数示例,并将它们拼凑在一起。我认为我能够打开文件和流,但我不知道如何将它们连接起来。
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()
// 使用sndfile打开文件
var i sndfile.Info
file, fileErr := sndfile.Open("hello.wav", sndfile.Read, &i)
fmt.Println("File: ", file, fileErr)
// 使用portaudio打开流
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)
// 播放portaudio流
// ....
framesOut := make([]int32, 32000)
data, err := file.ReadFrames(framesOut)
fmt.Println("Data: ", data, err)
}
如果你能提供一个能够使用其他库解决问题的解决方案,而不仅限于上述两个库,我将非常感激。同时,如果你能提供一些适合初学者的提示和链接,我也将非常感谢。
英文:
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
得分: 7
啊哈,音频编程!欢迎来到软实时计算的世界
想象一下数据的流动:磁盘上的.wav文件中的一堆位被你的程序读取,然后发送给操作系统,操作系统将其传递给声卡,声卡将其转换为驱动扬声器产生声波的模拟信号,最终到达你的耳朵。
这个流程对时间波动非常敏感。如果在任何一个环节上出现延迟,你会感觉到明显的、有时令人不适的声音伪影。
通常操作系统/声卡都是稳定且经过充分测试的 - 大多数音频伪影是由我们开发者编写的糟糕应用代码引起的
像PortAudio这样的库通过处理一些线程优先级黑魔法和使调度变得可行来帮助我们。它基本上是这样说的:“好的,我要启动这个音频流,每隔X毫秒,当我需要下一个样本数据位时,我会回调你提供的函数。”
在这种情况下,你提供了一个函数,用随机数据填充输出缓冲区。要播放波形文件,你需要更改这个回调函数。
但是!你不希望在回调中进行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()
,然后在此之后添加一个延迟,否则你的程序可能会在播放任何内容之前退出。
最后,这还假设波形文件中的样本格式与你从PortAudio请求的样本格式相同。如果格式不匹配,你仍然会听到声音,但可能不会很好听!不过,样本格式是另一个问题了。
当然,提前加载所有样本数据以便在音频回调中引用并不是一个很好的方法,除非你已经超过了入门级的内容。通常,你可以使用环形缓冲区或类似的方法将样本数据传递给音频回调。
PortAudio提供了另一个API(“阻塞”API),可以为你完成这个任务。对于portaudio-go,你可以通过将一个切片传递给OpenStream
来调用它。当使用阻塞API时,你可以通过(a)填充传递给OpenStream
的切片和(b)调用stream.Write()
来将样本数据传输到流中。
这个回答比我预期的要长,所以我就到这里吧。希望对你有帮助。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论