Thread: RE: [GD-Windows] Streaming WAVE audio
Brought to you by:
vexxed72
From: Johnson, J. <Jam...@si...> - 2004-07-14 05:37:11
|
You state DirectSound, but the example you give is standard windows audio waveOut. My experience with (bad) noise on waveOut is usually due to 1 of 3 things.=0D 1) Mixing audio close to the current play position may cause pops/static. When queuing a sound to play I try to mix at least 50ms ahead of the play position, but on some hardware I found that I could go as low as 10-15ms. This maybe due to DMA or inaccuracy of the returned play position.=0D 2) On some system there appears to be a minimum required buffer size, e.g. at least 250ms. On systems like these I got silence at best, and lock up at worst. I didn't spend a lot of time investigating this because 250ms is pretty short buffer. I was shooting for 4/8/16k buffer sizes at that time. In the end I don't think it matters. 3) Bad mixing code. We all make errors. First thing I check is decompressing the audio. The doco on the ACM isn't the best. Matter of fact we've noticed that buffered compression flat out fails on some CODECs in Win98/ME with an error code not identified in the doco. Furthermore I recommend not using the ACM to up/down sample as it appears to perform point sampling(!) which can produce terrible audio. My solution is based upon a hybrid approach. I allocate a single largish (2-4seconds) buffer from which I segment into a number of WAVEHDRs. I use 1 or 2 WAVEHDRs but the system doesn't care. Like the double buffer approach you queue the headers and can setup a callback (that's what I do). In the callback I mix audio and queue the header. The nice thing about a single buffer is just that. A single buffer is easy to manage and more cache friendly.=0D James -----Original Message----- From: gam...@li... [mailto:gam...@li...] On Behalf Of Brian Hook Sent: Thursday, July 08, 2004 10:21 PM To: gam...@li... Subject: RE: [GD-Windows] Streaming WAVE audio I may actually win the award for oldest thread resurrection (this is about 2.25 years old). So I'm rewriting my streaming audio code and open sourcing it (http://www.bookofhook.com/sal for those that are curious -- it's kind of pre-alpha right now but seems to work for the most part on DirectSound, OS X/CoreAudio, OSS/Linux, and ALSA/Linux). When revisiting my streaming audio code, I realized I was using three different buffers when in _theory_ I should be able to use one largish buffer and just query the output position and write the necessary number of bytes out to it when I'm done. The key to this is that the buffer is marked as WHDR_BEGINLOOP | WHDR_ENDLOOP. I then query the output position in my mixing thread with waveOutGetPosition(). This works on my system, but it seems to start failing on some systems randomly depending on the output format (e.g. one user reported that 44KHz worked fine, but there was massive noise/distortion on 22KHz, using the same executable I was using successfully). So the question is: is there a definite known problem doing it this way instead of multibuffering? I can go back to multibuffering fairly trivially and just do something like: queue( buffer[0] ); queue( buffer[1] ); Then in my main audio thread: while ( 1 ) { if ( buffer[ 0 ].is_done ) { fill( buffer[ 0 ] ); waveOutWrite( buffer[0] ); } if ( buffer[ 1 ].is_done ) { fill( buffer[ 1 ] ); waveOutWrite( buffer[ 0 ] ); } sleep( buffer_len/2); } You get the gist -- poll for completed buffers and then fill them up on demand, relying on sleep + poll instead of an event system or callbacks or a separate thread function. Brian ------------------------------------------------------- This SF.Net email sponsored by Black Hat Briefings & Training. Attend Black Hat Briefings & Training, Las Vegas July 24-29 - digital self defense, top technical experts, no vendor pitches, unmatched networking opportunities. Visit www.blackhat.com _______________________________________________ Gamedevlists-windows mailing list Gam...@li... https://lists.sourceforge.net/lists/listinfo/gamedevlists-windows Archives: http://sourceforge.net/mailarchive/forum.php?forum_idU5 ---------------------------------------------------- Vivendi Universal Games- <<http://www.vugames.com>>:=0D The information transmitted is intended only for the=0D person or entity to which it is addressed and may=0D contain confidential and/or privileged material of=0D Vivendi Universal Games which is for the exclusive=0D use of the individual designated above as the=0D recipient. Any review, retransmission, dissemination=0D or other use of, or taking of any action in reliance=0D upon, this information by persons or entities other=0D than the intended recipient is prohibited. If you=0D received this in error, please contact immediately=0D the sender by returning e-mail and delete the=0D material from any computer. If you are not the=0D specified recipient, you are hereby notified that=0D all disclosure, reproduction, distribution or action taken on the basis of this message is prohibited. |
From: Johnson, J. <Jam...@si...> - 2004-07-14 07:17:42
|
Buffer size and latency are different things. When a sound is queued it is immediately mixed into the current playing header sometime ahead of the current play position. On good hardware I was able to mix audio 10-15ms ahead of the play position without error. However like you, I discovered that to support most/all systems I had to mix a bit further ahead of the playback position; 50ms is what I chose. When I wrote this system I had some doubt about modifying the buffer addressed by a prepared and written WAVEHDR. The doco wasn't exactly clear on this point. It clearly states what members can be modified in the WAVEHDR after prepared, but doesn't mention the PCM data addressed by the header. I'm curious if others have had problems with this mixing strategy besides the obvious recomposition of the remaining playing buffer. James -----Original Message----- From: gam...@li... [mailto:gam...@li...] On Behalf Of Jon Watte Sent: Tuesday, July 13, 2004 11:40 PM To: gam...@li... Subject: RE: [GD-Windows] Streaming WAVE audio > 2) On some system there appears to be a minimum required buffer size,=0D > e.g. at least 250ms. On systems like these I got silence at best, and=0D > lock up at worst. I didn't spend a lot of time investigating this=0D > because 250ms is pretty short buffer. I was shooting for 4/8/16k=0D > buffer sizes at that time. In the end I don't think it matters. 250 ms is a REALLY BIG buffer. 250 ms of latency means you pull the trigger now, and hear the gunshot sound when the target's already dead. (When I say "buffer" I really mean "WAVHDR chunk"). I've found that once I went above 40 ms buffers, and used at least 3 buffers, any hardware I tested on worked well. Less that that in either variable caused problems. Note that the scheduler on Win98 is VERY jittery -- 20 ms scheduling jitter is common, and it can jerk higher than that, even on a quiescent system with a fast CPU. > Furthermore I recommend not using the ACM to up/down sample as it=0D > appears to perform point sampling(!) which can produce terrible audio. I always code my own re-sampler, which sits at the reader/input stage of the mixer, which I also code on my own, for the WAVE output. I prefer to use cubic Hermite interpolation, which runs fast and sounds good. Cheers, / h+ ---------------------------------------------------- Vivendi Universal Games- <<http://www.vugames.com>>:=0D The information transmitted is intended only for the=0D person or entity to which it is addressed and may=0D contain confidential and/or privileged material of=0D Vivendi Universal Games which is for the exclusive=0D use of the individual designated above as the=0D recipient. Any review, retransmission, dissemination=0D or other use of, or taking of any action in reliance=0D upon, this information by persons or entities other=0D than the intended recipient is prohibited. If you=0D received this in error, please contact immediately=0D the sender by returning e-mail and delete the=0D material from any computer. If you are not the=0D specified recipient, you are hereby notified that=0D all disclosure, reproduction, distribution or action taken on the basis of this message is prohibited. |
From: Brian H. <ho...@bo...> - 2004-07-14 14:37:47
|
As it turns out I ended up going with many multiple small buffers and using an event to do the mixing, which is the ickiest way (the old method was really elegant -- sleep/wake/read position/write/sleep). The granularity for notifications absolutely sucks. I'll get signaled and sometimes find 0, 1, or N buffers waiting to be filled. I ended up going with 8 small buffers instead of 1 large buffer, and now it seems reasonably robust but I can't get my latency much below 50ms (if that) without having it crackle, whereas with my old code I think I could go below 40ms (since it wasn't dependent on being signaled by the sound backend). Thanks, Brian |
From: Jon W. <hp...@mi...> - 2004-07-14 15:49:44
|
This is one of the few reasons to have a second thread in your application, in my opinion. If you set your process priority to above normal, and the thread priority to real time (permissions willing), then you can get 20 ms latency on NT based kernels, and 50 ms or so on DOS based kernels. This is with three buffers, each of which is 20 milliseconds. Communication of sound events to the sound playing thread can be done using a non-blocking queue, so you interlock penalties need apply. Adding interlock would be dangerous, anyway, as you would be subject to priority inversion. Cheers, / h+ -----Original Message----- From: gam...@li... [mailto:gam...@li...]On Behalf Of Brian Hook Sent: Wednesday, July 14, 2004 7:37 AM To: gam...@li... Subject: RE: [GD-Windows] Streaming WAVE audio As it turns out I ended up going with many multiple small buffers and using an event to do the mixing, which is the ickiest way (the old method was really elegant -- sleep/wake/read position/write/sleep). The granularity for notifications absolutely sucks. I'll get signaled and sometimes find 0, 1, or N buffers waiting to be filled. I ended up going with 8 small buffers instead of 1 large buffer, and now it seems reasonably robust but I can't get my latency much below 50ms (if that) without having it crackle, whereas with my old code I think I could go below 40ms (since it wasn't dependent on being signaled by the sound backend). Thanks, Brian ------------------------------------------------------- This SF.Net email sponsored by Black Hat Briefings & Training. Attend Black Hat Briefings & Training, Las Vegas July 24-29 - digital self defense, top technical experts, no vendor pitches, unmatched networking opportunities. Visit www.blackhat.com _______________________________________________ Gamedevlists-windows mailing list Gam...@li... https://lists.sourceforge.net/lists/listinfo/gamedevlists-windows Archives: http://sourceforge.net/mailarchive/forum.php?forum_idU5 |
From: Brian H. <ho...@bo...> - 2004-07-14 18:05:40
|
> This is one of the few reasons to have a second thread in your > application, in my opinion. Agreed (along with a background resource loader if your application needs that). > If you set your process priority to > above normal, and the thread priority to real time (permissions > willing), then you can get 20 ms latency on NT based kernels, and > 50 ms or so on DOS based kernels. This is with three buffers, each > of which is 20 milliseconds. I don't change the process priority, I just set the thread priority of the audio thread to HIGHEST (not real time) -- is there a particular advantage to altering the process priority as well/first? Brian |
From: Jon W. <hp...@mi...> - 2004-07-14 21:30:54
|
> I don't change the process priority, I just set the thread priority of > the audio thread to HIGHEST (not real time) -- is there a particular > advantage to altering the process priority as well/first? I really recommend time critical priority for audio threads -- that's what that priority is for! If you get a permission error, then fall back to HIGHEST. SetPriorityClass() is what bumps up the priority for your process in general. I wouldn't set the process class to real time priority, though, that might have bad effects on the rest of the system :-) HIGH or ABOVE_NORMAL are the values you can play with here. ABOVE_NORMAL doesn't exist on DOS based Windows. The full table of class + priority mapping to actual thread priorities as used by the scheduler is found here: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/bas e/scheduling_priorities.asp It's too bad that you can't make a single thread real time without making the entire process real time, so TIME_CRITICAL is as good as it gets. Cheers, / h+ |
From: Brian H. <ho...@bo...> - 2004-07-15 16:37:57
|
For my simple audio library I'm stuck doing something a little gross, and that's passing an HWND to the library upon initialization, only because IDirectSound::SetCooperativeLevel() needs an HWND. Now, you CAN use GetDesktopWindow() and NULL, but that's not the correct way and there are supposedly some failure cases when you use those (e.g. multiple instances using GetDesktopWindow()), and God knows what the HWND is really used for in DirectSound and what can happen if you do something like create a hidden window and bind DS to that instead of to your real application window. Anyway, that aside, instead of passing in HWND, I was thinking it might be possible to find the window of the current thread by enumerating all windows in the system and comparing their owner threads with the current thread and using that. Is there anything glaringly wrong with that, other than the potential situation that there is none (e.g. console application) and having to fallback at that point. Brian |
From: Donavon K. <kei...@ea...> - 2004-07-15 19:23:56
|
> God knows what the HWND is really used for in DirectSound and > what can happen if you do something like create a hidden window > and bind DS to that instead of to your real application window. I would imagine that it's subclassing the window and sniffing for deactivation while in exclusive mode, as D3D does with the "focus" window. That would imply that it needs to be top-level and that the choice of window is quite significant. For instance in a D3D app, it should be the same window as the D3D focus window. As an experiment, I'd create a test app with two top-level windows. Give one to D3D and the other to DSound, go exclusive on both, and see what happens when you Alt-Tab away. Will DSound realize that it should relinquish? I don't have a lot of experience with DSound, but my chips are on "no". Donavon Keithley |
From: Jon W. <hp...@mi...> - 2004-07-14 06:41:37
|
> 2) On some system there appears to be a minimum required buffer size, > e.g. at least 250ms. On systems like these I got silence at best, and > lock up at worst. I didn't spend a lot of time investigating this > because 250ms is pretty short buffer. I was shooting for 4/8/16k buffer > sizes at that time. In the end I don't think it matters. 250 ms is a REALLY BIG buffer. 250 ms of latency means you pull the trigger now, and hear the gunshot sound when the target's already dead. (When I say "buffer" I really mean "WAVHDR chunk"). I've found that once I went above 40 ms buffers, and used at least 3 buffers, any hardware I tested on worked well. Less that that in either variable caused problems. Note that the scheduler on Win98 is VERY jittery -- 20 ms scheduling jitter is common, and it can jerk higher than that, even on a quiescent system with a fast CPU. > Furthermore I recommend not using the ACM to up/down sample as it > appears to perform point sampling(!) which can produce terrible audio. I always code my own re-sampler, which sits at the reader/input stage of the mixer, which I also code on my own, for the WAVE output. I prefer to use cubic Hermite interpolation, which runs fast and sounds good. Cheers, / h+ |