mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 18:05:37 +00:00
audio capture to wab files (malc)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2059 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
6330126439
commit
ec36b695b0
|
@ -321,6 +321,7 @@ endif
|
||||||
ifdef CONFIG_ADLIB
|
ifdef CONFIG_ADLIB
|
||||||
SOUND_HW += fmopl.o adlib.o
|
SOUND_HW += fmopl.o adlib.o
|
||||||
endif
|
endif
|
||||||
|
AUDIODRV+= wavcapture.o
|
||||||
|
|
||||||
# SCSI layer
|
# SCSI layer
|
||||||
VL_OBJS+= scsi-disk.o cdrom.o lsi53c895a.o
|
VL_OBJS+= scsi-disk.o cdrom.o lsi53c895a.o
|
||||||
|
|
172
audio/audio.c
172
audio/audio.c
|
@ -510,7 +510,8 @@ static void audio_print_settings (audsettings_t *as)
|
||||||
AUD_log (NULL, "invalid(%d)", as->fmt);
|
AUD_log (NULL, "invalid(%d)", as->fmt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
AUD_log (NULL, "endianness=");
|
|
||||||
|
AUD_log (NULL, " endianness=");
|
||||||
switch (as->endianness) {
|
switch (as->endianness) {
|
||||||
case 0:
|
case 0:
|
||||||
AUD_log (NULL, "little");
|
AUD_log (NULL, "little");
|
||||||
|
@ -525,7 +526,7 @@ static void audio_print_settings (audsettings_t *as)
|
||||||
AUD_log (NULL, "\n");
|
AUD_log (NULL, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int audio_validate_settigs (audsettings_t *as)
|
static int audio_validate_settings (audsettings_t *as)
|
||||||
{
|
{
|
||||||
int invalid;
|
int invalid;
|
||||||
|
|
||||||
|
@ -654,15 +655,25 @@ static CaptureVoiceOut *audio_pcm_capture_find_specific (
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audio_notify_capture (CaptureVoiceOut *cap, int enabled)
|
static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
|
||||||
|
{
|
||||||
|
struct capture_callback *cb;
|
||||||
|
|
||||||
|
#ifdef DEBUG_CAPTURE
|
||||||
|
dolog ("notification %d sent\n", cmd);
|
||||||
|
#endif
|
||||||
|
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
|
||||||
|
cb->ops.notify (cb->opaque, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void audio_capture_maybe_changed (CaptureVoiceOut *cap, int enabled)
|
||||||
{
|
{
|
||||||
if (cap->hw.enabled != enabled) {
|
if (cap->hw.enabled != enabled) {
|
||||||
struct capture_callback *cb;
|
audcnotification_e cmd;
|
||||||
|
|
||||||
cap->hw.enabled = enabled;
|
cap->hw.enabled = enabled;
|
||||||
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
|
cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
|
||||||
cb->ops.state (cb->opaque, enabled);
|
audio_notify_capture (cap, cmd);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,29 +683,40 @@ static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
|
||||||
SWVoiceOut *sw;
|
SWVoiceOut *sw;
|
||||||
int enabled = 0;
|
int enabled = 0;
|
||||||
|
|
||||||
for (sw = hw->sw_cap_head.lh_first; sw; sw = sw->cap_entries.le_next) {
|
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
|
||||||
if (sw->active) {
|
if (sw->active) {
|
||||||
enabled = 1;
|
enabled = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audio_notify_capture (cap, enabled);
|
audio_capture_maybe_changed (cap, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audio_detach_capture (HWVoiceOut *hw)
|
static void audio_detach_capture (HWVoiceOut *hw)
|
||||||
{
|
{
|
||||||
SWVoiceOut *sw;
|
SWVoiceCap *sc = hw->cap_head.lh_first;
|
||||||
|
|
||||||
|
while (sc) {
|
||||||
|
SWVoiceCap *sc1 = sc->entries.le_next;
|
||||||
|
SWVoiceOut *sw = &sc->sw;
|
||||||
|
CaptureVoiceOut *cap = sc->cap;
|
||||||
|
int was_active = sw->active;
|
||||||
|
|
||||||
for (sw = hw->sw_cap_head.lh_first; sw; sw = sw->cap_entries.le_next) {
|
|
||||||
if (sw->rate) {
|
if (sw->rate) {
|
||||||
st_rate_stop (sw->rate);
|
st_rate_stop (sw->rate);
|
||||||
sw->rate = NULL;
|
sw->rate = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
LIST_REMOVE (sw, entries);
|
LIST_REMOVE (sw, entries);
|
||||||
LIST_REMOVE (sw, cap_entries);
|
LIST_REMOVE (sc, entries);
|
||||||
qemu_free (sw);
|
qemu_free (sc);
|
||||||
audio_recalc_and_notify_capture ((CaptureVoiceOut *) sw->hw);
|
if (was_active) {
|
||||||
|
/* We have removed soft voice from the capture:
|
||||||
|
this might have changed the overall status of the capture
|
||||||
|
since this might have been the only active voice */
|
||||||
|
audio_recalc_and_notify_capture (cap);
|
||||||
|
}
|
||||||
|
sc = sc1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,19 +726,21 @@ static int audio_attach_capture (AudioState *s, HWVoiceOut *hw)
|
||||||
|
|
||||||
audio_detach_capture (hw);
|
audio_detach_capture (hw);
|
||||||
for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
|
for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
|
||||||
|
SWVoiceCap *sc;
|
||||||
SWVoiceOut *sw;
|
SWVoiceOut *sw;
|
||||||
HWVoiceOut *hw_cap;
|
HWVoiceOut *hw_cap = &cap->hw;
|
||||||
|
|
||||||
hw_cap = &cap->hw;
|
sc = audio_calloc (AUDIO_FUNC, 1, sizeof (*sc));
|
||||||
sw = audio_calloc (AUDIO_FUNC, 1, sizeof (*sw));
|
if (!sc) {
|
||||||
if (!sw) {
|
|
||||||
dolog ("Could not allocate soft capture voice (%zu bytes)\n",
|
dolog ("Could not allocate soft capture voice (%zu bytes)\n",
|
||||||
sizeof (*sw));
|
sizeof (*sc));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sw->info = hw->info;
|
sc->cap = cap;
|
||||||
|
sw = &sc->sw;
|
||||||
sw->hw = hw_cap;
|
sw->hw = hw_cap;
|
||||||
|
sw->info = hw->info;
|
||||||
sw->empty = 1;
|
sw->empty = 1;
|
||||||
sw->active = hw->enabled;
|
sw->active = hw->enabled;
|
||||||
sw->conv = noop_conv;
|
sw->conv = noop_conv;
|
||||||
|
@ -728,12 +752,14 @@ static int audio_attach_capture (AudioState *s, HWVoiceOut *hw)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
LIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
|
LIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
|
||||||
LIST_INSERT_HEAD (&hw->sw_cap_head, sw, cap_entries);
|
LIST_INSERT_HEAD (&hw->cap_head, sc, entries);
|
||||||
|
#ifdef DEBUG_CAPTURE
|
||||||
|
asprintf (&sw->name, "for %p %d,%d,%d",
|
||||||
|
hw, sw->info.freq, sw->info.bits, sw->info.nchannels);
|
||||||
|
dolog ("Added %s active = %d\n", sw->name, sw->active);
|
||||||
|
#endif
|
||||||
if (sw->active) {
|
if (sw->active) {
|
||||||
audio_notify_capture (cap, 1);
|
audio_capture_maybe_changed (cap, 1);
|
||||||
}
|
|
||||||
else {
|
|
||||||
audio_recalc_and_notify_capture (cap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -915,6 +941,9 @@ int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (live == hwsamples) {
|
if (live == hwsamples) {
|
||||||
|
#ifdef DEBUG_OUT
|
||||||
|
dolog ("%s is full %d\n", sw->name, live);
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1033,6 +1062,7 @@ void AUD_set_active_out (SWVoiceOut *sw, int on)
|
||||||
hw = sw->hw;
|
hw = sw->hw;
|
||||||
if (sw->active != on) {
|
if (sw->active != on) {
|
||||||
SWVoiceOut *temp_sw;
|
SWVoiceOut *temp_sw;
|
||||||
|
SWVoiceCap *sc;
|
||||||
|
|
||||||
if (on) {
|
if (on) {
|
||||||
hw->pending_disable = 0;
|
hw->pending_disable = 0;
|
||||||
|
@ -1053,11 +1083,11 @@ void AUD_set_active_out (SWVoiceOut *sw, int on)
|
||||||
hw->pending_disable = nb_active == 1;
|
hw->pending_disable = nb_active == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (temp_sw = hw->sw_cap_head.lh_first; temp_sw;
|
|
||||||
temp_sw = temp_sw->entries.le_next) {
|
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
||||||
temp_sw->active = hw->enabled;
|
sc->sw.active = hw->enabled;
|
||||||
if (hw->enabled) {
|
if (hw->enabled) {
|
||||||
audio_notify_capture ((CaptureVoiceOut *) temp_sw->hw, 1);
|
audio_capture_maybe_changed (sc->cap, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sw->active = on;
|
sw->active = on;
|
||||||
|
@ -1156,9 +1186,10 @@ static void audio_capture_mix_and_clear (HWVoiceOut *hw, int rpos, int samples)
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if (hw->enabled) {
|
if (hw->enabled) {
|
||||||
SWVoiceOut *sw;
|
SWVoiceCap *sc;
|
||||||
|
|
||||||
for (sw = hw->sw_cap_head.lh_first; sw; sw = sw->cap_entries.le_next) {
|
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
||||||
|
SWVoiceOut *sw = &sc->sw;
|
||||||
int rpos2 = rpos;
|
int rpos2 = rpos;
|
||||||
|
|
||||||
n = samples;
|
n = samples;
|
||||||
|
@ -1171,8 +1202,9 @@ static void audio_capture_mix_and_clear (HWVoiceOut *hw, int rpos, int samples)
|
||||||
sw->buf = hw->mix_buf + rpos2;
|
sw->buf = hw->mix_buf + rpos2;
|
||||||
written = audio_pcm_sw_write (sw, NULL, bytes);
|
written = audio_pcm_sw_write (sw, NULL, bytes);
|
||||||
if (written - bytes) {
|
if (written - bytes) {
|
||||||
dolog ("Could not mix %d bytes into a capture buffer",
|
dolog ("Could not mix %d bytes into a capture "
|
||||||
bytes);
|
"buffer, mixed %d\n",
|
||||||
|
bytes, written);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
n -= to_write;
|
n -= to_write;
|
||||||
|
@ -1206,16 +1238,16 @@ static void audio_run_out (AudioState *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw->pending_disable && !nb_live) {
|
if (hw->pending_disable && !nb_live) {
|
||||||
|
SWVoiceCap *sc;
|
||||||
#ifdef DEBUG_OUT
|
#ifdef DEBUG_OUT
|
||||||
dolog ("Disabling voice\n");
|
dolog ("Disabling voice\n");
|
||||||
#endif
|
#endif
|
||||||
hw->enabled = 0;
|
hw->enabled = 0;
|
||||||
hw->pending_disable = 0;
|
hw->pending_disable = 0;
|
||||||
hw->pcm_ops->ctl_out (hw, VOICE_DISABLE);
|
hw->pcm_ops->ctl_out (hw, VOICE_DISABLE);
|
||||||
for (sw = hw->sw_cap_head.lh_first; sw;
|
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
||||||
sw = sw->cap_entries.le_next) {
|
sc->sw.active = 0;
|
||||||
sw->active = 0;
|
audio_recalc_and_notify_capture (sc->cap);
|
||||||
audio_recalc_and_notify_capture ((CaptureVoiceOut *) sw->hw);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1277,15 +1309,18 @@ static void audio_run_out (AudioState *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanup_required) {
|
if (cleanup_required) {
|
||||||
restart:
|
SWVoiceOut *sw1;
|
||||||
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
|
|
||||||
|
sw = hw->sw_head.lh_first;
|
||||||
|
while (sw) {
|
||||||
|
sw1 = sw->entries.le_next;
|
||||||
if (!sw->active && !sw->callback.fn) {
|
if (!sw->active && !sw->callback.fn) {
|
||||||
#ifdef DEBUG_PLIVE
|
#ifdef DEBUG_PLIVE
|
||||||
dolog ("Finishing with old voice\n");
|
dolog ("Finishing with old voice\n");
|
||||||
#endif
|
#endif
|
||||||
audio_close_out (s, sw);
|
audio_close_out (s, sw);
|
||||||
goto restart; /* play it safe */
|
|
||||||
}
|
}
|
||||||
|
sw = sw1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1537,13 +1572,18 @@ static void audio_atexit (void)
|
||||||
HWVoiceIn *hwi = NULL;
|
HWVoiceIn *hwi = NULL;
|
||||||
|
|
||||||
while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) {
|
while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) {
|
||||||
SWVoiceOut *sw;
|
SWVoiceCap *sc;
|
||||||
|
|
||||||
hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE);
|
hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE);
|
||||||
hwo->pcm_ops->fini_out (hwo);
|
hwo->pcm_ops->fini_out (hwo);
|
||||||
|
|
||||||
for (sw = hwo->sw_cap_head.lh_first; sw; sw = sw->entries.le_next) {
|
for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {
|
||||||
audio_notify_capture ((CaptureVoiceOut *) sw->hw, 0);
|
CaptureVoiceOut *cap = sc->cap;
|
||||||
|
struct capture_callback *cb;
|
||||||
|
|
||||||
|
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
|
||||||
|
cb->ops.destroy (cb->opaque);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1697,7 +1737,7 @@ AudioState *AUD_init (void)
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AUD_add_capture (
|
CaptureVoiceOut *AUD_add_capture (
|
||||||
AudioState *s,
|
AudioState *s,
|
||||||
audsettings_t *as,
|
audsettings_t *as,
|
||||||
struct audio_capture_ops *ops,
|
struct audio_capture_ops *ops,
|
||||||
|
@ -1712,10 +1752,10 @@ int AUD_add_capture (
|
||||||
s = &glob_audio_state;
|
s = &glob_audio_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audio_validate_settigs (as)) {
|
if (audio_validate_settings (as)) {
|
||||||
dolog ("Invalid settings were passed when trying to add capture\n");
|
dolog ("Invalid settings were passed when trying to add capture\n");
|
||||||
audio_print_settings (as);
|
audio_print_settings (as);
|
||||||
return -1;
|
goto err0;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb = audio_calloc (AUDIO_FUNC, 1, sizeof (*cb));
|
cb = audio_calloc (AUDIO_FUNC, 1, sizeof (*cb));
|
||||||
|
@ -1730,7 +1770,7 @@ int AUD_add_capture (
|
||||||
cap = audio_pcm_capture_find_specific (s, as);
|
cap = audio_pcm_capture_find_specific (s, as);
|
||||||
if (cap) {
|
if (cap) {
|
||||||
LIST_INSERT_HEAD (&cap->cb_head, cb, entries);
|
LIST_INSERT_HEAD (&cap->cb_head, cb, entries);
|
||||||
return 0;
|
return cap;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
HWVoiceOut *hw;
|
HWVoiceOut *hw;
|
||||||
|
@ -1780,7 +1820,7 @@ int AUD_add_capture (
|
||||||
while ((hw = audio_pcm_hw_find_any_out (s, hw))) {
|
while ((hw = audio_pcm_hw_find_any_out (s, hw))) {
|
||||||
audio_attach_capture (s, hw);
|
audio_attach_capture (s, hw);
|
||||||
}
|
}
|
||||||
return 0;
|
return cap;
|
||||||
|
|
||||||
err3:
|
err3:
|
||||||
qemu_free (cap->hw.mix_buf);
|
qemu_free (cap->hw.mix_buf);
|
||||||
|
@ -1789,6 +1829,38 @@ int AUD_add_capture (
|
||||||
err1:
|
err1:
|
||||||
qemu_free (cb);
|
qemu_free (cb);
|
||||||
err0:
|
err0:
|
||||||
return -1;
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
|
||||||
|
{
|
||||||
|
struct capture_callback *cb;
|
||||||
|
|
||||||
|
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
|
||||||
|
if (cb->opaque == cb_opaque) {
|
||||||
|
cb->ops.destroy (cb_opaque);
|
||||||
|
LIST_REMOVE (cb, entries);
|
||||||
|
qemu_free (cb);
|
||||||
|
|
||||||
|
if (!cap->cb_head.lh_first) {
|
||||||
|
SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;
|
||||||
|
while (sw) {
|
||||||
|
#ifdef DEBUG_CAPTURE
|
||||||
|
dolog ("freeing %s\n", sw->name);
|
||||||
|
#endif
|
||||||
|
sw1 = sw->entries.le_next;
|
||||||
|
if (sw->rate) {
|
||||||
|
st_rate_stop (sw->rate);
|
||||||
|
sw->rate = NULL;
|
||||||
|
}
|
||||||
|
LIST_REMOVE (sw, entries);
|
||||||
|
sw = sw1;
|
||||||
|
}
|
||||||
|
LIST_REMOVE (cap, entries);
|
||||||
|
qemu_free (cap);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,13 +49,31 @@ typedef struct {
|
||||||
int endianness;
|
int endianness;
|
||||||
} audsettings_t;
|
} audsettings_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AUD_CNOTIFY_ENABLE,
|
||||||
|
AUD_CNOTIFY_DISABLE
|
||||||
|
} audcnotification_e;
|
||||||
|
|
||||||
struct audio_capture_ops {
|
struct audio_capture_ops {
|
||||||
void (*state) (void *opaque, int enabled);
|
void (*notify) (void *opaque, audcnotification_e cmd);
|
||||||
void (*capture) (void *opaque, void *buf, int size);
|
void (*capture) (void *opaque, void *buf, int size);
|
||||||
|
void (*destroy) (void *opaque);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct capture_ops {
|
||||||
|
void (*info) (void *opaque);
|
||||||
|
void (*destroy) (void *opaque);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct CaptureState {
|
||||||
|
void *opaque;
|
||||||
|
struct capture_ops ops;
|
||||||
|
LIST_ENTRY (CaptureState) entries;
|
||||||
|
} CaptureState;
|
||||||
|
|
||||||
typedef struct AudioState AudioState;
|
typedef struct AudioState AudioState;
|
||||||
typedef struct SWVoiceOut SWVoiceOut;
|
typedef struct SWVoiceOut SWVoiceOut;
|
||||||
|
typedef struct CaptureVoiceOut CaptureVoiceOut;
|
||||||
typedef struct SWVoiceIn SWVoiceIn;
|
typedef struct SWVoiceIn SWVoiceIn;
|
||||||
|
|
||||||
typedef struct QEMUSoundCard {
|
typedef struct QEMUSoundCard {
|
||||||
|
@ -79,12 +97,13 @@ AudioState *AUD_init (void);
|
||||||
void AUD_help (void);
|
void AUD_help (void);
|
||||||
void AUD_register_card (AudioState *s, const char *name, QEMUSoundCard *card);
|
void AUD_register_card (AudioState *s, const char *name, QEMUSoundCard *card);
|
||||||
void AUD_remove_card (QEMUSoundCard *card);
|
void AUD_remove_card (QEMUSoundCard *card);
|
||||||
int AUD_add_capture (
|
CaptureVoiceOut *AUD_add_capture (
|
||||||
AudioState *s,
|
AudioState *s,
|
||||||
audsettings_t *as,
|
audsettings_t *as,
|
||||||
struct audio_capture_ops *ops,
|
struct audio_capture_ops *ops,
|
||||||
void *opaque
|
void *opaque
|
||||||
);
|
);
|
||||||
|
void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque);
|
||||||
|
|
||||||
SWVoiceOut *AUD_open_out (
|
SWVoiceOut *AUD_open_out (
|
||||||
QEMUSoundCard *card,
|
QEMUSoundCard *card,
|
||||||
|
|
|
@ -64,10 +64,11 @@ struct audio_pcm_info {
|
||||||
int swap_endianness;
|
int swap_endianness;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct SWVoiceCap SWVoiceCap;
|
||||||
|
|
||||||
typedef struct HWVoiceOut {
|
typedef struct HWVoiceOut {
|
||||||
int enabled;
|
int enabled;
|
||||||
int pending_disable;
|
int pending_disable;
|
||||||
int valid;
|
|
||||||
struct audio_pcm_info info;
|
struct audio_pcm_info info;
|
||||||
|
|
||||||
f_sample *clip;
|
f_sample *clip;
|
||||||
|
@ -79,7 +80,7 @@ typedef struct HWVoiceOut {
|
||||||
|
|
||||||
int samples;
|
int samples;
|
||||||
LIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head;
|
LIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head;
|
||||||
LIST_HEAD (sw_cap_listhead, SWVoiceOut) sw_cap_head;
|
LIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head;
|
||||||
struct audio_pcm_ops *pcm_ops;
|
struct audio_pcm_ops *pcm_ops;
|
||||||
LIST_ENTRY (HWVoiceOut) entries;
|
LIST_ENTRY (HWVoiceOut) entries;
|
||||||
} HWVoiceOut;
|
} HWVoiceOut;
|
||||||
|
@ -116,7 +117,6 @@ struct SWVoiceOut {
|
||||||
volume_t vol;
|
volume_t vol;
|
||||||
struct audio_callback callback;
|
struct audio_callback callback;
|
||||||
LIST_ENTRY (SWVoiceOut) entries;
|
LIST_ENTRY (SWVoiceOut) entries;
|
||||||
LIST_ENTRY (SWVoiceOut) cap_entries;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SWVoiceIn {
|
struct SWVoiceIn {
|
||||||
|
@ -168,12 +168,18 @@ struct capture_callback {
|
||||||
LIST_ENTRY (capture_callback) entries;
|
LIST_ENTRY (capture_callback) entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct CaptureVoiceOut {
|
struct CaptureVoiceOut {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
void *buf;
|
void *buf;
|
||||||
LIST_HEAD (cb_listhead, capture_callback) cb_head;
|
LIST_HEAD (cb_listhead, capture_callback) cb_head;
|
||||||
LIST_ENTRY (CaptureVoiceOut) entries;
|
LIST_ENTRY (CaptureVoiceOut) entries;
|
||||||
} CaptureVoiceOut;
|
};
|
||||||
|
|
||||||
|
struct SWVoiceCap {
|
||||||
|
SWVoiceOut sw;
|
||||||
|
CaptureVoiceOut *cap;
|
||||||
|
LIST_ENTRY (SWVoiceCap) entries;
|
||||||
|
};
|
||||||
|
|
||||||
struct AudioState {
|
struct AudioState {
|
||||||
struct audio_driver *drv;
|
struct audio_driver *drv;
|
||||||
|
|
|
@ -269,7 +269,7 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (AudioState *s, audsettings_t *as)
|
||||||
hw->pcm_ops = drv->pcm_ops;
|
hw->pcm_ops = drv->pcm_ops;
|
||||||
LIST_INIT (&hw->sw_head);
|
LIST_INIT (&hw->sw_head);
|
||||||
#ifdef DAC
|
#ifdef DAC
|
||||||
LIST_INIT (&hw->sw_cap_head);
|
LIST_INIT (&hw->cap_head);
|
||||||
#endif
|
#endif
|
||||||
if (glue (hw->pcm_ops->init_, TYPE) (hw, as)) {
|
if (glue (hw->pcm_ops->init_, TYPE) (hw, as)) {
|
||||||
goto err0;
|
goto err0;
|
||||||
|
@ -426,7 +426,7 @@ SW *glue (AUD_open_, TYPE) (
|
||||||
|
|
||||||
s = card->audio;
|
s = card->audio;
|
||||||
|
|
||||||
if (audio_bug (AUDIO_FUNC, audio_validate_settigs (as))) {
|
if (audio_bug (AUDIO_FUNC, audio_validate_settings (as))) {
|
||||||
audio_print_settings (as);
|
audio_print_settings (as);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ static int no_run_in (HWVoiceIn *hw)
|
||||||
NoVoiceIn *no = (NoVoiceIn *) hw;
|
NoVoiceIn *no = (NoVoiceIn *) hw;
|
||||||
int live = audio_pcm_hw_get_live_in (hw);
|
int live = audio_pcm_hw_get_live_in (hw);
|
||||||
int dead = hw->samples - live;
|
int dead = hw->samples - live;
|
||||||
int samples;
|
int samples = 0;
|
||||||
|
|
||||||
if (dead) {
|
if (dead) {
|
||||||
int64_t now = qemu_get_clock (vm_clock);
|
int64_t now = qemu_get_clock (vm_clock);
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
QEMUFile *f;
|
QEMUFile *f;
|
||||||
int bytes;
|
int bytes;
|
||||||
|
char *path;
|
||||||
|
int freq;
|
||||||
|
int bits;
|
||||||
|
int nchannels;
|
||||||
|
CaptureVoiceOut *cap;
|
||||||
} WAVState;
|
} WAVState;
|
||||||
|
|
||||||
/* VICE code: Store number as little endian. */
|
/* VICE code: Store number as little endian. */
|
||||||
|
@ -15,35 +20,39 @@ static void le_store (uint8_t *buf, uint32_t val, int len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wav_state_cb (void *opaque, int enabled)
|
static void wav_notify (void *opaque, audcnotification_e cmd)
|
||||||
|
{
|
||||||
|
(void) opaque;
|
||||||
|
(void) cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wav_destroy (void *opaque)
|
||||||
{
|
{
|
||||||
WAVState *wav = opaque;
|
WAVState *wav = opaque;
|
||||||
|
uint8_t rlen[4];
|
||||||
|
uint8_t dlen[4];
|
||||||
|
uint32_t datalen = wav->bytes;
|
||||||
|
uint32_t rifflen = datalen + 36;
|
||||||
|
|
||||||
if (!enabled) {
|
if (!wav->f) {
|
||||||
uint8_t rlen[4];
|
return;
|
||||||
uint8_t dlen[4];
|
|
||||||
uint32_t datalen = wav->bytes;
|
|
||||||
uint32_t rifflen = datalen + 36;
|
|
||||||
|
|
||||||
if (!wav->f) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
le_store (rlen, rifflen, 4);
|
|
||||||
le_store (dlen, datalen, 4);
|
|
||||||
|
|
||||||
qemu_fseek (wav->f, 4, SEEK_SET);
|
|
||||||
qemu_put_buffer (wav->f, rlen, 4);
|
|
||||||
|
|
||||||
qemu_fseek (wav->f, 32, SEEK_CUR);
|
|
||||||
qemu_put_buffer (wav->f, dlen, 4);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
qemu_fseek (wav->f, 0, SEEK_END);
|
le_store (rlen, rifflen, 4);
|
||||||
|
le_store (dlen, datalen, 4);
|
||||||
|
|
||||||
|
qemu_fseek (wav->f, 4, SEEK_SET);
|
||||||
|
qemu_put_buffer (wav->f, rlen, 4);
|
||||||
|
|
||||||
|
qemu_fseek (wav->f, 32, SEEK_CUR);
|
||||||
|
qemu_put_buffer (wav->f, dlen, 4);
|
||||||
|
fclose (wav->f);
|
||||||
|
if (wav->path) {
|
||||||
|
qemu_free (wav->path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wav_capture_cb (void *opaque, void *buf, int size)
|
static void wav_capture (void *opaque, void *buf, int size)
|
||||||
{
|
{
|
||||||
WAVState *wav = opaque;
|
WAVState *wav = opaque;
|
||||||
|
|
||||||
|
@ -51,7 +60,30 @@ static void wav_capture_cb (void *opaque, void *buf, int size)
|
||||||
wav->bytes += size;
|
wav->bytes += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wav_capture (const char *path, int freq, int bits16, int stereo)
|
static void wav_capture_destroy (void *opaque)
|
||||||
|
{
|
||||||
|
WAVState *wav = opaque;
|
||||||
|
|
||||||
|
AUD_del_capture (wav->cap, wav);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wav_capture_info (void *opaque)
|
||||||
|
{
|
||||||
|
WAVState *wav = opaque;
|
||||||
|
char *path = wav->path;
|
||||||
|
|
||||||
|
term_printf ("Capturing audio(%d,%d,%d) to %s: %d bytes\n",
|
||||||
|
wav->freq, wav->bits, wav->nchannels,
|
||||||
|
path ? path : "<not available>", wav->bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct capture_ops wav_capture_ops = {
|
||||||
|
.destroy = wav_capture_destroy,
|
||||||
|
.info = wav_capture_info
|
||||||
|
};
|
||||||
|
|
||||||
|
int wav_start_capture (CaptureState *s, const char *path, int freq,
|
||||||
|
int bits, int nchannels)
|
||||||
{
|
{
|
||||||
WAVState *wav;
|
WAVState *wav;
|
||||||
uint8_t hdr[] = {
|
uint8_t hdr[] = {
|
||||||
|
@ -62,23 +94,35 @@ void wav_capture (const char *path, int freq, int bits16, int stereo)
|
||||||
};
|
};
|
||||||
audsettings_t as;
|
audsettings_t as;
|
||||||
struct audio_capture_ops ops;
|
struct audio_capture_ops ops;
|
||||||
int shift;
|
int stereo, bits16, shift;
|
||||||
|
CaptureVoiceOut *cap;
|
||||||
|
|
||||||
stereo = !!stereo;
|
if (bits != 8 && bits != 16) {
|
||||||
bits16 = !!bits16;
|
term_printf ("incorrect bit count %d, must be 8 or 16\n", bits);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nchannels != 1 && nchannels != 2) {
|
||||||
|
term_printf ("incorrect channel count %d, must be 1 or 2\n", bits);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stereo = nchannels == 2;
|
||||||
|
bits16 = bits == 16;
|
||||||
|
|
||||||
as.freq = freq;
|
as.freq = freq;
|
||||||
as.nchannels = 1 << stereo;
|
as.nchannels = 1 << stereo;
|
||||||
as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8;
|
as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8;
|
||||||
as.endianness = 0;
|
as.endianness = 0;
|
||||||
|
|
||||||
ops.state = wav_state_cb;
|
ops.notify = wav_notify;
|
||||||
ops.capture = wav_capture_cb;
|
ops.capture = wav_capture;
|
||||||
|
ops.destroy = wav_destroy;
|
||||||
|
|
||||||
wav = qemu_mallocz (sizeof (*wav));
|
wav = qemu_mallocz (sizeof (*wav));
|
||||||
if (!wav) {
|
if (!wav) {
|
||||||
AUD_log ("wav", "Could not allocate memory (%zu bytes)", sizeof (*wav));
|
AUD_log ("wav", "Could not allocate memory (%zu bytes)", sizeof (*wav));
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
shift = bits16 + stereo;
|
shift = bits16 + stereo;
|
||||||
|
@ -91,12 +135,28 @@ void wav_capture (const char *path, int freq, int bits16, int stereo)
|
||||||
|
|
||||||
wav->f = fopen (path, "wb");
|
wav->f = fopen (path, "wb");
|
||||||
if (!wav->f) {
|
if (!wav->f) {
|
||||||
AUD_log ("wav", "Failed to open wave file `%s'\nReason: %s\n",
|
term_printf ("Failed to open wave file `%s'\nReason: %s\n",
|
||||||
path, strerror (errno));
|
path, strerror (errno));
|
||||||
qemu_free (wav);
|
qemu_free (wav);
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wav->path = qemu_strdup (path);
|
||||||
|
wav->bits = bits;
|
||||||
|
wav->nchannels = nchannels;
|
||||||
|
wav->freq = freq;
|
||||||
|
|
||||||
qemu_put_buffer (wav->f, hdr, sizeof (hdr));
|
qemu_put_buffer (wav->f, hdr, sizeof (hdr));
|
||||||
AUD_add_capture (NULL, &as, &ops, wav);
|
|
||||||
|
cap = AUD_add_capture (NULL, &as, &ops, wav);
|
||||||
|
if (!cap) {
|
||||||
|
term_printf ("Failed to add audio capture\n");
|
||||||
|
qemu_free (wav);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
wav->cap = cap;
|
||||||
|
s->opaque = wav;
|
||||||
|
s->ops = wav_capture_ops;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
70
monitor.c
70
monitor.c
|
@ -1077,6 +1077,64 @@ static void do_info_profile(void)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Capture support */
|
||||||
|
static LIST_HEAD (capture_list_head, CaptureState) capture_head;
|
||||||
|
|
||||||
|
static void do_info_capture (void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
CaptureState *s;
|
||||||
|
|
||||||
|
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) {
|
||||||
|
term_printf ("[%d]: ", i);
|
||||||
|
s->ops.info (s->opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_stop_capture (int n)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
CaptureState *s;
|
||||||
|
|
||||||
|
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) {
|
||||||
|
if (i == n) {
|
||||||
|
s->ops.destroy (s->opaque);
|
||||||
|
LIST_REMOVE (s, entries);
|
||||||
|
qemu_free (s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAS_AUDIO
|
||||||
|
int wav_start_capture (CaptureState *s, const char *path, int freq,
|
||||||
|
int bits, int nchannels);
|
||||||
|
|
||||||
|
static void do_wav_capture (const char *path,
|
||||||
|
int has_freq, int freq,
|
||||||
|
int has_bits, int bits,
|
||||||
|
int has_channels, int nchannels)
|
||||||
|
{
|
||||||
|
CaptureState *s;
|
||||||
|
|
||||||
|
s = qemu_mallocz (sizeof (*s));
|
||||||
|
if (!s) {
|
||||||
|
term_printf ("Not enough memory to add wave capture\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
freq = has_freq ? freq : 44100;
|
||||||
|
bits = has_bits ? bits : 16;
|
||||||
|
nchannels = has_channels ? nchannels : 2;
|
||||||
|
|
||||||
|
if (wav_start_capture (s, path, freq, bits, nchannels)) {
|
||||||
|
term_printf ("Faied to add wave capture\n");
|
||||||
|
qemu_free (s);
|
||||||
|
}
|
||||||
|
LIST_INSERT_HEAD (&capture_head, s, entries);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static term_cmd_t term_cmds[] = {
|
static term_cmd_t term_cmds[] = {
|
||||||
{ "help|?", "s?", do_help,
|
{ "help|?", "s?", do_help,
|
||||||
"[cmd]", "show the help" },
|
"[cmd]", "show the help" },
|
||||||
|
@ -1133,6 +1191,13 @@ static term_cmd_t term_cmds[] = {
|
||||||
"dx dy [dz]", "send mouse move events" },
|
"dx dy [dz]", "send mouse move events" },
|
||||||
{ "mouse_button", "i", do_mouse_button,
|
{ "mouse_button", "i", do_mouse_button,
|
||||||
"state", "change mouse button state (1=L, 2=M, 4=R)" },
|
"state", "change mouse button state (1=L, 2=M, 4=R)" },
|
||||||
|
#ifdef HAS_AUDIO
|
||||||
|
{ "wavcapture", "si?i?i?", do_wav_capture,
|
||||||
|
"path [frequency bits channels]",
|
||||||
|
"capture audio to a wave file (default frequency=44100 bits=16 channels=2)" },
|
||||||
|
#endif
|
||||||
|
{ "stopcapture", "i", do_stop_capture,
|
||||||
|
"capture index", "stop capture" },
|
||||||
{ NULL, NULL, },
|
{ NULL, NULL, },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1171,6 +1236,8 @@ static term_cmd_t info_cmds[] = {
|
||||||
"", "show host USB devices", },
|
"", "show host USB devices", },
|
||||||
{ "profile", "", do_info_profile,
|
{ "profile", "", do_info_profile,
|
||||||
"", "show profiling information", },
|
"", "show profiling information", },
|
||||||
|
{ "capture", "", do_info_capture,
|
||||||
|
"show capture information" },
|
||||||
{ NULL, NULL, },
|
{ NULL, NULL, },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2081,6 +2148,9 @@ static void monitor_handle_command(const char *cmdline)
|
||||||
case 6:
|
case 6:
|
||||||
cmd->handler(args[0], args[1], args[2], args[3], args[4], args[5]);
|
cmd->handler(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
cmd->handler(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
term_printf("unsupported number of arguments: %d\n", nb_args);
|
term_printf("unsupported number of arguments: %d\n", nb_args);
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
Loading…
Reference in a new issue