* [PATCH 2/5] qv4l2: new ALSA stream source code
2013-08-02 12:05 ` [PATCH 1/5] qv4l2: alter capture menu Bård Eirik Winther
@ 2013-08-02 12:05 ` Bård Eirik Winther
2013-08-02 12:05 ` [PATCH 3/5] qv4l2: add ALSA stream to qv4l2 Bård Eirik Winther
` (2 subsequent siblings)
3 siblings, 0 replies; 9+ messages in thread
From: Bård Eirik Winther @ 2013-08-02 12:05 UTC (permalink / raw)
To: linux-media
Code copied from xawtv3
Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
utils/qv4l2/alsa_stream.c | 645 ++++++++++++++++++++++++++++++++++++++++++++++
utils/qv4l2/alsa_stream.h | 5 +
2 files changed, 650 insertions(+)
create mode 100644 utils/qv4l2/alsa_stream.c
create mode 100644 utils/qv4l2/alsa_stream.h
diff --git a/utils/qv4l2/alsa_stream.c b/utils/qv4l2/alsa_stream.c
new file mode 100644
index 0000000..3e33b5e
--- /dev/null
+++ b/utils/qv4l2/alsa_stream.c
@@ -0,0 +1,645 @@
+/*
+ * ALSA streaming support
+ *
+ * Originally written by:
+ * Copyright (c) by Devin Heitmueller <dheitmueller@kernellabs.com>
+ * for usage at tvtime
+ * Derived from the alsa-driver test tool latency.c:
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ * Copyright (c) 2011 - Mauro Carvalho Chehab <mchehab@redhat.com>
+ * Ported to xawtv, with bug fixes and improvements
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "config.h"
+
+#ifdef HAVE_ALSA_ASOUNDLIB_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include "alsa_stream.h"
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
+
+/* Private vars to control alsa thread status */
+static int stop_alsa = 0;
+
+/* Error handlers */
+snd_output_t *output = NULL;
+FILE *error_fp;
+int verbose = 0;
+
+struct final_params {
+ int bufsize;
+ int rate;
+ int latency;
+ int channels;
+};
+
+static int setparams_stream(snd_pcm_t *handle,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_format_t format,
+ int *channels,
+ const char *id)
+{
+ int err;
+
+ err = snd_pcm_hw_params_any(handle, params);
+ if (err < 0) {
+ fprintf(error_fp,
+ "alsa: Broken configuration for %s PCM: no configurations available: %s\n",
+ snd_strerror(err), id);
+ return err;
+ }
+
+ err = snd_pcm_hw_params_set_access(handle, params,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Access type not available for %s: %s\n", id,
+ snd_strerror(err));
+ return err;
+ }
+
+ err = snd_pcm_hw_params_set_format(handle, params, format);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Sample format not available for %s: %s\n", id,
+ snd_strerror(err));
+ return err;
+ }
+
+retry:
+ err = snd_pcm_hw_params_set_channels(handle, params, *channels);
+ if (err < 0) {
+ if (strcmp(id, "capture") == 0 && *channels == 2) {
+ *channels = 1;
+ goto retry; /* Retry with mono capture */
+ }
+ fprintf(error_fp, "alsa: Channels count (%i) not available for %s: %s\n",
+ *channels, id, snd_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
+
+static void getparams_periods(snd_pcm_t *handle,
+ snd_pcm_hw_params_t *params,
+ unsigned int *usecs,
+ unsigned int *count,
+ const char *id)
+{
+ unsigned min = 0, max = 0;
+
+ snd_pcm_hw_params_get_periods_min(params, &min, 0);
+ snd_pcm_hw_params_get_periods_max(params, &max, 0);
+ if (min && max) {
+ if (verbose)
+ fprintf(error_fp, "alsa: %s periods range between %u and %u. Want: %u\n",
+ id, min, max, *count);
+ if (*count < min)
+ *count = min;
+ if (*count > max)
+ *count = max;
+ }
+
+ min = max = 0;
+ snd_pcm_hw_params_get_period_time_min(params, &min, 0);
+ snd_pcm_hw_params_get_period_time_max(params, &max, 0);
+ if (min && max) {
+ if (verbose)
+ fprintf(error_fp, "alsa: %s period time range between %u and %u. Want: %u\n",
+ id, min, max, *usecs);
+ if (*usecs < min)
+ *usecs = min;
+ if (*usecs > max)
+ *usecs = max;
+ }
+}
+
+static int setparams_periods(snd_pcm_t *handle,
+ snd_pcm_hw_params_t *params,
+ unsigned int *usecs,
+ unsigned int *count,
+ const char *id)
+{
+ int err;
+
+ err = snd_pcm_hw_params_set_period_time_near(handle, params, usecs, 0);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set period time %u for %s: %s\n",
+ *usecs, id, snd_strerror(err));
+ return err;
+ }
+
+ err = snd_pcm_hw_params_set_periods_near(handle, params, count, 0);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set %u periods for %s: %s\n",
+ *count, id, snd_strerror(err));
+ return err;
+ }
+
+ if (verbose)
+ fprintf(error_fp, "alsa: %s period set to %u periods of %u time\n",
+ id, *count, *usecs);
+
+ return 0;
+}
+
+static int setparams_set(snd_pcm_t *handle,
+ snd_pcm_hw_params_t *params,
+ snd_pcm_sw_params_t *swparams,
+ snd_pcm_uframes_t start_treshold,
+ const char *id)
+{
+ int err;
+
+ err = snd_pcm_hw_params(handle, params);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set hw params for %s: %s\n",
+ id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_sw_params_current(handle, swparams);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to determine current swparams for %s: %s\n",
+ id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_sw_params_set_start_threshold(handle, swparams,
+ start_treshold);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set start threshold mode for %s: %s\n",
+ id, snd_strerror(err));
+ return err;
+ }
+
+ err = snd_pcm_sw_params_set_avail_min(handle, swparams, 4);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set avail min for %s: %s\n",
+ id, snd_strerror(err));
+ return err;
+ }
+ err = snd_pcm_sw_params(handle, swparams);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Unable to set sw params for %s: %s\n",
+ id, snd_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+static int alsa_try_rate(snd_pcm_t *phandle, snd_pcm_t *chandle,
+ snd_pcm_hw_params_t *p_hwparams,
+ snd_pcm_hw_params_t *c_hwparams,
+ int allow_resample, unsigned *ratep, unsigned *ratec)
+{
+ int err;
+
+ err = snd_pcm_hw_params_set_rate_near(chandle, c_hwparams, ratec, 0);
+ if (err)
+ return err;
+
+ *ratep = *ratec;
+ err = snd_pcm_hw_params_set_rate_near(phandle, p_hwparams, ratep, 0);
+ if (err)
+ return err;
+
+ if (*ratep == *ratec)
+ return 0;
+
+ if (verbose)
+ fprintf(error_fp,
+ "alsa_try_rate: capture wanted %u, playback wanted %u%s\n",
+ *ratec, *ratep, allow_resample ? " with resample enabled": "");
+
+ return 1; /* No error, but also no match */
+}
+
+static int setparams(snd_pcm_t *phandle, snd_pcm_t *chandle,
+ snd_pcm_format_t format,
+ int latency, int allow_resample,
+ struct final_params *negotiated)
+{
+ int i;
+ unsigned ratep, ratec = 0;
+ unsigned ratemin = 32000, ratemax = 96000, val;
+ int err, channels = 2;
+ snd_pcm_hw_params_t *p_hwparams, *c_hwparams;
+ snd_pcm_sw_params_t *p_swparams, *c_swparams;
+ snd_pcm_uframes_t c_size, p_psize, c_psize;
+ /* Our latency is 2 periods (in usecs) */
+ unsigned int c_periods = 2, p_periods;
+ unsigned int c_periodtime, p_periodtime;
+ const unsigned int prefered_rates[] = { 44100, 48000, 32000 };
+
+ snd_pcm_hw_params_alloca(&p_hwparams);
+ snd_pcm_hw_params_alloca(&c_hwparams);
+ snd_pcm_sw_params_alloca(&p_swparams);
+ snd_pcm_sw_params_alloca(&c_swparams);
+
+ if (setparams_stream(chandle, c_hwparams, format, &channels, "capture"))
+ return 1;
+
+ if (setparams_stream(phandle, p_hwparams, format, &channels, "playback"))
+ return 1;
+
+ if (allow_resample) {
+ err = snd_pcm_hw_params_set_rate_resample(chandle, c_hwparams, 1);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Resample setup failed: %s\n", snd_strerror(err));
+ return 1;
+ } else if (verbose)
+ fprintf(error_fp, "alsa: Resample enabled.\n");
+ }
+
+ err = snd_pcm_hw_params_get_rate_min(c_hwparams, &ratemin, 0);
+ if (err >= 0 && verbose)
+ fprintf(error_fp, "alsa: Capture min rate is %d\n", ratemin);
+ err = snd_pcm_hw_params_get_rate_max(c_hwparams, &ratemax, 0);
+ if (err >= 0 && verbose)
+ fprintf(error_fp, "alsa: Capture max rate is %u\n", ratemax);
+
+ err = snd_pcm_hw_params_get_rate_min(p_hwparams, &val, 0);
+ if (err >= 0) {
+ if (verbose)
+ fprintf(error_fp, "alsa: Playback min rate is %u\n", val);
+ if (val > ratemin)
+ ratemin = val;
+ }
+ err = snd_pcm_hw_params_get_rate_max(p_hwparams, &val, 0);
+ if (err >= 0) {
+ if (verbose)
+ fprintf(error_fp, "alsa: Playback max rate is %u\n", val);
+ if (val < ratemax)
+ ratemax = val;
+ }
+
+ if (verbose)
+ fprintf(error_fp,
+ "alsa: Will search a common rate between %u and %u\n",
+ ratemin, ratemax);
+
+ /* First try a set of common rates */
+ err = -1;
+ for (i = 0; i < ARRAY_SIZE(prefered_rates); i++) {
+ if (prefered_rates[i] < ratemin || prefered_rates[i] > ratemax)
+ continue;
+ ratep = ratec = prefered_rates[i];
+ err = alsa_try_rate(phandle, chandle, p_hwparams, c_hwparams,
+ allow_resample, &ratep, &ratec);
+ if (err == 0)
+ break;
+ }
+
+ if (err != 0) {
+ if (ratemin >= 44100) {
+ for (i = ratemin; i <= ratemax; i += 100) {
+ ratep = ratec = i;
+ err = alsa_try_rate(phandle, chandle, p_hwparams, c_hwparams,
+ allow_resample, &ratep, &ratec);
+ if (err == 0)
+ break;
+ }
+ } else {
+ for (i = ratemax; i >= ratemin; i -= 100) {
+ ratep = ratec = i;
+ err = alsa_try_rate(phandle, chandle, p_hwparams, c_hwparams,
+ allow_resample, &ratep, &ratec);
+ if (err == 0)
+ break;
+ }
+ }
+ }
+
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Failed to set a supported rate: %s\n",
+ snd_strerror(err));
+ return 1;
+ }
+ if (ratep != ratec) {
+ if (verbose || allow_resample)
+ fprintf(error_fp,
+ "alsa: Couldn't find a rate that it is supported by both playback and capture\n");
+ return 2;
+ }
+ if (verbose)
+ fprintf(error_fp, "alsa: Using Rate %d\n", ratec);
+
+ /* Negotiate period parameters */
+
+ c_periodtime = latency * 1000 / c_periods;
+ getparams_periods(chandle, c_hwparams, &c_periodtime, &c_periods, "capture");
+ p_periods = c_periods * 2;
+ p_periodtime = c_periodtime;
+ getparams_periods(phandle, p_hwparams, &p_periodtime, &p_periods, "playback");
+ c_periods = p_periods / 2;
+
+ /*
+ * Some playback devices support a very limited periodtime range. If the user needs to
+ * use a higher latency to avoid overrun/underrun, use an alternate algorithm of incresing
+ * the number of periods, to archive the needed latency
+ */
+ if (p_periodtime < c_periodtime) {
+ c_periodtime = p_periodtime;
+ c_periods = round (latency * 1000.0 / c_periodtime + 0.5);
+ getparams_periods(chandle, c_hwparams, &c_periodtime, &c_periods, "capture");
+ p_periods = c_periods * 2;
+ p_periodtime = c_periodtime;
+ getparams_periods(phandle, p_hwparams, &p_periodtime, &p_periods, "playback");
+ c_periods = p_periods / 2;
+ }
+
+ if (setparams_periods(chandle, c_hwparams, &c_periodtime, &c_periods, "capture"))
+ return 1;
+
+ /* Note we use twice as much periods for the playback buffer, since we
+ will get a period size near the requested time and we don't want it to
+ end up smaller then the capture buffer as then we could end up blocking
+ on writing to it. Note we will configure the playback dev to start
+ playing as soon as it has 2 capture periods worth of data, so this
+ won't influence latency */
+ if (setparams_periods(phandle, p_hwparams, &p_periodtime, &p_periods, "playback"))
+ return 1;
+
+ snd_pcm_hw_params_get_period_size(p_hwparams, &p_psize, NULL);
+ snd_pcm_hw_params_get_period_size(c_hwparams, &c_psize, NULL);
+ snd_pcm_hw_params_get_buffer_size(c_hwparams, &c_size);
+
+ latency = c_periods * c_psize;
+ if (setparams_set(phandle, p_hwparams, p_swparams, latency, "playback"))
+ return 1;
+
+ if (setparams_set(chandle, c_hwparams, c_swparams, c_psize, "capture"))
+ return 1;
+
+ if ((err = snd_pcm_prepare(phandle)) < 0) {
+ fprintf(error_fp, "alsa: Prepare error: %s\n", snd_strerror(err));
+ return 1;
+ }
+
+ if (verbose) {
+ fprintf(error_fp, "alsa: Negociated configuration:\n");
+ snd_pcm_dump_setup(phandle, output);
+ snd_pcm_dump_setup(chandle, output);
+ fprintf(error_fp, "alsa: Parameters are %iHz, %s, %i channels\n",
+ ratep, snd_pcm_format_name(format), channels);
+ fprintf(error_fp, "alsa: Set bitrate to %u%s, buffer size is %u\n", ratec,
+ allow_resample ? " with resample enabled at playback": "",
+ (unsigned int)c_size);
+ }
+
+ negotiated->bufsize = c_size;
+ negotiated->rate = ratep;
+ negotiated->channels = channels;
+ negotiated->latency = latency;
+ return 0;
+}
+
+/* Read up to len frames */
+static snd_pcm_sframes_t readbuf(snd_pcm_t *handle, char *buf, long len)
+{
+ snd_pcm_sframes_t r;
+
+ r = snd_pcm_readi(handle, buf, len);
+ if (r < 0 && r != -EAGAIN) {
+ r = snd_pcm_recover(handle, r, 0);
+ if (r < 0)
+ fprintf(error_fp, "alsa: overrun recover error: %s\n", snd_strerror(r));
+ }
+ return r;
+}
+
+/* Write len frames (note not up to len, but all of len!) */
+static snd_pcm_sframes_t writebuf(snd_pcm_t *handle, char *buf, long len)
+{
+ snd_pcm_sframes_t r;
+
+ while (1) {
+ r = snd_pcm_writei(handle, buf, len);
+ if (r == len)
+ return 0;
+ if (r < 0) {
+ r = snd_pcm_recover(handle, r, 0);
+ if (r < 0) {
+ fprintf(error_fp, "alsa: underrun recover error: %s\n",
+ snd_strerror(r));
+ return r;
+ }
+ }
+ buf += r * 4;
+ len -= r;
+ snd_pcm_wait(handle, 100);
+ }
+}
+
+static int alsa_stream(const char *pdevice, const char *cdevice, int latency)
+{
+ snd_pcm_t *phandle, *chandle;
+ char *buffer;
+ int err;
+ ssize_t r;
+ struct final_params negotiated;
+ snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
+ char pdevice_new[32];
+
+ err = snd_output_stdio_attach(&output, error_fp, 0);
+ if (err < 0) {
+ fprintf(error_fp, "alsa: Output failed: %s\n", snd_strerror(err));
+ return 0;
+ }
+
+ /* Open the devices */
+ if ((err = snd_pcm_open(&phandle, pdevice, SND_PCM_STREAM_PLAYBACK,
+ 0)) < 0) {
+ fprintf(error_fp, "alsa: Cannot open playback device %s: %s\n",
+ pdevice, snd_strerror(err));
+ return 0;
+ }
+ if ((err = snd_pcm_open(&chandle, cdevice, SND_PCM_STREAM_CAPTURE,
+ SND_PCM_NONBLOCK)) < 0) {
+ fprintf(error_fp, "alsa: Cannot open capture device %s: %s\n",
+ cdevice, snd_strerror(err));
+ snd_pcm_close(phandle);
+ return 0;
+ }
+
+ err = setparams(phandle, chandle, format, latency, 0, &negotiated);
+
+ /* Try to use plughw instead, as it allows emulating speed */
+ if (err == 2 && strncmp(pdevice, "hw", 2) == 0) {
+
+ snd_pcm_close(phandle);
+
+ sprintf(pdevice_new, "plug%s", pdevice);
+ pdevice = pdevice_new;
+ if (verbose)
+ fprintf(error_fp, "alsa: Trying %s for playback\n", pdevice);
+ if ((err = snd_pcm_open(&phandle, pdevice, SND_PCM_STREAM_PLAYBACK,
+ 0)) < 0) {
+ fprintf(error_fp, "alsa: Cannot open playback device %s: %s\n",
+ pdevice, snd_strerror(err));
+ snd_pcm_close(chandle);
+ return 0;
+ }
+
+ err = setparams(phandle, chandle, format, latency, 1, &negotiated);
+ }
+
+ if (err != 0) {
+ fprintf(error_fp, "alsa: setparams failed\n");
+ snd_pcm_close(phandle);
+ snd_pcm_close(chandle);
+ return 1;
+ }
+
+ buffer = malloc((negotiated.bufsize * snd_pcm_format_width(format) / 8)
+ * negotiated.channels);
+ if (buffer == NULL) {
+ fprintf(error_fp, "alsa: Failed allocating buffer for audio\n");
+ snd_pcm_close(phandle);
+ snd_pcm_close(chandle);
+ return 0;
+ }
+
+ if (verbose)
+ fprintf(error_fp,
+ "alsa: stream started from %s to %s (%i Hz, buffer delay = %.2f ms)\n",
+ cdevice, pdevice, negotiated.rate,
+ negotiated.latency * 1000.0 / negotiated.rate);
+
+ while (!stop_alsa) {
+ /* We start with a read and not a wait to auto(re)start the capture */
+ r = readbuf(chandle, buffer, negotiated.bufsize);
+ if (r == 0) /* Succesfully recovered from an overrun? */
+ continue; /* Force restart of capture stream */
+ if (r > 0)
+ writebuf(phandle, buffer, r);
+ /* use poll to wait for next event */
+ while (!stop_alsa && !snd_pcm_wait(chandle, 50))
+ ;
+ }
+
+ snd_pcm_drop(chandle);
+ snd_pcm_drop(phandle);
+
+ snd_pcm_unlink(chandle);
+ snd_pcm_hw_free(phandle);
+ snd_pcm_hw_free(chandle);
+
+ snd_pcm_close(phandle);
+ snd_pcm_close(chandle);
+
+ return 0;
+}
+
+struct input_params {
+ char *pdevice;
+ char *cdevice;
+ int latency;
+};
+
+static void *alsa_thread_entry(void *whatever)
+{
+ struct input_params *inputs = (struct input_params *) whatever;
+
+ if (verbose)
+ fprintf(error_fp, "alsa: starting copying alsa stream from %s to %s\n",
+ inputs->cdevice, inputs->pdevice);
+ alsa_stream(inputs->pdevice, inputs->cdevice, inputs->latency);
+ if (verbose)
+ fprintf(error_fp, "alsa: stream stopped\n");
+
+ free(inputs->pdevice);
+ free(inputs->cdevice);
+ free(inputs);
+
+ return NULL;
+}
+
+/*************************************************************************
+ Public functions
+ *************************************************************************/
+
+static int alsa_is_running = 0;
+static pthread_t alsa_thread;
+
+int alsa_thread_startup(const char *pdevice, const char *cdevice, int latency,
+ FILE *__error_fp, int __verbose)
+{
+ int ret;
+ struct input_params *inputs;
+
+ if ((strcasecmp(pdevice, "disabled") == 0) ||
+ (strcasecmp(cdevice, "disabled") == 0))
+ return 0;
+
+ if (__error_fp)
+ error_fp = __error_fp;
+ else
+ error_fp = stderr;
+
+ verbose = __verbose;
+
+ if (alsa_is_running) {
+ fprintf(error_fp, "alsa: Already running\n");
+ return EBUSY;
+ }
+
+ inputs = malloc(sizeof(struct input_params));
+ if (inputs == NULL) {
+ fprintf(error_fp, "alsa: failed allocating memory for inputs\n");
+ return ENOMEM;
+ }
+
+ inputs->pdevice = strdup(pdevice);
+ inputs->cdevice = strdup(cdevice);
+ inputs->latency = latency;
+
+ stop_alsa = 0;
+ ret = pthread_create(&alsa_thread, NULL,
+ &alsa_thread_entry, (void *) inputs);
+ if (ret == 0)
+ alsa_is_running = 1;
+
+ return ret;
+}
+
+void alsa_thread_stop(void)
+{
+ if (!alsa_is_running)
+ return;
+
+ stop_alsa = 1;
+ pthread_join(alsa_thread, NULL);
+ alsa_is_running = 0;
+}
+
+int alsa_thread_is_running(void)
+{
+ return alsa_is_running;
+}
+
+#endif
diff --git a/utils/qv4l2/alsa_stream.h b/utils/qv4l2/alsa_stream.h
new file mode 100644
index 0000000..c68fd6d
--- /dev/null
+++ b/utils/qv4l2/alsa_stream.h
@@ -0,0 +1,5 @@
+int alsa_thread_startup(const char *pdevice, const char *cdevice, int latency,
+ FILE *__error_fp,
+ int __verbose);
+void alsa_thread_stop(void);
+int alsa_thread_is_running(void);
--
1.8.3.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 5/5] qv4l2: add ALSA audio playback
2013-08-02 12:05 ` [PATCH 1/5] qv4l2: alter capture menu Bård Eirik Winther
` (2 preceding siblings ...)
2013-08-02 12:05 ` [PATCH 4/5] qv4l2: fix a bug where the alsa thread never stops Bård Eirik Winther
@ 2013-08-02 12:05 ` Bård Eirik Winther
3 siblings, 0 replies; 9+ messages in thread
From: Bård Eirik Winther @ 2013-08-02 12:05 UTC (permalink / raw)
To: linux-media
The qv4l2 test utility now supports ALSA playback of audio.
This allows for PCM playback during capture for supported devices.
Signed-off-by: Bård Eirik Winther <bwinther@cisco.com>
---
utils/qv4l2/general-tab.cpp | 296 +++++++++++++++++++++++++++++++++++++++++++-
utils/qv4l2/general-tab.h | 36 ++++++
utils/qv4l2/qv4l2.cpp | 143 ++++++++++++++++++++-
utils/qv4l2/qv4l2.h | 7 ++
4 files changed, 478 insertions(+), 4 deletions(-)
diff --git a/utils/qv4l2/general-tab.cpp b/utils/qv4l2/general-tab.cpp
index 10b14ca..5996c03 100644
--- a/utils/qv4l2/general-tab.cpp
+++ b/utils/qv4l2/general-tab.cpp
@@ -30,6 +30,16 @@
#include <stdio.h>
#include <errno.h>
+#include <QRegExp>
+
+bool GeneralTab::m_fullAudioName = false;
+
+enum audioDeviceAdd {
+ AUDIO_ADD_NO,
+ AUDIO_ADD_READ,
+ AUDIO_ADD_WRITE,
+ AUDIO_ADD_READWRITE
+};
GeneralTab::GeneralTab(const QString &device, v4l2 &fd, int n, QWidget *parent) :
QGridLayout(parent),
@@ -48,12 +58,16 @@ GeneralTab::GeneralTab(const QString &device, v4l2 &fd, int n, QWidget *parent)
m_vidCapFormats(NULL),
m_frameSize(NULL),
m_vidOutFormats(NULL),
- m_vbiMethods(NULL)
+ m_vbiMethods(NULL),
+ m_audioInDevice(NULL),
+ m_audioOutDevice(NULL)
{
+ m_device.append(device);
setSpacing(3);
setSizeConstraint(QLayout::SetMinimumSize);
+
if (querycap(m_querycap)) {
addLabel("Device:");
addLabel(device + (useWrapper() ? " (wrapped)" : ""), Qt::AlignLeft);
@@ -132,6 +146,42 @@ GeneralTab::GeneralTab(const QString &device, v4l2 &fd, int n, QWidget *parent)
updateAudioOutput();
}
+ if (hasAlsaAudio()) {
+ m_audioInDevice = new QComboBox(parent);
+ m_audioOutDevice = new QComboBox(parent);
+ m_audioInDevice->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+ m_audioOutDevice->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+ if (createAudioDeviceList()) {
+ addLabel("Audio Input Device");
+ connect(m_audioInDevice, SIGNAL(activated(int)), SLOT(changeAudioDevice()));
+ addWidget(m_audioInDevice);
+
+ addLabel("Audio Output Device");
+ connect(m_audioOutDevice, SIGNAL(activated(int)), SLOT(changeAudioDevice()));
+ addWidget(m_audioOutDevice);
+
+ if (isRadio()) {
+ setAudioDeviceBufferSize(75);
+ } else {
+ v4l2_fract fract;
+ if (!v4l2::get_interval(fract)) {
+ // Default values are for 30 FPS
+ fract.numerator = 33;
+ fract.denominator = 1000;
+ }
+ // Standard capacity is two frames
+ setAudioDeviceBufferSize((fract.numerator * 2000) / fract.denominator);
+ }
+ } else {
+ fprintf(stderr, "BANNA\n");
+ delete m_audioInDevice;
+ delete m_audioOutDevice;
+ m_audioInDevice = NULL;
+ m_audioOutDevice = NULL;
+ }
+ }
+
if (needsStd) {
v4l2_std_id tmp;
@@ -370,6 +420,180 @@ done:
setRowStretch(rowCount() - 1, 1);
}
+void GeneralTab::showAllAudioDevices(bool use)
+{
+ QString oldIn(m_audioInDevice->currentText());
+ QString oldOut(m_audioOutDevice->currentText());
+
+ m_fullAudioName = use;
+ if (oldIn == NULL || oldOut == NULL || !createAudioDeviceList())
+ return;
+
+ // Select a similar device as before the listings method change
+ // check by comparing old selection with any matching in the new list
+ bool setIn = false, setOut = false;
+ int listSize = std::max(m_audioInDevice->count(), m_audioOutDevice->count());
+
+ for (int i = 0; i < listSize; i++) {
+ QString oldInCmp(oldIn.left(std::min(m_audioInDevice->itemText(i).length(), oldIn.length())));
+ QString oldOutCmp(oldOut.left(std::min(m_audioOutDevice->itemText(i).length(), oldOut.length())));
+
+ if (!setIn && i < m_audioInDevice->count()
+ && m_audioInDevice->itemText(i).startsWith(oldInCmp)) {
+ setIn = true;
+ m_audioInDevice->setCurrentIndex(i);
+ }
+
+ if (!setOut && i < m_audioOutDevice->count()
+ && m_audioOutDevice->itemText(i).startsWith(oldOutCmp)) {
+ setOut = true;
+ m_audioOutDevice->setCurrentIndex(i);
+ }
+ }
+}
+
+bool GeneralTab::filterAudioInDevice(QString &deviceName)
+{
+ // Removes S/PDIF, front speakers and surround from input devices
+ // as they are output devices, not input
+ if (deviceName.contains("surround")
+ || deviceName.contains("front")
+ || deviceName.contains("iec958"))
+ return false;
+
+ // Removes sysdefault too if not full audio mode listings
+ if (!m_fullAudioName && deviceName.contains("sysdefault"))
+ return false;
+
+ return true;
+}
+
+bool GeneralTab::filterAudioOutDevice(QString &deviceName)
+{
+ // Removes advanced options if not full audio mode listings
+ if (!m_fullAudioName && (deviceName.contains("surround")
+ || deviceName.contains("front")
+ || deviceName.contains("iec958")
+ || deviceName.contains("sysdefault"))) {
+ return false;
+ }
+
+ return true;
+}
+
+int GeneralTab::addAudioDevice(void *hint, int deviceNum)
+{
+ int added = 0;
+#ifdef ENABLE_ALSA
+ char *name;
+ char *iotype;
+ QString deviceName;
+ QString listName;
+ QStringList deviceType;
+ iotype = snd_device_name_get_hint(hint, "IOID");
+ name = snd_device_name_get_hint(hint, "NAME");
+ deviceName.append(name);
+
+ snd_card_get_name(deviceNum, &name);
+ listName.append(name);
+
+ deviceType = deviceName.split(":");
+
+ // Add device io capability to list name
+ if (m_fullAudioName) {
+ listName.append(" ");
+
+ // Makes the surround name more readable
+ if (deviceName.contains("surround"))
+ listName.append(QString("surround %1.%2")
+ .arg(deviceType.value(0)[8]).arg(deviceType.value(0)[9]));
+ else
+ listName.append(deviceType.value(0));
+
+ } else if (!deviceType.value(0).contains("default")) {
+ listName.append(" ").append(deviceType.value(0));
+ }
+
+ // Add device number if it is not 0
+ if (deviceName.contains("DEV=")) {
+ int devNo;
+ QStringList deviceNo = deviceName.split("DEV=");
+ devNo = deviceNo.value(1).toInt();
+ if (devNo)
+ listName.append(QString(" %1").arg(devNo));
+ }
+
+ if ((iotype == NULL || strncmp(iotype, "Input", 5) == 0) && filterAudioInDevice(deviceName)) {
+ m_audioInDevice->addItem(listName);
+ m_audioInDeviceMap[listName] = snd_device_name_get_hint(hint, "NAME");
+ added += AUDIO_ADD_READ;
+ }
+
+ if ((iotype == NULL || strncmp(iotype, "Output", 6) == 0) && filterAudioOutDevice(deviceName)) {
+ m_audioOutDevice->addItem(listName);
+ m_audioOutDeviceMap[listName] = snd_device_name_get_hint(hint, "NAME");
+ added += AUDIO_ADD_WRITE;
+ }
+#endif
+ return added;
+}
+
+bool GeneralTab::createAudioDeviceList()
+{
+#ifdef ENABLE_ALSA
+ if (m_audioInDevice == NULL || m_audioOutDevice == NULL)
+ return false;
+
+ m_audioInDevice->clear();
+ m_audioOutDevice->clear();
+ m_audioInDeviceMap.clear();
+ m_audioOutDeviceMap.clear();
+
+ m_audioInDevice->addItem("None");
+ m_audioOutDevice->addItem("Default");
+ m_audioInDeviceMap["None"] = "None";
+ m_audioOutDeviceMap["Default"] = "default";
+
+ int deviceNum = -1;
+ int audioDevices = 0;
+ int matchDevice = matchAudioDevice();
+ int indexDevice = -1;
+ int indexCount = 0;
+
+ while (snd_card_next(&deviceNum) >= 0) {
+ if (deviceNum == -1)
+ break;
+
+ audioDevices++;
+ if (deviceNum == matchDevice && indexDevice == -1)
+ indexDevice = indexCount;
+
+ void **hint;
+
+ snd_device_name_hint(deviceNum, "pcm", &hint);
+ for (int i = 0; hint[i] != NULL; i++) {
+ int addAs = addAudioDevice(hint[i], deviceNum);
+ if (addAs == AUDIO_ADD_READ || addAs == AUDIO_ADD_READWRITE)
+ indexCount++;
+ }
+ snd_device_name_free_hint(hint);
+ }
+
+ snd_config_update_free_global();
+ m_audioInDevice->setCurrentIndex(indexDevice + 1);
+ changeAudioDevice();
+ return m_audioInDeviceMap.size() > 1 && m_audioOutDeviceMap.size() > 1 && audioDevices > 1;
+#else
+ return false;
+#endif
+}
+
+void GeneralTab::changeAudioDevice()
+{
+ m_audioOutDevice->setEnabled(getAudioInDevice() != NULL ? getAudioInDevice().compare("None") : false);
+ emit audioDeviceChanged();
+}
+
void GeneralTab::addWidget(QWidget *w, Qt::Alignment align)
{
QGridLayout::addWidget(w, m_row, m_col, align | Qt::AlignVCenter);
@@ -932,3 +1156,73 @@ bool GeneralTab::get_interval(struct v4l2_fract &interval)
return m_has_interval;
}
+
+QString GeneralTab::getAudioInDevice()
+{
+ if (m_audioInDevice == NULL)
+ return NULL;
+
+ return m_audioInDeviceMap[m_audioInDevice->currentText()];
+}
+
+QString GeneralTab::getAudioOutDevice()
+{
+ if (m_audioOutDevice == NULL)
+ return NULL;
+
+ return m_audioOutDeviceMap[m_audioOutDevice->currentText()];
+}
+
+void GeneralTab::setAudioDeviceBufferSize(int size)
+{
+ m_audioDeviceBufferSize = size;
+}
+
+int GeneralTab::getAudioDeviceBufferSize()
+{
+ return m_audioDeviceBufferSize;
+}
+
+#ifdef ENABLE_ALSA
+int GeneralTab::checkMatchAudioDevice(void *md, const char *vid, const enum device_type type)
+{
+ const char *devname = NULL;
+
+ while ((devname = get_associated_device(md, devname, type, vid, MEDIA_V4L_VIDEO)) != NULL) {
+ if (type == MEDIA_SND_CAP) {
+ QStringList devAddr = QString(devname).split(QRegExp("[:,]"));
+ return devAddr.value(1).toInt();
+ }
+ }
+ return -1;
+}
+
+int GeneralTab::matchAudioDevice()
+{
+ QStringList devPath = m_device.split("/");
+ QString curDev = devPath.value(devPath.count() - 1);
+
+ void *media;
+ const char *video = NULL;
+ int match;
+
+ media = discover_media_devices();
+
+ while ((video = get_associated_device(media, video, MEDIA_V4L_VIDEO, NULL, NONE)) != NULL)
+ if (curDev.compare(video) == 0)
+ for (int i = 0; i <= MEDIA_SND_HW; i++)
+ if ((match = checkMatchAudioDevice(media, video, static_cast<device_type>(i))) != -1)
+ return match;
+
+ return -1;
+}
+#endif
+
+bool GeneralTab::hasAlsaAudio()
+{
+#ifdef ENABLE_ALSA
+ return !isVbi();
+#else
+ return false;
+#endif
+}
diff --git a/utils/qv4l2/general-tab.h b/utils/qv4l2/general-tab.h
index 5903ed8..c83368a 100644
--- a/utils/qv4l2/general-tab.h
+++ b/utils/qv4l2/general-tab.h
@@ -24,9 +24,18 @@
#include <QSpinBox>
#include <sys/time.h>
#include <linux/videodev2.h>
+#include <map>
#include "qv4l2.h"
#include "v4l2-api.h"
+#ifdef ENABLE_ALSA
+extern "C" {
+#include "../libmedia_dev/get_media_devices.h"
+#include "alsa_stream.h"
+}
+#include <alsa/asoundlib.h>
+#endif
+
class QComboBox;
class QCheckBox;
class QSpinBox;
@@ -41,6 +50,11 @@ public:
virtual ~GeneralTab() {}
CapMethod capMethod();
+ QString getAudioInDevice();
+ QString getAudioOutDevice();
+ void setAudioDeviceBufferSize(int size);
+ int getAudioDeviceBufferSize();
+ bool hasAlsaAudio();
bool get_interval(struct v4l2_fract &interval);
int width() const { return m_width; }
int height() const { return m_height; }
@@ -69,6 +83,12 @@ public:
inline bool streamon() { return v4l2::streamon(m_buftype); }
inline bool streamoff() { return v4l2::streamoff(m_buftype); }
+public slots:
+ void showAllAudioDevices(bool use);
+
+signals:
+ void audioDeviceChanged();
+
private slots:
void inputChanged(int);
void outputChanged(int);
@@ -92,6 +112,7 @@ private slots:
void frameIntervalChanged(int);
void vidOutFormatChanged(int);
void vbiMethodsChanged(int);
+ void changeAudioDevice();
private:
void updateVideoInput();
@@ -108,6 +129,14 @@ private:
void updateFrameSize();
void updateFrameInterval();
void updateVidOutFormat();
+ int addAudioDevice(void *hint, int deviceNum);
+ bool filterAudioInDevice(QString &deviceName);
+ bool filterAudioOutDevice(QString &deviceName);
+ bool createAudioDeviceList();
+#ifdef ENABLE_ALSA
+ int matchAudioDevice();
+ int checkMatchAudioDevice(void *md, const char *vid, const enum device_type type);
+#endif
void addWidget(QWidget *w, Qt::Alignment align = Qt::AlignLeft);
void addLabel(const QString &text, Qt::Alignment align = Qt::AlignRight)
@@ -130,6 +159,7 @@ private:
bool m_isVbi;
__u32 m_buftype;
__u32 m_audioModes[5];
+ QString m_device;
struct v4l2_tuner m_tuner;
struct v4l2_modulator m_modulator;
struct v4l2_capability m_querycap;
@@ -137,6 +167,10 @@ private:
__u32 m_width, m_height;
struct v4l2_fract m_interval;
bool m_has_interval;
+ int m_audioDeviceBufferSize;
+ static bool m_fullAudioName;
+ std::map<QString, QString> m_audioInDeviceMap;
+ std::map<QString, QString> m_audioOutDeviceMap;
// General tab
QComboBox *m_videoInput;
@@ -163,6 +197,8 @@ private:
QComboBox *m_vidOutFormats;
QComboBox *m_capMethods;
QComboBox *m_vbiMethods;
+ QComboBox *m_audioInDevice;
+ QComboBox *m_audioOutDevice;
};
#endif
diff --git a/utils/qv4l2/qv4l2.cpp b/utils/qv4l2/qv4l2.cpp
index 275b399..e078e91 100644
--- a/utils/qv4l2/qv4l2.cpp
+++ b/utils/qv4l2/qv4l2.cpp
@@ -24,6 +24,12 @@
#include "capture-win-qt.h"
#include "capture-win-gl.h"
+#ifdef ENABLE_ASLA
+extern "C" {
+#include "alsa_stream.h"
+}
+#endif
+
#include <QToolBar>
#include <QToolButton>
#include <QMenuBar>
@@ -45,15 +51,18 @@
#include <QWhatsThis>
#include <QThread>
#include <QCloseEvent>
+#include <QInputDialog>
#include <assert.h>
#include <sys/mman.h>
+#include <sys/time.h>
#include <errno.h>
#include <dirent.h>
#include <libv4l2.h>
ApplicationWindow::ApplicationWindow() :
m_capture(NULL),
+ m_genTab(NULL),
m_sigMapper(NULL)
{
setAttribute(Qt::WA_DeleteOnClose, true);
@@ -76,7 +85,7 @@ ApplicationWindow::ApplicationWindow() :
openRawAct->setShortcut(Qt::CTRL+Qt::Key_R);
connect(openRawAct, SIGNAL(triggered()), this, SLOT(openrawdev()));
- m_capStartAct = new QAction(QIcon(":/record.png"), "&Start Capturing", this);
+ m_capStartAct = new QAction(QIcon(":/record.png"), "Start &Capturing", this);
m_capStartAct->setStatusTip("Start capturing");
m_capStartAct->setCheckable(true);
m_capStartAct->setDisabled(true);
@@ -145,6 +154,21 @@ ApplicationWindow::ApplicationWindow() :
m_renderMethod = QV4L2_RENDER_QT;
}
+#ifdef ENABLE_ALSA
+ captureMenu->addSeparator();
+
+ m_showAllAudioAct = new QAction("Show All Audio Devices", this);
+ m_showAllAudioAct->setStatusTip("Show all audio input and output devices if set");
+ m_showAllAudioAct->setCheckable(true);
+ m_showAllAudioAct->setChecked(false);
+ captureMenu->addAction(m_showAllAudioAct);
+
+ m_audioBufferAct = new QAction("Set Audio Buffer Size...", this);
+ m_audioBufferAct->setStatusTip("Set audio buffer capacity in amout of ms than can be stored");
+ connect(m_audioBufferAct, SIGNAL(triggered()), this, SLOT(setAudioBufferSize()));
+ captureMenu->addAction(m_audioBufferAct);
+#endif
+
QMenu *helpMenu = menuBar()->addMenu("&Help");
helpMenu->addAction("&About", this, SLOT(about()), Qt::Key_F1);
@@ -172,8 +196,11 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
m_sigMapper = new QSignalMapper(this);
connect(m_sigMapper, SIGNAL(mapped(int)), this, SLOT(ctrlAction(int)));
- if (!open(device, !rawOpen))
+ if (!open(device, !rawOpen)) {
+ m_showAllAudioAct->setEnabled(false);
+ m_audioBufferAct->setEnabled(false);
return;
+ }
newCaptureWin();
@@ -181,6 +208,19 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
QWidget *w = new QWidget(m_tabs);
m_genTab = new GeneralTab(device, *this, 4, w);
+
+#ifdef ENABLE_ALSA
+ if (m_genTab->hasAlsaAudio()) {
+ connect(m_showAllAudioAct, SIGNAL(toggled(bool)), m_genTab, SLOT(showAllAudioDevices(bool)));
+ connect(m_genTab, SIGNAL(audioDeviceChanged()), this, SLOT(changeAudioDevice()));
+ m_showAllAudioAct->setEnabled(true);
+ m_audioBufferAct->setEnabled(true);
+ } else {
+ m_showAllAudioAct->setEnabled(false);
+ m_audioBufferAct->setEnabled(false);
+ }
+#endif
+
m_tabs->addTab(w, "General");
addTabs();
if (caps() & (V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE)) {
@@ -195,7 +235,7 @@ void ApplicationWindow::setDevice(const QString &device, bool rawOpen)
m_tabs->show();
m_tabs->setFocus();
m_convertData = v4lconvert_create(fd());
- m_capStartAct->setEnabled(fd() >= 0 && !m_genTab->isRadio());
+ m_capStartAct->setEnabled(fd() >= 0);
m_ctrlNotifier = new QSocketNotifier(fd(), QSocketNotifier::Exception, m_tabs);
connect(m_ctrlNotifier, SIGNAL(activated(int)), this, SLOT(ctrlEvent()));
}
@@ -235,6 +275,19 @@ void ApplicationWindow::setRenderMethod()
newCaptureWin();
}
+void ApplicationWindow::setAudioBufferSize()
+{
+ bool ok;
+ int buffer = QInputDialog::getInt(this, "Audio Device Buffer Size", "Capacity in ms:",
+ m_genTab->getAudioDeviceBufferSize(), 1, 65535, 1, &ok);
+
+ if (ok) {
+ m_genTab->setAudioDeviceBufferSize(buffer);
+ changeAudioDevice();
+ }
+}
+
+
void ApplicationWindow::ctrlEvent()
{
v4l2_event ev;
@@ -413,12 +466,18 @@ void ApplicationWindow::capFrame()
int s = 0;
int err = 0;
bool again;
+#ifdef ENABLE_ALSA
+ struct timeval tv_alsa;
+#endif
unsigned char *displaybuf = NULL;
switch (m_capMethod) {
case methodRead:
s = read(m_frameData, m_capSrcFormat.fmt.pix.sizeimage);
+#ifdef ENABLE_ALSA
+ alsa_thread_timestamp(&tv_alsa);
+#endif
if (s < 0) {
if (errno != EAGAIN) {
error("read");
@@ -449,6 +508,9 @@ void ApplicationWindow::capFrame()
m_capStartAct->setChecked(false);
return;
}
+#ifdef ENABLE_ALSA
+ alsa_thread_timestamp(&tv_alsa);
+#endif
if (again)
return;
@@ -475,6 +537,10 @@ void ApplicationWindow::capFrame()
m_capStartAct->setChecked(false);
return;
}
+#ifdef ENABLE_ALSA
+ alsa_thread_timestamp(&tv_alsa);
+#endif
+
if (again)
return;
@@ -511,9 +577,24 @@ void ApplicationWindow::capFrame()
m_lastFrame = m_frame;
m_tv = tv;
}
+
+
status = QString("Frame: %1 Fps: %2").arg(++m_frame).arg(m_fps);
+#ifdef ENABLE_ALSA
+ if (alsa_thread_is_running()) {
+ if (tv_alsa.tv_sec || tv_alsa.tv_usec) {
+ m_totalAudioLatency.tv_sec += buf.timestamp.tv_sec - tv_alsa.tv_sec;
+ m_totalAudioLatency.tv_usec += buf.timestamp.tv_usec - tv_alsa.tv_usec;
+ }
+ //m_totalAudioLatency.tv_sec = tv_alsa.tv_sec;
+ //m_totalAudioLatency.tv_usec = tv_alsa.tv_usec;
+ status.append(QString(" Average A-V: %3 ms")
+ .arg((m_totalAudioLatency.tv_sec * 1000 + m_totalAudioLatency.tv_usec / 1000) / m_frame));
+ }
+#endif
if (displaybuf == NULL && m_showFrames)
status.append(" Error: Unsupported format.");
+
if (m_showFrames)
m_capture->setFrame(m_capImage->width(), m_capImage->height(),
m_capDestFormat.fmt.pix.pixelformat, displaybuf, status);
@@ -530,6 +611,11 @@ void ApplicationWindow::capFrame()
bool ApplicationWindow::startCapture(unsigned buffer_size)
{
+ startAudio();
+
+ if (m_genTab->isRadio())
+ return true;
+
__u32 buftype = m_genTab->bufType();
v4l2_requestbuffers req;
unsigned int i;
@@ -645,6 +731,11 @@ error:
void ApplicationWindow::stopCapture()
{
+ stopAudio();
+
+ if (m_genTab->isRadio())
+ return;
+
__u32 buftype = m_genTab->bufType();
v4l2_requestbuffers reqbufs;
v4l2_encoder_cmd cmd;
@@ -695,6 +786,42 @@ void ApplicationWindow::stopOutput()
{
}
+void ApplicationWindow::startAudio()
+{
+#ifdef ENABLE_ALSA
+ m_totalAudioLatency.tv_sec = 0;
+ m_totalAudioLatency.tv_usec = 0;
+
+ QString audIn = m_genTab->getAudioInDevice();
+ QString audOut = m_genTab->getAudioOutDevice();
+
+ if (audIn != NULL && audOut != NULL && audIn.compare("None") && audIn.compare(audOut) != 0) {
+ alsa_thread_startup(audOut.toAscii().data(), audIn.toAscii().data(),
+ m_genTab->getAudioDeviceBufferSize(), NULL, 0);
+
+ if (m_genTab->isRadio())
+ statusBar()->showMessage("Capturing audio");
+ }
+#endif
+}
+
+void ApplicationWindow::stopAudio()
+{
+#ifdef ENABLE_ALSA
+ if (m_genTab != NULL && m_genTab->isRadio())
+ statusBar()->showMessage("");
+ alsa_thread_stop();
+#endif
+}
+
+void ApplicationWindow::changeAudioDevice()
+{
+ stopAudio();
+ if (m_capStartAct->isChecked()) {
+ startAudio();
+ }
+}
+
void ApplicationWindow::closeCaptureWin()
{
m_capStartAct->setChecked(false);
@@ -702,6 +829,15 @@ void ApplicationWindow::closeCaptureWin()
void ApplicationWindow::capStart(bool start)
{
+ if (m_genTab->isRadio()) {
+ if (start)
+ startCapture(0);
+ else
+ stopCapture();
+
+ return;
+ }
+
QImage::Format dstFmt = QImage::Format_RGB888;
struct v4l2_fract interval;
v4l2_pix_format &srcPix = m_capSrcFormat.fmt.pix;
@@ -821,6 +957,7 @@ void ApplicationWindow::capStart(bool start)
void ApplicationWindow::closeDevice()
{
+ stopAudio();
delete m_sigMapper;
m_sigMapper = NULL;
m_capStartAct->setEnabled(false);
diff --git a/utils/qv4l2/qv4l2.h b/utils/qv4l2/qv4l2.h
index 2921b16..223db75 100644
--- a/utils/qv4l2/qv4l2.h
+++ b/utils/qv4l2/qv4l2.h
@@ -98,6 +98,8 @@ private:
void startOutput(unsigned buffer_size);
void stopOutput();
void newCaptureWin();
+ void startAudio();
+ void stopAudio();
struct buffer *m_buffers;
struct v4l2_format m_capSrcFormat;
@@ -118,6 +120,7 @@ private slots:
void capVbiFrame();
void saveRaw(bool);
void setRenderMethod();
+ void changeAudioDevice();
// gui
private slots:
@@ -126,6 +129,7 @@ private slots:
void ctrlAction(int);
void openRawFile(const QString &s);
void rejectedRawFile();
+ void setAudioBufferSize();
void about();
@@ -176,6 +180,8 @@ private:
QAction *m_saveRawAct;
QAction *m_showFramesAct;
QAction *m_useGLAct;
+ QAction *m_showAllAudioAct;
+ QAction *m_audioBufferAct;
QString m_filename;
QSignalMapper *m_sigMapper;
QTabWidget *m_tabs;
@@ -196,6 +202,7 @@ private:
unsigned m_lastFrame;
unsigned m_fps;
struct timeval m_tv;
+ struct timeval m_totalAudioLatency;
QFile m_saveRaw;
};
--
1.8.3.2
^ permalink raw reply related [flat|nested] 9+ messages in thread