Hi Volker,

Thanks for the patch, I've tested the patch and it works. I don't hear the
choppy audio with this option "qemu-system-x86_64 -device ich9-intel-hda
-device hda-duplex,audiodev=audio0 -audiodev
pipewire,id=audio0,out.frequency=96000,in.frequency=96000 ...."

I don't understand how the req == 0 case can work at all.
>
how this works is that  b->requested could be zero when no suggestion is
provided. For playback streams, this field contains the suggested amount of
data to provide. hence the reason for this check.

I suggest to use the same option names as the pulseaudio backend.
> out.latency is the effective Pipewire buffer size.
>
Ack.

Thanks,
Dorinda.


On Sat, Mar 11, 2023 at 5:19 PM Volker Rümelin <vr_q...@t-online.de> wrote:

> > Based-on:<20230306171020.381116-1-dbas...@redhat.com>
> > ([PATCH v7] audio/pwaudio.c: Add Pipewire audio backend for QEMU)
> >
> > This is sample code for the review of the pipewire backed. The
> > code actually works.
> >
> > An email with explanations for the changes will follow.
> >
> > Signed-off-by: Volker Rümelin<vr_q...@t-online.de>
> > ---
> >   audio/pwaudio.c | 67 +++++++++++++++++++++++++++++++++----------------
> >   qapi/audio.json | 10 +++-----
> >   2 files changed, 49 insertions(+), 28 deletions(-)
> >
> > diff --git a/audio/pwaudio.c b/audio/pwaudio.c
> > index d357761152..8e2a38938f 100644
> > --- a/audio/pwaudio.c
> > +++ b/audio/pwaudio.c
> > @@ -23,7 +23,6 @@
> >   #define AUDIO_CAP "pipewire"
> >   #define RINGBUFFER_SIZE    (1u << 22)
> >   #define RINGBUFFER_MASK    (RINGBUFFER_SIZE - 1)
> > -#define BUFFER_SAMPLES    512
> >
> >   #include "audio_int.h"
> >
> > @@ -48,6 +47,7 @@ typedef struct PWVoice {
> >       struct pw_stream *stream;
> >       struct spa_hook stream_listener;
> >       struct spa_audio_info_raw info;
> > +    uint32_t highwater_mark;
> >       uint32_t frame_size;
> >       struct spa_ringbuffer ring;
> >       uint8_t buffer[RINGBUFFER_SIZE];
> > @@ -82,7 +82,7 @@ playback_on_process(void *data)
> >       void *p;
> >       struct pw_buffer *b;
> >       struct spa_buffer *buf;
> > -    uint32_t n_frames, req, index, n_bytes;
> > +    uint32_t req, index, n_bytes;
> >       int32_t avail;
> >
> >       if (!v->stream) {
> > @@ -105,8 +105,7 @@ playback_on_process(void *data)
> >       if (req == 0) {
> >           req = 4096 * v->frame_size;
> >       }
>
> I don't understand how the req == 0 case can work at all. The downstream
> audio device is the thinnest point in the playback stream. We can't
> write more audio frames than the audio device will consume.
>
> > -    n_frames = SPA_MIN(req, buf->datas[0].maxsize);
> > -    n_bytes = n_frames * v->frame_size;
> > +    n_bytes = SPA_MIN(req, buf->datas[0].maxsize);
>
> See Marc-André's review.
>
> >
> >       /* get no of available bytes to read data from buffer */
> >
> > @@ -270,6 +269,30 @@ done_unlock:
> >       return l;
> >   }
> >
> > +static size_t qpw_buffer_get_free(HWVoiceOut *hw)
> > +{
> > +    PWVoiceOut *pw = (PWVoiceOut *)hw;
> > +    PWVoice *v = &pw->v;
> > +    pwaudio *c = v->g;
> > +    const char *error = NULL;
> > +    int32_t filled, avail;
> > +    uint32_t index;
> > +
> > +    pw_thread_loop_lock(c->thread_loop);
> > +    if (pw_stream_get_state(v->stream, &error) !=
> PW_STREAM_STATE_STREAMING) {
> > +        /* wait for stream to become ready */
> > +        avail = 0;
> > +        goto done_unlock;
> > +    }
> > +
> > +    filled = spa_ringbuffer_get_write_index(&v->ring, &index);
> > +    avail = v->highwater_mark - filled;
> > +
> > +done_unlock:
> > +    pw_thread_loop_unlock(c->thread_loop);
> > +    return avail;
> > +}
> > +
>
> A pcm_ops buffer_get_free function is necessary. Otherwise the gus and
> via-ac97 audio devices will not work properly for the -audiodev
> pipewire,id=audio0,out.mixing-engine=off case. They need to know in
> advance how many bytes they can write.
>
> Also, without the buffer_get_free function, the generic audio buffer
> will increase the playback latency.
>
> >   static size_t
> >   qpw_write(HWVoiceOut *hw, void *data, size_t len)
> >   {
> > @@ -277,20 +300,18 @@ qpw_write(HWVoiceOut *hw, void *data, size_t len)
> >       PWVoice *v = &pw->v;
> >       pwaudio *c = v->g;
> >       const char *error = NULL;
> > -    const int periods = 3;
> > -    size_t l;
> >       int32_t filled, avail;
> >       uint32_t index;
> >
> >       pw_thread_loop_lock(c->thread_loop);
> >       if (pw_stream_get_state(v->stream, &error) !=
> PW_STREAM_STATE_STREAMING) {
> >           /* wait for stream to become ready */
> > -        l = 0;
> > +        len = 0;
> >           goto done_unlock;
> >       }
> > -    filled = spa_ringbuffer_get_write_index(&v->ring, &index);
> >
> > -    avail = BUFFER_SAMPLES * v->frame_size * periods - filled;
> > +    filled = spa_ringbuffer_get_write_index(&v->ring, &index);
> > +    avail = v->highwater_mark - filled;
> >
> >       trace_pw_write(filled, avail, index, len);
> >
> > @@ -312,11 +333,10 @@ qpw_write(HWVoiceOut *hw, void *data, size_t len)
> >                                   index & RINGBUFFER_MASK, data, len);
> >       index += len;
> >       spa_ringbuffer_write_update(&v->ring, index);
> > -    l = len;
> >
> >   done_unlock:
> >       pw_thread_loop_unlock(c->thread_loop);
> > -    return l;
> > +    return len;
> >   }
> >
> >   static int
> > @@ -420,8 +440,13 @@ create_stream(pwaudio *c, PWVoice *v, const char
> *name)
> >       const struct spa_pod *params[2];
> >       uint8_t buffer[1024];
> >       struct spa_pod_builder b;
> > +    struct pw_properties *props;
> >
> > -    v->stream = pw_stream_new(c->core, name, NULL);
> > +    props = pw_properties_new(NULL, NULL);
> > +    pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u",
> > +                       (uint64_t)v->g->dev->timer_period * v->info.rate
> > +                       * 3 / 4 / 1000000, v->info.rate);
> > +    v->stream = pw_stream_new(c->core, name, props);
>
> The PW_KEY_NODE_LATENCY property is a hint for Pipewire that we need
> updates faster than timer_period. I selected 75% of timer-period. Then
> there's a good chance the audio frontends can write or read new audio
> packets every timer-period. It doesn't matter that Pipewire calls the
> callbacks faster in most cases.
>
> If it turns out that Pipewire often can't even approximately fulfill
> this hint, we will additionally need a jitter buffer implementation to
> split the larger Pipewire audio packets into timer-period sized packets.
>
> >
> >       if (v->stream == NULL) {
> >           goto error;
> > @@ -563,7 +588,11 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings
> *as, void *drv_opaque)
> >       audio_pcm_init_info(&hw->info, &obt_as);
> >
> >       /* report the buffer size to qemu */
> > -    hw->samples = BUFFER_SAMPLES;
> > +    hw->samples = audio_buffer_frames(
> > +        qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as,
> 46440);
> > +    v->highwater_mark = MIN(RINGBUFFER_SIZE,
> > +                            (ppdo->has_latency ? ppdo->latency : 46440)
> > +                            * (uint64_t)v->info.rate / 1000000 *
> v->frame_size);
> >
>
> The reported buffer size should be much larger than BUFFER_SAMPLES. This
> gives the audio frontends a chance to catch up if they missed
> timer-periods or if they have to fill the pipewire backend buffer
> quickly after playback starts. The exact size is not critical, but to be
> command line compatible with the pulseaudio backend, I suggest to use
> 46ms. A large hw->samples value doesn't increase the playback latency.
>
> v->highwater_mark is the effective pipewire backend buffer size. At a
> audio frame rate of 44100 frames/s, the code without this patch uses a
> buffer size of BUFFER_SAMPLES * periods / 44100 frames/s = 512 frames *
> 3 / 44100 frames/s = 35ms. On my computer the buffer size has to be 30ms
> at minimum. I suggest to add a good margin and use a default of 46ms.
> This buffer is a larger contributor to the playback latency.
>
> >       pw_thread_loop_unlock(c->thread_loop);
> >       return 0;
> > @@ -606,7 +635,8 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as,
> void *drv_opaque)
> >       audio_pcm_init_info(&hw->info, &obt_as);
> >
> >       /* report the buffer size to qemu */
> > -    hw->samples = BUFFER_SAMPLES;
> > +    hw->samples = audio_buffer_frames(
> > +        qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as,
> 46440);
> >
>
> See qpw_init_out().
>
> >       pw_thread_loop_unlock(c->thread_loop);
> >       return 0;
> > @@ -695,15 +725,8 @@ qpw_audio_init(Audiodev *dev)
> >       pw = g_new0(pwaudio, 1);
> >       pw_init(NULL, NULL);
> >
> > -    AudiodevPipewireOptions *popts;
> >       trace_pw_audio_init("Initialize Pipewire context\n");
> >       assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE);
> > -    popts = &dev->u.pipewire;
> > -
> > -    if (!popts->has_latency) {
> > -        popts->has_latency = true;
> > -        popts->latency = 15000;
> > -    }
> >
> >       pw->dev = dev;
> >       pw->thread_loop = pw_thread_loop_new("Pipewire thread loop", NULL);
> > @@ -781,7 +804,7 @@ static struct audio_pcm_ops qpw_pcm_ops = {
> >       .init_out = qpw_init_out,
> >       .fini_out = qpw_fini_out,
> >       .write = qpw_write,
> > -    .buffer_get_free = audio_generic_buffer_get_free,
> > +    .buffer_get_free = qpw_buffer_get_free,
> >       .run_buffer_out = audio_generic_run_buffer_out,
> >       .enable_out = qpw_enable_out,
> >
> > diff --git a/qapi/audio.json b/qapi/audio.json
> > index 9a0d7d9ece..d49a8a670b 100644
> > --- a/qapi/audio.json
> > +++ b/qapi/audio.json
> > @@ -337,6 +337,7 @@
> >   #               create multiple Pipewire devices or run multiple qemu
> >   #               instances (default: audiodev's id, since 7.1)
> >   #
> > +# @latency: Pipewire backend buffer size in microseconds (default 46440)
> >   #
> >   # Since: 8.0
> >   ##
> > @@ -344,7 +345,8 @@
> >     'base': 'AudiodevPerDirectionOptions',
> >     'data': {
> >       '*name': 'str',
> > -    '*stream-name': 'str' } }
> > +    '*stream-name': 'str',
> > +    '*latency': 'uint32' } }
> >
>
> I suggest to use the same option names as the pulseaudio backend.
> out.latency is the effective Pipewire buffer size.
>
> With best regards,
> Volker
>
> >   ##
> >   # @AudiodevPipewireOptions:
> > @@ -355,16 +357,12 @@
> >   #
> >   # @out: options of the playback stream
> >   #
> > -# @latency: add latency to playback in microseconds
> > -#           (default 15000)
> > -#
> >   # Since: 8.0
> >   ##
> >   { 'struct': 'AudiodevPipewireOptions',
> >     'data': {
> >       '*in':     'AudiodevPipewirePerDirectionOptions',
> > -    '*out':    'AudiodevPipewirePerDirectionOptions',
> > -    '*latency': 'uint32' } }
> > +    '*out':    'AudiodevPipewirePerDirectionOptions' } }
> >
> >   ##
> >   # @AudiodevSdlPerDirectionOptions:
>
>

Reply via email to