The Soundblaster 16 audio device is strange and works differently
compared to the other audio frontends. For audio playback, a BH
routine copies the audio samples from the DMA buffer to the audio
backend. The BH routine then immediately reschedules itself. As far
fewer samples are required from the backend than the BH routine can
supply, this means that most of the BH routine calls don't copy any
audio samples. The buffers of the audio backend are simply full.
This is a very inefficient design.

Use the DREQ signal of the ISA DMA controller to throttle the audio
stream. The callback routine of the audio subsystem raises the DREQ
signal as soon as new samples are needed and also starts the DMA
callback routine.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/469
Signed-off-by: Volker Rümelin <vr_q...@t-online.de>
---
 hw/audio/sb16.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/hw/audio/sb16.c b/hw/audio/sb16.c
index 5c51940596..714144eb61 100644
--- a/hw/audio/sb16.c
+++ b/hw/audio/sb16.c
@@ -200,7 +200,9 @@ static void control (SB16State *s, int hold)
     ldebug("hold %d high %d nchan %d\n", hold, s->use_hdma, nchan);
 
     if (hold) {
-        hold_DREQ(s, nchan);
+        if (!s->voice) {
+            hold_DREQ(s, nchan);
+        }
         AUD_set_active_out (s->voice, 1);
     }
     else {
@@ -890,6 +892,8 @@ static void legacy_reset (SB16State *s)
     s->fmt_bits = 8;
     s->fmt_stereo = 0;
 
+    s->audio_free = 0;
+
     as.freq = s->freq;
     as.nchannels = 1;
     as.fmt = AUDIO_FORMAT_U8;
@@ -1243,8 +1247,12 @@ static int SB_read_DMA (void *opaque, int nchan, int 
dma_pos, int dma_len)
     }
 
     if (s->voice) {
+        if (!dma_len) {
+            return dma_pos;
+        }
         free = s->audio_free & ~s->align;
-        if ((free <= 0) || !dma_len) {
+        if (free <= 0) {
+            release_DREQ(s, nchan);
             return dma_pos;
         }
     }
@@ -1269,6 +1277,7 @@ static int SB_read_DMA (void *opaque, int nchan, int 
dma_pos, int dma_len)
     written = write_audio (s, nchan, dma_pos, dma_len, copy);
     dma_pos = (dma_pos + written) % dma_len;
     s->left_till_irq -= written;
+    s->audio_free -= written;
 
     if (s->left_till_irq <= 0) {
         s->mixer_regs[0x82] |= (nchan & 4) ? 2 : 1;
@@ -1289,13 +1298,23 @@ static int SB_read_DMA (void *opaque, int nchan, int 
dma_pos, int dma_len)
         s->left_till_irq = s->block_size + s->left_till_irq;
     }
 
+    if (s->voice) {
+        free = s->audio_free & ~s->align;
+        if (free <= 0) {
+            release_DREQ(s, nchan);
+        }
+    }
     return dma_pos;
 }
 
 static void SB_audio_callback (void *opaque, int free)
 {
     SB16State *s = opaque;
+    int nchan = s->use_hdma ? s->hdma : s->dma;
+
     s->audio_free = free;
+    /* run the DMA engine to call SB_read_DMA immediately */
+    hold_DREQ(s, nchan);
 }
 
 static int sb16_post_load (void *opaque, int version_id)
-- 
2.43.0


Reply via email to