Question

I'm trying to play a wave file with C# using the portaudiosharp bindings for the portaudio C library and am having trouble envisioning the proper way to go about doing it. I will paste the code that I am using at the moment. It works somewhat but I don't think it is the proper way of doing things.

This is my callback function:

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;
            }

And then I have a 'main loop':

    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);

I have tried to implement a FIFO buffer but I think I may have done it in a silly way as basically what happens is the queue gets filled up until there are no more samples left to fit in there and only then does the PA callback start firing.

What is the better way of doing this? How do I make my main loop yield so the callback function can fire without having to sleep?

I am using a NAudio wavreader to read from a wave file but I don't think that's important. I can post more details about that if it is though.

Was it helpful?

Solution

A few obvious things:

  • You should fill your ring buffer before calling StartStream()
  • You want your main loop to keep the buffer full by writing data to it whenever it isn't full. You could do this the wrong way by polling and sleeping. If the queue is big enough you can sleep for a second at a time and the overhead won't be so big.
  • The "right" way to do it is to use an Event object and signal it from the callback every time the queue is becomes "not full". The main() loop blocks on that Event with WFSO and wakes whenever it can write data to the queue. (hint: use an auto-reset Event).
  • If all you want to do is play a soundfile you could use PA's WriteStream() API which does all this internally.

Other notes:

  • It is non-trivial to write a correct atomic FIFO queue. You haven't shown your code for this.
  • Your callback doesn't deal with the case where the queue is empty. In that case it should probably output silence.
  • You might not want to be newing up a new buffer for every block. Consider returning used blocks to the main thread via a second queue and reusing them.
  • You probably want to bound the size of the queue (3-5 seconds of audio is more than enough for most scenarios) -- this is what I mean by "not full" above. Another way to think about this is in terms of a high watermark: PA callback drains the buffer while it is non-empty, main() fills the buffer while it is less full than the (e.g. 5 second duration) watermark. Callback wakes main whenever the buffer is lower than the watermark.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top