DirectSound, new patch available for testing

Davin McCall davmac at davmac.org
Sun Nov 6 23:24:58 CST 2005


On Mon, 07 Nov 2005 18:25:49 +0000
Randall Walls <rwalls at gwi.net> wrote:

> This has since been changed with your patch to:
> 
> hw_ptr = This->mmap_ppos;
> 
> Just curious if you could shed a bit of light on this. As I said, the 
> original patch did not fix my specific problem, but I would curious if 
> you reimplemented the same logic elsewhere, as it did seem to fix the 
> issue for Alex.

Here's a long description of what my Alsa-driver patch does. Probably goes into too much detail, but I figure too much is better than not enough and there may be others interested in this, who don't necessarily know how sound hardware works.

Basically, sound data is stored in a circular buffer. The hardware loops through the buffer and outputs the data as sound. When it gets to the end of the buffer, it immediately goes back to the beginning of the buffer and continues. So, after the hardware has gone past a particular point in the buffer it is up to software to fill buffer at that point with new data (otherwise the hardware will play the same sound again when it loops back). If the software doesn't provide the new data quickly enough, you have a situation known as a buffer underrun. Whatever sound or portion of sound was in the buffer might then be played several times as the hardware loops over the same data again and again (this may be why you heard "got it covered - ered - ered.." as mentioned in a previous post).

Previously Wine's Alsa driver was using an undocumented internal Alsa call to (_snd_pcm_hw_get_ptr, or something like that) to get the hardware's current buffer position (the "play position"). Apart from the fact that the method is only supposed to be used internally, there's the problem that Alsa provides "plug" devices (and others) which effectively insert another layer between the application and the hardware. The plug layer can do things like sample rate conversion (some hardware can only do a limited range of sample rates. AC'97 hardware for instance can generally only do 48000 samples per second or an integer division of that, whereas many software programs expect to be able to do 44100 samples/second. It's interesting that older hardware seems to be more flexible in this regard.)

If the Alsa plug layer is in use (which it is by default, unless you have used a variety of undocumented registry keys to change Wine's behaviour), a different buffer size may result and the raw hardware pointer value is less meaningful.

Alsa's model for buffer manipulation is that the application (i.e. Wine) requests a portion of the buffer using snd_pcm_mmap_begin() method, where you specify the maximum amount you want to work with at the moment. The call gives back the amount actually available, the location of the buffer, and the offset into the buffer of the available block. Then the application fills the block with sound samples and calls snd_pcm_mmap_commit() to tell Alsa that data is available.

The key thing here is that the amount of buffer actually available depends on the hardware pointer position. When snd_pcm_mmap_begin() is called Alsa will only allocate from the currently committed region (so called "application pointer") up to the current hardware playback position (the "hardware pointer"), or it's equivalent position in the translation buffer (if direct hardware access is not used). If you request a sufficiently large space when you call snd_pcm_mmap_begin() you can, therefore, determine the current playback position within the buffer.

This is one of the things that my patch does - when snd_pcm_mmap_begin() is called, the returned offset is saved in This->mmap_ppos. (It would be more correct, technically, to save the offset plus the allocated size - but in practice this isn't really necessary, seeing as the allocated size is always small. Furthermore, the reported position will actually about a single period behind the actual position, which makes the line you pointed out - "if (hw_ptr >= period_size) hw_ptr -= period_size; else hw_ptr = 0;" - redundant, which is why I removed it. Though to be honest I'm not sure if keeping the value behind by a period is even necessary anymore).

Why then is the allocated size always small? The answer depends on whether direct hardware access is used. If so, the whole buffer is initially allocated and committed, then more is allocated and committed as it becomes available. It becomes available in small chunks ("periods" in Alsa speak), so, the allocated area must be small.

If direct hardware access is not used, the initial buffer allocate and commit does not occur. The reason for this is, a commit operation actually copies and translates data from the translation buffer to the real hardware buffer. If a whole-buffer commit was performed initially, a whole buffers worth of data would be thus copied and this would be incorrect because the buffer is not even filled at that point. Instead, only a small chunk (2 periods worth) is initially committed, and even smaller chunks (1 period) are committed as each period passes (i.e. as the callback function is called). In this case, it is Alsa's responsibility to map the real hardware pointer into the user buffer.

I hope this makes sense.

Davin



More information about the wine-devel mailing list