[PATCH 00/17] audio: improve callback interface for audio frontends

Volker Rümelin posted 17 patches 1 year, 3 months ago
There is a newer version of this series
audio/audio.c          | 366 +++++++++++++++++++++--------------------
audio/audio_int.h      |  12 +-
audio/audio_template.h |  41 +++--
audio/mixeng.c         |  73 ++++++++
audio/mixeng.h         |   2 +
audio/rate_template.h  |  21 ++-
6 files changed, 304 insertions(+), 211 deletions(-)
[PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Volker Rümelin 1 year, 3 months ago
Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
([PATCH 00/11] audio: more improvements)

The callback interface for emulated audio devices is strange. The 
callback function has an 'avail' parameter that passes the number of 
bytes that can be written or read. Unfortunately, this value sometimes 
is only an imprecise estimate and the callback functions must check the 
actual bytes written or read. For playback devices, this means that they 
either need a ring buffer or have to write the unwritten bytes again the 
next time. For recording devices, things are a bit easier. They only 
need to continue with the actual number of bytes read.

After this patch series, the 'avail' argument for the -audiodev 
out.mixing-engine=on and in.mixing-engine=on cases is exact. Audio 
frontends only need a linear frame buffer and there's a guarantee they 
can write or read 'avail' bytes.

The -audiodev out.mixing-engine=off case is also mostly accurate. Only 
the D-Bus audio backend is still missing a required function. The 
-audiodev in.mixing-engine=off case always passes a much too large 
'avail' value. I haven't worked on this yet, because there was no reason 
for it so far.

The following logs show the improvements. Not only the audio frontends 
can write or read all needed or available bytes. The same is true for 
the audio backends. For playback, the first six lines in the logs are 
expected. Here you can see how quickly the guest fills the empty 
downstream buffers after playback starts.

QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
hda-duplex,audiodev=audio0 -audiodev 
pa,out.frequency=96000,in.frequency=96000,id=audio0

playback guest 44100Hz => host 96000Hz

unpatched version:
hda_audio_output_cb: to write 8188, written 1704
audio_run_out: free 4458, played 926
hda_audio_output_cb: to write 6488, written 2384
audio_run_out: free 3532, played 1297
hda_audio_output_cb: to write 4104, written 2648
audio_run_out: free 2235, played 1441
audio_run_out: free 794, played 793
audio_run_out: free 897, played 896
audio_run_out: free 831, played 829
...
hda_audio_output_cb: could not write 4 bytes
hda_audio_output_cb: to write 1764, written 1760
audio_run_out: free 960, played 958
...

patched version:
hda_audio_output_cb: to write 8192, written 1620
audio_run_out: free 4458, played 880
hda_audio_output_cb: to write 6576, written 2508
audio_run_out: free 3578, played 1365
hda_audio_output_cb: to write 4068, written 2500
audio_run_out: free 2213, played 1360

record host 96000Hz => guest 44100Hz

unpatched version:
audio_run_in: avail 4458, acquired 4454
audio_run_in: avail 1574, acquired 1572
audio_run_in: avail 766, acquired 764
audio_run_in: avail 1052, acquired 1051
audio_run_in: avail 761, acquired 760
audio_run_in: avail 1123, acquired 1121
...
hda_audio_input_cb: could not read 4 bytes
hda_audio_input_cb: to read 1988, read 1984
audio_run_in: avail 1082, acquired 1080
...

patched version:
(no output)

QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
hda-duplex,audiodev=audio0 -audiodev 
pa,out.frequency=32000,in.frequency=32000,id=audio0

playback guest 44100Hz => host 32000Hz

unpatched version:
hda_audio_output_cb: to write 8188, written 1620
audio_run_out: free 1486, played 294
hda_audio_output_cb: to write 6568, written 2512
audio_run_out: free 1192, played 455
hda_audio_output_cb: to write 4060, written 2504
audio_run_out: free 737, played 455
audio_run_out: free 282, played 281
audio_run_out: free 357, played 356
audio_run_out: free 314, played 313
...
hda_audio_output_cb: could not write 4 bytes
hda_audio_output_cb: to write 1416, written 1412
audio_run_out: free 257, played 256
...

patched version:
hda_audio_output_cb: to write 8192, written 1656
audio_run_out: free 1486, played 300
hda_audio_output_cb: to write 6536, written 2516
audio_run_out: free 1186, played 457
hda_audio_output_cb: to write 4020, written 2540
audio_run_out: free 729, played 460

record host 32000Hz => guest 44100Hz

unpatched version:
audio_run_in: avail 1486, acquired 1485
audio_run_in: avail 272, acquired 271
audio_run_in: avail 366, acquired 365
hda_audio_input_cb: could not read 4 bytes
hda_audio_input_cb: to read 1420, read 1416
audio_run_in: avail 258, acquired 257
audio_run_in: avail 375, acquired 374
hda_audio_input_cb: could not read 4 bytes
hda_audio_input_cb: to read 2056, read 2052
audio_run_in: avail 260, acquired 259
...

patched version:
(no output)

This is the debug code for the logs above.

---snip--
--- a/audio/audio.c    2022-12-13 19:14:31.793153558 +0100
+++ b/audio/audio.c    2022-12-11 16:24:48.842649711 +0100
@@ -1228,6 +1228,10 @@ static void audio_run_out (AudioState *s
  #ifdef DEBUG_OUT
          dolog("played=%zu\n", played);
  #endif
+        if (hw_free - played) {
+            fprintf(stderr, "%s: free %zu, played %zu\n",
+                    __func__, hw_free, played);
+        }

          if (played) {
              hw->ts_helper += played;
@@ -1318,6 +1322,7 @@ static void audio_run_in (AudioState *s)
              if (sw->active) {
                  size_t sw_avail = audio_get_avail(sw);
                  size_t avail;
+                size_t prev_acquired = sw->total_hw_samples_acquired;

                  avail = st_rate_frames_out(sw->rate, sw_avail);
                  if (avail > 0) {
@@ -1325,6 +1330,11 @@ static void audio_run_in (AudioState *s)
                      sw->callback.fn(sw->callback.opaque,
                                      avail * sw->info.bytes_per_frame);
                  }
+
+                if (sw_avail + prev_acquired - 
sw->total_hw_samples_acquired) {
+                    fprintf(stderr, "%s: avail %zu, acquired %zu\n", 
__func__,
+                            sw_avail, sw->total_hw_samples_acquired - 
prev_acquired);
+                }
              }
          }
      }
--- a/hw/audio/hda-codec.c    2023-01-04 14:07:31.954304889 +0100
+++ b/hw/audio/hda-codec.c    2023-01-04 13:57:47.687320406 +0100
@@ -265,20 +265,28 @@ static void hda_audio_input_cb(void *opa
      int64_t rpos = st->rpos;

      int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), avail);
+    unsigned int total_read = 0;

      while (to_transfer) {
          uint32_t start = (uint32_t) (wpos & B_MASK);
          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
          uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk);
          wpos += read;
+        total_read += read;
          to_transfer -= read;
          st->wpos += read;
          if (chunk != read) {
+            fprintf(stderr, "%s: could not read %u bytes\n", __func__,
+                    chunk - read);
              break;
          }
      }

      hda_timer_sync_adjust(st, -((wpos - rpos) - (B_SIZE >> 1)));
+    if (avail != total_read) {
+        fprintf(stderr, "%s: to read %d, read %u\n", __func__,
+                avail, total_read);
+    }
  }

  static void hda_audio_output_timer(void *opaque)
@@ -329,6 +337,7 @@ static void hda_audio_output_cb(void *op
      int64_t rpos = st->rpos;

      int64_t to_transfer = MIN(wpos - rpos, avail);
+    unsigned int total_written = 0;

      if (wpos - rpos == B_SIZE) {
          /* drop buffer, reset timer adjust */
@@ -343,15 +352,22 @@ static void hda_audio_output_cb(void *op
          uint32_t start = (uint32_t) (rpos & B_MASK);
          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
          uint32_t written = AUD_write(st->voice.out, st->buf + start, 
chunk);
+        total_written += written;
          rpos += written;
          to_transfer -= written;
          st->rpos += written;
          if (chunk != written) {
+            fprintf(stderr, "%s: could not write %u bytes\n", __func__,
+                    chunk - written);
              break;
          }
      }

      hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1));
+    if (avail != total_written) {
+        fprintf(stderr, "%s: to write %d, written %u\n", __func__,
+                avail, total_written);
+    }
  }

  static void hda_audio_compat_input_cb(void *opaque, int avail)
---snip--

Volker Rümelin (17):
   audio: change type of mix_buf and conv_buf
   audio: change type and name of the resample buffer
   audio: make the resampling code greedy
   audio: replace the resampling loop in audio_pcm_sw_write()
   audio: remove sw == NULL check
   audio: rename variables in audio_pcm_sw_write()
   audio: don't misuse audio_pcm_sw_write()
   audio: remove unused noop_conv() function
   audio/mixeng: calculate number of input frames
   audio: wire up st_rate_frames_in()
   audio: replace the resampling loop in audio_pcm_sw_read()
   audio: rename variables in audio_pcm_sw_read()
   audio/mixeng: calculate number of output frames
   audio: wire up st_rate_frames_out()
   audio: handle leftover audio frame from upsampling
   audio/audio_template: substitute sw->hw with hw
   audio: remove sw->ratio

  audio/audio.c          | 366 +++++++++++++++++++++--------------------
  audio/audio_int.h      |  12 +-
  audio/audio_template.h |  41 +++--
  audio/mixeng.c         |  73 ++++++++
  audio/mixeng.h         |   2 +
  audio/rate_template.h  |  21 ++-
  6 files changed, 304 insertions(+), 211 deletions(-)

-- 
2.35.3

Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Marc-André Lureau 1 year, 2 months ago
Hi

On Sun, Jan 15, 2023 at 5:10 PM Volker Rümelin <vr_qemu@t-online.de> wrote:
>
> Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
> ([PATCH 00/11] audio: more improvements)
>

