From nobody Mon Apr 29 21:05:43 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1507657933624886.7666733688321; Tue, 10 Oct 2017 10:52:13 -0700 (PDT) Received: from localhost ([::1]:36414 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1e1yhA-0006Hv-I9 for importer@patchew.org; Tue, 10 Oct 2017 13:51:56 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:56911) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1e1yZf-0000Jg-1X for qemu-devel@nongnu.org; Tue, 10 Oct 2017 13:44:14 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1e1yZb-0005fU-15 for qemu-devel@nongnu.org; Tue, 10 Oct 2017 13:44:11 -0400 Received: from hermes.schrodt.org ([2a01:488:66:1000:b24d:49f5:0:1]:45640) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1e1yZa-0005ez-Iy for qemu-devel@nongnu.org; Tue, 10 Oct 2017 13:44:06 -0400 Received: from schapa.schrodt.org ([46.237.225.84]:56102 helo=zoidberg.machine.schrodt.org) by lvps178-77-73-245.dedicated.hosteurope.de with esmtps (TLS1.2:DHE_RSA_AES_256_CBC_SHA256:256) (Exim 4.80) (envelope-from ) id 1e1yZX-0007Gy-Mq for qemu-devel@nongnu.org; Tue, 10 Oct 2017 19:44:03 +0200 Received: from spheenik by zoidberg.machine.schrodt.org with local (Exim 4.89) (envelope-from ) id 1e1yZX-0006QU-DM for qemu-devel@nongnu.org; Tue, 10 Oct 2017 19:44:03 +0200 From: Martin Schrodt To: qemu-devel@nongnu.org Date: Tue, 10 Oct 2017 19:44:03 +0200 Message-Id: <20171010174403.24660-1-martin@schrodt.org> X-Mailer: git-send-email 2.14.2 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 2a01:488:66:1000:b24d:49f5:0:1 X-Mailman-Approved-At: Tue, 10 Oct 2017 13:45:13 -0400 Subject: [Qemu-devel] [PATCH] Several fixes for the Pulse Audio driver, and the HDA device. X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Please see https://www.reddit.com/r/VFIO/comments/74vokw/improved_pulse_audio_driver_f= or_qemu/ for motivation, and more coverage. Changes: - Remove PA reader/writer threads from paaudio.c, and do IO from the audio = timer directly. - Add 1 millisecond timers which interface with the HDA device, pulling and= pushing data to and from it, to enable the guest driver to measure DMA timing by just = looking the LPIB registers. - Expose new configurable settings, such as TLENGTH and FRAGSIZE, plus sett= ings to enable PA_STREAM_ADJUST_LATENCY for input and output device separately. - Fix the input delay when first using the input device. Signed-off-by: Martin Schrodt --- audio/audio.c | 4 + audio/audio_int.h | 2 + audio/paaudio.c | 640 ++++++++++++++++++++----------------------= ---- hw/audio/hda-codec.c | 218 +++++++++++++--- hw/audio/intel-hda-defs.h | 1 + hw/audio/intel-hda.c | 14 +- 6 files changed, 468 insertions(+), 411 deletions(-) diff --git a/audio/audio.c b/audio/audio.c index beafed209b..fba1604c34 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -2066,3 +2066,7 @@ void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint= 8_t lvol, uint8_t rvol) } } } + +int64_t audio_get_timer_ticks(void) { + return conf.period.ticks; +} diff --git a/audio/audio_int.h b/audio/audio_int.h index 5bcb1c60e1..2f7fc4f8ac 100644 --- a/audio/audio_int.h +++ b/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; =20 +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); =20 diff --git a/audio/paaudio.c b/audio/paaudio.c index 65beb6f010..089af32e4d 100644 --- a/audio/paaudio.c +++ b/audio/paaudio.c @@ -1,16 +1,44 @@ -/* public domain */ +/* + * QEMU ALSA audio driver + * + * Copyright (c) 2017 Martin Schrodt (spheenik) + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included= in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS= OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OT= HER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING= FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS = IN + * THE SOFTWARE. + */ #include "qemu/osdep.h" -#include "qemu-common.h" #include "audio.h" =20 + #include +#include =20 #define AUDIO_CAP "pulseaudio" #include "audio_int.h" -#include "audio_pt_int.h" =20 typedef struct { - int samples; + int buffer_size; + int tlength; + int fragsize; +#ifdef PA_STREAM_ADJUST_LATENCY + int adjust_latency_out; + int adjust_latency_in; +#endif char *server; char *sink; char *source; @@ -24,30 +52,21 @@ typedef struct { =20 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; =20 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; =20 + static void qpa_audio_fini(void *opaque); =20 static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) @@ -84,207 +103,85 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t= x) #define CHECK_SUCCESS_GOTO(c, rerror, expression, label) \ do { \ if (!(expression)) { \ - if (rerror) { \ - *(rerror) =3D pa_context_errno ((c)->context); \ - } \ + *(rerror) =3D pa_context_errno ((c)->context); \ goto label; \ } \ } while (0); =20 + #define CHECK_DEAD_GOTO(c, stream, rerror, label) \ do { \ if (!(c)->context || !PA_CONTEXT_IS_GOOD (pa_context_get_state((c)= ->context)) || \ !(stream) || !PA_STREAM_IS_GOOD (pa_stream_get_state ((stream)= ))) { \ if (((c)->context && pa_context_get_state ((c)->context) =3D= =3D PA_CONTEXT_FAILED) || \ ((stream) && pa_stream_get_state ((stream)) =3D=3D PA_STRE= AM_FAILED)) { \ - if (rerror) { \ - *(rerror) =3D pa_context_errno ((c)->context); \ - } \ + *(rerror) =3D pa_context_errno ((c)->context); \ } else { \ - if (rerror) { \ - *(rerror) =3D PA_ERR_BADSTATE; \ - } \ + *(rerror) =3D PA_ERR_BADSTATE; \ } \ goto label; \ } \ - } while (0); - -static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *= rerror) -{ - paaudio *g =3D 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 =3D pa_stream_peek (p->stream, &p->read_data, &p->read_lengt= h); - CHECK_SUCCESS_GOTO (g, rerror, r =3D=3D 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 =3D 0; - } - } - - l =3D p->read_length < length ? p->read_length : length; - memcpy (data, (const uint8_t *) p->read_data+p->read_index, l); - - data =3D (uint8_t *) data + l; - length -=3D l; +} while (0); =20 - p->read_index +=3D l; - p->read_length -=3D l; - - if (!p->read_length) { - int r; - - r =3D pa_stream_drop (p->stream); - p->read_data =3D NULL; - p->read_length =3D 0; - p->read_index =3D 0; - - CHECK_SUCCESS_GOTO (g, rerror, r =3D=3D 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 lengt= h, int *rerror) -{ - paaudio *g =3D 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 =3D 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 !=3D (size_t) -1, unlock_and_fail= ); - - if (l > length) { - l =3D length; - } - - r =3D pa_stream_write (p->stream, data, l, NULL, 0LL, PA_SEEK_RELA= TIVE); - CHECK_SUCCESS_GOTO (g, rerror, r >=3D 0, unlock_and_fail); - - data =3D (const uint8_t *) data + l; - length -=3D 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) +static int qpa_run_out (HWVoiceOut *hw, int live) { - PAVoiceOut *pa =3D arg; - HWVoiceOut *hw =3D &pa->hw; + PAVoiceOut *pa =3D (PAVoiceOut *) hw; + int rpos, decr, samples; + size_t avail_bytes, max_bytes; + struct st_sample *src; + void *pa_dst; + int error =3D 0; + int r; =20 - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return NULL; - } + decr =3D 0; + rpos =3D hw->rpos; =20 - for (;;) { - int decr, to_mix, rpos; + pa_threaded_mainloop_lock (pa->g->mainloop); + CHECK_DEAD_GOTO (pa->g, pa->stream, &error, fail); =20 - for (;;) { - if (pa->done) { - goto exit; - } + avail_bytes =3D (size_t) live << hw->info.shift; + max_bytes =3D pa_stream_writable_size(pa->stream); + CHECK_SUCCESS_GOTO(pa->g, &error, max_bytes !=3D -1, fail); =20 - if (pa->live > 0) { - break; - } + samples =3D (int)(audio_MIN (avail_bytes, max_bytes)) >> hw->info.shif= t; =20 - if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { - goto exit; - } - } +// if (avail_bytes < max_bytes) { +// dolog("avail: %d, wanted: %d \n", (int)avail_bytes, (int)max_byt= es); +// } =20 - decr =3D to_mix =3D audio_MIN (pa->live, pa->g->conf.samples >> 2); - rpos =3D pa->rpos; +// dolog("TRANSFER avail: %d bytes, max %d bytes -> %d samples from %d\= n", (int)avail_bytes, (int)max_bytes, samples, rpos); =20 - if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { - return NULL; - } + while (samples) { + int left_till_end_samples =3D hw->samples - rpos; =20 - while (to_mix) { - int error; - int chunk =3D audio_MIN (to_mix, hw->samples - rpos); - struct st_sample *src =3D hw->mix_buf + rpos; + int convert_samples =3D audio_MIN (samples, left_till_end_samples); + size_t convert_bytes_wanted =3D (size_t) convert_samples << hw->in= fo.shift; + size_t convert_bytes =3D convert_bytes_wanted; =20 - hw->clip (pa->pcm_buf, src, chunk); + r =3D pa_stream_begin_write(pa->stream, &pa_dst, &convert_bytes); + CHECK_SUCCESS_GOTO(pa->g, &error, r =3D=3D 0, fail); + CHECK_SUCCESS_GOTO(pa->g, &error, convert_bytes =3D=3D convert_byt= es_wanted, fail); =20 - if (qpa_simple_write (pa, pa->pcm_buf, - chunk << hw->info.shift, &error) < 0) { - qpa_logerr (error, "pa_simple_write failed\n"); - return NULL; - } + src =3D hw->mix_buf + rpos; + hw->clip (pa_dst, src, convert_samples); =20 - rpos =3D (rpos + chunk) % hw->samples; - to_mix -=3D chunk; - } - - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return NULL; - } + r =3D pa_stream_write (pa->stream, pa_dst, convert_bytes, NULL, 0L= L, PA_SEEK_RELATIVE); + CHECK_SUCCESS_GOTO(pa->g, &error, r >=3D 0, fail); =20 - pa->rpos =3D rpos; - pa->live -=3D decr; - pa->decr +=3D decr; + rpos =3D (rpos + convert_samples) % hw->samples; + samples -=3D convert_samples; + decr +=3D convert_samples; } =20 - exit: - audio_pt_unlock (&pa->pt, AUDIO_FUNC); - return NULL; -} - -static int qpa_run_out (HWVoiceOut *hw, int live) -{ - int decr; - PAVoiceOut *pa =3D (PAVoiceOut *) hw; + bail: + pa_threaded_mainloop_unlock (pa->g->mainloop); =20 - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return 0; - } - - decr =3D audio_MIN (live, pa->decr); - pa->decr -=3D decr; - pa->live =3D live - decr; - hw->rpos =3D 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 =3D rpos; return decr; + + fail: + qpa_logerr (error, "qpa_run_out failed\n"); + goto bail; } =20 static int qpa_write (SWVoiceOut *sw, void *buf, int len) @@ -292,92 +189,86 @@ static int qpa_write (SWVoiceOut *sw, void *buf, int = len) return audio_pcm_sw_write (sw, buf, len); } =20 -/* capture */ -static void *qpa_thread_in (void *arg) +static int qpa_run_in (HWVoiceIn *hw) { - PAVoiceIn *pa =3D arg; - HWVoiceIn *hw =3D &pa->hw; + PAVoiceIn *pa =3D (PAVoiceIn *) hw; + int wpos, incr; + char *pa_src; + int error =3D 0; + int r; + + incr =3D 0; + wpos =3D hw->wpos; =20 - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return NULL; + pa_threaded_mainloop_lock (pa->g->mainloop); + CHECK_DEAD_GOTO (pa->g, pa->stream, &error, fail); + + size_t bytes_wanted =3D ((unsigned int)(hw->samples - audio_pcm_hw_get= _live_in(hw)) << hw->info.shift); + if (bytes_wanted =3D=3D 0) { + // no room + goto bail; } =20 - for (;;) { - int incr, to_grab, wpos; + size_t bytes_avail =3D pa_stream_readable_size(pa->stream); =20 - for (;;) { - if (pa->done) { - goto exit; - } + //dolog("WANT %d, HAVE %d\n", (int)bytes_wanted, (int) bytes_avail); =20 - if (pa->dead > 0) { - break; - } + size_t pa_avail; =20 - if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { - goto exit; + if (bytes_avail > bytes_wanted) { +#if 0 + size_t to_drop =3D bytes_avail - bytes_wanted; + while (to_drop) { + r =3D pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_a= vail); + CHECK_SUCCESS_GOTO(pa->g, &error, r =3D=3D 0, fail); + if (to_drop < pa_avail) { + break; } + r =3D pa_stream_drop(pa->stream); + CHECK_SUCCESS_GOTO(pa->g, &error, r =3D=3D 0, fail); + to_drop -=3D pa_avail; } - - incr =3D to_grab =3D audio_MIN (pa->dead, pa->g->conf.samples >> 2= ); - wpos =3D pa->wpos; - - if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { - return NULL; + int n_dropped =3D (int)(bytes_avail - bytes_wanted - to_drop); + if(n_dropped) { + dolog("dropped %d bytes\n", n_dropped); } +#endif + } else if (bytes_wanted < bytes_avail) { + bytes_wanted =3D bytes_avail; + } =20 - while (to_grab) { - int error; - int chunk =3D audio_MIN (to_grab, hw->samples - wpos); - void *buf =3D advance (pa->pcm_buf, wpos); + while (bytes_wanted) { + r =3D pa_stream_peek(pa->stream, (const void **)&pa_src, &pa_avail= ); + CHECK_SUCCESS_GOTO(pa->g, &error, r =3D=3D 0, fail); + if (pa_avail =3D=3D 0 || pa_avail > bytes_wanted) { + break; + } =20 - if (qpa_simple_read (pa, buf, - chunk << hw->info.shift, &error) < 0) { - qpa_logerr (error, "pa_simple_read failed\n"); - return NULL; - } + bytes_wanted -=3D pa_avail; =20 - hw->conv (hw->conv_buf + wpos, buf, chunk); + while (pa_avail) { + int chunk =3D audio_MIN ((int)(pa_avail >> hw->info.shift), hw= ->samples - wpos); + hw->conv (hw->conv_buf + wpos, pa_src, chunk); wpos =3D (wpos + chunk) % hw->samples; - to_grab -=3D chunk; - } - - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return NULL; + pa_src +=3D chunk << hw->info.shift; + pa_avail -=3D chunk << hw->info.shift; + incr +=3D chunk; } =20 - pa->wpos =3D wpos; - pa->dead -=3D incr; - pa->incr +=3D incr; + r =3D pa_stream_drop(pa->stream); + CHECK_SUCCESS_GOTO(pa->g, &error, r =3D=3D 0, fail); } =20 - exit: - audio_pt_unlock (&pa->pt, AUDIO_FUNC); - return NULL; -} + bail: + pa_threaded_mainloop_unlock (pa->g->mainloop); =20 -static int qpa_run_in (HWVoiceIn *hw) -{ - int live, incr, dead; - PAVoiceIn *pa =3D (PAVoiceIn *) hw; + hw->wpos =3D wpos; + return incr; =20 - if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { - return 0; - } + fail: + qpa_logerr (error, "qpa_run_in failed\n"); + goto bail; =20 - live =3D audio_pcm_hw_get_live_in (hw); - dead =3D hw->samples - live; - incr =3D audio_MIN (dead, pa->incr); - pa->incr -=3D incr; - pa->dead =3D dead - incr; - hw->wpos =3D pa->wpos; - if (pa->dead > 0) { - audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); - } - else { - audio_pt_unlock (&pa->pt, AUDIO_FUNC); - } - return incr; } =20 static int qpa_read (SWVoiceIn *sw, void *buf, int len) @@ -387,7 +278,7 @@ static int qpa_read (SWVoiceIn *sw, void *buf, int len) =20 static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) { - int format; + pa_sample_format_t format; =20 switch (afmt) { case AUD_FMT_S8: @@ -470,13 +361,6 @@ static void stream_state_cb (pa_stream *s, void * user= data) } } =20 -static void stream_request_cb (pa_stream *s, size_t length, void *userdata) -{ - paaudio *g =3D userdata; - - pa_threaded_mainloop_signal (g->mainloop, 0); -} - static pa_stream *qpa_simple_new ( paaudio *g, const char *name, @@ -498,21 +382,19 @@ static pa_stream *qpa_simple_new ( } =20 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); =20 if (dir =3D=3D PA_STREAM_PLAYBACK) { r =3D pa_stream_connect_playback (stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING #ifdef PA_STREAM_ADJUST_LATENCY - |PA_STREAM_ADJUST_LATENCY + | (g->conf.adjust_latency_out ? PA= _STREAM_ADJUST_LATENCY : 0) #endif |PA_STREAM_AUTO_TIMING_UPDATE, NUL= L, NULL); } else { r =3D pa_stream_connect_record (stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING #ifdef PA_STREAM_ADJUST_LATENCY - |PA_STREAM_ADJUST_LATENCY + | (g->conf.adjust_latency_in ? PA_ST= REAM_ADJUST_LATENCY : 0) #endif |PA_STREAM_AUTO_TIMING_UPDATE); } @@ -537,39 +419,62 @@ fail: return NULL; } =20 + 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 =3D *as; PAVoiceOut *pa =3D (PAVoiceOut *) hw; paaudio *g =3D pa->g =3D drv_opaque; =20 - ss.format =3D audfmt_to_pa (as->fmt, as->endianness); - ss.channels =3D as->nchannels; - ss.rate =3D as->freq; + int64_t timer_tick_duration =3D audio_MAX(audio_get_timer_ticks(), 1 *= SCALE_MS); + int64_t frames_per_tick_x1000 =3D ((timer_tick_duration * as->freq * 1= 000LL) / NANOSECONDS_PER_SECOND); + + int64_t tlength =3D g->conf.tlength; + if (tlength =3D=3D 0) { + tlength =3D (frames_per_tick_x1000) / 400; + } + int64_t buflen =3D g->conf.buffer_size; + if (buflen =3D=3D 0) { + buflen =3D frames_per_tick_x1000 / 400; + } + + float ms_per_frame =3D 1000.0f / as->freq; + + dolog("tick duration: %.2f ms (%.3f frames)\n", + ((float) timer_tick_duration) / SCALE_MS, + (float)frames_per_tick_x1000 / 1000.0f); + + dolog("OUT internal buffer: %.2f ms (%"PRId64" frames)\n", + buflen * ms_per_frame, + buflen); + + dolog("OUT tlength: %.2f ms (%"PRId64" frames)\n", + tlength * ms_per_frame, + tlength); + + dolog("OUT adjust latency: %s\n", g->conf.adjust_latency_out ? "yes" := "no"); =20 - /* - * 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 =3D pa_usec_to_bytes (10 * 1000, &ss); - ba.minreq =3D pa_usec_to_bytes (5 * 1000, &ss); - ba.maxlength =3D -1; - ba.prebuf =3D -1; + pa->ss.format =3D audfmt_to_pa (as->fmt, as->endianness); + pa->ss.channels =3D as->nchannels; + pa->ss.rate =3D as->freq; =20 - obt_as.fmt =3D pa_to_audfmt (ss.format, &obt_as.endianness); + pa->ba.tlength =3D tlength * pa_frame_size (&pa->ss); + pa->ba.maxlength =3D -1; + pa->ba.minreq =3D -1; + pa->ba.prebuf =3D -1; + + obt_as.fmt =3D pa_to_audfmt (pa->ss.format, &obt_as.endianness); =20 pa->stream =3D qpa_simple_new ( g, "qemu", PA_STREAM_PLAYBACK, g->conf.sink, - &ss, + &pa->ss, NULL, /* channel map */ - &ba, /* buffering attributes */ + &pa->ba, /* buffering attributes */ &error ); if (!pa->stream) { @@ -578,128 +483,101 @@ static int qpa_init_out(HWVoiceOut *hw, struct auds= ettings *as, } =20 audio_pcm_init_info (&hw->info, &obt_as); - hw->samples =3D g->conf.samples; - pa->pcm_buf =3D audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.s= hift); - pa->rpos =3D 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; - } + hw->samples =3D buflen; =20 return 0; =20 - fail3: - g_free (pa->pcm_buf); - pa->pcm_buf =3D NULL; - fail2: - if (pa->stream) { - pa_stream_unref (pa->stream); - pa->stream =3D NULL; - } fail1: return -1; } =20 -static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_op= aque) + +static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, + void *drv_opaque) { int error; - pa_sample_spec ss; struct audsettings obt_as =3D *as; PAVoiceIn *pa =3D (PAVoiceIn *) hw; paaudio *g =3D pa->g =3D drv_opaque; =20 - ss.format =3D audfmt_to_pa (as->fmt, as->endianness); - ss.channels =3D as->nchannels; - ss.rate =3D as->freq; + int64_t timer_tick_duration =3D audio_MAX(audio_get_timer_ticks(), 1 *= SCALE_MS); + int64_t frames_per_tick_x1000 =3D ((timer_tick_duration * as->freq * 1= 000LL) / NANOSECONDS_PER_SECOND); =20 - obt_as.fmt =3D pa_to_audfmt (ss.format, &obt_as.endianness); + int64_t fragsize =3D g->conf.fragsize; + if (fragsize =3D=3D 0) { + fragsize =3D frames_per_tick_x1000 / 2500; + } + int64_t buflen =3D g->conf.buffer_size; + if (buflen =3D=3D 0) { + buflen =3D frames_per_tick_x1000 / 400; + } + + float ms_per_frame =3D 1000.0f / as->freq; + + dolog("IN internal buffer: %.2f ms (%"PRId64" frames)\n", + buflen * ms_per_frame, + buflen); + + dolog("IN fragsize: %.2f ms (%"PRId64" frames)\n", + fragsize * ms_per_frame, + fragsize); + + dolog("IN adjust latency: %s\n", g->conf.adjust_latency_in ? "yes" : "= no"); + + pa->ss.format =3D audfmt_to_pa (as->fmt, as->endianness); + pa->ss.channels =3D as->nchannels; + pa->ss.rate =3D as->freq; + + pa->ba.fragsize =3D fragsize * pa_frame_size (&pa->ss); + pa->ba.maxlength =3D pa->ba.fragsize * 10; + pa->ba.minreq =3D -1; + pa->ba.prebuf =3D -1; + + obt_as.fmt =3D pa_to_audfmt (pa->ss.format, &obt_as.endianness); =20 pa->stream =3D qpa_simple_new ( - g, - "qemu", - PA_STREAM_RECORD, - g->conf.source, - &ss, - NULL, /* channel map */ - NULL, /* buffering attributes */ - &error - ); + 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"); + qpa_logerr (error, "pa_simple_new for playback failed\n"); goto fail1; } =20 audio_pcm_init_info (&hw->info, &obt_as); - hw->samples =3D g->conf.samples; - pa->pcm_buf =3D audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.s= hift); - pa->wpos =3D 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; - } + hw->samples =3D buflen; =20 return 0; =20 - fail3: - g_free (pa->pcm_buf); - pa->pcm_buf =3D NULL; - fail2: - if (pa->stream) { - pa_stream_unref (pa->stream); - pa->stream =3D NULL; - } - fail1: + fail1: return -1; } =20 + static void qpa_fini_out (HWVoiceOut *hw) { - void *ret; PAVoiceOut *pa =3D (PAVoiceOut *) hw; =20 - audio_pt_lock (&pa->pt, AUDIO_FUNC); - pa->done =3D 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 =3D NULL; } - - audio_pt_fini (&pa->pt, AUDIO_FUNC); - g_free (pa->pcm_buf); - pa->pcm_buf =3D NULL; } =20 static void qpa_fini_in (HWVoiceIn *hw) { - void *ret; PAVoiceIn *pa =3D (PAVoiceIn *) hw; =20 - audio_pt_lock (&pa->pt, AUDIO_FUNC); - pa->done =3D 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 =3D NULL; } - - audio_pt_fini (&pa->pt, AUDIO_FUNC); - g_free (pa->pcm_buf); - pa->pcm_buf =3D NULL; } =20 static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) @@ -766,7 +644,7 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) #endif =20 switch (cmd) { - case VOICE_VOLUME: + case VOICE_VOLUME: { SWVoiceIn *sw; va_list ap; @@ -782,8 +660,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) pa_threaded_mainloop_lock (g->mainloop); =20 op =3D pa_context_set_source_output_volume (g->context, - pa_stream_get_index (pa->stream), - &v, NULL, NULL); + pa_stream_get_index = (pa->stream), + &v, NULL, NULL); if (!op) { qpa_logerr (pa_context_errno (g->context), "set_source_output_volume() failed\n"); @@ -792,8 +670,8 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) } =20 op =3D pa_context_set_source_output_mute (g->context, - pa_stream_get_index (pa->stream), - sw->vol.mute, NULL, NULL); + pa_stream_get_index (p= a->stream), + sw->vol.mute, NULL, NU= LL); if (!op) { qpa_logerr (pa_context_errno (g->context), "set_source_output_mute() failed\n"); @@ -809,9 +687,13 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) =20 /* common */ static PAConf glob_conf =3D { - .samples =3D 4096, +#ifdef PA_STREAM_ADJUST_LATENCY + .adjust_latency_out =3D 0, + .adjust_latency_in =3D 1, +#endif }; =20 + static void *qpa_audio_init (void) { paaudio *g =3D g_malloc(sizeof(paaudio)); @@ -897,11 +779,37 @@ static void qpa_audio_fini (void *opaque) =20 struct audio_option qpa_options[] =3D { { - .name =3D "SAMPLES", + .name =3D "INT_BUF_SIZE", .tag =3D AUD_OPT_INT, - .valp =3D &glob_conf.samples, - .descr =3D "buffer size in samples" + .valp =3D &glob_conf.buffer_size, + .descr =3D "internal buffer size in frames" }, + { + .name =3D "TLENGTH", + .tag =3D AUD_OPT_INT, + .valp =3D &glob_conf.tlength, + .descr =3D "playback buffer target length in frames" + }, + { + .name =3D "FRAGSIZE", + .tag =3D AUD_OPT_INT, + .valp =3D &glob_conf.fragsize, + .descr =3D "fragment length of recording device in frames" + }, +#ifdef PA_STREAM_ADJUST_LATENCY + { + .name =3D "ADJUST_LATENCY_OUT", + .tag =3D AUD_OPT_BOOL, + .valp =3D &glob_conf.adjust_latency_out, + .descr =3D "let PA adjust latency for playback device" + }, + { + .name =3D "ADJUST_LATENCY_IN", + .tag =3D AUD_OPT_BOOL, + .valp =3D &glob_conf.adjust_latency_in, + .descr =3D "let PA adjust latency for recording device" + }, +#endif { .name =3D "SERVER", .tag =3D AUD_OPT_STR, diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c index 5402cd196c..760bfe40d4 100644 --- a/hw/audio/hda-codec.c +++ b/hw/audio/hda-codec.c @@ -18,6 +18,7 @@ */ =20 #include "qemu/osdep.h" +#include "qemu/atomic.h" #include "hw/hw.h" #include "hw/pci/pci.h" #include "intel-hda.h" @@ -154,8 +155,11 @@ struct HDAAudioStream { SWVoiceIn *in; SWVoiceOut *out; } voice; - uint8_t buf[HDA_BUFFER_SIZE]; - uint32_t bpos; + uint8_t buf[8192]; + int64_t rpos; + int64_t wpos; + QEMUTimer *buft; + int64_t buft_start; }; =20 #define TYPE_HDA_AUDIO "hda-audio" @@ -176,56 +180,180 @@ struct HDAAudioState { bool mixer; }; =20 +static void hda_audio_input_timer(void *opaque) { + +#define B_SIZE sizeof(st->buf) +#define B_MASK (sizeof(st->buf) - 1) + + HDAAudioStream *st =3D opaque; + + int64_t now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + int64_t buft_start =3D atomic_fetch_add(&st->buft_start, 0); + int64_t wpos =3D atomic_fetch_add(&st->wpos, 0); + int64_t rpos =3D atomic_fetch_add(&st->rpos, 0); + + int64_t wanted_rpos =3D (st->as.freq * 4 * (now - buft_start)) / NANOS= ECONDS_PER_SECOND; + wanted_rpos &=3D -4; // IMPORTANT! clip to frames + + if (wanted_rpos <=3D rpos) { + // we already transmitted the data + goto out_timer; + } + + if (wpos - rpos >=3D B_SIZE) { + goto out_timer; + } + + //dolog("%"PRId64"\n", wpos - rpos); + + //dolog("rpos: %"PRId64", wpos: %"PRId64", wanted: %"PRId64"\n", rpos,= wpos, wanted_wpos); + int64_t to_transfer =3D audio_MIN(B_SIZE - (wpos - rpos), wanted_rpos = - rpos); + while (to_transfer) { + uint32_t start =3D (rpos & B_MASK); + uint32_t chunk =3D audio_MIN(B_SIZE - start, to_transfer); + int rc =3D hda_codec_xfer(&st->state->hda, st->stream, false, st->= buf + start, chunk); + if (!rc) { + break; + } + rpos +=3D chunk; + to_transfer -=3D chunk; + atomic_fetch_add(&st->rpos, chunk); + } + +#undef B_MASK +#undef B_SIZE + + 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) { +#define B_SIZE sizeof(st->buf) +#define B_MASK (sizeof(st->buf) - 1) + HDAAudioStream *st =3D opaque; - int recv =3D 0; - int len; - bool rc; - - while (avail - recv >=3D sizeof(st->buf)) { - if (st->bpos !=3D sizeof(st->buf)) { - len =3D AUD_read(st->voice.in, st->buf + st->bpos, - sizeof(st->buf) - st->bpos); - st->bpos +=3D len; - recv +=3D len; - if (st->bpos !=3D sizeof(st->buf)) { - break; - } + + int64_t wpos =3D atomic_fetch_add(&st->wpos, 0); + int64_t rpos =3D atomic_fetch_add(&st->rpos, 0); + + int64_t to_transfer =3D audio_MIN(wpos - rpos, avail); + +// int64_t overflow =3D wpos - rpos - to_transfer - (B_SIZE >> 3); +// if (overflow > 0) { +// int64_t corr =3D NANOSECONDS_PER_SECOND * overflow / (4 * st->as= .freq); +// //dolog("CORR %"PRId64"\n", corr); +// atomic_fetch_add(&st->buft_start, corr); +// } + + while (to_transfer) { + uint32_t start =3D (uint32_t) (wpos & B_MASK); + uint32_t chunk =3D (uint32_t) audio_MIN(B_SIZE - start, to_transfe= r); + uint32_t read =3D AUD_read(st->voice.in, st->buf + start, chunk); + wpos +=3D read; + to_transfer -=3D read; + atomic_fetch_add(&st->wpos, read); + if (chunk !=3D read) { + break; } - rc =3D hda_codec_xfer(&st->state->hda, st->stream, false, - st->buf, sizeof(st->buf)); + } + +#undef B_MASK +#undef B_SIZE +} + + +#define dolog(fmt, ...) AUD_log("XX", fmt, ## __VA_ARGS__) + +static void hda_audio_output_timer(void *opaque) { + +#define B_SIZE sizeof(st->buf) +#define B_MASK (sizeof(st->buf) - 1) + + HDAAudioStream *st =3D opaque; + + int64_t now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + int64_t buft_start =3D atomic_fetch_add(&st->buft_start, 0); + int64_t wpos =3D atomic_fetch_add(&st->wpos, 0); + int64_t rpos =3D atomic_fetch_add(&st->rpos, 0); + + int64_t wanted_wpos =3D (st->as.freq * 4 * (now - buft_start)) / NANOS= ECONDS_PER_SECOND; + wanted_wpos &=3D -4; // IMPORTANT! clip to frames + + if (wanted_wpos <=3D wpos) { + // we already received the data + goto out_timer; + } + + if (wpos - rpos >=3D B_SIZE) { + goto out_timer; + } + + //dolog("%"PRId64"\n", wpos - rpos); + + //dolog("rpos: %"PRId64", wpos: %"PRId64", wanted: %"PRId64"\n", rpos,= wpos, wanted_wpos); + int64_t to_transfer =3D audio_MIN(B_SIZE - (wpos - rpos), wanted_wpos = - wpos); + while (to_transfer) { + uint32_t start =3D (wpos & B_MASK); + uint32_t chunk =3D audio_MIN(B_SIZE - start, to_transfer); + int rc =3D hda_codec_xfer(&st->state->hda, st->stream, true, st->b= uf + start, chunk); if (!rc) { break; } - st->bpos =3D 0; + wpos +=3D chunk; + to_transfer -=3D chunk; + atomic_fetch_add(&st->wpos, chunk); + } + +#undef B_MASK +#undef B_SIZE + + out_timer: + + if (st->running) { + timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); } } =20 static void hda_audio_output_cb(void *opaque, int avail) { +#define B_SIZE sizeof(st->buf) +#define B_MASK (sizeof(st->buf) - 1) + HDAAudioStream *st =3D opaque; - int sent =3D 0; - int len; - bool rc; - - while (avail - sent >=3D sizeof(st->buf)) { - if (st->bpos =3D=3D sizeof(st->buf)) { - rc =3D hda_codec_xfer(&st->state->hda, st->stream, true, - st->buf, sizeof(st->buf)); - if (!rc) { - break; - } - st->bpos =3D 0; - } - len =3D AUD_write(st->voice.out, st->buf + st->bpos, - sizeof(st->buf) - st->bpos); - st->bpos +=3D len; - sent +=3D len; - if (st->bpos !=3D sizeof(st->buf)) { + + int64_t wpos =3D atomic_fetch_add(&st->wpos, 0); + int64_t rpos =3D atomic_fetch_add(&st->rpos, 0); + + int64_t to_transfer =3D audio_MIN(wpos - rpos, avail); + + int64_t overflow =3D wpos - rpos - to_transfer - (B_SIZE >> 3); + if (overflow > 0) { + int64_t corr =3D NANOSECONDS_PER_SECOND * overflow / (4 * st->as.f= req); + //dolog("CORR %"PRId64"\n", corr); + atomic_fetch_add(&st->buft_start, corr); + } + + while (to_transfer) { + uint32_t start =3D (uint32_t) (rpos & B_MASK); + uint32_t chunk =3D (uint32_t) audio_MIN(B_SIZE - start, to_transfe= r); + uint32_t written =3D AUD_write(st->voice.out, st->buf + start, chu= nk); + rpos +=3D written; + to_transfer -=3D written; + atomic_fetch_add(&st->rpos, written); + if (chunk !=3D written) { break; } } + +#undef B_MASK +#undef B_SIZE } =20 static void hda_audio_set_running(HDAAudioStream *st, bool running) @@ -237,6 +365,17 @@ static void hda_audio_set_running(HDAAudioStream *st, = bool running) return; } st->running =3D running; + + if (running) { + int64_t now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + st->rpos =3D 0; + st->wpos =3D 0; + st->buft_start =3D now; + timer_mod_anticipate_ns(st->buft, now + HDA_TIMER_TICKS); + } else { + timer_del (st->buft); + } + dprint(st->state, 1, "%s: %s (stream %d)\n", st->node->name, st->running ? "on" : "off", st->stream); if (st->output) { @@ -286,10 +425,12 @@ static void hda_audio_setup(HDAAudioStream *st) st->voice.out =3D AUD_open_out(&st->state->card, st->voice.out, st->node->name, st, hda_audio_output_cb, &st->as); + st->buft =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_output_tim= er, st); } else { st->voice.in =3D AUD_open_in(&st->state->card, st->voice.in, st->node->name, st, hda_audio_input_cb, &st->as); + st->buft =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, hda_audio_input_time= r, st); } } =20 @@ -505,7 +646,6 @@ static int hda_audio_init(HDACodecDevice *hda, const st= ruct desc_codec *desc) /* unmute output by default */ st->gain_left =3D QEMU_HDA_AMP_STEPS; st->gain_right =3D QEMU_HDA_AMP_STEPS; - st->bpos =3D sizeof(st->buf); st->output =3D true; } else { st->output =3D false; @@ -533,6 +673,7 @@ static void hda_audio_exit(HDACodecDevice *hda) continue; } if (st->output) { + timer_del (st->buft); AUD_close_out(&a->card, st->voice.out); } else { AUD_close_in(&a->card, st->voice.in); @@ -592,8 +733,9 @@ static const VMStateDescription vmstate_hda_audio_strea= m =3D { VMSTATE_UINT32(gain_right, HDAAudioStream), VMSTATE_BOOL(mute_left, HDAAudioStream), VMSTATE_BOOL(mute_right, HDAAudioStream), - VMSTATE_UINT32(bpos, HDAAudioStream), VMSTATE_BUFFER(buf, HDAAudioStream), + VMSTATE_INT64(rpos, HDAAudioStream), + VMSTATE_INT64(wpos, HDAAudioStream), VMSTATE_END_OF_LIST() } }; diff --git a/hw/audio/intel-hda-defs.h b/hw/audio/intel-hda-defs.h index 2e37e5b874..900a2695ec 100644 --- a/hw/audio/intel-hda-defs.h +++ b/hw/audio/intel-hda-defs.h @@ -3,6 +3,7 @@ =20 /* qemu */ #define HDA_BUFFER_SIZE 256 +#define HDA_TIMER_TICKS (SCALE_MS) =20 /* --------------------------------------------------------------------- */ /* from linux/sound/pci/hda/hda_intel.c */ diff --git a/hw/audio/intel-hda.c b/hw/audio/intel-hda.c index 18a50a8f83..9d3da3185b 100644 --- a/hw/audio/intel-hda.c +++ b/hw/audio/intel-hda.c @@ -407,13 +407,13 @@ static bool intel_hda_xfer(HDACodecDevice *dev, uint3= 2_t stnr, bool output, if (st->bpl =3D=3D 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; - } +// if (st->ctl & (1 << 26)) { +// /* +// * Wait with the next DMA xfer until the guest +// * has acked the buffer completion interrupt +// */ +// return false; +// } =20 left =3D len; s =3D st->bentries; --=20 2.14.2