From d26f0dd9edde1de81630a1da1969fbadffbbb423 Mon Sep 17 00:00:00 2001 From: Cuda-Chen Date: Wed, 12 Feb 2025 12:38:23 +0800 Subject: [PATCH] Switch to PortAudio Switch to PortAudio for cross-platform and cross backend compatibility. Linux v6.7 propose a solution to resolve sound stutter effect. Therefore, upgrade the kernel version as well. It is confirmed that there exists unknown issues in Linux Kernel that user has to adjust the buffer size to more than four times of period size, or the program cannot write PCM frames into guest ALSA stack. --- .clang-format | 2 + .gitmodules | 10 +- Makefile | 54 ++++-- README.md | 9 +- cnfa | 1 - common.h | 16 ++ configs/linux.config | 1 + mk/check-libs.mk | 33 ++++ portaudio | 1 + virtio-snd.c | 389 +++++++++++++++++++++++++------------------ 10 files changed, 328 insertions(+), 188 deletions(-) delete mode 160000 cnfa create mode 160000 portaudio diff --git a/.clang-format b/.clang-format index 30c5a3f..4670690 100644 --- a/.clang-format +++ b/.clang-format @@ -24,3 +24,5 @@ ForEachMacros: - hlist_for_each_entry - rb_list_foreach - rb_list_foreach_safe +StatementAttributeLikeMacros: + - IIF diff --git a/.gitmodules b/.gitmodules index 0a900ad..eb01e2a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "cnfa"] - path = cnfa - url = https://github.com/cntools/cnfa - shallow = true [submodule "mini-gdbstub"] path = mini-gdbstub url = https://github.com/RinHizakura/mini-gdbstub @@ -9,4 +5,8 @@ [submodule "minislirp"] path = minislirp url = https://github.com/edubart/minislirp - shallow = true \ No newline at end of file + shallow = true +[submodule "portaudio"] + path = portaudio + url = https://github.com/PortAudio/portaudio + shallow = true diff --git a/Makefile b/Makefile index 22b6f14..b15e0f5 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ OBJS_EXTRA := # command line option OPTS := -LDFLAGS := -lm +LDFLAGS := # virtio-blk ENABLE_VIRTIOBLK ?= 1 @@ -56,37 +56,61 @@ endif # virtio-snd ENABLE_VIRTIOSND ?= 1 -ifneq ($(UNAME_S),$(filter $(UNAME_S),Linux)) +ifneq ($(UNAME_S),$(filter $(UNAME_S),Linux Darwin)) ENABLE_VIRTIOSND := 0 endif -# Check ALSA installation ifeq ($(UNAME_S),Linux) + # Check ALSA installation ifeq (0, $(call check-alsa)) $(warning No libasound installed. Check libasound in advance.) ENABLE_VIRTIOSND := 0 endif endif +ifeq ($(UNAME_S),Darwin) + ifeq (0, $(call check-coreaudio)) + $(warning No CoreAudio installed Check AudioToolbox in advance.) + ENABLE_VIRTIOSND := 0 + endif +endif $(call set-feature, VIRTIOSND) ifeq ($(call has, VIRTIOSND), 1) OBJS_EXTRA += virtio-snd.o - LDFLAGS += -lasound -lpthread - CFLAGS += -Icnfa + PORTAUDIOLIB := portaudio/lib/.libs/libportaudio.a + LDFLAGS += $(PORTAUDIOLIB) -cnfa/Makefile: - git submodule update --init cnfa -cnfa/os_generic: cnfa/Makefile - $(MAKE) -C $(dir $<) os_generic.h -CNFA_LIB := cnfa/CNFA_sf.h -$(CNFA_LIB): cnfa/Makefile cnfa/os_generic - $(MAKE) -C $(dir $<) CNFA_sf.h -main.o: $(CNFA_LIB) + ifeq ($(UNAME_S),Linux) + LDFLAGS += -lasound -lrt + # Check PulseAudio installation + ifeq (0, $(call check-pa)) + LDFLAGS += -lpulse + endif + endif + ifeq ($(UNAME_S),Darwin) + LDFLAGS += -framework CoreServices -framework CoreFoundation -framework AudioUnit -framework AudioToolbox -framework CoreAudio + endif -# suppress warning when compiling CNFA -virtio-snd.o: CFLAGS += -Wno-unused-parameter -Wno-sign-compare + CFLAGS += -Iportaudio/include + # PortAudio requires libm, yet we set -lm in the end of LDFLAGS + # so that the other libraries will be benefited for no need to set + # -lm separately. + LDFLAGS += -lpthread + +portaudio/Makefile: + git submodule update --init portaudio +$(PORTAUDIOLIB): portaudio/Makefile + cd $(dir $<) && ./configure + $(MAKE) -C $(dir $<) +main.o: $(PORTAUDIOLIB) + +# suppress warning when compiling PortAudio +virtio-snd.o: CFLAGS += -Wno-unused-parameter endif +# Set libm as the last dependency so that no need to set -lm seperately. +LDFLAGS += -lm + # .DEFAULT_GOAL should be set to all since the very first target is not all # after git submodule. .DEFAULT_GOAL := all diff --git a/README.md b/README.md index 48d067c..4a4a0db 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ A minimalist RISC-V system emulator capable of running Linux the kernel and corr - Three types of I/O support using VirtIO standard: - virtio-blk acquires disk image from the host. - virtio-net is mapped as TAP interface. - - virtio-snd uses ALSA for sound operation with the following limitations: - - The emulator will hang if PulseAudio is enabled on host. - - The playback may plays with repeating artifact. + - virtio-snd uses [PortAudio](https://github.com/PortAudio/portaudio) for sound playback on the host with one limitations: + - As some unknown issues in guest Linux OS (confirmed in v6.7 and v6.12), you need + to adjust the buffer size to more than four times of period size, or + the program cannot write the PCM frames into guest OS ALSA stack. + - For instance, the following buffer/period size settings on `aplay` has been tested + with broken and stutter effects yet complete with no any errors: `aplay --buffer-size=32768 --period-size=4096 /usr/share/sounds/alsa/Front_Center.wav`. ## Prerequisites diff --git a/cnfa b/cnfa deleted file mode 160000 index 60bcddd..0000000 --- a/cnfa +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 60bcddd1e22c727be8f967027a7efb4b6c58e167 diff --git a/common.h b/common.h index 5c6d807..d2b67f7 100644 --- a/common.h +++ b/common.h @@ -34,3 +34,19 @@ static inline int ilog2(int x) #else /* unsupported compilers */ #define PACKED(name) #endif + +/* Pattern Matching for C macros. + * https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms + */ + +/* In Visual Studio, __VA_ARGS__ is treated as a separate parameter. */ +#define FIX_VC_BUG(x) x + +/* catenate */ +#define PRIMITIVE_CAT(a, ...) FIX_VC_BUG(a##__VA_ARGS__) + +#define IIF(c) PRIMITIVE_CAT(IIF_, c) +/* run the 2nd parameter */ +#define IIF_0(t, ...) __VA_ARGS__ +/* run the 1st parameter */ +#define IIF_1(t, ...) t diff --git a/configs/linux.config b/configs/linux.config index 0696c26..cf2a4e5 100644 --- a/configs/linux.config +++ b/configs/linux.config @@ -945,6 +945,7 @@ CONFIG_DUMMY_CONSOLE_ROWS=25 CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_VIRTIO=y +CONFIG_SND_HRTIMER=y # # HID support diff --git a/mk/check-libs.mk b/mk/check-libs.mk index 888fc18..ce3ac41 100644 --- a/mk/check-libs.mk +++ b/mk/check-libs.mk @@ -10,8 +10,41 @@ int main(){\n\ }\n' endef +# Create a mininal PulseAudio program +define create-pa-prog +echo '\ +#include \n\ +int main(){\n\ + pa_mainloop *m;\n\ + return 0;\n\ +}\n' +endef + +# Create a mininal CoreAudio program +define create-ca-prog +echo '\ +#include \n\ +#include +int main(){\n\ + AudioQueueRef queue;\n\ + AudioQueueDispose(queue, TRUE);\n\ + return 0;\n\ +}\n' +endef + # Check ALSA installation define check-alsa $(shell $(call create-alsa-prog) | $(CC) -x c -lasound -o /dev/null > /dev/null 2> /dev/null - && echo $$?) endef + +# Check PulseAudio installation +define check-pa +$(shell $(call create-pa-prog) | $(CC) -x c -lpulse -o /dev/null - && echo 0 || echo 1) +endef + +# Check CoreAudio installation +define check-coreaudio +$(shell $(call create-ca-prog) | $(CC) -x c -framework AudioToolbox -o /dev/null > /dev/null 2> /dev/null - + && echo $$?) +endef diff --git a/portaudio b/portaudio new file mode 160000 index 0000000..e97effb --- /dev/null +++ b/portaudio @@ -0,0 +1 @@ +Subproject commit e97effb2b910e579efe8c5af40b43adf19fd8c33 diff --git a/virtio-snd.c b/virtio-snd.c index 3a1c491..efdbb50 100644 --- a/virtio-snd.c +++ b/virtio-snd.c @@ -4,8 +4,8 @@ #include #include #include -#define CNFA_IMPLEMENTATION -#include "CNFA_sf.h" + +#include #include "device.h" #include "riscv.h" @@ -274,11 +274,7 @@ typedef struct { } virtio_snd_queue_lock_t; typedef struct { - int32_t guest_playing; uint32_t stream_id; - int8_t is_done; - pthread_mutex_t ctrl_mutex; - pthread_cond_t ctrl_cond; } vsnd_stream_sel_t; typedef struct { @@ -294,7 +290,7 @@ typedef struct { virtio_snd_pcm_info_t p; virtio_snd_chmap_info_t c; virtio_snd_pcm_set_params_t pp; - struct CNFADriver *audio_host; + PaStream *pa_stream; // PCM frame queue lock virtio_snd_queue_lock_t lock; @@ -311,6 +307,12 @@ typedef struct { static virtio_snd_config_t vsnd_configs[VSND_DEV_CNT_MAX]; static virtio_snd_prop_t vsnd_props[VSND_DEV_CNT_MAX] = { [0 ... VSND_DEV_CNT_MAX - 1].pp.hdr.hdr.code = VIRTIO_SND_R_PCM_SET_PARAMS, + [0 ... VSND_DEV_CNT_MAX - 1].lock = + { + .lock = PTHREAD_MUTEX_INITIALIZER, + .readable = PTHREAD_COND_INITIALIZER, + .writable = PTHREAD_COND_INITIALIZER, + }, }; static int vsnd_dev_cnt = 0; @@ -318,13 +320,147 @@ static pthread_mutex_t virtio_snd_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t virtio_snd_tx_cond = PTHREAD_COND_INITIALIZER; static int tx_ev_notify; +/* vsnd virtq callback type */ +typedef int (*vsnd_virtq_cb)(virtio_snd_state_t *, /* vsnd state */ + const virtio_snd_queue_t *, /* vsnd queue */ + uint32_t, /* descriptor index */ + uint32_t * /* response length */); + /* Forward declaration */ -static void virtio_snd_cb(struct CNFADriver *dev, - short *out, - short *in, - int framesp, - int framesr); +static int virtio_snd_stream_cb(const void *input, + void *output, + unsigned long frame_cnt, + const PaStreamCallbackTimeInfo *time_info, + PaStreamCallbackFlags status_flags, + void *user_data); +static void virtio_queue_notify_handler(virtio_snd_state_t *vsnd, + int index, /* virtq index */ + vsnd_virtq_cb cb); +static void __virtio_snd_frame_enqueue(void *payload, + uint32_t n, + uint32_t stream_id); +typedef struct { + struct virtq_desc vq_desc; + struct list_head q; +} virtq_desc_queue_node_t; + +/* Flush only stream_id 0. + * FIXME: let TX queue flushing can select arbitrary stream_id. + */ +static uint32_t flush_stream_id = 0; + +#define VSND_GEN_TX_QUEUE_HANDLER(NAME_SUFFIX, WRITE) \ + static int virtio_snd_tx_desc_##NAME_SUFFIX##_handler( \ + virtio_snd_state_t *vsnd, const virtio_snd_queue_t *queue, \ + uint32_t desc_idx, uint32_t *plen) \ + { \ + /* A PCM I/O message uses at least 3 virtqueue descriptors to \ + * represent a PCM data of a period size. \ + * The first part contains one descriptor as follows: \ + * struct virtio_snd_pcm_xfer \ + * The second part contains one or more descriptors \ + * representing PCM frames. \ + * the last part contains one descriptor as follows: \ + * struct virtio_snd_pcm_status \ + */ \ + virtq_desc_queue_node_t *node; \ + struct list_head q; \ + INIT_LIST_HEAD(&q); \ + \ + /* Collect the descriptors */ \ + int cnt = 0; \ + for (;;) { \ + /* The size of the `struct virtq_desc` is 4 words */ \ + const uint32_t *desc = \ + &vsnd->ram[queue->QueueDesc + desc_idx * 4]; \ + \ + /* Retrieve the fields of current descriptor */ \ + node = (virtq_desc_queue_node_t *) malloc(sizeof(*node)); \ + node->vq_desc.addr = desc[0]; \ + node->vq_desc.len = desc[2]; \ + node->vq_desc.flags = desc[3]; \ + list_push(&node->q, &q); \ + desc_idx = desc[3] >> 16; /* vq_desc[desc_cnt].next */ \ + \ + cnt++; \ + \ + /* Leave the loop if next-flag is not set */ \ + if (!(desc[3] & VIRTIO_DESC_F_NEXT)) \ + break; \ + } \ + \ + int idx = 0; \ + uint32_t stream_id = 0; /* Explicitly set the stream_id */ \ + uintptr_t base = (uintptr_t) vsnd->ram; \ + uint32_t ret_len = 0; \ + uint8_t bad_msg_err = 0; \ + list_for_each_entry (node, &q, q) { \ + uint32_t addr = node->vq_desc.addr; \ + uint32_t len = node->vq_desc.len; \ + if (idx == 0) { /* the first descriptor */ \ + const virtio_snd_pcm_xfer_t *request = \ + (virtio_snd_pcm_xfer_t *) (base + addr); \ + stream_id = request->stream_id; \ + IIF(WRITE) \ + (/* enqueue frames */ \ + bad_msg_err = stream_id >= VSND_DEV_CNT_MAX ? 1 : 0; \ + , /* flush queue */ \ + bad_msg_err = stream_id != flush_stream_id \ + ? 1 \ + : 0; /* select only stream_id 0 */ \ + ) goto early_continue; \ + } else if (idx == cnt - 1) { /* the last descriptor */ \ + IIF(WRITE) \ + ( /* enqueue frames */ \ + , /* flush queue */ \ + if (bad_msg_err == 1) { \ + fprintf(stderr, "ignore flush stream_id %" PRIu32 "\n", \ + stream_id); \ + goto early_continue; \ + } fprintf(stderr, "flush stream_id %" PRIu32 "\n", \ + stream_id);) virtio_snd_pcm_status_t *response = \ + (virtio_snd_pcm_status_t *) (base + addr); \ + response->status = \ + bad_msg_err ? VIRTIO_SND_S_IO_ERR : VIRTIO_SND_S_OK; \ + response->latency_bytes = ret_len; \ + *plen = sizeof(*response); \ + goto early_continue; \ + } \ + \ + IIF(WRITE) \ + (/* enqueue frames */ \ + void *payload = (void *) (base + addr); if (bad_msg_err == 0) \ + __virtio_snd_frame_enqueue(payload, len, stream_id); \ + , /* flush queue */ \ + (void) stream_id; \ + /* Suppress unused variable warning. */) ret_len += len; \ + \ + early_continue: \ + idx++; \ + } \ + \ + if (bad_msg_err != 0) \ + goto finally; \ + IIF(WRITE) \ + (/* enque frames */ \ + virtio_snd_prop_t *props = &vsnd_props[stream_id]; \ + props->lock.buf_ev_notity++; \ + pthread_cond_signal(&props->lock.readable);, /* flush queue */ \ + ) \ + \ + /* Tear down the descriptor list and free space. */ \ + virtq_desc_queue_node_t *tmp = NULL; \ + list_for_each_entry_safe (node, tmp, &q, q) { \ + list_del(&node->q); \ + free(node); \ + } \ + \ + finally: \ + return 0; \ + } +VSND_GEN_TX_QUEUE_HANDLER(normal, 1); +VSND_GEN_TX_QUEUE_HANDLER(flush, 0); static void virtio_snd_set_fail(virtio_snd_state_t *vsnd) { @@ -415,7 +551,9 @@ static void virtio_snd_read_pcm_info_handler( props->p.hdr.hda_fn_nid = 0; props->p.features = 0; props->p.formats = (1 << VIRTIO_SND_PCM_FMT_S16); - props->p.rates = (1 << VIRTIO_SND_PCM_RATE_44100); +#define _(rate) props->p.rates |= (1 << VIRTIO_SND_PCM_RATE_##rate); + SND_PCM_RATE +#undef _ props->p.direction = VIRTIO_SND_D_OUTPUT; props->p.channels_min = 1; props->p.channels_max = 1; @@ -493,9 +631,7 @@ static void virtio_snd_read_pcm_prepare(const virtio_snd_pcm_hdr_t *query, } props->pp.hdr.hdr.code = VIRTIO_SND_R_PCM_PREPARE; - props->v.guest_playing = 0; props->v.stream_id = stream_id; - props->v.is_done = 0; uint32_t channels = props->pp.channels; uint32_t rate = pcm_rate_tbl[props->pp.rate]; @@ -515,21 +651,24 @@ static void virtio_snd_read_pcm_prepare(const virtio_snd_pcm_hdr_t *query, /* Calculate the period size (in frames) for CNFA . */ uint32_t cnfa_period_frames = cnfa_period_bytes / VSND_CNFA_FRAME_SZ; - /* The buffer size in frames when calling CNFAInit() - * is actually period size (i.e., period size then divide - * the length of frame). */ - /* CNFA only accept frame with signed 16-bit data in little-endian. */ - props->audio_host = - CNFAInit("ALSA", "semu-virtio-snd", virtio_snd_cb, rate, 0, channels, 0, - cnfa_period_frames, NULL, NULL, &props->v); - pthread_mutex_init(&props->lock.lock, NULL); - pthread_cond_init(&props->lock.readable, NULL); - pthread_cond_init(&props->lock.writable, NULL); - pthread_mutex_init(&props->v.ctrl_mutex, NULL); - pthread_cond_init(&props->v.ctrl_cond, NULL); INIT_LIST_HEAD(&props->buf_queue_head); props->intermediate = (void *) malloc(sizeof(*props->intermediate) * cnfa_period_bytes); + PaStreamParameters params = { + .device = Pa_GetDefaultOutputDevice(), + .channelCount = props->pp.channels, + .sampleFormat = paInt16, + .suggestedLatency = 0.1, /* 100 ms */ + .hostApiSpecificStreamInfo = NULL, + }; + PaError err = Pa_OpenStream(&props->pa_stream, NULL, /* no input */ + ¶ms, rate, cnfa_period_frames, paClipOff, + virtio_snd_stream_cb, &props->v); + if (err != paNoError) { + fprintf(stderr, "Cannot create PortAudio\n"); + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); + return; + } *plen = 0; } @@ -541,10 +680,9 @@ static void virtio_snd_read_pcm_start(const virtio_snd_pcm_hdr_t *query, uint32_t stream_id = request->stream_id; uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; if (code != VIRTIO_SND_R_PCM_PREPARE && code != VIRTIO_SND_R_PCM_STOP) { - fprintf( - stderr, - "virtio_snd_read_pcm_start with previous invalide state %#08x\n", - code); + fprintf(stderr, + "virtio_snd_read_pcm_start with previous invalide stat %#08x\n", + code); return; } @@ -552,8 +690,12 @@ static void virtio_snd_read_pcm_start(const virtio_snd_pcm_hdr_t *query, /* Control the callback to start playing */ props->pp.hdr.hdr.code = VIRTIO_SND_R_PCM_START; - props->v.guest_playing++; - pthread_cond_signal(&props->v.ctrl_cond); + PaError err = Pa_StartStream(props->pa_stream); + if (err != paNoError) { + fprintf(stderr, "get error in pcm_start\n"); + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); + return; + } *plen = 0; } @@ -566,7 +708,7 @@ static void virtio_snd_read_pcm_stop(const virtio_snd_pcm_hdr_t *query, uint32_t code = vsnd_props[stream_id].pp.hdr.hdr.code; if (code != VIRTIO_SND_R_PCM_START) { fprintf(stderr, - "virtio_snd_read_pcm_stop with previous invalide state %#08x\n", + "virtio_snd_read_pcm_stop with previous invalid state %#08x\n", code); return; } @@ -575,14 +717,19 @@ static void virtio_snd_read_pcm_stop(const virtio_snd_pcm_hdr_t *query, /* Control the callback to stop playing */ props->pp.hdr.hdr.code = VIRTIO_SND_R_PCM_STOP; - props->v.guest_playing--; - pthread_cond_signal(&props->v.ctrl_cond); + PaError err = Pa_StopStream(props->pa_stream); + if (err != paNoError) { + fprintf(stderr, "get error in pcm_stop\n"); + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); + return; + } *plen = 0; } static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, - uint32_t *plen) + uint32_t *plen, + virtio_snd_state_t *vsnd) { const virtio_snd_pcm_hdr_t *request = query; uint32_t stream_id = request->stream_id; @@ -591,7 +738,7 @@ static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, if (code != VIRTIO_SND_R_PCM_PREPARE && code != VIRTIO_SND_R_PCM_STOP) { fprintf( stderr, - "virtio_snd_read_pcm_release with previous invalide state %#08x\n", + "virtio_snd_read_pcm_release with previous invalid state %#08x\n", code); return; } @@ -604,11 +751,7 @@ static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, pthread_cond_broadcast(&props->lock.readable); pthread_cond_broadcast(&props->lock.writable); pthread_mutex_unlock(&props->lock.lock); - pthread_mutex_destroy(&props->lock.lock); - pthread_cond_destroy(&props->lock.readable); - pthread_cond_destroy(&props->lock.writable); - /* Tear down PCM buffer queue. */ vsnd_buf_queue_node_t *tmp = NULL; vsnd_buf_queue_node_t *node; if (!list_empty(&props->buf_queue_head)) { @@ -618,19 +761,29 @@ static void virtio_snd_read_pcm_release(const virtio_snd_pcm_hdr_t *query, } } - props->v.is_done = 1; - CNFAClose(props->audio_host); + /* avoid dangling pointers */ + props->intermediate = NULL; + + PaError err = Pa_CloseStream(props->pa_stream); + if (err != paNoError) { + fprintf(stderr, "get error in pcm_release\n"); + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); + } - /* Tear down stream related locking attributes. */ - pthread_cond_broadcast(&props->v.ctrl_cond); - pthread_mutex_unlock(&props->v.ctrl_mutex); - pthread_mutex_destroy(&props->v.ctrl_mutex); - pthread_cond_destroy(&props->v.ctrl_cond); + /* virtio-v1.3-csd01, 5.14.6.6.5.1, + * Device Requirements: Stream Release + * + * - The device MUST complete all pending I/O messages for the + * specified stream ID. + * - The device MUST NOT complete the control request while there + * are pending I/O messages for the specified stream ID. + */ + virtio_queue_notify_handler(vsnd, 2, virtio_snd_tx_desc_flush_handler); *plen = 0; } -static void __virtio_snd_frame_dequeue(short *out, +static void __virtio_snd_frame_dequeue(void *out, uint32_t n, uint32_t stream_id) { @@ -665,34 +818,22 @@ static void __virtio_snd_frame_dequeue(short *out, pthread_cond_signal(&props->lock.writable); pthread_mutex_unlock(&props->lock.lock); } -static void virtio_snd_cb(struct CNFADriver *dev, - short *out, - short *in, - int framesp, - int framesr) -{ - vsnd_stream_sel_t *v_ptr = (vsnd_stream_sel_t *) dev->opaque; - int channels = dev->channelsPlay; - uint32_t out_buf_sz = framesp * channels; - - pthread_mutex_lock(&v_ptr->ctrl_mutex); - if (v_ptr->is_done == 1) { - memset(out, 0, sizeof(*out) * out_buf_sz); - goto finally; - } - - while (v_ptr->guest_playing == 0) { - memset(out, 0, sizeof(*out) * out_buf_sz); - pthread_cond_wait(&v_ptr->ctrl_cond, &v_ptr->ctrl_mutex); - } +static int virtio_snd_stream_cb(const void *input, + void *output, + unsigned long frame_cnt, + const PaStreamCallbackTimeInfo *time_info, + PaStreamCallbackFlags status_flags, + void *user_data) +{ + vsnd_stream_sel_t *v_ptr = (vsnd_stream_sel_t *) user_data; uint32_t id = v_ptr->stream_id; + int channels = vsnd_props[id].pp.channels; + uint32_t out_buf_sz = frame_cnt * channels; uint32_t out_buf_bytes = out_buf_sz * VSND_CNFA_FRAME_SZ; + __virtio_snd_frame_dequeue(output, out_buf_bytes, id); - __virtio_snd_frame_dequeue(out, out_buf_bytes, id); - -finally: - pthread_mutex_unlock(&v_ptr->ctrl_mutex); + return paContinue; } #define VSND_DESC_CNT 3 @@ -758,7 +899,7 @@ static int virtio_snd_ctrl_desc_handler(virtio_snd_state_t *vsnd, virtio_snd_read_pcm_prepare(query, plen); break; case VIRTIO_SND_R_PCM_RELEASE: - virtio_snd_read_pcm_release(query, plen); + virtio_snd_read_pcm_release(query, plen, vsnd); break; case VIRTIO_SND_R_PCM_START: virtio_snd_read_pcm_start(query, plen); @@ -811,93 +952,6 @@ static void __virtio_snd_frame_enqueue(void *payload, pthread_mutex_unlock(&props->lock.lock); } -typedef struct { - struct virtq_desc vq_desc; - struct list_head q; -} virtq_desc_queue_node_t; -static int virtio_snd_tx_desc_handler(virtio_snd_state_t *vsnd, - const virtio_snd_queue_t *queue, - uint32_t desc_idx, - uint32_t *plen) -{ - /* A PCM I/O message uses at least 3 virtqueue descriptors to - * represent a PCM data of a period size. - * The first part contains one descriptor as follows: - * struct virtio_snd_pcm_xfer - * The second part contains one or more descriptors - * representing PCM frames. - * the last part contains one descriptor as follows: - * struct virtio_snd_pcm_status - */ - virtq_desc_queue_node_t *node; - struct list_head q; - INIT_LIST_HEAD(&q); - - /* Collect the descriptors */ - int cnt = 0; - for (;;) { - /* The size of the `struct virtq_desc` is 4 words */ - const struct virtq_desc *desc = - (struct virtq_desc *) &vsnd->ram[queue->QueueDesc + desc_idx * 4]; - - /* Retrieve the fields of current descriptor */ - node = (virtq_desc_queue_node_t *) malloc(sizeof(*node)); - node->vq_desc.addr = desc->addr; - node->vq_desc.len = desc->len; - node->vq_desc.flags = desc->flags; - list_push(&node->q, &q); - desc_idx = desc->next; - - cnt++; - - /* Leave the loop if next-flag is not set */ - if (!(desc->flags & VIRTIO_DESC_F_NEXT)) - break; - } - - int idx = 0; - uint32_t stream_id = 0; /* Explicitly set the stream_id */ - uintptr_t base = (uintptr_t) vsnd->ram; - uint32_t ret_len = 0; - list_for_each_entry (node, &q, q) { - uint32_t addr = node->vq_desc.addr; - uint32_t len = node->vq_desc.len; - if (idx == 0) { /* the first descriptor */ - const virtio_snd_pcm_xfer_t *request = - (virtio_snd_pcm_xfer_t *) (base + addr); - stream_id = request->stream_id; - goto early_continue; - } else if (idx == cnt - 1) { /* the last descriptor */ - virtio_snd_pcm_status_t *response = - (virtio_snd_pcm_status_t *) (base + addr); - response->status = VIRTIO_SND_S_OK; - response->latency_bytes = ret_len; - *plen = sizeof(*response); - goto early_continue; - } - - void *payload = (void *) (base + addr); - __virtio_snd_frame_enqueue(payload, len, stream_id); - ret_len += len; - - early_continue: - idx++; - } - - virtio_snd_prop_t *props = &vsnd_props[stream_id]; - props->lock.buf_ev_notity++; - pthread_cond_signal(&props->lock.readable); - - /* Tear down the descriptor list and free space. */ - virtq_desc_queue_node_t *tmp = NULL; - list_for_each_entry_safe (node, tmp, &q, q) { - list_del(&node->q); - free(node); - } - - return 0; -} - static void virtio_queue_notify_handler( virtio_snd_state_t *vsnd, int index, @@ -975,7 +1029,7 @@ static void *func(void *args) pthread_cond_wait(&virtio_snd_tx_cond, &virtio_snd_mutex); tx_ev_notify--; - virtio_queue_notify_handler(vsnd, 2, virtio_snd_tx_desc_handler); + virtio_queue_notify_handler(vsnd, 2, virtio_snd_tx_desc_normal_handler); pthread_mutex_unlock(&virtio_snd_mutex); } @@ -1172,7 +1226,7 @@ bool virtio_snd_init(virtio_snd_state_t *vsnd) { if (vsnd_dev_cnt >= VSND_DEV_CNT_MAX) { fprintf(stderr, - "Exceeded the number of virtio-snd devices that can be " + "Exceed the number of virtio-snd devices that can be " "allocated.\n"); return false; } @@ -1192,6 +1246,13 @@ bool virtio_snd_init(virtio_snd_state_t *vsnd) fprintf(stderr, "cannot create TX thread\n"); return false; } + /* Initialize PortAudio */ + PaError err = Pa_Initialize(); + if (err != paNoError) { + printf("PortAudio error: %s\n", Pa_GetErrorText(err)); + Pa_Terminate(); + return false; + } return true; }