|
@@ -28,9 +28,12 @@
|
|
#include "monitor/monitor.h"
|
|
#include "monitor/monitor.h"
|
|
#include "qemu/timer.h"
|
|
#include "qemu/timer.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/error.h"
|
|
|
|
+#include "qapi/clone-visitor.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/qapi-visit-audio.h"
|
|
#include "qapi/qapi-visit-audio.h"
|
|
|
|
+#include "qapi/qapi-commands-audio.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/cutils.h"
|
|
|
|
+#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/help_option.h"
|
|
#include "qemu/help_option.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "sysemu/sysemu.h"
|
|
@@ -146,26 +149,6 @@ static inline int audio_bits_to_index (int bits)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-void *audio_calloc (const char *funcname, int nmemb, size_t size)
|
|
|
|
-{
|
|
|
|
- int cond;
|
|
|
|
- size_t len;
|
|
|
|
-
|
|
|
|
- len = nmemb * size;
|
|
|
|
- cond = !nmemb || !size;
|
|
|
|
- cond |= nmemb < 0;
|
|
|
|
- cond |= len < size;
|
|
|
|
-
|
|
|
|
- if (audio_bug ("audio_calloc", cond)) {
|
|
|
|
- AUD_log (NULL, "%s passed invalid arguments to audio_calloc\n",
|
|
|
|
- funcname);
|
|
|
|
- AUD_log (NULL, "nmemb=%d size=%zu (len=%zu)\n", nmemb, size, len);
|
|
|
|
- return NULL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return g_malloc0 (len);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void AUD_vlog (const char *cap, const char *fmt, va_list ap)
|
|
void AUD_vlog (const char *cap, const char *fmt, va_list ap)
|
|
{
|
|
{
|
|
if (cap) {
|
|
if (cap) {
|
|
@@ -398,13 +381,6 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
|
|
/*
|
|
/*
|
|
* Capture
|
|
* Capture
|
|
*/
|
|
*/
|
|
-static void noop_conv (struct st_sample *dst, const void *src, int samples)
|
|
|
|
-{
|
|
|
|
- (void) src;
|
|
|
|
- (void) dst;
|
|
|
|
- (void) samples;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioState *s,
|
|
static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioState *s,
|
|
struct audsettings *as)
|
|
struct audsettings *as)
|
|
{
|
|
{
|
|
@@ -502,15 +478,8 @@ static int audio_attach_capture (HWVoiceOut *hw)
|
|
sw->info = hw->info;
|
|
sw->info = hw->info;
|
|
sw->empty = 1;
|
|
sw->empty = 1;
|
|
sw->active = hw->enabled;
|
|
sw->active = hw->enabled;
|
|
- sw->conv = noop_conv;
|
|
|
|
- sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq;
|
|
|
|
sw->vol = nominal_volume;
|
|
sw->vol = nominal_volume;
|
|
sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
|
|
sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
|
|
- if (!sw->rate) {
|
|
|
|
- dolog ("Could not start rate conversion for `%s'\n", SW_NAME (sw));
|
|
|
|
- g_free (sw);
|
|
|
|
- return -1;
|
|
|
|
- }
|
|
|
|
QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
|
|
QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
|
|
QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
|
|
QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
|
|
#ifdef DEBUG_CAPTURE
|
|
#ifdef DEBUG_CAPTURE
|
|
@@ -545,8 +514,8 @@ static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
|
|
static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
|
|
static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
|
|
{
|
|
{
|
|
size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
|
|
size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
|
|
- if (audio_bug(__func__, live > hw->conv_buf->size)) {
|
|
|
|
- dolog("live=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > hw->conv_buf.size)) {
|
|
|
|
+ dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
return live;
|
|
return live;
|
|
@@ -555,13 +524,13 @@ static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
|
|
static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
|
|
static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
|
|
{
|
|
{
|
|
size_t conv = 0;
|
|
size_t conv = 0;
|
|
- STSampleBuffer *conv_buf = hw->conv_buf;
|
|
|
|
|
|
+ STSampleBuffer *conv_buf = &hw->conv_buf;
|
|
|
|
|
|
while (samples) {
|
|
while (samples) {
|
|
uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
|
|
uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
|
|
size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
|
|
size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
|
|
|
|
|
|
- hw->conv(conv_buf->samples + conv_buf->pos, src, proc);
|
|
|
|
|
|
+ hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
|
|
conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
|
|
conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
|
|
samples -= proc;
|
|
samples -= proc;
|
|
conv += proc;
|
|
conv += proc;
|
|
@@ -573,56 +542,65 @@ static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
|
|
/*
|
|
/*
|
|
* Soft voice (capture)
|
|
* Soft voice (capture)
|
|
*/
|
|
*/
|
|
-static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t size)
|
|
|
|
|
|
+static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
|
|
|
|
+ size_t frames_in_max, size_t frames_out_max,
|
|
|
|
+ size_t *total_in, size_t *total_out)
|
|
|
|
+{
|
|
|
|
+ HWVoiceIn *hw = sw->hw;
|
|
|
|
+ struct st_sample *src, *dst;
|
|
|
|
+ size_t live, rpos, frames_in, frames_out;
|
|
|
|
+
|
|
|
|
+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;
|
|
|
|
+ rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
|
|
|
|
+
|
|
|
|
+ /* resample conv_buf from rpos to end of buffer */
|
|
|
|
+ src = hw->conv_buf.buffer + rpos;
|
|
|
|
+ frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
|
|
|
|
+ dst = sw->resample_buf.buffer;
|
|
|
|
+ frames_out = frames_out_max;
|
|
|
|
+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
|
|
|
|
+ rpos += frames_in;
|
|
|
|
+ *total_in = frames_in;
|
|
|
|
+ *total_out = frames_out;
|
|
|
|
+
|
|
|
|
+ /* resample conv_buf from start of buffer if there are input frames left */
|
|
|
|
+ if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
|
|
|
|
+ src = hw->conv_buf.buffer;
|
|
|
|
+ frames_in = frames_in_max - frames_in;
|
|
|
|
+ dst += frames_out;
|
|
|
|
+ frames_out = frames_out_max - frames_out;
|
|
|
|
+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
|
|
|
|
+ *total_in += frames_in;
|
|
|
|
+ *total_out += frames_out;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
|
|
{
|
|
{
|
|
HWVoiceIn *hw = sw->hw;
|
|
HWVoiceIn *hw = sw->hw;
|
|
- size_t samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0;
|
|
|
|
- struct st_sample *src, *dst = sw->buf;
|
|
|
|
|
|
+ size_t live, frames_out_max, total_in, total_out;
|
|
|
|
|
|
live = hw->total_samples_captured - sw->total_hw_samples_acquired;
|
|
live = hw->total_samples_captured - sw->total_hw_samples_acquired;
|
|
if (!live) {
|
|
if (!live) {
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
- if (audio_bug(__func__, live > hw->conv_buf->size)) {
|
|
|
|
- dolog("live_in=%zu hw->conv_buf->size=%zu\n", live, hw->conv_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > hw->conv_buf.size)) {
|
|
|
|
+ dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- rpos = audio_ring_posb(hw->conv_buf->pos, live, hw->conv_buf->size);
|
|
|
|
-
|
|
|
|
- samples = size / sw->info.bytes_per_frame;
|
|
|
|
-
|
|
|
|
- swlim = (live * sw->ratio) >> 32;
|
|
|
|
- swlim = MIN (swlim, samples);
|
|
|
|
-
|
|
|
|
- while (swlim) {
|
|
|
|
- src = hw->conv_buf->samples + rpos;
|
|
|
|
- if (hw->conv_buf->pos > rpos) {
|
|
|
|
- isamp = hw->conv_buf->pos - rpos;
|
|
|
|
- } else {
|
|
|
|
- isamp = hw->conv_buf->size - rpos;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!isamp) {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- osamp = swlim;
|
|
|
|
|
|
+ frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
|
|
|
|
+ sw->resample_buf.size);
|
|
|
|
|
|
- st_rate_flow (sw->rate, src, dst, &isamp, &osamp);
|
|
|
|
- swlim -= osamp;
|
|
|
|
- rpos = (rpos + isamp) % hw->conv_buf->size;
|
|
|
|
- dst += osamp;
|
|
|
|
- ret += osamp;
|
|
|
|
- total += isamp;
|
|
|
|
- }
|
|
|
|
|
|
+ audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
|
|
|
|
|
|
if (!hw->pcm_ops->volume_in) {
|
|
if (!hw->pcm_ops->volume_in) {
|
|
- mixeng_volume (sw->buf, ret, &sw->vol);
|
|
|
|
|
|
+ mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
|
|
}
|
|
}
|
|
|
|
+ sw->clip(buf, sw->resample_buf.buffer, total_out);
|
|
|
|
|
|
- sw->clip (buf, sw->buf, ret);
|
|
|
|
- sw->total_hw_samples_acquired += total;
|
|
|
|
- return ret * sw->info.bytes_per_frame;
|
|
|
|
|
|
+ sw->total_hw_samples_acquired += total_in;
|
|
|
|
+ return total_out * sw->info.bytes_per_frame;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -658,8 +636,8 @@ static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
|
|
if (nb_live1) {
|
|
if (nb_live1) {
|
|
size_t live = smin;
|
|
size_t live = smin;
|
|
|
|
|
|
- if (audio_bug(__func__, live > hw->mix_buf->size)) {
|
|
|
|
- dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
|
|
|
|
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
return live;
|
|
return live;
|
|
@@ -676,17 +654,17 @@ static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
|
|
static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
|
|
static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
|
|
{
|
|
{
|
|
size_t clipped = 0;
|
|
size_t clipped = 0;
|
|
- size_t pos = hw->mix_buf->pos;
|
|
|
|
|
|
+ size_t pos = hw->mix_buf.pos;
|
|
|
|
|
|
while (len) {
|
|
while (len) {
|
|
- st_sample *src = hw->mix_buf->samples + pos;
|
|
|
|
|
|
+ st_sample *src = hw->mix_buf.buffer + pos;
|
|
uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
|
|
uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
|
|
- size_t samples_till_end_of_buf = hw->mix_buf->size - pos;
|
|
|
|
|
|
+ size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
|
|
size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
|
|
size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
|
|
|
|
|
|
hw->clip(dst, src, samples_to_clip);
|
|
hw->clip(dst, src, samples_to_clip);
|
|
|
|
|
|
- pos = (pos + samples_to_clip) % hw->mix_buf->size;
|
|
|
|
|
|
+ pos = (pos + samples_to_clip) % hw->mix_buf.size;
|
|
len -= samples_to_clip;
|
|
len -= samples_to_clip;
|
|
clipped += samples_to_clip;
|
|
clipped += samples_to_clip;
|
|
}
|
|
}
|
|
@@ -695,84 +673,113 @@ static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
|
|
/*
|
|
/*
|
|
* Soft voice (playback)
|
|
* Soft voice (playback)
|
|
*/
|
|
*/
|
|
-static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t size)
|
|
|
|
|
|
+static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
|
|
|
|
+ size_t frames_in_max, size_t frames_out_max,
|
|
|
|
+ size_t *total_in, size_t *total_out)
|
|
{
|
|
{
|
|
- size_t hwsamples, samples, isamp, osamp, wpos, live, dead, left, blck;
|
|
|
|
- size_t hw_free;
|
|
|
|
- size_t ret = 0, pos = 0, total = 0;
|
|
|
|
|
|
+ HWVoiceOut *hw = sw->hw;
|
|
|
|
+ struct st_sample *src, *dst;
|
|
|
|
+ size_t live, wpos, frames_in, frames_out;
|
|
|
|
|
|
- if (!sw) {
|
|
|
|
- return size;
|
|
|
|
|
|
+ live = sw->total_hw_samples_mixed;
|
|
|
|
+ wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
|
|
|
|
+
|
|
|
|
+ /* write to mix_buf from wpos to end of buffer */
|
|
|
|
+ src = sw->resample_buf.buffer;
|
|
|
|
+ frames_in = frames_in_max;
|
|
|
|
+ dst = hw->mix_buf.buffer + wpos;
|
|
|
|
+ frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
|
|
|
|
+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
|
|
|
|
+ wpos += frames_out;
|
|
|
|
+ *total_in = frames_in;
|
|
|
|
+ *total_out = frames_out;
|
|
|
|
+
|
|
|
|
+ /* write to mix_buf from start of buffer if there are input frames left */
|
|
|
|
+ if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
|
|
|
|
+ src += frames_in;
|
|
|
|
+ frames_in = frames_in_max - frames_in;
|
|
|
|
+ dst = hw->mix_buf.buffer;
|
|
|
|
+ frames_out = frames_out_max - frames_out;
|
|
|
|
+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
|
|
|
|
+ *total_in += frames_in;
|
|
|
|
+ *total_out += frames_out;
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- hwsamples = sw->hw->mix_buf->size;
|
|
|
|
|
|
+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
|
|
|
|
+{
|
|
|
|
+ HWVoiceOut *hw = sw->hw;
|
|
|
|
+ size_t live, dead, hw_free, sw_max, fe_max;
|
|
|
|
+ size_t frames_in_max, frames_out_max, total_in, total_out;
|
|
|
|
|
|
live = sw->total_hw_samples_mixed;
|
|
live = sw->total_hw_samples_mixed;
|
|
- if (audio_bug(__func__, live > hwsamples)) {
|
|
|
|
- dolog("live=%zu hw->mix_buf->size=%zu\n", live, hwsamples);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
|
|
|
|
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- if (live == hwsamples) {
|
|
|
|
|
|
+ if (live == hw->mix_buf.size) {
|
|
#ifdef DEBUG_OUT
|
|
#ifdef DEBUG_OUT
|
|
dolog ("%s is full %zu\n", sw->name, live);
|
|
dolog ("%s is full %zu\n", sw->name, live);
|
|
#endif
|
|
#endif
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- wpos = (sw->hw->mix_buf->pos + live) % hwsamples;
|
|
|
|
-
|
|
|
|
- dead = hwsamples - live;
|
|
|
|
- hw_free = audio_pcm_hw_get_free(sw->hw);
|
|
|
|
|
|
+ dead = hw->mix_buf.size - live;
|
|
|
|
+ hw_free = audio_pcm_hw_get_free(hw);
|
|
hw_free = hw_free > live ? hw_free - live : 0;
|
|
hw_free = hw_free > live ? hw_free - live : 0;
|
|
- samples = ((int64_t)MIN(dead, hw_free) << 32) / sw->ratio;
|
|
|
|
- samples = MIN(samples, size / sw->info.bytes_per_frame);
|
|
|
|
- if (samples) {
|
|
|
|
- sw->conv(sw->buf, buf, samples);
|
|
|
|
|
|
+ frames_out_max = MIN(dead, hw_free);
|
|
|
|
+ sw_max = st_rate_frames_in(sw->rate, frames_out_max);
|
|
|
|
+ fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
|
|
|
|
+ sw->resample_buf.size);
|
|
|
|
+ frames_in_max = MIN(sw_max, fe_max);
|
|
|
|
+
|
|
|
|
+ if (!frames_in_max) {
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ if (frames_in_max > sw->resample_buf.pos) {
|
|
|
|
+ sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
|
|
|
|
+ buf, frames_in_max - sw->resample_buf.pos);
|
|
if (!sw->hw->pcm_ops->volume_out) {
|
|
if (!sw->hw->pcm_ops->volume_out) {
|
|
- mixeng_volume(sw->buf, samples, &sw->vol);
|
|
|
|
|
|
+ mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
|
|
|
|
+ frames_in_max - sw->resample_buf.pos, &sw->vol);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- while (samples) {
|
|
|
|
- dead = hwsamples - live;
|
|
|
|
- left = hwsamples - wpos;
|
|
|
|
- blck = MIN (dead, left);
|
|
|
|
- if (!blck) {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- isamp = samples;
|
|
|
|
- osamp = blck;
|
|
|
|
- st_rate_flow_mix (
|
|
|
|
- sw->rate,
|
|
|
|
- sw->buf + pos,
|
|
|
|
- sw->hw->mix_buf->samples + wpos,
|
|
|
|
- &isamp,
|
|
|
|
- &osamp
|
|
|
|
- );
|
|
|
|
- ret += isamp;
|
|
|
|
- samples -= isamp;
|
|
|
|
- pos += isamp;
|
|
|
|
- live += osamp;
|
|
|
|
- wpos = (wpos + osamp) % hwsamples;
|
|
|
|
- total += osamp;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- sw->total_hw_samples_mixed += total;
|
|
|
|
|
|
+ audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
|
|
|
|
+ &total_in, &total_out);
|
|
|
|
+
|
|
|
|
+ sw->total_hw_samples_mixed += total_out;
|
|
sw->empty = sw->total_hw_samples_mixed == 0;
|
|
sw->empty = sw->total_hw_samples_mixed == 0;
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Upsampling may leave one audio frame in the resample buffer. Decrement
|
|
|
|
+ * total_in by one if there was a leftover frame from the previous resample
|
|
|
|
+ * pass in the resample buffer. Increment total_in by one if the current
|
|
|
|
+ * resample pass left one frame in the resample buffer.
|
|
|
|
+ */
|
|
|
|
+ if (frames_in_max - total_in == 1) {
|
|
|
|
+ /* copy one leftover audio frame to the beginning of the buffer */
|
|
|
|
+ *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
|
|
|
|
+ total_in += 1 - sw->resample_buf.pos;
|
|
|
|
+ sw->resample_buf.pos = 1;
|
|
|
|
+ } else if (total_in >= sw->resample_buf.pos) {
|
|
|
|
+ total_in -= sw->resample_buf.pos;
|
|
|
|
+ sw->resample_buf.pos = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
#ifdef DEBUG_OUT
|
|
#ifdef DEBUG_OUT
|
|
dolog (
|
|
dolog (
|
|
- "%s: write size %zu ret %zu total sw %zu\n",
|
|
|
|
- SW_NAME (sw),
|
|
|
|
- size / sw->info.bytes_per_frame,
|
|
|
|
- ret,
|
|
|
|
|
|
+ "%s: write size %zu written %zu total mixed %zu\n",
|
|
|
|
+ SW_NAME(sw),
|
|
|
|
+ buf_len / sw->info.bytes_per_frame,
|
|
|
|
+ total_in,
|
|
sw->total_hw_samples_mixed
|
|
sw->total_hw_samples_mixed
|
|
);
|
|
);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
- return ret * sw->info.bytes_per_frame;
|
|
|
|
|
|
+ return total_in * sw->info.bytes_per_frame;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_AUDIO
|
|
#ifdef DEBUG_AUDIO
|
|
@@ -990,18 +997,6 @@ void AUD_set_active_in (SWVoiceIn *sw, int on)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * audio_frontend_frames_in() - returns the number of frames the resampling
|
|
|
|
- * code generates from frames_in frames
|
|
|
|
- *
|
|
|
|
- * @sw: audio recording frontend
|
|
|
|
- * @frames_in: number of frames
|
|
|
|
- */
|
|
|
|
-static size_t audio_frontend_frames_in(SWVoiceIn *sw, size_t frames_in)
|
|
|
|
-{
|
|
|
|
- return (int64_t)frames_in * sw->ratio >> 32;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static size_t audio_get_avail (SWVoiceIn *sw)
|
|
static size_t audio_get_avail (SWVoiceIn *sw)
|
|
{
|
|
{
|
|
size_t live;
|
|
size_t live;
|
|
@@ -1011,33 +1006,21 @@ static size_t audio_get_avail (SWVoiceIn *sw)
|
|
}
|
|
}
|
|
|
|
|
|
live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
|
|
live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;
|
|
- if (audio_bug(__func__, live > sw->hw->conv_buf->size)) {
|
|
|
|
- dolog("live=%zu sw->hw->conv_buf->size=%zu\n", live,
|
|
|
|
- sw->hw->conv_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {
|
|
|
|
+ dolog("live=%zu sw->hw->conv_buf.size=%zu\n", live,
|
|
|
|
+ sw->hw->conv_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
ldebug (
|
|
ldebug (
|
|
- "%s: get_avail live %zu frontend frames %zu\n",
|
|
|
|
|
|
+ "%s: get_avail live %zu frontend frames %u\n",
|
|
SW_NAME (sw),
|
|
SW_NAME (sw),
|
|
- live, audio_frontend_frames_in(sw, live)
|
|
|
|
|
|
+ live, st_rate_frames_out(sw->rate, live)
|
|
);
|
|
);
|
|
|
|
|
|
return live;
|
|
return live;
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * audio_frontend_frames_out() - returns the number of frames needed to
|
|
|
|
- * get frames_out frames after resampling
|
|
|
|
- *
|
|
|
|
- * @sw: audio playback frontend
|
|
|
|
- * @frames_out: number of frames
|
|
|
|
- */
|
|
|
|
-static size_t audio_frontend_frames_out(SWVoiceOut *sw, size_t frames_out)
|
|
|
|
-{
|
|
|
|
- return ((int64_t)frames_out << 32) / sw->ratio;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static size_t audio_get_free(SWVoiceOut *sw)
|
|
static size_t audio_get_free(SWVoiceOut *sw)
|
|
{
|
|
{
|
|
size_t live, dead;
|
|
size_t live, dead;
|
|
@@ -1048,17 +1031,17 @@ static size_t audio_get_free(SWVoiceOut *sw)
|
|
|
|
|
|
live = sw->total_hw_samples_mixed;
|
|
live = sw->total_hw_samples_mixed;
|
|
|
|
|
|
- if (audio_bug(__func__, live > sw->hw->mix_buf->size)) {
|
|
|
|
- dolog("live=%zu sw->hw->mix_buf->size=%zu\n", live,
|
|
|
|
- sw->hw->mix_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {
|
|
|
|
+ dolog("live=%zu sw->hw->mix_buf.size=%zu\n", live,
|
|
|
|
+ sw->hw->mix_buf.size);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- dead = sw->hw->mix_buf->size - live;
|
|
|
|
|
|
+ dead = sw->hw->mix_buf.size - live;
|
|
|
|
|
|
#ifdef DEBUG_OUT
|
|
#ifdef DEBUG_OUT
|
|
- dolog("%s: get_free live %zu dead %zu frontend frames %zu\n",
|
|
|
|
- SW_NAME(sw), live, dead, audio_frontend_frames_out(sw, dead));
|
|
|
|
|
|
+ dolog("%s: get_free live %zu dead %zu frontend frames %u\n",
|
|
|
|
+ SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));
|
|
#endif
|
|
#endif
|
|
|
|
|
|
return dead;
|
|
return dead;
|
|
@@ -1074,32 +1057,40 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
|
|
|
|
|
|
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
|
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
|
SWVoiceOut *sw = &sc->sw;
|
|
SWVoiceOut *sw = &sc->sw;
|
|
- int rpos2 = rpos;
|
|
|
|
|
|
+ size_t rpos2 = rpos;
|
|
|
|
|
|
n = samples;
|
|
n = samples;
|
|
while (n) {
|
|
while (n) {
|
|
- size_t till_end_of_hw = hw->mix_buf->size - rpos2;
|
|
|
|
- size_t to_write = MIN(till_end_of_hw, n);
|
|
|
|
- size_t bytes = to_write * hw->info.bytes_per_frame;
|
|
|
|
- size_t written;
|
|
|
|
-
|
|
|
|
- sw->buf = hw->mix_buf->samples + rpos2;
|
|
|
|
- written = audio_pcm_sw_write (sw, NULL, bytes);
|
|
|
|
- if (written - bytes) {
|
|
|
|
- dolog("Could not mix %zu bytes into a capture "
|
|
|
|
|
|
+ size_t till_end_of_hw = hw->mix_buf.size - rpos2;
|
|
|
|
+ size_t to_read = MIN(till_end_of_hw, n);
|
|
|
|
+ size_t live, frames_in, frames_out;
|
|
|
|
+
|
|
|
|
+ sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;
|
|
|
|
+ sw->resample_buf.size = to_read;
|
|
|
|
+ live = sw->total_hw_samples_mixed;
|
|
|
|
+
|
|
|
|
+ audio_pcm_sw_resample_out(sw,
|
|
|
|
+ to_read, sw->hw->mix_buf.size - live,
|
|
|
|
+ &frames_in, &frames_out);
|
|
|
|
+
|
|
|
|
+ sw->total_hw_samples_mixed += frames_out;
|
|
|
|
+ sw->empty = sw->total_hw_samples_mixed == 0;
|
|
|
|
+
|
|
|
|
+ if (to_read - frames_in) {
|
|
|
|
+ dolog("Could not mix %zu frames into a capture "
|
|
"buffer, mixed %zu\n",
|
|
"buffer, mixed %zu\n",
|
|
- bytes, written);
|
|
|
|
|
|
+ to_read, frames_in);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
- n -= to_write;
|
|
|
|
- rpos2 = (rpos2 + to_write) % hw->mix_buf->size;
|
|
|
|
|
|
+ n -= to_read;
|
|
|
|
+ rpos2 = (rpos2 + to_read) % hw->mix_buf.size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- n = MIN(samples, hw->mix_buf->size - rpos);
|
|
|
|
- mixeng_clear(hw->mix_buf->samples + rpos, n);
|
|
|
|
- mixeng_clear(hw->mix_buf->samples, samples - n);
|
|
|
|
|
|
+ n = MIN(samples, hw->mix_buf.size - rpos);
|
|
|
|
+ mixeng_clear(hw->mix_buf.buffer + rpos, n);
|
|
|
|
+ mixeng_clear(hw->mix_buf.buffer, samples - n);
|
|
}
|
|
}
|
|
|
|
|
|
static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
|
|
static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
|
|
@@ -1125,7 +1116,7 @@ static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
|
|
|
|
|
|
live -= proc;
|
|
live -= proc;
|
|
clipped += proc;
|
|
clipped += proc;
|
|
- hw->mix_buf->pos = (hw->mix_buf->pos + proc) % hw->mix_buf->size;
|
|
|
|
|
|
+ hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;
|
|
|
|
|
|
if (proc == 0 || proc < decr) {
|
|
if (proc == 0 || proc < decr) {
|
|
break;
|
|
break;
|
|
@@ -1179,12 +1170,14 @@ static void audio_run_out (AudioState *s)
|
|
size_t free;
|
|
size_t free;
|
|
|
|
|
|
if (hw_free > sw->total_hw_samples_mixed) {
|
|
if (hw_free > sw->total_hw_samples_mixed) {
|
|
- free = audio_frontend_frames_out(sw,
|
|
|
|
|
|
+ free = st_rate_frames_in(sw->rate,
|
|
MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
|
|
MIN(sw_free, hw_free - sw->total_hw_samples_mixed));
|
|
} else {
|
|
} else {
|
|
free = 0;
|
|
free = 0;
|
|
}
|
|
}
|
|
- if (free > 0) {
|
|
|
|
|
|
+ if (free > sw->resample_buf.pos) {
|
|
|
|
+ free = MIN(free, sw->resample_buf.size)
|
|
|
|
+ - sw->resample_buf.pos;
|
|
sw->callback.fn(sw->callback.opaque,
|
|
sw->callback.fn(sw->callback.opaque,
|
|
free * sw->info.bytes_per_frame);
|
|
free * sw->info.bytes_per_frame);
|
|
}
|
|
}
|
|
@@ -1196,8 +1189,8 @@ static void audio_run_out (AudioState *s)
|
|
live = 0;
|
|
live = 0;
|
|
}
|
|
}
|
|
|
|
|
|
- if (audio_bug(__func__, live > hw->mix_buf->size)) {
|
|
|
|
- dolog("live=%zu hw->mix_buf->size=%zu\n", live, hw->mix_buf->size);
|
|
|
|
|
|
+ if (audio_bug(__func__, live > hw->mix_buf.size)) {
|
|
|
|
+ dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1225,13 +1218,13 @@ static void audio_run_out (AudioState *s)
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- prev_rpos = hw->mix_buf->pos;
|
|
|
|
|
|
+ prev_rpos = hw->mix_buf.pos;
|
|
played = audio_pcm_hw_run_out(hw, live);
|
|
played = audio_pcm_hw_run_out(hw, live);
|
|
replay_audio_out(&played);
|
|
replay_audio_out(&played);
|
|
- if (audio_bug(__func__, hw->mix_buf->pos >= hw->mix_buf->size)) {
|
|
|
|
- dolog("hw->mix_buf->pos=%zu hw->mix_buf->size=%zu played=%zu\n",
|
|
|
|
- hw->mix_buf->pos, hw->mix_buf->size, played);
|
|
|
|
- hw->mix_buf->pos = 0;
|
|
|
|
|
|
+ if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {
|
|
|
|
+ dolog("hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\n",
|
|
|
|
+ hw->mix_buf.pos, hw->mix_buf.size, played);
|
|
|
|
+ hw->mix_buf.pos = 0;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_OUT
|
|
#ifdef DEBUG_OUT
|
|
@@ -1312,10 +1305,10 @@ static void audio_run_in (AudioState *s)
|
|
|
|
|
|
if (replay_mode != REPLAY_MODE_PLAY) {
|
|
if (replay_mode != REPLAY_MODE_PLAY) {
|
|
captured = audio_pcm_hw_run_in(
|
|
captured = audio_pcm_hw_run_in(
|
|
- hw, hw->conv_buf->size - audio_pcm_hw_get_live_in(hw));
|
|
|
|
|
|
+ hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));
|
|
}
|
|
}
|
|
- replay_audio_in(&captured, hw->conv_buf->samples, &hw->conv_buf->pos,
|
|
|
|
- hw->conv_buf->size);
|
|
|
|
|
|
+ replay_audio_in(&captured, hw->conv_buf.buffer, &hw->conv_buf.pos,
|
|
|
|
+ hw->conv_buf.size);
|
|
|
|
|
|
min = audio_pcm_hw_find_min_in (hw);
|
|
min = audio_pcm_hw_find_min_in (hw);
|
|
hw->total_samples_captured += captured - min;
|
|
hw->total_samples_captured += captured - min;
|
|
@@ -1328,8 +1321,9 @@ static void audio_run_in (AudioState *s)
|
|
size_t sw_avail = audio_get_avail(sw);
|
|
size_t sw_avail = audio_get_avail(sw);
|
|
size_t avail;
|
|
size_t avail;
|
|
|
|
|
|
- avail = audio_frontend_frames_in(sw, sw_avail);
|
|
|
|
|
|
+ avail = st_rate_frames_out(sw->rate, sw_avail);
|
|
if (avail > 0) {
|
|
if (avail > 0) {
|
|
|
|
+ avail = MIN(avail, sw->resample_buf.size);
|
|
sw->callback.fn(sw->callback.opaque,
|
|
sw->callback.fn(sw->callback.opaque,
|
|
avail * sw->info.bytes_per_frame);
|
|
avail * sw->info.bytes_per_frame);
|
|
}
|
|
}
|
|
@@ -1348,14 +1342,14 @@ static void audio_run_capture (AudioState *s)
|
|
SWVoiceOut *sw;
|
|
SWVoiceOut *sw;
|
|
|
|
|
|
captured = live = audio_pcm_hw_get_live_out (hw, NULL);
|
|
captured = live = audio_pcm_hw_get_live_out (hw, NULL);
|
|
- rpos = hw->mix_buf->pos;
|
|
|
|
|
|
+ rpos = hw->mix_buf.pos;
|
|
while (live) {
|
|
while (live) {
|
|
- size_t left = hw->mix_buf->size - rpos;
|
|
|
|
|
|
+ size_t left = hw->mix_buf.size - rpos;
|
|
size_t to_capture = MIN(live, left);
|
|
size_t to_capture = MIN(live, left);
|
|
struct st_sample *src;
|
|
struct st_sample *src;
|
|
struct capture_callback *cb;
|
|
struct capture_callback *cb;
|
|
|
|
|
|
- src = hw->mix_buf->samples + rpos;
|
|
|
|
|
|
+ src = hw->mix_buf.buffer + rpos;
|
|
hw->clip (cap->buf, src, to_capture);
|
|
hw->clip (cap->buf, src, to_capture);
|
|
mixeng_clear (src, to_capture);
|
|
mixeng_clear (src, to_capture);
|
|
|
|
|
|
@@ -1363,10 +1357,10 @@ static void audio_run_capture (AudioState *s)
|
|
cb->ops.capture (cb->opaque, cap->buf,
|
|
cb->ops.capture (cb->opaque, cap->buf,
|
|
to_capture * hw->info.bytes_per_frame);
|
|
to_capture * hw->info.bytes_per_frame);
|
|
}
|
|
}
|
|
- rpos = (rpos + to_capture) % hw->mix_buf->size;
|
|
|
|
|
|
+ rpos = (rpos + to_capture) % hw->mix_buf.size;
|
|
live -= to_capture;
|
|
live -= to_capture;
|
|
}
|
|
}
|
|
- hw->mix_buf->pos = rpos;
|
|
|
|
|
|
+ hw->mix_buf.pos = rpos;
|
|
|
|
|
|
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
|
|
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
|
|
if (!sw->active && sw->empty) {
|
|
if (!sw->active && sw->empty) {
|
|
@@ -1943,7 +1937,7 @@ CaptureVoiceOut *AUD_add_capture(
|
|
|
|
|
|
audio_pcm_init_info (&hw->info, as);
|
|
audio_pcm_init_info (&hw->info, as);
|
|
|
|
|
|
- cap->buf = g_malloc0_n(hw->mix_buf->size, hw->info.bytes_per_frame);
|
|
|
|
|
|
+ cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);
|
|
|
|
|
|
if (hw->info.is_float) {
|
|
if (hw->info.is_float) {
|
|
hw->clip = mixeng_clip_float[hw->info.nchannels == 2];
|
|
hw->clip = mixeng_clip_float[hw->info.nchannels == 2];
|
|
@@ -1995,7 +1989,7 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
|
|
sw = sw1;
|
|
sw = sw1;
|
|
}
|
|
}
|
|
QLIST_REMOVE (cap, entries);
|
|
QLIST_REMOVE (cap, entries);
|
|
- g_free (cap->hw.mix_buf);
|
|
|
|
|
|
+ g_free(cap->hw.mix_buf.buffer);
|
|
g_free (cap->buf);
|
|
g_free (cap->buf);
|
|
g_free (cap);
|
|
g_free (cap);
|
|
}
|
|
}
|
|
@@ -2053,29 +2047,47 @@ void audio_create_pdos(Audiodev *dev)
|
|
switch (dev->driver) {
|
|
switch (dev->driver) {
|
|
#define CASE(DRIVER, driver, pdo_name) \
|
|
#define CASE(DRIVER, driver, pdo_name) \
|
|
case AUDIODEV_DRIVER_##DRIVER: \
|
|
case AUDIODEV_DRIVER_##DRIVER: \
|
|
- if (!dev->u.driver.has_in) { \
|
|
|
|
|
|
+ if (!dev->u.driver.in) { \
|
|
dev->u.driver.in = g_malloc0( \
|
|
dev->u.driver.in = g_malloc0( \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
- dev->u.driver.has_in = true; \
|
|
|
|
} \
|
|
} \
|
|
- if (!dev->u.driver.has_out) { \
|
|
|
|
|
|
+ if (!dev->u.driver.out) { \
|
|
dev->u.driver.out = g_malloc0( \
|
|
dev->u.driver.out = g_malloc0( \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
- dev->u.driver.has_out = true; \
|
|
|
|
} \
|
|
} \
|
|
break
|
|
break
|
|
|
|
|
|
CASE(NONE, none, );
|
|
CASE(NONE, none, );
|
|
|
|
+#ifdef CONFIG_AUDIO_ALSA
|
|
CASE(ALSA, alsa, Alsa);
|
|
CASE(ALSA, alsa, Alsa);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_COREAUDIO
|
|
CASE(COREAUDIO, coreaudio, Coreaudio);
|
|
CASE(COREAUDIO, coreaudio, Coreaudio);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_DBUS_DISPLAY
|
|
CASE(DBUS, dbus, );
|
|
CASE(DBUS, dbus, );
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_DSOUND
|
|
CASE(DSOUND, dsound, );
|
|
CASE(DSOUND, dsound, );
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_JACK
|
|
CASE(JACK, jack, Jack);
|
|
CASE(JACK, jack, Jack);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_OSS
|
|
CASE(OSS, oss, Oss);
|
|
CASE(OSS, oss, Oss);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_PA
|
|
CASE(PA, pa, Pa);
|
|
CASE(PA, pa, Pa);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_SDL
|
|
CASE(SDL, sdl, Sdl);
|
|
CASE(SDL, sdl, Sdl);
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_AUDIO_SNDIO
|
|
CASE(SNDIO, sndio, );
|
|
CASE(SNDIO, sndio, );
|
|
|
|
+#endif
|
|
|
|
+#ifdef CONFIG_SPICE
|
|
CASE(SPICE, spice, );
|
|
CASE(SPICE, spice, );
|
|
|
|
+#endif
|
|
CASE(WAV, wav, );
|
|
CASE(WAV, wav, );
|
|
|
|
|
|
case AUDIODEV_DRIVER__MAX:
|
|
case AUDIODEV_DRIVER__MAX:
|
|
@@ -2331,3 +2343,13 @@ size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,
|
|
|
|
|
|
return bytes;
|
|
return bytes;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+AudiodevList *qmp_query_audiodevs(Error **errp)
|
|
|
|
+{
|
|
|
|
+ AudiodevList *ret = NULL;
|
|
|
|
+ AudiodevListEntry *e;
|
|
|
|
+ QSIMPLEQ_FOREACH(e, &audiodevs, next) {
|
|
|
|
+ QAPI_LIST_PREPEND(ret, QAPI_CLONE(Audiodev, e->dev));
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+}
|