On Mon, 07 Nov 2005 18:25:49 +0000
Randall Walls <[EMAIL PROTECTED]> 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


Reply via email to