[alsa-devel] [PATCH v1 0/4] *** add XRUN injections suppport in alsabat ***
The patch set add a new feature in alsabat, now alsabat can test XRUNs for both playback and capture by writing to proc file '/proc/sound/card*/pcm*/sub*/xrun_injection'.
Zhang Keqiao (4): alsabat: add one option for XRUN injections alsabat: parse the card and PCM ID for XRUN injections path alsabat: add XRUN injections function alsabat: XRUN injections test criteria
bat/analyze.c | 22 ++++++-- bat/bat.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- bat/common.h | 13 +++++ 3 files changed, 202 insertions(+), 4 deletions(-)
This patch add a new option '-X' to alsabat, this option allows alsabat to do the xrun injection with given period. eg. '-X 100' means do XRUN injections every 100ms for both playback and capture. '-X 100p/c' means do XRUN injections every 100ms for playback/capture.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com --- bat/bat.c | 9 ++++++++- bat/common.h | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/bat/bat.c b/bat/bat.c index 8645770..bf6873a 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -328,6 +328,7 @@ _("Usage: alsabat [-options]...\n" " -p total number of periods to play/capture\n" " -B buffer size in frames\n" " -E period size in frames\n" +" -X period of xrun injection, given in millisecond\n" " --log=# file that both stdout and strerr redirecting to\n" " --file=# file for playback\n" " --saveplay=# file that storing playback content, for debug\n" @@ -367,6 +368,9 @@ static void set_defaults(struct bat *bat) bat->buffer_size = 0; bat->period_size = 0; bat->roundtriplatency = false; + bat->xrun_period = 0; + bat->xrun_playback = 0; + bat->xrun_capture = 0; #ifdef HAVE_LIBTINYALSA bat->channels = 2; bat->playback.fct = &playback_tinyalsa; @@ -386,7 +390,7 @@ static void set_defaults(struct bat *bat) static void parse_arguments(struct bat *bat, int argc, char *argv[]) { int c, option_index, err; - static const char short_options[] = "D:P:C:f:n:F:c:r:s:k:p:B:E:lth"; + static const char short_options[] = "D:P:C:f:n:F:c:r:s:k:p:B:E:X:lth"; static const struct option long_options[] = { {"help", 0, 0, 'h'}, {"log", 1, 0, OPT_LOG}, @@ -479,6 +483,9 @@ static void parse_arguments(struct bat *bat, int argc, char *argv[]) bat->period_size = err >= MIN_PERIODSIZE && err < MAX_PERIODSIZE ? err : 0; break; + case 'X': + bat->xarg = optarg; + break; case 'h': default: usage(bat); diff --git a/bat/common.h b/bat/common.h index 1b07fbe..a014f87 100644 --- a/bat/common.h +++ b/bat/common.h @@ -214,6 +214,9 @@ struct bat { enum _bat_pcm_format format; /* PCM format */ int buffer_size; /* buffer size in frames */ int period_size; /* period size in frames */ + int xrun_playback; /* enable xrun injection for playback */ + int xrun_capture; /* enable xrun injection for capture */ + int xrun_period; /* period of XRUN injections, given in milliseconds */
float sigma_k; /* threshold for peak detection */ float snr_thd_db; /* threshold for noise detection (dB) */ @@ -221,6 +224,7 @@ struct bat {
int sinus_duration; /* number of frames for playback */ char *narg; /* argument string of duration */ + char *xarg; /* argument string of xrun injection */ char *logarg; /* path name of log file */ char *debugplay; /* path name to store playback signal */ bool standalone; /* enable to bypass analysis */
This patch intends to parse the card ID and PCM ID of playback and capture, then replace it in the path macro.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com --- bat/bat.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.h | 3 +++ 2 files changed, 72 insertions(+)
diff --git a/bat/bat.c b/bat/bat.c index bf6873a..b12e5d3 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -127,6 +127,69 @@ static void get_sine_frequencies(struct bat *bat, char *freq) } }
+/* parse the card and pcm ID of playback device and capture device */ +/* Then we can get the path for specify device to do the XRUN injection */ +static int parse_card_and_pcm_id(struct bat *bat) +{ + char *tmp1; + char *tmp2; + char playback_device[MAX_DEV]; + char capture_device[MAX_DEV]; + + strcpy(playback_device, bat->playback.device); + strcpy(capture_device, bat->capture.device); + + if (bat->playback.device == bat->capture.device) { + tmp1 = strchr(playback_device, ','); + if (tmp1 != NULL) { + *tmp1 = '\0'; + bat->playback.pcm_id = bat->capture.pcm_id = (tmp1 + 1); + } else { + fprintf(bat->err, _("err, cannot find the pcm ID\n")); + return -EINVAL; + } + tmp2 = strchr(playback_device, ':'); + if (tmp2 != NULL) { + bat->playback.card_id = bat->capture.card_id = (tmp2 + 1); + } else { + fprintf(bat->err, _("err, cannot find the card ID\n")); + return -EINVAL; + } + } else { + tmp1 = strchr(playback_device, ','); + if (tmp1 != NULL) { + *tmp1 = '\0'; + bat->playback.pcm_id = (tmp1 + 1); + } else { + fprintf(bat->err, _("err, cannot find the pcm ID of playback\n")); + return -EINVAL; + } + tmp2 = strchr(playback_device, ':'); + if (tmp2 != NULL) + bat->playback.card_id = (tmp2 + 1); + else { + fprintf(bat->err, _("err, cannot find the card ID of playback\n")); + return -EINVAL; + } + tmp1 = strchr(capture_device, ','); + if (tmp1 != NULL) { + *tmp1 = '\0'; + bat->capture.pcm_id = (tmp1 + 1); + } else { + fprintf(bat->err, _("err, cannot find the pcm ID of capture")); + return -EINVAL; + } + tmp2 = strchr(capture_device, ':'); + if (tmp2 != NULL) + bat->capture.card_id = (tmp2 + 1); + else { + fprintf(bat->err, _("err, cannot find the card ID of capture")); + return -EINVAL; + } + } + return 0; +} + static void get_format(struct bat *bat, char *optarg) { if (strcasecmp(optarg, "cd") == 0) { @@ -561,6 +624,12 @@ static int bat_init(struct bat *bat) return err; }
+ if (bat->xarg) { + err = parse_card_and_pcm_id(bat); + if (err < 0) + return err; + } + /* Set default playback and capture devices */ if (bat->playback.device == NULL && bat->capture.device == NULL) bat->playback.device = bat->capture.device = DEFAULT_DEV_NAME; diff --git a/bat/common.h b/bat/common.h index a014f87..9f71c6c 100644 --- a/bat/common.h +++ b/bat/common.h @@ -16,6 +16,7 @@ #define TEMP_RECORD_FILE_NAME "/tmp/bat.wav.XXXXXX" #define DEFAULT_DEV_NAME "default"
+#define MAX_DEV 15 #define OPT_BASE 300 #define OPT_LOG (OPT_BASE + 1) #define OPT_READFILE (OPT_BASE + 2) @@ -167,6 +168,8 @@ struct pcm { unsigned int device_tiny; char *device; char *file; + char *pcm_id; + char *card_id; enum _bat_op_mode mode; void *(*fct)(struct bat *); };
On Thu, 31 Aug 2017 15:37:07 +0200, Zhang Keqiao wrote:
This patch intends to parse the card ID and PCM ID of playback and capture, then replace it in the path macro.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com
bat/bat.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.h | 3 +++ 2 files changed, 72 insertions(+)
diff --git a/bat/bat.c b/bat/bat.c index bf6873a..b12e5d3 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -127,6 +127,69 @@ static void get_sine_frequencies(struct bat *bat, char *freq) } }
+/* parse the card and pcm ID of playback device and capture device */ +/* Then we can get the path for specify device to do the XRUN injection */ +static int parse_card_and_pcm_id(struct bat *bat) +{
- char *tmp1;
- char *tmp2;
- char playback_device[MAX_DEV];
- char capture_device[MAX_DEV];
- strcpy(playback_device, bat->playback.device);
- strcpy(capture_device, bat->capture.device);
- if (bat->playback.device == bat->capture.device) {
tmp1 = strchr(playback_device, ',');
if (tmp1 != NULL) {
*tmp1 = '\0';
bat->playback.pcm_id = bat->capture.pcm_id = (tmp1 + 1);
} else {
fprintf(bat->err, _("err, cannot find the pcm ID\n"));
return -EINVAL;
}
tmp2 = strchr(playback_device, ':');
if (tmp2 != NULL) {
bat->playback.card_id = bat->capture.card_id = (tmp2 + 1);
} else {
fprintf(bat->err, _("err, cannot find the card ID\n"));
return -EINVAL;
}
- } else {
tmp1 = strchr(playback_device, ',');
if (tmp1 != NULL) {
*tmp1 = '\0';
bat->playback.pcm_id = (tmp1 + 1);
} else {
fprintf(bat->err, _("err, cannot find the pcm ID of playback\n"));
return -EINVAL;
}
tmp2 = strchr(playback_device, ':');
if (tmp2 != NULL)
bat->playback.card_id = (tmp2 + 1);
else {
fprintf(bat->err, _("err, cannot find the card ID of playback\n"));
return -EINVAL;
}
tmp1 = strchr(capture_device, ',');
if (tmp1 != NULL) {
*tmp1 = '\0';
bat->capture.pcm_id = (tmp1 + 1);
} else {
fprintf(bat->err, _("err, cannot find the pcm ID of capture"));
return -EINVAL;
}
tmp2 = strchr(capture_device, ':');
if (tmp2 != NULL)
bat->capture.card_id = (tmp2 + 1);
else {
fprintf(bat->err, _("err, cannot find the card ID of capture"));
return -EINVAL;
}
- }
FYI, a handy function for this kind of parsing is sscanf(). For example, the above can be a simple form like:
if (sscanf(bat->playback.device, "pcm:%d,%d", &card, &pcm) != 2) return -EINVAL;
thanks,
Takashi
This patch add XRUN injections function, alsabat can trigger the XRUNs by writing the 'XRUN INJECTION' files.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com --- bat/bat.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- bat/common.h | 6 ++++ 2 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/bat/bat.c b/bat/bat.c index b12e5d3..20fa926 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -127,8 +127,81 @@ static void get_sine_frequencies(struct bat *bat, char *freq) } }
-/* parse the card and pcm ID of playback device and capture device */ -/* Then we can get the path for specify device to do the XRUN injection */ +static int xrun_injection(struct bat *bat) +{ + char playback_file[MAX_PATH + MAX_FILE]; + char capture_file[MAX_PATH + MAX_FILE]; + char filename[MAX_FILE] = XRUN_INJECTION_FILE; + char playback_filepath[MAX_PATH] = PLAYBACK_XRUN_INJECTION_PATH; + char capture_filepath[MAX_PATH] = CAPTURE_XRUN_INJECTION_PATH; + FILE *fp, *fc; + + if (bat->xrun_playback) { + /* replace the card ID and PCM ID in macro for xrun injection path */ + sprintf(playback_filepath, playback_filepath, bat->playback.card_id, + bat->playback.pcm_id); + sprintf(playback_file, "%s/%s", playback_filepath, filename); + bat->playback.xrun_file = playback_file; + + fp = fopen(playback_file, "w"); + if (fp == NULL) { + fprintf(bat->err, _("Can't open the XRUN injection file for playback: %s\n"), + playback_file); + return -ENOENT; /* No such file or directory */ + } + + /* enable XRUN for playback */ + fputs("1", fp); + fclose(fp); + } + + if (bat->xrun_capture) { + /* replace the card ID and PCM ID in macro for xrun injection path */ + sprintf(capture_filepath, capture_filepath, bat->capture.card_id, + bat->capture.pcm_id); + sprintf(capture_file, "%s/%s", capture_filepath, filename); + + fc = fopen(capture_file, "w"); + if (fc == NULL) { + fprintf(bat->err, _("Can't open the XRUN injection file for capture: %s\n"), + capture_file); + return -ENOENT; /* No such file or directory */ + } + + /* enable XRUN for capture */ + fputs("1", fc); + fclose(fc); + } + + return 0; +} + +static int check_xrun_period(struct bat *bat) +{ + int duration_t; + float duration_p; + char *ptrf; + + duration_t = (bat->frames / bat->rate) * 1000; + duration_p = strtof(bat->xarg, &ptrf); + bat->xrun_period = duration_p; + if (bat->xrun_period < 1 || bat->xrun_period > duration_t) { + fprintf(bat->err, _("Invalid period for xrun injections: (1, %d)\n"), + duration_t); + return -EINVAL; + } + + if (*ptrf == 'p') + bat->xrun_playback = 1; + else if (*ptrf == 'c') + bat->xrun_capture = 1; + else + bat->xrun_playback = bat->xrun_capture = 1; + return 0; +} + +/* parse the card and pcm ID of playback device and capture device, then */ +/* we can get the path for the specify device to do the XRUN injection */ static int parse_card_and_pcm_id(struct bat *bat) { char *tmp1; @@ -187,6 +260,7 @@ static int parse_card_and_pcm_id(struct bat *bat) return -EINVAL; } } + return 0; }
@@ -238,6 +312,7 @@ static void test_loopback(struct bat *bat) { pthread_t capture_id, playback_id; int err; + int timeout = 0; int *thread_result_capture, *thread_result_playback;
/* start playback */ @@ -261,6 +336,20 @@ static void test_loopback(struct bat *bat) exit(EXIT_FAILURE); }
+ /* check for xrun injections */ + while (bat->xrun_period) { + /* inject 1 XRUN every set time */ + usleep(bat->xrun_period * 1000); + timeout = timeout + (bat->xrun_period); + err = xrun_injection(bat); + if (err < 0) { + fprintf(bat->err, _("XRUN injections error: %d\n"), err); + exit(EXIT_FAILURE); + } + if (timeout >= ((bat->frames / bat->rate) * 1000)) + break; + } + /* wait for playback to complete */ err = thread_wait_completion(bat, playback_id, &thread_result_playback); if (err != 0) { @@ -625,6 +714,10 @@ static int bat_init(struct bat *bat) }
if (bat->xarg) { + err = check_xrun_period(bat); + if (err < 0) + return err; + err = parse_card_and_pcm_id(bat); if (err < 0) return err; diff --git a/bat/common.h b/bat/common.h index 9f71c6c..6eebba0 100644 --- a/bat/common.h +++ b/bat/common.h @@ -15,6 +15,11 @@
#define TEMP_RECORD_FILE_NAME "/tmp/bat.wav.XXXXXX" #define DEFAULT_DEV_NAME "default" +#define PLAYBACK_XRUN_INJECTION_PATH "/proc/asound/card%s/pcm%sp/sub0" +#define CAPTURE_XRUN_INJECTION_PATH "/proc/asound/card%s/pcm%sc/sub0" +#define XRUN_INJECTION_FILE "xrun_injection" +#define MAX_PATH 256 +#define MAX_FILE 128
#define MAX_DEV 15 #define OPT_BASE 300 @@ -170,6 +175,7 @@ struct pcm { char *file; char *pcm_id; char *card_id; + char *xrun_file; enum _bat_op_mode mode; void *(*fct)(struct bat *); };
On Thu, 31 Aug 2017 15:37:08 +0200, Zhang Keqiao wrote:
This patch add XRUN injections function, alsabat can trigger the XRUNs by writing the 'XRUN INJECTION' files.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com
bat/bat.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- bat/common.h | 6 ++++ 2 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/bat/bat.c b/bat/bat.c index b12e5d3..20fa926 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -127,8 +127,81 @@ static void get_sine_frequencies(struct bat *bat, char *freq) } }
-/* parse the card and pcm ID of playback device and capture device */ -/* Then we can get the path for specify device to do the XRUN injection */ +static int xrun_injection(struct bat *bat) +{
- char playback_file[MAX_PATH + MAX_FILE];
- char capture_file[MAX_PATH + MAX_FILE];
- char filename[MAX_FILE] = XRUN_INJECTION_FILE;
- char playback_filepath[MAX_PATH] = PLAYBACK_XRUN_INJECTION_PATH;
- char capture_filepath[MAX_PATH] = CAPTURE_XRUN_INJECTION_PATH;
- FILE *fp, *fc;
- if (bat->xrun_playback) {
/* replace the card ID and PCM ID in macro for xrun injection path */
sprintf(playback_filepath, playback_filepath, bat->playback.card_id,
bat->playback.pcm_id);
This doesn't look sane. The format path shouldn't be same as the target. There is no guarantee that the format path is evaluated before storing the resultant string.
Moreover:
+#define PLAYBACK_XRUN_INJECTION_PATH "/proc/asound/card%s/pcm%sp/sub0" +#define CAPTURE_XRUN_INJECTION_PATH "/proc/asound/card%s/pcm%sc/sub0"
This assumes the PCM#0, which isn't always true.
thanks,
Takashi
Check if target number of frames can be detected after XRUNs.
Signed-off-by: Zhang Keqiao keqiao.zhang@intel.com --- bat/analyze.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/bat/analyze.c b/bat/analyze.c index 17fff50..1c48a6b 100644 --- a/bat/analyze.c +++ b/bat/analyze.c @@ -471,9 +471,25 @@ int analyze_capture(struct bat *bat) goto exit2;
items = fread(bat->buf, bat->frame_size, bat->frames, bat->fp); - if (items != bat->frames) { - err = -EIO; - goto exit2; + + /* check the number of frames recorded after XRUNs */ + if (bat->xarg) { + if (items != bat->frames) { + fprintf(bat->err, _("\nXRUN injection test FAILED\n")); + fprintf(bat->err, _("Target frames number %d, actually got %zu\n"), + bat->frames, items); + err = -EIO; + goto exit2; + } else { + fprintf(bat->log, _("\nXRUN injection test PASSED\n")); + err = 0; /* target frames can be detected after XRUNs */ + goto exit2; + } + } else { + if (items != bat->frames) { + err = -EIO; + goto exit2; + } }
err = reorder_data(bat);
On Thu, 31 Aug 2017 15:37:05 +0200, Zhang Keqiao wrote:
The patch set add a new feature in alsabat, now alsabat can test XRUNs for both playback and capture by writing to proc file '/proc/sound/card*/pcm*/sub*/xrun_injection'.
The idea is good, but beware that the proc file is accessible only for root, and it's not always available, depending on the kernel config. This should be mentioned in the documentation.
Takashi
participants (2)
-
Takashi Iwai
-
Zhang Keqiao