Last active
February 16, 2023 21:01
-
-
Save spheenik/8140a4405f819c5cd2465a65c8bb6d09 to your computer and use it in GitHub Desktop.
QEMU sound improvement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git audio/audio.c audio/audio.c | |
index 6eccdb17ee..4eb190a18f 100644 | |
--- audio/audio.c | |
+++ audio/audio.c | |
@@ -2097,3 +2097,8 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) | |
} | |
} | |
} | |
+ | |
+int64_t audio_get_timer_ticks(void) | |
+{ | |
+ return conf.period.ticks; | |
+} | |
diff --git audio/audio_int.h audio/audio_int.h | |
index 244b454012..19ba2d7aa4 100644 | |
--- audio/audio_int.h | |
+++ audio/audio_int.h | |
@@ -210,6 +210,8 @@ extern const struct mixeng_volume nominal_volume; | |
void audio_driver_register(audio_driver *drv); | |
audio_driver *audio_driver_lookup(const char *name); | |
+int64_t audio_get_timer_ticks(void); | |
+ | |
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); | |
void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); | |
diff --git audio/paaudio.c audio/paaudio.c | |
index 949769774d..cfa200fa96 100644 | |
--- audio/paaudio.c | |
+++ audio/paaudio.c | |
@@ -1,16 +1,22 @@ | |
/* public domain */ | |
#include "qemu/osdep.h" | |
-#include "qemu-common.h" | |
+#include "qemu/timer.h" | |
#include "audio.h" | |
#include <pulse/pulseaudio.h> | |
#define AUDIO_CAP "pulseaudio" | |
+#define DEBUG | |
#include "audio_int.h" | |
-#include "audio_pt_int.h" | |
typedef struct { | |
- int samples; | |
+ int buffer_size_out; | |
+ int buffer_size_in; | |
+ int tlength; | |
+ int fragsize; | |
+ int maxlength_in; | |
+ int adjust_latency_out; | |
+ int adjust_latency_in; | |
char *server; | |
char *sink; | |
char *source; | |
@@ -24,28 +30,18 @@ typedef struct { | |
typedef struct { | |
HWVoiceOut hw; | |
- int done; | |
- int live; | |
- int decr; | |
- int rpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceOut; | |
typedef struct { | |
HWVoiceIn hw; | |
- int done; | |
- int dead; | |
- int incr; | |
- int wpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
- const void *read_data; | |
- size_t read_index, read_length; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceIn; | |
static void qpa_audio_fini(void *opaque); | |
@@ -89,7 +85,7 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | |
} \ | |
goto label; \ | |
} \ | |
- } while (0) | |
+ } while (0); | |
#define CHECK_DEAD_GOTO(c, stream, rerror, label) \ | |
do { \ | |
@@ -107,184 +103,61 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | |
} \ | |
goto label; \ | |
} \ | |
- } while (0) | |
- | |
-static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) | |
-{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- | |
- while (!p->read_data) { | |
- int r; | |
- | |
- r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- | |
- if (!p->read_data) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } else { | |
- p->read_index = 0; | |
- } | |
- } | |
- | |
- l = p->read_length < length ? p->read_length : length; | |
- memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); | |
- | |
- data = (uint8_t *) data + l; | |
- length -= l; | |
- | |
- p->read_index += l; | |
- p->read_length -= l; | |
- | |
- if (!p->read_length) { | |
- int r; | |
- | |
- r = pa_stream_drop (p->stream); | |
- p->read_data = NULL; | |
- p->read_length = 0; | |
- p->read_index = 0; | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- } | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
+ } while (0); | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) | |
+static int qpa_run_out(HWVoiceOut *hw, int live) | |
{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- int r; | |
- | |
- while (!(l = pa_stream_writable_size (p->stream))) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); | |
- | |
- if (l > length) { | |
- l = length; | |
- } | |
- | |
- r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); | |
- CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); | |
- | |
- data = (const uint8_t *) data + l; | |
- length -= l; | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
- | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static void *qpa_thread_out (void *arg) | |
-{ | |
- PAVoiceOut *pa = arg; | |
- HWVoiceOut *hw = &pa->hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ PAVoiceOut *pa = (PAVoiceOut *) hw; | |
+ int rpos, decr, samples; | |
+ size_t avail_bytes, max_bytes; | |
+ struct st_sample *src; | |
+ void *pa_dst; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
- for (;;) { | |
- int decr, to_mix, rpos; | |
+ decr = 0; | |
+ rpos = hw->rpos; | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- if (pa->live > 0) { | |
- break; | |
- } | |
+ avail_bytes = (size_t) live << hw->info.shift; | |
- if (audio_pt_wait(&pa->pt, __func__)) { | |
- goto exit; | |
- } | |
- } | |
+ max_bytes = pa_stream_writable_size(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, max_bytes != -1, fail); | |
- decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); | |
- rpos = pa->rpos; | |
+ samples = (int)(audio_MIN(avail_bytes, max_bytes)) >> hw->info.shift; | |
+ while (samples) { | |
+ int convert_samples = audio_MIN(samples, hw->samples - rpos); | |
+ size_t b_wanted = (size_t) convert_samples << hw->info.shift; | |
+ size_t b_effective = b_wanted; | |
- if (audio_pt_unlock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ r = pa_stream_begin_write(pa->stream, &pa_dst, &b_effective); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
+ CHECK_SUCCESS_GOTO(pa->g, (int *)0, b_effective == b_wanted, fail); | |
- while (to_mix) { | |
- int error; | |
- int chunk = audio_MIN (to_mix, hw->samples - rpos); | |
- struct st_sample *src = hw->mix_buf + rpos; | |
+ src = hw->mix_buf + rpos; | |
+ hw->clip(pa_dst, src, convert_samples); | |
- hw->clip (pa->pcm_buf, src, chunk); | |
- | |
- if (qpa_simple_write (pa, pa->pcm_buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_write failed\n"); | |
- return NULL; | |
- } | |
+ r = pa_stream_write(pa->stream, pa_dst, b_effective, | |
+ NULL, 0LL, PA_SEEK_RELATIVE); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r >= 0, fail); | |
- rpos = (rpos + chunk) % hw->samples; | |
- to_mix -= chunk; | |
- } | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
- | |
- pa->rpos = rpos; | |
- pa->live -= decr; | |
- pa->decr += decr; | |
+ rpos = (rpos + convert_samples) % hw->samples; | |
+ samples -= convert_samples; | |
+ decr += convert_samples; | |
} | |
- exit: | |
- audio_pt_unlock(&pa->pt, __func__); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_out (HWVoiceOut *hw, int live) | |
-{ | |
- int decr; | |
- PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return 0; | |
- } | |
+ bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- decr = audio_MIN (live, pa->decr); | |
- pa->decr -= decr; | |
- pa->live = live - decr; | |
- hw->rpos = pa->rpos; | |
- if (pa->live > 0) { | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- } | |
- else { | |
- audio_pt_unlock(&pa->pt, __func__); | |
- } | |
+ hw->rpos = rpos; | |
return decr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_out failed\n"); | |
+ goto bail; | |
} | |
static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
@@ -292,92 +165,68 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
return audio_pcm_sw_write (sw, buf, len); | |
} | |
-/* capture */ | |
-static void *qpa_thread_in (void *arg) | |
+static int qpa_run_in(HWVoiceIn *hw) | |
{ | |
- PAVoiceIn *pa = arg; | |
- HWVoiceIn *hw = &pa->hw; | |
+ PAVoiceIn *pa = (PAVoiceIn *) hw; | |
+ int wpos, incr; | |
+ char *pa_src; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
+ size_t pa_avail; | |
+ incr = 0; | |
+ wpos = hw->wpos; | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- for (;;) { | |
- int incr, to_grab, wpos; | |
+ size_t bytes_wanted = ((unsigned int) | |
+ (hw->samples - audio_pcm_hw_get_live_in(hw)) << hw->info.shift); | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ if (bytes_wanted == 0) { | |
+ /* no room */ | |
+ goto bail; | |
+ } | |
- if (pa->dead > 0) { | |
- break; | |
- } | |
+ size_t bytes_avail = pa_stream_readable_size(pa->stream); | |
- if (audio_pt_wait(&pa->pt, __func__)) { | |
- goto exit; | |
- } | |
- } | |
+ if (bytes_wanted > bytes_avail) { | |
+ bytes_wanted = bytes_avail; | |
+ } | |
- incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); | |
- wpos = pa->wpos; | |
+ while (bytes_wanted) { | |
+ r = pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
- if (audio_pt_unlock(&pa->pt, __func__)) { | |
- return NULL; | |
+ if (pa_avail == 0 || pa_avail > bytes_wanted) { | |
+ break; | |
} | |
- while (to_grab) { | |
- int error; | |
- int chunk = audio_MIN (to_grab, hw->samples - wpos); | |
- void *buf = advance (pa->pcm_buf, wpos); | |
+ bytes_wanted -= pa_avail; | |
- if (qpa_simple_read (pa, buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_read failed\n"); | |
- return NULL; | |
- } | |
- | |
- hw->conv (hw->conv_buf + wpos, buf, chunk); | |
+ while (pa_avail) { | |
+ int chunk = audio_MIN( | |
+ (int)(pa_avail >> hw->info.shift), hw->samples - wpos); | |
+ hw->conv(hw->conv_buf + wpos, pa_src, chunk); | |
wpos = (wpos + chunk) % hw->samples; | |
- to_grab -= chunk; | |
- } | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
+ pa_src += chunk << hw->info.shift; | |
+ pa_avail -= chunk << hw->info.shift; | |
+ incr += chunk; | |
} | |
- pa->wpos = wpos; | |
- pa->dead -= incr; | |
- pa->incr += incr; | |
+ r = pa_stream_drop(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
} | |
- exit: | |
- audio_pt_unlock(&pa->pt, __func__); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_in (HWVoiceIn *hw) | |
-{ | |
- int live, incr, dead; | |
- PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return 0; | |
- } | |
+bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- live = audio_pcm_hw_get_live_in (hw); | |
- dead = hw->samples - live; | |
- incr = audio_MIN (dead, pa->incr); | |
- pa->incr -= incr; | |
- pa->dead = dead - incr; | |
- hw->wpos = pa->wpos; | |
- if (pa->dead > 0) { | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- } | |
- else { | |
- audio_pt_unlock(&pa->pt, __func__); | |
- } | |
+ hw->wpos = wpos; | |
return incr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_in failed\n"); | |
+ goto bail; | |
} | |
static int qpa_read (SWVoiceIn *sw, void *buf, int len) | |
@@ -470,13 +319,6 @@ static void stream_state_cb (pa_stream *s, void * userdata) | |
} | |
} | |
-static void stream_request_cb (pa_stream *s, size_t length, void *userdata) | |
-{ | |
- paaudio *g = userdata; | |
- | |
- pa_threaded_mainloop_signal (g->mainloop, 0); | |
-} | |
- | |
static pa_stream *qpa_simple_new ( | |
paaudio *g, | |
const char *name, | |
@@ -498,23 +340,17 @@ static pa_stream *qpa_simple_new ( | |
} | |
pa_stream_set_state_callback (stream, stream_state_cb, g); | |
- pa_stream_set_read_callback (stream, stream_request_cb, g); | |
- pa_stream_set_write_callback (stream, stream_request_cb, g); | |
if (dir == PA_STREAM_PLAYBACK) { | |
- r = pa_stream_connect_playback (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
+ r = pa_stream_connect_playback(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_out ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
} else { | |
- r = pa_stream_connect_record (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE); | |
+ r = pa_stream_connect_record(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_in ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE); | |
} | |
if (r < 0) { | |
@@ -541,165 +377,167 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, | |
void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
- pa_buffer_attr ba; | |
struct audsettings obt_as = *as; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- /* | |
- * qemu audio tick runs at 100 Hz (by default), so processing | |
- * data chunks worth 10 ms of sound should be a good fit. | |
- */ | |
- ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); | |
- ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); | |
- ba.maxlength = -1; | |
- ba.prebuf = -1; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_PLAYBACK, | |
- g->conf.sink, | |
- &ss, | |
- NULL, /* channel map */ | |
- &ba, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t tlength = g->conf.tlength; | |
+ if (tlength == 0) { | |
+ tlength = (frames_per_tick_x1000) / 400; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_out; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ | |
+ ldebug("tick duration: %.2f ms (%.3f frames)\n", | |
+ ((float)timer_tick_duration) / SCALE_MS, | |
+ (float)frames_per_tick_x1000 / 1000.0f); | |
+ | |
+ ldebug("OUT internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("OUT tlength: %.2f ms (%"PRId64" frames)\n", | |
+ tlength * (1000.0f / as->freq), | |
+ tlength); | |
+ | |
+ ldebug("OUT adjust latency: %s\n", | |
+ g->conf.adjust_latency_out ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.tlength = tlength * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = -1; | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_PLAYBACK, | |
+ g->conf.sink, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for playback failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); | |
- pa->rpos = hw->rpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init(&pa->pt, qpa_thread_out, hw, AUDIO_CAP, __func__)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+fail1: | |
return -1; | |
} | |
static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
struct audsettings obt_as = *as; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_RECORD, | |
- g->conf.source, | |
- &ss, | |
- NULL, /* channel map */ | |
- NULL, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t fragsize = g->conf.fragsize; | |
+ if (fragsize == 0) { | |
+ fragsize = frames_per_tick_x1000 / 1000; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_in; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ int64_t maxlength = g->conf.maxlength_in; | |
+ if (maxlength == 0) { | |
+ maxlength = fragsize * 2; | |
+ } | |
+ | |
+ ldebug("IN internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("IN fragsize: %.2f ms (%"PRId64" frames)\n", | |
+ fragsize * (1000.0f / as->freq), | |
+ fragsize); | |
+ | |
+ ldebug("IN maxlength: %.2f ms (%"PRId64" frames)\n", | |
+ maxlength * (1000.0f / as->freq), | |
+ maxlength); | |
+ | |
+ ldebug("IN adjust latency: %s\n", | |
+ g->conf.adjust_latency_in ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.fragsize = fragsize * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = maxlength * pa_frame_size(&pa->ss); | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_RECORD, | |
+ g->conf.source, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for capture failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); | |
- pa->wpos = hw->wpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init(&pa->pt, qpa_thread_in, hw, AUDIO_CAP, __func__)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+ fail1: | |
return -1; | |
} | |
static void qpa_fini_out (HWVoiceOut *hw) | |
{ | |
- void *ret; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- audio_pt_lock(&pa->pt, __func__); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- audio_pt_join(&pa->pt, &ret, __func__); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini(&pa->pt, __func__); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static void qpa_fini_in (HWVoiceIn *hw) | |
{ | |
- void *ret; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- audio_pt_lock(&pa->pt, __func__); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- audio_pt_join(&pa->pt, &ret, __func__); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini(&pa->pt, __func__); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) | |
@@ -809,7 +647,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) | |
/* common */ | |
static PAConf glob_conf = { | |
- .samples = 4096, | |
+ .adjust_latency_out = 0, | |
+ .adjust_latency_in = 1, | |
}; | |
static void *qpa_audio_init (void) | |
@@ -897,10 +736,46 @@ static void qpa_audio_fini (void *opaque) | |
struct audio_option qpa_options[] = { | |
{ | |
- .name = "SAMPLES", | |
+ .name = "BUFFER_SIZE_OUT", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_out, | |
+ .descr = "internal buffer size in frames for playback device" | |
+ }, | |
+ { | |
+ .name = "BUFFER_SIZE_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_in, | |
+ .descr = "internal buffer size in frames for recording device" | |
+ }, | |
+ { | |
+ .name = "TLENGTH", | |
.tag = AUD_OPT_INT, | |
- .valp = &glob_conf.samples, | |
- .descr = "buffer size in samples" | |
+ .valp = &glob_conf.tlength, | |
+ .descr = "playback buffer target length in frames" | |
+ }, | |
+ { | |
+ .name = "FRAGSIZE", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.fragsize, | |
+ .descr = "fragment length of recording device in frames" | |
+ }, | |
+ { | |
+ .name = "MAXLENGTH_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.maxlength_in, | |
+ .descr = "maximum length of PA recording buffer in frames" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_OUT", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_out, | |
+ .descr = "instruct PA to adjust latency for playback device" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_IN", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_in, | |
+ .descr = "instruct PA to adjust latency for recording device" | |
}, | |
{ | |
.name = "SERVER", | |
diff --git hw/audio/hda-codec.c hw/audio/hda-codec.c | |
index e8aa7842e6..e68830490f 100644 | |
--- hw/audio/hda-codec.c | |
+++ hw/audio/hda-codec.c | |
@@ -18,6 +18,7 @@ | |
*/ | |
#include "qemu/osdep.h" | |
+#include "qemu/atomic.h" | |
#include "hw/hw.h" | |
#include "hw/pci/pci.h" | |
#include "intel-hda.h" | |
@@ -126,6 +127,11 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) | |
#define PARAM nomixemu | |
#include "hda-codec-common.h" | |
+#define HDA_TIMER_TICKS (SCALE_MS) | |
+#define MAX_CORR (SCALE_US * 100) | |
+#define B_SIZE sizeof(st->buf) | |
+#define B_MASK (sizeof(st->buf) - 1) | |
+ | |
/* -------------------------------------------------------------------------- */ | |
static const char *fmt2name[] = { | |
@@ -154,8 +160,13 @@ struct HDAAudioStream { | |
SWVoiceIn *in; | |
SWVoiceOut *out; | |
} voice; | |
- uint8_t buf[HDA_BUFFER_SIZE]; | |
- uint32_t bpos; | |
+ uint8_t compat_buf[HDA_BUFFER_SIZE]; | |
+ uint32_t compat_bpos; | |
+ uint8_t buf[8192]; /* size must be power of two */ | |
+ int64_t rpos; | |
+ int64_t wpos; | |
+ QEMUTimer *buft; | |
+ int64_t buft_start; | |
}; | |
#define TYPE_HDA_AUDIO "hda-audio" | |
@@ -176,53 +187,146 @@ struct HDAAudioState { | |
bool mixer; | |
}; | |
+static inline int64_t hda_bytes_per_second(HDAAudioStream *st) | |
+{ | |
+ return 2 * st->as.nchannels * st->as.freq; | |
+} | |
+ | |
+static inline void hda_timer_sync_adjust(HDAAudioStream *st, int64_t target_pos) | |
+{ | |
+ int64_t corr = | |
+ NANOSECONDS_PER_SECOND * target_pos / hda_bytes_per_second(st); | |
+ if (corr > MAX_CORR) { | |
+ corr = MAX_CORR; | |
+ } else if (corr < -MAX_CORR) { | |
+ corr = -MAX_CORR; | |
+ } | |
+ atomic_fetch_add(&st->buft_start, corr); | |
+} | |
+ | |
+static void hda_audio_input_timer(void *opaque) | |
+{ | |
+ HDAAudioStream *st = opaque; | |
+ | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ | |
+ int64_t buft_start = atomic_fetch_add(&st->buft_start, 0); | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t wanted_rpos = hda_bytes_per_second(st) * (now - buft_start) | |
+ / NANOSECONDS_PER_SECOND; | |
+ wanted_rpos &= -4; /* IMPORTANT! clip to frames */ | |
+ | |
+ if (wanted_rpos <= rpos) { | |
+ /* we already transmitted the data */ | |
+ goto out_timer; | |
+ } | |
+ | |
+ int64_t to_transfer = audio_MIN(wpos - rpos, wanted_rpos - rpos); | |
+ while (to_transfer) { | |
+ uint32_t start = (rpos & B_MASK); | |
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer); | |
+ int rc = hda_codec_xfer( | |
+ &st->state->hda, st->stream, false, st->buf + start, chunk); | |
+ if (!rc) { | |
+ break; | |
+ } | |
+ rpos += chunk; | |
+ to_transfer -= chunk; | |
+ atomic_fetch_add(&st->rpos, chunk); | |
+ } | |
+ | |
+out_timer: | |
+ | |
+ if (st->running) { | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
+ } | |
+} | |
+ | |
static void hda_audio_input_cb(void *opaque, int avail) | |
{ | |
HDAAudioStream *st = opaque; | |
- int recv = 0; | |
- int len; | |
- bool rc; | |
- | |
- while (avail - recv >= sizeof(st->buf)) { | |
- if (st->bpos != sizeof(st->buf)) { | |
- len = AUD_read(st->voice.in, st->buf + st->bpos, | |
- sizeof(st->buf) - st->bpos); | |
- st->bpos += len; | |
- recv += len; | |
- if (st->bpos != sizeof(st->buf)) { | |
- break; | |
- } | |
+ | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), avail); | |
+ | |
+ hda_timer_sync_adjust(st, -((wpos - rpos) + to_transfer - (B_SIZE >> 1))); | |
+ | |
+ while (to_transfer) { | |
+ uint32_t start = (uint32_t) (wpos & B_MASK); | |
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer); | |
+ uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk); | |
+ wpos += read; | |
+ to_transfer -= read; | |
+ atomic_fetch_add(&st->wpos, read); | |
+ if (chunk != read) { | |
+ break; | |
} | |
- rc = hda_codec_xfer(&st->state->hda, st->stream, false, | |
- st->buf, sizeof(st->buf)); | |
+ } | |
+} | |
+ | |
+static void hda_audio_output_timer(void *opaque) | |
+{ | |
+ HDAAudioStream *st = opaque; | |
+ | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ | |
+ int64_t buft_start = atomic_fetch_add(&st->buft_start, 0); | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t wanted_wpos = hda_bytes_per_second(st) * (now - buft_start) | |
+ / NANOSECONDS_PER_SECOND; | |
+ wanted_wpos &= -4; /* IMPORTANT! clip to frames */ | |
+ | |
+ if (wanted_wpos <= wpos) { | |
+ /* we already received the data */ | |
+ goto out_timer; | |
+ } | |
+ | |
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), wanted_wpos - wpos); | |
+ while (to_transfer) { | |
+ uint32_t start = (wpos & B_MASK); | |
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer); | |
+ int rc = hda_codec_xfer( | |
+ &st->state->hda, st->stream, true, st->buf + start, chunk); | |
if (!rc) { | |
break; | |
} | |
- st->bpos = 0; | |
+ wpos += chunk; | |
+ to_transfer -= chunk; | |
+ atomic_fetch_add(&st->wpos, chunk); | |
+ } | |
+ | |
+out_timer: | |
+ | |
+ if (st->running) { | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
} | |
} | |
static void hda_audio_output_cb(void *opaque, int avail) | |
{ | |
HDAAudioStream *st = opaque; | |
- int sent = 0; | |
- int len; | |
- bool rc; | |
- | |
- while (avail - sent >= sizeof(st->buf)) { | |
- if (st->bpos == sizeof(st->buf)) { | |
- rc = hda_codec_xfer(&st->state->hda, st->stream, true, | |
- st->buf, sizeof(st->buf)); | |
- if (!rc) { | |
- break; | |
- } | |
- st->bpos = 0; | |
- } | |
- len = AUD_write(st->voice.out, st->buf + st->bpos, | |
- sizeof(st->buf) - st->bpos); | |
- st->bpos += len; | |
- sent += len; | |
- if (st->bpos != sizeof(st->buf)) { | |
+ | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t to_transfer = audio_MIN(wpos - rpos, avail); | |
+ | |
+ hda_timer_sync_adjust(st, (wpos - rpos) - to_transfer - (B_SIZE >> 1)); | |
+ | |
+ while (to_transfer) { | |
+ uint32_t start = (uint32_t) (rpos & B_MASK); | |
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer); | |
+ uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk); | |
+ rpos += written; | |
+ to_transfer -= written; | |
+ atomic_fetch_add(&st->rpos, written); | |
+ if (chunk != written) { | |
break; | |
} | |
} | |
@@ -239,6 +343,15 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running) | |
st->running = running; | |
dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name, | |
st->running ? "on" : "off", st->stream); | |
+ if (running) { | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ st->rpos = 0; | |
+ st->wpos = 0; | |
+ st->buft_start = now; | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
+ } else { | |
+ timer_del(st->buft); | |
+ } | |
if (st->output) { | |
AUD_set_active_out(st->voice.out, st->running); | |
} else { | |
@@ -286,10 +399,12 @@ static void hda_audio_setup(HDAAudioStream *st) | |
st->voice.out = AUD_open_out(&st->state->card, st->voice.out, | |
st->node->name, st, | |
hda_audio_output_cb, &st->as); | |
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_output_timer, st); | |
} else { | |
st->voice.in = AUD_open_in(&st->state->card, st->voice.in, | |
st->node->name, st, | |
hda_audio_input_cb, &st->as); | |
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_input_timer, st); | |
} | |
} | |
@@ -505,7 +620,6 @@ static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc) | |
/* unmute output by default */ | |
st->gain_left = QEMU_HDA_AMP_STEPS; | |
st->gain_right = QEMU_HDA_AMP_STEPS; | |
- st->bpos = sizeof(st->buf); | |
st->output = true; | |
} else { | |
st->output = false; | |
@@ -532,6 +646,7 @@ static void hda_audio_exit(HDACodecDevice *hda) | |
if (st->node == NULL) { | |
continue; | |
} | |
+ timer_del(st->buft); | |
if (st->output) { | |
AUD_close_out(&a->card, st->voice.out); | |
} else { | |
@@ -592,8 +707,8 @@ static const VMStateDescription vmstate_hda_audio_stream = { | |
VMSTATE_UINT32(gain_right, HDAAudioStream), | |
VMSTATE_BOOL(mute_left, HDAAudioStream), | |
VMSTATE_BOOL(mute_right, HDAAudioStream), | |
- VMSTATE_UINT32(bpos, HDAAudioStream), | |
- VMSTATE_BUFFER(buf, HDAAudioStream), | |
+ VMSTATE_UINT32(compat_bpos, HDAAudioStream), | |
+ VMSTATE_BUFFER(compat_buf, HDAAudioStream), | |
VMSTATE_END_OF_LIST() | |
} | |
}; | |
diff --git hw/audio/intel-hda.c hw/audio/intel-hda.c | |
index 948268afd8..23a2cf6484 100644 | |
--- hw/audio/intel-hda.c | |
+++ hw/audio/intel-hda.c | |
@@ -407,13 +407,6 @@ static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, | |
if (st->bpl == NULL) { | |
return false; | |
} | |
- if (st->ctl & (1 << 26)) { | |
- /* | |
- * Wait with the next DMA xfer until the guest | |
- * has acked the buffer completion interrupt | |
- */ | |
- return false; | |
- } | |
left = len; | |
s = st->bentries; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git audio/audio.c audio/audio.c | |
index 6eccdb17ee..4eb190a18f 100644 | |
--- audio/audio.c | |
+++ audio/audio.c | |
@@ -2097,3 +2097,8 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) | |
} | |
} | |
} | |
+ | |
+int64_t audio_get_timer_ticks(void) | |
+{ | |
+ return conf.period.ticks; | |
+} | |
diff --git audio/audio_int.h audio/audio_int.h | |
index 244b454012..19ba2d7aa4 100644 | |
--- audio/audio_int.h | |
+++ audio/audio_int.h | |
@@ -210,6 +210,8 @@ extern const struct mixeng_volume nominal_volume; | |
void audio_driver_register(audio_driver *drv); | |
audio_driver *audio_driver_lookup(const char *name); | |
+int64_t audio_get_timer_ticks(void); | |
+ | |
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); | |
void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); | |
diff --git audio/paaudio.c audio/paaudio.c | |
index 949769774d..cfa200fa96 100644 | |
--- audio/paaudio.c | |
+++ audio/paaudio.c | |
@@ -1,16 +1,22 @@ | |
/* public domain */ | |
#include "qemu/osdep.h" | |
-#include "qemu-common.h" | |
+#include "qemu/timer.h" | |
#include "audio.h" | |
#include <pulse/pulseaudio.h> | |
#define AUDIO_CAP "pulseaudio" | |
+#define DEBUG | |
#include "audio_int.h" | |
-#include "audio_pt_int.h" | |
typedef struct { | |
- int samples; | |
+ int buffer_size_out; | |
+ int buffer_size_in; | |
+ int tlength; | |
+ int fragsize; | |
+ int maxlength_in; | |
+ int adjust_latency_out; | |
+ int adjust_latency_in; | |
char *server; | |
char *sink; | |
char *source; | |
@@ -24,28 +30,18 @@ typedef struct { | |
typedef struct { | |
HWVoiceOut hw; | |
- int done; | |
- int live; | |
- int decr; | |
- int rpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceOut; | |
typedef struct { | |
HWVoiceIn hw; | |
- int done; | |
- int dead; | |
- int incr; | |
- int wpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
- const void *read_data; | |
- size_t read_index, read_length; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceIn; | |
static void qpa_audio_fini(void *opaque); | |
@@ -89,7 +85,7 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | |
} \ | |
goto label; \ | |
} \ | |
- } while (0) | |
+ } while (0); | |
#define CHECK_DEAD_GOTO(c, stream, rerror, label) \ | |
do { \ | |
@@ -107,184 +103,61 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | |
} \ | |
goto label; \ | |
} \ | |
- } while (0) | |
- | |
-static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) | |
-{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- | |
- while (!p->read_data) { | |
- int r; | |
- | |
- r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- | |
- if (!p->read_data) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } else { | |
- p->read_index = 0; | |
- } | |
- } | |
- | |
- l = p->read_length < length ? p->read_length : length; | |
- memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); | |
- | |
- data = (uint8_t *) data + l; | |
- length -= l; | |
- | |
- p->read_index += l; | |
- p->read_length -= l; | |
- | |
- if (!p->read_length) { | |
- int r; | |
- | |
- r = pa_stream_drop (p->stream); | |
- p->read_data = NULL; | |
- p->read_length = 0; | |
- p->read_index = 0; | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- } | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
+ } while (0); | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) | |
+static int qpa_run_out(HWVoiceOut *hw, int live) | |
{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- int r; | |
- | |
- while (!(l = pa_stream_writable_size (p->stream))) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); | |
- | |
- if (l > length) { | |
- l = length; | |
- } | |
- | |
- r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); | |
- CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); | |
- | |
- data = (const uint8_t *) data + l; | |
- length -= l; | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
- | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static void *qpa_thread_out (void *arg) | |
-{ | |
- PAVoiceOut *pa = arg; | |
- HWVoiceOut *hw = &pa->hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ PAVoiceOut *pa = (PAVoiceOut *) hw; | |
+ int rpos, decr, samples; | |
+ size_t avail_bytes, max_bytes; | |
+ struct st_sample *src; | |
+ void *pa_dst; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
- for (;;) { | |
- int decr, to_mix, rpos; | |
+ decr = 0; | |
+ rpos = hw->rpos; | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- if (pa->live > 0) { | |
- break; | |
- } | |
+ avail_bytes = (size_t) live << hw->info.shift; | |
- if (audio_pt_wait(&pa->pt, __func__)) { | |
- goto exit; | |
- } | |
- } | |
+ max_bytes = pa_stream_writable_size(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, max_bytes != -1, fail); | |
- decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); | |
- rpos = pa->rpos; | |
+ samples = (int)(audio_MIN(avail_bytes, max_bytes)) >> hw->info.shift; | |
+ while (samples) { | |
+ int convert_samples = audio_MIN(samples, hw->samples - rpos); | |
+ size_t b_wanted = (size_t) convert_samples << hw->info.shift; | |
+ size_t b_effective = b_wanted; | |
- if (audio_pt_unlock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ r = pa_stream_begin_write(pa->stream, &pa_dst, &b_effective); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
+ CHECK_SUCCESS_GOTO(pa->g, (int *)0, b_effective == b_wanted, fail); | |
- while (to_mix) { | |
- int error; | |
- int chunk = audio_MIN (to_mix, hw->samples - rpos); | |
- struct st_sample *src = hw->mix_buf + rpos; | |
+ src = hw->mix_buf + rpos; | |
+ hw->clip(pa_dst, src, convert_samples); | |
- hw->clip (pa->pcm_buf, src, chunk); | |
- | |
- if (qpa_simple_write (pa, pa->pcm_buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_write failed\n"); | |
- return NULL; | |
- } | |
+ r = pa_stream_write(pa->stream, pa_dst, b_effective, | |
+ NULL, 0LL, PA_SEEK_RELATIVE); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r >= 0, fail); | |
- rpos = (rpos + chunk) % hw->samples; | |
- to_mix -= chunk; | |
- } | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
- | |
- pa->rpos = rpos; | |
- pa->live -= decr; | |
- pa->decr += decr; | |
+ rpos = (rpos + convert_samples) % hw->samples; | |
+ samples -= convert_samples; | |
+ decr += convert_samples; | |
} | |
- exit: | |
- audio_pt_unlock(&pa->pt, __func__); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_out (HWVoiceOut *hw, int live) | |
-{ | |
- int decr; | |
- PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return 0; | |
- } | |
+ bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- decr = audio_MIN (live, pa->decr); | |
- pa->decr -= decr; | |
- pa->live = live - decr; | |
- hw->rpos = pa->rpos; | |
- if (pa->live > 0) { | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- } | |
- else { | |
- audio_pt_unlock(&pa->pt, __func__); | |
- } | |
+ hw->rpos = rpos; | |
return decr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_out failed\n"); | |
+ goto bail; | |
} | |
static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
@@ -292,92 +165,68 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
return audio_pcm_sw_write (sw, buf, len); | |
} | |
-/* capture */ | |
-static void *qpa_thread_in (void *arg) | |
+static int qpa_run_in(HWVoiceIn *hw) | |
{ | |
- PAVoiceIn *pa = arg; | |
- HWVoiceIn *hw = &pa->hw; | |
+ PAVoiceIn *pa = (PAVoiceIn *) hw; | |
+ int wpos, incr; | |
+ char *pa_src; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
+ size_t pa_avail; | |
+ incr = 0; | |
+ wpos = hw->wpos; | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- for (;;) { | |
- int incr, to_grab, wpos; | |
+ size_t bytes_wanted = ((unsigned int) | |
+ (hw->samples - audio_pcm_hw_get_live_in(hw)) << hw->info.shift); | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ if (bytes_wanted == 0) { | |
+ /* no room */ | |
+ goto bail; | |
+ } | |
- if (pa->dead > 0) { | |
- break; | |
- } | |
+ size_t bytes_avail = pa_stream_readable_size(pa->stream); | |
- if (audio_pt_wait(&pa->pt, __func__)) { | |
- goto exit; | |
- } | |
- } | |
+ if (bytes_wanted > bytes_avail) { | |
+ bytes_wanted = bytes_avail; | |
+ } | |
- incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); | |
- wpos = pa->wpos; | |
+ while (bytes_wanted) { | |
+ r = pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
- if (audio_pt_unlock(&pa->pt, __func__)) { | |
- return NULL; | |
+ if (pa_avail == 0 || pa_avail > bytes_wanted) { | |
+ break; | |
} | |
- while (to_grab) { | |
- int error; | |
- int chunk = audio_MIN (to_grab, hw->samples - wpos); | |
- void *buf = advance (pa->pcm_buf, wpos); | |
+ bytes_wanted -= pa_avail; | |
- if (qpa_simple_read (pa, buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_read failed\n"); | |
- return NULL; | |
- } | |
- | |
- hw->conv (hw->conv_buf + wpos, buf, chunk); | |
+ while (pa_avail) { | |
+ int chunk = audio_MIN( | |
+ (int)(pa_avail >> hw->info.shift), hw->samples - wpos); | |
+ hw->conv(hw->conv_buf + wpos, pa_src, chunk); | |
wpos = (wpos + chunk) % hw->samples; | |
- to_grab -= chunk; | |
- } | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return NULL; | |
+ pa_src += chunk << hw->info.shift; | |
+ pa_avail -= chunk << hw->info.shift; | |
+ incr += chunk; | |
} | |
- pa->wpos = wpos; | |
- pa->dead -= incr; | |
- pa->incr += incr; | |
+ r = pa_stream_drop(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
} | |
- exit: | |
- audio_pt_unlock(&pa->pt, __func__); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_in (HWVoiceIn *hw) | |
-{ | |
- int live, incr, dead; | |
- PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- | |
- if (audio_pt_lock(&pa->pt, __func__)) { | |
- return 0; | |
- } | |
+bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- live = audio_pcm_hw_get_live_in (hw); | |
- dead = hw->samples - live; | |
- incr = audio_MIN (dead, pa->incr); | |
- pa->incr -= incr; | |
- pa->dead = dead - incr; | |
- hw->wpos = pa->wpos; | |
- if (pa->dead > 0) { | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- } | |
- else { | |
- audio_pt_unlock(&pa->pt, __func__); | |
- } | |
+ hw->wpos = wpos; | |
return incr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_in failed\n"); | |
+ goto bail; | |
} | |
static int qpa_read (SWVoiceIn *sw, void *buf, int len) | |
@@ -470,13 +319,6 @@ static void stream_state_cb (pa_stream *s, void * userdata) | |
} | |
} | |
-static void stream_request_cb (pa_stream *s, size_t length, void *userdata) | |
-{ | |
- paaudio *g = userdata; | |
- | |
- pa_threaded_mainloop_signal (g->mainloop, 0); | |
-} | |
- | |
static pa_stream *qpa_simple_new ( | |
paaudio *g, | |
const char *name, | |
@@ -498,23 +340,17 @@ static pa_stream *qpa_simple_new ( | |
} | |
pa_stream_set_state_callback (stream, stream_state_cb, g); | |
- pa_stream_set_read_callback (stream, stream_request_cb, g); | |
- pa_stream_set_write_callback (stream, stream_request_cb, g); | |
if (dir == PA_STREAM_PLAYBACK) { | |
- r = pa_stream_connect_playback (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
+ r = pa_stream_connect_playback(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_out ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
} else { | |
- r = pa_stream_connect_record (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE); | |
+ r = pa_stream_connect_record(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_in ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE); | |
} | |
if (r < 0) { | |
@@ -541,165 +377,167 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, | |
void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
- pa_buffer_attr ba; | |
struct audsettings obt_as = *as; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- /* | |
- * qemu audio tick runs at 100 Hz (by default), so processing | |
- * data chunks worth 10 ms of sound should be a good fit. | |
- */ | |
- ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); | |
- ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); | |
- ba.maxlength = -1; | |
- ba.prebuf = -1; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_PLAYBACK, | |
- g->conf.sink, | |
- &ss, | |
- NULL, /* channel map */ | |
- &ba, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t tlength = g->conf.tlength; | |
+ if (tlength == 0) { | |
+ tlength = (frames_per_tick_x1000) / 400; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_out; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ | |
+ ldebug("tick duration: %.2f ms (%.3f frames)\n", | |
+ ((float)timer_tick_duration) / SCALE_MS, | |
+ (float)frames_per_tick_x1000 / 1000.0f); | |
+ | |
+ ldebug("OUT internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("OUT tlength: %.2f ms (%"PRId64" frames)\n", | |
+ tlength * (1000.0f / as->freq), | |
+ tlength); | |
+ | |
+ ldebug("OUT adjust latency: %s\n", | |
+ g->conf.adjust_latency_out ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.tlength = tlength * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = -1; | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_PLAYBACK, | |
+ g->conf.sink, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for playback failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); | |
- pa->rpos = hw->rpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init(&pa->pt, qpa_thread_out, hw, AUDIO_CAP, __func__)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+fail1: | |
return -1; | |
} | |
static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
struct audsettings obt_as = *as; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_RECORD, | |
- g->conf.source, | |
- &ss, | |
- NULL, /* channel map */ | |
- NULL, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t fragsize = g->conf.fragsize; | |
+ if (fragsize == 0) { | |
+ fragsize = frames_per_tick_x1000 / 1000; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_in; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ int64_t maxlength = g->conf.maxlength_in; | |
+ if (maxlength == 0) { | |
+ maxlength = fragsize * 2; | |
+ } | |
+ | |
+ ldebug("IN internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("IN fragsize: %.2f ms (%"PRId64" frames)\n", | |
+ fragsize * (1000.0f / as->freq), | |
+ fragsize); | |
+ | |
+ ldebug("IN maxlength: %.2f ms (%"PRId64" frames)\n", | |
+ maxlength * (1000.0f / as->freq), | |
+ maxlength); | |
+ | |
+ ldebug("IN adjust latency: %s\n", | |
+ g->conf.adjust_latency_in ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.fragsize = fragsize * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = maxlength * pa_frame_size(&pa->ss); | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_RECORD, | |
+ g->conf.source, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for capture failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc(__func__, hw->samples, 1 << hw->info.shift); | |
- pa->wpos = hw->wpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init(&pa->pt, qpa_thread_in, hw, AUDIO_CAP, __func__)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+ fail1: | |
return -1; | |
} | |
static void qpa_fini_out (HWVoiceOut *hw) | |
{ | |
- void *ret; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- audio_pt_lock(&pa->pt, __func__); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- audio_pt_join(&pa->pt, &ret, __func__); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini(&pa->pt, __func__); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static void qpa_fini_in (HWVoiceIn *hw) | |
{ | |
- void *ret; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- audio_pt_lock(&pa->pt, __func__); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal(&pa->pt, __func__); | |
- audio_pt_join(&pa->pt, &ret, __func__); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini(&pa->pt, __func__); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) | |
@@ -809,7 +647,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) | |
/* common */ | |
static PAConf glob_conf = { | |
- .samples = 4096, | |
+ .adjust_latency_out = 0, | |
+ .adjust_latency_in = 1, | |
}; | |
static void *qpa_audio_init (void) | |
@@ -897,10 +736,46 @@ static void qpa_audio_fini (void *opaque) | |
struct audio_option qpa_options[] = { | |
{ | |
- .name = "SAMPLES", | |
+ .name = "BUFFER_SIZE_OUT", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_out, | |
+ .descr = "internal buffer size in frames for playback device" | |
+ }, | |
+ { | |
+ .name = "BUFFER_SIZE_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_in, | |
+ .descr = "internal buffer size in frames for recording device" | |
+ }, | |
+ { | |
+ .name = "TLENGTH", | |
.tag = AUD_OPT_INT, | |
- .valp = &glob_conf.samples, | |
- .descr = "buffer size in samples" | |
+ .valp = &glob_conf.tlength, | |
+ .descr = "playback buffer target length in frames" | |
+ }, | |
+ { | |
+ .name = "FRAGSIZE", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.fragsize, | |
+ .descr = "fragment length of recording device in frames" | |
+ }, | |
+ { | |
+ .name = "MAXLENGTH_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.maxlength_in, | |
+ .descr = "maximum length of PA recording buffer in frames" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_OUT", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_out, | |
+ .descr = "instruct PA to adjust latency for playback device" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_IN", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_in, | |
+ .descr = "instruct PA to adjust latency for recording device" | |
}, | |
{ | |
.name = "SERVER", |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git audio/audio.c audio/audio.c | |
index beafed209b..6f42a019b0 100644 | |
--- audio/audio.c | |
+++ audio/audio.c | |
@@ -2066,3 +2066,8 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) | |
} | |
} | |
} | |
+ | |
+int64_t audio_get_timer_ticks(void) | |
+{ | |
+ return conf.period.ticks; | |
+} | |
diff --git audio/audio_int.h audio/audio_int.h | |
index 5bcb1c60e1..2f7fc4f8ac 100644 | |
--- audio/audio_int.h | |
+++ audio/audio_int.h | |
@@ -214,6 +214,8 @@ extern struct audio_driver pa_audio_driver; | |
extern struct audio_driver spice_audio_driver; | |
extern const struct mixeng_volume nominal_volume; | |
+int64_t audio_get_timer_ticks(void); | |
+ | |
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); | |
void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); | |
diff --git audio/paaudio.c audio/paaudio.c | |
index 65beb6f010..b46beeea92 100644 | |
--- audio/paaudio.c | |
+++ audio/paaudio.c | |
@@ -1,16 +1,22 @@ | |
/* public domain */ | |
#include "qemu/osdep.h" | |
-#include "qemu-common.h" | |
+#include "qemu/timer.h" | |
#include "audio.h" | |
#include <pulse/pulseaudio.h> | |
#define AUDIO_CAP "pulseaudio" | |
+#define DEBUG | |
#include "audio_int.h" | |
-#include "audio_pt_int.h" | |
typedef struct { | |
- int samples; | |
+ int buffer_size_out; | |
+ int buffer_size_in; | |
+ int tlength; | |
+ int fragsize; | |
+ int maxlength_in; | |
+ int adjust_latency_out; | |
+ int adjust_latency_in; | |
char *server; | |
char *sink; | |
char *source; | |
@@ -24,28 +30,18 @@ typedef struct { | |
typedef struct { | |
HWVoiceOut hw; | |
- int done; | |
- int live; | |
- int decr; | |
- int rpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceOut; | |
typedef struct { | |
HWVoiceIn hw; | |
- int done; | |
- int dead; | |
- int incr; | |
- int wpos; | |
pa_stream *stream; | |
- void *pcm_buf; | |
- struct audio_pt pt; | |
- const void *read_data; | |
- size_t read_index, read_length; | |
paaudio *g; | |
+ pa_sample_spec ss; | |
+ pa_buffer_attr ba; | |
} PAVoiceIn; | |
static void qpa_audio_fini(void *opaque); | |
@@ -109,182 +105,59 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) | |
} \ | |
} while (0); | |
-static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror) | |
-{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- | |
- while (!p->read_data) { | |
- int r; | |
- | |
- r = pa_stream_peek (p->stream, &p->read_data, &p->read_length); | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- | |
- if (!p->read_data) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } else { | |
- p->read_index = 0; | |
- } | |
- } | |
- | |
- l = p->read_length < length ? p->read_length : length; | |
- memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); | |
- | |
- data = (uint8_t *) data + l; | |
- length -= l; | |
- | |
- p->read_index += l; | |
- p->read_length -= l; | |
- | |
- if (!p->read_length) { | |
- int r; | |
- | |
- r = pa_stream_drop (p->stream); | |
- p->read_data = NULL; | |
- p->read_length = 0; | |
- p->read_index = 0; | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, r == 0, unlock_and_fail); | |
- } | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
- | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror) | |
+static int qpa_run_out(HWVoiceOut *hw, int live) | |
{ | |
- paaudio *g = p->g; | |
- | |
- pa_threaded_mainloop_lock (g->mainloop); | |
- | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- | |
- while (length > 0) { | |
- size_t l; | |
- int r; | |
- | |
- while (!(l = pa_stream_writable_size (p->stream))) { | |
- pa_threaded_mainloop_wait (g->mainloop); | |
- CHECK_DEAD_GOTO (g, p->stream, rerror, unlock_and_fail); | |
- } | |
- | |
- CHECK_SUCCESS_GOTO (g, rerror, l != (size_t) -1, unlock_and_fail); | |
- | |
- if (l > length) { | |
- l = length; | |
- } | |
- | |
- r = pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); | |
- CHECK_SUCCESS_GOTO (g, rerror, r >= 0, unlock_and_fail); | |
- | |
- data = (const uint8_t *) data + l; | |
- length -= l; | |
- } | |
- | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return 0; | |
- | |
-unlock_and_fail: | |
- pa_threaded_mainloop_unlock (g->mainloop); | |
- return -1; | |
-} | |
- | |
-static void *qpa_thread_out (void *arg) | |
-{ | |
- PAVoiceOut *pa = arg; | |
- HWVoiceOut *hw = &pa->hw; | |
- | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
- } | |
+ PAVoiceOut *pa = (PAVoiceOut *) hw; | |
+ int rpos, decr, samples; | |
+ size_t avail_bytes, max_bytes; | |
+ struct st_sample *src; | |
+ void *pa_dst; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
- for (;;) { | |
- int decr, to_mix, rpos; | |
+ decr = 0; | |
+ rpos = hw->rpos; | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- if (pa->live > 0) { | |
- break; | |
- } | |
+ avail_bytes = (size_t) live << hw->info.shift; | |
- if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { | |
- goto exit; | |
- } | |
- } | |
+ max_bytes = pa_stream_writable_size(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, max_bytes != -1, fail); | |
- decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2); | |
- rpos = pa->rpos; | |
+ samples = (int)(audio_MIN(avail_bytes, max_bytes)) >> hw->info.shift; | |
+ while (samples) { | |
+ int convert_samples = audio_MIN(samples, hw->samples - rpos); | |
+ size_t b_wanted = (size_t) convert_samples << hw->info.shift; | |
+ size_t b_effective = b_wanted; | |
- if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
- } | |
+ r = pa_stream_begin_write(pa->stream, &pa_dst, &b_effective); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
+ CHECK_SUCCESS_GOTO(pa->g, (int *)0, b_effective == b_wanted, fail); | |
- while (to_mix) { | |
- int error; | |
- int chunk = audio_MIN (to_mix, hw->samples - rpos); | |
- struct st_sample *src = hw->mix_buf + rpos; | |
+ src = hw->mix_buf + rpos; | |
+ hw->clip(pa_dst, src, convert_samples); | |
- hw->clip (pa->pcm_buf, src, chunk); | |
- | |
- if (qpa_simple_write (pa, pa->pcm_buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_write failed\n"); | |
- return NULL; | |
- } | |
+ r = pa_stream_write(pa->stream, pa_dst, b_effective, | |
+ NULL, 0LL, PA_SEEK_RELATIVE); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r >= 0, fail); | |
- rpos = (rpos + chunk) % hw->samples; | |
- to_mix -= chunk; | |
- } | |
- | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
- } | |
- | |
- pa->rpos = rpos; | |
- pa->live -= decr; | |
- pa->decr += decr; | |
+ rpos = (rpos + convert_samples) % hw->samples; | |
+ samples -= convert_samples; | |
+ decr += convert_samples; | |
} | |
- exit: | |
- audio_pt_unlock (&pa->pt, AUDIO_FUNC); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_out (HWVoiceOut *hw, int live) | |
-{ | |
- int decr; | |
- PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return 0; | |
- } | |
+ bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- decr = audio_MIN (live, pa->decr); | |
- pa->decr -= decr; | |
- pa->live = live - decr; | |
- hw->rpos = pa->rpos; | |
- if (pa->live > 0) { | |
- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); | |
- } | |
- else { | |
- audio_pt_unlock (&pa->pt, AUDIO_FUNC); | |
- } | |
+ hw->rpos = rpos; | |
return decr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_out failed\n"); | |
+ goto bail; | |
} | |
static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
@@ -292,92 +165,68 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int len) | |
return audio_pcm_sw_write (sw, buf, len); | |
} | |
-/* capture */ | |
-static void *qpa_thread_in (void *arg) | |
+static int qpa_run_in(HWVoiceIn *hw) | |
{ | |
- PAVoiceIn *pa = arg; | |
- HWVoiceIn *hw = &pa->hw; | |
+ PAVoiceIn *pa = (PAVoiceIn *) hw; | |
+ int wpos, incr; | |
+ char *pa_src; | |
+ int error = 0; | |
+ int *rerror = &error; | |
+ int r; | |
+ size_t pa_avail; | |
+ incr = 0; | |
+ wpos = hw->wpos; | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
- } | |
+ pa_threaded_mainloop_lock(pa->g->mainloop); | |
+ CHECK_DEAD_GOTO(pa->g, pa->stream, rerror, fail); | |
- for (;;) { | |
- int incr, to_grab, wpos; | |
+ size_t bytes_wanted = ((unsigned int) | |
+ (hw->samples - audio_pcm_hw_get_live_in(hw)) << hw->info.shift); | |
- for (;;) { | |
- if (pa->done) { | |
- goto exit; | |
- } | |
+ if (bytes_wanted == 0) { | |
+ /* no room */ | |
+ goto bail; | |
+ } | |
- if (pa->dead > 0) { | |
- break; | |
- } | |
+ size_t bytes_avail = pa_stream_readable_size(pa->stream); | |
- if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { | |
- goto exit; | |
- } | |
- } | |
+ if (bytes_wanted > bytes_avail) { | |
+ bytes_wanted = bytes_avail; | |
+ } | |
- incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2); | |
- wpos = pa->wpos; | |
+ while (bytes_wanted) { | |
+ r = pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
- if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
+ if (pa_avail == 0 || pa_avail > bytes_wanted) { | |
+ break; | |
} | |
- while (to_grab) { | |
- int error; | |
- int chunk = audio_MIN (to_grab, hw->samples - wpos); | |
- void *buf = advance (pa->pcm_buf, wpos); | |
+ bytes_wanted -= pa_avail; | |
- if (qpa_simple_read (pa, buf, | |
- chunk << hw->info.shift, &error) < 0) { | |
- qpa_logerr (error, "pa_simple_read failed\n"); | |
- return NULL; | |
- } | |
- | |
- hw->conv (hw->conv_buf + wpos, buf, chunk); | |
+ while (pa_avail) { | |
+ int chunk = audio_MIN( | |
+ (int)(pa_avail >> hw->info.shift), hw->samples - wpos); | |
+ hw->conv(hw->conv_buf + wpos, pa_src, chunk); | |
wpos = (wpos + chunk) % hw->samples; | |
- to_grab -= chunk; | |
- } | |
- | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return NULL; | |
+ pa_src += chunk << hw->info.shift; | |
+ pa_avail -= chunk << hw->info.shift; | |
+ incr += chunk; | |
} | |
- pa->wpos = wpos; | |
- pa->dead -= incr; | |
- pa->incr += incr; | |
+ r = pa_stream_drop(pa->stream); | |
+ CHECK_SUCCESS_GOTO(pa->g, rerror, r == 0, fail); | |
} | |
- exit: | |
- audio_pt_unlock (&pa->pt, AUDIO_FUNC); | |
- return NULL; | |
-} | |
- | |
-static int qpa_run_in (HWVoiceIn *hw) | |
-{ | |
- int live, incr, dead; | |
- PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- | |
- if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { | |
- return 0; | |
- } | |
+bail: | |
+ pa_threaded_mainloop_unlock(pa->g->mainloop); | |
- live = audio_pcm_hw_get_live_in (hw); | |
- dead = hw->samples - live; | |
- incr = audio_MIN (dead, pa->incr); | |
- pa->incr -= incr; | |
- pa->dead = dead - incr; | |
- hw->wpos = pa->wpos; | |
- if (pa->dead > 0) { | |
- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); | |
- } | |
- else { | |
- audio_pt_unlock (&pa->pt, AUDIO_FUNC); | |
- } | |
+ hw->wpos = wpos; | |
return incr; | |
+ | |
+fail: | |
+ qpa_logerr(error, "qpa_run_in failed\n"); | |
+ goto bail; | |
} | |
static int qpa_read (SWVoiceIn *sw, void *buf, int len) | |
@@ -470,13 +319,6 @@ static void stream_state_cb (pa_stream *s, void * userdata) | |
} | |
} | |
-static void stream_request_cb (pa_stream *s, size_t length, void *userdata) | |
-{ | |
- paaudio *g = userdata; | |
- | |
- pa_threaded_mainloop_signal (g->mainloop, 0); | |
-} | |
- | |
static pa_stream *qpa_simple_new ( | |
paaudio *g, | |
const char *name, | |
@@ -498,23 +340,17 @@ static pa_stream *qpa_simple_new ( | |
} | |
pa_stream_set_state_callback (stream, stream_state_cb, g); | |
- pa_stream_set_read_callback (stream, stream_request_cb, g); | |
- pa_stream_set_write_callback (stream, stream_request_cb, g); | |
if (dir == PA_STREAM_PLAYBACK) { | |
- r = pa_stream_connect_playback (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
+ r = pa_stream_connect_playback(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_out ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); | |
} else { | |
- r = pa_stream_connect_record (stream, dev, attr, | |
- PA_STREAM_INTERPOLATE_TIMING | |
-#ifdef PA_STREAM_ADJUST_LATENCY | |
- |PA_STREAM_ADJUST_LATENCY | |
-#endif | |
- |PA_STREAM_AUTO_TIMING_UPDATE); | |
+ r = pa_stream_connect_record(stream, dev, attr, | |
+ PA_STREAM_INTERPOLATE_TIMING | |
+ | (g->conf.adjust_latency_in ? PA_STREAM_ADJUST_LATENCY : 0) | |
+ | PA_STREAM_AUTO_TIMING_UPDATE); | |
} | |
if (r < 0) { | |
@@ -541,165 +377,167 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as, | |
void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
- pa_buffer_attr ba; | |
struct audsettings obt_as = *as; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- /* | |
- * qemu audio tick runs at 100 Hz (by default), so processing | |
- * data chunks worth 10 ms of sound should be a good fit. | |
- */ | |
- ba.tlength = pa_usec_to_bytes (10 * 1000, &ss); | |
- ba.minreq = pa_usec_to_bytes (5 * 1000, &ss); | |
- ba.maxlength = -1; | |
- ba.prebuf = -1; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_PLAYBACK, | |
- g->conf.sink, | |
- &ss, | |
- NULL, /* channel map */ | |
- &ba, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t tlength = g->conf.tlength; | |
+ if (tlength == 0) { | |
+ tlength = (frames_per_tick_x1000) / 400; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_out; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ | |
+ ldebug("tick duration: %.2f ms (%.3f frames)\n", | |
+ ((float)timer_tick_duration) / SCALE_MS, | |
+ (float)frames_per_tick_x1000 / 1000.0f); | |
+ | |
+ ldebug("OUT internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("OUT tlength: %.2f ms (%"PRId64" frames)\n", | |
+ tlength * (1000.0f / as->freq), | |
+ tlength); | |
+ | |
+ ldebug("OUT adjust latency: %s\n", | |
+ g->conf.adjust_latency_out ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.tlength = tlength * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = -1; | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_PLAYBACK, | |
+ g->conf.sink, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for playback failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); | |
- pa->rpos = hw->rpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+fail1: | |
return -1; | |
} | |
static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) | |
{ | |
int error; | |
- pa_sample_spec ss; | |
struct audsettings obt_as = *as; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
paaudio *g = pa->g = drv_opaque; | |
- ss.format = audfmt_to_pa (as->fmt, as->endianness); | |
- ss.channels = as->nchannels; | |
- ss.rate = as->freq; | |
- | |
- obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); | |
- | |
- pa->stream = qpa_simple_new ( | |
- g, | |
- "qemu", | |
- PA_STREAM_RECORD, | |
- g->conf.source, | |
- &ss, | |
- NULL, /* channel map */ | |
- NULL, /* buffering attributes */ | |
- &error | |
- ); | |
+ int64_t timer_tick_duration = | |
+ audio_MAX(audio_get_timer_ticks(), 1 * SCALE_MS); | |
+ int64_t frames_per_tick_x1000 = | |
+ ((timer_tick_duration * as->freq * 1000LL) / NANOSECONDS_PER_SECOND); | |
+ | |
+ int64_t fragsize = g->conf.fragsize; | |
+ if (fragsize == 0) { | |
+ fragsize = frames_per_tick_x1000 / 1000; | |
+ } | |
+ int64_t buflen = g->conf.buffer_size_in; | |
+ if (buflen == 0) { | |
+ buflen = frames_per_tick_x1000 / 400; | |
+ } | |
+ int64_t maxlength = g->conf.maxlength_in; | |
+ if (maxlength == 0) { | |
+ maxlength = fragsize * 2; | |
+ } | |
+ | |
+ ldebug("IN internal buffer: %.2f ms (%"PRId64" frames)\n", | |
+ buflen * (1000.0f / as->freq), | |
+ buflen); | |
+ | |
+ ldebug("IN fragsize: %.2f ms (%"PRId64" frames)\n", | |
+ fragsize * (1000.0f / as->freq), | |
+ fragsize); | |
+ | |
+ ldebug("IN maxlength: %.2f ms (%"PRId64" frames)\n", | |
+ maxlength * (1000.0f / as->freq), | |
+ maxlength); | |
+ | |
+ ldebug("IN adjust latency: %s\n", | |
+ g->conf.adjust_latency_in ? "yes" : "no"); | |
+ | |
+ pa->ss.format = audfmt_to_pa(as->fmt, as->endianness); | |
+ pa->ss.channels = as->nchannels; | |
+ pa->ss.rate = as->freq; | |
+ | |
+ pa->ba.fragsize = fragsize * pa_frame_size(&pa->ss); | |
+ pa->ba.maxlength = maxlength * pa_frame_size(&pa->ss); | |
+ pa->ba.minreq = -1; | |
+ pa->ba.prebuf = -1; | |
+ | |
+ obt_as.fmt = pa_to_audfmt(pa->ss.format, &obt_as.endianness); | |
+ | |
+ pa->stream = qpa_simple_new( | |
+ g, | |
+ "qemu", | |
+ PA_STREAM_RECORD, | |
+ g->conf.source, | |
+ &pa->ss, | |
+ NULL, /* channel map */ | |
+ &pa->ba, /* buffering attributes */ | |
+ &error | |
+ ); | |
if (!pa->stream) { | |
qpa_logerr (error, "pa_simple_new for capture failed\n"); | |
goto fail1; | |
} | |
- audio_pcm_init_info (&hw->info, &obt_as); | |
- hw->samples = g->conf.samples; | |
- pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); | |
- pa->wpos = hw->wpos; | |
- if (!pa->pcm_buf) { | |
- dolog ("Could not allocate buffer (%d bytes)\n", | |
- hw->samples << hw->info.shift); | |
- goto fail2; | |
- } | |
- | |
- if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { | |
- goto fail3; | |
- } | |
+ audio_pcm_init_info(&hw->info, &obt_as); | |
+ hw->samples = buflen; | |
return 0; | |
- fail3: | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
- fail2: | |
- if (pa->stream) { | |
- pa_stream_unref (pa->stream); | |
- pa->stream = NULL; | |
- } | |
- fail1: | |
+ fail1: | |
return -1; | |
} | |
static void qpa_fini_out (HWVoiceOut *hw) | |
{ | |
- void *ret; | |
PAVoiceOut *pa = (PAVoiceOut *) hw; | |
- audio_pt_lock (&pa->pt, AUDIO_FUNC); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); | |
- audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini (&pa->pt, AUDIO_FUNC); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static void qpa_fini_in (HWVoiceIn *hw) | |
{ | |
- void *ret; | |
PAVoiceIn *pa = (PAVoiceIn *) hw; | |
- audio_pt_lock (&pa->pt, AUDIO_FUNC); | |
- pa->done = 1; | |
- audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); | |
- audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); | |
- | |
if (pa->stream) { | |
pa_stream_unref (pa->stream); | |
pa->stream = NULL; | |
} | |
- | |
- audio_pt_fini (&pa->pt, AUDIO_FUNC); | |
- g_free (pa->pcm_buf); | |
- pa->pcm_buf = NULL; | |
} | |
static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) | |
@@ -809,7 +647,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) | |
/* common */ | |
static PAConf glob_conf = { | |
- .samples = 4096, | |
+ .adjust_latency_out = 0, | |
+ .adjust_latency_in = 1, | |
}; | |
static void *qpa_audio_init (void) | |
@@ -897,10 +736,46 @@ static void qpa_audio_fini (void *opaque) | |
struct audio_option qpa_options[] = { | |
{ | |
- .name = "SAMPLES", | |
+ .name = "BUFFER_SIZE_OUT", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_out, | |
+ .descr = "internal buffer size in frames for playback device" | |
+ }, | |
+ { | |
+ .name = "BUFFER_SIZE_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.buffer_size_in, | |
+ .descr = "internal buffer size in frames for recording device" | |
+ }, | |
+ { | |
+ .name = "TLENGTH", | |
.tag = AUD_OPT_INT, | |
- .valp = &glob_conf.samples, | |
- .descr = "buffer size in samples" | |
+ .valp = &glob_conf.tlength, | |
+ .descr = "playback buffer target length in frames" | |
+ }, | |
+ { | |
+ .name = "FRAGSIZE", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.fragsize, | |
+ .descr = "fragment length of recording device in frames" | |
+ }, | |
+ { | |
+ .name = "MAXLENGTH_IN", | |
+ .tag = AUD_OPT_INT, | |
+ .valp = &glob_conf.maxlength_in, | |
+ .descr = "maximum length of PA recording buffer in frames" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_OUT", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_out, | |
+ .descr = "instruct PA to adjust latency for playback device" | |
+ }, | |
+ { | |
+ .name = "ADJUST_LATENCY_IN", | |
+ .tag = AUD_OPT_BOOL, | |
+ .valp = &glob_conf.adjust_latency_in, | |
+ .descr = "instruct PA to adjust latency for recording device" | |
}, | |
{ | |
.name = "SERVER", | |
diff --git hw/audio/hda-codec.c hw/audio/hda-codec.c | |
index 5402cd196c..ab89158bfc 100644 | |
--- hw/audio/hda-codec.c | |
+++ hw/audio/hda-codec.c | |
@@ -18,6 +18,7 @@ | |
*/ | |
#include "qemu/osdep.h" | |
+#include "qemu/atomic.h" | |
#include "hw/hw.h" | |
#include "hw/pci/pci.h" | |
#include "intel-hda.h" | |
@@ -126,6 +127,11 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) | |
#define PARAM nomixemu | |
#include "hda-codec-common.h" | |
+#define HDA_TIMER_TICKS (SCALE_MS) | |
+#define MAX_CORR (SCALE_US * 100) | |
+#define B_SIZE sizeof(st->buf) | |
+#define B_MASK (sizeof(st->buf) - 1) | |
+ | |
/* -------------------------------------------------------------------------- */ | |
static const char *fmt2name[] = { | |
@@ -154,8 +160,13 @@ struct HDAAudioStream { | |
SWVoiceIn *in; | |
SWVoiceOut *out; | |
} voice; | |
- uint8_t buf[HDA_BUFFER_SIZE]; | |
- uint32_t bpos; | |
+ uint8_t compat_buf[HDA_BUFFER_SIZE]; | |
+ uint32_t compat_bpos; | |
+ uint8_t buf[8192]; /* size must be power of two */ | |
+ int64_t rpos; | |
+ int64_t wpos; | |
+ QEMUTimer *buft; | |
+ int64_t buft_start; | |
}; | |
#define TYPE_HDA_AUDIO "hda-audio" | |
@@ -176,53 +187,146 @@ struct HDAAudioState { | |
bool mixer; | |
}; | |
+static inline int64_t hda_bytes_per_second(HDAAudioStream *st) | |
+{ | |
+ return 2 * st->as.nchannels * st->as.freq; | |
+} | |
+ | |
+static inline void hda_timer_sync_adjust(HDAAudioStream *st, int64_t target_pos) | |
+{ | |
+ int64_t corr = | |
+ NANOSECONDS_PER_SECOND * target_pos / hda_bytes_per_second(st); | |
+ if (corr > MAX_CORR) { | |
+ corr = MAX_CORR; | |
+ } else if (corr < -MAX_CORR) { | |
+ corr = -MAX_CORR; | |
+ } | |
+ atomic_fetch_add(&st->buft_start, corr); | |
+} | |
+ | |
+static void hda_audio_input_timer(void *opaque) | |
+{ | |
+ HDAAudioStream *st = opaque; | |
+ | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ | |
+ int64_t buft_start = atomic_fetch_add(&st->buft_start, 0); | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t wanted_rpos = hda_bytes_per_second(st) * (now - buft_start) | |
+ / NANOSECONDS_PER_SECOND; | |
+ wanted_rpos &= -4; /* IMPORTANT! clip to frames */ | |
+ | |
+ if (wanted_rpos <= rpos) { | |
+ /* we already transmitted the data */ | |
+ goto out_timer; | |
+ } | |
+ | |
+ int64_t to_transfer = audio_MIN(wpos - rpos, wanted_rpos - rpos); | |
+ while (to_transfer) { | |
+ uint32_t start = (rpos & B_MASK); | |
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer); | |
+ int rc = hda_codec_xfer( | |
+ &st->state->hda, st->stream, false, st->buf + start, chunk); | |
+ if (!rc) { | |
+ break; | |
+ } | |
+ rpos += chunk; | |
+ to_transfer -= chunk; | |
+ atomic_fetch_add(&st->rpos, chunk); | |
+ } | |
+ | |
+out_timer: | |
+ | |
+ if (st->running) { | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
+ } | |
+} | |
+ | |
static void hda_audio_input_cb(void *opaque, int avail) | |
{ | |
HDAAudioStream *st = opaque; | |
- int recv = 0; | |
- int len; | |
- bool rc; | |
- | |
- while (avail - recv >= sizeof(st->buf)) { | |
- if (st->bpos != sizeof(st->buf)) { | |
- len = AUD_read(st->voice.in, st->buf + st->bpos, | |
- sizeof(st->buf) - st->bpos); | |
- st->bpos += len; | |
- recv += len; | |
- if (st->bpos != sizeof(st->buf)) { | |
- break; | |
- } | |
+ | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), avail); | |
+ | |
+ hda_timer_sync_adjust(st, -((wpos - rpos) + to_transfer - (B_SIZE >> 1))); | |
+ | |
+ while (to_transfer) { | |
+ uint32_t start = (uint32_t) (wpos & B_MASK); | |
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer); | |
+ uint32_t read = AUD_read(st->voice.in, st->buf + start, chunk); | |
+ wpos += read; | |
+ to_transfer -= read; | |
+ atomic_fetch_add(&st->wpos, read); | |
+ if (chunk != read) { | |
+ break; | |
} | |
- rc = hda_codec_xfer(&st->state->hda, st->stream, false, | |
- st->buf, sizeof(st->buf)); | |
+ } | |
+} | |
+ | |
+static void hda_audio_output_timer(void *opaque) | |
+{ | |
+ HDAAudioStream *st = opaque; | |
+ | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ | |
+ int64_t buft_start = atomic_fetch_add(&st->buft_start, 0); | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t wanted_wpos = hda_bytes_per_second(st) * (now - buft_start) | |
+ / NANOSECONDS_PER_SECOND; | |
+ wanted_wpos &= -4; /* IMPORTANT! clip to frames */ | |
+ | |
+ if (wanted_wpos <= wpos) { | |
+ /* we already received the data */ | |
+ goto out_timer; | |
+ } | |
+ | |
+ int64_t to_transfer = audio_MIN(B_SIZE - (wpos - rpos), wanted_wpos - wpos); | |
+ while (to_transfer) { | |
+ uint32_t start = (wpos & B_MASK); | |
+ uint32_t chunk = audio_MIN(B_SIZE - start, to_transfer); | |
+ int rc = hda_codec_xfer( | |
+ &st->state->hda, st->stream, true, st->buf + start, chunk); | |
if (!rc) { | |
break; | |
} | |
- st->bpos = 0; | |
+ wpos += chunk; | |
+ to_transfer -= chunk; | |
+ atomic_fetch_add(&st->wpos, chunk); | |
+ } | |
+ | |
+out_timer: | |
+ | |
+ if (st->running) { | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
} | |
} | |
static void hda_audio_output_cb(void *opaque, int avail) | |
{ | |
HDAAudioStream *st = opaque; | |
- int sent = 0; | |
- int len; | |
- bool rc; | |
- | |
- while (avail - sent >= sizeof(st->buf)) { | |
- if (st->bpos == sizeof(st->buf)) { | |
- rc = hda_codec_xfer(&st->state->hda, st->stream, true, | |
- st->buf, sizeof(st->buf)); | |
- if (!rc) { | |
- break; | |
- } | |
- st->bpos = 0; | |
- } | |
- len = AUD_write(st->voice.out, st->buf + st->bpos, | |
- sizeof(st->buf) - st->bpos); | |
- st->bpos += len; | |
- sent += len; | |
- if (st->bpos != sizeof(st->buf)) { | |
+ | |
+ int64_t wpos = atomic_fetch_add(&st->wpos, 0); | |
+ int64_t rpos = atomic_fetch_add(&st->rpos, 0); | |
+ | |
+ int64_t to_transfer = audio_MIN(wpos - rpos, avail); | |
+ | |
+ hda_timer_sync_adjust(st, (wpos - rpos) - to_transfer - (B_SIZE >> 1)); | |
+ | |
+ while (to_transfer) { | |
+ uint32_t start = (uint32_t) (rpos & B_MASK); | |
+ uint32_t chunk = (uint32_t) audio_MIN(B_SIZE - start, to_transfer); | |
+ uint32_t written = AUD_write(st->voice.out, st->buf + start, chunk); | |
+ rpos += written; | |
+ to_transfer -= written; | |
+ atomic_fetch_add(&st->rpos, written); | |
+ if (chunk != written) { | |
break; | |
} | |
} | |
@@ -239,6 +343,15 @@ static void hda_audio_set_running(HDAAudioStream *st, bool running) | |
st->running = running; | |
dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name, | |
st->running ? "on" : "off", st->stream); | |
+ if (running) { | |
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
+ st->rpos = 0; | |
+ st->wpos = 0; | |
+ st->buft_start = now; | |
+ timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); | |
+ } else { | |
+ timer_del(st->buft); | |
+ } | |
if (st->output) { | |
AUD_set_active_out(st->voice.out, st->running); | |
} else { | |
@@ -286,10 +399,12 @@ static void hda_audio_setup(HDAAudioStream *st) | |
st->voice.out = AUD_open_out(&st->state->card, st->voice.out, | |
st->node->name, st, | |
hda_audio_output_cb, &st->as); | |
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_output_timer, st); | |
} else { | |
st->voice.in = AUD_open_in(&st->state->card, st->voice.in, | |
st->node->name, st, | |
hda_audio_input_cb, &st->as); | |
+ st->buft = timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_input_timer, st); | |
} | |
} | |
@@ -505,7 +620,6 @@ static int hda_audio_init(HDACodecDevice *hda, const struct desc_codec *desc) | |
/* unmute output by default */ | |
st->gain_left = QEMU_HDA_AMP_STEPS; | |
st->gain_right = QEMU_HDA_AMP_STEPS; | |
- st->bpos = sizeof(st->buf); | |
st->output = true; | |
} else { | |
st->output = false; | |
@@ -532,6 +646,7 @@ static void hda_audio_exit(HDACodecDevice *hda) | |
if (st->node == NULL) { | |
continue; | |
} | |
+ timer_del(st->buft); | |
if (st->output) { | |
AUD_close_out(&a->card, st->voice.out); | |
} else { | |
@@ -592,8 +707,8 @@ static const VMStateDescription vmstate_hda_audio_stream = { | |
VMSTATE_UINT32(gain_right, HDAAudioStream), | |
VMSTATE_BOOL(mute_left, HDAAudioStream), | |
VMSTATE_BOOL(mute_right, HDAAudioStream), | |
- VMSTATE_UINT32(bpos, HDAAudioStream), | |
- VMSTATE_BUFFER(buf, HDAAudioStream), | |
+ VMSTATE_UINT32(compat_bpos, HDAAudioStream), | |
+ VMSTATE_BUFFER(compat_buf, HDAAudioStream), | |
VMSTATE_END_OF_LIST() | |
} | |
}; | |
diff --git hw/audio/intel-hda.c hw/audio/intel-hda.c | |
index 18a50a8f83..721eba792d 100644 | |
--- hw/audio/intel-hda.c | |
+++ hw/audio/intel-hda.c | |
@@ -407,13 +407,6 @@ static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, | |
if (st->bpl == NULL) { | |
return false; | |
} | |
- if (st->ctl & (1 << 26)) { | |
- /* | |
- * Wait with the next DMA xfer until the guest | |
- * has acked the buffer completion interrupt | |
- */ | |
- return false; | |
- } | |
left = len; | |
s = st->bentries; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@Vaporeon: Done