[alsa-devel] [RFCv3][PATCH 33/39] axfer: add an option to support volume unit meter

Takashi Sakamoto o-takashi at sakamocchi.jp
Mon Oct 2 02:19:34 CEST 2017


In aplay, volume unit (vu) meter is implemented, by an option '--vumeter'
(-V). When 'm' or 's' is given for the option, terminal displays monaural
or stereo meter via stderr.

This commit adds support for this feature. Original implementation just
supports meters up to 2 channel and this commit follows it. However,
actual calculation is done all of channels with a consideration about
future extension to display all of channels.

Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
 axfer/Makefile.am                 |   7 +-
 axfer/options.c                   |  43 ++-
 axfer/options.h                   |   4 +
 axfer/subcmd-transfer.c           |   1 -
 axfer/vumeter.c                   | 546 ++++++++++++++++++++++++++++++++++++++
 axfer/vumeter.h                   |  55 ++++
 axfer/xfer-libasound-irq-mmap.c   |  16 +-
 axfer/xfer-libasound-irq-rw.c     |  40 ++-
 axfer/xfer-libasound-timer-mmap.c |  16 +-
 axfer/xfer-libasound.c            |   5 +-
 axfer/xfer-libasound.h            |   3 +-
 axfer/xfer.c                      |  39 ++-
 axfer/xfer.h                      |   5 +-
 13 files changed, 746 insertions(+), 34 deletions(-)
 create mode 100644 axfer/vumeter.c
 create mode 100644 axfer/vumeter.h

diff --git a/axfer/Makefile.am b/axfer/Makefile.am
index 3a0b1d26..7a079e62 100644
--- a/axfer/Makefile.am
+++ b/axfer/Makefile.am
@@ -22,7 +22,8 @@ noinst_HEADERS = \
 	options.h \
 	xfer.h \
 	xfer-alsa.h \
-	waiter.h
+	waiter.h \
+	vumeter.h
 
 axfer_SOURCES = \
 	misc.h \
@@ -52,4 +53,6 @@ axfer_SOURCES = \
 	waiter-poll.c \
 	xfer-libasound-irq-mmap.c \
 	waiter-epoll.c \
-	xfer-libasound-timer-mmap.c
+	xfer-libasound-timer-mmap.c \
+	vumeter.h \
+	vumeter.c
diff --git a/axfer/options.c b/axfer/options.c
index b1a3184a..d595d857 100644
--- a/axfer/options.c
+++ b/axfer/options.c
@@ -114,6 +114,30 @@ static int verify_cntr_format(struct context_options *opts, const char *literal)
 	return -EINVAL;
 }
 
+static int verify_vu_mode(struct context_options *opts, const char *literal)
+{
+	static const struct {
+		const char *const literal;
+		enum vumeter_mode mode;
+	} *entry, entries[] = {
+		{"m",	VUMETER_MODE_MONO},
+		{"s",	VUMETER_MODE_STEREO},
+	};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+		entry = &entries[i];
+		if (!strcmp(entry->literal, literal)) {
+			opts->vu_mode = entry->mode;
+			return 0;
+		}
+	}
+
+	fprintf(stderr, "Invalid argument for vumeter mode: %s\n", literal);
+
+	return -EINVAL;
+}
+
 /* This should be called after 'verify_cntr_format()'. */
 static int verify_sample_format(struct context_options *opts,
 				const char *literal)
@@ -179,7 +203,8 @@ static int apply_policies(struct context_options *opts,
 			  const char *node_literal,
 			  const char *sample_format_literal,
 			  const char *waiter_type_literal,
-			  const char *sched_type_literal)
+			  const char *sched_type_literal,
+			  const char *vu_mode_literal)
 {
 	int err;
 
@@ -255,6 +280,12 @@ static int apply_policies(struct context_options *opts,
 		}
 	}
 