Something didn't work with patchew
(https://patchew.org/QEMU/61bd351f-0683-7f58-b746-66c9578a7cdc@t-online.de/).
If you could rebase/resend, or share git branch that would be great.

Thanks


-- 
Marc-André Lureau
Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Volker Rümelin 1 year, 2 months ago
Am 31.01.23 um 15:53 schrieb Marc-André Lureau:
> Hi
>
> On Sun, Jan 15, 2023 at 5:10 PM Volker Rümelin <vr_qemu@t-online.de> wrote:
>> Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
>> ([PATCH 00/11] audio: more improvements)
>>
> Something didn't work with patchew
> (https://patchew.org/QEMU/61bd351f-0683-7f58-b746-66c9578a7cdc@t-online.de/).
> If you could rebase/resend, or share git branch that would be great.

Hi Marc-André,

I will resend the patch series rebased onto [PATCH v2 00/11] audio: more 
improvements. This time I will take care that patchew doesn't try to 
apply the cover letter as the first patch.

With best regards,
Volker

Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Volker Rümelin 1 year, 3 months ago
Am 15.01.23 um 14:08 schrieb Volker Rümelin:

Ccing a few more people who might be interested in this patch series.

@Mark:
After this patch series, the code in your out of tree ASC audio device 
(and a few in tree audio devices) could be simplified. write_audio() and 
the loops calling write_audio() could be removed.

With best regards,
Volker

> Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
> ([PATCH 00/11] audio: more improvements)
>
> The callback interface for emulated audio devices is strange. The 
> callback function has an 'avail' parameter that passes the number of 
> bytes that can be written or read. Unfortunately, this value sometimes 
> is only an imprecise estimate and the callback functions must check 
> the actual bytes written or read. For playback devices, this means 
> that they either need a ring buffer or have to write the unwritten 
> bytes again the next time. For recording devices, things are a bit 
> easier. They only need to continue with the actual number of bytes read.
>
> After this patch series, the 'avail' argument for the -audiodev 
> out.mixing-engine=on and in.mixing-engine=on cases is exact. Audio 
> frontends only need a linear frame buffer and there's a guarantee they 
> can write or read 'avail' bytes.
>
> The -audiodev out.mixing-engine=off case is also mostly accurate. Only 
> the D-Bus audio backend is still missing a required function. The 
> -audiodev in.mixing-engine=off case always passes a much too large 
> 'avail' value. I haven't worked on this yet, because there was no 
> reason for it so far.
>
> The following logs show the improvements. Not only the audio frontends 
> can write or read all needed or available bytes. The same is true for 
> the audio backends. For playback, the first six lines in the logs are 
> expected. Here you can see how quickly the guest fills the empty 
> downstream buffers after playback starts.
>
> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
> hda-duplex,audiodev=audio0 -audiodev 
> pa,out.frequency=96000,in.frequency=96000,id=audio0
>
> playback guest 44100Hz => host 96000Hz
>
> unpatched version:
> hda_audio_output_cb: to write 8188, written 1704
> audio_run_out: free 4458, played 926
> hda_audio_output_cb: to write 6488, written 2384
> audio_run_out: free 3532, played 1297
> hda_audio_output_cb: to write 4104, written 2648
> audio_run_out: free 2235, played 1441
> audio_run_out: free 794, played 793
> audio_run_out: free 897, played 896
> audio_run_out: free 831, played 829
> ...
> hda_audio_output_cb: could not write 4 bytes
> hda_audio_output_cb: to write 1764, written 1760
> audio_run_out: free 960, played 958
> ...
>
> patched version:
> hda_audio_output_cb: to write 8192, written 1620
> audio_run_out: free 4458, played 880
> hda_audio_output_cb: to write 6576, written 2508
> audio_run_out: free 3578, played 1365
> hda_audio_output_cb: to write 4068, written 2500
> audio_run_out: free 2213, played 1360
>
> record host 96000Hz => guest 44100Hz
>
> unpatched version:
> audio_run_in: avail 4458, acquired 4454
> audio_run_in: avail 1574, acquired 1572
> audio_run_in: avail 766, acquired 764
> audio_run_in: avail 1052, acquired 1051
> audio_run_in: avail 761, acquired 760
> audio_run_in: avail 1123, acquired 1121
> ...
> hda_audio_input_cb: could not read 4 bytes
> hda_audio_input_cb: to read 1988, read 1984
> audio_run_in: avail 1082, acquired 1080
> ...
>
> patched version:
> (no output)
>
> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
> hda-duplex,audiodev=audio0 -audiodev 
> pa,out.frequency=32000,in.frequency=32000,id=audio0
>
> playback guest 44100Hz => host 32000Hz
>
> unpatched version:
> hda_audio_output_cb: to write 8188, written 1620
> audio_run_out: free 1486, played 294
> hda_audio_output_cb: to write 6568, written 2512
> audio_run_out: free 1192, played 455
> hda_audio_output_cb: to write 4060, written 2504
> audio_run_out: free 737, played 455
> audio_run_out: free 282, played 281
> audio_run_out: free 357, played 356
> audio_run_out: free 314, played 313
> ...
> hda_audio_output_cb: could not write 4 bytes
> hda_audio_output_cb: to write 1416, written 1412
> audio_run_out: free 257, played 256
> ...
>
> patched version:
> hda_audio_output_cb: to write 8192, written 1656
> audio_run_out: free 1486, played 300
> hda_audio_output_cb: to write 6536, written 2516
> audio_run_out: free 1186, played 457
> hda_audio_output_cb: to write 4020, written 2540
> audio_run_out: free 729, played 460
>
> record host 32000Hz => guest 44100Hz
>
> unpatched version:
> audio_run_in: avail 1486, acquired 1485
> audio_run_in: avail 272, acquired 271
> audio_run_in: avail 366, acquired 365
> hda_audio_input_cb: could not read 4 bytes
> hda_audio_input_cb: to read 1420, read 1416
> audio_run_in: avail 258, acquired 257
> audio_run_in: avail 375, acquired 374
> hda_audio_input_cb: could not read 4 bytes
> hda_audio_input_cb: to read 2056, read 2052
> audio_run_in: avail 260, acquired 259
> ...
>
> patched version:
> (no output)
>
> This is the debug code for the logs above.
>
> ---snip--
> --- a/audio/audio.c    2022-12-13 19:14:31.793153558 +0100
> +++ b/audio/audio.c    2022-12-11 16:24:48.842649711 +0100
> @@ -1228,6 +1228,10 @@ static void audio_run_out (AudioState *s
>  #ifdef DEBUG_OUT
>          dolog("played=%zu\n", played);
>  #endif
> +        if (hw_free - played) {
> +            fprintf(stderr, "%s: free %zu, played %zu\n",
> +                    __func__, hw_free, played);
> +        }
>
>          if (played) {
>              hw->ts_helper += played;
> @@ -1318,6 +1322,7 @@ static void audio_run_in (AudioState *s)
>              if (sw->active) {
>                  size_t sw_avail = audio_get_avail(sw);
>                  size_t avail;
> +                size_t prev_acquired = sw->total_hw_samples_acquired;
>
>                  avail = st_rate_frames_out(sw->rate, sw_avail);
>                  if (avail > 0) {
> @@ -1325,6 +1330,11 @@ static void audio_run_in (AudioState *s)
>                      sw->callback.fn(sw->callback.opaque,
>                                      avail * sw->info.bytes_per_frame);
>                  }
> +
> +                if (sw_avail + prev_acquired - 
> sw->total_hw_samples_acquired) {
> +                    fprintf(stderr, "%s: avail %zu, acquired %zu\n", 
> __func__,
> +                            sw_avail, sw->total_hw_samples_acquired - 
> prev_acquired);
> +                }
>              }
>          }
>      }
> --- a/hw/audio/hda-codec.c    2023-01-04 14:07:31.954304889 +0100
> +++ b/hw/audio/hda-codec.c    2023-01-04 13:57:47.687320406 +0100
> @@ -265,20 +265,28 @@ static void hda_audio_input_cb(void *opa
>      int64_t rpos = st->rpos;
>
>      int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), avail);
> +    unsigned int total_read = 0;
>
>      while (to_transfer) {
>          uint32_t start = (uint32_t) (wpos & B_MASK);
>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>          uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk);
>          wpos += read;
> +        total_read += read;
>          to_transfer -= read;
>          st->wpos += read;
>          if (chunk != read) {
> +            fprintf(stderr, "%s: could not read %u bytes\n", __func__,
> +                    chunk - read);
>              break;
>          }
>      }
>
>      hda_timer_sync_adjust(st, -((wpos - rpos) - (B_SIZE >> 1)));
> +    if (avail != total_read) {
> +        fprintf(stderr, "%s: to read %d, read %u\n", __func__,
> +                avail, total_read);
> +    }
>  }
>
>  static void hda_audio_output_timer(void *opaque)
> @@ -329,6 +337,7 @@ static void hda_audio_output_cb(void *op
>      int64_t rpos = st->rpos;
>
>      int64_t to_transfer = MIN(wpos - rpos, avail);
> +    unsigned int total_written = 0;
>
>      if (wpos - rpos == B_SIZE) {
>          /* drop buffer, reset timer adjust */
> @@ -343,15 +352,22 @@ static void hda_audio_output_cb(void *op
>          uint32_t start = (uint32_t) (rpos & B_MASK);
>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>          uint32_t written = AUD_write(st->voice.out, st->buf + start, 
> chunk);
> +        total_written += written;
>          rpos += written;
>          to_transfer -= written;
>          st->rpos += written;
>          if (chunk != written) {
> +            fprintf(stderr, "%s: could not write %u bytes\n", __func__,
> +                    chunk - written);
>              break;
>          }
>      }
>
>      hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1));
> +    if (avail != total_written) {
> +        fprintf(stderr, "%s: to write %d, written %u\n", __func__,
> +                avail, total_written);
> +    }
>  }
>
>  static void hda_audio_compat_input_cb(void *opaque, int avail)
> ---snip--
>
> Volker Rümelin (17):
>   audio: change type of mix_buf and conv_buf
>   audio: change type and name of the resample buffer
>   audio: make the resampling code greedy
>   audio: replace the resampling loop in audio_pcm_sw_write()
>   audio: remove sw == NULL check
>   audio: rename variables in audio_pcm_sw_write()
>   audio: don't misuse audio_pcm_sw_write()
>   audio: remove unused noop_conv() function
>   audio/mixeng: calculate number of input frames
>   audio: wire up st_rate_frames_in()
>   audio: replace the resampling loop in audio_pcm_sw_read()
>   audio: rename variables in audio_pcm_sw_read()
>   audio/mixeng: calculate number of output frames
>   audio: wire up st_rate_frames_out()
>   audio: handle leftover audio frame from upsampling
>   audio/audio_template: substitute sw->hw with hw
>   audio: remove sw->ratio
>
>  audio/audio.c          | 366 +++++++++++++++++++++--------------------
>  audio/audio_int.h      |  12 +-
>  audio/audio_template.h |  41 +++--
>  audio/mixeng.c         |  73 ++++++++
>  audio/mixeng.h         |   2 +
>  audio/rate_template.h  |  21 ++-
>  6 files changed, 304 insertions(+), 211 deletions(-)
>


Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Mark Cave-Ayland 1 year, 2 months ago
On 15/01/2023 13:45, Volker Rümelin wrote:

> Am 15.01.23 um 14:08 schrieb Volker Rümelin:
> 
> Ccing a few more people who might be interested in this patch series.
> 
> @Mark:
> After this patch series, the code in your out of tree ASC audio device (and a few in 
> tree audio devices) could be simplified. write_audio() and the loops calling 
> write_audio() could be removed.

Hi Volker,

I know we have discussed this in a separate thread off-list, but this is fantastic!

Just out of interest, if the available bytes wraps the circular buffer will the audio 
core call the audio callback twice to maximise the ability of the guest to generate 
samples before the next audio timer? Or does that not make much difference in practice?

I'm not too familiar with the audio subsystem, but a quick skim of the series looks 
good (and being able to remove the write_audio() loops is a big plus). So I would 
certainly give this a thumbs up:

Acked-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>


ATB,

Mark.

