[openal] Buffer mapping

Chris Robinson chris.kcat at gmail.com
Sun Jan 21 18:21:30 EST 2018


Hello,

Recent updates for OpenAL Soft have added functions to directly map a 
buffer's storage, which enables a way to fill a buffer without 
unnecessary copying. However, given some issues with it, I'm looking for 
feedback.

Firstly, this creates an issue related to formats. Obviously, to 
directly write into or read from a buffer's storage, the format and size 
should match what was originally given to alBufferData. This works fine 
for 8-bit, 16-bit, 32-bit float, and now even alaw and mulaw. But 
something like ADPCM gets converted on load, so mapping it won't work as 
expected.

There are a few options I can think of to deal with this. The obvious 
one is to double-buffer: keep a copy of the original data and use 
separate storage for the converted samples, converting whenever the 
original data is modified. This breaks the zero-copy behavior, though 
given that it needs to convert, it can't be completely avoided. But it 
also wastes memory when the original copy is no longer needed.

Alternatively, it could allocate some temporary memory. This helps save 
memory when the mapping is no longer needed, but it would be slower 
since it needs to allocate memory when mapping. For a readable mapping, 
it also needs to fill/re-encode the mapped memory, which brings the 
implication that the data given back isn't necessarily the same as what 
was previously given (assuming either a lossy encoding or a higher 
bit-depth than it's stored as).

The other option is to disallow mapping such formats. This helps ensure 
mapping a buffer doesn't do anything weird behind the app's back, but 
means the app can't be sure a given format can be mapped without testing 
it. This is the cleanest from the implementation's perspective, though 
slightly burdening on the app.


Second, currently the ability to map a buffer is declared by OR'ing some 
flags with the format (if you want to be able to map a buffer for 
writing, you first call alBufferData(bid, format | AL_MAP_WRITE_BIT, 
...) then can later call alMapBuffer with AL_MAP_WRITE_BIT). Presuming 
the format enums occupy 24 bits (all current ones do, AFAIK), the access 
flags can fit in the upper 8 bits. This works fine and avoids having to 
make another function, though it does put a limit on the number of 
potential flags alBufferData can take (8), so I'm not sure if it would 
be preferable to have a new function that takes an explicit flags parameter.


There's also the question of persistent mapping, or more generally, 
allowing a buffer to be mapped that's in use. Reading has the benefit of 
allowing an app to analyze the audio that's currently playing without 
holding its own copy, or to read back a buffer's samples regardless if 
it's currently in use or not. Writing has the benefit of more efficient 
streaming as samples are allocated all in one chunk which is 
periodically written to, rather than juggling and refilling multiple 
buffers (though multiple buffers does offer slightly better underrun 
behavior).

However, given that memory is asynchronously read in the mixer, allowing 
the app to write to it creates coherency problems. Without some 
guarantee of atomicity with reading and writing the same area, it's not 
just a case of 'randomly' reading one value or the other, but it can be 
literal junk that resembles neither what it was or what it will be 
(particularly a problem for floats; invalid bit patterns could create 
NaNs which have a habit of propagating uncontrollably if they get into a 
feedback loop or IIR filter). Maybe this is fine with a stern warning of 
"don't write where the mixer is reading" (and a flush function that acts 
as a memory barrier to "commit" what was written), but I don't know. 
Thoughts?


More information about the openal mailing list