使用回调来播放带端口音频的音频文件?

时间:2013-11-14 20:26:39

标签: c# audio portaudio

我正在尝试使用端口音频C库的portaudiosharp绑定使用C#播放wave文件,并且无法设想正确的方法来执行此操作。我将粘贴我正在使用的代码。它有点工作,但我不认为这是正确的做事方式。

这是我的回调函数:

public PortAudio.PaStreamCallbackResult myPaStreamCallback(
            IntPtr input,
            IntPtr output,
            uint frameCount,
            ref PortAudio.PaStreamCallbackTimeInfo timeInfo,
            PortAudio.PaStreamCallbackFlags statusFlags,
            IntPtr userData)
            {
                short[] mybuffer = (short[])myQ.Dequeue();
                Marshal.Copy(mybuffer, 0, output, (int)frameCount * 2);
                return PortAudio.PaStreamCallbackResult.paContinue;
            }

然后我有一个'主循环':

    PortAudio.Pa_Initialize();

    IntPtr stream;
    IntPtr userdata = IntPtr.Zero;
    PortAudio.Pa_OpenDefaultStream(out stream, 1, 2, 8,
            48000, NUM_SAMPLES/2, new PortAudio.PaStreamCallbackDelegate(myPaStreamCallback), userdata);

    PortAudio.Pa_StartStream(stream);

    while (readerPosition < reader.Length)
    {
            short[] qBuffer = new short[NUM_SAMPLES];
            read = reader.Read(buffer, 0, NUM_SAMPLES * 2); //read a block out from my wave file
            Buffer.BlockCopy(buffer, 0, qBuffer, 0, read); //copy them to the short buffer
            myQ.Enqueue(qBuffer);
            readerPosition += read;
    }

    while(PortAudio.Pa_IsStreamActive(stream) == 0)
    {
           //this while loop never gets entered -- why??
           Console.WriteLine("waiting");
    }

   System.Threading.Thread.Sleep(5000); //need this so that the callback function fires
   PortAudio.Pa_StopStream(stream);

我试图实现一个FIFO缓冲区,但我想我可能是以一种愚蠢的方式完成它,因为基本上会发生什么是队列被填满,直到没有更多的样本留在那里然后只有PA回调开始射击。

这样做的更好方法是什么?如何使我的主循环产生,以便回调函数可以在不必睡觉的情况下触发?

我正在使用NAudio wavreader从wave文件中读取,但我认为这不重要。如果是的话,我可以发布更多关于这方面的细节。

1 个答案:

答案 0 :(得分:1)

一些显而易见的事情:

  • 您应该在调用StartStream()
  • 之前填写您的环缓冲区
  • 您希望主循环通过在数据未满时向其写入数据来保持缓冲区已满。你可以通过轮询和睡觉以错误的方式做到这一点。如果队列足够大,你可以一次睡一秒,而且开销也不会那么大。
  • “正确”的方法是使用Event对象,并在每次队列变为“未满”时从回调中发出信号。 main()循环使用WFSO阻塞该事件,并且只要它可以将数据写入队列就会唤醒。 (提示:使用自动重置事件)。
  • 如果你想要的只是播放声音文件,你可以使用PA的WriteStream()API在内部完成所有这些。

其他说明:

  • 编写正确的原子FIFO队列并非易事。您尚未显示此代码。
  • 您的回调不处理队列为空的情况。在那种情况下,它应该输出沉默。
  • 您可能不希望为每个块新建一个缓冲区。考虑通过第二个队列将已使用的块返回到主线程并重用它们。
  • 您可能想要限制队列的大小(对于大多数情况,3-5秒的音频已经足够了) - 这就是我所说的“不完整”的意思。考虑这个问题的另一种方法是在高水印方面:PA回调在非空时消耗缓冲区,main()填充缓冲区,而不是(例如5秒持续时间)水印。只要缓冲区低于水印,回调就会唤醒。