> With best regards,
> Volker
> 
>> Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
>> ([PATCH 00/11] audio: more improvements)
>>
>> The callback interface for emulated audio devices is strange. The callback function 
>> has an 'avail' parameter that passes the number of bytes that can be written or 
>> read. Unfortunately, this value sometimes is only an imprecise estimate and the 
>> callback functions must check the actual bytes written or read. For playback 
>> devices, this means that they either need a ring buffer or have to write the 
>> unwritten bytes again the next time. For recording devices, things are a bit 
>> easier. They only need to continue with the actual number of bytes read.
>>
>> After this patch series, the 'avail' argument for the -audiodev 
>> out.mixing-engine=on and in.mixing-engine=on cases is exact. Audio frontends only 
>> need a linear frame buffer and there's a guarantee they can write or read 'avail' 
>> bytes.
>>
>> The -audiodev out.mixing-engine=off case is also mostly accurate. Only the D-Bus 
>> audio backend is still missing a required function. The -audiodev 
>> in.mixing-engine=off case always passes a much too large 'avail' value. I haven't 
>> worked on this yet, because there was no reason for it so far.
>>
>> The following logs show the improvements. Not only the audio frontends can write or 
>> read all needed or available bytes. The same is true for the audio backends. For 
>> playback, the first six lines in the logs are expected. Here you can see how 
>> quickly the guest fills the empty downstream buffers after playback starts.
>>
>> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
>> hda-duplex,audiodev=audio0 -audiodev 
>> pa,out.frequency=96000,in.frequency=96000,id=audio0
>>
>> playback guest 44100Hz => host 96000Hz
>>
>> unpatched version:
>> hda_audio_output_cb: to write 8188, written 1704
>> audio_run_out: free 4458, played 926
>> hda_audio_output_cb: to write 6488, written 2384
>> audio_run_out: free 3532, played 1297
>> hda_audio_output_cb: to write 4104, written 2648
>> audio_run_out: free 2235, played 1441
>> audio_run_out: free 794, played 793
>> audio_run_out: free 897, played 896
>> audio_run_out: free 831, played 829
>> ...
>> hda_audio_output_cb: could not write 4 bytes
>> hda_audio_output_cb: to write 1764, written 1760
>> audio_run_out: free 960, played 958
>> ...
>>
>> patched version:
>> hda_audio_output_cb: to write 8192, written 1620
>> audio_run_out: free 4458, played 880
>> hda_audio_output_cb: to write 6576, written 2508
>> audio_run_out: free 3578, played 1365
>> hda_audio_output_cb: to write 4068, written 2500
>> audio_run_out: free 2213, played 1360
>>
>> record host 96000Hz => guest 44100Hz
>>
>> unpatched version:
>> audio_run_in: avail 4458, acquired 4454
>> audio_run_in: avail 1574, acquired 1572
>> audio_run_in: avail 766, acquired 764
>> audio_run_in: avail 1052, acquired 1051
>> audio_run_in: avail 761, acquired 760
>> audio_run_in: avail 1123, acquired 1121
>> ...
>> hda_audio_input_cb: could not read 4 bytes
>> hda_audio_input_cb: to read 1988, read 1984
>> audio_run_in: avail 1082, acquired 1080
>> ...
>>
>> patched version:
>> (no output)
>>
>> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
>> hda-duplex,audiodev=audio0 -audiodev 
>> pa,out.frequency=32000,in.frequency=32000,id=audio0
>>
>> playback guest 44100Hz => host 32000Hz
>>
>> unpatched version:
>> hda_audio_output_cb: to write 8188, written 1620
>> audio_run_out: free 1486, played 294
>> hda_audio_output_cb: to write 6568, written 2512
>> audio_run_out: free 1192, played 455
>> hda_audio_output_cb: to write 4060, written 2504
>> audio_run_out: free 737, played 455
>> audio_run_out: free 282, played 281
>> audio_run_out: free 357, played 356
>> audio_run_out: free 314, played 313
>> ...
>> hda_audio_output_cb: could not write 4 bytes
>> hda_audio_output_cb: to write 1416, written 1412
>> audio_run_out: free 257, played 256
>> ...
>>
>> patched version:
>> hda_audio_output_cb: to write 8192, written 1656
>> audio_run_out: free 1486, played 300
>> hda_audio_output_cb: to write 6536, written 2516
>> audio_run_out: free 1186, played 457
>> hda_audio_output_cb: to write 4020, written 2540
>> audio_run_out: free 729, played 460
>>
>> record host 32000Hz => guest 44100Hz
>>
>> unpatched version:
>> audio_run_in: avail 1486, acquired 1485
>> audio_run_in: avail 272, acquired 271
>> audio_run_in: avail 366, acquired 365
>> hda_audio_input_cb: could not read 4 bytes
>> hda_audio_input_cb: to read 1420, read 1416
>> audio_run_in: avail 258, acquired 257
>> audio_run_in: avail 375, acquired 374
>> hda_audio_input_cb: could not read 4 bytes
>> hda_audio_input_cb: to read 2056, read 2052
>> audio_run_in: avail 260, acquired 259
>> ...
>>
>> patched version:
>> (no output)
>>
>> This is the debug code for the logs above.
>>
>> ---snip--
>> --- a/audio/audio.c    2022-12-13 19:14:31.793153558 +0100
>> +++ b/audio/audio.c    2022-12-11 16:24:48.842649711 +0100
>> @@ -1228,6 +1228,10 @@ static void audio_run_out (AudioState *s
>>  #ifdef DEBUG_OUT
>>          dolog("played=%zu\n", played);
>>  #endif
>> +        if (hw_free - played) {
>> +            fprintf(stderr, "%s: free %zu, played %zu\n",
>> +                    __func__, hw_free, played);
>> +        }
>>
>>          if (played) {
>>              hw->ts_helper += played;
>> @@ -1318,6 +1322,7 @@ static void audio_run_in (AudioState *s)
>>              if (sw->active) {
>>                  size_t sw_avail = audio_get_avail(sw);
>>                  size_t avail;
>> +                size_t prev_acquired = sw->total_hw_samples_acquired;
>>
>>                  avail = st_rate_frames_out(sw->rate, sw_avail);
>>                  if (avail > 0) {
>> @@ -1325,6 +1330,11 @@ static void audio_run_in (AudioState *s)
>>                      sw->callback.fn(sw->callback.opaque,
>>                                      avail * sw->info.bytes_per_frame);
>>                  }
>> +
>> +                if (sw_avail + prev_acquired - sw->total_hw_samples_acquired) {
>> +                    fprintf(stderr, "%s: avail %zu, acquired %zu\n", __func__,
>> +                            sw_avail, sw->total_hw_samples_acquired - prev_acquired);
>> +                }
>>              }
>>          }
>>      }
>> --- a/hw/audio/hda-codec.c    2023-01-04 14:07:31.954304889 +0100
>> +++ b/hw/audio/hda-codec.c    2023-01-04 13:57:47.687320406 +0100
>> @@ -265,20 +265,28 @@ static void hda_audio_input_cb(void *opa
>>      int64_t rpos = st->rpos;
>>
>>      int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), avail);
>> +    unsigned int total_read = 0;
>>
>>      while (to_transfer) {
>>          uint32_t start = (uint32_t) (wpos & B_MASK);
>>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>>          uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk);
>>          wpos += read;
>> +        total_read += read;
>>          to_transfer -= read;
>>          st->wpos += read;
>>          if (chunk != read) {
>> +            fprintf(stderr, "%s: could not read %u bytes\n", __func__,
>> +                    chunk - read);
>>              break;
>>          }
>>      }
>>
>>      hda_timer_sync_adjust(st, -((wpos - rpos) - (B_SIZE >> 1)));
>> +    if (avail != total_read) {
>> +        fprintf(stderr, "%s: to read %d, read %u\n", __func__,
>> +                avail, total_read);
>> +    }
>>  }
>>
>>  static void hda_audio_output_timer(void *opaque)
>> @@ -329,6 +337,7 @@ static void hda_audio_output_cb(void *op
>>      int64_t rpos = st->rpos;
>>
>>      int64_t to_transfer = MIN(wpos - rpos, avail);
>> +    unsigned int total_written = 0;
>>
>>      if (wpos - rpos == B_SIZE) {
>>          /* drop buffer, reset timer adjust */
>> @@ -343,15 +352,22 @@ static void hda_audio_output_cb(void *op
>>          uint32_t start = (uint32_t) (rpos & B_MASK);
>>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>>          uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk);
>> +        total_written += written;
>>          rpos += written;
>>          to_transfer -= written;
>>          st->rpos += written;
>>          if (chunk != written) {
>> +            fprintf(stderr, "%s: could not write %u bytes\n", __func__,
>> +                    chunk - written);
>>              break;
>>          }
>>      }
>>
>>      hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1));
>> +    if (avail != total_written) {
>> +        fprintf(stderr, "%s: to write %d, written %u\n", __func__,
>> +                avail, total_written);
>> +    }
>>  }
>>
>>  static void hda_audio_compat_input_cb(void *opaque, int avail)
>> ---snip--
>>
>> Volker Rümelin (17):
>>   audio: change type of mix_buf and conv_buf
>>   audio: change type and name of the resample buffer
>>   audio: make the resampling code greedy
>>   audio: replace the resampling loop in audio_pcm_sw_write()
>>   audio: remove sw == NULL check
>>   audio: rename variables in audio_pcm_sw_write()
>>   audio: don't misuse audio_pcm_sw_write()
>>   audio: remove unused noop_conv() function
>>   audio/mixeng: calculate number of input frames
>>   audio: wire up st_rate_frames_in()
>>   audio: replace the resampling loop in audio_pcm_sw_read()
>>   audio: rename variables in audio_pcm_sw_read()
>>   audio/mixeng: calculate number of output frames
>>   audio: wire up st_rate_frames_out()
>>   audio: handle leftover audio frame from upsampling
>>   audio/audio_template: substitute sw->hw with hw
>>   audio: remove sw->ratio
>>
>>  audio/audio.c          | 366 +++++++++++++++++++++--------------------
>>  audio/audio_int.h      |  12 +-
>>  audio/audio_template.h |  41 +++--
>>  audio/mixeng.c         |  73 ++++++++
>>  audio/mixeng.h         |   2 +
>>  audio/rate_template.h  |  21 ++-
>>  6 files changed, 304 insertions(+), 211 deletions(-)
>>
> 
> 


Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Volker Rümelin 1 year, 2 months ago
Am 22.01.23 um 19:13 schrieb Mark Cave-Ayland:
> On 15/01/2023 13:45, Volker Rümelin wrote:
>
>> Am 15.01.23 um 14:08 schrieb Volker Rümelin:
>>
>> Ccing a few more people who might be interested in this patch series.
>>
>> @Mark:
>> After this patch series, the code in your out of tree ASC audio 
>> device (and a few in tree audio devices) could be simplified. 
>> write_audio() and the loops calling write_audio() could be removed.
>
> Hi Volker,
>
> I know we have discussed this in a separate thread off-list, but this 
> is fantastic!
>
> Just out of interest, if the available bytes wraps the circular buffer 
> will the audio core call the audio callback twice to maximise the 
> ability of the guest to generate samples before the next audio timer? 
> Or does that not make much difference in practice?

Hi Mark,

I guess with circular buffer you refer to the mixing engine buffer. The 
audio system calls the callback once on every audio timer event. If the 
available bytes wrap the mixing engine ringbuffer, the 
audio_pcm_sw_resample_out() function uses two writes to write all 
available bytes. Compared to the unpatched version, nothing has changed 
in this regard. Of course the audio frontend devices are still free to 
write 'avail' bytes with multiple calls to AUD_write().

With best regards,
Volker

