05-25-2012 08:31 PM
Something that I have been trying to figure out is how to play back-to-back WAV files without there being a slight "seam" or gap in between one WAV file ending and another starting.
I was using the PlayWav sample code.
I talked to a few QNX / Native SDK people at BB 10 Jam but they didn't have any suggestions for how to adapt the PlayWav code.
What I eventually determined is that if you remove:
bytes_read = snd_pcm_plugin_flush(pcm_handle, SND_PCM_CHANNEL_PLAYBACK);
... then the gap is eliminated. ie. You can write more audio data, and so long as you do that soon enough, there won't be a gap.
The downside to this is that the code no longer blocks, so any code you execute after that point is no longer executing after the audio has finished.
I have been using:
... and then looking at:
... to estimate where the audio is in terms of it actually being played. That seems to be working well.
05-26-2012 12:33 AM
According to the docs:
snd_pcm_plugin_flush() will block until all data is flushed out of the PCM driver output queue.
This effectively means that once this function returns, all audio samples have completed playing.
If you are going to then close() and open() a new file, you're going to have a brief gap.
I wouldn't ever flush this out unless you need to know when audio is finished. (eg. only call flush when your last .wav file is finished being streamed)
What I would suggest is that you use a thread to drive the pcm output which is separate from your file-reading thread. Implement a buffer queue of some sort between the two threads. This already should exist to some extent inside the pcm driver, however the buffer size may be small enough that it takes more time to close() and open() new files than it does to empty the buffer. Especially if your last write does not completely fill the last buffer before a close(). This is pretty much the definition of how streaming audio works.
So you would have:
file-reading-thread -->(circular buffer)--> pcm-writer-thread-->(snd_pcm_plugin_write)
The circular buffer would decouple input jitter from the pcm-writer-thread. This includes jitter caused by closing and opening the files. You would need to implement a simple flow-control to stop the file-reading-thread from appending to the buffer when no free space remains because the pcm-writer-thread will empty the buffer slower than the file-reading-thread fills it.
You would probably still want to use the snd_pcm_plugin_flush() call, but only when you have no further audio data to write (eg. when your circular buffer transitions from non-empty to empty). This would allow you to synchronize the rest of your code to an "end of playback" condition.
05-26-2012 12:37 AM
after writing this, it occurs to me that you probably turned the wav-playing bit of PlayWav's main() function into your own standalone PlayWav() function, and are just calling it back-to-back with different filenames.
If that's the case, then a simpler solution would be to add a boolean argument to the function call which dictates whether to wait for the track to complete playing, or to return just prior to completion.
eg. void PlayWav(char* filename, bool waitForDone);
This of course still hinges on the assumption that the time taken to playout your final buffer is longer than the time taken to close() and open() a new file. If this is not the case, then you will still have a gap.
05-26-2012 09:39 PM
05-26-2012 10:20 PM
Thanks for long a detailed reply on the topic!
My current implementation, as you surmised, wraps the PlayWav sample code in a function. I have a thread that plays the audio and a thread that downloads data from the network. The audio thread checks whether it has more data, and if so, calls the function that plays the next WAV. Rather than execute the PlayWav code immediately, it first checks:
... and then looking at:
... it waits until all but about of second of the audio has been played, and then proceeds to play the next WAV. This ensures that the audio buffers never run out of data, but also ensure that the audio thread doesn't go like a runaway train writing massive amounts of data to the audio buffers.
I haven't run into any issues with this approach. Seems to work.
05-26-2012 10:38 PM
Sounds like the right approach to me. Just be sure to build in conditions for the audio thread to exit gracefully when needed. Thread management can be tricky, and a runaway thread writing data to the audio device could become a very annoying bug!
05-26-2012 11:28 PM
Yup. Sounds good.
I should actually point out that as written, the PlayWav sample does not really implement any additional buffering between file-i/o and pcm-i/o aside from the single read buffer. This could be exteneded to do more buffering without adding addiitional threads since the event loop already covers bother the input events and the output events. Instead of reading from the file and outputting directly to PCM, you could just output to an intermediary buffer.