+	if (vu_mode_literal) {
+		err = verify_vu_mode(opts, vu_mode_literal);
+		if (err < 0)
+			return err;
+	}
+
 	if (opts->frames_per_second > 0) {
 		unsigned int orig = opts->frames_per_second;
 
@@ -362,7 +393,7 @@ void context_options_calculate_duration(struct context_options *opts,
 int context_options_init(struct context_options *opts, int argc,
 			 char *const *argv, snd_pcm_stream_t direction)
 {
-	static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:";
+	static const char *s_opts = "hvqd:s:t:ID:f:c:r:NMw:F:B:A:R:T:V:";
 	static const struct option l_opts[] = {
 		/* For generic purposes. */
 		{"help",		0, 0, 'h'},
@@ -400,6 +431,8 @@ int context_options_init(struct context_options *opts, int argc,
 		{"dump-hw-params",	0, 0, OPT_DUMP_HW_PARAMS},
 		{"fatal-errors",	0, 0, OPT_FATAL_ERRORS},
 		{"test-nowait",		0, 0, OPT_TEST_NOWAIT},
+		/* Misc features. */
+		{"vumeter",		1, 0, 'V'},
 		/* Obsoleted. */
 		{"max-file-time",       1, 0, OPT_MAX_FILE_TIME},
 		{NULL,			0, 0, 0},
@@ -409,6 +442,7 @@ int context_options_init(struct context_options *opts, int argc,
 	const char *sample_format_literal = NULL;
 	const char *waiter_type_literal = NULL;
 	const char *sched_type_literal = NULL;
+	const char *vu_mode_literal = NULL;
 	int l_index = 0;
 	int c;
 	int err = 0;
@@ -477,6 +511,8 @@ int context_options_init(struct context_options *opts, int argc,
 			opts->finish_at_xrun = true;
 		else if (c == OPT_TEST_NOWAIT)
 			opts->test_nowait = true;
+		else if (c == 'V')
+			vu_mode_literal = optarg;
 		else if (c == OPT_MAX_FILE_TIME) {
 			fprintf(stderr,
 				"An option '--%s' is obsoleted and has no "
@@ -496,7 +532,8 @@ int context_options_init(struct context_options *opts, int argc,
 
 	return apply_policies(opts, direction, cntr_format_literal,
 			      node_literal, sample_format_literal,
-			      waiter_type_literal, sched_type_literal);
+			      waiter_type_literal, sched_type_literal,
+			      vu_mode_literal);
 }
 
 static int generate_path_with_suffix(struct context_options *opts,
diff --git a/axfer/options.h b/axfer/options.h
index 769757a8..188668b0 100644
--- a/axfer/options.h
+++ b/axfer/options.h
@@ -11,6 +11,7 @@
 
 #include "container.h"
 #include "waiter.h"
+#include "vumeter.h"
 
 enum sched_type {
 	SCHED_TYPE_IRQ = 0,
@@ -67,6 +68,9 @@ struct context_options {
 	bool dump_hw_params;
 	bool finish_at_xrun;
 	bool test_nowait;
+
+	/* Misc features. */
+	enum vumeter_mode vu_mode;
 };
 
 int context_options_init(struct context_options *opts, int argc,
diff --git a/axfer/subcmd-transfer.c b/axfer/subcmd-transfer.c
index e9a8c181..480b4eb5 100644
--- a/axfer/subcmd-transfer.c
+++ b/axfer/subcmd-transfer.c
@@ -128,7 +128,6 @@ static int prepare_signal_handler(struct context *ctx)
 
 static int context_init(struct context *ctx, snd_pcm_stream_t direction)
 {
-	/* Initialize transfer. */
 	return xfer_context_init(&ctx->xfer, XFER_TYPE_LIBASOUND, direction,
 				 &ctx->opts);
 }
diff --git a/axfer/vumeter.c b/axfer/vumeter.c
new file mode 100644
index 00000000..4d842866
--- /dev/null
+++ b/axfer/vumeter.c
@@ -0,0 +1,546 @@
+/*
+ * vumeter.c - Volume Unit meter.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "vumeter.h"
+#include "misc.h"
+
+#include <endian.h>
+
+/*
+ * MEMO: Casting to 'intXX_t' is important to calculate absolute value from
+ * 'uintXX_t' representation of PCM frame.
+ */
+
+static void calculate_8bit_sample_buf(struct vumeter_context *vu,
+				      void *frame_buffer,
+				      unsigned int frame_count)
+{
+	int8_t *buf = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	int8_t zero_point = (int8_t)vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int8_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int8_t val;
+			unsigned int offset;
+
+			offset = i * samples_per_frame + c;
+
+			val = abs(buf[offset] ^ zero_point);
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = (uint32_t)peak;
+	}
+}
+
+static void calculate_8bit_sample_vector(struct vumeter_context *vu,
+					 void *frame_buffer,
+					 unsigned int frame_count)
+{
+	int8_t **bufs = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	int8_t zero_point = (int8_t)vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int8_t *buf = bufs[c];
+		int8_t peak = 0;
+		for (i = 0; i < frame_count; ++i) {
+			int8_t val;
+
+			val = abs(buf[i] ^ zero_point);
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = (uint32_t)peak;
+	}
+}
+
+static void calculate_16bit_sample_buf(struct vumeter_context *vu,
+				       void *frame_buffer,
+				       unsigned int frame_count)
+{
+	int16_t *buf = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	bool le = vu->le;
+	int16_t zero_point = (int16_t)vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int16_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int16_t val;
+			unsigned int offset;
+
+			offset = i * samples_per_frame + c;
+
+			if (le)
+				val = (int32_t)le16toh(buf[offset]);
+			else
+				val = (int32_t)be16toh(buf[offset]);
+
+			val = abs(val ^ zero_point);
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = (uint32_t)peak;
+	}
+}
+
+static void calculate_16bit_sample_vector(struct vumeter_context *vu,
+					  void *frame_buffer,
+					  unsigned int frame_count)
+{
+	int16_t **bufs = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	bool le = vu->le;
+	int16_t zero_point = vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int16_t *buf = bufs[c];
+		int16_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int16_t val;
+
+			if (le)
+				val = (int16_t)le16toh(buf[i]);
+			else
+				val = (int16_t)be16toh(buf[i]);
+
+			val = abs(val ^ zero_point);
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = peak;
+	}
+}
+
+static void calculate_24bit_sample_buf(struct vumeter_context *vu,
+				       void *frame_buffer,
+				       unsigned int frame_count)
+{
+	int8_t *buf = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	unsigned int bytes_per_sample = vu->bytes_per_sample;
+	bool le = vu->le;
+	int32_t zero_point = vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int32_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int32_t val;
+			unsigned int offset;
+
+			offset = i * bytes_per_sample * samples_per_frame + c;
+
+			if (le) {
+				val = (int32_t)(((buf[offset] << 8) |
+				      (buf[offset + 1] << 16) |
+				      (buf[offset + 2] << 24)));
+			} else {
+				val = (int32_t)(((buf[offset] << 24) |
+				      (buf[offset + 1] << 16) |
+				      (buf[offset + 2] << 8)));
+			}
+
+			val = abs(val ^ zero_point);
+			if (vu->significant_bits_per_sample == 18)
+				val >>= 14;
+			else if (vu->significant_bits_per_sample == 20)
+				val >>= 12;
+			else
+				val >>= 8;
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = peak;
+	}
+}
+
+static void calculate_24bit_sample_vector(struct vumeter_context *vu,
+					  void *frame_buffer,
+					  unsigned int frame_count)
+{
+	int8_t **bufs = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	unsigned int bytes_per_sample = vu->bytes_per_sample;
+	bool le = vu->le;
+	int32_t zero_point = vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int8_t *buf = bufs[c];
+		int32_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int32_t val;
+			unsigned int offset;
+
+			offset = bytes_per_sample * i;
+
+			if (le) {
+				val = (int32_t)((buf[offset] << 8) |
+				      (buf[offset + 1] << 16) |
+				      (buf[offset + 2] << 24));
+			} else {
+				val = (int32_t)((buf[offset] << 24) |
+				      (buf[offset + 1] << 16) |
+				      (buf[offset + 2] << 8));
+			}
+
+			val = abs(val ^ zero_point);
+			if (vu->significant_bits_per_sample == 18)
+				val >>= 14;
+			else if (vu->significant_bits_per_sample == 20)
+				val >>= 12;
+			else
+				val >>= 8;
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = peak;
+	}
+}
+
+static void calculate_32bit_sample_buf(struct vumeter_context *vu,
+				       void *frame_buffer,
+				       unsigned int frame_count)
+{
+	int32_t *buf = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	bool le = vu->le;
+	int32_t zero_point = vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int32_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int32_t val;
+			unsigned int offset;
+
+			offset = i * samples_per_frame + c;
+
+			if (le)
+				val = (int32_t)le32toh(buf[offset]);
+			else
+				val = (int32_t)be32toh(buf[offset]);
+
+			val = abs(val ^ zero_point);
+			/* For [S/U]24_[B|L]E. */
+			if (vu->significant_bits_per_sample == 24)
+				val >>= 8;
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = peak;
+	}
+}
+
+static void calculate_32bit_sample_vector(struct vumeter_context *vu,
+					  void *frame_buffer,
+					  unsigned int frame_count)
+{
+	int32_t **bufs = frame_buffer;
+	unsigned int samples_per_frame = vu->samples_per_frame;
+	bool le = vu->le;
+	int32_t zero_point = vu->zero_point;
+	int c, i;
+
+	for (c = 0; c < samples_per_frame; ++c) {
+		int32_t *buf = bufs[c];
+		int32_t peak = 0;
+
+		for (i = 0; i < frame_count; ++i) {
+			int32_t val;
+
+			if (le)
+				val = (int32_t)le32toh(buf[i]);
+			else
+				val = (int32_t)be32toh(buf[i]);
+
+			val = abs(val ^ zero_point);
+			/* For [S/U]24_[B|L]E. */
+			if (vu->significant_bits_per_sample == 24)
+				val >>= 8;
+			if (val > peak)
+				peak = val;
+		}
+
+		vu->peaks[c] = peak;
+	}
+}
+
+static void print_monaural(struct vumeter_context *vu, char line[80])
+{
+	const int bar_len = 50;
+	unsigned int pos;
+
+	/* NOTE: 74 + '| 00%' + '\0' = 80 characters. */
+
+	/* Print peaks on current buffer. */
+	pos = vu->ratios[0] * bar_len / 100;
+	memset(line, '#', pos);
+
+	/* Print maximum peaks within a second. */
+	pos = vu->max_ratios_in_second[0] * bar_len / 100;
+	line[pos] = '+';
+
+	if (vu->max_ratios_in_second[0] > 99) {
+		snprintf(line + bar_len + 1, 80 - bar_len,
+			 "| MAX");
+	} else {
+		snprintf(line + bar_len + 1, 80 - bar_len,
+			 "| %02u%%", vu->max_ratios_in_second[0]);
+	}
+}
+
+static void print_stereo(struct vumeter_context *vu, char line[80])
+{
+	const int bar_length = 35;
+	bool even;
+	int width;
+	char *substr;
+	int c;
+
+	/* NOTE: 35 + ' 00%|00% ' + 35 + '\0' = 80 characters. */
+
+	for (c = 0; c < 2; c++) {
+		even = (c % 2 > 0);
+
+		/* Print peaks on current buffer. */
+		width = vu->ratios[c] * bar_length / 100;
+		if (width > bar_length)
+			width = bar_length;
+		if (even)
+			memset(line + bar_length + 9, '#', width);
+		else
+			memset(line + bar_length - width, '#', width);
+
+		/* Print maximum peaks within a second. */
+		width = vu->max_ratios_in_second[c] * bar_length / 100;
+		if (width > bar_length)
+			width = bar_length;
+		if (even)
+			line[bar_length + 8 + width] = '+';
+		else
+			line[bar_length - width] = '+';
+
+		/* Print numerical value of maximum peaks within a second. */
+		if (even)
+			substr = line + bar_length + 5;
+		else
+			substr = line + bar_length + 1;
+		if (vu->max_ratios_in_second[c] > 99) {
+			snprintf(substr, 4, "MAX");
+		} else {
+			snprintf(substr, 4,
+				 "%02u%%", vu->max_ratios_in_second[c]);
+		}
+		substr[3] = ' ';
+	}
+
+	line[bar_length + 4] = '|';
+}
+
+int vumeter_context_init(struct vumeter_context *vu, enum vumeter_mode mode,
+			 snd_pcm_access_t access, snd_pcm_format_t format,
+			 unsigned int samples_per_frame,
+			 unsigned int frames_per_second)
+{
+	int bits_per_sample;
+
+	/* Just for sample formats of PCM. */
+	if (format != SND_PCM_FORMAT_S8 &&
+	    format != SND_PCM_FORMAT_U8 &&
+	    format != SND_PCM_FORMAT_S16_LE &&
+	    format != SND_PCM_FORMAT_S16_BE &&
+	    format != SND_PCM_FORMAT_U16_LE &&
+	    format != SND_PCM_FORMAT_U16_BE &&
+	    format != SND_PCM_FORMAT_S24_LE &&
+	    format != SND_PCM_FORMAT_S24_BE &&
+	    format != SND_PCM_FORMAT_U24_LE &&
+	    format != SND_PCM_FORMAT_U24_BE &&
+	    format != SND_PCM_FORMAT_S32_LE &&
+	    format != SND_PCM_FORMAT_S32_BE &&
+	    format != SND_PCM_FORMAT_U32_LE &&
+	    format != SND_PCM_FORMAT_U32_BE &&
+	    format != SND_PCM_FORMAT_S24_3LE &&
+	    format != SND_PCM_FORMAT_S24_3BE &&
+	    format != SND_PCM_FORMAT_U24_3LE &&
+	    format != SND_PCM_FORMAT_U24_3BE &&
+	    format != SND_PCM_FORMAT_S24_3LE &&
+	    format != SND_PCM_FORMAT_S24_3BE &&
+	    format != SND_PCM_FORMAT_U24_3LE &&
+	    format != SND_PCM_FORMAT_U24_3BE &&
+	    format != SND_PCM_FORMAT_S20_3LE &&
+	    format != SND_PCM_FORMAT_S20_3BE &&
+	    format != SND_PCM_FORMAT_U20_3LE &&
+	    format != SND_PCM_FORMAT_U20_3BE &&
+	    format != SND_PCM_FORMAT_S18_3LE &&
+	    format != SND_PCM_FORMAT_S18_3BE &&
+	    format != SND_PCM_FORMAT_U18_3LE &&
+	    format != SND_PCM_FORMAT_U18_3BE)
+		return -EINVAL;
+
+	bits_per_sample = snd_pcm_format_physical_width(format);
+	if (access == SND_PCM_ACCESS_RW_INTERLEAVED ||
+	    access == SND_PCM_ACCESS_MMAP_INTERLEAVED) {
+		if (bits_per_sample == 8)
+			vu->calculator = calculate_8bit_sample_buf;
+		else if (bits_per_sample == 16)
+			vu->calculator = calculate_16bit_sample_buf;
+		else if (bits_per_sample == 24)
+			vu->calculator = calculate_24bit_sample_buf;
+		else if (bits_per_sample == 32)
+			vu->calculator = calculate_32bit_sample_buf;
+		else
+			return -EINVAL;
+	} else if (access == SND_PCM_ACCESS_RW_NONINTERLEAVED ||
+		   access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
+		if (bits_per_sample == 8)
+			vu->calculator = calculate_8bit_sample_vector;
+		else if (bits_per_sample == 16)
+			vu->calculator = calculate_16bit_sample_vector;
+		else if (bits_per_sample == 24)
+			vu->calculator = calculate_24bit_sample_vector;
+		else if (bits_per_sample == 32)
+			vu->calculator = calculate_32bit_sample_vector;
+		else
+			return -EINVAL;
+	} else {
+		return -EINVAL;
+	}
+	vu->bytes_per_sample = bits_per_sample / 8;
+
+	vu->peaks = calloc(samples_per_frame, sizeof(*vu->peaks));
+	if (vu->peaks == NULL)
+		return -ENOMEM;
+	vu->samples_per_frame = samples_per_frame;
+	vu->frames_per_second = frames_per_second;
+
+	if (mode == VUMETER_MODE_MONO || samples_per_frame == 1)
+		vu->printer = print_monaural;
+	else if (mode == VUMETER_MODE_STEREO)
+		vu->printer = print_stereo;
+	else
+		return -EINVAL;
+	vu->mode = mode;
+
+	vu->ratios = calloc(samples_per_frame, sizeof(*vu->ratios));
+	if (vu->ratios == NULL)
+		return -ENOMEM;
+	vu->max_ratios_in_second = calloc(samples_per_frame,
+					  sizeof(*vu->max_ratios_in_second));
+	if (vu->max_ratios_in_second == NULL)
+		return -ENOMEM;
+
+	vu->significant_bits_per_sample = snd_pcm_format_width(format);
+	vu->max = (1 << (vu->significant_bits_per_sample - 1)) - 1;
+
+	vu->le = !!snd_pcm_format_little_endian(format);
+	vu->zero_point = snd_pcm_format_silence_64(format);
+	/* For these sample formats, use 32bit width storage temporarily. */
+	if (vu->significant_bits_per_sample == 18)
+		vu->zero_point <<= 14;
+	if (vu->significant_bits_per_sample == 20)
+		vu->zero_point <<= 12;
+	if (vu->significant_bits_per_sample == 24)
+		vu->zero_point <<= 8;
+
+	return 0;
+}
+
+void vumeter_context_prepare(struct vumeter_context *vu)
+{
+	int c;
+
+	for (c = 0; c < vu->samples_per_frame; ++c) {
+		vu->ratios[c] = 0;
+		vu->max_ratios_in_second[c] = 0;
+	}
+	vu->total_frame_count = 0;
+}
+
+void vumeter_context_calculate(struct vumeter_context *vu, void *frame_buffer,
+			       unsigned int frame_count)
+{
+	int64_t val;
+	unsigned int ratio;
+	int c;
+
+	vu->calculator(vu, frame_buffer, frame_count);
+
+	vu->total_frame_count += frame_count;
+
+	for (c = 0; c < vu->samples_per_frame; ++c) {
+		val = (int64_t)vu->peaks[c];
+	        ratio = val * 100 / vu->max;
+
+		/* Reset ratios every second. */
+		if (vu->total_frame_count >= vu->frames_per_second)
+			vu->max_ratios_in_second[c] = 0;
+		if (ratio > vu->max_ratios_in_second[c])
+			vu->max_ratios_in_second[c] = ratio;
+
+		vu->ratios[c] = ratio;
+	}
+
+	vu->total_frame_count %= vu->frames_per_second;
+}
+
+void vumeter_context_print(struct vumeter_context *vu)
+{
+	char line[80];
+
+	memset(line, ' ', 79);
+
+	putc('\r', stderr);
+
+	vu->printer(vu, line);
+
+	line[79] = '\0';
+	fputs(line, stderr);
+	fflush(stderr);
+}
+
+void vumeter_context_break(struct vumeter_context *vu)
+{
+	fprintf(stderr, "\r");
+}
+
+void vumeter_context_destroy(struct vumeter_context *vu)
+{
+	if (vu->ratios)
+		free(vu->ratios);
+	vu->ratios = NULL;
+
+	if (vu->max_ratios_in_second)
+		free(vu->max_ratios_in_second);
+	vu->max_ratios_in_second = NULL;
+
+	if (vu->peaks)
+		free(vu->peaks);
+	vu->peaks = NULL;
+}
diff --git a/axfer/vumeter.h b/axfer/vumeter.h
new file mode 100644
index 00000000..dcaa8b4f
--- /dev/null
+++ b/axfer/vumeter.h
@@ -0,0 +1,55 @@
+/*
+ * vumeter.h - Volume Unit meter.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef __ALSA_UTILS_AXFER_VUMETER__H_
+#define __ALSA_UTILS_AXFER_VUMETER__H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <alsa/asoundlib.h>
+
+enum vumeter_mode {
+	VUMETER_MODE_NONE = 0,
+	VUMETER_MODE_MONO,
+	VUMETER_MODE_STEREO
+};
+
+struct vumeter_context {
+	enum vumeter_mode mode;
+
+	bool le;
+	unsigned int significant_bits_per_sample;
+	unsigned int bytes_per_sample;
+	unsigned int samples_per_frame;
+	unsigned int frames_per_second;
+
+	int64_t zero_point;
+	uint32_t max;
+	int32_t *peaks;
+
+	unsigned int *ratios;
+	unsigned int *max_ratios_in_second;
+	uint64_t total_frame_count;
+
+	void (*printer)(struct vumeter_context *vu, char line[80]);
+	void (*calculator)(struct vumeter_context *vu, void *frame_buffer,
+			   unsigned int frame_count);
+};
+
+int vumeter_context_init(struct vumeter_context *vu, enum vumeter_mode mode,
+			 snd_pcm_access_t access, snd_pcm_format_t format,
+			 unsigned int samples_per_frame,
+			 unsigned int frames_per_second);
+void vumeter_context_prepare(struct vumeter_context *vu);
+void vumeter_context_calculate(struct vumeter_context *vu, void *frame_buffer,
+			       unsigned int frame_count);
+void vumeter_context_print(struct vumeter_context *vu);
+void vumeter_context_break(struct vumeter_context *vu);
+void vumeter_context_destroy(struct vumeter_context *vu);
+
+#endif
diff --git a/axfer/xfer-libasound-irq-mmap.c b/axfer/xfer-libasound-irq-mmap.c
index fb70bbc5..dcb8480e 100644
--- a/axfer/xfer-libasound-irq-mmap.c
+++ b/axfer/xfer-libasound-irq-mmap.c
@@ -70,7 +70,8 @@ static int irq_mmap_pre_process(struct libasound_state *state)
 static int irq_mmap_process_frames(struct libasound_state *state,
 				   unsigned int *frame_count,
 				   struct mapper_context *mapper,
-				   struct container_context *cntrs)
+				   struct container_context *cntrs,
+				   struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	const snd_pcm_channel_area_t *areas;
@@ -139,6 +140,9 @@ static int irq_mmap_process_frames(struct libasound_state *state,
 	if (consumed_count != avail_count)
 		logging(state, "A bug of access plugin for this PCM node.\n");
 
+	if (vu)
+		vumeter_context_calculate(vu, frame_buf, consumed_count);
+
 	*frame_count = consumed_count;
 
 	return 0;
@@ -150,7 +154,8 @@ error:
 static int irq_mmap_r_process_frames(struct libasound_state *state,
 				     unsigned *frame_count,
 				     struct mapper_context *mapper,
-				     struct container_context *cntrs)
+				     struct container_context *cntrs,
+				     struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	snd_pcm_state_t s;
@@ -188,7 +193,7 @@ static int irq_mmap_r_process_frames(struct libasound_state *state,
 			goto error;
 	}
 
-	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
+	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
@@ -201,7 +206,8 @@ error:
 static int irq_mmap_w_process_frames(struct libasound_state *state,
 				     unsigned *frame_count,
 				     struct mapper_context *mapper,
-				     struct container_context *cntrs)
+				     struct container_context *cntrs,
+				     struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	snd_pcm_state_t s;
@@ -215,7 +221,7 @@ static int irq_mmap_w_process_frames(struct libasound_state *state,
 
 	/* TODO: if reporting something, do here with the status data. */
 
-	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
+	err = irq_mmap_process_frames(state, frame_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c
index ad320f6f..ee5ce801 100644
--- a/axfer/xfer-libasound-irq-rw.c
+++ b/axfer/xfer-libasound-irq-rw.c
@@ -20,7 +20,8 @@ struct frame_cache {
 
 	int (*handle)(struct libasound_state *state, snd_pcm_state_t status,
 		      unsigned int *frame_count, struct mapper_context *mapper,
-		      struct container_context *cntrs);
+		      struct container_context *cntrs,
+		      struct vumeter_context *vu);
 	void (*align_frames)(struct frame_cache *cache,
 			     unsigned int consumed_count,
 			     unsigned int bytes_per_sample,
@@ -73,7 +74,8 @@ static void align_frames_in_n(struct frame_cache *cache,
 
 static int read_frames(struct libasound_state *state, unsigned int *frame_count,
 		       unsigned int avail_count, struct mapper_context *mapper,
-		       struct container_context *cntrs)
+		       struct container_context *cntrs,
+		       struct vumeter_context *vu)
 {
 	struct frame_cache *cache = state->private_data;
 	unsigned int consumed_count;
@@ -111,6 +113,9 @@ static int read_frames(struct libasound_state *state, unsigned int *frame_count,
 	if (err < 0)
 		return err;
 
+	if (vu)
+		vumeter_context_calculate(vu, cache->buf, consumed_count);
+
 	cache->align_frames(cache, consumed_count, cache->bytes_per_sample,
 			    cache->samples_per_frame);
 
@@ -123,7 +128,8 @@ static int r_process_frames_blocking(struct libasound_state *state,
 				     snd_pcm_state_t status,
 				     unsigned int *frame_count,
 				     struct mapper_context *mapper,
-				     struct container_context *cntrs)
+				     struct container_context *cntrs,
+				     struct vumeter_context *vu)
 {
 	snd_pcm_sframes_t avail;
 	snd_pcm_uframes_t avail_count;
@@ -159,7 +165,7 @@ static int r_process_frames_blocking(struct libasound_state *state,
 		avail_count = (unsigned int)frame_count;
 	}
 
-	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
+	err = read_frames(state, frame_count, avail_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
@@ -173,7 +179,8 @@ static int r_process_frames_nonblocking(struct libasound_state *state,
 					snd_pcm_state_t status,
 					unsigned int *frame_count,
 					struct mapper_context *mapper,
-					struct container_context *cntrs)
+					struct container_context *cntrs,
+					struct vumeter_context *vu)
 {
 	snd_pcm_sframes_t avail;
 	snd_pcm_uframes_t avail_count;
@@ -206,7 +213,7 @@ static int r_process_frames_nonblocking(struct libasound_state *state,
 		goto error;
 	}
 
-	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
+	err = read_frames(state, frame_count, avail_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
@@ -219,7 +226,8 @@ error:
 static int write_frames(struct libasound_state *state,
 			unsigned int *frame_count, unsigned int avail_count,
 			struct mapper_context *mapper,
-			struct container_context *cntrs)
+			struct container_context *cntrs,
+			struct vumeter_context *vu)
 {
 	struct frame_cache *cache = state->private_data;
 	snd_pcm_uframes_t consumed_count;
@@ -254,6 +262,9 @@ static int write_frames(struct libasound_state *state,
 	if (err < 0)
 		return err;
 
+	if (vu)
+		vumeter_context_calculate(vu, cache->buf, consumed_count);
+
 	consumed_count = (unsigned int)err;
 	cache->align_frames(cache, consumed_count, cache->bytes_per_sample,
 			    cache->samples_per_frame);
@@ -267,7 +278,8 @@ static int w_process_frames_blocking(struct libasound_state *state,
 				     snd_pcm_state_t status,
 				     unsigned int *frame_count,
 				     struct mapper_context *mapper,
-				     struct container_context *cntrs)
+				     struct container_context *cntrs,
+				     struct vumeter_context *vu)
 {
 	snd_pcm_sframes_t avail;
 	unsigned int avail_count;
@@ -323,7 +335,7 @@ static int w_process_frames_blocking(struct libasound_state *state,
 			avail_count = (unsigned int)frames_per_period;
 	}
 
-	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
+	err = write_frames(state, frame_count, avail_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
@@ -337,7 +349,8 @@ static int w_process_frames_nonblocking(struct libasound_state *state,
 					snd_pcm_state_t status,
 					unsigned int *frame_count,
 					struct mapper_context *mapper,
-					struct container_context *cntrs)
+					struct container_context *cntrs,
+					struct vumeter_context *vu)
 {
 	snd_pcm_sframes_t avail;
 	unsigned int avail_count;
@@ -364,7 +377,7 @@ static int w_process_frames_nonblocking(struct libasound_state *state,
 		goto error;
 	}
 
-	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
+	err = write_frames(state, frame_count, avail_count, mapper, cntrs, vu);
 	if (err < 0)
 		goto error;
 
@@ -461,7 +474,8 @@ static int irq_rw_pre_process(struct libasound_state *state)
 static int irq_rw_process_frames(struct libasound_state *state,
 				unsigned int *frame_count,
 				struct mapper_context *mapper,
-				struct container_context *cntrs)
+				struct container_context *cntrs,
+				struct vumeter_context *vu)
 {
 	struct frame_cache *cache = state->private_data;
 	snd_pcm_state_t status;
@@ -472,7 +486,7 @@ static int irq_rw_process_frames(struct libasound_state *state,
 		return -EPIPE;
 
 	/* NOTE: Actually, status can be shift always. */
-	return cache->handle(state, status, frame_count, mapper, cntrs);
+	return cache->handle(state, status, frame_count, mapper, cntrs, vu);
 }
 
 static void irq_rw_post_process(struct libasound_state *state)
diff --git a/axfer/xfer-libasound-timer-mmap.c b/axfer/xfer-libasound-timer-mmap.c
index 413b7958..90439414 100644
--- a/axfer/xfer-libasound-timer-mmap.c
+++ b/axfer/xfer-libasound-timer-mmap.c
@@ -121,7 +121,8 @@ static void *get_buffer(struct libasound_state *state,
 static int timer_mmap_process_frames(struct libasound_state *state,
 				     unsigned int *frame_count,
 				     struct mapper_context *mapper,
-				     struct container_context *cntrs)
+				     struct container_context *cntrs,
+				     struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	snd_pcm_uframes_t planned_count;
@@ -215,6 +216,9 @@ static int timer_mmap_process_frames(struct libasound_state *state,
 	}
 	*frame_count = consumed_count;
 
+	if (vu)
+		vumeter_context_calculate(vu, frame_buf, *frame_count);
+
 	return 0;
 }
 
@@ -246,7 +250,8 @@ static int forward_appl_ptr(struct libasound_state *state)
 static int timer_mmap_r_process_frames(struct libasound_state *state,
 				       unsigned *frame_count,
 				       struct mapper_context *mapper,
-				       struct container_context *cntrs)
+				       struct container_context *cntrs,
+				       struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	snd_pcm_state_t s;
@@ -280,7 +285,7 @@ static int timer_mmap_r_process_frames(struct libasound_state *state,
 		}
 
 		err = timer_mmap_process_frames(state, frame_count, mapper,
-						cntrs);
+						cntrs, vu);
 		if (err < 0)
 			goto error;
 	} else {
@@ -384,7 +389,8 @@ static int fill_buffer_with_zero_samples(struct libasound_state *state)
 static int timer_mmap_w_process_frames(struct libasound_state *state,
 				       unsigned *frame_count,
 				       struct mapper_context *mapper,
-				       struct container_context *cntrs)
+				       struct container_context *cntrs,
+				       struct vumeter_context *vu)
 {
 	struct map_layout *layout = state->private_data;
 	snd_pcm_state_t s;
@@ -411,7 +417,7 @@ static int timer_mmap_w_process_frames(struct libasound_state *state,
 		}
 
 		err = timer_mmap_process_frames(state, frame_count, mapper,
-						cntrs);
+						cntrs, vu);
 		if (err < 0)
 			goto error;
 	} else {
diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c
index 12c90936..92aba3c4 100644
--- a/axfer/xfer-libasound.c
+++ b/axfer/xfer-libasound.c
@@ -562,7 +562,8 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer,
 static int xfer_libasound_process_frames(struct xfer_context *xfer,
 					 unsigned int *frame_count,
 					 struct mapper_context *mapper,
-					 struct container_context *cntrs)
+					 struct container_context *cntrs,
+					 struct vumeter_context *vu)
 {
 	struct libasound_state *state = xfer->private_data;
 	int err;
@@ -570,7 +571,7 @@ static int xfer_libasound_process_frames(struct xfer_context *xfer,
 	if (state->handle == NULL)
 		return -ENXIO;
 
-	err = state->ops->process_frames(state, frame_count, mapper, cntrs);
+	err = state->ops->process_frames(state, frame_count, mapper, cntrs, vu);
 	if (err < 0) {
 		if (err == -EPIPE && !state->finish_at_xrun) {
 			/*
diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h
index 5d399571..004613fa 100644
--- a/axfer/xfer-libasound.h
+++ b/axfer/xfer-libasound.h
@@ -41,7 +41,8 @@ struct xfer_libasound_ops {
 	int (*process_frames)(struct libasound_state *state,
 			      unsigned int *frame_count,
 			      struct mapper_context *mapper,
-			      struct container_context *cntrs);
+			      struct container_context *cntrs,
+			      struct vumeter_context *vu);
 	void (*post_process)(struct libasound_state *state);
 	unsigned int private_size;
 };
diff --git a/axfer/xfer.c b/axfer/xfer.c
index 2f9f9422..211d9aa5 100644
--- a/axfer/xfer.c
+++ b/axfer/xfer.c
@@ -47,6 +47,14 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type,
 		return -ENOMEM;
 	memset(xfer->private_data, 0, entries[i].data->private_size);
 
+	if (opts->vu_mode != VUMETER_MODE_NONE) {
+		xfer->vu = malloc(sizeof(*xfer->vu));
+		if (xfer->vu == NULL)
+			return -ENOMEM;
+		memset(xfer->vu, 0, sizeof(*xfer->vu));
+		xfer->vu->mode = opts->vu_mode;
+	}
+
 	return xfer->ops->init(xfer, direction, opts);
 }
 
@@ -61,6 +69,10 @@ void xfer_context_destroy(struct xfer_context *xfer)
 		xfer->ops->destroy(xfer);
 	if (xfer->private_data)
 		free(xfer->private_data);
+
+	if (xfer->vu)
+		free(xfer->vu);
+	xfer->vu = NULL;
 }
 
 int xfer_context_pre_process(struct xfer_context *xfer,
@@ -97,6 +109,16 @@ int xfer_context_pre_process(struct xfer_context *xfer,
 	assert(*access <= SND_PCM_ACCESS_LAST);
 	assert(*frames_per_buffer > 0);
 
+	if (xfer->vu) {
+		err = vumeter_context_init(xfer->vu, xfer->vu->mode, *access,
+					   *format, *samples_per_frame,
+					   *frames_per_second);
+		if (err < 0)
+			return err;
+
+		vumeter_context_prepare(xfer->vu);
+	}
+
 	if (xfer->verbose > 1) {
 		fprintf(stderr, "Transfer: %s\n",
 			xfer_type_labels[xfer->type]);
@@ -122,6 +144,8 @@ int xfer_context_process_frames(struct xfer_context *xfer,
 				struct container_context *cntrs,
 				unsigned int *frame_count)
 {
+	int err;
+
 	assert(xfer);
 	assert(mapper);
 	assert(cntrs);
@@ -130,7 +154,15 @@ int xfer_context_process_frames(struct xfer_context *xfer,
 	if (!xfer->ops)
 		return -ENXIO;
 
-	return xfer->ops->process_frames(xfer, frame_count, mapper, cntrs);
+	err = xfer->ops->process_frames(xfer, frame_count, mapper, cntrs,
+					xfer->vu);
+	if (err < 0)
+		return err;
+
+	if (xfer->vu)
+		vumeter_context_print(xfer->vu);
+
+	return 0;
 }
 
 void xfer_context_pause(struct xfer_context *xfer, bool enable)
@@ -151,4 +183,9 @@ void xfer_context_post_process(struct xfer_context *xfer)
 		return;
 
 	xfer->ops->post_process(xfer);
+
+	if (xfer->vu) {
+		vumeter_context_break(xfer->vu);
+		vumeter_context_destroy(xfer->vu);
+	}
 }
diff --git a/axfer/xfer.h b/axfer/xfer.h
index 167c9903..eccf042c 100644
--- a/axfer/xfer.h
+++ b/axfer/xfer.h
@@ -23,6 +23,8 @@ struct xfer_context  {
 	const struct xfer_ops *ops;
 	void *private_data;
 
+	struct vumeter_context *vu;
+
 	unsigned int verbose;
 };
 
@@ -57,7 +59,8 @@ struct xfer_ops {
 	int (*process_frames)(struct xfer_context *xfer,
 			      unsigned int *frame_count,
 			      struct mapper_context *mapper,
-			      struct container_context *cntrs);
+			      struct container_context *cntrs,
+			      struct vumeter_context *vu);
 	void (*post_process)(struct xfer_context *xfer);
 	void (*destroy)(struct xfer_context *xfer);
 	void (*pause)(struct xfer_context *xfer, bool enable);
-- 
2.11.0



More information about the Alsa-devel mailing list