>
> I'm not too familiar with the audio subsystem, but a quick skim of the 
> series looks good (and being able to remove the write_audio() loops is 
> a big plus). So I would certainly give this a thumbs up:
>
> Acked-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
>
>
> ATB,
>
> Mark.
>
>> With best regards,
>> Volker
>>
>>> Based-on: <3b1404eb-a7c5-f64c-3e47-1397c54c45bb@t-online.de>
>>> ([PATCH 00/11] audio: more improvements)
>>>
>>> The callback interface for emulated audio devices is strange. The 
>>> callback function has an 'avail' parameter that passes the number of 
>>> bytes that can be written or read. Unfortunately, this value 
>>> sometimes is only an imprecise estimate and the callback functions 
>>> must check the actual bytes written or read. For playback devices, 
>>> this means that they either need a ring buffer or have to write the 
>>> unwritten bytes again the next time. For recording devices, things 
>>> are a bit easier. They only need to continue with the actual number 
>>> of bytes read.
>>>
>>> After this patch series, the 'avail' argument for the -audiodev 
>>> out.mixing-engine=on and in.mixing-engine=on cases is exact. Audio 
>>> frontends only need a linear frame buffer and there's a guarantee 
>>> they can write or read 'avail' bytes.
>>>
>>> The -audiodev out.mixing-engine=off case is also mostly accurate. 
>>> Only the D-Bus audio backend is still missing a required function. 
>>> The -audiodev in.mixing-engine=off case always passes a much too 
>>> large 'avail' value. I haven't worked on this yet, because there was 
>>> no reason for it so far.
>>>
>>> The following logs show the improvements. Not only the audio 
>>> frontends can write or read all needed or available bytes. The same 
>>> is true for the audio backends. For playback, the first six lines in 
>>> the logs are expected. Here you can see how quickly the guest fills 
>>> the empty downstream buffers after playback starts.
>>>
>>> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
>>> hda-duplex,audiodev=audio0 -audiodev 
>>> pa,out.frequency=96000,in.frequency=96000,id=audio0
>>>
>>> playback guest 44100Hz => host 96000Hz
>>>
>>> unpatched version:
>>> hda_audio_output_cb: to write 8188, written 1704
>>> audio_run_out: free 4458, played 926
>>> hda_audio_output_cb: to write 6488, written 2384
>>> audio_run_out: free 3532, played 1297
>>> hda_audio_output_cb: to write 4104, written 2648
>>> audio_run_out: free 2235, played 1441
>>> audio_run_out: free 794, played 793
>>> audio_run_out: free 897, played 896
>>> audio_run_out: free 831, played 829
>>> ...
>>> hda_audio_output_cb: could not write 4 bytes
>>> hda_audio_output_cb: to write 1764, written 1760
>>> audio_run_out: free 960, played 958
>>> ...
>>>
>>> patched version:
>>> hda_audio_output_cb: to write 8192, written 1620
>>> audio_run_out: free 4458, played 880
>>> hda_audio_output_cb: to write 6576, written 2508
>>> audio_run_out: free 3578, played 1365
>>> hda_audio_output_cb: to write 4068, written 2500
>>> audio_run_out: free 2213, played 1360
>>>
>>> record host 96000Hz => guest 44100Hz
>>>
>>> unpatched version:
>>> audio_run_in: avail 4458, acquired 4454
>>> audio_run_in: avail 1574, acquired 1572
>>> audio_run_in: avail 766, acquired 764
>>> audio_run_in: avail 1052, acquired 1051
>>> audio_run_in: avail 761, acquired 760
>>> audio_run_in: avail 1123, acquired 1121
>>> ...
>>> hda_audio_input_cb: could not read 4 bytes
>>> hda_audio_input_cb: to read 1988, read 1984
>>> audio_run_in: avail 1082, acquired 1080
>>> ...
>>>
>>> patched version:
>>> (no output)
>>>
>>> QEMU was started with -device ich9-intel-hda,addr=0x1b -device 
>>> hda-duplex,audiodev=audio0 -audiodev 
>>> pa,out.frequency=32000,in.frequency=32000,id=audio0
>>>
>>> playback guest 44100Hz => host 32000Hz
>>>
>>> unpatched version:
>>> hda_audio_output_cb: to write 8188, written 1620
>>> audio_run_out: free 1486, played 294
>>> hda_audio_output_cb: to write 6568, written 2512
>>> audio_run_out: free 1192, played 455
>>> hda_audio_output_cb: to write 4060, written 2504
>>> audio_run_out: free 737, played 455
>>> audio_run_out: free 282, played 281
>>> audio_run_out: free 357, played 356
>>> audio_run_out: free 314, played 313
>>> ...
>>> hda_audio_output_cb: could not write 4 bytes
>>> hda_audio_output_cb: to write 1416, written 1412
>>> audio_run_out: free 257, played 256
>>> ...
>>>
>>> patched version:
>>> hda_audio_output_cb: to write 8192, written 1656
>>> audio_run_out: free 1486, played 300
>>> hda_audio_output_cb: to write 6536, written 2516
>>> audio_run_out: free 1186, played 457
>>> hda_audio_output_cb: to write 4020, written 2540
>>> audio_run_out: free 729, played 460
>>>
>>> record host 32000Hz => guest 44100Hz
>>>
>>> unpatched version:
>>> audio_run_in: avail 1486, acquired 1485
>>> audio_run_in: avail 272, acquired 271
>>> audio_run_in: avail 366, acquired 365
>>> hda_audio_input_cb: could not read 4 bytes
>>> hda_audio_input_cb: to read 1420, read 1416
>>> audio_run_in: avail 258, acquired 257
>>> audio_run_in: avail 375, acquired 374
>>> hda_audio_input_cb: could not read 4 bytes
>>> hda_audio_input_cb: to read 2056, read 2052
>>> audio_run_in: avail 260, acquired 259
>>> ...
>>>
>>> patched version:
>>> (no output)
>>>
>>> This is the debug code for the logs above.
>>>
>>> ---snip--
>>> --- a/audio/audio.c    2022-12-13 19:14:31.793153558 +0100
>>> +++ b/audio/audio.c    2022-12-11 16:24:48.842649711 +0100
>>> @@ -1228,6 +1228,10 @@ static void audio_run_out (AudioState *s
>>>  #ifdef DEBUG_OUT
>>>          dolog("played=%zu\n", played);
>>>  #endif
>>> +        if (hw_free - played) {
>>> +            fprintf(stderr, "%s: free %zu, played %zu\n",
>>> +                    __func__, hw_free, played);
>>> +        }
>>>
>>>          if (played) {
>>>              hw->ts_helper += played;
>>> @@ -1318,6 +1322,7 @@ static void audio_run_in (AudioState *s)
>>>              if (sw->active) {
>>>                  size_t sw_avail = audio_get_avail(sw);
>>>                  size_t avail;
>>> +                size_t prev_acquired = sw->total_hw_samples_acquired;
>>>
>>>                  avail = st_rate_frames_out(sw->rate, sw_avail);
>>>                  if (avail > 0) {
>>> @@ -1325,6 +1330,11 @@ static void audio_run_in (AudioState *s)
>>> sw->callback.fn(sw->callback.opaque,
>>>                                      avail * sw->info.bytes_per_frame);
>>>                  }
>>> +
>>> +                if (sw_avail + prev_acquired - 
>>> sw->total_hw_samples_acquired) {
>>> +                    fprintf(stderr, "%s: avail %zu, acquired 
>>> %zu\n", __func__,
>>> +                            sw_avail, sw->total_hw_samples_acquired 
>>> - prev_acquired);
>>> +                }
>>>              }
>>>          }
>>>      }
>>> --- a/hw/audio/hda-codec.c    2023-01-04 14:07:31.954304889 +0100
>>> +++ b/hw/audio/hda-codec.c    2023-01-04 13:57:47.687320406 +0100
>>> @@ -265,20 +265,28 @@ static void hda_audio_input_cb(void *opa
>>>      int64_t rpos = st->rpos;
>>>
>>>      int64_t to_transfer = MIN(B_SIZE - (wpos - rpos), avail);
>>> +    unsigned int total_read = 0;
>>>
>>>      while (to_transfer) {
>>>          uint32_t start = (uint32_t) (wpos & B_MASK);
>>>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>>>          uint32_t read = AUD_read(st->voice.in, st->buf + start, 
>>> chunk);
>>>          wpos += read;
>>> +        total_read += read;
>>>          to_transfer -= read;
>>>          st->wpos += read;
>>>          if (chunk != read) {
>>> +            fprintf(stderr, "%s: could not read %u bytes\n", __func__,
>>> +                    chunk - read);
>>>              break;
>>>          }
>>>      }
>>>
>>>      hda_timer_sync_adjust(st, -((wpos - rpos) - (B_SIZE >> 1)));
>>> +    if (avail != total_read) {
>>> +        fprintf(stderr, "%s: to read %d, read %u\n", __func__,
>>> +                avail, total_read);
>>> +    }
>>>  }
>>>
>>>  static void hda_audio_output_timer(void *opaque)
>>> @@ -329,6 +337,7 @@ static void hda_audio_output_cb(void *op
>>>      int64_t rpos = st->rpos;
>>>
>>>      int64_t to_transfer = MIN(wpos - rpos, avail);
>>> +    unsigned int total_written = 0;
>>>
>>>      if (wpos - rpos == B_SIZE) {
>>>          /* drop buffer, reset timer adjust */
>>> @@ -343,15 +352,22 @@ static void hda_audio_output_cb(void *op
>>>          uint32_t start = (uint32_t) (rpos & B_MASK);
>>>          uint32_t chunk = (uint32_t) MIN(B_SIZE - start, to_transfer);
>>>          uint32_t written = AUD_write(st->voice.out, st->buf + 
>>> start, chunk);
>>> +        total_written += written;
>>>          rpos += written;
>>>          to_transfer -= written;
>>>          st->rpos += written;
>>>          if (chunk != written) {
>>> +            fprintf(stderr, "%s: could not write %u bytes\n", 
>>> __func__,
>>> +                    chunk - written);
>>>              break;
>>>          }
>>>      }
>>>
>>>      hda_timer_sync_adjust(st, (wpos - rpos) - (B_SIZE >> 1));
>>> +    if (avail != total_written) {
>>> +        fprintf(stderr, "%s: to write %d, written %u\n", __func__,
>>> +                avail, total_written);
>>> +    }
>>>  }
>>>
>>>  static void hda_audio_compat_input_cb(void *opaque, int avail)
>>> ---snip--
>>>
>>> Volker Rümelin (17):
>>>   audio: change type of mix_buf and conv_buf
>>>   audio: change type and name of the resample buffer
>>>   audio: make the resampling code greedy
>>>   audio: replace the resampling loop in audio_pcm_sw_write()
>>>   audio: remove sw == NULL check
>>>   audio: rename variables in audio_pcm_sw_write()
>>>   audio: don't misuse audio_pcm_sw_write()
>>>   audio: remove unused noop_conv() function
>>>   audio/mixeng: calculate number of input frames
>>>   audio: wire up st_rate_frames_in()
>>>   audio: replace the resampling loop in audio_pcm_sw_read()
>>>   audio: rename variables in audio_pcm_sw_read()
>>>   audio/mixeng: calculate number of output frames
>>>   audio: wire up st_rate_frames_out()
>>>   audio: handle leftover audio frame from upsampling
>>>   audio/audio_template: substitute sw->hw with hw
>>>   audio: remove sw->ratio
>>>
>>>  audio/audio.c          | 366 +++++++++++++++++++++--------------------
>>>  audio/audio_int.h      |  12 +-
>>>  audio/audio_template.h |  41 +++--
>>>  audio/mixeng.c         |  73 ++++++++
>>>  audio/mixeng.h         |   2 +
>>>  audio/rate_template.h  |  21 ++-
>>>  6 files changed, 304 insertions(+), 211 deletions(-)
>>>
>>
>>
>


Re: [PATCH 00/17] audio: improve callback interface for audio frontends
Posted by Mark Cave-Ayland 1 year, 2 months ago
On 28/01/2023 09:03, Volker Rümelin wrote:

> Am 22.01.23 um 19:13 schrieb Mark Cave-Ayland:
>> On 15/01/2023 13:45, Volker Rümelin wrote:
>>
>>> Am 15.01.23 um 14:08 schrieb Volker Rümelin:
>>>
>>> Ccing a few more people who might be interested in this patch series.
>>>
>>> @Mark:
>>> After this patch series, the code in your out of tree ASC audio device (and a few 
>>> in tree audio devices) could be simplified. write_audio() and the loops calling 
>>> write_audio() could be removed.
>>
>> Hi Volker,
>>
>> I know we have discussed this in a separate thread off-list, but this is fantastic!
>>
>> Just out of interest, if the available bytes wraps the circular buffer will the 
>> audio core call the audio callback twice to maximise the ability of the guest to 
>> generate samples before the next audio timer? Or does that not make much difference 
>> in practice?
> 
> Hi Mark,
> 
> I guess with circular buffer you refer to the mixing engine buffer. The audio system 
> calls the callback once on every audio timer event. If the available bytes wrap the 
> mixing engine ringbuffer, the audio_pcm_sw_resample_out() function uses two writes to 
> write all available bytes. Compared to the unpatched version, nothing has changed in 
> this regard. Of course the audio frontend devices are still free to write 'avail' 
> bytes with multiple calls to AUD_write().
> 
> With best regards,
> Volker

Yes that makes sense, thanks for confirming this. I'm sorry that I'm not familiar 
enough with the audio side to do a proper review but obviously the A-B still stands 
and I would certainly be keen to see this merged.


ATB,

Mark.

[PATCH 01/17] audio: change type of mix_buf and conv_buf
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Change the type of mix_buf in struct HWVoiceOut and conv_buf
in struct HWVoiceIn from STSampleBuffer * to STSampleBuffer.
However, a buffer pointer is still needed. For this reason in
struct STSampleBuffer samples[] is changed to *buffer.

This is a preparation for the next patch. The next patch will
add this line, which is not possible with the current struct
STSampleBuffer definition.

+        sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;

There are no functional changes.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c          | 106 ++++++++++++++++++++---------------------
 audio/audio_int.h      |   6 +--
 audio/audio_template.h |  19 ++++----
 3 files changed, 67 insertions(+), 64 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index fb0d4a2cac..6a17b3bb2f 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -521,8 +521,8 @@ static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
 static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
 {
     size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
-    if (audio_bug(__func__, live > hw->conv_buf->size)) {
-        dolog("live=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
+    if (audio_bug(__func__, live > hw->conv_buf.size)) {
+        dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
         return 0;
     }
     return live;
@@ -531,13 +531,13 @@ static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
 static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
 {
     size_t conv = 0;
-    STSampleBuffer *conv_buf = hw->conv_buf;
+    STSampleBuffer *conv_buf = &hw->conv_buf;
 
     while (samples) {
         uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
         size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
 
-        hw->conv(conv_buf->samples + conv_buf->pos, src, proc);
+        hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
         conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
         samples -= proc;
         conv += proc;
@@ -559,12 +559,12 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
     if (!live) {
         return 0;
     }
-    if (audio_bug(__func__, live > hw->conv_buf->size)) {
-        dolog("live_in=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
+    if (audio_bug(__func__, live > hw->conv_buf.size)) {
+        dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
         return 0;
     }
 
-    rpos = audio_ring_posb(hw->conv_buf->pos, live, hw->conv_buf->size);
+    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
 
     samples = size / sw->info.bytes_per_frame;
 
@@ -572,11 +572,11 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
     swlim = MIN (swlim, samples);
 
     while (swlim) {
-        src = hw->conv_buf->samples + rpos;
-        if (hw->conv_buf->pos > rpos) {
-            isamp = hw->conv_buf->pos - rpos;
+        src = hw->conv_buf.buffer + rpos;
+        if (hw->conv_buf.pos > rpos) {
+            isamp = hw->conv_buf.pos - rpos;
         } else {
-            isamp = hw->conv_buf->size - rpos;
+            isamp = hw->conv_buf.size - rpos;
         }
 
         if (!isamp) {
@@ -586,7 +586,7 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
 
         st_rate_flow (sw->rate, src, dst, &isamp, &osamp);
         swlim -= osamp;
-        rpos = (rpos + isamp) % hw->conv_buf->size;
+        rpos = (rpos + isamp) % hw->conv_buf.size;
         dst += osamp;
         ret += osamp;
         total += isamp;
@@ -634,8 +634,8 @@ static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
     if (nb_live1) {
         size_t live = smin;
 
-        if (audio_bug(__func__, live > hw->mix_buf->size)) {
-            dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
+        if (audio_bug(__func__, live > hw->mix_buf.size)) {
+            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
             return 0;
         }
         return live;
@@ -652,17 +652,17 @@ static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
 static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
 {
     size_t clipped = 0;
-    size_t pos = hw->mix_buf->pos;
+    size_t pos = hw->mix_buf.pos;
 
     while (len) {
-        st_sample *src = hw->mix_buf->samples + pos;
+        st_sample *src = hw->mix_buf.buffer + pos;
         uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
-        size_t samples_till_end_of_buf = hw->mix_buf->size - pos;
+        size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
         size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
 
         hw->clip(dst, src, samples_to_clip);
 
-        pos = (pos + samples_to_clip) % hw->mix_buf->size;
+        pos = (pos + samples_to_clip) % hw->mix_buf.size;
         len -= samples_to_clip;
         clipped += samples_to_clip;
     }
@@ -681,11 +681,11 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         return size;
     }
 
-    hwsamples = sw->hw->mix_buf->size;
+    hwsamples = sw->hw->mix_buf.size;
 
     live = sw->total_hw_samples_mixed;
     if (audio_bug(__func__, live > hwsamples)) {
-        dolog("live=%zu hw->mix_buf->size=%zu\n", live, hwsamples);
+        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hwsamples);
         return 0;
     }
 
@@ -696,7 +696,7 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         return 0;
     }
 
-    wpos = (sw->hw->mix_buf->pos + live) % hwsamples;
+    wpos = (sw->hw->mix_buf.pos + live) % hwsamples;
 
     dead = hwsamples - live;
     hw_free = audio_pcm_hw_get_free(sw->hw);
@@ -723,7 +723,7 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         st_rate_flow_mix (
             sw->rate,
             sw->buf + pos,
-            sw->hw->mix_buf->samples + wpos,
+            sw->hw->mix_buf.buffer + wpos,
             &isamp,
             &osamp
             );
@@ -987,9 +987,9 @@ static size_t audio_get_avail (SWVoiceIn *sw)
     }
 
     live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
-    if (audio_bug(__func__, live > sw->hw->conv_buf->size)) {
-        dolog("live=%zu sw->hw->conv_buf->size=%zu\n", live,
-              sw->hw->conv_buf->size);
+    if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
+        dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
+              sw->hw->conv_buf.size);
         return 0;
     }
 
@@ -1024,13 +1024,13 @@ static size_t audio_get_free(SWVoiceOut *sw)
 
     live = sw->total_hw_samples_mixed;
 
-    if (audio_bug(__func__, live > sw->hw->mix_buf->size)) {
-        dolog("live=%zu sw->hw->mix_buf->size=%zu\n", live,
-              sw->hw->mix_buf->size);
+    if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
+        dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
+              sw->hw->mix_buf.size);
         return 0;
     }
 
-    dead = sw->hw->mix_buf->size - live;
+    dead = sw->hw->mix_buf.size - live;
 
 #ifdef DEBUG_OUT
     dolog("%s: get_free live %zu dead %zu frontend frames %zu\n",
@@ -1054,12 +1054,12 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
 
             n = samples;
             while (n) {
-                size_t till_end_of_hw = hw->mix_buf->size - rpos2;
+                size_t till_end_of_hw = hw->mix_buf.size - rpos2;
                 size_t to_write = MIN(till_end_of_hw, n);
                 size_t bytes = to_write * hw->info.bytes_per_frame;
                 size_t written;
 
-                sw->buf = hw->mix_buf->samples + rpos2;
+                sw->buf = hw->mix_buf.buffer + rpos2;
                 written = audio_pcm_sw_write (sw, NULL, bytes);
                 if (written - bytes) {
                     dolog("Could not mix %zu bytes into a capture "
@@ -1068,14 +1068,14 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
                     break;
                 }
                 n -= to_write;
-                rpos2 = (rpos2 + to_write) % hw->mix_buf->size;
+                rpos2 = (rpos2 + to_write) % hw->mix_buf.size;
             }
         }
     }
 
-    n = MIN(samples, hw->mix_buf->size - rpos);
-    mixeng_clear(hw->mix_buf->samples + rpos, n);
-    mixeng_clear(hw->mix_buf->samples, samples - n);
+    n = MIN(samples, hw->mix_buf.size - rpos);
+    mixeng_clear(hw->mix_buf.buffer + rpos, n);
+    mixeng_clear(hw->mix_buf.buffer, samples - n);
 }
 
 static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
@@ -1101,7 +1101,7 @@ static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
 
         live -= proc;
         clipped += proc;
-        hw->mix_buf->pos = (hw->mix_buf->pos + proc) % hw->mix_buf->size;
+        hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
 
         if (proc == 0 || proc < decr) {
             break;
@@ -1172,8 +1172,8 @@ static void audio_run_out (AudioState *s)
             live = 0;
         }
 
-        if (audio_bug(__func__, live > hw->mix_buf->size)) {
-            dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
+        if (audio_bug(__func__, live > hw->mix_buf.size)) {
+            dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
             continue;
         }
 
@@ -1201,13 +1201,13 @@ static void audio_run_out (AudioState *s)
             continue;
         }
 
-        prev_rpos = hw->mix_buf->pos;
+        prev_rpos = hw->mix_buf.pos;
         played = audio_pcm_hw_run_out(hw, live);
         replay_audio_out(&played);
-        if (audio_bug(__func__, hw->mix_buf->pos >= hw->mix_buf->size)) {
-            dolog("hw->mix_buf->pos=%zu hw->mix_buf->size=%zu played=%zu\n",
-                  hw->mix_buf->pos, hw->mix_buf->size, played);
-            hw->mix_buf->pos = 0;
+        if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
+            dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
+                  hw->mix_buf.pos, hw->mix_buf.size, played);
+            hw->mix_buf.pos = 0;
         }
 
 #ifdef DEBUG_OUT
@@ -1288,10 +1288,10 @@ static void audio_run_in (AudioState *s)
 
         if (replay_mode != REPLAY_MODE_PLAY) {
             captured = audio_pcm_hw_run_in(
-                hw, hw->conv_buf->size - audio_pcm_hw_get_live_in(hw));
+                hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
         }
-        replay_audio_in(&captured, hw->conv_buf->samples, &hw->conv_buf->pos,
-                        hw->conv_buf->size);
+        replay_audio_in(&captured, hw->conv_buf.buffer, &hw->conv_buf.pos,
+                        hw->conv_buf.size);
 
         min = audio_pcm_hw_find_min_in (hw);
         hw->total_samples_captured += captured - min;
@@ -1324,14 +1324,14 @@ static void audio_run_capture (AudioState *s)
         SWVoiceOut *sw;
 
         captured = live = audio_pcm_hw_get_live_out (hw, NULL);
-        rpos = hw->mix_buf->pos;
+        rpos = hw->mix_buf.pos;
         while (live) {
-            size_t left = hw->mix_buf->size - rpos;
+            size_t left = hw->mix_buf.size - rpos;
             size_t to_capture = MIN(live, left);
             struct st_sample *src;
             struct capture_callback *cb;
 
-            src = hw->mix_buf->samples + rpos;
+            src = hw->mix_buf.buffer + rpos;
             hw->clip (cap->buf, src, to_capture);
             mixeng_clear (src, to_capture);
 
@@ -1339,10 +1339,10 @@ static void audio_run_capture (AudioState *s)
                 cb->ops.capture (cb->opaque, cap->buf,
                                  to_capture * hw->info.bytes_per_frame);
             }
-            rpos = (rpos + to_capture) % hw->mix_buf->size;
+            rpos = (rpos + to_capture) % hw->mix_buf.size;
             live -= to_capture;
         }
-        hw->mix_buf->pos = rpos;
+        hw->mix_buf.pos = rpos;
 
         for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
             if (!sw->active && sw->empty) {
@@ -1901,7 +1901,7 @@ CaptureVoiceOut *AUD_add_capture(
 
         audio_pcm_init_info (&hw->info, as);
 
-        cap->buf = g_malloc0_n(hw->mix_buf->size, hw->info.bytes_per_frame);
+        cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
 
         if (hw->info.is_float) {
             hw->clip = mixeng_clip_float[hw->info.nchannels == 2];
@@ -1953,7 +1953,7 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
                     sw = sw1;
                 }
                 QLIST_REMOVE (cap, entries);
-                g_free (cap->hw.mix_buf);
+                g_free(cap->hw.mix_buf.buffer);
                 g_free (cap->buf);
                 g_free (cap);
             }
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 9d04be9128..900b0a6255 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -58,7 +58,7 @@ typedef struct SWVoiceCap SWVoiceCap;
 
 typedef struct STSampleBuffer {
     size_t pos, size;
-    st_sample samples[];
+    st_sample *buffer;
 } STSampleBuffer;
 
 typedef struct HWVoiceOut {
@@ -71,7 +71,7 @@ typedef struct HWVoiceOut {
     f_sample *clip;
     uint64_t ts_helper;
 
-    STSampleBuffer *mix_buf;
+    STSampleBuffer mix_buf;
     void *buf_emul;
     size_t pos_emul, pending_emul, size_emul;
 
@@ -93,7 +93,7 @@ typedef struct HWVoiceIn {
     size_t total_samples_captured;
     uint64_t ts_helper;
 
-    STSampleBuffer *conv_buf;
+    STSampleBuffer conv_buf;
     void *buf_emul;
     size_t pos_emul, pending_emul, size_emul;
 
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 9c600448fb..9283f00e9e 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -71,8 +71,9 @@ static void glue(audio_init_nb_voices_, TYPE)(AudioState *s,
 static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw)
 {
     g_free(hw->buf_emul);
-    g_free (HWBUF);
-    HWBUF = NULL;
+    g_free(HWBUF.buffer);
+    HWBUF.buffer = NULL;
+    HWBUF.size = 0;
 }
 
 static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
@@ -83,10 +84,12 @@ static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
             dolog("Attempted to allocate empty buffer\n");
         }
 
-        HWBUF = g_malloc0(sizeof(STSampleBuffer) + sizeof(st_sample) * samples);
-        HWBUF->size = samples;
+        HWBUF.buffer = g_new0(st_sample, samples);
+        HWBUF.size = samples;
+        HWBUF.pos = 0;
     } else {
-        HWBUF = NULL;
+        HWBUF.buffer = NULL;
+        HWBUF.size = 0;
     }
 }
 
@@ -111,16 +114,16 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
     }
 
 #ifdef DAC
-    samples = ((int64_t) sw->HWBUF->size << 32) / sw->ratio;
+    samples = ((int64_t)sw->HWBUF.size << 32) / sw->ratio;
 #else
-    samples = (int64_t)sw->HWBUF->size * sw->ratio >> 32;
+    samples = (int64_t)sw->HWBUF.size * sw->ratio >> 32;
 #endif
     if (samples == 0) {
         HW *hw = sw->hw;
         size_t f_fe_min;
 
         /* f_fe_min = ceil(1 [frames] * f_be [Hz] / size_be [frames]) */
-        f_fe_min = (hw->info.freq + HWBUF->size - 1) / HWBUF->size;
+        f_fe_min = (hw->info.freq + HWBUF.size - 1) / HWBUF.size;
         qemu_log_mask(LOG_UNIMP,
                       AUDIO_CAP ": The guest selected a " NAME " sample rate"
                       " of %d Hz for %s. Only sample rates >= %zu Hz are"
-- 
2.35.3


[PATCH 02/17] audio: change type and name of the resample buffer
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Change the type of the resample buffer from struct st_sample *
to STSampleBuffer. Also change the name from buf to resample_buf
for better readability.

The new variables resample_buf.size and resample_buf.pos will be
used after the next patches. There is no functional change.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c          | 15 ++++++++-------
 audio/audio_int.h      |  4 ++--
 audio/audio_template.h | 10 ++++++----
 3 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 6a17b3bb2f..22ec7d3093 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -553,7 +553,7 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
 {
     HWVoiceIn *hw = sw->hw;
     size_t samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0;
-    struct st_sample *src, *dst = sw->buf;
+    struct st_sample *src, *dst = sw->resample_buf.buffer;
 
     live = hw->total_samples_captured - sw->total_hw_samples_acquired;
     if (!live) {
@@ -593,10 +593,10 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
     }
 
     if (!hw->pcm_ops->volume_in) {
-        mixeng_volume (sw->buf, ret, &sw->vol);
+        mixeng_volume(sw->resample_buf.buffer, ret, &sw->vol);
     }
 
-    sw->clip (buf, sw->buf, ret);
+    sw->clip(buf, sw->resample_buf.buffer, ret);
     sw->total_hw_samples_acquired += total;
     return ret * sw->info.bytes_per_frame;
 }
@@ -704,10 +704,10 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
     samples = ((int64_t)MIN(dead, hw_free) << 32) / sw->ratio;
     samples = MIN(samples, size / sw->info.bytes_per_frame);
     if (samples) {
-        sw->conv(sw->buf, buf, samples);
+        sw->conv(sw->resample_buf.buffer, buf, samples);
 
         if (!sw->hw->pcm_ops->volume_out) {
-            mixeng_volume(sw->buf, samples, &sw->vol);
+            mixeng_volume(sw->resample_buf.buffer, samples, &sw->vol);
         }
     }
 
@@ -722,7 +722,7 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         osamp = blck;
         st_rate_flow_mix (
             sw->rate,
-            sw->buf + pos,
+            sw->resample_buf.buffer + pos,
             sw->hw->mix_buf.buffer + wpos,
             &isamp,
             &osamp
@@ -1059,7 +1059,8 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
                 size_t bytes = to_write * hw->info.bytes_per_frame;
                 size_t written;
 
-                sw->buf = hw->mix_buf.buffer + rpos2;
+                sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
+                sw->resample_buf.size = to_write;
                 written = audio_pcm_sw_write (sw, NULL, bytes);
                 if (written - bytes) {
                     dolog("Could not mix %zu bytes into a capture "
diff --git a/audio/audio_int.h b/audio/audio_int.h
index 900b0a6255..f4ec5dcf11 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -109,7 +109,7 @@ struct SWVoiceOut {
     struct audio_pcm_info info;
     t_sample *conv;
     int64_t ratio;
-    struct st_sample *buf;
+    STSampleBuffer resample_buf;
     void *rate;
     size_t total_hw_samples_mixed;
     int active;
@@ -129,7 +129,7 @@ struct SWVoiceIn {
     int64_t ratio;
     void *rate;
     size_t total_hw_samples_acquired;
-    struct st_sample *buf;
+    STSampleBuffer resample_buf;
     f_sample *clip;
     HWVoiceIn *hw;
     char *name;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 9283f00e9e..07c14e7821 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -95,13 +95,13 @@ static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw)
 
 static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw)
 {
-    g_free (sw->buf);
+    g_free(sw->resample_buf.buffer);
+    sw->resample_buf.buffer = NULL;
+    sw->resample_buf.size = 0;
 
     if (sw->rate) {
         st_rate_stop (sw->rate);
     }
-
-    sw->buf = NULL;
     sw->rate = NULL;
 }
 
@@ -132,7 +132,9 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
         return -1;
     }
 
-    sw->buf = g_new0(st_sample, samples);
+    sw->resample_buf.buffer = g_new0(st_sample, samples);
+    sw->resample_buf.size = samples;
+    sw->resample_buf.pos = 0;
 
 #ifdef DAC
     sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq);
-- 
2.35.3


[PATCH 03/17] audio: make the resampling code greedy
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Read the maximum possible number of audio frames instead of the
minimum necessary number of frames when the audio stream is
downsampled and the output buffer is limited. This makes the
function symmetrical to upsampling when the input buffer is
limited. The maximum possible number of frames is written here.

With this change it's easier to calculate the exact number of
audio frames the resample function will read or write. These two
functions will be introduced later.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/rate_template.h | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/audio/rate_template.h b/audio/rate_template.h
index b432719ebb..6648f0d2e5 100644
--- a/audio/rate_template.h
+++ b/audio/rate_template.h
@@ -40,8 +40,6 @@ void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf,
     int64_t t;
 #endif
 
-    ilast = rate->ilast;
-
     istart = ibuf;
     iend = ibuf + *isamp;
 
@@ -59,15 +57,17 @@ void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf,
         return;
     }
 
-    while (obuf < oend) {
+    /* without input samples, there's nothing to do */
+    if (ibuf >= iend) {
+        *osamp = 0;
+        return;
+    }
 
-        /* Safety catch to make sure we have input samples.  */
-        if (ibuf >= iend) {
-            break;
-        }
+    ilast = rate->ilast;
 
-        /* read as many input samples so that ipos > opos */
+    while (true) {
 
+        /* read as many input samples so that ipos > opos */
         while (rate->ipos <= (rate->opos >> 32)) {
             ilast = *ibuf++;
             rate->ipos++;
@@ -78,6 +78,11 @@ void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf,
             }
         }
 
+        /* make sure that the next output sample can be written */
+        if (obuf >= oend) {
+            break;
+        }
+
         icur = *ibuf;
 
         /* wrap ipos and opos around long before they overflow */
-- 
2.35.3


[PATCH 04/17] audio: replace the resampling loop in audio_pcm_sw_write()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Replace the resampling loop in audio_pcm_sw_write() with the new
function audio_pcm_sw_resample_out(). Unlike the old resample
loop the new function will try to consume input frames even if
the output buffer is full. This is necessary when downsampling
to avoid reading less audio frames than calculated in advance.
The loop was unrolled to avoid complicated loop control conditions
in this case.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 63 +++++++++++++++++++++++++++++----------------------
 1 file changed, 36 insertions(+), 27 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 22ec7d3093..b0a270ba85 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -671,11 +671,44 @@ static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
 /*
  * Soft voice (playback)
  */
+static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
+    size_t frames_in_max, size_t frames_out_max,
+    size_t *total_in, size_t *total_out)
+{
+    HWVoiceOut *hw = sw->hw;
+    struct st_sample *src, *dst;
+    size_t live, wpos, frames_in, frames_out;
+
+    live = sw->total_hw_samples_mixed;
+    wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
+
+    /* write to mix_buf from wpos to end of buffer */
+    src = sw->resample_buf.buffer;
+    frames_in = frames_in_max;
+    dst = hw->mix_buf.buffer + wpos;
+    frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
+    st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+    wpos += frames_out;
+    *total_in = frames_in;
+    *total_out = frames_out;
+
+    /* write to mix_buf from start of buffer if there are input frames left */
+    if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
+        src += frames_in;
+        frames_in = frames_in_max - frames_in;
+        dst = hw->mix_buf.buffer;
+        frames_out = frames_out_max - frames_out;
+        st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
+        *total_in += frames_in;
+        *total_out += frames_out;
+    }
+}
+
 static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
 {
-    size_t hwsamples, samples, isamp, osamp, wpos, live, dead, left, blck;
+    size_t hwsamples, samples, live, dead;
     size_t hw_free;
-    size_t ret = 0, pos = 0, total = 0;
+    size_t ret, total;
 
     if (!sw) {
         return size;
@@ -696,8 +729,6 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         return 0;
     }
 
-    wpos = (sw->hw->mix_buf.pos + live) % hwsamples;
-
     dead = hwsamples - live;
     hw_free = audio_pcm_hw_get_free(sw->hw);
     hw_free = hw_free > live ? hw_free - live : 0;
@@ -711,29 +742,7 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
         }
     }
 
-    while (samples) {
-        dead = hwsamples - live;
-        left = hwsamples - wpos;
-        blck = MIN (dead, left);
-        if (!blck) {
-            break;
-        }
-        isamp = samples;
-        osamp = blck;
-        st_rate_flow_mix (
-            sw->rate,
-            sw->resample_buf.buffer + pos,
-            sw->hw->mix_buf.buffer + wpos,
-            &isamp,
-            &osamp
-            );
-        ret += isamp;
-        samples -= isamp;
-        pos += isamp;
-        live += osamp;
-        wpos = (wpos + osamp) % hwsamples;
-        total += osamp;
-    }
+    audio_pcm_sw_resample_out(sw, samples, MIN(dead, hw_free), &ret, &total);
 
     sw->total_hw_samples_mixed += total;
     sw->empty = sw->total_hw_samples_mixed == 0;
-- 
2.35.3


[PATCH 05/17] audio: remove sw == NULL check
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

All call sites of audio_pcm_sw_write() guarantee that sw is not
NULL. Remove the unnecessary NULL check.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index b0a270ba85..9d6ffa500a 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -710,10 +710,6 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
     size_t hw_free;
     size_t ret, total;
 
-    if (!sw) {
-        return size;
-    }
-
     hwsamples = sw->hw->mix_buf.size;
 
     live = sw->total_hw_samples_mixed;
-- 
2.35.3


[PATCH 06/17] audio: rename variables in audio_pcm_sw_write()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

The audio_pcm_sw_write() function uses a lot of very unspecific
variable names. Rename them for better readability.

ret => total_in
total => total_out
size => buf_len
hwsamples => hw->mix_buf.size
samples => frames_in_max

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 45 ++++++++++++++++++++++-----------------------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 9d6ffa500a..a8571100ff 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -704,56 +704,55 @@ static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
     }
 }
 
-static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
 {
-    size_t hwsamples, samples, live, dead;
-    size_t hw_free;
-    size_t ret, total;
-
-    hwsamples = sw->hw->mix_buf.size;
+    HWVoiceOut *hw = sw->hw;
+    size_t live, dead, hw_free;
+    size_t frames_in_max, total_in, total_out;
 
     live = sw->total_hw_samples_mixed;
-    if (audio_bug(__func__, live > hwsamples)) {
-        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hwsamples);
+    if (audio_bug(__func__, live > hw->mix_buf.size)) {
+        dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
         return 0;
     }
 
-    if (live == hwsamples) {
+    if (live == hw->mix_buf.size) {
 #ifdef DEBUG_OUT
         dolog ("%s is full %zu\n", sw->name, live);
 #endif
         return 0;
     }
 
-    dead = hwsamples - live;
-    hw_free = audio_pcm_hw_get_free(sw->hw);
+    dead = hw->mix_buf.size - live;
+    hw_free = audio_pcm_hw_get_free(hw);
     hw_free = hw_free > live ? hw_free - live : 0;
-    samples = ((int64_t)MIN(dead, hw_free) << 32) / sw->ratio;
-    samples = MIN(samples, size / sw->info.bytes_per_frame);
-    if (samples) {
-        sw->conv(sw->resample_buf.buffer, buf, samples);
+    frames_in_max = ((int64_t)MIN(dead, hw_free) << 32) / sw->ratio;
+    frames_in_max = MIN(frames_in_max, buf_len / sw->info.bytes_per_frame);
+    if (frames_in_max) {
+        sw->conv(sw->resample_buf.buffer, buf, frames_in_max);
 
         if (!sw->hw->pcm_ops->volume_out) {
-            mixeng_volume(sw->resample_buf.buffer, samples, &sw->vol);
+            mixeng_volume(sw->resample_buf.buffer, frames_in_max, &sw->vol);
         }
     }
 
-    audio_pcm_sw_resample_out(sw, samples, MIN(dead, hw_free), &ret, &total);
+    audio_pcm_sw_resample_out(sw, frames_in_max, MIN(dead, hw_free),
+                              &total_in, &total_out);
 
-    sw->total_hw_samples_mixed += total;
+    sw->total_hw_samples_mixed += total_out;
     sw->empty = sw->total_hw_samples_mixed == 0;
 
 #ifdef DEBUG_OUT
     dolog (
-        "%s: write size %zu ret %zu total sw %zu\n",
-        SW_NAME (sw),
-        size / sw->info.bytes_per_frame,
-        ret,
+        "%s: write size %zu written %zu total mixed %zu\n",
+        SW_NAME(sw),
+        buf_len / sw->info.bytes_per_frame,
+        total_in,
         sw->total_hw_samples_mixed
         );
 #endif
 
-    return ret * sw->info.bytes_per_frame;
+    return total_in * sw->info.bytes_per_frame;
 }
 
 #ifdef DEBUG_AUDIO
-- 
2.35.3


[PATCH 07/17] audio: don't misuse audio_pcm_sw_write()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

The audio_pcm_sw_write() function is intended to convert a
PCM audio stream to the internal representation, adjust the
volume, and then mix it with the other audio streams with a
possibly changed sample rate in mix_buf. In order for the
audio_capture_mix_and_clear() function to use audio_pcm_sw_write(),
it must bypass the first two tasks of audio_pcm_sw_write().

Since patch "audio: split out the resampling loop in
audio_pcm_sw_write()" this is no longer necessary, because now
the audio_pcm_sw_resample_out() function can be used instead of
audio_pcm_sw_write().

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 29 ++++++++++++++++++-----------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index a8571100ff..0cfd56850f 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -1054,26 +1054,33 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
 
         for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
             SWVoiceOut *sw = &sc->sw;
-            int rpos2 = rpos;
+            size_t rpos2 = rpos;
 
             n = samples;
             while (n) {
                 size_t till_end_of_hw = hw->mix_buf.size - rpos2;
-                size_t to_write = MIN(till_end_of_hw, n);
-                size_t bytes = to_write * hw->info.bytes_per_frame;
-                size_t written;
+                size_t to_read = MIN(till_end_of_hw, n);
+                size_t live, frames_in, frames_out;
 
                 sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
-                sw->resample_buf.size = to_write;
-                written = audio_pcm_sw_write (sw, NULL, bytes);
-                if (written - bytes) {
-                    dolog("Could not mix %zu bytes into a capture "
+                sw->resample_buf.size = to_read;
+                live = sw->total_hw_samples_mixed;
+
+                audio_pcm_sw_resample_out(sw,
+                                          to_read, sw->hw->mix_buf.size - live,
+                                          &frames_in, &frames_out);
+
+                sw->total_hw_samples_mixed += frames_out;
+                sw->empty = sw->total_hw_samples_mixed == 0;
+
+                if (to_read - frames_in) {
+                    dolog("Could not mix %zu frames into a capture "
                           "buffer, mixed %zu\n",
-                          bytes, written);
+                          to_read, frames_in);
                     break;
                 }
-                n -= to_write;
-                rpos2 = (rpos2 + to_write) % hw->mix_buf.size;
+                n -= to_read;
+                rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
             }
         }
     }
-- 
2.35.3


[PATCH 08/17] audio: remove unused noop_conv() function
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

The function audio_capture_mix_and_clear() no longer uses
audio_pcm_sw_write() to resample audio frames from one internal
buffer to another. For this reason, the noop_conv() function is
now unused. Remove it.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 0cfd56850f..9c0855fb13 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -379,13 +379,6 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
 /*
  * Capture
  */
-static void noop_conv (struct st_sample *dst, const void *src, int samples)
-{
-    (void) src;
-    (void) dst;
-    (void) samples;
-}
-
 static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioState *s,
                                                         struct audsettings *as)
 {
@@ -483,7 +476,6 @@ static int audio_attach_capture (HWVoiceOut *hw)
         sw->info = hw->info;
         sw->empty = 1;
         sw->active = hw->enabled;
-        sw->conv = noop_conv;
         sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq;
         sw->vol = nominal_volume;
         sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
-- 
2.35.3


[PATCH 09/17] audio/mixeng: calculate number of input frames
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Calculate the exact number of audio input frames needed to get
a given number of audio output frames. The exact number of
frames depends only on the difference of opos - ipos and the
number of output frames. When downsampling, this function
returns the maximum number of input frames needed.

This function will later replace the audio_frontend_frames_out()
function, which calculates the average number of input frames
rounded down to the nearest integer.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/mixeng.c | 36 ++++++++++++++++++++++++++++++++++++
 audio/mixeng.h |  1 +
 2 files changed, 37 insertions(+)

diff --git a/audio/mixeng.c b/audio/mixeng.c
index fe454e0725..6bb3d54f77 100644
--- a/audio/mixeng.c
+++ b/audio/mixeng.c
@@ -440,6 +440,42 @@ void st_rate_stop (void *opaque)
     g_free (opaque);
 }
 
+/**
+ * st_rate_frames_in() - returns the number of frames needed to
+ * get frames_out frames after resampling
+ *
+ * @opaque: pointer to struct rate
+ * @frames_out: number of frames
+ */
+uint32_t st_rate_frames_in(void *opaque, uint32_t frames_out)
+{
+    struct rate *rate = opaque;
+    uint64_t opos_start, opos_end;
+    uint32_t ipos_start, ipos_end;
+
+    if (rate->opos_inc == 1ULL << 32) {
+        return frames_out;
+    }
+
+    if (frames_out) {
+        opos_start = rate->opos;
+        ipos_start = rate->ipos;
+    } else {
+        uint64_t offset;
+
+        /* add offset = ceil(opos_inc) to opos and ipos to avoid an underflow */
+        offset = (rate->opos_inc + (1ULL << 32) - 1) & ~((1ULL << 32) - 1);
+        opos_start = rate->opos + offset;
+        ipos_start = rate->ipos + (offset >> 32);
+    }
+    /* last frame written was at opos_start - rate->opos_inc */
+    opos_end = opos_start - rate->opos_inc + rate->opos_inc * frames_out;
+    ipos_end = (opos_end >> 32) + 1;
+
+    /* last frame read was at ipos_start - 1 */
+    return ipos_end + 1 > ipos_start ? ipos_end + 1 - ipos_start : 0;
+}
+
 void mixeng_clear (struct st_sample *buf, int len)
 {
     memset (buf, 0, len * sizeof (struct st_sample));
diff --git a/audio/mixeng.h b/audio/mixeng.h
index 2dcd6df245..64c1e231cc 100644
--- a/audio/mixeng.h
+++ b/audio/mixeng.h
@@ -52,6 +52,7 @@ void st_rate_flow(void *opaque, st_sample *ibuf, st_sample *obuf,
 void st_rate_flow_mix(void *opaque, st_sample *ibuf, st_sample *obuf,
                       size_t *isamp, size_t *osamp);
 void st_rate_stop (void *opaque);
+uint32_t st_rate_frames_in(void *opaque, uint32_t frames_out);
 void mixeng_clear (struct st_sample *buf, int len);
 void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol);
 
-- 
2.35.3


[PATCH 10/17] audio: wire up st_rate_frames_in()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Wire up the st_rate_frames_in() function and replace
audio_frontend_frames_out() to make audio packet length
calculation exact. When upsampling, it's still possible that
the audio frontends can't write the last audio frame. This will
be fixed later.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 43 ++++++++++++++++++-------------------------
 1 file changed, 18 insertions(+), 25 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 9c0855fb13..3d3b5e5b91 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -699,8 +699,8 @@ static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
 static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
 {
     HWVoiceOut *hw = sw->hw;
-    size_t live, dead, hw_free;
-    size_t frames_in_max, total_in, total_out;
+    size_t live, dead, hw_free, sw_max, fe_max;
+    size_t frames_in_max, frames_out_max, total_in, total_out;
 
     live = sw->total_hw_samples_mixed;
     if (audio_bug(__func__, live > hw->mix_buf.size)) {
@@ -718,17 +718,21 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
     dead = hw->mix_buf.size - live;
     hw_free = audio_pcm_hw_get_free(hw);
     hw_free = hw_free > live ? hw_free - live : 0;
-    frames_in_max = ((int64_t)MIN(dead, hw_free) << 32) / sw->ratio;
-    frames_in_max = MIN(frames_in_max, buf_len / sw->info.bytes_per_frame);
-    if (frames_in_max) {
-        sw->conv(sw->resample_buf.buffer, buf, frames_in_max);
+    frames_out_max = MIN(dead, hw_free);
+    sw_max = st_rate_frames_in(sw->rate, frames_out_max);
+    fe_max = MIN(buf_len / sw->info.bytes_per_frame, sw->resample_buf.size);
+    frames_in_max = MIN(sw_max, fe_max);
 
-        if (!sw->hw->pcm_ops->volume_out) {
-            mixeng_volume(sw->resample_buf.buffer, frames_in_max, &sw->vol);
-        }
+    if (!frames_in_max) {
+        return 0;
     }
 
-    audio_pcm_sw_resample_out(sw, frames_in_max, MIN(dead, hw_free),
+    sw->conv(sw->resample_buf.buffer, buf, frames_in_max);
+    if (!sw->hw->pcm_ops->volume_out) {
+        mixeng_volume(sw->resample_buf.buffer, frames_in_max, &sw->vol);
+    }
+
+    audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
                               &total_in, &total_out);
 
     sw->total_hw_samples_mixed += total_out;
@@ -998,18 +1002,6 @@ static size_t audio_get_avail (SWVoiceIn *sw)
     return live;
 }
 
-/**
- * audio_frontend_frames_out() - returns the number of frames needed to
- * get frames_out frames after resampling
- *
- * @sw: audio playback frontend
- * @frames_out: number of frames
- */
-static size_t audio_frontend_frames_out(SWVoiceOut *sw, size_t frames_out)
-{
-    return ((int64_t)frames_out << 32) / sw->ratio;
-}
-
 static size_t audio_get_free(SWVoiceOut *sw)
 {
     size_t live, dead;
@@ -1029,8 +1021,8 @@ static size_t audio_get_free(SWVoiceOut *sw)
     dead = sw->hw->mix_buf.size - live;
 
 #ifdef DEBUG_OUT
-    dolog("%s: get_free live %zu dead %zu frontend frames %zu\n",
-          SW_NAME(sw), live, dead, audio_frontend_frames_out(sw, dead));
+    dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
+          SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
 #endif
 
     return dead;
@@ -1159,12 +1151,13 @@ static void audio_run_out (AudioState *s)
                 size_t free;
 
                 if (hw_free > sw->total_hw_samples_mixed) {
-                    free = audio_frontend_frames_out(sw,
+                    free = st_rate_frames_in(sw->rate,
                         MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
                 } else {
                     free = 0;
                 }
                 if (free > 0) {
+                    free = MIN(free, sw->resample_buf.size);
                     sw->callback.fn(sw->callback.opaque,
                                     free * sw->info.bytes_per_frame);
                 }
-- 
2.35.3


[PATCH 11/17] audio: replace the resampling loop in audio_pcm_sw_read()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Replace the resampling loop in audio_pcm_sw_read() with the new
function audio_pcm_sw_resample_in(). Unlike the old resample
loop the new function will try to consume input frames even if
the output buffer is full. This is necessary when downsampling
to avoid reading less audio frames than calculated in advance.
The loop was unrolled to avoid complicated loop control conditions
in this case.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 59 ++++++++++++++++++++++++++++++---------------------
 1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 3d3b5e5b91..83bac97fa4 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -541,11 +541,43 @@ static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
 /*
  * Soft voice (capture)
  */
+static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
+    size_t frames_in_max, size_t frames_out_max,
+    size_t *total_in, size_t *total_out)
+{
+    HWVoiceIn *hw = sw->hw;
+    struct st_sample *src, *dst;
+    size_t live, rpos, frames_in, frames_out;
+
+    live = hw->total_samples_captured - sw->total_hw_samples_acquired;
+    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
+
+    /* resample conv_buf from rpos to end of buffer */
+    src = hw->conv_buf.buffer + rpos;
+    frames_in = MIN(live, hw->conv_buf.size - rpos);
+    dst = sw->resample_buf.buffer;
+    frames_out = frames_out_max;
+    st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+    rpos += frames_in;
+    *total_in = frames_in;
+    *total_out = frames_out;
+
+    /* resample conv_buf from start of buffer if there are input frames left */
+    if (live - frames_in && rpos == hw->conv_buf.size) {
+        src = hw->conv_buf.buffer;
+        frames_in = live - frames_in;
+        dst += frames_out;
+        frames_out = frames_out_max - frames_out;
+        st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
+        *total_in += frames_in;
+        *total_out += frames_out;
+    }
+}
+
 static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
 {
     HWVoiceIn *hw = sw->hw;
-    size_t samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0;
-    struct st_sample *src, *dst = sw->resample_buf.buffer;
+    size_t samples, live, ret, swlim, total;
 
     live = hw->total_samples_captured - sw->total_hw_samples_acquired;
     if (!live) {
@@ -556,33 +588,12 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
         return 0;
     }
 
-    rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
-
     samples = size / sw->info.bytes_per_frame;
 
     swlim = (live * sw->ratio) >> 32;
     swlim = MIN (swlim, samples);
 
-    while (swlim) {
-        src = hw->conv_buf.buffer + rpos;
-        if (hw->conv_buf.pos > rpos) {
-            isamp = hw->conv_buf.pos - rpos;
-        } else {
-            isamp = hw->conv_buf.size - rpos;
-        }
-
-        if (!isamp) {
-            break;
-        }
-        osamp = swlim;
-
-        st_rate_flow (sw->rate, src, dst, &isamp, &osamp);
-        swlim -= osamp;
-        rpos = (rpos + isamp) % hw->conv_buf.size;
-        dst += osamp;
-        ret += osamp;
-        total += isamp;
-    }
+    audio_pcm_sw_resample_in(sw, live, swlim, &total, &ret);
 
     if (!hw->pcm_ops->volume_in) {
         mixeng_volume(sw->resample_buf.buffer, ret, &sw->vol);
-- 
2.35.3


[PATCH 12/17] audio: rename variables in audio_pcm_sw_read()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

The audio_pcm_sw_read() function uses a few very unspecific
variable names. Rename them for better readability.

ret => total_out
total => total_in
size => buf_len
samples => frames_out_max

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index 83bac97fa4..b660569928 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -574,10 +574,10 @@ static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
     }
 }
 
-static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
 {
     HWVoiceIn *hw = sw->hw;
-    size_t samples, live, ret, swlim, total;
+    size_t live, frames_out_max, swlim, total_in, total_out;
 
     live = hw->total_samples_captured - sw->total_hw_samples_acquired;
     if (!live) {
@@ -588,20 +588,20 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
         return 0;
     }
 
-    samples = size / sw->info.bytes_per_frame;
+    frames_out_max = buf_len / sw->info.bytes_per_frame;
 
     swlim = (live * sw->ratio) >> 32;
-    swlim = MIN (swlim, samples);
+    swlim = MIN(swlim, frames_out_max);
 
-    audio_pcm_sw_resample_in(sw, live, swlim, &total, &ret);
+    audio_pcm_sw_resample_in(sw, live, swlim, &total_in, &total_out);
 
     if (!hw->pcm_ops->volume_in) {
-        mixeng_volume(sw->resample_buf.buffer, ret, &sw->vol);
+        mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
     }
+    sw->clip(buf, sw->resample_buf.buffer, total_out);
 
-    sw->clip(buf, sw->resample_buf.buffer, ret);
-    sw->total_hw_samples_acquired += total;
-    return ret * sw->info.bytes_per_frame;
+    sw->total_hw_samples_acquired += total_in;
+    return total_out * sw->info.bytes_per_frame;
 }
 
 /*
-- 
2.35.3


[PATCH 13/17] audio/mixeng: calculate number of output frames
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Calculate the exact number of audio output frames the resampling
code can generate from a given number of audio input frames.
When upsampling, this function returns the maximum number of
output frames.

This function will later replace the audio_frontend_frames_in()
function, which calculates the average number of output frames
rounded down to the nearest integer.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/mixeng.c | 37 +++++++++++++++++++++++++++++++++++++
 audio/mixeng.h |  1 +
 2 files changed, 38 insertions(+)

diff --git a/audio/mixeng.c b/audio/mixeng.c
index 6bb3d54f77..92a3a1ac58 100644
--- a/audio/mixeng.c
+++ b/audio/mixeng.c
@@ -440,6 +440,43 @@ void st_rate_stop (void *opaque)
     g_free (opaque);
 }
 
+/**
+ * st_rate_frames_out() - returns the number of frames the resampling code
+ * generates from frames_in frames
+ *
+ * @opaque: pointer to struct rate
+ * @frames_in: number of frames
+ */
+uint32_t st_rate_frames_out(void *opaque, uint32_t frames_in)
+{
+    struct rate *rate = opaque;
+    uint64_t opos_end, opos_delta;
+    uint32_t ipos_end;
+    uint32_t frames_out;
+
+    if (rate->opos_inc == 1ULL << 32) {
+        return frames_in;
+    }
+
+    /* no output frame without at least one input frame */
+    if (!frames_in) {
+        return 0;
+    }
+
+    /* last frame read was at rate->ipos - 1 */
+    ipos_end = rate->ipos - 1 + frames_in;
+    opos_end = (uint64_t)ipos_end << 32;
+
+    /* last frame written was at rate->opos - rate->opos_inc */
+    if (opos_end + rate->opos_inc <= rate->opos) {
+        return 0;
+    }
+    opos_delta = opos_end - rate->opos + rate->opos_inc;
+    frames_out = opos_delta / rate->opos_inc;
+
+    return opos_delta % rate->opos_inc ? frames_out : frames_out - 1;
+}
+
 /**
  * st_rate_frames_in() - returns the number of frames needed to
  * get frames_out frames after resampling
diff --git a/audio/mixeng.h b/audio/mixeng.h
index 64c1e231cc..f9de7cffeb 100644
--- a/audio/mixeng.h
+++ b/audio/mixeng.h
@@ -52,6 +52,7 @@ void st_rate_flow(void *opaque, st_sample *ibuf, st_sample *obuf,
 void st_rate_flow_mix(void *opaque, st_sample *ibuf, st_sample *obuf,
                       size_t *isamp, size_t *osamp);
 void st_rate_stop (void *opaque);
+uint32_t st_rate_frames_out(void *opaque, uint32_t frames_in);
 uint32_t st_rate_frames_in(void *opaque, uint32_t frames_out);
 void mixeng_clear (struct st_sample *buf, int len);
 void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol);
-- 
2.35.3


[PATCH 14/17] audio: wire up st_rate_frames_out()
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Wire up the st_rate_frames_out() function and replace
audio_frontend_frames_in() to make audio packet length
calculation exact.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c | 29 ++++++++---------------------
 1 file changed, 8 insertions(+), 21 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index b660569928..ecd5d31260 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -577,7 +577,7 @@ static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
 static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
 {
     HWVoiceIn *hw = sw->hw;
-    size_t live, frames_out_max, swlim, total_in, total_out;
+    size_t live, frames_out_max, total_in, total_out;
 
     live = hw->total_samples_captured - sw->total_hw_samples_acquired;
     if (!live) {
@@ -588,12 +588,10 @@ static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
         return 0;
     }
 
-    frames_out_max = buf_len / sw->info.bytes_per_frame;
+    frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
+                         sw->resample_buf.size);
 
-    swlim = (live * sw->ratio) >> 32;
-    swlim = MIN(swlim, frames_out_max);
-
-    audio_pcm_sw_resample_in(sw, live, swlim, &total_in, &total_out);
+    audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
 
     if (!hw->pcm_ops->volume_in) {
         mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
@@ -977,18 +975,6 @@ void AUD_set_active_in (SWVoiceIn *sw, int on)
     }
 }
 
-/**
- * audio_frontend_frames_in() - returns the number of frames the resampling
- * code generates from frames_in frames
- *
- * @sw: audio recording frontend
- * @frames_in: number of frames
- */
-static size_t audio_frontend_frames_in(SWVoiceIn *sw, size_t frames_in)
-{
-    return (int64_t)frames_in * sw->ratio >> 32;
-}
-
 static size_t audio_get_avail (SWVoiceIn *sw)
 {
     size_t live;
@@ -1005,9 +991,9 @@ static size_t audio_get_avail (SWVoiceIn *sw)
     }
 
     ldebug (
-        "%s: get_avail live %zu frontend frames %zu\n",
+        "%s: get_avail live %zu frontend frames %u\n",
         SW_NAME (sw),
-        live, audio_frontend_frames_in(sw, live)
+        live, st_rate_frames_out(sw->rate, live)
         );
 
     return live;
@@ -1312,8 +1298,9 @@ static void audio_run_in (AudioState *s)
                 size_t sw_avail = audio_get_avail(sw);
                 size_t avail;
 
-                avail = audio_frontend_frames_in(sw, sw_avail);
+                avail = st_rate_frames_out(sw->rate, sw_avail);
                 if (avail > 0) {
+                    avail = MIN(avail, sw->resample_buf.size);
                     sw->callback.fn(sw->callback.opaque,
                                     avail * sw->info.bytes_per_frame);
                 }
-- 
2.35.3


[PATCH 15/17] audio: handle leftover audio frame from upsampling
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Upsampling may leave one remaining audio frame in the input
buffer. The emulated audio playback devices are currently
resposible to write this audio frame again in the next write
cycle. Push that task down to audio_pcm_sw_write.

This is another step towards an audio callback interface that
guarantees that when audio frontends are told they can write
n audio frames, they can actually do so.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c          | 34 ++++++++++++++++++++++++++++------
 audio/audio_template.h |  5 +++--
 2 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index ecd5d31260..b846b89a27 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -729,16 +729,21 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
     hw_free = hw_free > live ? hw_free - live : 0;
     frames_out_max = MIN(dead, hw_free);
     sw_max = st_rate_frames_in(sw->rate, frames_out_max);
-    fe_max = MIN(buf_len / sw->info.bytes_per_frame, sw->resample_buf.size);
+    fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
+                 sw->resample_buf.size);
     frames_in_max = MIN(sw_max, fe_max);
 
     if (!frames_in_max) {
         return 0;
     }
 
-    sw->conv(sw->resample_buf.buffer, buf, frames_in_max);
-    if (!sw->hw->pcm_ops->volume_out) {
-        mixeng_volume(sw->resample_buf.buffer, frames_in_max, &sw->vol);
+    if (frames_in_max > sw->resample_buf.pos) {
+        sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
+                 buf, frames_in_max - sw->resample_buf.pos);
+        if (!sw->hw->pcm_ops->volume_out) {
+            mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
+                          frames_in_max - sw->resample_buf.pos, &sw->vol);
+        }
     }
 
     audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
@@ -747,6 +752,22 @@ static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
     sw->total_hw_samples_mixed += total_out;
     sw->empty = sw->total_hw_samples_mixed == 0;
 
+    /*
+     * Upsampling may leave one audio frame in the resample buffer. Decrement
+     * total_in by one if there was a leftover frame from the previous resample
+     * pass in the resample buffer. Increment total_in by one if the current
+     * resample pass left one frame in the resample buffer.
+     */
+    if (frames_in_max - total_in == 1) {
+        /* copy one leftover audio frame to the beginning of the buffer */
+        *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
+        total_in += 1 - sw->resample_buf.pos;
+        sw->resample_buf.pos = 1;
+    } else if (total_in >= sw->resample_buf.pos) {
+        total_in -= sw->resample_buf.pos;
+        sw->resample_buf.pos = 0;
+    }
+
 #ifdef DEBUG_OUT
     dolog (
         "%s: write size %zu written %zu total mixed %zu\n",
@@ -1153,8 +1174,9 @@ static void audio_run_out (AudioState *s)
                 } else {
                     free = 0;
                 }
-                if (free > 0) {
-                    free = MIN(free, sw->resample_buf.size);
+                if (free > sw->resample_buf.pos) {
+                    free = MIN(free, sw->resample_buf.size)
+                           - sw->resample_buf.pos;
                     sw->callback.fn(sw->callback.opaque,
                                     free * sw->info.bytes_per_frame);
                 }
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 07c14e7821..a9a550a3b7 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -132,8 +132,9 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
         return -1;
     }
 
-    sw->resample_buf.buffer = g_new0(st_sample, samples);
-    sw->resample_buf.size = samples;
+    /* allocate one additional audio frame that is needed for upsampling */
+    sw->resample_buf.buffer = g_new0(st_sample, samples + 1);
+    sw->resample_buf.size = samples + 1;
     sw->resample_buf.pos = 0;
 
 #ifdef DAC
-- 
2.35.3


[PATCH 16/17] audio/audio_template: substitute sw->hw with hw
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Substitute sw->hw with hw in the audio_pcm_sw_alloc_resources_*
functions.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio_template.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/audio/audio_template.h b/audio/audio_template.h
index a9a550a3b7..0cdf57760e 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -107,6 +107,7 @@ static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw)
 
 static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
 {
+    HW *hw = sw->hw;
     int samples;
 
     if (!glue(audio_get_pdo_, TYPE)(sw->s->dev)->mixing_engine) {
@@ -119,7 +120,6 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
     samples = (int64_t)sw->HWBUF.size * sw->ratio >> 32;
 #endif
     if (samples == 0) {
-        HW *hw = sw->hw;
         size_t f_fe_min;
 
         /* f_fe_min = ceil(1 [frames] * f_be [Hz] / size_be [frames]) */
@@ -138,9 +138,9 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
     sw->resample_buf.pos = 0;
 
 #ifdef DAC
-    sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq);
+    sw->rate = st_rate_start(sw->info.freq, hw->info.freq);
 #else
-    sw->rate = st_rate_start (sw->hw->info.freq, sw->info.freq);
+    sw->rate = st_rate_start(hw->info.freq, sw->info.freq);
 #endif
 
     return 0;
-- 
2.35.3


[PATCH 17/17] audio: remove sw->ratio
Posted by Volker Rümelin 1 year, 3 months ago
From: Volker Rümelin <vr_qemu@t-online.de>

Simplify the resample buffer size calculation.

For audio playback we have
sw->ratio = ((int64_t)sw->hw->info.freq << 32) / sw->info.freq;
samples = ((int64_t)sw->HWBUF.size << 32) / sw->ratio;

This can be simplified to
samples = muldiv64(sw->HWBUF.size, sw->info.freq, sw->hw->info.freq);

For audio recording we have
sw->ratio = ((int64_t)sw->info.freq << 32) / sw->hw->info.freq;
samples = (int64_t)sw->HWBUF.size * sw->ratio >> 32;

This can be simplified to
samples = muldiv64(sw->HWBUF.size, sw->info.freq, sw->hw->info.freq);

With hw = sw->hw this becomes in both cases
samples = muldiv64(HWBUF.size, sw->info.freq, hw->info.freq);

Now that sw->ratio is no longer needed, remove sw->ratio.

Signed-off-by: Volker Rümelin <vr_qemu@t-online.de>
---
 audio/audio.c          | 1 -
 audio/audio_int.h      | 2 --
 audio/audio_template.h | 9 +--------
 3 files changed, 1 insertion(+), 11 deletions(-)

diff --git a/audio/audio.c b/audio/audio.c
index b846b89a27..b68ed4eb68 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -476,7 +476,6 @@ static int audio_attach_capture (HWVoiceOut *hw)
         sw->info = hw->info;
         sw->empty = 1;
         sw->active = hw->enabled;
-        sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq;
         sw->vol = nominal_volume;
         sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
         QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
diff --git a/audio/audio_int.h b/audio/audio_int.h
index f4ec5dcf11..3cd3539bd4 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -108,7 +108,6 @@ struct SWVoiceOut {
     AudioState *s;
     struct audio_pcm_info info;
     t_sample *conv;
-    int64_t ratio;
     STSampleBuffer resample_buf;
     void *rate;
     size_t total_hw_samples_mixed;
@@ -126,7 +125,6 @@ struct SWVoiceIn {
     AudioState *s;
     int active;
     struct audio_pcm_info info;
-    int64_t ratio;
     void *rate;
     size_t total_hw_samples_acquired;
     STSampleBuffer resample_buf;
diff --git a/audio/audio_template.h b/audio/audio_template.h
index 0cdf57760e..c053792da3 100644
--- a/audio/audio_template.h
+++ b/audio/audio_template.h
@@ -114,11 +114,7 @@ static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw)
         return 0;
     }
 
-#ifdef DAC
-    samples = ((int64_t)sw->HWBUF.size << 32) / sw->ratio;
-#else
-    samples = (int64_t)sw->HWBUF.size * sw->ratio >> 32;
-#endif
+    samples = muldiv64(HWBUF.size, sw->info.freq, hw->info.freq);
     if (samples == 0) {
         size_t f_fe_min;
 
@@ -159,11 +155,8 @@ static int glue (audio_pcm_sw_init_, TYPE) (
     sw->hw = hw;
     sw->active = 0;
 #ifdef DAC
-    sw->ratio = ((int64_t) sw->hw->info.freq << 32) / sw->info.freq;
     sw->total_hw_samples_mixed = 0;
     sw->empty = 1;
-#else
-    sw->ratio = ((int64_t) sw->info.freq << 32) / sw->hw->info.freq;
 #endif
 
     if (sw->info.is_float) {
-- 
2.35.3