I'm trying to achieve smooth, continuous audio playback using waveOutWrite().
The docs say:
"Applications must continually supply the device driver with data blocks
until playback or recording is complete."
Whenever I receive a WOM_DONE message via my callback function,
I play another 100KB block with waveOutWrite(). The memory blocks
are prepared in advance.
However, there are always audible gaps/irregularities in playback
when waveOutWrite() is called. Does anyone know how to overcome this?
Thanks in advance!
Stefan
Francois
Stefan Fleischmann wrote:
>
> Hi,
>
> I'm trying to achieve smooth, continuous audio playback using waveOutWrite().
>
> The docs say:
> "Applications must continually supply the device driver with data blocks
> until playback or recording is complete."
>
> Whenever I receive a WOM_DONE message via my callback function,
> I play another 100KB block with waveOutWrite(). The memory blocks
> are prepared in advance.
>
> However, there are always audible gaps/irregularities in playback
> when waveOutWrite() is called. Does anyone know how to overcome this?
>
> Thanks in advance!
>
> Stefan
Phil Frisbie, Jr.
Hawk Software
http://www.hawksoft.com
Thanks, but this is exactly what I'm doing,
yet I have audible interruptions... :-(
The callback function starts playing the already
prepared block 2 immediately after block 1 has
finished playing. Then it prepares block 1 again
and is called another time when block 2 has
finished playing.
Thanks, but I already tried all conceivable buffer sizes,
from 1 KB to 1 MB...
> It takes time to load 100KB, and during that time no sound is
> playing.
This is not the case, since I'm using a double-buffering scheme,
as explained in the other reply.
Markus
Stefan Fleischmann schrieb in Nachricht
<38271D46...@NOSPAM.sf-soft.de>...
Thank you, Markus, it does work not!
Sorry!!! This was a typo, I actually meant:
Thank you, Markus, it does work NOW! :-)
Markus
Stefan Fleischmann <s...@NOSPAM.sf-soft.de> schrieb in im Newsbeitrag:
382742B4...@NOSPAM.sf-soft.de...
Double-buffering can be made to work too, but you must be sure to call
waveOutWrite() for the next buffer *before* the current buffer has finished
playing. Otherwise, the driver will find the buffer queue empty when the
current buffer is done (causing an audible glitch).
I think that probably the original poster *thought* he was
double-buffering, but in truth he had simply implemented it incorrectly.
Let's take a look at his original description:
> The callback function starts playing the already
> prepared block 2 immediately after block 1 has
> finished playing. Then it prepares block 1 again
> and is called another time when block 2 has
> finished playing
This doesn't sound correct.
The callback function should merely fill the returned buffer (which ever
one it is) and requeue it. The callback should not "[start] playing the
already prepared block 2 immediately after block 1 has finished playing" --
block 2 should *already* be playing because it should have already been
queued *before* the callback function was called for first time!
Here's the general sequence for successful double-buffering...
In the "mainline" code...
// Fill blocks will first two chunks of audio here.
waveOutPrepare(block1);
waveOutPrepare(block2);
waveOutWrite(block1);
waveOutWrite(block2);
In the WaveProc() callback...
// Fill returned buffer with next chunk of audio here.
waveOutWrite(block);
This sequence will result is "seamless" sound as the driver will always
find the next buffer in the queue as soon as it done playing the current
one.
I suspect the original poster was actually doing this...
// In the main line code...
waveOutPrepare(block1);
// Fill block1 will first chunk of audio here.
waveOutWrite(block1);
// In the WaveProc() callback...
// Fill returned block2 with next chunk of audio here.
waveOutWrite(block2);
// (And vice versa on the next callback instance.)
This will NOT result in seamless sound as the driver will find the queue
empty after it finished the first block1 and calls the callback. That is,
by the time the callback queues block2, IT'S TOO LATE. An audible glitch
will have already been heard.
Best regards,
Matt Arnold
Professional Music Products
Mark of the Unicorn, Inc.
http://www.motu.com
In an attempt to foil spammers I use a "fake" e-mail address when posting
to newsgroups. Replace "biteme" with "motu" to obtain my actual address.
*-----------------------------------------------------------------------*
| N O T I C E T O S P A M M E R S |
| |
| Pursuant to US Code, Title 47, Chapter 5, Subchapter II, Sec. 227 any |
| and all unsolicited commercial e-mail sent to this address is subject |
| to a download and archival fee in the amount of US$500.00. E-MAILING |
| THIS ADDRESS DENOTES ACCEPTANCE OF THESE TERMS. For more information |
| go to http://thomas.loc.gov/cgi-bin/bdquery/z?d105:SN01618:@@@D. |
| |
*-----------------------------------------------------------------------*
You assumption is correct.
When the original poster (BTW, that's me :-) read in Markus Zingg's reply
"Feed (waveOutWrite()) as many bufferes as you can...", he realized what
was wrong in his code... Now he happily finds the playback crossings from
one buffer to the next one as smooth as an android's bottom.
Good of you to clarify the matter for everyone.
Stefan
"Matt A." wrote:
> Stefan Fleischmann wrote in message
> <38273AB8...@NOSPAM.sf-soft.de>...
> >> You should use more than 2 buffers. Use 3 or more of them. It's clear
> that
> >> audio get's interupted if you feed the second one if the first one is
> >> finished. Feed (waveOutWrite()) as many bufferes as you can, re-fill
> those
> >> which get back ready and retransmitt them.
>
> Double-buffering can be made to work too, but you must be sure to call
> waveOutWrite() for the next buffer *before* the current buffer has finished
> playing. Otherwise, the driver will find the buffer queue empty when the
> current buffer is done (causing an audible glitch).
>
> I think that probably the original poster *thought* he was
> double-buffering, but in truth he had simply implemented it incorrectly.
>
However, if the docs say its wrong, then it is and it should be changed.
As far as the algorithm is concerned, it is equivalent to
unprepare/prepare/add the buffer in another synchronized thread...
Dimitris
Francois Miousse <fran...@crescendo.ca> wrote in message
news:38297A17...@crescendo.ca...
No, the docs are right.
I didn't mean to literally call waveOutWrite() directly from the
callback -- just that this should be the "effective outcome" of the
WaveProc() callback's processing.
In an actual implementation, the callback should post a message or signal
an event (or something like that) in order to have *another* thread
eventually call waveOutWrite().
Thanks for helping clarifying this detail. Sorry if I caused any
confusion.
[snip]
As you realize, aside from being wrong (the docs are right, you should NOT
call these functions from your WaveProc), I'm afraid your "first waveAPI
program" is probably also needlessly inefficient.
The waveIn/OutPrepare() functions only prepare the *memory* a WAVEHDR points
to for use with the Wave driver -- they don't care what's *in* the memory.
That is, it is NOT necessary to repeatedly unprepare and then re-prepare
buffers in order to simply send (or receive) additional Wave data (for
example, in a multi-buffering scheme). Simply write some new data to an
already-prepared buffer and then write it again!
Put another way, it's perfectly legal to allocate and prepare all the
WAVEHDR's and Wave data buffers that you'll need *once* (at program startup,
for instance), continuously fill them with data and re-queue them via
waveOutWrite() after WOM_DONE (or re-queue them via waveInAddBuffer() for
reading upon WIM_DATA) . Finally, you'd unprepare and delete the buffers
and WAVEHDRs *once* (as your program performs it's final cleanup).
For example, to implement a typical double-buffering scheme for playback, a
program could prepare the two required Wave buffers *once*. Then, while
playing back, it would then simply fill each buffer with successive portions
of the Wave data to be played back, using WOM_DONE as the "triggering" event
for each buffering cycle.
> However, if the docs say its wrong, then it is and it should be changed.
> As far as the algorithm is concerned, it is equivalent to
> unprepare/prepare/add the buffer in another synchronized thread...
Yes, but again, the extra prepare/unprepare steps should not be necessary.
(BTW, all the same rules hold true for MIDI buffers and midiInOutPrepare().)
--
Best regards,
Matt Arnold
((( C++:Win32:WDM:VxD:Win16:MIDI 7R7:A-Box:C&E:DM:e:H-Kah:OMD:PSB:RF )))
In an attempt to foil spammers I use a "fake" e-mail address when posting
to newsgroups. Remove all underbars ('_') to obtain my real address.
*-----------------------------------------------------------------------*
| N O T I C E |
| |
| Pursuant to US Code, Title 47, Chapter 5, Subchapter II, Sec. 227 any |
| and all unsolicited commercial e-mail sent to this address is subject |
| to a download and archival fee in the amount of $500.00 US. E-MAILING |
| THIS ADDRESS DENOTES ACCEPTANCE OF THESE TERMS. For more information |
| visit http://thomas.loc.gov/cgi-bin/bdquery/z?d105:SN01618:@@@D. |
| |
*-----------------------------------------------------------------------*
but I am sure there was a related issue on this group some time ago, and
the result was that the headers should be Unprepared and Prepared
*every* time we add them ...
I will find those messages if someone tells me where the archives of
this newsgroup are kept (if there are any).
Cheers,
Dimitris
> Put another way, it's perfectly legal to allocate and prepare all the
> WAVEHDR's and Wave data buffers that you'll need *once* (at program
startup,
> for instance), continuously fill them with data and re-queue them via
> waveOutWrite() after WOM_DONE (or re-queue them via waveInAddBuffer() for
> reading upon WIM_DATA) . Finally, you'd unprepare and delete the buffers
> and WAVEHDRs *once* (as your program performs it's final cleanup).
Well, I guess we do it exactly the opposite way .
Check out MSDN docs, compared to what you write above, especially the last
sentence :
[MSDN quote from waveOutProc( ) documentation] :
Applications should not call any system-defined functions from inside a
callback function,
except for EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg,
midiOutShortMsg,
OutputDebugString, PostMessage, PostThreadMessage, SetEvent,
timeGetSystemTime,
timeGetTime, timeKillEvent, and timeSetEvent. Calling other wave functions
will cause deadlock.
[END MSDN quote]
So calling waveOutWrite( ) from WOM_DONE: is wrong, although it works fine
too ...
My suggestion about whats correct is (using a callback function,
irrelevant code is omitted for simplicity):
waveOutProc( )
WOM_DONE :
EnterCriticalSection( &some_Mutex);
/*Mark buffer returned as unused by the driver, eg via dwUser field on
WAVEHDR */
LeaveCriticalSection( &some_Mutex);
}
MainWaveLoopRunningOnItsOwnThread( ) {
while(TRUE) {
/* Obtain new data to play, eg from a UDP socket */
/*Wait Until Find an unused buffer by the driver, by checking the
dwUser fields
of the buffers.This is also where this thread stays idle, so its not a
pig */
EnterCriticalSection( &some_Mutex);
/*Unprepare it */
/*Update its fields (maybe not necessary)*/
/*Prepare it */
/*waveOutWrite ( ) */
LeaveCriticalSection( &some_Mutex);
}
}
What I suggest is have a dedicated thread that does the Unprepares /
waveOutWrites( ) etc, because
we cant do those from the Callback function. They synchronize via
EnterCriticalSection( ).
Also, Prepare/Unprepare( ) is always performed.
I am eager to read Matt's and other people's replies.
Cheers,
Dimitris
But I have one question. If you cannot call the waveInXXX functions in the
callback function where do you use waveInAddBuffer to re-queue another buffer?
Thanks
Francois
"Matt A." wrote:
> Dimitris Tripakis <dt...@csd.uch.gr> wrote in message
> news:eY8JnG5K$GA.248@cppssbbsa05...
> > In my first waveAPI program I also missed this MSDN remark,
> > so I did the obvious thing : In the callback function, WOM_DONE message,
> > I Unprepared, Prepared, and Wrote the buffers again ...
> > It has never caused any problems, at least obvious and direct ones.
>
> As you realize, aside from being wrong (the docs are right, you should NOT
> call these functions from your WaveProc), I'm afraid your "first waveAPI
> program" is probably also needlessly inefficient.
>
> The waveIn/OutPrepare() functions only prepare the *memory* a WAVEHDR points
> to for use with the Wave driver -- they don't care what's *in* the memory.
> That is, it is NOT necessary to repeatedly unprepare and then re-prepare
> buffers in order to simply send (or receive) additional Wave data (for
> example, in a multi-buffering scheme). Simply write some new data to an
> already-prepared buffer and then write it again!
>
> Put another way, it's perfectly legal to allocate and prepare all the
> WAVEHDR's and Wave data buffers that you'll need *once* (at program startup,
> for instance), continuously fill them with data and re-queue them via
> waveOutWrite() after WOM_DONE (or re-queue them via waveInAddBuffer() for
> reading upon WIM_DATA) . Finally, you'd unprepare and delete the buffers
> and WAVEHDRs *once* (as your program performs it's final cleanup).
>
As I already stated elsewhere in this thread, by phrases like
"waveOutWrite() after WOM_DONE", I *don't* mean literally to call
waveOutWrite() from WaveProc(). As the docs warn, this can indeed lead to
deadlock. Sorry for the added confusion, but I thought we'd already moved
past these details.
So again to clarify, from your WaveProc(), you should of course post a
message or signal an event (or *something* along those lines) in order to
delegate your WOM_DONE and/or WIM_DATA processing to some *other* thread
(from which it's safe to call waveXxx() functions. Failure to do so might
prevent your program from working with all Wave drivers (it might "work
just fine" with some drivers, but fail miserably with others).
In any case, probably the simplest solution to all this is to not use a
WaveProc() at all! -- and instead specify CALLBACK_THREAD when opening a
Wave device, passing the handle of the seperate, dedicated thread. This
thread's message loop can simply (and safely) handle MM_WOM_DONE and
MM_WIM_DONE.
[snip]
Best regards,
Matt Arnold
Professional Music Products
Mark of the Unicorn, Inc.
http://www.motu.com
In an attempt to foil spammers I use a "fake" e-mail address when posting
to newsgroups. Replace "biteme" with "motu" to obtain my actual address.
*-----------------------------------------------------------------------*
| N O T I C E T O S P A M M E R S |
| |
| Pursuant to US Code, Title 47, Chapter 5, Subchapter II, Sec. 227 any |
| and all unsolicited commercial e-mail sent to this address is subject |
| to a download and archival fee in the amount of US$500.00. E-MAILING |
| THIS ADDRESS DENOTES ACCEPTANCE OF THESE TERMS. For more information |
| go to http://thomas.loc.gov/cgi-bin/bdquery/z?d105:SN01618:@@@D. |
| |
*-----------------------------------------------------------------------*
Sent via Deja.com http://www.deja.com/
Before you buy.
The callback can post a message, set an event, or otherwise "signal" another
thread to do it.
For example, your WaveProc() could call PostMessage() with a custom-defined
message code named, say, WM_REQUEUE_THIS_WAVE_BUFFER with the WAVEHDR
pointer in LPARAM. Back in your main message loop (which is running in a
different thread than WaveProc() -- namely, your program's main thread),
could respond to WM_REQUEUE_THIS_WAVE_BUFFER by extracting the WAVEHDR and
calling waveInAddBuffer(), etc..
BTW, as I and someone else has mentioned, using the thread callback option
(CALLBACK_THREAD) instead of the function callback option
(CALLBACK_FUNCTION) can greatly simplify your coding.
Unlike a WaveProc(), a thread callback (which requires a thread running a
message loop) *already* handles Wave notifications in a separate thread.
That is, with a thread callback, it's already safe to call any waveXxx()
function directly. You can use your program's main thread (which likely
already has a message loop) or one dedicated specifically to handling Wave
notifications.
--
Best regards,
Matt Arnold
((( C++:Win32:WDM:VxD:Win16:MIDI 7R7:A-Box:C&E:DM:e:H-Kah:OMD:PSB:RF )))
In an attempt to foil spammers I use a "fake" e-mail address when posting
to newsgroups. Remove all underbars ('_') to obtain my real address.
*-----------------------------------------------------------------------*
| N O T I C E |
| |
| Pursuant to US Code, Title 47, Chapter 5, Subchapter II, Sec. 227 any |
| and all unsolicited commercial e-mail sent to this address is subject |
| to a download and archival fee in the amount of $500.00 US. E-MAILING |
| THIS ADDRESS DENOTES ACCEPTANCE OF THESE TERMS. For more information |
| visit http://thomas.loc.gov/cgi-bin/bdquery/z?d105:SN01618:@@@D. |
| |
*-----------------------------------------------------------------------*