| /* | 
 |  * QEMU Audio subsystem header | 
 |  * | 
 |  * Copyright (c) 2005 Vassili Karpov (malc) | 
 |  * | 
 |  * Permission is hereby granted, free of charge, to any person obtaining a copy | 
 |  * of this software and associated documentation files (the "Software"), to deal | 
 |  * in the Software without restriction, including without limitation the rights | 
 |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
 |  * 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 OTHER | 
 |  * 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. | 
 |  */ | 
 |  | 
 | #ifdef DAC | 
 | #define NAME "playback" | 
 | #define HWBUF hw->mix_buf | 
 | #define TYPE out | 
 | #define HW HWVoiceOut | 
 | #define SW SWVoiceOut | 
 | #else | 
 | #define NAME "capture" | 
 | #define TYPE in | 
 | #define HW HWVoiceIn | 
 | #define SW SWVoiceIn | 
 | #define HWBUF hw->conv_buf | 
 | #endif | 
 |  | 
 | static void glue(audio_init_nb_voices_, TYPE)(AudioState *s, | 
 |                                               struct audio_driver *drv, int min_voices) | 
 | { | 
 |     int max_voices = glue (drv->max_voices_, TYPE); | 
 |     size_t voice_size = glue(drv->voice_size_, TYPE); | 
 |  | 
 |     glue (s->nb_hw_voices_, TYPE) = glue(audio_get_pdo_, TYPE)(s->dev)->voices; | 
 |     if (glue (s->nb_hw_voices_, TYPE) > max_voices) { | 
 |         if (!max_voices) { | 
 | #ifdef DAC | 
 |             dolog ("Driver `%s' does not support " NAME "\n", drv->name); | 
 | #endif | 
 |         } else { | 
 |             dolog ("Driver `%s' does not support %d " NAME " voices, max %d\n", | 
 |                    drv->name, | 
 |                    glue (s->nb_hw_voices_, TYPE), | 
 |                    max_voices); | 
 |         } | 
 |         glue (s->nb_hw_voices_, TYPE) = max_voices; | 
 |     } | 
 |  | 
 |     if (glue (s->nb_hw_voices_, TYPE) < min_voices) { | 
 |         dolog ("Bogus number of " NAME " voices %d, setting to %d\n", | 
 |                glue (s->nb_hw_voices_, TYPE), | 
 |                min_voices); | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, !voice_size && max_voices)) { | 
 |         dolog ("drv=`%s' voice_size=0 max_voices=%d\n", | 
 |                drv->name, max_voices); | 
 |         glue (s->nb_hw_voices_, TYPE) = 0; | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, voice_size && !max_voices)) { | 
 |         dolog("drv=`%s' voice_size=%zu max_voices=0\n", | 
 |               drv->name, voice_size); | 
 |     } | 
 | } | 
 |  | 
 | static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw) | 
 | { | 
 |     g_free(hw->buf_emul); | 
 |     g_free(HWBUF.buffer); | 
 |     HWBUF.buffer = NULL; | 
 |     HWBUF.size = 0; | 
 | } | 
 |  | 
 | static void glue(audio_pcm_hw_alloc_resources_, TYPE)(HW *hw) | 
 | { | 
 |     if (glue(audio_get_pdo_, TYPE)(hw->s->dev)->mixing_engine) { | 
 |         size_t samples = hw->samples; | 
 |         if (audio_bug(__func__, samples == 0)) { | 
 |             dolog("Attempted to allocate empty buffer\n"); | 
 |         } | 
 |  | 
 |         HWBUF.buffer = g_new0(st_sample, samples); | 
 |         HWBUF.size = samples; | 
 |         HWBUF.pos = 0; | 
 |     } else { | 
 |         HWBUF.buffer = NULL; | 
 |         HWBUF.size = 0; | 
 |     } | 
 | } | 
 |  | 
 | static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw) | 
 | { | 
 |     g_free(sw->resample_buf.buffer); | 
 |     sw->resample_buf.buffer = NULL; | 
 |     sw->resample_buf.size = 0; | 
 |  | 
 |     if (sw->rate) { | 
 |         st_rate_stop (sw->rate); | 
 |     } | 
 |     sw->rate = NULL; | 
 | } | 
 |  | 
 | static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw) | 
 | { | 
 |     HW *hw = sw->hw; | 
 |     uint64_t samples; | 
 |  | 
 |     if (!glue(audio_get_pdo_, TYPE)(sw->s->dev)->mixing_engine) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     samples = muldiv64(HWBUF.size, sw->info.freq, hw->info.freq); | 
 |     if (samples == 0) { | 
 |         uint64_t f_fe_min; | 
 |         uint64_t f_be = (uint32_t)hw->info.freq; | 
 |  | 
 |         /* f_fe_min = ceil(1 [frames] * f_be [Hz] / size_be [frames]) */ | 
 |         f_fe_min = (f_be + HWBUF.size - 1) / HWBUF.size; | 
 |         qemu_log_mask(LOG_UNIMP, | 
 |                       AUDIO_CAP ": The guest selected a " NAME " sample rate" | 
 |                       " of %d Hz for %s. Only sample rates >= %" PRIu64 " Hz" | 
 |                       " are supported.\n", | 
 |                       sw->info.freq, sw->name, f_fe_min); | 
 |         return -1; | 
 |     } | 
 |  | 
 |     /* | 
 |      * Allocate one additional audio frame that is needed for upsampling | 
 |      * if the resample buffer size is small. For large buffer sizes take | 
 |      * care of overflows and truncation. | 
 |      */ | 
 |     samples = samples < SIZE_MAX ? samples + 1 : SIZE_MAX; | 
 |     sw->resample_buf.buffer = g_new0(st_sample, samples); | 
 |     sw->resample_buf.size = samples; | 
 |     sw->resample_buf.pos = 0; | 
 |  | 
 | #ifdef DAC | 
 |     sw->rate = st_rate_start(sw->info.freq, hw->info.freq); | 
 | #else | 
 |     sw->rate = st_rate_start(hw->info.freq, sw->info.freq); | 
 | #endif | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int glue (audio_pcm_sw_init_, TYPE) ( | 
 |     SW *sw, | 
 |     HW *hw, | 
 |     const char *name, | 
 |     struct audsettings *as | 
 |     ) | 
 | { | 
 |     int err; | 
 |  | 
 |     audio_pcm_init_info (&sw->info, as); | 
 |     sw->hw = hw; | 
 |     sw->active = 0; | 
 | #ifdef DAC | 
 |     sw->total_hw_samples_mixed = 0; | 
 |     sw->empty = 1; | 
 | #endif | 
 |  | 
 |     if (sw->info.is_float) { | 
 | #ifdef DAC | 
 |         sw->conv = mixeng_conv_float[sw->info.nchannels == 2] | 
 |             [sw->info.swap_endianness]; | 
 | #else | 
 |         sw->clip = mixeng_clip_float[sw->info.nchannels == 2] | 
 |             [sw->info.swap_endianness]; | 
 | #endif | 
 |     } else { | 
 | #ifdef DAC | 
 |         sw->conv = mixeng_conv | 
 | #else | 
 |         sw->clip = mixeng_clip | 
 | #endif | 
 |             [sw->info.nchannels == 2] | 
 |             [sw->info.is_signed] | 
 |             [sw->info.swap_endianness] | 
 |             [audio_bits_to_index(sw->info.bits)]; | 
 |     } | 
 |  | 
 |     sw->name = g_strdup (name); | 
 |     err = glue (audio_pcm_sw_alloc_resources_, TYPE) (sw); | 
 |     if (err) { | 
 |         g_free (sw->name); | 
 |         sw->name = NULL; | 
 |     } | 
 |     return err; | 
 | } | 
 |  | 
 | static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw) | 
 | { | 
 |     glue (audio_pcm_sw_free_resources_, TYPE) (sw); | 
 |     g_free (sw->name); | 
 |     sw->name = NULL; | 
 | } | 
 |  | 
 | static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw) | 
 | { | 
 |     QLIST_INSERT_HEAD (&hw->sw_head, sw, entries); | 
 | } | 
 |  | 
 | static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) | 
 | { | 
 |     QLIST_REMOVE (sw, entries); | 
 | } | 
 |  | 
 | static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp) | 
 | { | 
 |     HW *hw = *hwp; | 
 |     AudioState *s = hw->s; | 
 |  | 
 |     if (!hw->sw_head.lh_first) { | 
 | #ifdef DAC | 
 |         audio_detach_capture(hw); | 
 | #endif | 
 |         QLIST_REMOVE(hw, entries); | 
 |         glue(hw->pcm_ops->fini_, TYPE) (hw); | 
 |         glue(s->nb_hw_voices_, TYPE) += 1; | 
 |         glue(audio_pcm_hw_free_resources_ , TYPE) (hw); | 
 |         g_free(hw); | 
 |         *hwp = NULL; | 
 |     } | 
 | } | 
 |  | 
 | static HW *glue(audio_pcm_hw_find_any_, TYPE)(AudioState *s, HW *hw) | 
 | { | 
 |     return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first; | 
 | } | 
 |  | 
 | static HW *glue(audio_pcm_hw_find_any_enabled_, TYPE)(AudioState *s, HW *hw) | 
 | { | 
 |     while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) { | 
 |         if (hw->enabled) { | 
 |             return hw; | 
 |         } | 
 |     } | 
 |     return NULL; | 
 | } | 
 |  | 
 | static HW *glue(audio_pcm_hw_find_specific_, TYPE)(AudioState *s, HW *hw, | 
 |                                                    struct audsettings *as) | 
 | { | 
 |     while ((hw = glue(audio_pcm_hw_find_any_, TYPE)(s, hw))) { | 
 |         if (audio_pcm_info_eq (&hw->info, as)) { | 
 |             return hw; | 
 |         } | 
 |     } | 
 |     return NULL; | 
 | } | 
 |  | 
 | static HW *glue(audio_pcm_hw_add_new_, TYPE)(AudioState *s, | 
 |                                              struct audsettings *as) | 
 | { | 
 |     HW *hw; | 
 |     struct audio_driver *drv = s->drv; | 
 |  | 
 |     if (!glue (s->nb_hw_voices_, TYPE)) { | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, !drv)) { | 
 |         dolog ("No host audio driver\n"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, !drv->pcm_ops)) { | 
 |         dolog ("Host audio driver without pcm_ops\n"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     /* | 
 |      * Since glue(s->nb_hw_voices_, TYPE) is != 0, glue(drv->voice_size_, TYPE) | 
 |      * is guaranteed to be != 0. See the audio_init_nb_voices_* functions. | 
 |      */ | 
 |     hw = g_malloc0(glue(drv->voice_size_, TYPE)); | 
 |     hw->s = s; | 
 |     hw->pcm_ops = drv->pcm_ops; | 
 |  | 
 |     QLIST_INIT (&hw->sw_head); | 
 | #ifdef DAC | 
 |     QLIST_INIT (&hw->cap_head); | 
 | #endif | 
 |     if (glue (hw->pcm_ops->init_, TYPE) (hw, as, s->drv_opaque)) { | 
 |         goto err0; | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, hw->samples <= 0)) { | 
 |         dolog("hw->samples=%zd\n", hw->samples); | 
 |         goto err1; | 
 |     } | 
 |  | 
 |     if (hw->info.is_float) { | 
 | #ifdef DAC | 
 |         hw->clip = mixeng_clip_float[hw->info.nchannels == 2] | 
 |             [hw->info.swap_endianness]; | 
 | #else | 
 |         hw->conv = mixeng_conv_float[hw->info.nchannels == 2] | 
 |             [hw->info.swap_endianness]; | 
 | #endif | 
 |     } else { | 
 | #ifdef DAC | 
 |         hw->clip = mixeng_clip | 
 | #else | 
 |         hw->conv = mixeng_conv | 
 | #endif | 
 |             [hw->info.nchannels == 2] | 
 |             [hw->info.is_signed] | 
 |             [hw->info.swap_endianness] | 
 |             [audio_bits_to_index(hw->info.bits)]; | 
 |     } | 
 |  | 
 |     glue(audio_pcm_hw_alloc_resources_, TYPE)(hw); | 
 |  | 
 |     QLIST_INSERT_HEAD (&s->glue (hw_head_, TYPE), hw, entries); | 
 |     glue (s->nb_hw_voices_, TYPE) -= 1; | 
 | #ifdef DAC | 
 |     audio_attach_capture (hw); | 
 | #endif | 
 |     return hw; | 
 |  | 
 |  err1: | 
 |     glue (hw->pcm_ops->fini_, TYPE) (hw); | 
 |  err0: | 
 |     g_free (hw); | 
 |     return NULL; | 
 | } | 
 |  | 
 | AudiodevPerDirectionOptions *glue(audio_get_pdo_, TYPE)(Audiodev *dev) | 
 | { | 
 |     switch (dev->driver) { | 
 |     case AUDIODEV_DRIVER_NONE: | 
 |         return dev->u.none.TYPE; | 
 | #ifdef CONFIG_AUDIO_ALSA | 
 |     case AUDIODEV_DRIVER_ALSA: | 
 |         return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_COREAUDIO | 
 |     case AUDIODEV_DRIVER_COREAUDIO: | 
 |         return qapi_AudiodevCoreaudioPerDirectionOptions_base( | 
 |             dev->u.coreaudio.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_DBUS_DISPLAY | 
 |     case AUDIODEV_DRIVER_DBUS: | 
 |         return dev->u.dbus.TYPE; | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_DSOUND | 
 |     case AUDIODEV_DRIVER_DSOUND: | 
 |         return dev->u.dsound.TYPE; | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_JACK | 
 |     case AUDIODEV_DRIVER_JACK: | 
 |         return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_OSS | 
 |     case AUDIODEV_DRIVER_OSS: | 
 |         return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_PA | 
 |     case AUDIODEV_DRIVER_PA: | 
 |         return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_PIPEWIRE | 
 |     case AUDIODEV_DRIVER_PIPEWIRE: | 
 |         return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_SDL | 
 |     case AUDIODEV_DRIVER_SDL: | 
 |         return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.TYPE); | 
 | #endif | 
 | #ifdef CONFIG_AUDIO_SNDIO | 
 |     case AUDIODEV_DRIVER_SNDIO: | 
 |         return dev->u.sndio.TYPE; | 
 | #endif | 
 | #ifdef CONFIG_SPICE | 
 |     case AUDIODEV_DRIVER_SPICE: | 
 |         return dev->u.spice.TYPE; | 
 | #endif | 
 |     case AUDIODEV_DRIVER_WAV: | 
 |         return dev->u.wav.TYPE; | 
 |  | 
 |     case AUDIODEV_DRIVER__MAX: | 
 |         break; | 
 |     } | 
 |     abort(); | 
 | } | 
 |  | 
 | static HW *glue(audio_pcm_hw_add_, TYPE)(AudioState *s, struct audsettings *as) | 
 | { | 
 |     HW *hw; | 
 |     AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); | 
 |  | 
 |     if (!pdo->mixing_engine || pdo->fixed_settings) { | 
 |         hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as); | 
 |         if (!pdo->mixing_engine || hw) { | 
 |             return hw; | 
 |         } | 
 |     } | 
 |  | 
 |     hw = glue(audio_pcm_hw_find_specific_, TYPE)(s, NULL, as); | 
 |     if (hw) { | 
 |         return hw; | 
 |     } | 
 |  | 
 |     hw = glue(audio_pcm_hw_add_new_, TYPE)(s, as); | 
 |     if (hw) { | 
 |         return hw; | 
 |     } | 
 |  | 
 |     return glue(audio_pcm_hw_find_any_, TYPE)(s, NULL); | 
 | } | 
 |  | 
 | static SW *glue(audio_pcm_create_voice_pair_, TYPE)( | 
 |     AudioState *s, | 
 |     const char *sw_name, | 
 |     struct audsettings *as | 
 |     ) | 
 | { | 
 |     SW *sw; | 
 |     HW *hw; | 
 |     struct audsettings hw_as; | 
 |     AudiodevPerDirectionOptions *pdo = glue(audio_get_pdo_, TYPE)(s->dev); | 
 |  | 
 |     if (pdo->fixed_settings) { | 
 |         hw_as = audiodev_to_audsettings(pdo); | 
 |     } else { | 
 |         hw_as = *as; | 
 |     } | 
 |  | 
 |     sw = g_new0(SW, 1); | 
 |     sw->s = s; | 
 |  | 
 |     hw = glue(audio_pcm_hw_add_, TYPE)(s, &hw_as); | 
 |     if (!hw) { | 
 |         dolog("Could not create a backend for voice `%s'\n", sw_name); | 
 |         goto err1; | 
 |     } | 
 |  | 
 |     glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw); | 
 |  | 
 |     if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, sw_name, as)) { | 
 |         goto err2; | 
 |     } | 
 |  | 
 |     return sw; | 
 |  | 
 | err2: | 
 |     glue (audio_pcm_hw_del_sw_, TYPE) (sw); | 
 |     glue (audio_pcm_hw_gc_, TYPE) (&hw); | 
 | err1: | 
 |     g_free(sw); | 
 |     return NULL; | 
 | } | 
 |  | 
 | static void glue (audio_close_, TYPE) (SW *sw) | 
 | { | 
 |     glue (audio_pcm_sw_fini_, TYPE) (sw); | 
 |     glue (audio_pcm_hw_del_sw_, TYPE) (sw); | 
 |     glue (audio_pcm_hw_gc_, TYPE) (&sw->hw); | 
 |     g_free (sw); | 
 | } | 
 |  | 
 | void glue (AUD_close_, TYPE) (QEMUSoundCard *card, SW *sw) | 
 | { | 
 |     if (sw) { | 
 |         if (audio_bug(__func__, !card)) { | 
 |             dolog ("card=%p\n", card); | 
 |             return; | 
 |         } | 
 |  | 
 |         glue (audio_close_, TYPE) (sw); | 
 |     } | 
 | } | 
 |  | 
 | SW *glue (AUD_open_, TYPE) ( | 
 |     QEMUSoundCard *card, | 
 |     SW *sw, | 
 |     const char *name, | 
 |     void *callback_opaque , | 
 |     audio_callback_fn callback_fn, | 
 |     struct audsettings *as | 
 |     ) | 
 | { | 
 |     AudioState *s; | 
 |     AudiodevPerDirectionOptions *pdo; | 
 |  | 
 |     if (audio_bug(__func__, !card || !name || !callback_fn || !as)) { | 
 |         dolog ("card=%p name=%p callback_fn=%p as=%p\n", | 
 |                card, name, callback_fn, as); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     s = card->state; | 
 |     pdo = glue(audio_get_pdo_, TYPE)(s->dev); | 
 |  | 
 |     ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", | 
 |             name, as->freq, as->nchannels, as->fmt); | 
 |  | 
 |     if (audio_bug(__func__, audio_validate_settings(as))) { | 
 |         audio_print_settings (as); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     if (audio_bug(__func__, !s->drv)) { | 
 |         dolog ("Can not open `%s' (no host audio driver)\n", name); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     if (sw && audio_pcm_info_eq (&sw->info, as)) { | 
 |         return sw; | 
 |     } | 
 |  | 
 |     if (!pdo->fixed_settings && sw) { | 
 |         glue (AUD_close_, TYPE) (card, sw); | 
 |         sw = NULL; | 
 |     } | 
 |  | 
 |     if (sw) { | 
 |         HW *hw = sw->hw; | 
 |  | 
 |         if (!hw) { | 
 |             dolog("Internal logic error: voice `%s' has no backend\n", | 
 |                   SW_NAME(sw)); | 
 |             goto fail; | 
 |         } | 
 |  | 
 |         glue (audio_pcm_sw_fini_, TYPE) (sw); | 
 |         if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, as)) { | 
 |             goto fail; | 
 |         } | 
 |     } else { | 
 |         sw = glue(audio_pcm_create_voice_pair_, TYPE)(s, name, as); | 
 |         if (!sw) { | 
 |             return NULL; | 
 |         } | 
 |     } | 
 |  | 
 |     sw->card = card; | 
 |     sw->vol = nominal_volume; | 
 |     sw->callback.fn = callback_fn; | 
 |     sw->callback.opaque = callback_opaque; | 
 |  | 
 | #ifdef DEBUG_AUDIO | 
 |     dolog ("%s\n", name); | 
 |     audio_pcm_print_info ("hw", &sw->hw->info); | 
 |     audio_pcm_print_info ("sw", &sw->info); | 
 | #endif | 
 |  | 
 |     return sw; | 
 |  | 
 |  fail: | 
 |     glue (AUD_close_, TYPE) (card, sw); | 
 |     return NULL; | 
 | } | 
 |  | 
 | int glue (AUD_is_active_, TYPE) (SW *sw) | 
 | { | 
 |     return sw ? sw->active : 0; | 
 | } | 
 |  | 
 | void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) | 
 | { | 
 |     if (!sw) { | 
 |         return; | 
 |     } | 
 |  | 
 |     ts->old_ts = sw->hw->ts_helper; | 
 | } | 
 |  | 
 | uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) | 
 | { | 
 |     uint64_t delta, cur_ts, old_ts; | 
 |  | 
 |     if (!sw) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     cur_ts = sw->hw->ts_helper; | 
 |     old_ts = ts->old_ts; | 
 |     /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */ | 
 |  | 
 |     if (cur_ts >= old_ts) { | 
 |         delta = cur_ts - old_ts; | 
 |     } else { | 
 |         delta = UINT64_MAX - old_ts + cur_ts; | 
 |     } | 
 |  | 
 |     if (!delta) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     return muldiv64 (delta, sw->hw->info.freq, 1000000); | 
 | } | 
 |  | 
 | #undef TYPE | 
 | #undef HW | 
 | #undef SW | 
 | #undef HWBUF | 
 | #undef NAME |