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; }