@samitouri / QOSamiQemu / commits / cd5f9b9c02

audio/pw: convert to QOM lifecycle methods

Migrate the PipeWire audio backend from the legacy driver init/fini callbacks to proper QOM realize and finalize methods. The pwaudio struct fields are now embedded directly in the AudioPw QOM object instead of being allocated separately as drv_opaque. This allows accessing the backend state through proper QOM type casting with AUDIO_PW() rather than casting drv_opaque pointers. The PipeWire thread loop and context are now managed through the QOM lifecycle, with initialization in realize and cleanup in finalize. Reviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Marc-André Lureau committed Jan 16, 2026 at 18:44 UTC cd5f9b9c0237936387f801aa01f4a6a0dc203341
1 file changed +58 -61
audio/pwaudio.c
+58 -61
@@ -31,36 +31,27 @@
31 #define TYPE_AUDIO_PW "audio-pipewire"
32 OBJECT_DECLARE_SIMPLE_TYPE(AudioPw, AUDIO_PW)
33
34 +static AudioBackendClass *audio_pw_parent_class;
35 +
36 struct AudioPw {
37 AudioMixengBackend parent_obj;
36 -};
38
38 -static struct audio_driver pw_audio_driver;
39 + struct pw_thread_loop *thread_loop;
40 + struct pw_context *context;
41
40 -static void audio_pw_class_init(ObjectClass *klass, const void *data)
41 -{
42 - AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass);
42 + struct pw_core *core;
43 + struct spa_hook core_listener;
44 + int last_seq, pending_seq, error;
45 +};
46
44 - k->driver = &pw_audio_driver;
45 -}
47 +static struct audio_driver pw_audio_driver;
48
49 typedef struct pwvolume {
50 uint32_t channels;
51 float values[SPA_AUDIO_MAX_CHANNELS];
52 } pwvolume;
53
52 -typedef struct pwaudio {
53 - Audiodev *dev;
54 - struct pw_thread_loop *thread_loop;
55 - struct pw_context *context;
56 -
57 - struct pw_core *core;
58 - struct spa_hook core_listener;
59 - int last_seq, pending_seq, error;
60 -} pwaudio;
61 -
54 typedef struct PWVoice {
63 - pwaudio *g;
55 struct pw_stream *stream;
56 struct spa_hook stream_listener;
57 struct spa_audio_info_raw info;
@@ -236,9 +227,9 @@ static const struct pw_stream_events playback_stream_events = {
227 static size_t
228 qpw_read(HWVoiceIn *hw, void *data, size_t len)
229 {
230 + AudioPw *c = AUDIO_PW(hw->s);
231 PWVoiceIn *pw = (PWVoiceIn *) hw;
232 PWVoice *v = &pw->v;
241 - pwaudio *c = v->g;
233 const char *error = NULL;
234 size_t l;
235 int32_t avail;
@@ -273,9 +264,9 @@ done_unlock:
264
265 static size_t qpw_buffer_get_free(HWVoiceOut *hw)
266 {
267 + AudioPw *c = AUDIO_PW(hw->s);
268 PWVoiceOut *pw = (PWVoiceOut *)hw;
269 PWVoice *v = &pw->v;
278 - pwaudio *c = v->g;
270 const char *error = NULL;
271 int32_t filled, avail;
272 uint32_t index;
@@ -298,9 +289,9 @@ done_unlock:
289 static size_t
290 qpw_write(HWVoiceOut *hw, void *data, size_t len)
291 {
292 + AudioPw *c = AUDIO_PW(hw->s);
293 PWVoiceOut *pw = (PWVoiceOut *) hw;
294 PWVoice *v = &pw->v;
303 - pwaudio *c = v->g;
295 const char *error = NULL;
296 int32_t filled, avail;
297 uint32_t index;
@@ -434,7 +425,7 @@ pw_to_audfmt(enum spa_audio_format fmt, int *endianness,
425 }
426
427 static int
437 -qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name,
428 +qpw_stream_new(AudioPw *c, PWVoice *v, const char *stream_name,
429 const char *name, enum spa_direction dir)
430 {
431 int res;
@@ -452,8 +443,8 @@ qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name,
443 }
444
445 /* 75% of the timer period for faster updates */
455 - buf_samples = (uint64_t)v->g->dev->timer_period * v->info.rate
456 - * 3 / 4 / 1000000;
446 + buf_samples = (uint64_t)AUDIO_MIXENG_BACKEND(c)->dev->timer_period
447 + * v->info.rate * 3 / 4 / 1000000;
448 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u",
449 buf_samples, v->info.rate);
450
@@ -535,11 +526,11 @@ qpw_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
526 static int
527 qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
528 {
529 + AudioPw *c = AUDIO_PW(hw->s);
530 PWVoiceOut *pw = (PWVoiceOut *) hw;
531 PWVoice *v = &pw->v;
532 struct audsettings obt_as = *as;
541 - pwaudio *c = v->g = drv_opaque;
542 - AudiodevPipewireOptions *popts = &c->dev->u.pipewire;
533 + AudiodevPipewireOptions *popts = &AUDIO_MIXENG_BACKEND(c)->dev->u.pipewire;
534 AudiodevPipewirePerDirectionOptions *ppdo = popts->out;
535 int r;
536
@@ -554,11 +545,11 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
545 pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size);
546 v->frame_size *= as->nchannels;
547
557 - v->req = (uint64_t)c->dev->timer_period * v->info.rate
548 + v->req = (uint64_t)AUDIO_MIXENG_BACKEND(c)->dev->timer_period * v->info.rate
549 * 1 / 2 / 1000000 * v->frame_size;
550
551 /* call the function that creates a new stream for playback */
561 - r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id,
552 + r = qpw_stream_new(c, v, ppdo->stream_name ?: AUDIO_MIXENG_BACKEND(c)->dev->id,
553 ppdo->name, SPA_DIRECTION_OUTPUT);
554 if (r < 0) {
555 pw_thread_loop_unlock(c->thread_loop);
@@ -582,11 +573,11 @@ qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
573 static int
574 qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
575 {
576 + AudioPw *c = AUDIO_PW(hw->s);
577 PWVoiceIn *pw = (PWVoiceIn *) hw;
578 PWVoice *v = &pw->v;
579 struct audsettings obt_as = *as;
588 - pwaudio *c = v->g = drv_opaque;
589 - AudiodevPipewireOptions *popts = &c->dev->u.pipewire;
580 + AudiodevPipewireOptions *popts = &AUDIO_MIXENG_BACKEND(c)->dev->u.pipewire;
581 AudiodevPipewirePerDirectionOptions *ppdo = popts->in;
582 int r;
583
@@ -602,7 +593,7 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
593 v->frame_size *= as->nchannels;
594
595 /* call the function that creates a new stream for recording */
605 - r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id,
596 + r = qpw_stream_new(c, v, ppdo->stream_name ? : AUDIO_MIXENG_BACKEND(c)->dev->id,
597 ppdo->name, SPA_DIRECTION_INPUT);
598 if (r < 0) {
599 pw_thread_loop_unlock(c->thread_loop);
@@ -621,10 +612,8 @@ qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
612 }
613
614 static void
624 -qpw_voice_fini(PWVoice *v)
615 +qpw_voice_fini(AudioPw *c, PWVoice *v)
616 {
626 - pwaudio *c = v->g;
627 -
617 if (!v->stream) {
618 return;
619 }
@@ -637,19 +626,18 @@ qpw_voice_fini(PWVoice *v)
626 static void
627 qpw_fini_out(HWVoiceOut *hw)
628 {
640 - qpw_voice_fini(&PW_VOICE_OUT(hw)->v);
629 + qpw_voice_fini(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v);
630 }
631
632 static void
633 qpw_fini_in(HWVoiceIn *hw)
634 {
646 - qpw_voice_fini(&PW_VOICE_IN(hw)->v);
635 + qpw_voice_fini(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v);
636 }
637
638 static void
650 -qpw_voice_set_enabled(PWVoice *v, bool enable)
639 +qpw_voice_set_enabled(AudioPw *c, PWVoice *v, bool enable)
640 {
652 - pwaudio *c = v->g;
641 pw_thread_loop_lock(c->thread_loop);
642 pw_stream_set_active(v->stream, enable);
643 pw_thread_loop_unlock(c->thread_loop);
@@ -658,19 +646,18 @@ qpw_voice_set_enabled(PWVoice *v, bool enable)
646 static void
647 qpw_enable_out(HWVoiceOut *hw, bool enable)
648 {
661 - qpw_voice_set_enabled(&PW_VOICE_OUT(hw)->v, enable);
649 + qpw_voice_set_enabled(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v, enable);
650 }
651
652 static void
653 qpw_enable_in(HWVoiceIn *hw, bool enable)
654 {
667 - qpw_voice_set_enabled(&PW_VOICE_IN(hw)->v, enable);
655 + qpw_voice_set_enabled(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v, enable);
656 }
657
658 static void
671 -qpw_voice_set_volume(PWVoice *v, Volume *vol)
659 +qpw_voice_set_volume(AudioPw *c, PWVoice *v, Volume *vol)
660 {
673 - pwaudio *c = v->g;
661 int i, ret;
662
663 pw_thread_loop_lock(c->thread_loop);
@@ -693,16 +680,16 @@ qpw_voice_set_volume(PWVoice *v, Volume *vol)
680 static void
681 qpw_volume_out(HWVoiceOut *hw, Volume *vol)
682 {
696 - qpw_voice_set_volume(&PW_VOICE_OUT(hw)->v, vol);
683 + qpw_voice_set_volume(AUDIO_PW(hw->s), &PW_VOICE_OUT(hw)->v, vol);
684 }
685
686 static void
687 qpw_volume_in(HWVoiceIn *hw, Volume *vol)
688 {
702 - qpw_voice_set_volume(&PW_VOICE_IN(hw)->v, vol);
689 + qpw_voice_set_volume(AUDIO_PW(hw->s), &PW_VOICE_IN(hw)->v, vol);
690 }
691
705 -static int wait_resync(pwaudio *pw)
692 +static int wait_resync(AudioPw *pw)
693 {
694 int res;
695 pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq);
@@ -725,7 +712,7 @@ static int wait_resync(pwaudio *pw)
712 static void
713 on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
714 {
728 - pwaudio *pw = data;
715 + AudioPw *pw = data;
716
717 error_report("error id:%u seq:%d res:%d (%s): %s",
718 id, seq, res, spa_strerror(res), message);
@@ -737,7 +724,7 @@ on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
724 static void
725 on_core_done(void *data, uint32_t id, int seq)
726 {
740 - pwaudio *pw = data;
727 + AudioPw *pw = data;
728 assert(id == PW_ID_CORE);
729 pw->last_seq = seq;
730 if (pw->pending_seq == seq) {
@@ -752,17 +739,20 @@ static const struct pw_core_events core_events = {
739 .error = on_core_error,
740 };
741
755 -static void *
756 -qpw_audio_init(Audiodev *dev, Error **errp)
742 +static bool
743 +audio_pw_realize(AudioBackend *abe, Audiodev *dev, Error **errp)
744 {
758 - g_autofree pwaudio *pw = g_new0(pwaudio, 1);
745 + AudioPw *pw = AUDIO_PW(abe);
746
747 assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE);
748 trace_pw_audio_init();
749
750 + if (!audio_pw_parent_class->realize(abe, dev, errp)) {
751 + return false;
752 + }
753 +
754 pw_init(NULL, NULL);
755
765 - pw->dev = dev;
756 pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL);
757 if (pw->thread_loop == NULL) {
758 error_setg_errno(errp, errno, "Could not create PipeWire loop");
@@ -801,8 +791,7 @@ qpw_audio_init(Audiodev *dev, Error **errp)
791 }
792
793 pw_thread_loop_unlock(pw->thread_loop);
804 -
805 - return g_steal_pointer(&pw);
794 + return true;
795
796 fail:
797 if (pw->thread_loop) {
@@ -810,13 +799,13 @@ fail:
799 }
800 g_clear_pointer(&pw->context, pw_context_destroy);
801 g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy);
813 - return NULL;
802 + return false;
803 }
804
805 static void
817 -qpw_audio_fini(void *opaque)
806 +audio_pw_finalize(Object *obj)
807 {
819 - pwaudio *pw = opaque;
808 + AudioPw *pw = AUDIO_PW(obj);
809
810 if (pw->thread_loop) {
811 pw_thread_loop_stop(pw->thread_loop);
@@ -831,9 +820,7 @@ qpw_audio_fini(void *opaque)
820 if (pw->context) {
821 pw_context_destroy(pw->context);
822 }
834 - pw_thread_loop_destroy(pw->thread_loop);
835 -
836 - g_free(pw);
823 + g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy);
824 }
825
826 static struct audio_pcm_ops qpw_pcm_ops = {
@@ -855,8 +842,6 @@ static struct audio_pcm_ops qpw_pcm_ops = {
842
843 static struct audio_driver pw_audio_driver = {
844 .name = "pipewire",
858 - .init = qpw_audio_init,
859 - .fini = qpw_audio_fini,
845 .pcm_ops = &qpw_pcm_ops,
846 .max_voices_out = INT_MAX,
847 .max_voices_in = INT_MAX,
@@ -864,12 +849,24 @@ static struct audio_driver pw_audio_driver = {
849 .voice_size_in = sizeof(PWVoiceIn),
850 };
851
852 +static void audio_pw_class_init(ObjectClass *klass, const void *data)
853 +{
854 + AudioBackendClass *b = AUDIO_BACKEND_CLASS(klass);
855 + AudioMixengBackendClass *k = AUDIO_MIXENG_BACKEND_CLASS(klass);
856 +
857 + audio_pw_parent_class = AUDIO_BACKEND_CLASS(object_class_get_parent(klass));
858 +
859 + b->realize = audio_pw_realize;
860 + k->driver = &pw_audio_driver;
861 +}
862 +
863 static const TypeInfo audio_types[] = {
864 {
865 .name = TYPE_AUDIO_PW,
866 .parent = TYPE_AUDIO_MIXENG_BACKEND,
867 .instance_size = sizeof(AudioPw),
868 .class_init = audio_pw_class_init,
869 + .instance_finalize = audio_pw_finalize,
870 },
871 };
872