[alsa-devel] [PATCH 00/23] alsa-utils: rewrite aplay
Hi,
I think all of you in this mailing list might have used the command 'aplay', at least once. This patchset is my attempt to rewrite it because current shape of code is not enough good. Especially, ALSA PCM interface have got extensions since aplay was firstly developed. One of my motivation is to refine the tool to test such extensions.
Below diagram illustrates new internal design of this program:
<-> container <-> (file) (device) <-> (alsa-lib) <-> xfer <-> aligner <-> container <-> (file) <-> container <-> (file)
For description of each modules, please read patch comment.
This patchset is early preview for developers to see the above design, thus some features of original aplay are not still implemented. Especially, some options have no actual effect even if they're parsed. Presently, I see below command lines run as I expected:
$ ./aplay -C (-v) (-M)(-N) -D [hw:0,0|default] record.wav $ ./aplay -P (-v) (-M)(-N) -D [hw:0,0|default] playback.wav $ ./aplay --list-devices $ ./aplay --list-pcm $ ./aplay --version
Here, I work with Intel HDA (hw:0,0) and pulse PCM plugin (default).
It's my pleasure if received some comments from you.
Takashi Sakamoto (23): aplay: add an abstraction of container to parse/build audio-specific data format aplay: add an implementation of container for Microsoft/IBM RIFF/Wave format aplay: add an implementation of container for Sparc AU format aplay: add an implementation of container for Creative Tech. voice format aplay: add an implementation of container for raw format aplay: aligner: add an abstraction to align buffers with different data aplay: add an implementation of aligner for single target aplay: add an implementation of aligner for multiple target aplay: add an abstruction of waiter for I/O event notification aplay: add an implementation of waiter for poll(2) aplay: add an implementation of waiter for epoll(7) aplay: options: add a parser for command-line options aplay: add an abstraction for transferring of PCM frames aplay: add an implementation for transferring by ALSA PCM APIs aplay: add implementation of I/O aplay: add implementations to scheduling aplay: add a sub-command to print list of PCMs/devices aplay: add a sub-command to transfer data frames aplay: obsolete main routine and introduce sub-command style aplay: add an implementation for volume unit meter aplay: add a parser for channel map API aplay: add a handler for key events aplay: add a feature to generate PID file
aplay/Makefile.am | 48 +- aplay/aligner-multiple.c | 337 ++++ aplay/aligner-single.c | 256 +++ aplay/aligner.c | 122 ++ aplay/aligner.h | 88 ++ aplay/aplay.c | 3425 ----------------------------------------- aplay/container-au.c | 203 +++ aplay/container-riff-wave.c | 549 +++++++ aplay/container-voc.c | 736 +++++++++ aplay/container.c | 375 +++++ aplay/container.h | 127 ++ aplay/formats.h | 134 -- aplay/key-event.c | 120 ++ aplay/key-event.h | 21 + aplay/main.c | 178 +++ aplay/main.h | 32 + aplay/options.c | 674 ++++++++ aplay/options.h | 71 + aplay/subcmd-list.c | 233 +++ aplay/subcmd-transfer.c | 431 ++++++ aplay/vumeter.c | 299 ++++ aplay/vumeter.h | 50 + aplay/waiter-epoll.c | 76 + aplay/waiter-poll.c | 66 + aplay/waiter.c | 61 + aplay/waiter.h | 59 + aplay/xfer-alsa-io-mmap.c | 135 ++ aplay/xfer-alsa-io-rw.c | 370 +++++ aplay/xfer-alsa-sched-irq.c | 18 + aplay/xfer-alsa-sched-timer.c | 49 + aplay/xfer-alsa.c | 610 ++++++++ aplay/xfer-alsa.h | 67 + aplay/xfer.c | 124 ++ aplay/xfer.h | 74 + configure.ac | 2 +- 35 files changed, 6655 insertions(+), 3565 deletions(-) create mode 100644 aplay/aligner-multiple.c create mode 100644 aplay/aligner-single.c create mode 100644 aplay/aligner.c create mode 100644 aplay/aligner.h delete mode 100644 aplay/aplay.c create mode 100644 aplay/container-au.c create mode 100644 aplay/container-riff-wave.c create mode 100644 aplay/container-voc.c create mode 100644 aplay/container.c create mode 100644 aplay/container.h delete mode 100644 aplay/formats.h create mode 100644 aplay/key-event.c create mode 100644 aplay/key-event.h create mode 100644 aplay/main.c create mode 100644 aplay/main.h create mode 100644 aplay/options.c create mode 100644 aplay/options.h create mode 100644 aplay/subcmd-list.c create mode 100644 aplay/subcmd-transfer.c create mode 100644 aplay/vumeter.c create mode 100644 aplay/vumeter.h create mode 100644 aplay/waiter-epoll.c create mode 100644 aplay/waiter-poll.c create mode 100644 aplay/waiter.c create mode 100644 aplay/waiter.h create mode 100644 aplay/xfer-alsa-io-mmap.c create mode 100644 aplay/xfer-alsa-io-rw.c create mode 100644 aplay/xfer-alsa-sched-irq.c create mode 100644 aplay/xfer-alsa-sched-timer.c create mode 100644 aplay/xfer-alsa.c create mode 100644 aplay/xfer-alsa.h create mode 100644 aplay/xfer.c create mode 100644 aplay/xfer.h
Current implementation of aplay supports three types of data format for file; Microsoft/IBM RIFF/Wave (.wav), Sparc AU (.au) and Creative Tech. voice (.voc). These formats were designed to handle audio-related data with interleaved frame alignment.
This commit adds an abstraction of the file format, named as 'container' module. This abstraction includes several functions to build/parse the format data from any file descriptors. Furthermore, this includes several helper functions for implementations for each builders/parsers. --- aplay/Makefile.am | 14 ++- aplay/container.c | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/container.h | 114 ++++++++++++++++++ 3 files changed, 463 insertions(+), 5 deletions(-) create mode 100644 aplay/container.c create mode 100644 aplay/container.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index c3256e9..44143e0 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -3,13 +3,17 @@ LIBRT = @LIBRT@ AM_CPPFLAGS = -I$(top_srcdir)/include LDADD = $(LIBINTL) $(LIBRT)
-# debug flags -#LDFLAGS = -static -#LDADD += -ldl - bin_PROGRAMS = aplay man_MANS = aplay.1 arecord.1 -noinst_HEADERS = formats.h +noinst_HEADERS = \ + formats.h \ + container.h + +aplay_SOURCES = \ + formats.h \ + aplay.c \ + container.h \ + container.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/container.c b/aplay/container.c new file mode 100644 index 0000000..7f8e31b --- /dev/null +++ b/aplay/container.c @@ -0,0 +1,340 @@ +/* + * container.c - utilities for I/O to file. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <gettext.h> + +static const char *const cntr_type_labels[] = { + [CONTAINER_TYPE_PARSER] = "parser", + [CONTAINER_TYPE_BUILDER] = "builder", +}; + +static const char *const cntr_format_labels[] = { + [CONTAINER_FORMAT_COUNT] = "", +}; + +static const char *const suffixes[] = { + [CONTAINER_FORMAT_COUNT] = "", +}; + +const char *const container_suffix_from_format(enum container_format format) +{ + return suffixes[format]; +} + +int container_recursive_read(struct container_context *cntr, void *buf, + unsigned int size) +{ + char *dst = buf; + ssize_t result; + size_t consumed = 0; + + while (consumed < size && !cntr->interrupted) { + result = read(cntr->fd, dst + consumed, size - consumed); + if (result < 0) { + /* + * This descriptor was configured with non-blocking + * mode. EINTR is not cought when get any interrupts. + */ + if (cntr->interrupted) + return -EINTR; + if (errno == EAGAIN) + continue; + return -errno; + } + /* Reach EOF. */ + if (result == 0) { + cntr->eof = true; + return 0; + } + + consumed += result; + } + + return 0; +} + +int container_recursive_write(struct container_context *cntr, void *buf, + unsigned int size) +{ + char *src = buf; + ssize_t result; + size_t consumed = 0; + + while (consumed < size && !cntr->interrupted) { + result = write(cntr->fd, src + consumed, size - consumed); + if (result < 0) { + /* + * This descriptor was configured with non-blocking + * mode. EINTR is not cought when get any interrupts. + */ + if (cntr->interrupted) + return -EINTR; + if (errno == EAGAIN) + continue; + return -errno; + } + + consumed += result; + } + + return 0; +} + +enum container_format container_format_from_path(const char *path) +{ + const char *suffix; + const char *pos; + int i; + + for (i = 0; i < ARRAY_SIZE(suffixes); ++i) { + suffix = suffixes[i]; + + /* Check last part of the string. */ + pos = path + strlen(path) - strlen(suffix); + if (!strcmp(pos, suffix)) + return i; + } + + /* Unsupported. */ + return CONTAINER_FORMAT_COUNT; +} + +int container_seek_offset(struct container_context *cntr, off64_t offset) +{ + off64_t pos; + + pos = lseek64(cntr->fd, offset, SEEK_SET); + if (pos < 0) + return -errno; + if (pos != offset) + return -EIO; + + return 0; +} + +/* + * To avoid blocking execution at system call iteration after receiving UNIX + * signals. + */ +static int set_nonblock_flag(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + return -errno; + + return 0; +} + +int container_parser_init(struct container_context *cntr, + const char *const path, unsigned int verbose) +{ + const struct container_parser *parsers[] = { + NULL, + }; + const struct container_parser *parser; + unsigned int size; + int i; + int err; + + /* Open a target descriptor. */ + if (path == NULL || path == '\0') + return -EINVAL; + if (!strcmp(path, "-")) { + cntr->fd = fileno(stdin); + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + } else { + cntr->fd = open(path, O_RDONLY | O_NONBLOCK); + if (cntr->fd < 0) + return -errno; + } + + /* 4 bytes are enough to detect supported containers. */ + err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic)); + if (err < 0) + return err; + for (i = 0; i < ARRAY_SIZE(parsers); ++i) { + parser = parsers[i]; + size = strlen(parser->magic); + if (size > 4) + size = 4; + if (!strncmp(cntr->magic, parser->magic, size)) + break; + } + /* + * Don't forget that the first 4 bytes were already read for magic + * bytes. + */ + cntr->magic_handled = false; + + /* + * Unless detected, use raw container. + */ + if (i == ARRAY_SIZE(parsers)) + return -EINVAL; + + /* Allocate private data for the parser. */ + if (parser->private_size > 0) { + cntr->private_data = malloc(parser->private_size); + if (cntr->private_data == NULL) + return -ENOMEM; + memset(cntr->private_data, 0, parser->private_size); + } + + cntr->type = CONTAINER_TYPE_PARSER; + cntr->process_bytes = container_recursive_read; + cntr->format = parser->format; + cntr->ops = &parser->ops; + cntr->max_size = parser->max_size; + cntr->verbose = verbose; + + return 0; +} + +int container_builder_init(struct container_context *cntr, + const char *const path, enum container_format format, + unsigned int verbose) +{ + const struct container_builder *builders[] = { + NULL, + }; + const struct container_builder *builder; + int err; + + /* Open a target descriptor. */ + if (path == NULL || path == '\0') + return -EINVAL; + if (!strcmp(path, "-")) { + cntr->fd = fileno(stdout); + err = set_nonblock_flag(cntr->fd); + if (err < 0) + return err; + } else { + cntr->fd = open(path, O_RDWR | O_NONBLOCK | O_CREAT, 0644); + if (cntr->fd < 0) + return -errno; + } + + builder = builders[format]; + + /* Allocate private data for the builder. */ + if (builder->private_size > 0) { + cntr->private_data = malloc(builder->private_size); + if (cntr->private_data == NULL) + return -ENOMEM; + memset(cntr->private_data, 0, builder->private_size); + } + + cntr->type = CONTAINER_TYPE_BUILDER; + cntr->process_bytes = container_recursive_write; + cntr->format = builder->format; + cntr->ops = &builder->ops; + cntr->max_size = builder->max_size; + cntr->verbose = verbose; + + return 0; +} + +int container_context_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + int err; + + if (cntr->ops->pre_process) { + err = cntr->ops->pre_process(cntr, format, samples_per_frame, + frames_per_second, frame_count); + if (err < 0) { + printf("%s(): %s\n", __func__, snd_strerror(err)); + return err; + } + } + + cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + cntr->samples_per_frame = *samples_per_frame; + cntr->frames_per_second = *frames_per_second; + + if (cntr->verbose > 0) { + printf(_("Container: %s\n"), cntr_type_labels[cntr->type]); + printf(_(" format: %s\n"), cntr_format_labels[cntr->format]); + printf(_(" sample format: %s\n"), snd_pcm_format_name(*format)); + printf(_(" bytes/sample: %u\n"), cntr->bytes_per_sample); + printf(_(" samples/frame: %u\n"), cntr->samples_per_frame); + printf(_(" frames/second: %u\n"), cntr->frames_per_second); + printf(_(" frames: %lu\n"), *frame_count); + printf("\n"); + } + + return 0; +} + +int container_context_process_frames(struct container_context *cntr, + void *frame_buffer, + unsigned int *frame_count) +{ + char *buf = frame_buffer; + unsigned int size = cntr->bytes_per_sample * cntr->samples_per_frame * + *frame_count; + int err; + + /* All of supported containers include interleaved PCM frames. */ + err = cntr->process_bytes(cntr, buf, size); + if (err < 0) { + *frame_count = 0; + return err; + } + + /* NOTE: For simplicity, drop the last chunk. */ + if (cntr->eof) { + *frame_count = 0; + return 0; + } + + cntr->handled_byte_count += size; + + return 0; +} + +int container_context_post_process(struct container_context *cntr, + uint64_t actual_frame_count) +{ + /* We cannot seek when using standard input/output. */ + if (cntr->fd == fileno(stdin) || cntr->fd == fileno(stdout)) + return 0; + if (cntr->verbose && cntr->handled_byte_count > 0) { + printf("%s:\n", __func__); + printf(" Handled bytes: %lu\n", cntr->handled_byte_count); + } + + if (!cntr->ops || !cntr->ops->post_process) + return 0; + + return cntr->ops->post_process(cntr, actual_frame_count); +} + +void container_context_destroy(struct container_context *cntr) +{ + close(cntr->fd); + if (cntr->private_data) + free(cntr->private_data); +} diff --git a/aplay/container.h b/aplay/container.h new file mode 100644 index 0000000..df1a90c --- /dev/null +++ b/aplay/container.h @@ -0,0 +1,114 @@ +/* + * container.h - a header file for parser/builder of any containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_CONTAINER__H_ +#define __ALSA_UTILS_APLAY_CONTAINER__H_ + +#define _LARGEFILE64_SOURCE +#include <sys/types.h> +#include <unistd.h> + +#include <stdbool.h> +#include <stdint.h> + +/* Not portable to all of UNIX platforms. */ +#include <endian.h> + +#include <alsa/asoundlib.h> + +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) + +enum container_type { + CONTAINER_TYPE_PARSER = 0, + CONTAINER_TYPE_BUILDER, + CONTAINER_TYPE_COUNT, +}; + +enum container_format { + CONTAINER_FORMAT_COUNT, +}; + +struct container_ops; + +struct container_context { + enum container_type type; + int fd; + int (*process_bytes)(struct container_context *cntr, + void *buffer, unsigned int byte_count); + bool magic_handled; + bool eof; + bool interrupted; + + enum container_format format; + uint64_t max_size; + char magic[4]; + const struct container_ops *ops; + void *private_data; + + /* Available after pre-process. */ + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; + + unsigned int verbose; + uint64_t handled_byte_count; +}; + +const char *const container_suffix_from_format(enum container_format format); +enum container_format container_format_from_path(const char *path); +int container_parser_init(struct container_context *cntr, + const char *const path, unsigned int verbose); +int container_builder_init(struct container_context *cntr, + const char *const path, enum container_format format, + unsigned int verbose); +void container_context_destroy(struct container_context *cntr); +int container_context_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count); +int container_context_process_frames(struct container_context *cntr, + void *frame_buffer, + unsigned int *frame_count); +int container_context_post_process(struct container_context *cntr, + uint64_t actual_frame_count); + +/* For internal use in 'container' module. */ + +struct container_ops { + int (*pre_process)(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count); + int (*post_process)(struct container_context *cntr, + uint64_t actual_frame_count); +}; +struct container_parser { + enum container_format format; + const char const *magic; + uint64_t max_size; + struct container_ops ops; + unsigned int private_size; +}; + +struct container_builder { + enum container_format format; + const char const *suffix; + uint64_t max_size; + struct container_ops ops; + unsigned int private_size; +}; + +int container_recursive_read(struct container_context *cntr, void *buf, + unsigned int size); +int container_recursive_write(struct container_context *cntr, void *buf, + unsigned int size); +int container_seek_offset(struct container_context *cntr, off64_t offset); + +#endif
This commit adds support for data of Microsoft/IBM RIFF/Wave format. In this data format, each of field can have value in both of big/little endian-ness. Magic bytes in the beginning of data indicates the endian-ness. --- aplay/Makefile.am | 3 +- aplay/container-riff-wave.c | 549 ++++++++++++++++++++++++++++++++++++++++++++ aplay/container.c | 9 +- aplay/container.h | 4 + 4 files changed, 560 insertions(+), 5 deletions(-) create mode 100644 aplay/container-riff-wave.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 44143e0..854b54e 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -13,7 +13,8 @@ aplay_SOURCES = \ formats.h \ aplay.c \ container.h \ - container.c + container.c \ + container-riff-wave.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/container-riff-wave.c b/aplay/container-riff-wave.c new file mode 100644 index 0000000..c9815c0 --- /dev/null +++ b/aplay/container-riff-wave.c @@ -0,0 +1,549 @@ +/* + * container-riff-wave.c - a parser/builder for a container of RIFF/Wave File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" + +/* + * References: + * - 'Resource Interchange File Format (RIFF)' at msdn.microsoft.com + * - 'Multiple channel audio data and WAVE files' at msdn.microsoft.com + * - RFC 2361 'WAVE and AVI Codec Registries' at ietf.org + * - 'mmreg.h' in Wine project + * - 'mmreg.h' in ReactOS project + */ + +#define RIFF_MAGIC "RIF" /* A common part. */ + +#define RIFF_CHUNK_ID_LE "RIFF" +#define RIFF_CHUNK_ID_BE "RIFX" +#define RIFF_FORM_WAVE "WAVE" +#define FMT_SUBCHUNK_ID "fmt " +#define DATA_SUBCHUNK_ID "data" + +/* + * See 'WAVE and AVI Codec Registries (Historic Registry)' in 'iana.org'. + * https://www.iana.org/assignments/wave-avi-codec-registry/ + */ +enum wave_format { + WAVE_FORMAT_PCM = 0x0001, + WAVE_FORMAT_ADPCM = 0x0002, + WAVE_FORMAT_IEEE_FLOAT = 0x0003, + WAVE_FORMAT_ALAW = 0x0006, + WAVE_FORMAT_MULAW = 0x0007, + WAVE_FORMAT_G723_ADPCM = 0x0014, + /* The others are not supported. */ +}; + +struct format_map { + enum wave_format wformat; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S8}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U8}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S16_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S16_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U16_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U16_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U24_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U24_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S32_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S32_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U32_LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U32_BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S24_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U24_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U24_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S20_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S20_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U20_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U20_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S18_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_S18_3BE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U18_3LE}, + {WAVE_FORMAT_PCM, SND_PCM_FORMAT_U18_3BE}, + {WAVE_FORMAT_ADPCM, SND_PCM_FORMAT_IMA_ADPCM}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT_LE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT_BE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT64_LE}, + {WAVE_FORMAT_IEEE_FLOAT, SND_PCM_FORMAT_FLOAT64_BE}, + {WAVE_FORMAT_ALAW, SND_PCM_FORMAT_A_LAW}, + {WAVE_FORMAT_MULAW, SND_PCM_FORMAT_MU_LAW}, + {WAVE_FORMAT_G723_ADPCM, SND_PCM_FORMAT_G723_24}, + {WAVE_FORMAT_G723_ADPCM, SND_PCM_FORMAT_G723_24_1B}, + {WAVE_FORMAT_G723_ADPCM, SND_PCM_FORMAT_G723_40}, + {WAVE_FORMAT_G723_ADPCM, SND_PCM_FORMAT_G723_40_1B}, +}; + +struct riff_chunk { + uint8_t id[4]; + uint32_t size; + + uint8_t data[0]; +}; + +struct riff_chunk_data { + uint8_t id[4]; + + uint8_t subchunks[0]; +}; + +struct riff_subchunk { + uint8_t id[4]; + uint32_t size; + + uint8_t data[0]; +}; + +struct wave_fmt_subchunk { + uint8_t id[4]; + uint32_t size; + + uint16_t format; + uint16_t samples_per_frame; + uint32_t frames_per_second; + uint32_t average_bytes_per_second; + uint16_t bytes_per_frame; + uint16_t bits_per_sample; + uint8_t extension[0]; +}; + +struct wave_data_subchunk { + uint8_t id[4]; + uint32_t size; + + uint8_t frames[0]; +}; + +struct parser_state { + bool be; + enum wave_format format; + unsigned int samples_per_frame; + unsigned int frames_per_second; + unsigned int average_bytes_per_second; + unsigned int bytes_per_frame; + unsigned int bytes_per_sample; + unsigned int byte_count; +}; + +static int parse_riff_chunk_header(struct parser_state *state, + struct riff_chunk *chunk, + uint64_t *byte_count) +{ + if (!memcmp(chunk->id, RIFF_CHUNK_ID_BE, sizeof(chunk->id))) + state->be = true; + else if (!memcmp(chunk->id, RIFF_CHUNK_ID_LE, sizeof(chunk->id))) + state->be = false; + else + return -EINVAL; + + if (state->be) + *byte_count = be32toh(chunk->size); + else + *byte_count = le32toh(chunk->size); + + return 0; +} + +static int parse_riff_chunk(struct container_context *cntr, + uint64_t *byte_count) +{ + struct parser_state *state = cntr->private_data; + union { + struct riff_chunk chunk; + struct riff_chunk_data chunk_data; + } buf = {0}; + int err; + + /* Chunk header. 4 bytes were alread read to detect container type. */ + memcpy(buf.chunk.id, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&buf.chunk + sizeof(cntr->magic), + sizeof(buf.chunk) - sizeof(cntr->magic)); + if (err < 0) + return err; + err = parse_riff_chunk_header(state, &buf.chunk, byte_count); + if (err < 0) + return err; + + /* Chunk data header. */ + err = container_recursive_read(cntr, &buf, sizeof(buf.chunk_data)); + if (err < 0) + return err; + if (memcmp(buf.chunk_data.id, RIFF_FORM_WAVE, + sizeof(buf.chunk_data.id))) + return -EINVAL; + + return 0; +} + +static void parse_wave_fmt_subchunk(struct parser_state *state, + struct wave_fmt_subchunk *subchunk) +{ + if (state->be) { + state->format = be16toh(subchunk->format); + state->samples_per_frame = be16toh(subchunk->samples_per_frame); + state->frames_per_second = be32toh(subchunk->frames_per_second); + state->average_bytes_per_second = + be32toh(subchunk->average_bytes_per_second); + state->bytes_per_frame = be16toh(subchunk->bytes_per_frame); + state->bytes_per_sample = be16toh(subchunk->bits_per_sample) / 8; + } else { + state->format = le16toh(subchunk->format); + state->samples_per_frame = le16toh(subchunk->samples_per_frame); + state->frames_per_second = le32toh(subchunk->frames_per_second); + state->average_bytes_per_second = + le32toh(subchunk->average_bytes_per_second); + state->bytes_per_frame = le16toh(subchunk->bytes_per_frame); + state->bytes_per_sample = le16toh(subchunk->bits_per_sample) / 8; + } +} + +static void parse_wave_data_subchunk(struct parser_state *state, + struct wave_data_subchunk *subchunk) +{ + if (state->be) + state->byte_count = be32toh(subchunk->size); + else + state->byte_count = le32toh(subchunk->size); +} + +static int parse_wave_subchunk(struct container_context *cntr) +{ + union { + struct riff_subchunk subchunk; + struct wave_fmt_subchunk fmt_subchunk; + struct wave_data_subchunk data_subchunk; + } buf = {0}; + enum { + SUBCHUNK_TYPE_UNKNOWN = -1, + SUBCHUNK_TYPE_FMT, + SUBCHUNK_TYPE_DATA, + } subchunk_type; + struct parser_state *state = cntr->private_data; + unsigned int required_size; + unsigned int subchunk_data_size; + int err; + + while (1) { + err = container_recursive_read(cntr, &buf, + sizeof(buf.subchunk)); + if (err < 0) + return err; + + /* Calculate the size of subchunk data. */ + if (state->be) + subchunk_data_size = be32toh(buf.subchunk.size); + else + subchunk_data_size = le32toh(buf.subchunk.size); + + /* Detect type of subchunk. */ + if (!memcmp(buf.subchunk.id, FMT_SUBCHUNK_ID, + sizeof(buf.subchunk.id))) { + subchunk_type = SUBCHUNK_TYPE_FMT; + } else if (!memcmp(buf.subchunk.id, DATA_SUBCHUNK_ID, + sizeof(buf.subchunk.id))) { + subchunk_type = SUBCHUNK_TYPE_DATA; + } else { + subchunk_type = SUBCHUNK_TYPE_UNKNOWN; + } + + if (subchunk_type != SUBCHUNK_TYPE_UNKNOWN) { + /* Parse data of this subchunk. */ + if (subchunk_type == SUBCHUNK_TYPE_FMT) { + required_size = + sizeof(struct wave_fmt_subchunk) - + sizeof(struct riff_chunk); + } else { + required_size = + sizeof(struct wave_data_subchunk)- + sizeof(struct riff_chunk); + } + + if (subchunk_data_size < required_size) + return -EINVAL; + + err = container_recursive_read(cntr, &buf.subchunk.data, + required_size); + if (err < 0) + return err; + subchunk_data_size -= required_size; + + if (subchunk_type == SUBCHUNK_TYPE_FMT) { + parse_wave_fmt_subchunk(state, + &buf.fmt_subchunk); + } else if (subchunk_type == SUBCHUNK_TYPE_DATA) { + parse_wave_data_subchunk(state, + &buf.data_subchunk); + } + + /* Found frame data. */ + if (subchunk_type == SUBCHUNK_TYPE_DATA) + break; + } + + /* Go to next subchunk. */ + while (subchunk_data_size > 0) { + unsigned int consume; + + if (subchunk_data_size > sizeof(buf)) + consume = sizeof(buf); + else + consume = subchunk_data_size; + + err = container_recursive_read(cntr, &buf, consume); + if (err < 0) + return err; + subchunk_data_size -= consume; + } + } + + return 0; +} + +static int parse_riff_wave_format(struct container_context *cntr) +{ + uint64_t byte_count; + int err; + + err = parse_riff_chunk(cntr, &byte_count); + if (err < 0) + return err; + + err = parse_wave_subchunk(cntr); + if (err < 0) + return err; + + return 0; +} + +static int wave_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct parser_state *state = cntr->private_data; + int width; + int phys_width; + const struct format_map *map; + int i; + int err; + + err = parse_riff_wave_format(cntr); + if (err < 0) + return err; + + width = state->bytes_per_sample * 8; + phys_width = 8 * state->average_bytes_per_second / + state->samples_per_frame / state->frames_per_second; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + map = &format_maps[i]; + if (state->format != map->wformat) + continue; + if (width != snd_pcm_format_width(map->format)) + continue; + if (phys_width != snd_pcm_format_physical_width(map->format)) + continue; + if (state->be != !!snd_pcm_format_big_endian(map->format)) + continue; + + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + /* Set parameters. */ + *format = format_maps[i].format; + *samples_per_frame = state->samples_per_frame; + *frames_per_second = state->frames_per_second; + *frame_count = state->byte_count / state->bytes_per_sample / + state->samples_per_frame; + + return 0; +} + +struct builder_state { + bool be; + enum wave_format format; + unsigned int avail_bytes_in_sample; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; +}; + +static void build_riff_chunk_header(struct riff_chunk *chunk, + uint64_t byte_count, bool be) +{ + uint64_t data_size = sizeof(struct riff_chunk_data) + + sizeof(struct wave_fmt_subchunk) + + sizeof(struct wave_data_subchunk) + byte_count; + + memcpy(chunk->id, RIFF_CHUNK_ID_LE, sizeof(chunk->id)); + if (be) + chunk->size = htobe32(data_size); + else + chunk->size = htole32(data_size); +} + +static void build_subchunk_header(struct riff_subchunk *subchunk, + const char *const form, uint64_t size, + bool be) +{ + memcpy(subchunk->id, form, sizeof(subchunk->id)); + if (be) + subchunk->size = htobe32(size); + else + subchunk->size = htole32(size); +} + +static void build_wave_format_subchunk(struct wave_fmt_subchunk *subchunk, + struct builder_state *state) +{ + unsigned int bytes_per_frame = + state->bytes_per_sample * state->samples_per_frame; + unsigned int average_bytes_per_second = + state->avail_bytes_in_sample * + state->samples_per_frame * state->frames_per_second; + uint64_t size; + + /* No extensions. */ + size = sizeof(struct wave_fmt_subchunk) - sizeof(struct riff_subchunk); + build_subchunk_header((struct riff_subchunk *)subchunk, FMT_SUBCHUNK_ID, + size, state->be); + + if (state->be) { + subchunk->format = htobe16(state->format); + subchunk->samples_per_frame = htobe16(state->samples_per_frame); + subchunk->frames_per_second = htobe32(state->frames_per_second); + subchunk->average_bytes_per_second = + htobe32(average_bytes_per_second); + subchunk->bytes_per_frame = htobe16(bytes_per_frame); + subchunk->bits_per_sample = + htobe16(state->bytes_per_sample * 8); + } else { + subchunk->format = htole16(state->format); + subchunk->samples_per_frame = htole16(state->samples_per_frame); + subchunk->frames_per_second = htole32(state->frames_per_second); + subchunk->average_bytes_per_second = + htole32(average_bytes_per_second); + subchunk->bytes_per_frame = htole16(bytes_per_frame); + subchunk->bits_per_sample = + htole16(state->bytes_per_sample * 8); + } +} + +static void build_wave_data_subchunk(struct wave_data_subchunk *subchunk, + uint64_t byte_count, bool be) +{ + build_subchunk_header((struct riff_subchunk *)subchunk, + DATA_SUBCHUNK_ID, byte_count, be); +} + +static int write_riff_chunk_for_wave(struct container_context *cntr, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + union { + struct riff_chunk chunk; + struct riff_chunk_data chunk_data; + struct wave_fmt_subchunk fmt_subchunk; + struct wave_data_subchunk data_subchunk; + } buf = {0}; + uint64_t byte_count; + int err; + + byte_count = state->bytes_per_sample * state->samples_per_frame * + frame_count; + + /* Chunk header. */ + build_riff_chunk_header(&buf.chunk, byte_count, state->be); + err = container_recursive_write(cntr, &buf, sizeof(buf.chunk)); + if (err < 0) + return err; + + /* Chunk data header. */ + memcpy(buf.chunk_data.id, RIFF_FORM_WAVE, sizeof(buf.chunk_data.id)); + err = container_recursive_write(cntr, &buf, sizeof(buf.chunk_data)); + if (err < 0) + return err; + + /* A subchunk in the chunk data for WAVE format. */ + build_wave_format_subchunk(&buf.fmt_subchunk, state); + err = container_recursive_write(cntr, &buf, sizeof(buf.fmt_subchunk)); + if (err < 0) + return err; + + /* A subchunk in the chunk data for WAVE data. */ + build_wave_data_subchunk(&buf.data_subchunk, byte_count, state->be); + return container_recursive_write(cntr, &buf, sizeof(buf.data_subchunk)); +} + +static int wave_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct builder_state *state = cntr->private_data; + int i; + + /* Validate parameters. */ + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + state->format = format_maps[i].wformat; + state->avail_bytes_in_sample = snd_pcm_format_width(*format) / 8; + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + state->samples_per_frame = *samples_per_frame; + state->frames_per_second = *frames_per_second; + state->be = !!snd_pcm_format_big_endian(*format); + + return write_riff_chunk_for_wave(cntr, *frame_count); +} + +static int wave_builder_post_process(struct container_context *cntr, + uint64_t actual_frame_count) +{ + int err; + + err = container_seek_offset(cntr, 0); + if (err < 0) + return err; + + return write_riff_chunk_for_wave(cntr, actual_frame_count); +} + +const struct container_parser container_parser_riff_wave = { + .format = CONTAINER_FORMAT_RIFF_WAVE, + .magic = RIFF_MAGIC, + .max_size = 0x7fffffff, /* INT_MAX */ + .ops = { + .pre_process = wave_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_riff_wave = { + .format = CONTAINER_FORMAT_RIFF_WAVE, + .max_size = 0x7fffffff, /* INT_MAX */ + .ops = { + .pre_process = wave_builder_pre_process, + .post_process = wave_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/aplay/container.c b/aplay/container.c index 7f8e31b..1b35d4e 100644 --- a/aplay/container.c +++ b/aplay/container.c @@ -20,13 +20,14 @@ static const char *const cntr_type_labels[] = { };
static const char *const cntr_format_labels[] = { - [CONTAINER_FORMAT_COUNT] = "", + [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", };
static const char *const suffixes[] = { - [CONTAINER_FORMAT_COUNT] = "", + [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", };
+ const char *const container_suffix_from_format(enum container_format format) { return suffixes[format]; @@ -146,7 +147,7 @@ int container_parser_init(struct container_context *cntr, const char *const path, unsigned int verbose) { const struct container_parser *parsers[] = { - NULL, + [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, }; const struct container_parser *parser; unsigned int size; @@ -214,7 +215,7 @@ int container_builder_init(struct container_context *cntr, unsigned int verbose) { const struct container_builder *builders[] = { - NULL, + [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, }; const struct container_builder *builder; int err; diff --git a/aplay/container.h b/aplay/container.h index df1a90c..5cb77bd 100644 --- a/aplay/container.h +++ b/aplay/container.h @@ -30,6 +30,7 @@ enum container_type { };
enum container_format { + CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_COUNT, };
@@ -111,4 +112,7 @@ int container_recursive_write(struct container_context *cntr, void *buf, unsigned int size); int container_seek_offset(struct container_context *cntr, off64_t offset);
+extern const struct container_parser container_parser_riff_wave; +extern const struct container_builder container_builder_riff_wave; + #endif
This commit adds support for data of Sparc AU format. In this data format, each of field has value in big endian-ness. --- aplay/Makefile.am | 3 +- aplay/container-au.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/container.c | 6 +- aplay/container.h | 4 + 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 aplay/container-au.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 854b54e..8e4a31f 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -14,7 +14,8 @@ aplay_SOURCES = \ aplay.c \ container.h \ container.c \ - container-riff-wave.c + container-riff-wave.c \ + container-au.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/container-au.c b/aplay/container-au.c new file mode 100644 index 0000000..b418d75 --- /dev/null +++ b/aplay/container-au.c @@ -0,0 +1,203 @@ +/* + * container-au.c - a parser/builder for a container of Sun Audio File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" + +/* + * Reference: + * * http://pubs.opengroup.org/external/auformat.html + */ + +#define AU_MAGIC ".snd" +#define UNKNOWN_SIZE ((uint32_t)~0) + +enum code_id { + CODE_ID_CCIT_MU_LAW_BE = 0x01, + CODE_ID_GENERIC_MBLA_S8 = 0x02, + CODE_ID_GENERIC_MBLA_S16_BE = 0x03, + CODE_ID_GENERIC_MBLA_S32_BE = 0x05, + CODE_ID_IEEE754_FLOAT_S32_BE = 0x06, + CODE_ID_IEEE754_DOUBLE_S64_BE = 0x07, + CODE_ID_CCIT_ADPCM_G721_4BIT_BE = 0x17, + CODE_ID_CCIT_ADPCM_G723_3BIT_BE = 0x19, + CODE_ID_CCIT_A_LAW_BE = 0x1b, +}; + +struct format_map { + enum code_id code_id; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {CODE_ID_CCIT_MU_LAW_BE, SND_PCM_FORMAT_MU_LAW}, + {CODE_ID_GENERIC_MBLA_S8, SND_PCM_FORMAT_S8}, + {CODE_ID_GENERIC_MBLA_S16_BE, SND_PCM_FORMAT_S16_BE}, + {CODE_ID_GENERIC_MBLA_S32_BE, SND_PCM_FORMAT_S32_BE}, + {CODE_ID_IEEE754_FLOAT_S32_BE, SND_PCM_FORMAT_FLOAT_BE}, + {CODE_ID_IEEE754_DOUBLE_S64_BE, SND_PCM_FORMAT_FLOAT64_BE}, + /* CODE_ID_CCIT_ADPCM_G721_4BIT_BE is not supported by ALSA. */ + {CODE_ID_CCIT_ADPCM_G723_3BIT_BE, SND_PCM_FORMAT_G723_24}, + {CODE_ID_CCIT_A_LAW_BE, SND_PCM_FORMAT_A_LAW}, +}; + +struct container_header { + uint8_t magic[4]; + uint32_t hdr_size; + uint32_t data_size; + uint32_t code_id; + uint32_t frames_per_second; + uint32_t samples_per_frame; +}; + +struct container_annotation { + uint32_t chunks[0]; +}; + +struct parser_state { + enum code_id code_id; + unsigned int samples_per_frame; + unsigned int bytes_per_sample; +}; + +static int au_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct parser_state *state = cntr->private_data; + struct container_header header; + unsigned int data_size; + enum code_id code_id; + int i; + int err; + + /* Parse header. 4 bytes are enough to detect supported containers. */ + memcpy(&header.magic, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&header + sizeof(cntr->magic), + sizeof(header) - sizeof(cntr->magic)); + if (err < 0) + return err; + + if (memcmp(header.magic, AU_MAGIC, sizeof(header.magic)) != 0) + return -EINVAL; + if (be32toh(header.hdr_size) != sizeof(struct container_header)) + return -EINVAL; + + data_size = be32toh(header.data_size); + code_id = be32toh(header.code_id); + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].code_id == code_id) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + *format = format_maps[0].format; + *frames_per_second = be32toh(header.frames_per_second); + *samples_per_frame = be32toh(header.samples_per_frame); + + state->code_id = code_id; + state->samples_per_frame = *samples_per_frame; + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + + *frame_count = data_size / state->bytes_per_sample / *samples_per_frame; + + return 0; +} + +struct builder_state { + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_second; + enum code_id code_id; +}; + +static void build_container_header(struct builder_state *state, + struct container_header *header, + unsigned int frames_per_second, + uint64_t byte_count) +{ + memcpy(header->magic, AU_MAGIC, sizeof(header->magic)); + header->hdr_size = htobe32(sizeof(struct container_header)); + header->data_size = htobe32(byte_count); + header->code_id = htobe32(state->code_id); + header->frames_per_second = htobe32(frames_per_second); + header->samples_per_frame = htobe32(state->samples_per_frame); +} + +static int write_container_header(struct container_context *cntr, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + uint64_t byte_count; + struct container_header header; + + byte_count = state->bytes_per_sample * state->samples_per_frame * + frame_count; + + build_container_header(state, &header, state->frames_per_second, + byte_count); + + return container_recursive_write(cntr, &header, sizeof(header)); +} + +static int au_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct builder_state *status = cntr->private_data; + int i; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + status->code_id = format_maps[i].code_id; + status->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + status->samples_per_frame = *samples_per_frame; + + return write_container_header(cntr, *frame_count); +} + +static int au_builder_post_process(struct container_context *cntr, + uint64_t actual_frame_count) +{ + int err; + + err = container_seek_offset(cntr, 0); + if (err < 0) + return err; + + return write_container_header(cntr, actual_frame_count); +} + +const struct container_parser container_parser_au = { + .format = CONTAINER_FORMAT_AU, + .magic = AU_MAGIC, + .max_size = 0x7fffffffffffffff, /* LLONG_MAX */ + .ops = { + .pre_process = au_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_au = { + .format = CONTAINER_FORMAT_AU, + .max_size = 0x7fffffffffffffff, /* LLONG_MAX */ + .ops = { + .pre_process = au_builder_pre_process, + .post_process = au_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/aplay/container.c b/aplay/container.c index 1b35d4e..6214bbf 100644 --- a/aplay/container.c +++ b/aplay/container.c @@ -21,10 +21,12 @@ static const char *const cntr_type_labels[] = {
static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", + [CONTAINER_FORMAT_AU] = "au", };
static const char *const suffixes[] = { - [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", + [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", + [CONTAINER_FORMAT_AU] = ".au", };
@@ -148,6 +150,7 @@ int container_parser_init(struct container_context *cntr, { const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, + [CONTAINER_FORMAT_AU] = &container_parser_au, }; const struct container_parser *parser; unsigned int size; @@ -216,6 +219,7 @@ int container_builder_init(struct container_context *cntr, { const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, + [CONTAINER_FORMAT_AU] = &container_builder_au, }; const struct container_builder *builder; int err; diff --git a/aplay/container.h b/aplay/container.h index 5cb77bd..73029f4 100644 --- a/aplay/container.h +++ b/aplay/container.h @@ -31,6 +31,7 @@ enum container_type {
enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, + CONTAINER_FORMAT_AU, CONTAINER_FORMAT_COUNT, };
@@ -115,4 +116,7 @@ int container_seek_offset(struct container_context *cntr, off64_t offset); extern const struct container_parser container_parser_riff_wave; extern const struct container_builder container_builder_riff_wave;
+extern const struct container_parser container_parser_au; +extern const struct container_builder container_builder_au; + #endif
This commit adds support for data of Creative Tech. voice format. In this data format, each of field can have value in little endian-ness. A file can include multiple data streams in each block. However for simplicity, this commit supports the first block for data stream. --- aplay/Makefile.am | 3 +- aplay/container-voc.c | 736 ++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/container.c | 4 + aplay/container.h | 4 + 4 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 aplay/container-voc.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 8e4a31f..ef70bcb 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -15,7 +15,8 @@ aplay_SOURCES = \ container.h \ container.c \ container-riff-wave.c \ - container-au.c + container-au.c \ + container-voc.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/container-voc.c b/aplay/container-voc.c new file mode 100644 index 0000000..d9491cd --- /dev/null +++ b/aplay/container-voc.c @@ -0,0 +1,736 @@ +/* + * container-voc.c - a parser/builder for a container of Creative Voice File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" + +/* + * References: + * - 'Creative Voice (VOC) file format' documentation in libRealSpace + */ + +#define VOC_MAGIC "Creative Voice File\x1A" +#define VOC_VERSION_1_10 0x010a +#define VOC_VERSION_1_20 0x0114 + +enum block_type { + BLOCK_TYPE_TERMINATOR = 0x00, + BLOCK_TYPE_V110_DATA = 0x01, + BLOCK_TYPE_CONTINUOUS_DATA = 0x02, + BLOCK_TYPE_SILENCE = 0x03, + BLOCK_TYPE_MARKER = 0x04, + BLOCK_TYPE_STRING = 0x05, + BLOCK_TYPE_REPEAT_START = 0x06, + BLOCK_TYPE_REPEAT_END = 0x07, + BLOCK_TYPE_EXTENDED_V110_FORMAT = 0x08, + BLOCK_TYPE_V120_DATA = 0x09, +}; + +enum code_id { + /* Version 1.10. */ + CODE_ID_GENERIC_MBLA_U8 = 0x00, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_4BIT_LE = 0x01, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_3BIT_LE = 0x02, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_2BIT_LE = 0x03, + /* Version 1.20. */ + CODE_ID_GENERIC_MBLA_S16_LE = 0x04, + CODE_ID_CCIT_A_LAW_LE = 0x06, + CODE_ID_CCIT_MU_LAW_LE = 0x07, + CODE_ID_CREATIVE_ADPCM_16BIT_TO_4BIT_LE = 0x2000, +}; + +struct format_map { + unsigned int minimal_version; + enum code_id code_id; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {VOC_VERSION_1_10, CODE_ID_GENERIC_MBLA_U8, SND_PCM_FORMAT_U8}, + {VOC_VERSION_1_20, CODE_ID_GENERIC_MBLA_S16_LE, SND_PCM_FORMAT_S16_LE}, + {VOC_VERSION_1_20, CODE_ID_CCIT_A_LAW_LE, SND_PCM_FORMAT_A_LAW}, + {VOC_VERSION_1_20, CODE_ID_CCIT_MU_LAW_LE, SND_PCM_FORMAT_MU_LAW}, + /* The other formats are not supported by ALSA. */ +}; + +struct container_header { + uint8_t magic[20]; + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; +}; + +/* A format for data blocks except for terminator type. */ +struct block_header { + uint8_t type; + uint8_t size[3]; + + uint8_t data[0]; +}; + +/* Data block for terminator type has an exceptional format. */ +struct block_terminator { + uint8_t type; +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const) + */ +struct block_v110_data { + uint8_t type; + uint8_t size[3]; /* Equals to (2 + the size of frames). */ + + uint8_t time_const; + uint8_t code_id; + uint8_t samples[0]; /* Aligned to big-endian. */ +}; + +struct block_continuous_data { + uint8_t type; + uint8_t size[3]; /* Equals to the size of frames. */ + + uint8_t samples[0]; /* Aligned to big-endian. */ +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const). + */ +struct block_silence { + uint8_t type; + uint8_t size[3]; /* Equals to 3. */ + + uint16_t frame_count; + uint8_t time_const; +}; + +struct block_marker { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t mark; +}; + +struct block_string { + uint8_t type; + uint8_t size[3]; /* Equals to the length of string with 0x00. */ + + uint8_t chars[0]; +}; + +struct block_repeat_start { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t count; +}; + +struct block_repeat_end { + uint8_t type; + uint8_t size[3]; /* Equals to 0. */ +}; + +/* + * Extended v1.10 format: + * - manaural/stereo. + * - frames_per_second = + * 256,000,000 / (samples_per_frame * (65536 - time_const)). + * - Appear just before v110_data block. + */ +struct block_extended_v110_format { + uint8_t type; + uint8_t size[3]; /* Equals to 4. */ + + uint16_t time_const; + uint8_t code_id; + uint8_t ch_mode; /* 0 is monaural, 1 is stereo. */ +}; + +/* + * v1.20 format: + * - monaural/stereo. + * - 8/16 bits_per_sample. + * - time_const is not used. + * - code_id is extended. + */ +struct block_v120_format { + uint8_t type; + uint8_t size[3]; /* Equals to (12 + ). */ + + uint32_t frames_per_second; + uint8_t bits_per_sample; + uint8_t samples_per_frame; + uint16_t code_id; + uint8_t reserved[4]; + + uint8_t samples[0]; +}; + +/* Aligned to little endian order but 24 bits field. */ +static unsigned int parse_block_data_size(uint8_t fields[3]) +{ + return (fields[2] << 16) | (fields[1] << 8) | fields[0]; +} + +static void build_block_data_size(uint8_t fields[3], unsigned int size) +{ + fields[0] = (size & 0x0000ff); + fields[1] = (size & 0x00ff00) >> 8; + fields[2] = (size & 0xff0000) >> 16; +} + +static unsigned int build_time_constant(unsigned int frames_per_second, + unsigned int samples_per_frame, + bool extended) +{ + unsigned int samples_per_second = samples_per_frame * frames_per_second; + + /* 16 bits are available for this purpose. */ + if (extended) + return 65536 - 256000000 / samples_per_second; + + /* Should be within 8 bit. */ + return 256 - 1000000 / samples_per_second; +} + +static unsigned int parse_time_constant(unsigned int time_const, + unsigned int samples_per_frame, + bool extended) +{ + if (extended) + return 256000000 / (samples_per_frame * (65536 - time_const)); + + return 1000000 / (256 - time_const); +} + +struct parser_state { + unsigned int version; + bool extended; + + unsigned int frames_per_second; + unsigned int samples_per_frame; + unsigned int bytes_per_sample; + enum code_id code_id; + unsigned int byte_count; +}; + +static int parse_container_header(struct parser_state *state, + struct container_header *header) +{ + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; + + hdr_size = le16toh(header->hdr_size); + version = le16toh(header->version); + version_compr = le16toh(header->version_compr); + + if (!memcmp(header->magic, VOC_MAGIC, sizeof(header->magic))) + return -EIO; + + if (hdr_size != sizeof(*header)) + return -EIO; + + if (version_compr != 0x1234 - ~version) + return -EIO; + + if (version != VOC_VERSION_1_10 || version != VOC_VERSION_1_20) + return -EIO; + + state->version = version; + + return 0; +} + +static bool check_code_id(uint8_t code_id, unsigned int version) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].minimal_version > version) + continue; + if (format_maps[i].code_id == code_id) + return true; + } + + return false; +} + +static int parse_v120_format_block(struct parser_state *state, + struct block_v120_format *block) +{ + state->frames_per_second = le32toh(block->frames_per_second); + state->bytes_per_sample = block->bits_per_sample / 8; + state->samples_per_frame = block->samples_per_frame; + state->code_id = le16toh(block->code_id); + state->byte_count = parse_block_data_size(block->size) - 12; + + if (check_code_id(state->code_id, VOC_VERSION_1_20)) + return -EIO; + + return 0; +} + +static int parse_extended_v110_format(struct parser_state *state, + struct block_extended_v110_format *block) +{ + unsigned int time_const; + + state->code_id = block->code_id; + if (check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + if (block->ch_mode == 0) + state->samples_per_frame = 1; + else if (block->ch_mode == 1) + state->samples_per_frame = 2; + else + return -EIO; + + time_const = le16toh(block->time_const); + state->frames_per_second = + parse_time_constant(time_const, state->samples_per_frame, true); + if (state->frames_per_second == 0) + return -EIO; + + state->extended = true; + + return 0; +} + +static int parse_v110_data(struct parser_state *state, + struct block_v110_data *block) +{ + unsigned int time_const; + + if (!state->extended) { + state->code_id = block->code_id; + if (check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + time_const = block->time_const; + state->frames_per_second = + parse_time_constant(time_const, 1, false); + if (state->frames_per_second == 0) + return -EIO; + } + + state->bytes_per_sample = 1; + state->byte_count = parse_block_data_size(block->size) - 2; + + return 0; +} + +static int detect_container_version(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct container_header header = {0}; + int err; + + /* 4 bytes were alread read to detect container type. */ + memcpy(&header.magic, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&header + sizeof(cntr->magic), + sizeof(header) - sizeof(cntr->magic)); + if (err < 0) + return err; + + return parse_container_header(state, &header); +} + +static int allocate_for_block_cache(struct container_context *cntr, + struct block_header *header, void **buf) +{ + unsigned int block_size; + char *cache; + int err; + + if (header->type == BLOCK_TYPE_V110_DATA) + block_size = sizeof(struct block_v110_data); + else if (header->type == BLOCK_TYPE_CONTINUOUS_DATA) + block_size = sizeof(struct block_continuous_data); + else if (header->type == BLOCK_TYPE_V120_DATA) + block_size = sizeof(struct block_v120_format); + else + block_size = parse_block_data_size(header->size); + + cache = malloc(block_size); + if (cache == NULL) + return -ENOMEM; + memset(cache, 0, block_size); + + memcpy(cache, header, sizeof(*header)); + err = container_recursive_read(cntr, cache + sizeof(*header), + block_size - sizeof(*header)); + if (err < 0) { + free(cache); + return err; + } + + *buf = cache; + + return 0; +} + +static int cache_data_block(struct container_context *cntr, + struct block_header *header, void **buf) +{ + int err; + + /* Check type of this block. */ + err = container_recursive_read(cntr, header, sizeof(*header)); + if (err < 0) + return err; + if (header->type > BLOCK_TYPE_V120_DATA) + return -EIO; + if (header->type == BLOCK_TYPE_TERMINATOR) + return 0; + + /* Check size of this block. If the block includes a batch of data, */ + err = container_recursive_read(cntr, &header->size, + sizeof(header->size)); + if (err < 0) + return err; + + return allocate_for_block_cache(cntr, header, buf); +} + +static int detect_format_block(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct block_header header; + void *buf; + int err; + +again: + err = cache_data_block(cntr, &header, &buf); + if (err < 0) + return err; + + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) { + err = parse_extended_v110_format(state, buf); + } else if (header.type == BLOCK_TYPE_V120_DATA) { + err = parse_v120_format_block(state, buf); + } else if (header.type == BLOCK_TYPE_V110_DATA) { + err = parse_v110_data(state, buf); + } else { + free(buf); + goto again; + } + + free(buf); + + if (err < 0) { + return err; + } + + /* Expect to detect block_v110_data. */ + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) + goto again; + + return 0; +} + +static int voc_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct parser_state *state = cntr->private_data; + int i; + int err; + + err = detect_container_version(cntr); + if (err < 0) + return err; + + err = detect_format_block(cntr); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].code_id == state->code_id) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + *format = format_maps[i].format; + *samples_per_frame = state->samples_per_frame; + *frames_per_second = state->frames_per_second; + + /* This program handles PCM frames in this data block only. */ + *frame_count = state->byte_count / snd_pcm_format_width(*format) / + state->samples_per_frame; + + return 0; +} + +struct builder_state { + unsigned int version; + bool extended; + + unsigned int samples_per_frame; + unsigned int bytes_per_sample; +}; + +static int write_container_header(struct container_context *cntr, void *buf) +{ + struct builder_state *state = cntr->private_data; + struct container_header *header = buf; + + /* Process container header. */ + memcpy(header->magic, VOC_MAGIC, sizeof(header->magic)); + header->hdr_size = htole16(sizeof(header)); + header->version = htole16(state->version); + header->version_compr = htole16(0x1234 + ~state->version); + + return container_recursive_write(cntr, &header, sizeof(*header)); +} + +static int write_v120_format_block(struct container_context *cntr, + struct block_v120_format *block, + unsigned int frames_per_second, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + uint64_t byte_count; + + block->type = BLOCK_TYPE_V120_DATA; + byte_count = frame_count * state->samples_per_frame * + state->bytes_per_sample; + build_block_data_size(block->size, 12 + byte_count); + + block->frames_per_second = htole32(frames_per_second); + block->bits_per_sample = state->bytes_per_sample * 8; + block->samples_per_frame = state->samples_per_frame; + if (state->bytes_per_sample == 1) + block->code_id = htole16(CODE_ID_GENERIC_MBLA_U8); + else + block->code_id = htole16(CODE_ID_GENERIC_MBLA_S16_LE); + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_extended_v110_format_block(struct container_context *cntr, + unsigned int frames_per_second, + struct block_extended_v110_format *block) +{ + struct builder_state *state = cntr->private_data; + unsigned int time_const; + + block->type = BLOCK_TYPE_EXTENDED_V110_FORMAT; + build_block_data_size(block->size, 4); + + /* 16 bits are available for this purpose. */ + time_const = build_time_constant(frames_per_second, + state->samples_per_frame, true); + block->time_const = htobe16(time_const); + block->code_id = CODE_ID_GENERIC_MBLA_U8; + + if (state->samples_per_frame == 1) + block->ch_mode = 0; + else + block->ch_mode = 1; + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_v110_format_block(struct container_context *cntr, + struct block_v110_data *block, + unsigned int frames_per_second, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + uint64_t byte_count; + + block->type = BLOCK_TYPE_V110_DATA; + byte_count = state->bytes_per_sample * state->samples_per_frame * + frame_count; + build_block_data_size(block->size, 2 + byte_count); + + /* This field was obsoleted by extension. */ + block->time_const = build_time_constant(frames_per_second, 1, false); + block->code_id = CODE_ID_GENERIC_MBLA_U8; + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_data_blocks(struct container_context *cntr, + unsigned int frames_per_second, + uint64_t frame_count) +{ + union { + struct container_header header; + struct block_v110_data v110_data; + struct block_extended_v110_format extended_v110_format; + struct block_v120_format v120_format; + } buf = {0}; + struct builder_state *state = cntr->private_data; + int err; + + err = write_container_header(cntr, &buf.header); + if (err < 0) + return err; + + if (state->version == VOC_VERSION_1_20) { + err = write_v120_format_block(cntr, &buf.v120_format, + frames_per_second, frame_count); + } else { + if (state->extended) { + err = write_extended_v110_format_block(cntr, + frames_per_second, + &buf.extended_v110_format); + if (err < 0) + return err; + } + err = write_v110_format_block(cntr, &buf.v110_data, + frames_per_second, frame_count); + } + + return err; +} + +static int voc_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct builder_state *state = cntr->private_data; + bool extended = false; + int i; + + /* Validate parameters. */ + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + /* Decide container version. */ + if (format_maps[i].minimal_version == VOC_VERSION_1_10) { + if (*samples_per_frame > 2) + return -EINVAL; + extended = !!(*samples_per_frame == 2); + } + + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + state->samples_per_frame = *samples_per_frame; + state->version = format_maps[i].minimal_version; + state->extended = extended; + + return write_data_blocks(cntr, *frames_per_second, *frame_count); +} + +static int write_block_terminator(struct container_context *cntr) +{ + struct block_terminator block = {0}; + + block.type = BLOCK_TYPE_TERMINATOR; + return container_recursive_write(cntr, &block, sizeof(block)); +} + +static int write_size_to_v120_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + off64_t offset; + uint64_t actual_byte_count; + uint8_t size_field[3]; + int err; + + offset = sizeof(struct container_header) + sizeof(uint8_t); + err = container_seek_offset(cntr, offset); + if (err < 0) + return err; + + /* Don't forget to add 12 bytes for this block. */ + actual_byte_count = 12 + actual_frame_count * + state->samples_per_frame * state->bytes_per_sample; + size_field[0] = (actual_byte_count & 0x0000ff); + size_field[1] = (actual_byte_count & 0x00ff00) >> 8; + size_field[2] = (actual_byte_count & 0xff0000) >> 16; + + return container_recursive_write(cntr, &size_field, sizeof(size_field)); +} + +static int write_size_to_v110_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + off64_t offset; + uint64_t actual_byte_count; + uint8_t size_field[3]; + int err; + + offset = sizeof(struct container_header) + sizeof(uint8_t); + if (state->extended) + offset += sizeof(struct block_extended_v110_format); + err = container_seek_offset(cntr, offset); + if (err < 0) + return err; + + /* Don't forget to add 2 bytes for this block. */ + actual_byte_count = 2 + actual_frame_count; + size_field[0] = (actual_byte_count & 0x000000); + size_field[1] = (actual_byte_count & 0x000000) >> 8; + size_field[2] = (actual_byte_count & 0x000000) >> 16; + + return container_recursive_write(cntr, &size_field, sizeof(size_field)); +} + +static int write_size_to_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + int err; + + if (state->version == VOC_VERSION_1_20) + err = write_size_to_v120_format_block(cntr, actual_frame_count); + else + err = write_size_to_v110_format_block(cntr, actual_frame_count); + + return err; +} + +static int voc_builder_post_process(struct container_context *cntr, + uint64_t actual_frame_count) +{ + int err; + + err = write_block_terminator(cntr); + if (err < 0) + return err; + + return write_size_to_format_block(cntr, actual_frame_count); +} + +const struct container_parser container_parser_voc = { + .format = CONTAINER_FORMAT_VOC, + .magic = VOC_MAGIC, + .max_size = 16000000ll, + .ops = { + .pre_process = voc_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_voc = { + .format = CONTAINER_FORMAT_VOC, + .max_size = 16000000ll, + .ops = { + .pre_process = voc_builder_pre_process, + .post_process = voc_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/aplay/container.c b/aplay/container.c index 6214bbf..1cd7ada 100644 --- a/aplay/container.c +++ b/aplay/container.c @@ -22,11 +22,13 @@ static const char *const cntr_type_labels[] = { static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", [CONTAINER_FORMAT_AU] = "au", + [CONTAINER_FORMAT_VOC] = "voc", };
static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", + [CONTAINER_FORMAT_VOC] = ".voc", };
@@ -151,6 +153,7 @@ int container_parser_init(struct container_context *cntr, const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, [CONTAINER_FORMAT_AU] = &container_parser_au, + [CONTAINER_FORMAT_VOC] = &container_parser_voc, }; const struct container_parser *parser; unsigned int size; @@ -220,6 +223,7 @@ int container_builder_init(struct container_context *cntr, const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, [CONTAINER_FORMAT_AU] = &container_builder_au, + [CONTAINER_FORMAT_VOC] = &container_builder_voc, }; const struct container_builder *builder; int err; diff --git a/aplay/container.h b/aplay/container.h index 73029f4..994ff9b 100644 --- a/aplay/container.h +++ b/aplay/container.h @@ -32,6 +32,7 @@ enum container_type { enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_AU, + CONTAINER_FORMAT_VOC, CONTAINER_FORMAT_COUNT, };
@@ -119,4 +120,7 @@ extern const struct container_builder container_builder_riff_wave; extern const struct container_parser container_parser_au; extern const struct container_builder container_builder_au;
+extern const struct container_parser container_parser_voc; +extern const struct container_builder container_builder_voc; + #endif
This commit adds support for raw data without any headers/chunks/blocks. --- aplay/container.c | 30 ++++++++++++++++++++++++++++-- aplay/container.h | 1 + 2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/aplay/container.c b/aplay/container.c index 1cd7ada..f8fe296 100644 --- a/aplay/container.c +++ b/aplay/container.c @@ -23,14 +23,24 @@ static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", [CONTAINER_FORMAT_AU] = "au", [CONTAINER_FORMAT_VOC] = "voc", + [CONTAINER_FORMAT_RAW] = "raw", };
static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", [CONTAINER_FORMAT_VOC] = ".voc", + [CONTAINER_FORMAT_RAW] = "", };
+/* Raw container. */ +static const struct container_parser container_parser_raw = { + .format = CONTAINER_FORMAT_RAW, +}; + +static const struct container_builder container_builder_raw = { + .format = CONTAINER_FORMAT_RAW, +};
const char *const container_suffix_from_format(enum container_format format) { @@ -112,7 +122,7 @@ enum container_format container_format_from_path(const char *path) }
/* Unsupported. */ - return CONTAINER_FORMAT_COUNT; + return CONTAINER_FORMAT_RAW; }
int container_seek_offset(struct container_context *cntr, off64_t offset) @@ -196,7 +206,7 @@ int container_parser_init(struct container_context *cntr, * Unless detected, use raw container. */ if (i == ARRAY_SIZE(parsers)) - return -EINVAL; + parser = &container_parser_raw;
/* Allocate private data for the parser. */ if (parser->private_size > 0) { @@ -224,6 +234,7 @@ int container_builder_init(struct container_context *cntr, [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, [CONTAINER_FORMAT_AU] = &container_builder_au, [CONTAINER_FORMAT_VOC] = &container_builder_voc, + [CONTAINER_FORMAT_RAW] = &container_builder_raw, }; const struct container_builder *builder; int err; @@ -306,6 +317,21 @@ int container_context_process_frames(struct container_context *cntr, *frame_count; int err;
+ /* + * A parser of cotainers already read first 4 bytes to detect format + * of container, however they includes PCM frames when any format was + * undetected. Surely to write out them. + */ + if (!cntr->magic_handled && cntr->type == CONTAINER_TYPE_PARSER && + cntr->format == CONTAINER_FORMAT_RAW) { + memcpy(buf, cntr->magic, sizeof(cntr->magic)); + buf += sizeof(cntr->magic); + size -= sizeof(cntr->magic); + cntr->magic_handled = true; + + cntr->handled_byte_count += sizeof(cntr->magic); + } + /* All of supported containers include interleaved PCM frames. */ err = cntr->process_bytes(cntr, buf, size); if (err < 0) { diff --git a/aplay/container.h b/aplay/container.h index 994ff9b..01ad7af 100644 --- a/aplay/container.h +++ b/aplay/container.h @@ -33,6 +33,7 @@ enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_AU, CONTAINER_FORMAT_VOC, + CONTAINER_FORMAT_RAW, CONTAINER_FORMAT_COUNT, };
In current implementation of aplay, several files can be handled as source or destination of handled PCM frames, by an option '--separate-channels' (-I).
On the other hand, in ALSA kernel/user interface, several types of buffer are used to communicate between applications/hardwares; - mapped page frame with interleaved frames - mapped page frame with non-interleaved frames - buffer in user space with interleaved frames - a list of buffer in user space for non-interleaved frames
This commit adds an abstraction to convert frame alignment between these two sides, named as 'aligner'. This gets buffer pointer for the interface and call functions of 'container' to write/read files. This includes two types; muxer and demuxer. The 'muxer' is for playback direction, to construct playback buffer with PCM frames from several files. The 'demuxer' is for capture direction, to split PCM frames from capture buffer to each of file. --- aplay/Makefile.am | 5 ++- aplay/aligner.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/aligner.h | 80 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 aplay/aligner.c create mode 100644 aplay/aligner.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index ef70bcb..59052d9 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -7,7 +7,8 @@ bin_PROGRAMS = aplay man_MANS = aplay.1 arecord.1 noinst_HEADERS = \ formats.h \ - container.h + container.h \ + aligner.h
aplay_SOURCES = \ formats.h \ @@ -17,6 +18,8 @@ aplay_SOURCES = \ container-riff-wave.c \ container-au.c \ container-voc.c + aligner.h \ + aligner.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/aligner.c b/aplay/aligner.c new file mode 100644 index 0000000..db59fc8 --- /dev/null +++ b/aplay/aligner.c @@ -0,0 +1,104 @@ +/* + * aligner.c - a muxer/demuxer between data frams and file containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "aligner.h" + +#include <gettext.h> + +static const char *const aligner_type_labels[] = { + [ALIGNER_TYPE_MUXER] = "muxer", + [ALIGNER_TYPE_DEMUXER] = "demuxer", +}; + +static const char *const aligner_target_labels[] = { + [ALIGNER_TARGET_COUNT] = "", +}; + +int aligner_context_init(struct aligner_context *aligner, + enum aligner_type type, unsigned int cntr_count, + unsigned int verbose) +{ + const struct aligner_data *data = NULL; + + aligner->ops = &data->ops; + aligner->type = type; + + aligner->private_data = malloc(data->private_size); + if (aligner->private_data == NULL) + return -ENOMEM; + memset(aligner->private_data, 0, data->private_size); + + aligner->cntr_count = cntr_count; + aligner->verbose = verbose; + + return 0; +} + +int aligner_context_pre_process(struct aligner_context *aligner, + snd_pcm_access_t access, + snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + struct container_context *cntrs) +{ + int err; + + /* + * The purpose of multiple target is to mux/demux each channels to/from + * containers. + */ + if (aligner->target == ALIGNER_TARGET_MULTIPLE && + samples_per_frame != aligner->cntr_count) + return -EINVAL; + + aligner->access = access; + aligner->bytes_per_sample = snd_pcm_format_physical_width(format) / 8; + aligner->samples_per_frame = samples_per_frame; + aligner->frames_per_buffer = frames_per_buffer; + + err = aligner->ops->pre_process(aligner, cntrs, aligner->cntr_count); + if (err < 0) + return err; + + if (aligner->verbose > 0) { + printf(_("Aligner: %s\n"), + aligner_type_labels[aligner->type]); + printf(_(" target: %s\n"), + aligner_target_labels[aligner->target]); + printf(_(" access: %s\n"), + snd_pcm_access_name(aligner->access)); + printf(_(" bytes/sample: %u\n"), aligner->bytes_per_sample); + printf(_(" samples/frame: %u\n"), aligner->samples_per_frame); + printf(_(" frames/buffer: %lu\n"), aligner->frames_per_buffer); + printf("\n"); + } + + return 0; +} + +int aligner_context_process_frames(struct aligner_context *aligner, + void *frame_buffer, + unsigned int *frame_count, + struct container_context *cntrs) +{ + return aligner->ops->process_frames(aligner, frame_buffer, frame_count, + cntrs, aligner->cntr_count); +} + +void aligner_context_post_process(struct aligner_context *aligner) +{ + if (aligner->ops && aligner->ops->post_process) + aligner->ops->post_process(aligner); +} + +void aligner_context_destroy(struct aligner_context *aligner) +{ + if (aligner->private_data) + free(aligner->private_data); + aligner->private_data = NULL; +} diff --git a/aplay/aligner.h b/aplay/aligner.h new file mode 100644 index 0000000..441dda4 --- /dev/null +++ b/aplay/aligner.h @@ -0,0 +1,80 @@ +/* + * aligner.h - a header muxer/demuxer between data frames and file containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_ALIGNER__H_ +#define __ALSA_UTILS_APLAY_ALIGNER__H_ + +#include "container.h" + +#include <string.h> + +enum aligner_type { + ALIGNER_TYPE_MUXER = 0, + ALIGNER_TYPE_DEMUXER, + ALIGNER_TYPE_COUNT, +}; + +enum aligner_target { + ALIGNER_TARGET_COUNT, +}; + +struct aligner_ops; + +struct aligner_context { + enum aligner_type type; + enum aligner_target target; + const struct aligner_ops *ops; + unsigned int private_size; + + void *private_data; + unsigned int cntr_count; + + /* A part of parameters of PCM substream. */ + snd_pcm_access_t access; + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + snd_pcm_uframes_t frames_per_buffer; + + unsigned int verbose; +}; + +int aligner_context_init(struct aligner_context *aligner, + enum aligner_type type, unsigned int cntr_count, + unsigned int verbose); +int aligner_context_pre_process(struct aligner_context *aligner, + snd_pcm_access_t access, + snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_buffer, + struct container_context *cntrs); +int aligner_context_process_frames(struct aligner_context *aligner, + void *frame_buffer, + unsigned int *frame_count, + struct container_context *cntrs); +void aligner_context_post_process(struct aligner_context *aligner); +void aligner_context_destroy(struct aligner_context *aligner); + +/* For internal use in 'aligner' module. */ + +struct aligner_ops { + int (*pre_process)(struct aligner_context *aligner, + struct container_context *cntrs, + unsigned int cntr_count); + int (*process_frames)(struct aligner_context *aligner, + void *frame_buffer, unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count); + void (*post_process)(struct aligner_context *aligner); +}; + +struct aligner_data { + struct aligner_ops ops; + unsigned int private_size; +}; + +#endif
This commit adds support for aligner with 'single' target. This handles one file via 'container' functions. --- aplay/Makefile.am | 5 +- aplay/aligner-single.c | 256 +++++++++++++++++++++++++++++++++++++++++++++++++ aplay/aligner.c | 21 ++-- aplay/aligner.h | 4 + 4 files changed, 275 insertions(+), 11 deletions(-) create mode 100644 aplay/aligner-single.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 59052d9..68f6411 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -17,9 +17,10 @@ aplay_SOURCES = \ container.c \ container-riff-wave.c \ container-au.c \ - container-voc.c + container-voc.c \ aligner.h \ - aligner.c + aligner.c \ + aligner-single.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/aligner-single.c b/aplay/aligner-single.c new file mode 100644 index 0000000..70f0f91 --- /dev/null +++ b/aplay/aligner-single.c @@ -0,0 +1,256 @@ +/* + * aligner-single.c - a muxer/demuxer for single containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "aligner.h" + +struct single_state { + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char *buf, unsigned int bytes_per_sample, + unsigned int samples_per_frame); + char *buf; + + /* A part of parameters of a container. */ + unsigned int bytes_per_sample; + unsigned int samples_per_frame; +}; + +static void align_to_n(void *frame_buf, unsigned int frame_count, + char *src, unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char *dst = frame_buf; + unsigned int src_pos; + unsigned int dst_pos; + int i, j; + + /* src: interleaved => dst: non-interleaved. */ + for (i = 0; i < samples_per_frame; ++i) { + for (j = 0; j < frame_count; ++j) { + src_pos = j + bytes_per_sample * samples_per_frame * i; + dst_pos = i + bytes_per_sample * samples_per_frame * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_to_vector(void *frame_buf, unsigned int frame_count, + char *src, unsigned int bytes_per_sample, + unsigned samples_per_frame) +{ + char **dst_bufs = frame_buf; + char *dst; + unsigned int src_pos; + unsigned int dst_pos; + int i, j; + + /* src: interleaved => dst: a set of interleaved buffers. */ + for (i = 0; i < samples_per_frame; ++i) { + dst = dst_bufs[i]; + for (j = 0; j < frame_count; ++j) { + src_pos = bytes_per_sample * samples_per_frame * j + i; + dst_pos = j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_n(void *frame_buf, unsigned int frame_count, + char *dst, unsigned int bytes_per_sample, + unsigned samples_per_frame) +{ + char *src = frame_buf; + unsigned int dst_pos; + unsigned int src_pos; + int i, j; + + /* src: non-interleaved => dst: interleaved. */ + for (i = 0; i < samples_per_frame; ++i) { + for (j = 0; j < frame_count; ++j) { + src_pos = i + bytes_per_sample * samples_per_frame * j; + dst_pos = j + bytes_per_sample * samples_per_frame * i; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_vector(void *frame_buf, unsigned int frame_count, + char *dst, unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char **src_bufs = frame_buf; + char *src; + unsigned int dst_pos; + unsigned int src_pos; + int i, j; + + /* src: a set of interleaved buffers => dst:interleaved. */ + for (i = 0; i < samples_per_frame; ++i) { + src = src_bufs[i]; + for (j = 0; j < frame_count; ++j) { + src_pos = j; + dst_pos = bytes_per_sample * samples_per_frame * j + i; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static int single_pre_process(struct aligner_context *aligner, + struct container_context *cntrs, + unsigned int cntr_count) +{ + static const struct { + enum aligner_type type; + int access; + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char *buf, unsigned int bytes_per_sample, + unsigned int samples_per_frame); + } entries[] = { + { + ALIGNER_TYPE_MUXER, + SND_PCM_ACCESS_RW_NONINTERLEAVED, + align_to_n, + }, + { + ALIGNER_TYPE_MUXER, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + align_to_vector, + }, + { + ALIGNER_TYPE_DEMUXER, + SND_PCM_ACCESS_RW_NONINTERLEAVED, + align_from_n, + }, + { + ALIGNER_TYPE_DEMUXER, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + align_from_vector, + }, + }; + struct single_state *state = aligner->private_data; + unsigned int bytes_per_buffer; + int i; + + if (cntrs->bytes_per_sample != aligner->bytes_per_sample || + cntrs->samples_per_frame != aligner->samples_per_frame) + return -EINVAL; + + /* Decide method to align frames. */ + state->align_frames = NULL; + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == aligner->type && + (entries[i].access == aligner->access)) { + state->align_frames = entries[i].align_frames; + break; + } + } + + if (state->align_frames) { + /* Allocate intermediate buffer as the same size as a period. */ + bytes_per_buffer = aligner->bytes_per_sample * + aligner->samples_per_frame * + aligner->frames_per_buffer; + state->buf = malloc(bytes_per_buffer); + if (state->buf == NULL) + return -ENOMEM; + memset(state->buf, 0, bytes_per_buffer); + } + + return 0; +} + +static int single_muxer_process_frames(struct aligner_context *aligner, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct single_state *state = aligner->private_data; + void *src; + int err; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + src = frame_buf; + } else { + src = state->buf; + } + err = container_context_process_frames(cntrs, src, frame_count); + if (err < 0) + return err; + + /* Unlikely. */ + if (src != frame_buf && *frame_count > 0) + state->align_frames(frame_buf, *frame_count, src, + state->bytes_per_sample, + state->samples_per_frame); + + return 0; +} + +static int single_demuxer_process_frames(struct aligner_context *aligner, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct single_state *state = aligner->private_data; + void *dst; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + dst = frame_buf; + } else { + state->align_frames(frame_buf, *frame_count, state->buf, + state->bytes_per_sample, + state->samples_per_frame); + dst = state->buf; + } + + return container_context_process_frames(cntrs, dst, frame_count); +} + +static void single_post_process(struct aligner_context *aligner) +{ + struct single_state *state = aligner->private_data; + + if (state->buf) + free(state->buf); + + state->buf = NULL; + state->align_frames = NULL; +} + +const struct aligner_data aligner_muxer_single = { + .ops = { + .pre_process = single_pre_process, + .process_frames = single_muxer_process_frames, + .post_process = single_post_process, + }, + .private_size = sizeof(struct single_state), +}; + +const struct aligner_data aligner_demuxer_single = { + .ops = { + .pre_process = single_pre_process, + .process_frames = single_demuxer_process_frames, + .post_process = single_post_process, + }, + .private_size = sizeof(struct single_state), +}; diff --git a/aplay/aligner.c b/aplay/aligner.c index db59fc8..e578031 100644 --- a/aplay/aligner.c +++ b/aplay/aligner.c @@ -16,7 +16,7 @@ static const char *const aligner_type_labels[] = { };
static const char *const aligner_target_labels[] = { - [ALIGNER_TARGET_COUNT] = "", + [ALIGNER_TARGET_SINGLE] = "single", };
int aligner_context_init(struct aligner_context *aligner, @@ -25,6 +25,17 @@ int aligner_context_init(struct aligner_context *aligner, { const struct aligner_data *data = NULL;
+ if (type == ALIGNER_TYPE_MUXER) { + if (cntr_count == 1) { + data = &aligner_muxer_single; + aligner->target = ALIGNER_TARGET_SINGLE; + } + } else { + if (cntr_count == 1) { + data = &aligner_demuxer_single; + aligner->target = ALIGNER_TARGET_SINGLE; + } + } aligner->ops = &data->ops; aligner->type = type;
@@ -48,14 +59,6 @@ int aligner_context_pre_process(struct aligner_context *aligner, { int err;
- /* - * The purpose of multiple target is to mux/demux each channels to/from - * containers. - */ - if (aligner->target == ALIGNER_TARGET_MULTIPLE && - samples_per_frame != aligner->cntr_count) - return -EINVAL; - aligner->access = access; aligner->bytes_per_sample = snd_pcm_format_physical_width(format) / 8; aligner->samples_per_frame = samples_per_frame; diff --git a/aplay/aligner.h b/aplay/aligner.h index 441dda4..333b584 100644 --- a/aplay/aligner.h +++ b/aplay/aligner.h @@ -20,6 +20,7 @@ enum aligner_type { };
enum aligner_target { + ALIGNER_TARGET_SINGLE = 0, ALIGNER_TARGET_COUNT, };
@@ -77,4 +78,7 @@ struct aligner_data { unsigned int private_size; };
+extern const struct aligner_data aligner_muxer_single; +extern const struct aligner_data aligner_demuxer_single; + #endif
This commit adds support for aligner with 'multiple' target. This handles several files via 'container' functions. --- aplay/Makefile.am | 3 +- aplay/aligner-multiple.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++ aplay/aligner.c | 17 ++- aplay/aligner.h | 4 + 4 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 aplay/aligner-multiple.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 68f6411..79f1535 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -20,7 +20,8 @@ aplay_SOURCES = \ container-voc.c \ aligner.h \ aligner.c \ - aligner-single.c + aligner-single.c \ + aligner-multiple.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/aligner-multiple.c b/aplay/aligner-multiple.c new file mode 100644 index 0000000..437e3cc --- /dev/null +++ b/aplay/aligner-multiple.c @@ -0,0 +1,337 @@ +/* + * aligner-multiple.c - a muxer/demuxer for multiple containers. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "aligner.h" + +struct multiple_state { + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char **buf, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count); + char **bufs; +}; + +static void align_to_i(void *frame_buf, unsigned int frame_count, + char **src_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, unsigned int cntr_count) +{ + char *dst = frame_buf; + char *src; + unsigned int dst_pos; + unsigned int src_pos; + struct container_context *cntr; + int i, j; + + /* + * src: first channel in each of interleaved buffers in containers => + * dst:interleaved. + */ + for (i = 0; i < cntr_count; ++i) { + src = src_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * cntr->samples_per_frame * j; + dst_pos = i + bytes_per_sample * cntr_count * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_to_n(void *frame_buf, unsigned int frame_count, + char **src_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, unsigned int cntr_count) +{ + char *dst = frame_buf; + char *src; + unsigned int dst_pos; + unsigned int src_pos; + struct container_context *cntr; + int i, j; + + /* + * src: first channel in each of interleaved buffers in containers => + * dst:noninterleaved. + */ + for (i = 0; i < cntr_count; ++i) { + src = src_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * cntr->samples_per_frame * j; + dst_pos = j + bytes_per_sample * cntr_count * i; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_i(void *frame_buf, unsigned int frame_count, + char **dst_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count) +{ + char *src = frame_buf; + char *dst; + unsigned int src_pos; + unsigned int dst_pos; + struct container_context *cntr; + int i, j; + + for (i = 0; i < cntr_count; ++i) { + dst = dst_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * cntr_count * j + i; + dst_pos = bytes_per_sample * cntr->samples_per_frame * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static void align_from_n(void *frame_buf, unsigned int frame_count, + char **dst_bufs, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count) +{ + char *src = frame_buf; + char *dst; + unsigned int src_pos; + unsigned int dst_pos; + struct container_context *cntr; + int i, j; + + for (i = 0; i < cntr_count; ++i) { + dst = dst_bufs[i]; + cntr = cntrs + i; + + for (j = 0; j < frame_count; ++j) { + /* Use first src channel for each of dst channel. */ + src_pos = bytes_per_sample * cntr_count * i + j; + dst_pos = bytes_per_sample * cntr->samples_per_frame * j; + + memcpy(dst + dst_pos, src + src_pos, bytes_per_sample); + } + } +} + +static int multiple_pre_process(struct aligner_context *aligner, + struct container_context *cntrs, + unsigned int cntr_count) +{ + static const struct { + enum aligner_type type; + int access; + void (*align_frames)(void *frame_buf, unsigned int frame_count, + char **buf, unsigned int bytes_per_sample, + struct container_context *cntrs, + unsigned int cntr_count); + } entries[] = { + { + ALIGNER_TYPE_MUXER, + SND_PCM_ACCESS_RW_INTERLEAVED, + align_to_i, + }, + { + ALIGNER_TYPE_MUXER, + SND_PCM_ACCESS_RW_NONINTERLEAVED, + align_to_n, + }, + { + ALIGNER_TYPE_DEMUXER, + SND_PCM_ACCESS_MMAP_INTERLEAVED, + align_from_i, + }, + { + ALIGNER_TYPE_DEMUXER, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED, + align_from_n, + }, + }; + struct multiple_state *state = aligner->private_data; + struct container_context *cntr; + int i; + + /* + * Additionally, format of samples in the containers should be the same + * as the format in PCM substream. + */ + for (i = 0; i < cntr_count; ++i) { + cntr = cntrs + i; + if (aligner->bytes_per_sample != cntr->bytes_per_sample) + return -EINVAL; + } + + state->align_frames = NULL; + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == aligner->type && + (entries[i].access == aligner->access)) { + state->align_frames = entries[i].align_frames; + break; + } + } + + if (state->align_frames) { + /* + * Furthermore, in demuxer case, each container should be + * configured to store one sample per frame. + */ + if (aligner->type == ALIGNER_TYPE_DEMUXER) { + for (i = 0; i < cntr_count; ++i) { + cntr = cntrs + i; + if (cntrs->samples_per_frame != 1) + return -EINVAL; + } + } + + state->bufs = calloc(cntr_count, sizeof(char *)); + if (state->bufs == NULL) + return -ENOMEM; + + for (i = 0; i < cntr_count; ++i) { + unsigned int bytes_per_buffer; + + /* + * Allocate intermediate buffer as the same size as a + * period for each of containers. + */ + cntr = cntrs + i; + + bytes_per_buffer = aligner->bytes_per_sample * + cntr->samples_per_frame * + aligner->frames_per_buffer; + + state->bufs[i] = malloc(bytes_per_buffer); + if (state->bufs[i] == NULL) + return -ENOMEM; + memset(state->bufs[i], 0, bytes_per_buffer); + } + } + + return 0; +} + +static int process_containers(char **src_bufs, unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct container_context *cntr; + char *src; + int i; + int err = 0; + + /* TODO: arrangement for *frame_count. */ + for (i = 0; i < cntr_count; ++i) { + cntr = &cntrs[i]; + src = src_bufs[i]; + + err = container_context_process_frames(cntr, src, frame_count); + if (err < 0) + break; + } + + return err; +} + +static int multiple_muxer_process_frames(struct aligner_context *aligner, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct multiple_state *state = aligner->private_data; + char **src_bufs; + int err; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + src_bufs = frame_buf; + } else { + src_bufs = state->bufs; + } + err = process_containers(src_bufs, frame_count, cntrs, cntr_count); + if (err < 0) + return err; + + /* Unlikely. */ + if (src_bufs != frame_buf && *frame_count > 0) { + state->align_frames(frame_buf, *frame_count, src_bufs, + aligner->bytes_per_sample, cntrs, + cntr_count); + } + + return 0; +} + +static int multiple_demuxer_process_frames(struct aligner_context *aligner, + void *frame_buf, + unsigned int *frame_count, + struct container_context *cntrs, + unsigned int cntr_count) +{ + struct multiple_state *state = aligner->private_data; + char **dst_bufs; + + /* + * If need to align PCM frames, process PCM frames to the intermediate + * buffer once. + */ + if (!state->align_frames) { + /* The most likely. */ + dst_bufs = frame_buf; + } else { + dst_bufs = state->bufs; + state->align_frames(frame_buf, *frame_count, dst_bufs, + aligner->bytes_per_sample, cntrs, + cntr_count); + } + + return process_containers(dst_bufs, frame_count, cntrs, cntr_count); +} + +static void multiple_post_process(struct aligner_context *aligner) +{ + struct multiple_state *state = aligner->private_data; + + if (state->bufs) { + if (state->bufs[0]) + free(state->bufs[0]); + free(state->bufs); + } + + state->bufs = NULL; + state->align_frames = NULL; +} + +const struct aligner_data aligner_muxer_multiple = { + .ops = { + .pre_process = multiple_pre_process, + .process_frames = multiple_muxer_process_frames, + .post_process = multiple_post_process, + }, + .private_size = sizeof(struct multiple_state), +}; + +const struct aligner_data aligner_demuxer_multiple = { + .ops = { + .pre_process = multiple_pre_process, + .process_frames = multiple_demuxer_process_frames, + .post_process = multiple_post_process, + }, + .private_size = sizeof(struct multiple_state), +}; diff --git a/aplay/aligner.c b/aplay/aligner.c index e578031..6d260a3 100644 --- a/aplay/aligner.c +++ b/aplay/aligner.c @@ -17,23 +17,30 @@ static const char *const aligner_type_labels[] = {
static const char *const aligner_target_labels[] = { [ALIGNER_TARGET_SINGLE] = "single", + [ALIGNER_TARGET_MULTIPLE] = "multiple", };
int aligner_context_init(struct aligner_context *aligner, enum aligner_type type, unsigned int cntr_count, unsigned int verbose) { - const struct aligner_data *data = NULL; + const struct aligner_data *data;
if (type == ALIGNER_TYPE_MUXER) { if (cntr_count == 1) { data = &aligner_muxer_single; aligner->target = ALIGNER_TARGET_SINGLE; + } else { + data = &aligner_muxer_multiple; + aligner->target = ALIGNER_TARGET_MULTIPLE; } } else { if (cntr_count == 1) { data = &aligner_demuxer_single; aligner->target = ALIGNER_TARGET_SINGLE; + } else { + data = &aligner_demuxer_multiple; + aligner->target = ALIGNER_TARGET_MULTIPLE; } } aligner->ops = &data->ops; @@ -59,6 +66,14 @@ int aligner_context_pre_process(struct aligner_context *aligner, { int err;
+ /* + * The purpose of multiple target is to mux/demux each channels to/from + * containers. + */ + if (aligner->target == ALIGNER_TARGET_MULTIPLE && + samples_per_frame != aligner->cntr_count) + return -EINVAL; + aligner->access = access; aligner->bytes_per_sample = snd_pcm_format_physical_width(format) / 8; aligner->samples_per_frame = samples_per_frame; diff --git a/aplay/aligner.h b/aplay/aligner.h index 333b584..bf8b689 100644 --- a/aplay/aligner.h +++ b/aplay/aligner.h @@ -21,6 +21,7 @@ enum aligner_type {
enum aligner_target { ALIGNER_TARGET_SINGLE = 0, + ALIGNER_TARGET_MULTIPLE, ALIGNER_TARGET_COUNT, };
@@ -81,4 +82,7 @@ struct aligner_data { extern const struct aligner_data aligner_muxer_single; extern const struct aligner_data aligner_demuxer_single;
+extern const struct aligner_data aligner_muxer_multiple; +extern const struct aligner_data aligner_demuxer_multiple; + #endif
There're several types of system calls for multiplexed I/O. They're used to receive notifications of I/O events. Typically, userspace applications call it for file descriptor to yield CPU. When I/O is enabled, process of the application is rescheduled, then the application execute I/O calls.
This commit adds an abstraction of the calls, named as 'waiter'. This is expected to be used with non-blocking file operation. --- aplay/Makefile.am | 7 +++++-- aplay/waiter.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/waiter.h | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 aplay/waiter.c create mode 100644 aplay/waiter.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 79f1535..634124b 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -8,7 +8,8 @@ man_MANS = aplay.1 arecord.1 noinst_HEADERS = \ formats.h \ container.h \ - aligner.h + aligner.h \ + waiter.h
aplay_SOURCES = \ formats.h \ @@ -21,7 +22,9 @@ aplay_SOURCES = \ aligner.h \ aligner.c \ aligner-single.c \ - aligner-multiple.c + aligner-multiple.c \ + waiter.h \ + waiter.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/waiter.c b/aplay/waiter.c new file mode 100644 index 0000000..d29a533 --- /dev/null +++ b/aplay/waiter.c @@ -0,0 +1,60 @@ +/* + * waiter.c - I/O event waiter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" + +int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) +{ + struct { + enum waiter_type type; + const struct waiter_data *waiter; + } entries[] = { + {WAITER_TYPE_COUNT, NULL}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == type) + break; + } + if (i == ARRAY_SIZE(entries)) + return -EINVAL; + + waiter->private_data = malloc(entries[i].waiter->private_size); + if (waiter->private_data == NULL) + return -ENOMEM; + memset(waiter->private_data, 0, entries[i].waiter->private_size); + + waiter->type = type; + waiter->ops = &entries[i].waiter->ops; + + return 0; +} + +int waiter_context_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + return waiter->ops->prepare(waiter, fds, fd_count); +} + +int waiter_context_wait_event(struct waiter_context *waiter) +{ + return waiter->ops->wait_event(waiter); +} + +void waiter_context_release(struct waiter_context *waiter) +{ + waiter->ops->release(waiter); +} + +void waiter_context_destroy(struct waiter_context *waiter) +{ + if (waiter->private_data) + free(waiter->private_data); + waiter->private_data = NULL; +} diff --git a/aplay/waiter.h b/aplay/waiter.h new file mode 100644 index 0000000..c63274a --- /dev/null +++ b/aplay/waiter.h @@ -0,0 +1,54 @@ +/* + * waiter.h - a header for I/O event waiter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_WAITER__H_ +#define __ALSA_UTILS_APLAY_WAITER__H_ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) +#endif + +enum waiter_type { + WAITER_TYPE_COUNT, +}; + +struct waiter_ops; + +struct waiter_context { + enum waiter_type type; + const struct waiter_ops *ops; + void *private_data; +}; + +int waiter_context_init(struct waiter_context *waiter, enum waiter_type type); +int waiter_context_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count); +int waiter_context_wait_event(struct waiter_context *waiter); +void waiter_context_release(struct waiter_context *waiter); +void waiter_context_destroy(struct waiter_context *waiter); + +/* For internal use in 'waiter' module. */ + +struct waiter_ops { + int (*prepare)(struct waiter_context *waiter, int *fds, + unsigned int fd_count); + int (*wait_event)(struct waiter_context *waiter); + void (*release)(struct waiter_context *waiter); +}; + +struct waiter_data { + struct waiter_ops ops; + unsigned int private_size; +}; + +#endif
This commit adds support for poll(2) system call. --- aplay/Makefile.am | 3 ++- aplay/waiter-poll.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/waiter.c | 2 +- aplay/waiter.h | 3 +++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 aplay/waiter-poll.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 634124b..a4a353e 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -24,7 +24,8 @@ aplay_SOURCES = \ aligner-single.c \ aligner-multiple.c \ waiter.h \ - waiter.c + waiter.c \ + waiter-poll.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/waiter-poll.c b/aplay/waiter-poll.c new file mode 100644 index 0000000..68ef4ae --- /dev/null +++ b/aplay/waiter-poll.c @@ -0,0 +1,66 @@ +/* + * waiter-waiter-poll.c - Waiter for event notification by poll(2). + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" + +#include <poll.h> + +struct poll_state { + struct pollfd *pfds; + unsigned int count; +}; + +static int poll_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + struct poll_state *state = waiter->private_data; + int i; + + state->pfds = calloc(fd_count, sizeof(struct pollfd)); + if (state->pfds == NULL) + return -ENOMEM; + + for (i = 0; i < fd_count; ++i) { + state->pfds[i].fd = fds[i]; + state->pfds[i].events = POLLIN | POLLOUT; + } + + state->count = fd_count; + + return 0; +} + +static int poll_wait_event(struct waiter_context *waiter) +{ + struct poll_state *state = waiter->private_data; + int err; + + + err = poll(state->pfds, state->count, -1); + if (err < 0) + return -errno; + + return 0; +} + +static void poll_release(struct waiter_context *waiter) +{ + struct poll_state *state = waiter->private_data; + + free(state->pfds); + state->pfds = 0; +} + +const struct waiter_data waiter_poll = { + .ops = { + .prepare = poll_prepare, + .wait_event = poll_wait_event, + .release = poll_release, + }, + .private_size = sizeof(struct poll_state), +}; diff --git a/aplay/waiter.c b/aplay/waiter.c index d29a533..c238bf1 100644 --- a/aplay/waiter.c +++ b/aplay/waiter.c @@ -14,7 +14,7 @@ int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) enum waiter_type type; const struct waiter_data *waiter; } entries[] = { - {WAITER_TYPE_COUNT, NULL}, + {WAITER_TYPE_POLL, &waiter_poll}, }; int i;
diff --git a/aplay/waiter.h b/aplay/waiter.h index c63274a..5059a00 100644 --- a/aplay/waiter.h +++ b/aplay/waiter.h @@ -19,6 +19,7 @@ #endif
enum waiter_type { + WAITER_TYPE_POLL = 0, WAITER_TYPE_COUNT, };
@@ -51,4 +52,6 @@ struct waiter_data { unsigned int private_size; };
+extern const struct waiter_data waiter_poll; + #endif
This commit adds support for Linux specific epoll(7) system call. --- aplay/Makefile.am | 3 ++- aplay/waiter-epoll.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/waiter.c | 1 + aplay/waiter.h | 2 ++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 aplay/waiter-epoll.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index a4a353e..4042bbe 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -25,7 +25,8 @@ aplay_SOURCES = \ aligner-multiple.c \ waiter.h \ waiter.c \ - waiter-poll.c + waiter-poll.c \ + waiter-epoll.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/waiter-epoll.c b/aplay/waiter-epoll.c new file mode 100644 index 0000000..579f16c --- /dev/null +++ b/aplay/waiter-epoll.c @@ -0,0 +1,76 @@ +/* + * waiter-waiter-epoll.c - Waiter for event notification by epoll(7). + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "waiter.h" + +#include <sys/epoll.h> + +struct epoll_state { + int epfd; + struct epoll_event *events; + unsigned int count; +}; + +static int epoll_prepare(struct waiter_context *waiter, int *fds, + unsigned int fd_count) +{ + struct epoll_state *state = waiter->private_data; + int i; + + state->events = calloc(fd_count, sizeof(struct epoll_event)); + if (state->events == NULL) + return -ENOMEM; + state->count = fd_count; + + state->epfd = epoll_create(1); + if (state->epfd < 0) + return -errno; + + for (i = 0; i < fd_count; ++i) { + struct epoll_event *ev = &state->events[i]; + ev->events = EPOLLIN | EPOLLOUT; + if (epoll_ctl(state->epfd, EPOLL_CTL_ADD, fds[i], ev) < 0) + return -errno; + } + + return 0; +} + +static int epoll_wait_event(struct waiter_context *waiter) +{ + struct epoll_state *state = waiter->private_data; + int err; + + err = epoll_wait(state->epfd, state->events, state->count, 0); + if (err < 0) + return -errno; + + return 0; +} + +static void epoll_release(struct waiter_context *waiter) +{ + struct epoll_state *state = waiter->private_data; + + if (state->events) + free(state->events); + + close(state->epfd); + + state->events = NULL; + state->epfd = 0; +} + +const struct waiter_data waiter_epoll = { + .ops = { + .prepare = epoll_prepare, + .wait_event = epoll_wait_event, + .release = epoll_release, + }, + .private_size = sizeof(struct epoll_state), +}; diff --git a/aplay/waiter.c b/aplay/waiter.c index c238bf1..b822359 100644 --- a/aplay/waiter.c +++ b/aplay/waiter.c @@ -15,6 +15,7 @@ int waiter_context_init(struct waiter_context *waiter, enum waiter_type type) const struct waiter_data *waiter; } entries[] = { {WAITER_TYPE_POLL, &waiter_poll}, + {WAITER_TYPE_EPOLL, &waiter_epoll}, }; int i;
diff --git a/aplay/waiter.h b/aplay/waiter.h index 5059a00..0fb508f 100644 --- a/aplay/waiter.h +++ b/aplay/waiter.h @@ -20,6 +20,7 @@
enum waiter_type { WAITER_TYPE_POLL = 0, + WAITER_TYPE_EPOLL = 0, WAITER_TYPE_COUNT, };
@@ -53,5 +54,6 @@ struct waiter_data { };
extern const struct waiter_data waiter_poll; +extern const struct waiter_data waiter_epoll;
#endif
In current implementation, several types of command-line options are supported. Some of them have dependency or conflicts.
This commit adds a structure and a parser for the options. --- aplay/Makefile.am | 7 +- aplay/options.c | 585 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/options.h | 65 ++++++ 3 files changed, 655 insertions(+), 2 deletions(-) create mode 100644 aplay/options.c create mode 100644 aplay/options.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 4042bbe..196e1ca 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -9,7 +9,8 @@ noinst_HEADERS = \ formats.h \ container.h \ aligner.h \ - waiter.h + waiter.h \ + options.h
aplay_SOURCES = \ formats.h \ @@ -26,7 +27,9 @@ aplay_SOURCES = \ waiter.h \ waiter.c \ waiter-poll.c \ - waiter-epoll.c + waiter-epoll.c \ + options.h \ + options.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/options.c b/aplay/options.c new file mode 100644 index 0000000..efe0ce0 --- /dev/null +++ b/aplay/options.c @@ -0,0 +1,585 @@ +/* + * options.c - a parser of commandline options. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "options.h" + +#include <gettext.h> +#include <getopt.h> +#include <math.h> + +enum no_short_opts { + /* 128 belongs to non us-ascii character set. */ + OPT_USE_STRFTIME = 128, + OPT_MAX_FILE_TIME, + OPT_PERIOD_SIZE, + OPT_BUFFER_SIZE, + OPT_DISABLE_RESAMPLE, + OPT_DISABLE_CHANNELS, + OPT_DISABLE_FORMAT, + OPT_DISABLE_SOFTVOL, + OPT_TEST_POSITION, + OPT_TEST_COEF, + OPT_TEST_NOWAIT, + OPT_DUMP_HWPARAMS, + OPT_FATAL_ERRORS, +}; + +static long parse_l(const char *str, int *err) +{ + long val; + char *endptr; + + val = strtol(str, &endptr, 10); + if (errno) + return -errno; + if (*endptr != '\0') + return -EINVAL; + + return val; +} + +static int allocate_paths(struct context_options *opts, char *const *paths, + unsigned int count) +{ + bool stdio = false; + char *path; + int i, j; + + if (count == 0) { + stdio = true; + count = 1; + } + + opts->paths = calloc(count, sizeof(opts->paths[0])); + if (opts->paths == NULL) + return -ENOMEM; + opts->path_count = count; + + if (stdio) { + opts->paths[0] = malloc(2); + if (opts->paths[0] == NULL) + return -ENOMEM; + strcpy(opts->paths[0], "-"); + return 0; + } + + for (i = 0; i < count; ++i) { + for (j = 0; j < i; ++j) { + if (!strcmp(paths[i], opts->paths[j])) { + printf("The same file name appears several " + "times.\n"); + return -EINVAL; + } + } + + path = malloc(strlen(paths[i]) + 1); + if (path == NULL) + return -ENOMEM; + strcpy(path, paths[i]); + opts->paths[i] = path; + } + + return 0; +} + +static int verify_cntr_format(struct context_options *opts, const char *literal) +{ + static const struct { + const char *const literal; + enum container_format cntr_format; + } *entry, entries[] = { + {"raw", CONTAINER_FORMAT_RAW}, + {"voc", CONTAINER_FORMAT_VOC}, + {"wav", CONTAINER_FORMAT_RIFF_WAVE}, + {"au", CONTAINER_FORMAT_AU}, + {"sparc", CONTAINER_FORMAT_AU}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (strcasecmp(literal, entry->literal)) + continue; + + opts->cntr_format = entry->cntr_format; + return 0; + } + + printf(_("unrecognized file format '%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) +{ + struct { + const char *const literal; + unsigned int frames_per_second; + unsigned int samples_per_frame; + snd_pcm_format_t le_format; + snd_pcm_format_t be_format; + } *entry, entries[] = { + {"cd", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + {"cdr", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + {"dat", 48000, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE}, + }; + int i; + + opts->sample_format = snd_pcm_format_value(literal); + if (opts->sample_format != SND_PCM_FORMAT_UNKNOWN) + return 0; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = &entries[i]; + if (strcmp(entry->literal, literal)) + continue; + + if (opts->frames_per_second > 0 && + opts->frames_per_second != entry->frames_per_second) { + printf("'%s' format can't be used with rate except for " + "%u.\n", + entry->literal, entry->frames_per_second); + return -EINVAL; + } + + if (opts->samples_per_frame > 0 && + opts->samples_per_frame != entry->samples_per_frame) { + printf("'%s' format can't be used with channel except " + "for %u.\n", + entry->literal, entry->samples_per_frame); + return -EINVAL; + } + + if (opts->cntr_format == CONTAINER_FORMAT_AU) + opts->sample_format = entry->be_format; + else + opts->sample_format = entry->le_format; + + return 0; + } + + printf(_("wrong extended format '%s'\n"), literal); + + return -EINVAL; +} + +static int apply_policies(struct context_options *opts, + snd_pcm_stream_t direction, + const char *cntr_format_literal, + const char *node_literal, + const char *sample_format_literal) +{ + int err; + + if (node_literal != NULL) { + opts->node = malloc(strlen(node_literal) + 1); + if (opts->node == NULL) { + printf(_("Fail to allocate for node: %s\n"), + node_literal); + return -ENOMEM; + } + strcpy(opts->node, node_literal); + } + + if (!cntr_format_literal) { + if (direction == SND_PCM_STREAM_CAPTURE) { + /* To stdout. */ + if (opts->path_count == 1 && + !strcmp(opts->paths[0], "-")) { + opts->cntr_format = CONTAINER_FORMAT_RAW; + } else { + /* Use first path as a representative. */ + opts->cntr_format = container_format_from_path( + opts->paths[0]); + } + } + /* For playback, perform auto-detection. */ + } else { + err = verify_cntr_format(opts, cntr_format_literal); + if (err < 0) + return err; + } + + if (opts->samples_per_frame > 0) { + if (opts->samples_per_frame < 1 || + opts->samples_per_frame > 256) { + printf(_("invalid channels argument '%u'\n"), + opts->samples_per_frame); + return -EINVAL; + } + } + + if (opts->multiple_cntrs) { + if (!strcmp(opts->paths[0], "-")) { + printf(_("An option for separated channels is not " + "available with stdin/stdout.\n")); + return -EINVAL; + } + + if (direction == SND_PCM_STREAM_PLAYBACK) { + /* Require several paths for containers. */ + if (opts->path_count == 1) { + printf(_("An option for separated channels " + "requires several files to playback " + "PCM frames.\n")); + return -EINVAL; + } + } else { + /* Require to indicate the number of channels. */ + if (opts->samples_per_frame == 0) { + printf(_("An option for separated channels " + "requires an option for the number of " + "channels to capture PCM frames.\n")); + return -EINVAL; + } + + /* + * Even if one path is given for container files, it can + * be used to generate several paths for captured PCM + * frames. For this purpose, please see + * 'context_options_normalize_paths()'. + */ + } + } else { + /* A single path is available only. */ + if (opts->path_count > 1) { + printf(_("When using several files, an option for" + "sepatated channels is used with.\n")); + return -EINVAL; + } + } + + if (opts->frames_per_second > 0) { + unsigned int orig = opts->frames_per_second; + + /* For backward compatibility. */ + if (opts->frames_per_second < 300) + opts->frames_per_second *= 300; + if (opts->frames_per_second < 2000 || + opts->frames_per_second > 192000) { + printf(_("bad speed value '%i'\n"), orig); + return -EINVAL; + } + } + + opts->sample_format = SND_PCM_FORMAT_UNKNOWN; + if (sample_format_literal) { + err = verify_sample_format(opts, sample_format_literal); + if (err < 0) + return err; + } + + /* TODO: frames_per_period v.s. msec_per_period. */ + /* TODO: frames_per_buffer v.s. msec_per_buffer. */ + + return 0; +} + +int context_options_init(struct context_options *opts, int argc, + char *const *argv, snd_pcm_stream_t direction) +{ + static const char *s_opts = "hqd:vt:D:c:f:r:MNF:A:R:T:B:I"; + static const struct option l_opts[] = { + /* For generic purposes. */ + {"help", 0, 0, 'h'}, + {"quiet", 0, 0, 'q'}, + {"duration", 1, 0 ,'d'}, + {"verbose", 0, 0, 'v'}, + /* For containers. */ + {"file-type", 1, 0, 't'}, + {"use-strftime", 0, 0, OPT_USE_STRFTIME}, + {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, + /* For aligner. */ + {"separate-channels", 0, 0, 'I'}, + /* For ALSA backend. */ + {"device", 1, 0, 'D'}, + {"channels", 1, 0, 'c'}, + {"format", 1, 0, 'f'}, + {"rate", 1, 0, 'r'}, + {"mmap", 0, 0, 'M'}, + {"nonblock", 0, 0, 'N'}, + {"period-time", 1, 0, 'F'}, + {"avail-min", 1, 0, 'A'}, + {"start-delay", 1, 0, 'R'}, + {"stop-delay", 1, 0, 'T'}, + {"buffer-time", 1, 0, 'B'}, + {"period-size", 1, 0, OPT_PERIOD_SIZE}, + {"buffer-size", 1, 0, OPT_BUFFER_SIZE}, + {"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE}, + {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, + {"disable-format", 0, 0, OPT_DISABLE_FORMAT}, + {"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL}, + {"test-position", 0, 0, OPT_TEST_POSITION}, + {"test-coef", 1, 0, OPT_TEST_COEF}, + {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, + {"dump-hw-params", 0, 0, OPT_DUMP_HWPARAMS}, + {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, + {NULL, 0, 0, 0}, + }; + const char *cntr_format_literal = NULL; + const char *node_literal = NULL; + const char *sample_format_literal = NULL; + int c; + int err = 0; + + optind = 0; + opterr = 0; + while (1) { + c = getopt_long(argc, argv, s_opts, l_opts, NULL); + if (c < 0) + break; + else if (c == 'h') + opts->help = true; + else if (c == 'q') + opts->quiet = true; + else if (c == 'd') + opts->timelimit_seconds = parse_l(optarg, &err); + else if (c == 'v') + ++opts->verbose; + else if (c == 't') + cntr_format_literal = optarg; + else if (c == OPT_USE_STRFTIME) + opts->use_strftime = true; + else if (c == OPT_MAX_FILE_TIME) + opts->max_file_seconds = true; + else if (c == 'I') + opts->multiple_cntrs = true; + else if (c == 'D') + node_literal = optarg; + else if (c == 'c') + opts->samples_per_frame = parse_l(optarg, &err); + else if (c == 'f') + sample_format_literal = optarg; + else if (c == 'r') + opts->frames_per_second = parse_l(optarg, &err); + else if (c == 'M') + opts->mmap = true; + else if (c == 'N') + opts->nonblock = true; + else if (c == 'F') + opts->msec_per_period = parse_l(optarg, &err); + else if (c == 'A') + opts->msec_for_avail_min = parse_l(optarg, &err); + else if (c == 'R') + opts->msec_for_start_delay = parse_l(optarg, &err); + else if (c == 'T') + opts->msec_for_stop_threshold = parse_l(optarg, &err); + else if (c == 'B') + opts->msec_per_buffer = parse_l(optarg, &err); + else if (c == OPT_PERIOD_SIZE) + opts->frames_per_period = parse_l(optarg, &err); + else if (c == OPT_BUFFER_SIZE) + opts->frames_per_buffer = parse_l(optarg, &err); + else if (c == OPT_DISABLE_RESAMPLE) + opts->no_auto_resample = true; + else if (c == OPT_DISABLE_CHANNELS) + opts->no_auto_channels = true; + else if (c == OPT_DISABLE_FORMAT) + opts->no_auto_format = true; + else if (c == OPT_DISABLE_SOFTVOL) + opts->no_softvol = true; + else if (c == OPT_TEST_POSITION) + opts->test_position = true; + else if (c == OPT_TEST_COEF) + opts->text_coef = parse_l(optarg, &err); + else if (c == OPT_TEST_NOWAIT) + opts->test_nowait = true; + else if (c == OPT_DUMP_HWPARAMS) + opts->dump_hw_params = true; + else if (c == OPT_FATAL_ERRORS) + opts->fatal_errors = true; + else + continue; + + if (err < 0) + return err; + } + + err = allocate_paths(opts, argv + optind, argc - optind); + if (err < 0) + return err; + + return apply_policies(opts, direction, cntr_format_literal, + node_literal, sample_format_literal); +} + +/* + * A variant of strftime(3) that supports additional format specifiers in the + * format string: + * '%v': file number. + * + * This function should be called after 'samples_per_frame' is decided. + */ +static size_t generate_paths_with_strftime(struct context_options *opts, + char *template, + unsigned int path_count) +{ + char *format; + time_t now_time; + struct tm now_tm; + unsigned int len; + char *pos; + unsigned int width; + int i; + int err; + + /* This might be enough to process formatted strings. */ + format = malloc(4096); + if (format == NULL) + return -ENOMEM; + + now_time = time(NULL); + if ((int)now_time < 0) + return -errno; + + if (localtime_r(&now_time, &now_tm) == NULL) + return -errno; + + len = strftime(format, strlen(template) + 1, template, &now_tm); + if (len == 0) { + err = -EINVAL; + goto end; + } + format[len] = '\0'; + + width = (unsigned int)log10(path_count); + + /* Estimate required length for formatted result. */ + len = 0; + for (pos = format; *pos != '\0'; ++pos) { + ++len; + /* '%v' is unique format for numbering. */ + if (*pos == '%') + len += width; + } + + for (i = 0; i < opts->path_count; ++i) { + opts->paths[i] = malloc(len + 1); + if (opts->paths[i] == NULL) { + err = -ENOMEM; + goto end; + } + snprintf(opts->paths[i], len + 1, format, i); + } +end: + free(format); + return err; +} + +static size_t generate_paths_with_suffix(struct context_options *opts, + const char *name, + const char *suffix, + unsigned int path_count) +{ + static const char *const format = "%s-%i.%s"; + unsigned int width; + unsigned int len; + int i; + + width = (unsigned int)log10(path_count) + 1; + len = strlen(name) + 3 + width + strlen(suffix); + + for (i = 0; i < path_count; ++i) { + opts->paths[i] = malloc(len); + if (opts->paths[i] == NULL) + return -ENOMEM; + snprintf(opts->paths[i], len, format, name, i, suffix); + } + + return 0; +} + +static size_t generate_paths_without_suffix(struct context_options *opts, + const char *name, + unsigned int path_count) +{ + static const char *const format = "%s-%i"; + unsigned int width; + unsigned int len; + int i; + + width = (unsigned int)log10(path_count) + 1; + len = strlen(name) + 2 + width; + + for (i = 0; i < path_count; ++i) { + opts->paths[i] = malloc(len); + if (opts->paths[i] == NULL) + return -ENOMEM; + snprintf(opts->paths[i], len, format, name, i); + } + + return 0; +} + +int context_options_normalize_paths(struct context_options *opts, + enum container_format format, + unsigned int path_count) +{ + const char *suffix; + char *template; + char *pos; + int err = 0; + + /* This option should be used for one given path. */ + if (opts->path_count > 1 || + opts->paths == NULL || opts->paths[0] == NULL) + return -EINVAL; + + suffix = container_suffix_from_format(format); + + /* Release at first. */ + template = opts->paths[0]; + free(opts->paths); + opts->paths = NULL; + + /* Allocate again. */ + opts->paths = calloc(path_count, sizeof(char *)); + if (opts->paths == NULL) { + err = -ENOMEM; + goto end; + } + + if (opts->use_strftime) { + err = generate_paths_with_strftime(opts, template, path_count); + } else { + pos = template + strlen(template) - strlen(suffix); + if (strcmp(pos, suffix) != 0) { + err = generate_paths_without_suffix(opts, template, + path_count); + } else { + /* Separate extension from filename. */ + template[pos - template] = '\0'; + if (*suffix == '.') + ++suffix; + err = generate_paths_with_suffix(opts, template, suffix, + path_count); + } + } + + /* TODO: check the paths. */ +end: + free(template); + return err; +} + +void context_options_destroy(struct context_options *opts) +{ + int i; + + if (opts->paths) { + for (i = 0; i < opts->path_count; ++i) + free(opts->paths[i]); + free(opts->paths); + } + if (opts->node) + free(opts->node); + opts->paths = NULL; + opts->node = NULL; +} diff --git a/aplay/options.h b/aplay/options.h new file mode 100644 index 0000000..dc474d5 --- /dev/null +++ b/aplay/options.h @@ -0,0 +1,65 @@ +/* + * options.h - a header for a parser of commandline options. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_OPTIONS__H_ +#define __ALSA_UTILS_APLAY_OPTIONS__H_ + +#include "container.h" + +struct context_options { + bool help; + + /* For generic purposes. */ + bool quiet; + unsigned int timelimit_seconds; + unsigned int verbose; + + /* For containers. */ + enum container_format cntr_format; + bool use_strftime; + bool max_file_seconds; + + /* For aligner. */ + bool multiple_cntrs; + + /* For ALSA backend. */ + char *node; + unsigned int samples_per_frame; + snd_pcm_format_t sample_format; + unsigned int frames_per_second; + bool mmap; + bool nonblock; + unsigned int msec_per_period; + unsigned int msec_for_avail_min; + unsigned int msec_for_start_delay; + unsigned int msec_for_stop_threshold; + unsigned int msec_per_buffer; + unsigned int frames_per_period; + unsigned int frames_per_buffer; + bool no_auto_resample; + bool no_auto_channels; + bool no_auto_format; + bool no_softvol; + bool test_position; + int text_coef; + bool test_nowait; + bool dump_hw_params; + bool fatal_errors; + + char **paths; + unsigned int path_count; +}; + +int context_options_init(struct context_options *opts, int argc, + char *const *argv, snd_pcm_stream_t direction); +int context_options_normalize_paths(struct context_options *opts, + enum container_format format, + unsigned int path_count); +void context_options_destroy(struct context_options *opts); + +#endif
ALSA has PCM interface to transfer data frames, while the interface includes several variations of use cases.
This commit adds an abstraction to transfer data frames, named as 'xfer'. This includes some functions expected to be called by main program. --- aplay/Makefile.am | 7 ++-- aplay/xfer.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/xfer.h | 71 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 aplay/xfer.c create mode 100644 aplay/xfer.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 196e1ca..9056f56 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -10,7 +10,8 @@ noinst_HEADERS = \ container.h \ aligner.h \ waiter.h \ - options.h + options.h \ + xfer.h
aplay_SOURCES = \ formats.h \ @@ -29,7 +30,9 @@ aplay_SOURCES = \ waiter-poll.c \ waiter-epoll.c \ options.h \ - options.c + options.c \ + xfer.h \ + xfer.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/xfer.c b/aplay/xfer.c new file mode 100644 index 0000000..3fe094e --- /dev/null +++ b/aplay/xfer.c @@ -0,0 +1,101 @@ +/* + * xfer.c - receiver/transmiter of data frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" + +#include <gettext.h> + +static const char *const xfer_type_labels[] = { + [XFER_TYPE_ALSA] = "alsa", +}; + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, struct context_options *opts) +{ + struct { + enum xfer_type type; + const struct xfer_data *data; + } entries[] = { + {XFER_TYPE_ALSA, NULL}, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + if (entries[i].type == type) + break; + } + if (i == ARRAY_SIZE(entries)) + return -EINVAL; + + xfer->type = type; + xfer->ops = &entries[i].data->ops; + xfer->verbose = opts->verbose; + + xfer->private_data = malloc(entries[i].data->private_size); + if (xfer->private_data == NULL) + return -ENOMEM; + memset(xfer->private_data, 0, entries[i].data->private_size); + + return xfer->ops->init(xfer, direction, opts); +} + +void xfer_context_destroy(struct xfer_context *xfer) +{ + if (xfer->ops->destroy) + xfer->ops->destroy(xfer); + if (xfer->private_data) + free(xfer->private_data); +} + +int xfer_context_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + int err; + + err = xfer->ops->pre_process(xfer, format, samples_per_frame, + frames_per_second, access, + frames_per_buffer); + if (err < 0) + return err; + + if (xfer->verbose > 0) { + printf("Transfer: %s\n", xfer_type_labels[xfer->type]); + printf(" access: %s\n", snd_pcm_access_name(*access)); + printf(" sample format: %s\n", snd_pcm_format_name(*format)); + printf(" bytes/sample: %u\n", + snd_pcm_format_physical_width(*format) / 8); + printf(" samples/frame: %u\n", *samples_per_frame); + printf(" frames/second: %u\n", *frames_per_second); + printf(" frames/buffer: %lu\n", *frames_per_buffer); + printf("\n"); + } + + return 0; +} + +int xfer_context_process_frames(struct xfer_context *xfer, + struct aligner_context *aligner, + struct container_context *cntrs, + unsigned int *frame_count) +{ + return xfer->ops->process_frames(xfer, frame_count, aligner, cntrs); +} + +void xfer_context_pause(struct xfer_context *xfer, bool enable) +{ + xfer->ops->pause(xfer, enable); +} + +void xfer_context_post_process(struct xfer_context *xfer) +{ + xfer->ops->post_process(xfer); +} diff --git a/aplay/xfer.h b/aplay/xfer.h new file mode 100644 index 0000000..749fa6c --- /dev/null +++ b/aplay/xfer.h @@ -0,0 +1,71 @@ +/* + * xfer.h - a header for receiver/transmiter of data frames. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_XFER__H_ +#define __ALSA_UTILS_APLAY_XFER__H_ + +#include "aligner.h" +#include "options.h" + +enum xfer_type { + XFER_TYPE_ALSA = 0, +}; + +struct xfer_ops; + +struct xfer_context { + enum xfer_type type; + const struct xfer_ops *ops; + void *private_data; + + unsigned int verbose; +}; + +int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, + snd_pcm_stream_t direction, struct context_options *opts); +void xfer_context_destroy(struct xfer_context *xfer); +int xfer_context_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer); +int xfer_context_process_frames(struct xfer_context *xfer, + struct aligner_context *aligner, + struct container_context *cntrs, + unsigned int *frame_count); +void xfer_context_pause(struct xfer_context *xfer, bool enable); +void xfer_context_post_process(struct xfer_context *xfer); + +/* For internal use in 'xfer' module. */ + +struct xfer_ops { + int (*init)(struct xfer_context *xfer, snd_pcm_stream_t direction, + struct context_options *opts); + int (*pre_process)(struct xfer_context *xfer, snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer); + int (*process_frames)(struct xfer_context *xfer, + unsigned int *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs); + void (*post_process)(struct xfer_context *xfer); + void (*destroy)(struct xfer_context *xfer); + void (*pause)(struct xfer_context *xfer, bool enable); +}; + +struct xfer_data { + struct xfer_ops ops; + unsigned int private_size; +}; + +extern const struct xfer_data xfer_alsa; + +#endif
This commit adds an implementation of xfer to transfer data frames via ALSA PCM interface. Actually, APIs in alsa-lib are used. Hardware and software parameters are configured directly by this implementation. There're two ways to handle PCM frames; on mapped page frames or call system call with buffer in user space. For these ways, this implementation has an abstraction named as 'xfer_alsa_io_ops'.
Furthermore, in a recent decade, timer-based scheduling model is introduced by PulseAudio developers. To support this model, this implementation has an abstraction named as 'xfer_alsa_sched_ops'. --- aplay/Makefile.am | 7 +- aplay/xfer-alsa.c | 537 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/xfer-alsa.h | 57 ++++++ aplay/xfer.c | 2 +- 4 files changed, 600 insertions(+), 3 deletions(-) create mode 100644 aplay/xfer-alsa.c create mode 100644 aplay/xfer-alsa.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 9056f56..9108049 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -11,7 +11,8 @@ noinst_HEADERS = \ aligner.h \ waiter.h \ options.h \ - xfer.h + xfer.h \ + xfer-alsa.h
aplay_SOURCES = \ formats.h \ @@ -32,7 +33,9 @@ aplay_SOURCES = \ options.h \ options.c \ xfer.h \ - xfer.c + xfer.c \ + xfer-alsa.h \ + xfer-alsa.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c new file mode 100644 index 0000000..4a84153 --- /dev/null +++ b/aplay/xfer-alsa.c @@ -0,0 +1,537 @@ +/* + * xfer-alsa.c - receive/transmit frames by ALSA. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-alsa.h" +#include <gettext.h> + +static int get_mode(struct context_options *options) +{ + int mode = 0; + + if (options->nonblock) + mode |= SND_PCM_NONBLOCK; + if (options->no_auto_resample) + mode |= SND_PCM_NO_AUTO_RESAMPLE; + if (options->no_auto_channels) + mode |= SND_PCM_NO_AUTO_CHANNELS; + if (options->no_auto_format) + mode |= SND_PCM_NO_AUTO_FORMAT; + if (options->no_softvol) + mode |= SND_PCM_NO_SOFTVOL; + + return mode; +} + +static int set_access_hw_param(snd_pcm_t *handle, + snd_pcm_hw_params_t *hw_params, + struct context_options *opts) +{ + snd_pcm_access_mask_t *mask; + int err; + + err = snd_pcm_access_mask_malloc(&mask); + if (err < 0) + return err; + snd_pcm_access_mask_none(mask); + if (opts->mmap) { + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); + snd_pcm_access_mask_set(mask, + SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + } else { + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_access_mask_set(mask, + SND_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_params_set_access_mask(handle, hw_params, mask); + snd_pcm_access_mask_free(mask); + + return err; +} + +static void dump_available_hw_params(snd_pcm_hw_params_t *hw_params, + const char *const node) +{ + unsigned int min_i, max_i; + snd_pcm_uframes_t min_l, max_l; + snd_pcm_access_mask_t *access_mask; + snd_pcm_format_mask_t *format_mask; + snd_pcm_subformat_mask_t *subformat_mask; + int i; + int err; + + printf("Avail params for node: %s\n", node); + + err = snd_pcm_hw_params_get_channels_min(hw_params, &min_i); + if (err < 0) + return; + err = snd_pcm_hw_params_get_channels_max(hw_params, &max_i); + if (err < 0) + return; + printf(" samples/frame: %u to %u\n", min_i, max_i); + + err = snd_pcm_hw_params_get_rate_min(hw_params, &min_i, NULL); + if (err < 0) + return; + err = snd_pcm_hw_params_get_rate_max(hw_params, &max_i, NULL); + if (err < 0) + return; + printf(" frames/second: %u to %u\n", min_i, max_i); + + err = snd_pcm_hw_params_get_period_time_min(hw_params, &min_i, NULL); + if (err < 0) + return; + err = snd_pcm_hw_params_get_period_time_max(hw_params, &max_i, NULL); + if (err < 0) + return; + printf(" msec/period: %u to %u\n", min_i, max_i); + + err = snd_pcm_hw_params_get_period_size_min(hw_params, &min_l, NULL); + if (err < 0) + return; + err = snd_pcm_hw_params_get_period_size_max(hw_params, &max_l, NULL); + if (err < 0) + return; + printf(" frames/period: %lu to %lu\n", min_l, max_l); + + err = snd_pcm_hw_params_get_periods_min(hw_params, &min_i, NULL); + if (err < 0) + return; + err = snd_pcm_hw_params_get_periods_max(hw_params, &max_i, NULL); + if (err < 0) + return; + printf(" periods/buffer: %u to %u\n", min_i, max_i); + + err = snd_pcm_hw_params_get_buffer_time_min(hw_params, &min_i, NULL); + if (err < 0) + return; + err = snd_pcm_hw_params_get_buffer_time_max(hw_params, &max_i, NULL); + if (err < 0) + return; + printf(" msec/buffer: %u to %u\n", min_i, max_i); + + err = snd_pcm_hw_params_get_buffer_size_min(hw_params, &min_l); + if (err < 0) + return; + err = snd_pcm_hw_params_get_buffer_size_max(hw_params, &max_l); + if (err < 0) + return; + printf(" frames/buffer: %lu to %lu\n", min_l, max_l); + + err = snd_pcm_access_mask_malloc(&access_mask); + if (err < 0) + return; + err = snd_pcm_hw_params_get_access_mask(hw_params, access_mask); + if (err < 0) + return; + printf(" supported access methods:\n"); + for (i = 0; i <= SND_PCM_ACCESS_LAST; ++i) { + if (!snd_pcm_access_mask_test(access_mask, i)) + continue; + printf(" '%s'\n", snd_pcm_access_name(i)); + } + snd_pcm_access_mask_free(access_mask); + + err = snd_pcm_format_mask_malloc(&format_mask); + if (err < 0) + return; + snd_pcm_hw_params_get_format_mask(hw_params, format_mask); + printf(" supported sample formats:\n"); + for (i = 0; i <= SND_PCM_FORMAT_LAST; ++i) { + if (!snd_pcm_format_mask_test(format_mask, i)) + continue; + printf(" '%s'\n", snd_pcm_format_name(i)); + } + snd_pcm_format_mask_free(format_mask); + + err = snd_pcm_subformat_mask_malloc(&subformat_mask); + if (err < 0) + return; + snd_pcm_hw_params_get_subformat_mask(hw_params, subformat_mask); + printf(" supported sample sub-formats:\n"); + for (i = 0; i <= SND_PCM_SUBFORMAT_LAST; ++i) { + if (!snd_pcm_subformat_mask_test(subformat_mask, i)) + continue; + printf(" '%s'\n", snd_pcm_subformat_name(i)); + } + printf("\n"); +} + +static int xfer_alsa_init(struct xfer_context *xfer, + snd_pcm_stream_t direction, + struct context_options *opts) +{ + struct alsa_state *state = xfer->private_data; + char *node; + int mode = 0; + int err; + + state->verbose = xfer->verbose; + + if (opts->node == NULL) + node = "default"; + else + node = opts->node; + + mode = get_mode(opts); + if (mode & SND_PCM_NONBLOCK) { + state->waiter = malloc(sizeof(*state->waiter)); + if (state->waiter == NULL) + return -ENOMEM; + + /* TODO: configurable. */ + err = waiter_context_init(state->waiter, WAITER_TYPE_POLL); + if (err < 0) + return err; + } + + err = snd_pcm_open(&state->handle, node, direction, mode); + if (err < 0) + return err; + + err = snd_pcm_hw_params_malloc(&state->hw_params); + if (err < 0) + return err; + err = snd_pcm_sw_params_malloc(&state->sw_params); + if (err < 0) + return err; + + err = snd_pcm_hw_params_any(state->handle, state->hw_params); + if (err < 0) + return err; + + if (xfer->verbose) + dump_available_hw_params(state->hw_params, node); + + return set_access_hw_param(state->handle, state->hw_params, opts); +} + +static int prepare_waiter(struct alsa_state *state) +{ + int fd_count; + int *fds; + struct pollfd *pfds; + int i; + int err; + + fd_count = snd_pcm_poll_descriptors_count(state->handle); + + fds = calloc(fd_count, sizeof(int)); + if (fds == NULL) + return -ENOMEM; + + pfds = calloc(fd_count, sizeof(struct pollfd)); + if (pfds == NULL) { + free(fds); + return -ENOMEM; + } + + err = snd_pcm_poll_descriptors(state->handle, pfds, fd_count); + if (err < 0) + goto end; + + for (i = 0; i < fd_count; ++i) + fds[i] = pfds[i].fd; + + err = waiter_context_prepare(state->waiter, fds, fd_count); +end: + free(pfds); + free(fds); + return err; +} + +static int configure_requested_params(struct alsa_state *state, + snd_pcm_format_t format, + unsigned int samples_per_frame, + unsigned int frames_per_second) +{ + int err; + + if (format != SND_PCM_FORMAT_UNKNOWN) { + err = snd_pcm_hw_params_set_format(state->handle, + state->hw_params, format); + if (err < 0) { + printf(_("Sample format '%s' is not available: %s\n"), + snd_pcm_format_name(format), snd_strerror(err)); + return err; + } + } + + if (samples_per_frame > 0) { + err = snd_pcm_hw_params_set_channels(state->handle, + state->hw_params, + samples_per_frame); + if (err < 0) { + printf(_("Channels count '%u' is not available: %s\n"), + samples_per_frame, snd_strerror(err)); + return err; + } + } + + if (frames_per_second > 0) { + err = snd_pcm_hw_params_set_rate(state->handle, + state->hw_params, + frames_per_second, 0); + if (err < 0) { + printf(_("Sampling rate '%u' is not available: %s\n"), + frames_per_second, snd_strerror(err)); + return err; + } + } + + return 0; +} + +static int retrieve_actual_params(snd_pcm_hw_params_t *hw_params, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + int err; + + err = snd_pcm_hw_params_get_format(hw_params, format); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_channels(hw_params, + samples_per_frame); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_rate(hw_params, frames_per_second, + NULL); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_access(hw_params, access); + if (err < 0) + return err; + + return snd_pcm_hw_params_get_buffer_size(hw_params, frames_per_buffer); +} + +static void dump_sw_params(struct alsa_state *state) +{ + snd_pcm_uframes_t val_l; + int val_i; + int err; + + printf("sw params\n"); + + err = snd_pcm_sw_params_get_avail_min(state->sw_params, &val_l); + if (err < 0) + return; + printf(" avail-min: %lu\n", val_l); + + err = snd_pcm_sw_params_get_period_event(state->sw_params, &val_i); + if (err < 0) + return; + printf(" period-event: %d\n", val_i); + + err = snd_pcm_sw_params_get_start_threshold(state->sw_params, &val_l); + if (err < 0) + return; + printf(" start-threshold: %lu\n", val_l); + + err = snd_pcm_sw_params_get_stop_threshold(state->sw_params, &val_l); + if (err < 0) + return; + printf(" stop-threshold: %lu\n", val_l); + + err = snd_pcm_sw_params_get_silence_threshold(state->sw_params, &val_l); + if (err < 0) + return; + printf(" silence-threshold: %lu\n", val_l); + + err = snd_pcm_sw_params_get_silence_size(state->sw_params, &val_l); + if (err < 0) + return; + printf(" silence-size: %lu\n", val_l); + + printf("\n"); +} + +static int xfer_alsa_pre_process(struct xfer_context *xfer, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + snd_pcm_access_t *access, + snd_pcm_uframes_t *frames_per_buffer) +{ + struct alsa_state *state = xfer->private_data; + int err; + + err = configure_requested_params(state, *format, *samples_per_frame, + *frames_per_second); + if (err < 0) + return err; + + /* Configure hardware parameters. */ + err = snd_pcm_hw_params(state->handle, state->hw_params); + if (err < 0) + return err; + + /* Retrieve actual parameters. */ + err = retrieve_actual_params(state->hw_params, format, + samples_per_frame, frames_per_second, + access, frames_per_buffer); + if (err < 0) + return err; + state->frames_per_buffer = *frames_per_buffer; + + /* Query software parameters. */ + err = snd_pcm_sw_params_current(state->handle, state->sw_params); + if (err < 0) + return err; + + /* Assign I/O operation. */ + if (state->io_ops->private_size > 0) { + state->io_private_data = malloc(state->io_ops->private_size); + if (state->io_private_data == NULL) + return -ENOMEM; + memset(state->io_private_data, 0, state->io_ops->private_size); + } + state->access = *access; + err = state->io_ops->pre_process(state); + if (err < 0) + return err; + + /* Assign scheduling operation. */ + err = state->sched_ops->pre_process(xfer); + if (err < 0) + return err; + + err = snd_pcm_sw_params(state->handle, state->sw_params); + if (err < 0) + return err; + + if (state->waiter) { + /* + * NOTE: This should be after configuring sw_params due to + * timer descriptor. + */ + err = prepare_waiter(state); + if (err < 0) + return err; + } + + if (xfer->verbose) + dump_sw_params(state); + + return 0; +} + +static int xfer_alsa_process_frames(struct xfer_context *xfer, + unsigned int *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs) +{ + struct alsa_state *state = xfer->private_data; + int err; + err = state->io_ops->process_frames(state, frame_count, aligner, cntrs); + if (err < 0) { + if (err == -EPIPE) + err = snd_pcm_prepare(state->handle); + } + + return err; +} + +static void xfer_alsa_pause(struct xfer_context *xfer, bool enable) +{ + struct alsa_state *state = xfer->private_data; + snd_pcm_state_t s = snd_pcm_state(state->handle); + int err; + + if (enable) { + if (s != SND_PCM_STATE_RUNNING) + return; + } else { + if (s != SND_PCM_STATE_PAUSED) + return; + } + + /* Not supported. Leave the substream to enter XRUN state. */ + if (!snd_pcm_hw_params_can_pause(state->hw_params)) + return; + + err = snd_pcm_pause(state->handle, enable); + if (err < 0 && xfer->verbose > 0) { + printf("snd_pcm_pause(): %s\n", + snd_strerror(err)); + } +} + +static void xfer_alsa_post_process(struct xfer_context *xfer) +{ + struct alsa_state *state = xfer->private_data; + snd_pcm_state_t pcm_state; + int err; + + pcm_state = snd_pcm_state(state->handle); + if (pcm_state != SND_PCM_STATE_OPEN && + pcm_state != SND_PCM_STATE_DISCONNECTED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + err = snd_pcm_drop(state->handle); + if (err < 0) + printf("snd_pcm_drop(): %s\n", + snd_strerror(err)); + } else { + err = snd_pcm_drain(state->handle); + if (err < 0) + printf("snd_pcm_drain(): %s\n", + snd_strerror(err)); + } + } + + err = snd_pcm_hw_free(state->handle); + if (err < 0) + printf("snd_pcm_hw_free(): %s\n", snd_strerror(err)); + + snd_pcm_close(state->handle); + state->handle = NULL; + + if (state->io_ops && state->io_ops->post_process) + state->io_ops->post_process(state); + if (state->io_private_data) + free(state->io_private_data); + state->io_private_data = NULL; + + if (state->waiter) + waiter_context_release(state->waiter); +} + +static void xfer_alsa_destroy(struct xfer_context *xfer) +{ + struct alsa_state *state = xfer->private_data; + + if (state->waiter) + waiter_context_destroy(state->waiter); + free(state->waiter); + state->waiter = NULL; + + if (state->hw_params) + snd_pcm_hw_params_free(state->hw_params); + if (state->sw_params) + snd_pcm_sw_params_free(state->sw_params); + state->hw_params = NULL; + state->sw_params = NULL; +} + +const struct xfer_data xfer_alsa = { + .ops = { + .init = xfer_alsa_init, + .pre_process = xfer_alsa_pre_process, + .process_frames = xfer_alsa_process_frames, + .pause = xfer_alsa_pause, + .post_process = xfer_alsa_post_process, + .destroy = xfer_alsa_destroy, + }, + .private_size = sizeof(struct alsa_state), +}; diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h new file mode 100644 index 0000000..10c417b --- /dev/null +++ b/aplay/xfer-alsa.h @@ -0,0 +1,57 @@ +/* + * xfer-alsa.h - a header for receiver/transmitter of frames by ALSA. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_XFER_ALSA__H_ +#define __ALSA_UTILS_APLAY_XFER_ALSA__H_ + +#include "xfer.h" +#include "waiter.h" + +enum xfer_alsa_sched_mode { + XFER_ALSA_SCHED_MODE_IRQ = 0, + XFER_ALSA_SCHED_MODE_TIMER, +}; + +struct xfer_alsa_io_ops; +struct xfer_alsa_sched_ops; + +struct alsa_state { + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + + struct waiter_context *waiter; + + snd_pcm_access_t access; + snd_pcm_uframes_t frames_per_buffer; + bool running; + + const struct xfer_alsa_io_ops *io_ops; + void *io_private_data; + + const struct xfer_alsa_sched_ops *sched_ops; + void *private_data; + + bool verbose; +}; + +struct xfer_alsa_io_ops { + int (*pre_process)(struct alsa_state *state); + int (*process_frames)(struct alsa_state *state, + unsigned int *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs); + void (*post_process)(struct alsa_state *state); + unsigned int private_size; +}; + +struct xfer_alsa_sched_ops { + int (*pre_process)(struct xfer_context *xfer); +}; + +#endif diff --git a/aplay/xfer.c b/aplay/xfer.c index 3fe094e..977f00a 100644 --- a/aplay/xfer.c +++ b/aplay/xfer.c @@ -21,7 +21,7 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type, enum xfer_type type; const struct xfer_data *data; } entries[] = { - {XFER_TYPE_ALSA, NULL}, + {XFER_TYPE_ALSA, &xfer_alsa}, }; int i;
This commit adds I/O implementation for 'mmap' and 'rw' operations. In 'mmap' operation, a pointer to mapped page frame is given to 'aligner' function to copy file data directly. In 'rw' operations, this implementation has buffers to cache PCM frames to absorb a case that requested number of frames are not fully handled. --- aplay/Makefile.am | 4 +- aplay/xfer-alsa-io-mmap.c | 134 +++++++++++++++++ aplay/xfer-alsa-io-rw.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++ aplay/xfer-alsa.c | 12 ++ aplay/xfer-alsa.h | 4 + 5 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 aplay/xfer-alsa-io-mmap.c create mode 100644 aplay/xfer-alsa-io-rw.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 9108049..9d5e044 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -35,7 +35,9 @@ aplay_SOURCES = \ xfer.h \ xfer.c \ xfer-alsa.h \ - xfer-alsa.c + xfer-alsa.c \ + xfer-alsa-io-mmap.c \ + xfer-alsa-io-rw.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/xfer-alsa-io-mmap.c b/aplay/xfer-alsa-io-mmap.c new file mode 100644 index 0000000..7ddb880 --- /dev/null +++ b/aplay/xfer-alsa-io-mmap.c @@ -0,0 +1,134 @@ +/* + * xfer-alsa-io-mmap.c - I/O helper for SND_PCM_ACCESS_MMAP_XXX. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-alsa.h" + +static int alsa_mmap_pre_process(struct alsa_state *state) +{ + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail = 0; + unsigned int samples_per_frame; + int i; + int err; + + if (state->verbose) { + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, + &avail); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &samples_per_frame); + if (err < 0) + return err; + + printf("attributes for mapped page frame:\n"); + for (i = 0; i < samples_per_frame; ++i) { + const snd_pcm_channel_area_t *area = areas + i; + printf(" sample number: %d\n", i); + printf(" address: %p\n", area->addr); + printf(" bits for offset: %u\n", area->first); + printf(" bits/frame: %u\n", area->step); + } + printf("\n"); + } + return 0; +} + +static int alsa_mmap_process_frames(struct alsa_state *state, + unsigned *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs) +{ + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t frame_offset; + snd_pcm_uframes_t avail; + unsigned int avail_count; + char *frame_buf; + int err; + + /* For capture direction, need to start stream explicitly. */ + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) { + snd_pcm_state_t s = snd_pcm_state(state->handle); + if (s != SND_PCM_STATE_RUNNING) { + if (s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + } + + if (state->waiter) { + err = waiter_context_wait_event(state->waiter); + if (err < 0) + goto error; + } + + avail = *frame_count; + err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail); + if (err < 0) + goto error; + + /* Trim according up to expected frame count. */ + if (*frame_count < avail) + avail_count = *frame_count; + else + avail_count = (unsigned int)avail; + + frame_buf = areas[0].addr; + /* TODO: What's going on non-interleaved buffer??? */ + frame_buf += snd_pcm_frames_to_bytes(state->handle, frame_offset); + err = aligner_context_process_frames(aligner, frame_buf, &avail_count, + cntrs); + if (err < 0) + goto error; + if (avail_count == 0) + goto error; + + err = snd_pcm_mmap_commit(state->handle, frame_offset, avail_count); + if (err < 0) + goto error; + + /* Need to start playback stream explicitly */ + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_PLAYBACK) { + snd_pcm_state_t s = snd_pcm_state(state->handle); + if (s != SND_PCM_STATE_RUNNING) { + if (s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + } + + *frame_count = avail_count; + + return 0; +error: + *frame_count = 0; + return err; +} + +static void alsa_mmap_post_process(struct alsa_state *state) +{ + return; +} + +const struct xfer_alsa_io_ops xfer_alsa_mmap_ops = { + .pre_process = alsa_mmap_pre_process, + .process_frames = alsa_mmap_process_frames, + .post_process = alsa_mmap_post_process, +}; diff --git a/aplay/xfer-alsa-io-rw.c b/aplay/xfer-alsa-io-rw.c new file mode 100644 index 0000000..a69d7e0 --- /dev/null +++ b/aplay/xfer-alsa-io-rw.c @@ -0,0 +1,368 @@ +/* + * xfer-alsa-io-rw.c - I/O helper for SND_PCM_ACCESS_RW_XXX. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-alsa.h" + +struct frame_cache { + void *buf; + void *buf_ptr; + + unsigned int bytes_per_sample; + unsigned int samples_per_frame; + unsigned int frames_per_buffer; + snd_pcm_access_t access; + + void (*align_frames)(struct frame_cache *cache, + unsigned int avail_count, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame); + + unsigned int remained_count; +}; + +static void align_frames_in_i(struct frame_cache *cache, + unsigned int avail_count, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char *buf = cache->buf; + unsigned int offset; + unsigned int size; + + cache->remained_count = avail_count - consumed_count; + + if (cache->remained_count == 0) { + cache->buf_ptr = buf; + } else { + offset = bytes_per_sample * samples_per_frame * consumed_count; + size = bytes_per_sample * samples_per_frame * + cache->remained_count; + if (offset > 0) + memcpy(buf, buf + offset, size); + cache->buf_ptr = buf + size; + } +} + +static void align_frames_in_n(struct frame_cache *cache, + unsigned int avail_count, + unsigned int consumed_count, + unsigned int bytes_per_sample, + unsigned int samples_per_frame) +{ + char **bufs = cache->buf; + char **buf_ptrs = cache->buf_ptr; + char *buf; + unsigned int offset; + unsigned int size; + int i; + + cache->remained_count = avail_count - consumed_count; + + if (cache->remained_count == 0) { + for (i = 0; i < samples_per_frame; ++i) + buf_ptrs[i] = bufs[i]; + return; + } + + for (i = 0; i < samples_per_frame; ++i) { + buf = bufs[i]; + offset = bytes_per_sample * consumed_count; + size = bytes_per_sample * cache->remained_count; + memcpy(buf, buf + offset, size); + buf_ptrs[i] = buf + size; + } +} + +static int alsa_rw_pre_process(struct alsa_state *state) +{ + struct frame_cache *cache = state->io_private_data; + snd_pcm_format_t format; + snd_pcm_uframes_t frames_per_buffer; + uint64_t bytes_per_buffer; + int err; + + err = snd_pcm_hw_params_get_format(state->hw_params, &format); + if (err < 0) + return err; + cache->bytes_per_sample = snd_pcm_format_physical_width(format); + + err = snd_pcm_hw_params_get_channels(state->hw_params, + &cache->samples_per_frame); + if (err < 0) + return err; + + err = snd_pcm_hw_params_get_buffer_size(state->hw_params, + &frames_per_buffer); + if (err < 0) + return err; + cache->frames_per_buffer = (unsigned int)frames_per_buffer; + + /* Allocate buffer and assign callback for alignment. */ + err = snd_pcm_hw_params_get_access(state->hw_params, &cache->access); + if (err < 0) + return err; + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + bytes_per_buffer = cache->bytes_per_sample * + cache->samples_per_frame * + cache->frames_per_buffer; + cache->buf = malloc(bytes_per_buffer); + if (cache->buf == NULL) + return -ENOMEM; + cache->buf_ptr = cache->buf; + cache->align_frames = align_frames_in_i; + } else if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + char **bufs; + char **buf_ptrs; + int i; + bufs = calloc(cache->samples_per_frame * 2, sizeof(*bufs)); + if (bufs == NULL) + return -ENOMEM; + buf_ptrs = bufs + cache->samples_per_frame * sizeof(*bufs); + for (i = 0; i < cache->samples_per_frame; ++i) { + bytes_per_buffer = cache->bytes_per_sample * + cache->frames_per_buffer; + bufs[i] = malloc(bytes_per_buffer); + if (bufs[i] == NULL) + return -ENOMEM; + buf_ptrs[i] = bufs[i]; + } + cache->buf = bufs; + cache->buf_ptr = buf_ptrs; + cache->align_frames = align_frames_in_n; + } else { + return -ENXIO; + } + + return 0; +} + +static int alsa_r_process_frames(struct alsa_state *state, + unsigned int *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs) +{ + struct frame_cache *cache = state->io_private_data; + snd_pcm_sframes_t avail; + snd_pcm_uframes_t avail_count; + unsigned int consumed_count; + int err = 0; + + /* In non-blocking mode, work with I/O notifier. */ + if (state->waiter) { + snd_pcm_state_t s = snd_pcm_state(state->handle); + if (s != SND_PCM_STATE_RUNNING) { + if (s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = snd_pcm_start(state->handle); + if (err < 0) + goto error; + } + + err = waiter_context_wait_event(state->waiter); + if (err < 0) + goto error; + } + + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (snd_pcm_uframes_t)avail; + + if (avail_count == 0) { + /* In non-blocking operation, let's go to a next iteration. */ + if (state->waiter) { + err = -EAGAIN; + goto error; + } + + /* Prepare for blocking this process till 'avail_min'. */ + err = snd_pcm_sw_params_get_avail_min(state->sw_params, + &avail_count); + if (err < 0) + goto error; + } + + /* Trim according up to expected frame count. */ + if (*frame_count < avail_count) + avail_count = *frame_count; + + /* Cache required amount of frames. */ + if (avail_count > cache->remained_count) { + avail_count -= cache->remained_count; + + /* + * Execute write operation according to the shape of buffer. + * These operations automatically start the substream. + */ + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) { + err = snd_pcm_readi(state->handle, cache->buf_ptr, + avail_count); + } else { + err = snd_pcm_readn(state->handle, cache->buf_ptr, + avail_count); + } + if (err < 0) + goto error; + cache->remained_count += err; + avail_count = cache->remained_count; + } + + /* Write out to file descriptors. */ + consumed_count = avail_count; + err = aligner_context_process_frames(aligner, cache->buf, + &consumed_count, cntrs); + if (err < 0) + goto error; + cache->align_frames(cache, cache->remained_count, consumed_count, + cache->bytes_per_sample, cache->samples_per_frame); + + *frame_count = consumed_count; + + return 0; +error: + *frame_count = 0; + return err; +} + +static int alsa_w_process_frames(struct alsa_state *state, + unsigned *frame_count, + struct aligner_context *aligner, + struct container_context *cntrs) +{ + struct frame_cache *cache = state->io_private_data; + snd_pcm_sframes_t avail; + unsigned int avail_count; + snd_pcm_uframes_t consumed_count; + int err; + + /* In non-blocking mode, work with I/O notifier. */ + if (state->waiter) { + snd_pcm_state_t s = snd_pcm_state(state->handle); + /* Need to recover the stream. */ + if (s != SND_PCM_STATE_RUNNING && s != SND_PCM_STATE_PREPARED) { + err = -EPIPE; + goto error; + } + + err = waiter_context_wait_event(state->waiter); + if (err < 0) + goto error; + } + + /* Check available space on the buffer. */ + avail = snd_pcm_avail(state->handle); + if (avail < 0) { + err = avail; + goto error; + } + avail_count = (unsigned int)avail; + + if (avail_count == 0) { + /* In non-blocking operation, let's go to a next iteration. */ + if (state->waiter) { + err = 0; + goto error; + } + + if (snd_pcm_state(state->handle) == SND_PCM_STATE_RUNNING) { + snd_pcm_uframes_t avail_min; + err = snd_pcm_sw_params_get_avail_min(state->sw_params, + &avail_min); + if (err < 0) + goto error; + avail_count = (unsigned int)avail_min; + } else { + /* NOTE: precisely, start_threshold is important. */ + avail_count = state->frames_per_buffer; + } + } + + /* Trim according up to expected frame count. */ + if (*frame_count < avail_count) + avail_count = *frame_count; + + /* Cache required amount of frames. */ + if (avail_count > cache->remained_count) { + avail_count -= cache->remained_count; + + /* Read frames to transfer. */ + err = aligner_context_process_frames(aligner, cache->buf_ptr, + &avail_count, cntrs); + if (err < 0) + goto error; + cache->remained_count += avail_count; + avail_count = cache->remained_count; + } + + /* + * Execute write operation according to the shape of buffer. These + * operations automatically start the stream. + */ + consumed_count = avail_count; + if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) + err = snd_pcm_writei(state->handle, cache->buf, consumed_count); + else + err = snd_pcm_writen(state->handle, cache->buf, consumed_count); + if (err < 0) + goto error; + cache->align_frames(cache, cache->remained_count, (unsigned int)err, + cache->bytes_per_sample, cache->samples_per_frame); + + *frame_count = consumed_count; + + return 0; +error: + *frame_count = 0; + return err; +} + +static void alsa_rw_post_process(struct alsa_state *state) +{ + struct frame_cache *cache = state->io_private_data; + + if (cache->buf == NULL) { + if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + char **bufs = cache->buf; + char **buf_ptrs = cache->buf_ptr; + int i; + for (i = 0; i < cache->samples_per_frame; ++i) { + if (bufs[i]) + free(bufs[i]); + bufs[i] = NULL; + buf_ptrs[i] = NULL; + } + + } + free(cache->buf); + free(cache->buf_ptr); + } + cache->buf = NULL; + cache->buf_ptr = NULL; +} + +const struct xfer_alsa_io_ops xfer_alsa_r_ops = { + .pre_process = alsa_rw_pre_process, + .process_frames = alsa_r_process_frames, + .post_process = alsa_rw_post_process, + .private_size = sizeof(struct frame_cache), +}; + +const struct xfer_alsa_io_ops xfer_alsa_w_ops = { + .pre_process = alsa_rw_pre_process, + .process_frames = alsa_w_process_frames, + .post_process = alsa_rw_post_process, + .private_size = sizeof(struct frame_cache), +}; diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c index 4a84153..57be488 100644 --- a/aplay/xfer-alsa.c +++ b/aplay/xfer-alsa.c @@ -391,6 +391,18 @@ static int xfer_alsa_pre_process(struct xfer_context *xfer, return err;
/* Assign I/O operation. */ + if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED || + *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) { + state->io_ops = &xfer_alsa_mmap_ops; + } else if (*access == SND_PCM_ACCESS_RW_INTERLEAVED || + *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) + state->io_ops = &xfer_alsa_r_ops; + else + state->io_ops = &xfer_alsa_w_ops; + } else { + return -ENXIO; + } if (state->io_ops->private_size > 0) { state->io_private_data = malloc(state->io_ops->private_size); if (state->io_private_data == NULL) diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h index 10c417b..1731f04 100644 --- a/aplay/xfer-alsa.h +++ b/aplay/xfer-alsa.h @@ -54,4 +54,8 @@ struct xfer_alsa_sched_ops { int (*pre_process)(struct xfer_context *xfer); };
+extern const struct xfer_alsa_io_ops xfer_alsa_mmap_ops; +extern const struct xfer_alsa_io_ops xfer_alsa_r_ops; +extern const struct xfer_alsa_io_ops xfer_alsa_w_ops; + #endif
This commit is planned to add an implementation for scheduling model. --- aplay/Makefile.am | 4 +++- aplay/xfer-alsa-sched-irq.c | 18 ++++++++++++++++ aplay/xfer-alsa-sched-timer.c | 49 +++++++++++++++++++++++++++++++++++++++++++ aplay/xfer-alsa.c | 1 + aplay/xfer-alsa.h | 3 +++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 aplay/xfer-alsa-sched-irq.c create mode 100644 aplay/xfer-alsa-sched-timer.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 9d5e044..c55fac7 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -37,7 +37,9 @@ aplay_SOURCES = \ xfer-alsa.h \ xfer-alsa.c \ xfer-alsa-io-mmap.c \ - xfer-alsa-io-rw.c + xfer-alsa-io-rw.c \ + xfer-alsa-sched-irq.c \ + xfer-alsa-sched-timer.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/xfer-alsa-sched-irq.c b/aplay/xfer-alsa-sched-irq.c new file mode 100644 index 0000000..135819f --- /dev/null +++ b/aplay/xfer-alsa-sched-irq.c @@ -0,0 +1,18 @@ +/* + * xfer-alsa-sched-irq.c - interrupt-based scheduling model of ALSA. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-alsa.h" + +static int alsa_irq_pre_process(struct xfer_context *xfer) +{ + return 0; +} + +const struct xfer_alsa_sched_ops xfer_alsa_sched_irq_ops = { + .pre_process = alsa_irq_pre_process, +}; diff --git a/aplay/xfer-alsa-sched-timer.c b/aplay/xfer-alsa-sched-timer.c new file mode 100644 index 0000000..72187cc --- /dev/null +++ b/aplay/xfer-alsa-sched-timer.c @@ -0,0 +1,49 @@ +/* + * xfer-alsa-sched-timer.c - timer-based scheduling model of ALSA. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer-alsa.h" + +static int alsa_timer_pre_process(struct xfer_context *xfer) +{ + struct alsa_state *state = xfer->private_data; + snd_pcm_uframes_t boundary; + snd_pcm_uframes_t avail_min; + int err; + + err = snd_pcm_sw_params_get_boundary(state->sw_params, &boundary); + if (err < 0) + return err; + + err = snd_pcm_sw_params_set_stop_threshold(state->handle, + state->sw_params, boundary); + if (err < 0) + return err; + + err = snd_pcm_sw_params_set_start_threshold(state->handle, + state->sw_params, -1); + if (err < 0) + return err; + + /* TODO: calculate proper value fo ravail_min. */ + err = snd_pcm_hw_params_get_period_size(state->hw_params, &avail_min, + NULL); + if (err < 0) + return err; + + err = snd_pcm_sw_params_set_avail_min(state->handle, state->sw_params, + avail_min); + if (err < 0) + return err; + + return snd_pcm_sw_params_set_period_event(state->handle, + state->sw_params, 1); +} + +const struct xfer_alsa_sched_ops xfer_alsa_sched_timer_ops = { + .pre_process = alsa_timer_pre_process, +}; diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c index 57be488..05e6e7e 100644 --- a/aplay/xfer-alsa.c +++ b/aplay/xfer-alsa.c @@ -415,6 +415,7 @@ static int xfer_alsa_pre_process(struct xfer_context *xfer, return err;
/* Assign scheduling operation. */ + state->sched_ops = &xfer_alsa_sched_irq_ops; err = state->sched_ops->pre_process(xfer); if (err < 0) return err; diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h index 1731f04..9cc079d 100644 --- a/aplay/xfer-alsa.h +++ b/aplay/xfer-alsa.h @@ -58,4 +58,7 @@ extern const struct xfer_alsa_io_ops xfer_alsa_mmap_ops; extern const struct xfer_alsa_io_ops xfer_alsa_r_ops; extern const struct xfer_alsa_io_ops xfer_alsa_w_ops;
+extern const struct xfer_alsa_sched_ops xfer_alsa_sched_irq_ops; +extern const struct xfer_alsa_sched_ops xfer_alsa_sched_timer_ops; + #endif
In current implementation of aplay, available PCM nodes and devices are listed by options '--list-pcms' (-L) and '--list-devices' (-l).
This commit adds a file to handle the action. In future commit, this is handled by 'list' sub-command. --- aplay/Makefile.am | 7 +- aplay/main.h | 32 ++++++++ aplay/subcmd-list.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 aplay/main.h create mode 100644 aplay/subcmd-list.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index c55fac7..0113b5c 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -12,7 +12,8 @@ noinst_HEADERS = \ waiter.h \ options.h \ xfer.h \ - xfer-alsa.h + xfer-alsa.h \ + main.h
aplay_SOURCES = \ formats.h \ @@ -39,7 +40,9 @@ aplay_SOURCES = \ xfer-alsa-io-mmap.c \ xfer-alsa-io-rw.c \ xfer-alsa-sched-irq.c \ - xfer-alsa-sched-timer.c + xfer-alsa-sched-timer.c \ + main.h \ + subcmd-list.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/main.h b/aplay/main.h new file mode 100644 index 0000000..21f2269 --- /dev/null +++ b/aplay/main.h @@ -0,0 +1,32 @@ +/* + * main.h - a header for main routine of each sub-commands. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_MAIN__H_ +#define __ALSA_UTILS_APLAY_MAIN__H_ + +#include <stdbool.h> +#include <gettext.h> + +#include <alsa/asoundlib.h> + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) +#endif + +enum subcmds { + SUBCMD_TRANSFER = 0, + SUBCMD_LIST, + SUBCMD_HELP, + SUBCMD_VERSION, +}; + +int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction); + +int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction); + +#endif diff --git a/aplay/subcmd-list.c b/aplay/subcmd-list.c new file mode 100644 index 0000000..28862e1 --- /dev/null +++ b/aplay/subcmd-list.c @@ -0,0 +1,233 @@ +/* + * subcmd-list.c - operations for list sub command. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "main.h" + +#include <getopt.h> + +static int dump_device(snd_ctl_t *handle, const char *id, const char *name, + snd_pcm_stream_t direction, snd_pcm_info_t *info) +{ + unsigned int count; + int i; + int err; + + printf(_("card %i: %s [%s], device %i: %s [%s]\n"), + snd_pcm_info_get_card(info), id, name, + snd_pcm_info_get_device(info), snd_pcm_info_get_id(info), + snd_pcm_info_get_name(info)); + + count = snd_pcm_info_get_subdevices_count(info); + printf(_(" Subdevices: %i/%i\n"), + snd_pcm_info_get_subdevices_avail(info), count); + + for (i = 0; i < count; ++i) { + snd_pcm_info_set_subdevice(info, i); + + err = snd_ctl_pcm_info(handle, info); + if (err < 0) { + printf("control digital audio playback info (%i): %s", + snd_pcm_info_get_card(info), snd_strerror(err)); + continue; + } + + printf(_(" Subdevice #%i: %s\n"), + i, snd_pcm_info_get_subdevice_name(info)); + } + + return 0; +} + +static int dump_devices(snd_ctl_t *handle, const char *id, const char *name, + snd_pcm_stream_t direction) +{ + snd_pcm_info_t *info; + int device = -1; + int err; + + err = snd_pcm_info_malloc(&info); + if (err < 0) + return err; + + while (1) { + err = snd_ctl_pcm_next_device(handle, &device); + if (err < 0) + break; + if (device < 0) + break; + + snd_pcm_info_set_device(info, device); + snd_pcm_info_set_subdevice(info, 0); + snd_pcm_info_set_stream(info, direction); + err = snd_ctl_pcm_info(handle, info); + if (err < 0) + continue; + + err = dump_device(handle, id, name, direction, info); + if (err < 0) + break; + } + + free(info); + return err; +} + +static int list_devices(snd_pcm_stream_t direction) +{ + int card = -1; + char name[32]; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + int err; + + err = snd_ctl_card_info_malloc(&info); + if (err < 0) + return err; + + /* Not found. */ + if (snd_card_next(&card) < 0 || card < 0) + goto end; + + printf(_("**** List of %s Hardware Devices ****\n"), + snd_pcm_stream_name(direction)); + + while (card >= 0) { + sprintf(name, "hw:%d", card); + err = snd_ctl_open(&handle, name, 0); + if (err < 0) { + printf("control open (%i): %s", + card, snd_strerror(err)); + } else { + err = snd_ctl_card_info(handle, info); + if (err < 0) { + printf("control hardware info (%i): %s", + card, snd_strerror(err)); + } else { + err = dump_devices(handle, + snd_ctl_card_info_get_id(info), + snd_ctl_card_info_get_name(info), + direction); + } + snd_ctl_close(handle); + } + + if (err < 0) + break; + + /* Go to next. */ + if (snd_card_next(&card) < 0) { + printf("snd_card_next"); + break; + } + } +end: + free(info); + return err; +} + +static int list_pcms(snd_pcm_stream_t direction) +{ + static const char *const filters[] = { + [SND_PCM_STREAM_CAPTURE] = "Input", + [SND_PCM_STREAM_PLAYBACK] = "Output", + }; + const char *filter; + void **hints; + void **n; + char *io; + char *name; + char *desc; + + if (snd_device_name_hint(-1, "pcm", &hints) < 0) + return -EINVAL; + + filter = filters[direction]; + + n = hints; + for (n = hints; *n != NULL; ++n) { + io = snd_device_name_get_hint(*n, "IOID"); + if (io != NULL && strcmp(io, filter) != 0) { + free(io); + continue; + } + + name = snd_device_name_get_hint(*n, "NAME"); + desc = snd_device_name_get_hint(*n, "DESC"); + + printf("%s\n", name); + if (desc == NULL) { + free(name); + free(desc); + continue; + } + + + printf(" "); + while (*desc) { + if (*desc == '\n') + printf("\n "); + else + putchar(*desc); + desc++; + } + putchar('\n'); + } + + snd_device_name_free_hint(hints); + + return 0; +} + +static void print_help(void) +{ + printf("help for list sub-command.\n"); +} + +int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction) +{ + static const struct { + const char *const category; + int (*func)(snd_pcm_stream_t direction); + } ops[] = { + {"device", list_devices}, + {"pcm", list_pcms}, + }; + int i; + static const char *s_opts = "hlL"; + static const struct option l_opts[] = { + {"list-devices", 0, NULL, 'l'}, + {"list-pcms", 0, NULL, 'L'}, + {NULL, 0, NULL, 0} + }; + int c; + + /* Renewed command system. */ + if (argc > 2 && !strcmp(argv[1], "list")) { + for (i = 0; i < ARRAY_SIZE(ops); ++i) { + if (!strcmp(ops[i].category, argv[2])) + return ops[i].func(direction); + } + } + + /* Original command system. */ + optind = 0; + opterr = 0; + while (1) { + c = getopt_long(argc, argv, s_opts, l_opts, NULL); + if (c < 0) + break; + if (c == 'l') + return list_devices(direction); + if (c == 'L') + return list_pcms(direction); + } + + print_help(); + + return 1; +}
In current implementation of aplay, default action is to transfer data frames from/to devices.
This commit adds a file to handle the action. In future commit, this is handled by 'transfer' sub-command. Event loop is included in this file. In the loop, the number of handled data frames is maintained by an appropriate way. As a result, users can stop data transmission frames by frame. --- aplay/Makefile.am | 3 +- aplay/subcmd-transfer.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 aplay/subcmd-transfer.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 0113b5c..f604083 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -42,7 +42,8 @@ aplay_SOURCES = \ xfer-alsa-sched-irq.c \ xfer-alsa-sched-timer.c \ main.h \ - subcmd-list.c + subcmd-list.c \ + subcmd-transfer.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/subcmd-transfer.c b/aplay/subcmd-transfer.c new file mode 100644 index 0000000..f163b05 --- /dev/null +++ b/aplay/subcmd-transfer.c @@ -0,0 +1,414 @@ +/* + * subcmd-transfer.c - operations for transfer sub command. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" +#include "key-event.h" + +#include <signal.h> +#include <gettext.h> + +struct context { + struct xfer_context xfer; + struct aligner_context aligner; + struct container_context *cntrs; + unsigned int cntr_count; + + struct context_options opts; + struct keyevent_context key; + + /* NOTE: To handling Unix signal. */ + bool interrupted; + int signal; +}; + +/* NOTE: To handling Unix signal. */ +static struct context *ctx_ptr; + +static void handle_unix_signal_for_finish(int sig) +{ + int i; + + for (i = 0; i < ctx_ptr->cntr_count; ++i) + ctx_ptr->cntrs[i].interrupted = true; + + ctx_ptr->signal = sig; + ctx_ptr->interrupted = true; +} + +static void handle_unix_signal_for_suspend(int sig) +{ + sigset_t curr, prev; + struct sigaction sa = {0}; + + /* + * 1. suspend substream. + */ + xfer_context_pause(&ctx_ptr->xfer, true); + + /* + * 2. Run default handler(SIG_DFL) for SIGTSTP to stop this process, by + * yielding CPU. + */ + if (sigaction(SIGTSTP, NULL, &sa) < 0) { + printf("sigaction(2)\n"); + exit(EXIT_FAILURE); + } + if (sa.sa_handler == SIG_ERR) + exit(EXIT_FAILURE); + if (sa.sa_handler == handle_unix_signal_for_suspend) + sa.sa_handler = SIG_DFL; + if (sigaction(SIGTSTP, &sa, NULL) < 0) { + printf("sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + /* Queue SIGTSTP. */ + raise(SIGTSTP); + + /* + * Release the queued signal from being blocked, then yield CPU to the + * default handler. + */ + sigemptyset(&curr); + sigaddset(&curr, SIGTSTP); + if (sigprocmask(SIG_UNBLOCK, &curr, &prev) < 0) { + printf("sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* + * 3. SIGCONT is cought and rescheduled again, then continue substream. + */ + if (sigprocmask(SIG_SETMASK, &prev, NULL) < 0) { + printf("sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* Reconfigure this handler for SIGTSTP. */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = handle_unix_signal_for_suspend; + if (sigaction(SIGTSTP, &sa, NULL) < 0) { + printf("sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + xfer_context_pause(&ctx_ptr->xfer, false); +} + +static int prepare_signal_handler(struct context *ctx) +{ + struct sigaction sa = {0}; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_unix_signal_for_finish; + + if (sigaction(SIGINT, &sa, NULL) < 0) + return -errno; + if (sigaction(SIGTERM, &sa, NULL) < 0) + return -errno; + if (sigaction(SIGABRT, &sa, NULL) < 0) + return -errno; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handle_unix_signal_for_suspend; + if (sigaction(SIGTSTP, &sa, NULL) < 0) + return -errno; + + ctx_ptr = ctx; + + return 0; +} + +static int context_init(struct context *ctx, snd_pcm_stream_t direction) +{ + enum aligner_type type; + int err; + + /* Initialize transfer. */ + err = xfer_context_init(&ctx->xfer, XFER_TYPE_ALSA, direction, + &ctx->opts); + if (err < 0) + return err; + + /* Allocate containers. */ + ctx->cntrs = calloc(ctx->opts.path_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = ctx->opts.path_count; + + /* Initialize aligner. */ + if (direction == SND_PCM_STREAM_CAPTURE) + type = ALIGNER_TYPE_DEMUXER; + else + type = ALIGNER_TYPE_MUXER; + err = aligner_context_init(&ctx->aligner, type, ctx->cntr_count, + ctx->opts.verbose); + if (err < 0) + return err; + + return 0; +} + +static uint64_t calculate_expected_frame_count(struct context_options *opts, + unsigned int frames_per_second, + uint64_t total_frame_count) +{ + uint64_t frame_count; + + if (opts->timelimit_seconds > 0) { + frame_count = frames_per_second * opts->timelimit_seconds; + + if (frame_count < total_frame_count) + return frame_count; + } + + return total_frame_count; +} + +static int capture_context_pre_process(struct context *ctx, + uint64_t *total_frame_count) +{ + snd_pcm_access_t access = 0; + snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN; + unsigned int samples_per_frame = 0; + unsigned int frames_per_second = 0; + snd_pcm_uframes_t frames_per_buffer = 0; + int i; + int err; + + /* + * Use given options for common parameters. Else, leave the decition to + * each backend. + */ + if (ctx->opts.sample_format != SND_PCM_FORMAT_UNKNOWN) + sample_format = ctx->opts.sample_format; + if (ctx->opts.samples_per_frame > 0) + samples_per_frame = ctx->opts.samples_per_frame; + if (ctx->opts.frames_per_second > 0) + frames_per_second = ctx->opts.frames_per_second; + + err = xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + &access, &frames_per_buffer, + ctx->opts.chmap, ctx->opts.vu_mode); + if (err < 0) + return err; + + /* Normalize paths. */ + if (ctx->opts.path_count != samples_per_frame) { + err = context_options_normalize_paths(&ctx->opts, + ctx->opts.cntr_format, + samples_per_frame); + if (err < 0) + return err; + } + + for (i = 0; i < ctx->cntr_count; ++i) { + err = container_builder_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.cntr_format, + ctx->opts.verbose); + if (err < 0) + return err; + + err = container_context_pre_process(ctx->cntrs + i, + &sample_format, + &samples_per_frame, + &frames_per_second, + total_frame_count); + if (err < 0) + return err; + } + + err = aligner_context_pre_process(&ctx->aligner, access, sample_format, + samples_per_frame, frames_per_buffer, + ctx->cntrs); + if (err < 0) + return err; + + *total_frame_count = calculate_expected_frame_count(&ctx->opts, + frames_per_second, *total_frame_count); + + return 0; +} + +static int playback_context_pre_process(struct context *ctx, + uint64_t *total_frame_count) +{ + snd_pcm_access_t access = 0; + snd_pcm_format_t sample_format; + unsigned int samples_per_frame; + unsigned int frames_per_second; + snd_pcm_uframes_t frames_per_buffer; + int i; + int err; + + /* Initialize containers to retrieve parameters. */ + for (i = 0; i < ctx->cntr_count; ++i) { + err = container_parser_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.verbose); + if (err < 0) + return err; + + err = container_context_pre_process(ctx->cntrs + i, + &sample_format, + &samples_per_frame, + &frames_per_second, + total_frame_count); + if (err < 0) + return err; + } + + /* Configure hardware with these parameters. */ + err = xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + &access, &frames_per_buffer, + ctx->opts.chmap, ctx->opts.vu_mode); + if (err < 0) + return err; + + err = aligner_context_pre_process(&ctx->aligner, access, sample_format, + samples_per_frame, frames_per_buffer, + ctx->cntrs); + if (err < 0) + return err; + + *total_frame_count = calculate_expected_frame_count(&ctx->opts, + frames_per_second, *total_frame_count); + + return 0; +} + +static int context_process_frames(struct context *ctx, + uint64_t expected_frame_count, + uint64_t *actual_frame_count) +{ + bool verbose = ctx->opts.verbose; + unsigned int frame_count; + bool emitted; + int i; + int err; + + *actual_frame_count = 0; + while (!ctx->interrupted) { + /* Tell remains to expected frame count. */ + frame_count = expected_frame_count - *actual_frame_count; + err = xfer_context_process_frames(&ctx->xfer, &ctx->aligner, + ctx->cntrs, &frame_count); + if (err < 0) { + if (err == -EAGAIN || err == -EINTR) + continue; + break; + } + /* Perhaps, reach EOF of any container file for playback. */ + if (frame_count == 0) { + struct container_context *cntr; + for (i = 0; i < ctx->cntr_count; ++i) { + cntr = &ctx->cntrs[i]; + if (cntr->type != CONTAINER_TYPE_PARSER && + cntr->eof) + break; + } + } + + *actual_frame_count += frame_count; + if (*actual_frame_count >= expected_frame_count) + break; + } + + if (verbose) { + printf(_("Total frames: expected: %lu, actual: %lu\n"), + expected_frame_count, *actual_frame_count); + } + + return err; +} + +static void context_post_process(struct context *ctx, + uint64_t accumulated_frame_count) +{ + int i; + + for (i = 0; i < ctx->cntr_count; ++i) { + container_context_post_process(ctx->cntrs + i, + accumulated_frame_count); + container_context_destroy(ctx->cntrs + i); + } + + xfer_context_post_process(&ctx->xfer); + + aligner_context_post_process(&ctx->aligner); +} + +static void context_destroy(struct context *ctx) +{ + context_options_destroy(&ctx->opts); + + free(ctx->cntrs); + + aligner_context_destroy(&ctx->aligner); + xfer_context_destroy(&ctx->xfer); +} + +int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction) +{ + struct context ctx = {0}; + + uint64_t expected_frame_count; + uint64_t actual_frame_count = 0; + + int err = 0; + + /* Renewed command system. */ + if (argc > 2 && !strcmp(argv[1], "transfer")) { + /* Go ahead to parse file paths properly. */ + --argc; + ++argv; + } + + err = prepare_signal_handler(&ctx); + if (err < 0) + return err; + + err = context_options_init(&ctx.opts, argc, argv, direction); + if (err < 0) + return err; + if (ctx.opts.help) { + printf("xfer help.\n"); + return 0; + } + + err = context_init(&ctx, direction); + if (err < 0) + goto end; + + if (direction == SND_PCM_STREAM_CAPTURE) + err = capture_context_pre_process(&ctx, &expected_frame_count); + else + err = playback_context_pre_process(&ctx, &expected_frame_count); + if (err < 0) + goto end; + + err = context_process_frames(&ctx, expected_frame_count, + &actual_frame_count); +end: + context_post_process(&ctx, actual_frame_count); + + context_destroy(&ctx); + + if (ctx.interrupted) { + printf(_("Aborted by signal: %s\n"), + strsignal(ctx.signal)); + return 0; + } + + return err; +} +
This commit removes old stuffs of aplay and introduce sub-command style. Old combinations of parameters are still available. In below list, correspondence of old/new style are shown.
$ aplay list pcm (= -L, --list-pcms) $ aplay list device (= -l, --list-devices) $ aplay version (= --version) $ aplay help (= -h, --help) $ aplay transfer .. (the others) --- aplay/Makefile.am | 6 +- aplay/aplay.c | 3425 ----------------------------------------------- aplay/formats.h | 134 -- aplay/main.c | 178 +++ aplay/subcmd-transfer.c | 9 +- configure.ac | 2 +- 6 files changed, 183 insertions(+), 3571 deletions(-) delete mode 100644 aplay/aplay.c delete mode 100644 aplay/formats.h create mode 100644 aplay/main.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index f604083..5536c00 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -6,7 +6,6 @@ LDADD = $(LIBINTL) $(LIBRT) bin_PROGRAMS = aplay man_MANS = aplay.1 arecord.1 noinst_HEADERS = \ - formats.h \ container.h \ aligner.h \ waiter.h \ @@ -16,8 +15,6 @@ noinst_HEADERS = \ main.h
aplay_SOURCES = \ - formats.h \ - aplay.c \ container.h \ container.c \ container-riff-wave.c \ @@ -43,7 +40,8 @@ aplay_SOURCES = \ xfer-alsa-sched-timer.c \ main.h \ subcmd-list.c \ - subcmd-transfer.c + subcmd-transfer.c \ + main.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/aplay.c b/aplay/aplay.c deleted file mode 100644 index 0aa1688..0000000 --- a/aplay/aplay.c +++ /dev/null @@ -1,3425 +0,0 @@ -/* - * aplay.c - plays and records - * - * CREATIVE LABS CHANNEL-files - * Microsoft WAVE-files - * SPARC AUDIO .AU-files - * Raw Data - * - * Copyright (c) by Jaroslav Kysela perex@perex.cz - * Based on vplay program by Michael Beck - * - * - * 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 - * - */ - -#define _GNU_SOURCE -#include <stdio.h> -#include <malloc.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <getopt.h> -#include <fcntl.h> -#include <ctype.h> -#include <errno.h> -#include <limits.h> -#include <time.h> -#include <locale.h> -#include <alsa/asoundlib.h> -#include <assert.h> -#include <termios.h> -#include <signal.h> -#include <sys/poll.h> -#include <sys/uio.h> -#include <sys/time.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <endian.h> -#include "aconfig.h" -#include "gettext.h" -#include "formats.h" -#include "version.h" - -#ifdef SND_CHMAP_API_VERSION -#define CONFIG_SUPPORT_CHMAP 1 -#endif - -#ifndef LLONG_MAX -#define LLONG_MAX 9223372036854775807LL -#endif - -#ifndef le16toh -#include <asm/byteorder.h> -#define le16toh(x) __le16_to_cpu(x) -#define be16toh(x) __be16_to_cpu(x) -#define le32toh(x) __le32_to_cpu(x) -#define be32toh(x) __be32_to_cpu(x) -#endif - -#define DEFAULT_FORMAT SND_PCM_FORMAT_U8 -#define DEFAULT_SPEED 8000 - -#define FORMAT_DEFAULT -1 -#define FORMAT_RAW 0 -#define FORMAT_VOC 1 -#define FORMAT_WAVE 2 -#define FORMAT_AU 3 - -/* global data */ - -static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size); -static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size); -static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); -static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); - -enum { - VUMETER_NONE, - VUMETER_MONO, - VUMETER_STEREO -}; - -static char *command; -static snd_pcm_t *handle; -static struct { - snd_pcm_format_t format; - unsigned int channels; - unsigned int rate; -} hwparams, rhwparams; -static int timelimit = 0; -static int sampleslimit = 0; -static int quiet_mode = 0; -static int file_type = FORMAT_DEFAULT; -static int open_mode = 0; -static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; -static int mmap_flag = 0; -static int interleaved = 1; -static int nonblock = 0; -static volatile sig_atomic_t in_aborting = 0; -static u_char *audiobuf = NULL; -static snd_pcm_uframes_t chunk_size = 0; -static unsigned period_time = 0; -static unsigned buffer_time = 0; -static snd_pcm_uframes_t period_frames = 0; -static snd_pcm_uframes_t buffer_frames = 0; -static int avail_min = -1; -static int start_delay = 0; -static int stop_delay = 0; -static int monotonic = 0; -static int interactive = 0; -static int can_pause = 0; -static int fatal_errors = 0; -static int verbose = 0; -static int vumeter = VUMETER_NONE; -static int buffer_pos = 0; -static size_t significant_bits_per_sample, bits_per_sample, bits_per_frame; -static size_t chunk_bytes; -static int test_position = 0; -static int test_coef = 8; -static int test_nowait = 0; -static snd_output_t *log; -static long long max_file_size = 0; -static int max_file_time = 0; -static int use_strftime = 0; -volatile static int recycle_capture_file = 0; -static long term_c_lflag = -1; -static int dump_hw_params = 0; - -static int fd = -1; -static off64_t pbrec_count = LLONG_MAX, fdcount; -static int vocmajor, vocminor; - -static char *pidfile_name = NULL; -FILE *pidf = NULL; -static int pidfile_written = 0; - -#ifdef CONFIG_SUPPORT_CHMAP -static snd_pcm_chmap_t *channel_map = NULL; /* chmap to override */ -static unsigned int *hw_map = NULL; /* chmap to follow */ -#endif - -/* needed prototypes */ - -static void done_stdin(void); - -static void playback(char *filename); -static void capture(char *filename); -static void playbackv(char **filenames, unsigned int count); -static void capturev(char **filenames, unsigned int count); - -static void begin_voc(int fd, size_t count); -static void end_voc(int fd); -static void begin_wave(int fd, size_t count); -static void end_wave(int fd); -static void begin_au(int fd, size_t count); -static void end_au(int fd); - -static void suspend(void); - -static const struct fmt_capture { - void (*start) (int fd, size_t count); - void (*end) (int fd); - char *what; - long long max_filesize; -} fmt_rec_table[] = { - { NULL, NULL, N_("raw data"), LLONG_MAX }, - { begin_voc, end_voc, N_("VOC"), 16000000LL }, - /* FIXME: can WAV handle exactly 2GB or less than it? */ - { begin_wave, end_wave, N_("WAVE"), 2147483648LL }, - { begin_au, end_au, N_("Sparc Audio"), LLONG_MAX } -}; - -#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) -#define error(...) do {\ - fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ - fprintf(stderr, __VA_ARGS__); \ - putc('\n', stderr); \ -} while (0) -#else -#define error(args...) do {\ - fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ - fprintf(stderr, ##args); \ - putc('\n', stderr); \ -} while (0) -#endif - -static void usage(char *command) -{ - snd_pcm_format_t k; - printf( -_("Usage: %s [OPTION]... [FILE]...\n" -"\n" -"-h, --help help\n" -" --version print current version\n" -"-l, --list-devices list all soundcards and digital audio devices\n" -"-L, --list-pcms list device names\n" -"-D, --device=NAME select PCM by name\n" -"-q, --quiet quiet mode\n" -"-t, --file-type TYPE file type (voc, wav, raw or au)\n" -"-c, --channels=# channels\n" -"-f, --format=FORMAT sample format (case insensitive)\n" -"-r, --rate=# sample rate\n" -"-d, --duration=# interrupt after # seconds\n" -"-s, --samples=# interrupt after # samples per channel\n" -"-M, --mmap mmap stream\n" -"-N, --nonblock nonblocking mode\n" -"-F, --period-time=# distance between interrupts is # microseconds\n" -"-B, --buffer-time=# buffer duration is # microseconds\n" -" --period-size=# distance between interrupts is # frames\n" -" --buffer-size=# buffer duration is # frames\n" -"-A, --avail-min=# min available space for wakeup is # microseconds\n" -"-R, --start-delay=# delay for automatic PCM start is # microseconds \n" -" (relative to buffer size if <= 0)\n" -"-T, --stop-delay=# delay for automatic PCM stop is # microseconds from xrun\n" -"-v, --verbose show PCM structure and setup (accumulative)\n" -"-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)\n" -"-I, --separate-channels one file for each channel\n" -"-i, --interactive allow interactive operation from stdin\n" -"-m, --chmap=ch1,ch2,.. Give the channel map to override or follow\n" -" --disable-resample disable automatic rate resample\n" -" --disable-channels disable automatic channel conversions\n" -" --disable-format disable automatic format conversions\n" -" --disable-softvol disable software volume control (softvol)\n" -" --test-position test ring buffer position\n" -" --test-coef=# test coefficient for ring buffer position (default 8)\n" -" expression for validation is: coef * (buffer_size / 2)\n" -" --test-nowait do not wait for ring buffer - eats whole CPU\n" -" --max-file-time=# start another output file when the old file has recorded\n" -" for this many seconds\n" -" --process-id-file write the process ID here\n" -" --use-strftime apply the strftime facility to the output file name\n" -" --dump-hw-params dump hw_params of the device\n" -" --fatal-errors treat all errors as fatal\n" - ) - , command); - printf(_("Recognized sample formats are:")); - for (k = 0; k <= SND_PCM_FORMAT_LAST; ++k) { - const char *s = snd_pcm_format_name(k); - if (s) - printf(" %s", s); - } - printf(_("\nSome of these may not be available on selected hardware\n")); - printf(_("The available format shortcuts are:\n")); - printf(_("-f cd (16 bit little endian, 44100, stereo)\n")); - printf(_("-f cdr (16 bit big endian, 44100, stereo)\n")); - printf(_("-f dat (16 bit little endian, 48000, stereo)\n")); -} - -static void device_list(void) -{ - snd_ctl_t *handle; - int card, err, dev, idx; - snd_ctl_card_info_t *info; - snd_pcm_info_t *pcminfo; - snd_ctl_card_info_alloca(&info); - snd_pcm_info_alloca(&pcminfo); - - card = -1; - if (snd_card_next(&card) < 0 || card < 0) { - error(_("no soundcards found...")); - return; - } - printf(_("**** List of %s Hardware Devices ****\n"), - snd_pcm_stream_name(stream)); - while (card >= 0) { - char name[32]; - sprintf(name, "hw:%d", card); - if ((err = snd_ctl_open(&handle, name, 0)) < 0) { - error("control open (%i): %s", card, snd_strerror(err)); - goto next_card; - } - if ((err = snd_ctl_card_info(handle, info)) < 0) { - error("control hardware info (%i): %s", card, snd_strerror(err)); - snd_ctl_close(handle); - goto next_card; - } - dev = -1; - while (1) { - unsigned int count; - if (snd_ctl_pcm_next_device(handle, &dev)<0) - error("snd_ctl_pcm_next_device"); - if (dev < 0) - break; - snd_pcm_info_set_device(pcminfo, dev); - snd_pcm_info_set_subdevice(pcminfo, 0); - snd_pcm_info_set_stream(pcminfo, stream); - if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { - if (err != -ENOENT) - error("control digital audio info (%i): %s", card, snd_strerror(err)); - continue; - } - printf(_("card %i: %s [%s], device %i: %s [%s]\n"), - card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_name(info), - dev, - snd_pcm_info_get_id(pcminfo), - snd_pcm_info_get_name(pcminfo)); - count = snd_pcm_info_get_subdevices_count(pcminfo); - printf( _(" Subdevices: %i/%i\n"), - snd_pcm_info_get_subdevices_avail(pcminfo), count); - for (idx = 0; idx < (int)count; idx++) { - snd_pcm_info_set_subdevice(pcminfo, idx); - if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { - error("control digital audio playback info (%i): %s", card, snd_strerror(err)); - } else { - printf(_(" Subdevice #%i: %s\n"), - idx, snd_pcm_info_get_subdevice_name(pcminfo)); - } - } - } - snd_ctl_close(handle); - next_card: - if (snd_card_next(&card) < 0) { - error("snd_card_next"); - break; - } - } -} - -static void pcm_list(void) -{ - void **hints, **n; - char *name, *descr, *descr1, *io; - const char *filter; - - if (snd_device_name_hint(-1, "pcm", &hints) < 0) - return; - n = hints; - filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output"; - while (*n != NULL) { - name = snd_device_name_get_hint(*n, "NAME"); - descr = snd_device_name_get_hint(*n, "DESC"); - io = snd_device_name_get_hint(*n, "IOID"); - if (io != NULL && strcmp(io, filter) != 0) - goto __end; - printf("%s\n", name); - if ((descr1 = descr) != NULL) { - printf(" "); - while (*descr1) { - if (*descr1 == '\n') - printf("\n "); - else - putchar(*descr1); - descr1++; - } - putchar('\n'); - } - __end: - if (name != NULL) - free(name); - if (descr != NULL) - free(descr); - if (io != NULL) - free(io); - n++; - } - snd_device_name_free_hint(hints); -} - -static void version(void) -{ - printf("%s: version " SND_UTIL_VERSION_STR " by Jaroslav Kysela perex@perex.cz\n", command); -} - -/* - * Subroutine to clean up before exit. - */ -static void prg_exit(int code) -{ - done_stdin(); - if (handle) - snd_pcm_close(handle); - if (pidfile_written) - remove (pidfile_name); - exit(code); -} - -static void signal_handler(int sig) -{ - if (in_aborting) - return; - - in_aborting = 1; - if (verbose==2) - putchar('\n'); - if (!quiet_mode) - fprintf(stderr, _("Aborted by signal %s...\n"), strsignal(sig)); - if (handle) - snd_pcm_abort(handle); - if (sig == SIGABRT) { - /* do not call snd_pcm_close() and abort immediately */ - handle = NULL; - prg_exit(EXIT_FAILURE); - } - signal(sig, SIG_DFL); -} - -/* call on SIGUSR1 signal. */ -static void signal_handler_recycle (int sig) -{ - /* flag the capture loop to start a new output file */ - recycle_capture_file = 1; -} - -enum { - OPT_VERSION = 1, - OPT_PERIOD_SIZE, - OPT_BUFFER_SIZE, - OPT_DISABLE_RESAMPLE, - OPT_DISABLE_CHANNELS, - OPT_DISABLE_FORMAT, - OPT_DISABLE_SOFTVOL, - OPT_TEST_POSITION, - OPT_TEST_COEF, - OPT_TEST_NOWAIT, - OPT_MAX_FILE_TIME, - OPT_PROCESS_ID_FILE, - OPT_USE_STRFTIME, - OPT_DUMP_HWPARAMS, - OPT_FATAL_ERRORS, -}; - -/* - * make sure we write all bytes or return an error - */ -static ssize_t xwrite(int fd, const void *buf, size_t count) -{ - ssize_t written; - size_t offset = 0; - - while (offset < count) { - written = write(fd, buf + offset, count - offset); - if (written <= 0) - return written; - - offset += written; - }; - - return offset; -} - -static long parse_long(const char *str, int *err) -{ - long val; - char *endptr; - - errno = 0; - val = strtol(str, &endptr, 0); - - if (errno != 0 || *endptr != '\0') - *err = -1; - else - *err = 0; - - return val; -} - -int main(int argc, char *argv[]) -{ - int duration_or_sample = 0; - int option_index; - static const char short_options[] = "hnlLD:qt:c:f:r:d:s:MNF:A:R:T:B:vV:IPCi" -#ifdef CONFIG_SUPPORT_CHMAP - "m:" -#endif - ; - static const struct option long_options[] = { - {"help", 0, 0, 'h'}, - {"version", 0, 0, OPT_VERSION}, - {"list-devnames", 0, 0, 'n'}, - {"list-devices", 0, 0, 'l'}, - {"list-pcms", 0, 0, 'L'}, - {"device", 1, 0, 'D'}, - {"quiet", 0, 0, 'q'}, - {"file-type", 1, 0, 't'}, - {"channels", 1, 0, 'c'}, - {"format", 1, 0, 'f'}, - {"rate", 1, 0, 'r'}, - {"duration", 1, 0 ,'d'}, - {"samples", 1, 0, 's'}, - {"mmap", 0, 0, 'M'}, - {"nonblock", 0, 0, 'N'}, - {"period-time", 1, 0, 'F'}, - {"period-size", 1, 0, OPT_PERIOD_SIZE}, - {"avail-min", 1, 0, 'A'}, - {"start-delay", 1, 0, 'R'}, - {"stop-delay", 1, 0, 'T'}, - {"buffer-time", 1, 0, 'B'}, - {"buffer-size", 1, 0, OPT_BUFFER_SIZE}, - {"verbose", 0, 0, 'v'}, - {"vumeter", 1, 0, 'V'}, - {"separate-channels", 0, 0, 'I'}, - {"playback", 0, 0, 'P'}, - {"capture", 0, 0, 'C'}, - {"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE}, - {"disable-channels", 0, 0, OPT_DISABLE_CHANNELS}, - {"disable-format", 0, 0, OPT_DISABLE_FORMAT}, - {"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL}, - {"test-position", 0, 0, OPT_TEST_POSITION}, - {"test-coef", 1, 0, OPT_TEST_COEF}, - {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, - {"max-file-time", 1, 0, OPT_MAX_FILE_TIME}, - {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, - {"use-strftime", 0, 0, OPT_USE_STRFTIME}, - {"interactive", 0, 0, 'i'}, - {"dump-hw-params", 0, 0, OPT_DUMP_HWPARAMS}, - {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, -#ifdef CONFIG_SUPPORT_CHMAP - {"chmap", 1, 0, 'm'}, -#endif - {0, 0, 0, 0} - }; - char *pcm_name = "default"; - int tmp, err, c; - int do_device_list = 0, do_pcm_list = 0; - snd_pcm_info_t *info; - FILE *direction; - -#ifdef ENABLE_NLS - setlocale(LC_ALL, ""); - textdomain(PACKAGE); -#endif - - snd_pcm_info_alloca(&info); - - err = snd_output_stdio_attach(&log, stderr, 0); - assert(err >= 0); - - command = argv[0]; - file_type = FORMAT_DEFAULT; - if (strstr(argv[0], "arecord")) { - stream = SND_PCM_STREAM_CAPTURE; - file_type = FORMAT_WAVE; - command = "arecord"; - start_delay = 1; - direction = stdout; - } else if (strstr(argv[0], "aplay")) { - stream = SND_PCM_STREAM_PLAYBACK; - command = "aplay"; - direction = stdin; - } else { - error(_("command should be named either arecord or aplay")); - return 1; - } - - if (isatty(fileno(direction)) && (argc == 1)) { - usage(command); - return 1; - } - - chunk_size = -1; - rhwparams.format = DEFAULT_FORMAT; - rhwparams.rate = DEFAULT_SPEED; - rhwparams.channels = 1; - - while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { - switch (c) { - case 'h': - usage(command); - return 0; - case OPT_VERSION: - version(); - return 0; - case 'l': - do_device_list = 1; - break; - case 'L': - do_pcm_list = 1; - break; - case 'D': - pcm_name = optarg; - break; - case 'q': - quiet_mode = 1; - break; - case 't': - if (strcasecmp(optarg, "raw") == 0) - file_type = FORMAT_RAW; - else if (strcasecmp(optarg, "voc") == 0) - file_type = FORMAT_VOC; - else if (strcasecmp(optarg, "wav") == 0) - file_type = FORMAT_WAVE; - else if (strcasecmp(optarg, "au") == 0 || strcasecmp(optarg, "sparc") == 0) - file_type = FORMAT_AU; - else { - error(_("unrecognized file format %s"), optarg); - return 1; - } - break; - case 'c': - rhwparams.channels = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid channels argument '%s'"), optarg); - return 1; - } - if (rhwparams.channels < 1 || rhwparams.channels > 256) { - error(_("value %i for channels is invalid"), rhwparams.channels); - return 1; - } - break; - case 'f': - if (strcasecmp(optarg, "cd") == 0 || strcasecmp(optarg, "cdr") == 0) { - if (strcasecmp(optarg, "cdr") == 0) - rhwparams.format = SND_PCM_FORMAT_S16_BE; - else - rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE; - rhwparams.rate = 44100; - rhwparams.channels = 2; - } else if (strcasecmp(optarg, "dat") == 0) { - rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE; - rhwparams.rate = 48000; - rhwparams.channels = 2; - } else { - rhwparams.format = snd_pcm_format_value(optarg); - if (rhwparams.format == SND_PCM_FORMAT_UNKNOWN) { - error(_("wrong extended format '%s'"), optarg); - prg_exit(EXIT_FAILURE); - } - } - break; - case 'r': - tmp = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid rate argument '%s'"), optarg); - return 1; - } - if (tmp < 300) - tmp *= 1000; - rhwparams.rate = tmp; - if (tmp < 2000 || tmp > 192000) { - error(_("bad speed value %i"), tmp); - return 1; - } - break; - case 'd': - if (duration_or_sample) { - error(_("duration and samples arguments cannot be used together")); - return 1; - } - timelimit = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid duration argument '%s'"), optarg); - return 1; - } - duration_or_sample = 1; - break; - case 's': - if (duration_or_sample) { - error(_("samples and duration arguments cannot be used together")); - return 1; - } - sampleslimit = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid samples argument '%s'"), optarg); - return 1; - } - duration_or_sample = 1; - break; - case 'N': - nonblock = 1; - open_mode |= SND_PCM_NONBLOCK; - break; - case 'F': - period_time = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid period time argument '%s'"), optarg); - return 1; - } - break; - case 'B': - buffer_time = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid buffer time argument '%s'"), optarg); - return 1; - } - break; - case OPT_PERIOD_SIZE: - period_frames = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid period size argument '%s'"), optarg); - return 1; - } - break; - case OPT_BUFFER_SIZE: - buffer_frames = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid buffer size argument '%s'"), optarg); - return 1; - } - break; - case 'A': - avail_min = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid min available space argument '%s'"), optarg); - return 1; - } - break; - case 'R': - start_delay = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid start delay argument '%s'"), optarg); - return 1; - } - break; - case 'T': - stop_delay = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid stop delay argument '%s'"), optarg); - return 1; - } - break; - case 'v': - verbose++; - if (verbose > 1 && !vumeter) - vumeter = VUMETER_MONO; - break; - case 'V': - if (*optarg == 's') - vumeter = VUMETER_STEREO; - else if (*optarg == 'm') - vumeter = VUMETER_MONO; - else - vumeter = VUMETER_NONE; - break; - case 'M': - mmap_flag = 1; - break; - case 'I': - interleaved = 0; - break; - case 'P': - stream = SND_PCM_STREAM_PLAYBACK; - command = "aplay"; - break; - case 'C': - stream = SND_PCM_STREAM_CAPTURE; - command = "arecord"; - start_delay = 1; - if (file_type == FORMAT_DEFAULT) - file_type = FORMAT_WAVE; - break; - case 'i': - interactive = 1; - break; - case OPT_DISABLE_RESAMPLE: - open_mode |= SND_PCM_NO_AUTO_RESAMPLE; - break; - case OPT_DISABLE_CHANNELS: - open_mode |= SND_PCM_NO_AUTO_CHANNELS; - break; - case OPT_DISABLE_FORMAT: - open_mode |= SND_PCM_NO_AUTO_FORMAT; - break; - case OPT_DISABLE_SOFTVOL: - open_mode |= SND_PCM_NO_SOFTVOL; - break; - case OPT_TEST_POSITION: - test_position = 1; - break; - case OPT_TEST_COEF: - test_coef = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid test coef argument '%s'"), optarg); - return 1; - } - if (test_coef < 1) - test_coef = 1; - break; - case OPT_TEST_NOWAIT: - test_nowait = 1; - break; - case OPT_MAX_FILE_TIME: - max_file_time = parse_long(optarg, &err); - if (err < 0) { - error(_("invalid max file time argument '%s'"), optarg); - return 1; - } - break; - case OPT_PROCESS_ID_FILE: - pidfile_name = optarg; - break; - case OPT_USE_STRFTIME: - use_strftime = 1; - break; - case OPT_DUMP_HWPARAMS: - dump_hw_params = 1; - break; - case OPT_FATAL_ERRORS: - fatal_errors = 1; - break; -#ifdef CONFIG_SUPPORT_CHMAP - case 'm': - channel_map = snd_pcm_chmap_parse_string(optarg); - if (!channel_map) { - fprintf(stderr, _("Unable to parse channel map string: %s\n"), optarg); - return 1; - } - break; -#endif - default: - fprintf(stderr, _("Try `%s --help' for more information.\n"), command); - return 1; - } - } - - if (do_device_list) { - if (do_pcm_list) pcm_list(); - device_list(); - goto __end; - } else if (do_pcm_list) { - pcm_list(); - goto __end; - } - - err = snd_pcm_open(&handle, pcm_name, stream, open_mode); - if (err < 0) { - error(_("audio open error: %s"), snd_strerror(err)); - return 1; - } - - if ((err = snd_pcm_info(handle, info)) < 0) { - error(_("info error: %s"), snd_strerror(err)); - return 1; - } - - if (nonblock) { - err = snd_pcm_nonblock(handle, 1); - if (err < 0) { - error(_("nonblock setting error: %s"), snd_strerror(err)); - return 1; - } - } - - chunk_size = 1024; - hwparams = rhwparams; - - audiobuf = (u_char *)malloc(1024); - if (audiobuf == NULL) { - error(_("not enough memory")); - return 1; - } - - if (mmap_flag) { - writei_func = snd_pcm_mmap_writei; - readi_func = snd_pcm_mmap_readi; - writen_func = snd_pcm_mmap_writen; - readn_func = snd_pcm_mmap_readn; - } else { - writei_func = snd_pcm_writei; - readi_func = snd_pcm_readi; - writen_func = snd_pcm_writen; - readn_func = snd_pcm_readn; - } - - if (pidfile_name) { - errno = 0; - pidf = fopen (pidfile_name, "w"); - if (pidf) { - (void)fprintf (pidf, "%d\n", getpid()); - fclose(pidf); - pidfile_written = 1; - } else { - error(_("Cannot create process ID file %s: %s"), - pidfile_name, strerror (errno)); - return 1; - } - } - - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - signal(SIGABRT, signal_handler); - signal(SIGUSR1, signal_handler_recycle); - if (interleaved) { - if (optind > argc - 1) { - if (stream == SND_PCM_STREAM_PLAYBACK) - playback(NULL); - else - capture(NULL); - } else { - while (optind <= argc - 1) { - if (stream == SND_PCM_STREAM_PLAYBACK) - playback(argv[optind++]); - else - capture(argv[optind++]); - } - } - } else { - if (stream == SND_PCM_STREAM_PLAYBACK) - playbackv(&argv[optind], argc - optind); - else - capturev(&argv[optind], argc - optind); - } - if (verbose==2) - putchar('\n'); - snd_pcm_close(handle); - handle = NULL; - free(audiobuf); - __end: - snd_output_close(log); - snd_config_update_free_global(); - prg_exit(EXIT_SUCCESS); - /* avoid warning */ - return EXIT_SUCCESS; -} - -/* - * Safe read (for pipes) - */ - -static ssize_t safe_read(int fd, void *buf, size_t count) -{ - ssize_t result = 0, res; - - while (count > 0 && !in_aborting) { - if ((res = read(fd, buf, count)) == 0) - break; - if (res < 0) - return result > 0 ? result : res; - count -= res; - result += res; - buf = (char *)buf + res; - } - return result; -} - -/* - * Test, if it is a .VOC file and return >=0 if ok (this is the length of rest) - * < 0 if not - */ -static int test_vocfile(void *buffer) -{ - VocHeader *vp = buffer; - - if (!memcmp(vp->magic, VOC_MAGIC_STRING, 20)) { - vocminor = LE_SHORT(vp->version) & 0xFF; - vocmajor = LE_SHORT(vp->version) / 256; - if (LE_SHORT(vp->version) != (0x1233 - LE_SHORT(vp->coded_ver))) - return -2; /* coded version mismatch */ - return LE_SHORT(vp->headerlen) - sizeof(VocHeader); /* 0 mostly */ - } - return -1; /* magic string fail */ -} - -/* - * helper for test_wavefile - */ - -static size_t test_wavefile_read(int fd, u_char *buffer, size_t *size, size_t reqsize, int line) -{ - if (*size >= reqsize) - return *size; - if ((size_t)safe_read(fd, buffer + *size, reqsize - *size) != reqsize - *size) { - error(_("read error (called from line %i)"), line); - prg_exit(EXIT_FAILURE); - } - return *size = reqsize; -} - -#define check_wavefile_space(buffer, len, blimit) \ - if (len > blimit) { \ - blimit = len; \ - if ((buffer = realloc(buffer, blimit)) == NULL) { \ - error(_("not enough memory")); \ - prg_exit(EXIT_FAILURE); \ - } \ - } - -/* - * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.) - * == 0 if not - * Value returned is bytes to be discarded. - */ -static ssize_t test_wavefile(int fd, u_char *_buffer, size_t size) -{ - WaveHeader *h = (WaveHeader *)_buffer; - u_char *buffer = NULL; - size_t blimit = 0; - WaveFmtBody *f; - WaveChunkHeader *c; - u_int type, len; - unsigned short format, channels; - int big_endian, native_format; - - if (size < sizeof(WaveHeader)) - return -1; - if (h->magic == WAV_RIFF) - big_endian = 0; - else if (h->magic == WAV_RIFX) - big_endian = 1; - else - return -1; - if (h->type != WAV_WAVE) - return -1; - - if (size > sizeof(WaveHeader)) { - check_wavefile_space(buffer, size - sizeof(WaveHeader), blimit); - memcpy(buffer, _buffer + sizeof(WaveHeader), size - sizeof(WaveHeader)); - } - size -= sizeof(WaveHeader); - while (1) { - check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); - test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); - c = (WaveChunkHeader*)buffer; - type = c->type; - len = TO_CPU_INT(c->length, big_endian); - len += len % 2; - if (size > sizeof(WaveChunkHeader)) - memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); - size -= sizeof(WaveChunkHeader); - if (type == WAV_FMT) - break; - check_wavefile_space(buffer, len, blimit); - test_wavefile_read(fd, buffer, &size, len, __LINE__); - if (size > len) - memmove(buffer, buffer + len, size - len); - size -= len; - } - - if (len < sizeof(WaveFmtBody)) { - error(_("unknown length of 'fmt ' chunk (read %u, should be %u at least)"), - len, (u_int)sizeof(WaveFmtBody)); - prg_exit(EXIT_FAILURE); - } - check_wavefile_space(buffer, len, blimit); - test_wavefile_read(fd, buffer, &size, len, __LINE__); - f = (WaveFmtBody*) buffer; - format = TO_CPU_SHORT(f->format, big_endian); - if (format == WAV_FMT_EXTENSIBLE) { - WaveFmtExtensibleBody *fe = (WaveFmtExtensibleBody*)buffer; - if (len < sizeof(WaveFmtExtensibleBody)) { - error(_("unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"), - len, (u_int)sizeof(WaveFmtExtensibleBody)); - prg_exit(EXIT_FAILURE); - } - if (memcmp(fe->guid_tag, WAV_GUID_TAG, 14) != 0) { - error(_("wrong format tag in extensible 'fmt ' chunk")); - prg_exit(EXIT_FAILURE); - } - format = TO_CPU_SHORT(fe->guid_format, big_endian); - } - if (format != WAV_FMT_PCM && - format != WAV_FMT_IEEE_FLOAT) { - error(_("can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"), format); - prg_exit(EXIT_FAILURE); - } - channels = TO_CPU_SHORT(f->channels, big_endian); - if (channels < 1) { - error(_("can't play WAVE-files with %d tracks"), channels); - prg_exit(EXIT_FAILURE); - } - hwparams.channels = channels; - switch (TO_CPU_SHORT(f->bit_p_spl, big_endian)) { - case 8: - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != SND_PCM_FORMAT_U8) - fprintf(stderr, _("Warning: format is changed to U8\n")); - hwparams.format = SND_PCM_FORMAT_U8; - break; - case 16: - if (big_endian) - native_format = SND_PCM_FORMAT_S16_BE; - else - native_format = SND_PCM_FORMAT_S16_LE; - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != native_format) - fprintf(stderr, _("Warning: format is changed to %s\n"), - snd_pcm_format_name(native_format)); - hwparams.format = native_format; - break; - case 24: - switch (TO_CPU_SHORT(f->byte_p_spl, big_endian) / hwparams.channels) { - case 3: - if (big_endian) - native_format = SND_PCM_FORMAT_S24_3BE; - else - native_format = SND_PCM_FORMAT_S24_3LE; - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != native_format) - fprintf(stderr, _("Warning: format is changed to %s\n"), - snd_pcm_format_name(native_format)); - hwparams.format = native_format; - break; - case 4: - if (big_endian) - native_format = SND_PCM_FORMAT_S24_BE; - else - native_format = SND_PCM_FORMAT_S24_LE; - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != native_format) - fprintf(stderr, _("Warning: format is changed to %s\n"), - snd_pcm_format_name(native_format)); - hwparams.format = native_format; - break; - default: - error(_(" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"), - TO_CPU_SHORT(f->bit_p_spl, big_endian), - TO_CPU_SHORT(f->byte_p_spl, big_endian), - hwparams.channels); - prg_exit(EXIT_FAILURE); - } - break; - case 32: - if (format == WAV_FMT_PCM) { - if (big_endian) - native_format = SND_PCM_FORMAT_S32_BE; - else - native_format = SND_PCM_FORMAT_S32_LE; - hwparams.format = native_format; - } else if (format == WAV_FMT_IEEE_FLOAT) { - if (big_endian) - native_format = SND_PCM_FORMAT_FLOAT_BE; - else - native_format = SND_PCM_FORMAT_FLOAT_LE; - hwparams.format = native_format; - } - break; - default: - error(_(" can't play WAVE-files with sample %d bits wide"), - TO_CPU_SHORT(f->bit_p_spl, big_endian)); - prg_exit(EXIT_FAILURE); - } - hwparams.rate = TO_CPU_INT(f->sample_fq, big_endian); - - if (size > len) - memmove(buffer, buffer + len, size - len); - size -= len; - - while (1) { - u_int type, len; - - check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); - test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); - c = (WaveChunkHeader*)buffer; - type = c->type; - len = TO_CPU_INT(c->length, big_endian); - if (size > sizeof(WaveChunkHeader)) - memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); - size -= sizeof(WaveChunkHeader); - if (type == WAV_DATA) { - if (len < pbrec_count && len < 0x7ffffffe) - pbrec_count = len; - if (size > 0) - memcpy(_buffer, buffer, size); - free(buffer); - return size; - } - len += len % 2; - check_wavefile_space(buffer, len, blimit); - test_wavefile_read(fd, buffer, &size, len, __LINE__); - if (size > len) - memmove(buffer, buffer + len, size - len); - size -= len; - } - - /* shouldn't be reached */ - return -1; -} - -/* - - */ - -static int test_au(int fd, void *buffer) -{ - AuHeader *ap = buffer; - - if (ap->magic != AU_MAGIC) - return -1; - if (BE_INT(ap->hdr_size) > 128 || BE_INT(ap->hdr_size) < 24) - return -1; - pbrec_count = BE_INT(ap->data_size); - switch (BE_INT(ap->encoding)) { - case AU_FMT_ULAW: - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != SND_PCM_FORMAT_MU_LAW) - fprintf(stderr, _("Warning: format is changed to MU_LAW\n")); - hwparams.format = SND_PCM_FORMAT_MU_LAW; - break; - case AU_FMT_LIN8: - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != SND_PCM_FORMAT_U8) - fprintf(stderr, _("Warning: format is changed to U8\n")); - hwparams.format = SND_PCM_FORMAT_U8; - break; - case AU_FMT_LIN16: - if (hwparams.format != DEFAULT_FORMAT && - hwparams.format != SND_PCM_FORMAT_S16_BE) - fprintf(stderr, _("Warning: format is changed to S16_BE\n")); - hwparams.format = SND_PCM_FORMAT_S16_BE; - break; - default: - return -1; - } - hwparams.rate = BE_INT(ap->sample_rate); - if (hwparams.rate < 2000 || hwparams.rate > 256000) - return -1; - hwparams.channels = BE_INT(ap->channels); - if (hwparams.channels < 1 || hwparams.channels > 256) - return -1; - if ((size_t)safe_read(fd, buffer + sizeof(AuHeader), BE_INT(ap->hdr_size) - sizeof(AuHeader)) != BE_INT(ap->hdr_size) - sizeof(AuHeader)) { - error(_("read error")); - prg_exit(EXIT_FAILURE); - } - return 0; -} - -static void show_available_sample_formats(snd_pcm_hw_params_t* params) -{ - snd_pcm_format_t format; - - fprintf(stderr, "Available formats:\n"); - for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { - if (snd_pcm_hw_params_test_format(handle, params, format) == 0) - fprintf(stderr, "- %s\n", snd_pcm_format_name(format)); - } -} - -#ifdef CONFIG_SUPPORT_CHMAP -static int setup_chmap(void) -{ - snd_pcm_chmap_t *chmap = channel_map; - char mapped[hwparams.channels]; - snd_pcm_chmap_t *hw_chmap; - unsigned int ch, i; - int err; - - if (!chmap) - return 0; - - if (chmap->channels != hwparams.channels) { - error(_("Channel numbers don't match between hw_params and channel map")); - return -1; - } - err = snd_pcm_set_chmap(handle, chmap); - if (!err) - return 0; - - hw_chmap = snd_pcm_get_chmap(handle); - if (!hw_chmap) { - fprintf(stderr, _("Warning: unable to get channel map\n")); - return 0; - } - - if (hw_chmap->channels == chmap->channels && - !memcmp(hw_chmap, chmap, 4 * (chmap->channels + 1))) { - /* maps are identical, so no need to convert */ - free(hw_chmap); - return 0; - } - - hw_map = calloc(hwparams.channels, sizeof(int)); - if (!hw_map) { - error(_("not enough memory")); - return -1; - } - - memset(mapped, 0, sizeof(mapped)); - for (ch = 0; ch < hw_chmap->channels; ch++) { - if (chmap->pos[ch] == hw_chmap->pos[ch]) { - mapped[ch] = 1; - hw_map[ch] = ch; - continue; - } - for (i = 0; i < hw_chmap->channels; i++) { - if (!mapped[i] && chmap->pos[ch] == hw_chmap->pos[i]) { - mapped[i] = 1; - hw_map[ch] = i; - break; - } - } - if (i >= hw_chmap->channels) { - char buf[256]; - error(_("Channel %d doesn't match with hw_parmas"), ch); - snd_pcm_chmap_print(hw_chmap, sizeof(buf), buf); - fprintf(stderr, "hardware chmap = %s\n", buf); - return -1; - } - } - free(hw_chmap); - return 0; -} -#else -#define setup_chmap() 0 -#endif - -static void set_params(void) -{ - snd_pcm_hw_params_t *params; - snd_pcm_sw_params_t *swparams; - snd_pcm_uframes_t buffer_size; - int err; - size_t n; - unsigned int rate; - snd_pcm_uframes_t start_threshold, stop_threshold; - snd_pcm_hw_params_alloca(¶ms); - snd_pcm_sw_params_alloca(&swparams); - err = snd_pcm_hw_params_any(handle, params); - if (err < 0) { - error(_("Broken configuration for this PCM: no configurations available")); - prg_exit(EXIT_FAILURE); - } - if (dump_hw_params) { - fprintf(stderr, _("HW Params of device "%s":\n"), - snd_pcm_name(handle)); - fprintf(stderr, "--------------------\n"); - snd_pcm_hw_params_dump(params, log); - fprintf(stderr, "--------------------\n"); - } - if (mmap_flag) { - snd_pcm_access_mask_t *mask = alloca(snd_pcm_access_mask_sizeof()); - snd_pcm_access_mask_none(mask); - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); - snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX); - err = snd_pcm_hw_params_set_access_mask(handle, params, mask); - } else if (interleaved) - err = snd_pcm_hw_params_set_access(handle, params, - SND_PCM_ACCESS_RW_INTERLEAVED); - else - err = snd_pcm_hw_params_set_access(handle, params, - SND_PCM_ACCESS_RW_NONINTERLEAVED); - if (err < 0) { - error(_("Access type not available")); - prg_exit(EXIT_FAILURE); - } - err = snd_pcm_hw_params_set_format(handle, params, hwparams.format); - if (err < 0) { - error(_("Sample format non available")); - show_available_sample_formats(params); - prg_exit(EXIT_FAILURE); - } - err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels); - if (err < 0) { - error(_("Channels count non available")); - prg_exit(EXIT_FAILURE); - } - -#if 0 - err = snd_pcm_hw_params_set_periods_min(handle, params, 2); - assert(err >= 0); -#endif - rate = hwparams.rate; - err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0); - assert(err >= 0); - if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate) { - if (!quiet_mode) { - char plugex[64]; - const char *pcmname = snd_pcm_name(handle); - fprintf(stderr, _("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate); - if (! pcmname || strchr(snd_pcm_name(handle), ':')) - *plugex = 0; - else - snprintf(plugex, sizeof(plugex), "(-Dplug:%s)", - snd_pcm_name(handle)); - fprintf(stderr, _(" please, try the plug plugin %s\n"), - plugex); - } - } - rate = hwparams.rate; - if (buffer_time == 0 && buffer_frames == 0) { - err = snd_pcm_hw_params_get_buffer_time_max(params, - &buffer_time, 0); - assert(err >= 0); - if (buffer_time > 500000) - buffer_time = 500000; - } - if (period_time == 0 && period_frames == 0) { - if (buffer_time > 0) - period_time = buffer_time / 4; - else - period_frames = buffer_frames / 4; - } - if (period_time > 0) - err = snd_pcm_hw_params_set_period_time_near(handle, params, - &period_time, 0); - else - err = snd_pcm_hw_params_set_period_size_near(handle, params, - &period_frames, 0); - assert(err >= 0); - if (buffer_time > 0) { - err = snd_pcm_hw_params_set_buffer_time_near(handle, params, - &buffer_time, 0); - } else { - err = snd_pcm_hw_params_set_buffer_size_near(handle, params, - &buffer_frames); - } - assert(err >= 0); - monotonic = snd_pcm_hw_params_is_monotonic(params); - can_pause = snd_pcm_hw_params_can_pause(params); - err = snd_pcm_hw_params(handle, params); - if (err < 0) { - error(_("Unable to install hw params:")); - snd_pcm_hw_params_dump(params, log); - prg_exit(EXIT_FAILURE); - } - snd_pcm_hw_params_get_period_size(params, &chunk_size, 0); - snd_pcm_hw_params_get_buffer_size(params, &buffer_size); - if (chunk_size == buffer_size) { - error(_("Can't use period equal to buffer size (%lu == %lu)"), - chunk_size, buffer_size); - prg_exit(EXIT_FAILURE); - } - snd_pcm_sw_params_current(handle, swparams); - if (avail_min < 0) - n = chunk_size; - else - n = (double) rate * avail_min / 1000000; - err = snd_pcm_sw_params_set_avail_min(handle, swparams, n); - - /* round up to closest transfer boundary */ - n = buffer_size; - if (start_delay <= 0) { - start_threshold = n + (double) rate * start_delay / 1000000; - } else - start_threshold = (double) rate * start_delay / 1000000; - if (start_threshold < 1) - start_threshold = 1; - if (start_threshold > n) - start_threshold = n; - err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold); - assert(err >= 0); - if (stop_delay <= 0) - stop_threshold = buffer_size + (double) rate * stop_delay / 1000000; - else - stop_threshold = (double) rate * stop_delay / 1000000; - err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold); - assert(err >= 0); - - if (snd_pcm_sw_params(handle, swparams) < 0) { - error(_("unable to install sw params:")); - snd_pcm_sw_params_dump(swparams, log); - prg_exit(EXIT_FAILURE); - } - - if (setup_chmap()) - prg_exit(EXIT_FAILURE); - - if (verbose) - snd_pcm_dump(handle, log); - - bits_per_sample = snd_pcm_format_physical_width(hwparams.format); - significant_bits_per_sample = snd_pcm_format_width(hwparams.format); - bits_per_frame = bits_per_sample * hwparams.channels; - chunk_bytes = chunk_size * bits_per_frame / 8; - audiobuf = realloc(audiobuf, chunk_bytes); - if (audiobuf == NULL) { - error(_("not enough memory")); - prg_exit(EXIT_FAILURE); - } - // fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size); - - /* stereo VU-meter isn't always available... */ - if (vumeter == VUMETER_STEREO) { - if (hwparams.channels != 2 || !interleaved || verbose > 2) - vumeter = VUMETER_MONO; - } - - /* show mmap buffer arragment */ - if (mmap_flag && verbose) { - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, size = chunk_size; - int i; - err = snd_pcm_mmap_begin(handle, &areas, &offset, &size); - if (err < 0) { - error(_("snd_pcm_mmap_begin problem: %s"), snd_strerror(err)); - prg_exit(EXIT_FAILURE); - } - for (i = 0; i < hwparams.channels; i++) - fprintf(stderr, "mmap_area[%i] = %p,%u,%u (%u)\n", i, areas[i].addr, areas[i].first, areas[i].step, snd_pcm_format_physical_width(hwparams.format)); - /* not required, but for sure */ - snd_pcm_mmap_commit(handle, offset, 0); - } - - buffer_frames = buffer_size; /* for position test */ -} - -static void init_stdin(void) -{ - struct termios term; - long flags; - - if (!interactive) - return; - if (!isatty(fileno(stdin))) { - interactive = 0; - return; - } - tcgetattr(fileno(stdin), &term); - term_c_lflag = term.c_lflag; - if (fd == fileno(stdin)) - return; - flags = fcntl(fileno(stdin), F_GETFL); - if (flags < 0 || fcntl(fileno(stdin), F_SETFL, flags|O_NONBLOCK) < 0) - fprintf(stderr, _("stdin O_NONBLOCK flag setup failed\n")); - term.c_lflag &= ~ICANON; - tcsetattr(fileno(stdin), TCSANOW, &term); -} - -static void done_stdin(void) -{ - struct termios term; - - if (!interactive) - return; - if (fd == fileno(stdin) || term_c_lflag == -1) - return; - tcgetattr(fileno(stdin), &term); - term.c_lflag = term_c_lflag; - tcsetattr(fileno(stdin), TCSANOW, &term); -} - -static void do_pause(void) -{ - int err; - unsigned char b; - - if (!can_pause) { - fprintf(stderr, _("\rPAUSE command ignored (no hw support)\n")); - return; - } - if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) - suspend(); - - err = snd_pcm_pause(handle, 1); - if (err < 0) { - error(_("pause push error: %s"), snd_strerror(err)); - return; - } - while (1) { - while (read(fileno(stdin), &b, 1) != 1); - if (b == ' ' || b == '\r') { - while (read(fileno(stdin), &b, 1) == 1); - if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) - suspend(); - err = snd_pcm_pause(handle, 0); - if (err < 0) - error(_("pause release error: %s"), snd_strerror(err)); - return; - } - } -} - -static void check_stdin(void) -{ - unsigned char b; - - if (!interactive) - return; - if (fd != fileno(stdin)) { - while (read(fileno(stdin), &b, 1) == 1) { - if (b == ' ' || b == '\r') { - while (read(fileno(stdin), &b, 1) == 1); - fprintf(stderr, _("\r=== PAUSE === ")); - fflush(stderr); - do_pause(); - fprintf(stderr, " \r"); - fflush(stderr); - } - } - } -} - -#ifndef timersub -#define timersub(a, b, result) \ -do { \ - (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ - (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ - if ((result)->tv_usec < 0) { \ - --(result)->tv_sec; \ - (result)->tv_usec += 1000000; \ - } \ -} while (0) -#endif - -#ifndef timermsub -#define timermsub(a, b, result) \ -do { \ - (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ - (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ - if ((result)->tv_nsec < 0) { \ - --(result)->tv_sec; \ - (result)->tv_nsec += 1000000000L; \ - } \ -} while (0) -#endif - -/* I/O error handler */ -static void xrun(void) -{ - snd_pcm_status_t *status; - int res; - - snd_pcm_status_alloca(&status); - if ((res = snd_pcm_status(handle, status))<0) { - error(_("status error: %s"), snd_strerror(res)); - prg_exit(EXIT_FAILURE); - } - if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { - if (fatal_errors) { - error(_("fatal %s: %s"), - stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"), - snd_strerror(res)); - prg_exit(EXIT_FAILURE); - } - if (monotonic) { -#ifdef HAVE_CLOCK_GETTIME - struct timespec now, diff, tstamp; - clock_gettime(CLOCK_MONOTONIC, &now); - snd_pcm_status_get_trigger_htstamp(status, &tstamp); - timermsub(&now, &tstamp, &diff); - fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"), - stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"), - diff.tv_sec * 1000 + diff.tv_nsec / 1000000.0); -#else - fprintf(stderr, "%s !!!\n", _("underrun")); -#endif - } else { - struct timeval now, diff, tstamp; - gettimeofday(&now, 0); - snd_pcm_status_get_trigger_tstamp(status, &tstamp); - timersub(&now, &tstamp, &diff); - fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"), - stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"), - diff.tv_sec * 1000 + diff.tv_usec / 1000.0); - } - if (verbose) { - fprintf(stderr, _("Status:\n")); - snd_pcm_status_dump(status, log); - } - if ((res = snd_pcm_prepare(handle))<0) { - error(_("xrun: prepare error: %s"), snd_strerror(res)); - prg_exit(EXIT_FAILURE); - } - return; /* ok, data should be accepted again */ - } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) { - if (verbose) { - fprintf(stderr, _("Status(DRAINING):\n")); - snd_pcm_status_dump(status, log); - } - if (stream == SND_PCM_STREAM_CAPTURE) { - fprintf(stderr, _("capture stream format change? attempting recover...\n")); - if ((res = snd_pcm_prepare(handle))<0) { - error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res)); - prg_exit(EXIT_FAILURE); - } - return; - } - } - if (verbose) { - fprintf(stderr, _("Status(R/W):\n")); - snd_pcm_status_dump(status, log); - } - error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status))); - prg_exit(EXIT_FAILURE); -} - -/* I/O suspend handler */ -static void suspend(void) -{ - int res; - - if (!quiet_mode) - fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr); - while ((res = snd_pcm_resume(handle)) == -EAGAIN) - sleep(1); /* wait until suspend flag is released */ - if (res < 0) { - if (!quiet_mode) - fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr); - if ((res = snd_pcm_prepare(handle)) < 0) { - error(_("suspend: prepare error: %s"), snd_strerror(res)); - prg_exit(EXIT_FAILURE); - } - } - if (!quiet_mode) - fprintf(stderr, _("Done.\n")); -} - -static void print_vu_meter_mono(int perc, int maxperc) -{ - const int bar_length = 50; - char line[80]; - int val; - - for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++) - line[val] = '#'; - for (; val <= maxperc * bar_length / 100 && val < bar_length; val++) - line[val] = ' '; - line[val] = '+'; - for (++val; val <= bar_length; val++) - line[val] = ' '; - if (maxperc > 99) - sprintf(line + val, "| MAX"); - else - sprintf(line + val, "| %02i%%", maxperc); - fputs(line, stderr); - if (perc > 100) - fprintf(stderr, _(" !clip ")); -} - -static void print_vu_meter_stereo(int *perc, int *maxperc) -{ - const int bar_length = 35; - char line[80]; - int c; - - memset(line, ' ', sizeof(line) - 1); - line[bar_length + 3] = '|'; - - for (c = 0; c < 2; c++) { - int p = perc[c] * bar_length / 100; - char tmp[4]; - if (p > bar_length) - p = bar_length; - if (c) - memset(line + bar_length + 6 + 1, '#', p); - else - memset(line + bar_length - p - 1, '#', p); - p = maxperc[c] * bar_length / 100; - if (p > bar_length) - p = bar_length; - if (c) - line[bar_length + 6 + 1 + p] = '+'; - else - line[bar_length - p - 1] = '+'; - if (maxperc[c] > 99) - sprintf(tmp, "MAX"); - else - sprintf(tmp, "%02d%%", maxperc[c]); - if (c) - memcpy(line + bar_length + 3 + 1, tmp, 3); - else - memcpy(line + bar_length, tmp, 3); - } - line[bar_length * 2 + 6 + 2] = 0; - fputs(line, stderr); -} - -static void print_vu_meter(signed int *perc, signed int *maxperc) -{ - if (vumeter == VUMETER_STEREO) - print_vu_meter_stereo(perc, maxperc); - else - print_vu_meter_mono(*perc, *maxperc); -} - -/* peak handler */ -static void compute_max_peak(u_char *data, size_t count) -{ - signed int val, max, perc[2], max_peak[2]; - static int run = 0; - size_t ocount = count; - int format_little_endian = snd_pcm_format_little_endian(hwparams.format); - int ichans, c; - - if (vumeter == VUMETER_STEREO) - ichans = 2; - else - ichans = 1; - - memset(max_peak, 0, sizeof(max_peak)); - switch (bits_per_sample) { - case 8: { - signed char *valp = (signed char *)data; - signed char mask = snd_pcm_format_silence(hwparams.format); - c = 0; - while (count-- > 0) { - val = *valp++ ^ mask; - val = abs(val); - if (max_peak[c] < val) - max_peak[c] = val; - if (vumeter == VUMETER_STEREO) - c = !c; - } - break; - } - case 16: { - signed short *valp = (signed short *)data; - signed short mask = snd_pcm_format_silence_16(hwparams.format); - signed short sval; - - count /= 2; - c = 0; - while (count-- > 0) { - if (format_little_endian) - sval = le16toh(*valp); - else - sval = be16toh(*valp); - sval = abs(sval) ^ mask; - if (max_peak[c] < sval) - max_peak[c] = sval; - valp++; - if (vumeter == VUMETER_STEREO) - c = !c; - } - break; - } - case 24: { - unsigned char *valp = data; - signed int mask = snd_pcm_format_silence_32(hwparams.format); - - count /= 3; - c = 0; - while (count-- > 0) { - if (format_little_endian) { - val = valp[0] | (valp[1]<<8) | (valp[2]<<16); - } else { - val = (valp[0]<<16) | (valp[1]<<8) | valp[2]; - } - /* Correct signed bit in 32-bit value */ - if (val & (1<<(bits_per_sample-1))) { - val |= 0xff<<24; /* Negate upper bits too */ - } - val = abs(val) ^ mask; - if (max_peak[c] < val) - max_peak[c] = val; - valp += 3; - if (vumeter == VUMETER_STEREO) - c = !c; - } - break; - } - case 32: { - signed int *valp = (signed int *)data; - signed int mask = snd_pcm_format_silence_32(hwparams.format); - - count /= 4; - c = 0; - while (count-- > 0) { - if (format_little_endian) - val = le32toh(*valp); - else - val = be32toh(*valp); - val = abs(val) ^ mask; - if (max_peak[c] < val) - max_peak[c] = val; - valp++; - if (vumeter == VUMETER_STEREO) - c = !c; - } - break; - } - default: - if (run == 0) { - fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample); - run = 1; - } - return; - } - max = 1 << (significant_bits_per_sample-1); - if (max <= 0) - max = 0x7fffffff; - - for (c = 0; c < ichans; c++) { - if (bits_per_sample > 16) - perc[c] = max_peak[c] / (max / 100); - else - perc[c] = max_peak[c] * 100 / max; - } - - if (interleaved && verbose <= 2) { - static int maxperc[2]; - static time_t t=0; - const time_t tt=time(NULL); - if(tt>t) { - t=tt; - maxperc[0] = 0; - maxperc[1] = 0; - } - for (c = 0; c < ichans; c++) - if (perc[c] > maxperc[c]) - maxperc[c] = perc[c]; - - putc('\r', stderr); - print_vu_meter(perc, maxperc); - fflush(stderr); - } - else if(verbose==3) { - fprintf(stderr, _("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]); - for (val = 0; val < 20; val++) - if (val <= perc[0] / 5) - putc('#', stderr); - else - putc(' ', stderr); - fprintf(stderr, " %i%%\n", perc[0]); - fflush(stderr); - } -} - -static void do_test_position(void) -{ - static long counter = 0; - static time_t tmr = -1; - time_t now; - static float availsum, delaysum, samples; - static snd_pcm_sframes_t maxavail, maxdelay; - static snd_pcm_sframes_t minavail, mindelay; - static snd_pcm_sframes_t badavail = 0, baddelay = 0; - snd_pcm_sframes_t outofrange; - snd_pcm_sframes_t avail, delay; - int err; - - err = snd_pcm_avail_delay(handle, &avail, &delay); - if (err < 0) - return; - outofrange = (test_coef * (snd_pcm_sframes_t)buffer_frames) / 2; - if (avail > outofrange || avail < -outofrange || - delay > outofrange || delay < -outofrange) { - badavail = avail; baddelay = delay; - availsum = delaysum = samples = 0; - maxavail = maxdelay = 0; - minavail = mindelay = buffer_frames * 16; - fprintf(stderr, _("Suspicious buffer position (%li total): " - "avail = %li, delay = %li, buffer = %li\n"), - ++counter, (long)avail, (long)delay, (long)buffer_frames); - } else if (verbose) { - time(&now); - if (tmr == (time_t) -1) { - tmr = now; - availsum = delaysum = samples = 0; - maxavail = maxdelay = 0; - minavail = mindelay = buffer_frames * 16; - } - if (avail > maxavail) - maxavail = avail; - if (delay > maxdelay) - maxdelay = delay; - if (avail < minavail) - minavail = avail; - if (delay < mindelay) - mindelay = delay; - availsum += avail; - delaysum += delay; - samples++; - if (avail != 0 && now != tmr) { - fprintf(stderr, "BUFPOS: avg%li/%li " - "min%li/%li max%li/%li (%li) (%li:%li/%li)\n", - (long)(availsum / samples), - (long)(delaysum / samples), - (long)minavail, (long)mindelay, - (long)maxavail, (long)maxdelay, - (long)buffer_frames, - counter, badavail, baddelay); - tmr = now; - } - } -} - -/* - */ -#ifdef CONFIG_SUPPORT_CHMAP -static u_char *remap_data(u_char *data, size_t count) -{ - static u_char *tmp, *src, *dst; - static size_t tmp_size; - size_t sample_bytes = bits_per_sample / 8; - size_t step = bits_per_frame / 8; - size_t chunk_bytes; - unsigned int ch, i; - - if (!hw_map) - return data; - - chunk_bytes = count * bits_per_frame / 8; - if (tmp_size < chunk_bytes) { - free(tmp); - tmp = malloc(chunk_bytes); - if (!tmp) { - error(_("not enough memory")); - exit(1); - } - tmp_size = count; - } - - src = data; - dst = tmp; - for (i = 0; i < count; i++) { - for (ch = 0; ch < hwparams.channels; ch++) { - memcpy(dst, src + sample_bytes * hw_map[ch], - sample_bytes); - dst += sample_bytes; - } - src += step; - } - return tmp; -} - -static u_char **remap_datav(u_char **data, size_t count) -{ - static u_char **tmp; - unsigned int ch; - - if (!hw_map) - return data; - - if (!tmp) { - tmp = malloc(sizeof(*tmp) * hwparams.channels); - if (!tmp) { - error(_("not enough memory")); - exit(1); - } - for (ch = 0; ch < hwparams.channels; ch++) - tmp[ch] = data[hw_map[ch]]; - } - return tmp; -} -#else -#define remap_data(data, count) (data) -#define remap_datav(data, count) (data) -#endif - -/* - * write function - */ - -static ssize_t pcm_write(u_char *data, size_t count) -{ - ssize_t r; - ssize_t result = 0; - - if (count < chunk_size) { - snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels); - count = chunk_size; - } - data = remap_data(data, count); - while (count > 0 && !in_aborting) { - if (test_position) - do_test_position(); - check_stdin(); - r = writei_func(handle, data, count); - if (test_position) - do_test_position(); - if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { - if (!test_nowait) - snd_pcm_wait(handle, 100); - } else if (r == -EPIPE) { - xrun(); - } else if (r == -ESTRPIPE) { - suspend(); - } else if (r < 0) { - error(_("write error: %s"), snd_strerror(r)); - prg_exit(EXIT_FAILURE); - } - if (r > 0) { - if (vumeter) - compute_max_peak(data, r * hwparams.channels); - result += r; - count -= r; - data += r * bits_per_frame / 8; - } - } - return result; -} - -static ssize_t pcm_writev(u_char **data, unsigned int channels, size_t count) -{ - ssize_t r; - size_t result = 0; - - if (count != chunk_size) { - unsigned int channel; - size_t offset = count; - size_t remaining = chunk_size - count; - for (channel = 0; channel < channels; channel++) - snd_pcm_format_set_silence(hwparams.format, data[channel] + offset * bits_per_sample / 8, remaining); - count = chunk_size; - } - data = remap_datav(data, count); - while (count > 0 && !in_aborting) { - unsigned int channel; - void *bufs[channels]; - size_t offset = result; - for (channel = 0; channel < channels; channel++) - bufs[channel] = data[channel] + offset * bits_per_sample / 8; - if (test_position) - do_test_position(); - check_stdin(); - r = writen_func(handle, bufs, count); - if (test_position) - do_test_position(); - if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { - if (!test_nowait) - snd_pcm_wait(handle, 100); - } else if (r == -EPIPE) { - xrun(); - } else if (r == -ESTRPIPE) { - suspend(); - } else if (r < 0) { - error(_("writev error: %s"), snd_strerror(r)); - prg_exit(EXIT_FAILURE); - } - if (r > 0) { - if (vumeter) { - for (channel = 0; channel < channels; channel++) - compute_max_peak(data[channel], r); - } - result += r; - count -= r; - } - } - return result; -} - -/* - * read function - */ - -static ssize_t pcm_read(u_char *data, size_t rcount) -{ - ssize_t r; - size_t result = 0; - size_t count = rcount; - - if (count != chunk_size) { - count = chunk_size; - } - - while (count > 0 && !in_aborting) { - if (test_position) - do_test_position(); - check_stdin(); - r = readi_func(handle, data, count); - if (test_position) - do_test_position(); - if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { - if (!test_nowait) - snd_pcm_wait(handle, 100); - } else if (r == -EPIPE) { - xrun(); - } else if (r == -ESTRPIPE) { - suspend(); - } else if (r < 0) { - error(_("read error: %s"), snd_strerror(r)); - prg_exit(EXIT_FAILURE); - } - if (r > 0) { - if (vumeter) - compute_max_peak(data, r * hwparams.channels); - result += r; - count -= r; - data += r * bits_per_frame / 8; - } - } - return rcount; -} - -static ssize_t pcm_readv(u_char **data, unsigned int channels, size_t rcount) -{ - ssize_t r; - size_t result = 0; - size_t count = rcount; - - if (count != chunk_size) { - count = chunk_size; - } - - while (count > 0 && !in_aborting) { - unsigned int channel; - void *bufs[channels]; - size_t offset = result; - for (channel = 0; channel < channels; channel++) - bufs[channel] = data[channel] + offset * bits_per_sample / 8; - if (test_position) - do_test_position(); - check_stdin(); - r = readn_func(handle, bufs, count); - if (test_position) - do_test_position(); - if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { - if (!test_nowait) - snd_pcm_wait(handle, 100); - } else if (r == -EPIPE) { - xrun(); - } else if (r == -ESTRPIPE) { - suspend(); - } else if (r < 0) { - error(_("readv error: %s"), snd_strerror(r)); - prg_exit(EXIT_FAILURE); - } - if (r > 0) { - if (vumeter) { - for (channel = 0; channel < channels; channel++) - compute_max_peak(data[channel], r); - } - result += r; - count -= r; - } - } - return rcount; -} - -/* - * ok, let's play a .voc file - */ - -static ssize_t voc_pcm_write(u_char *data, size_t count) -{ - ssize_t result = count, r; - size_t size; - - while (count > 0 && !in_aborting) { - size = count; - if (size > chunk_bytes - buffer_pos) - size = chunk_bytes - buffer_pos; - memcpy(audiobuf + buffer_pos, data, size); - data += size; - count -= size; - buffer_pos += size; - if ((size_t)buffer_pos == chunk_bytes) { - if ((size_t)(r = pcm_write(audiobuf, chunk_size)) != chunk_size) - return r; - buffer_pos = 0; - } - } - return result; -} - -static void voc_write_silence(unsigned x) -{ - unsigned l; - u_char *buf; - - buf = (u_char *) malloc(chunk_bytes); - if (buf == NULL) { - error(_("can't allocate buffer for silence")); - return; /* not fatal error */ - } - snd_pcm_format_set_silence(hwparams.format, buf, chunk_size * hwparams.channels); - while (x > 0 && !in_aborting) { - l = x; - if (l > chunk_size) - l = chunk_size; - if (voc_pcm_write(buf, l) != (ssize_t)l) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - x -= l; - } - free(buf); -} - -static void voc_pcm_flush(void) -{ - if (buffer_pos > 0) { - size_t b; - if (snd_pcm_format_set_silence(hwparams.format, audiobuf + buffer_pos, chunk_bytes - buffer_pos * 8 / bits_per_sample) < 0) - fprintf(stderr, _("voc_pcm_flush - silence error")); - b = chunk_size; - if (pcm_write(audiobuf, b) != (ssize_t)b) - error(_("voc_pcm_flush error")); - } - snd_pcm_nonblock(handle, 0); - snd_pcm_drain(handle); - snd_pcm_nonblock(handle, nonblock); -} - -static void voc_play(int fd, int ofs, char *name) -{ - int l; - VocBlockType *bp; - VocVoiceData *vd; - VocExtBlock *eb; - size_t nextblock, in_buffer; - u_char *data, *buf; - char was_extended = 0, output = 0; - u_short *sp, repeat = 0; - off64_t filepos = 0; - -#define COUNT(x) nextblock -= x; in_buffer -= x; data += x -#define COUNT1(x) in_buffer -= x; data += x - - data = buf = (u_char *)malloc(64 * 1024); - buffer_pos = 0; - if (data == NULL) { - error(_("malloc error")); - prg_exit(EXIT_FAILURE); - } - if (!quiet_mode) { - fprintf(stderr, _("Playing Creative Labs Channel file '%s'...\n"), name); - } - /* first we waste the rest of header, ugly but we don't need seek */ - while (ofs > (ssize_t)chunk_bytes) { - if ((size_t)safe_read(fd, buf, chunk_bytes) != chunk_bytes) { - error(_("read error")); - prg_exit(EXIT_FAILURE); - } - ofs -= chunk_bytes; - } - if (ofs) { - if (safe_read(fd, buf, ofs) != ofs) { - error(_("read error")); - prg_exit(EXIT_FAILURE); - } - } - hwparams.format = DEFAULT_FORMAT; - hwparams.channels = 1; - hwparams.rate = DEFAULT_SPEED; - set_params(); - - in_buffer = nextblock = 0; - while (!in_aborting) { - Fill_the_buffer: /* need this for repeat */ - if (in_buffer < 32) { - /* move the rest of buffer to pos 0 and fill the buf up */ - if (in_buffer) - memcpy(buf, data, in_buffer); - data = buf; - if ((l = safe_read(fd, buf + in_buffer, chunk_bytes - in_buffer)) > 0) - in_buffer += l; - else if (!in_buffer) { - /* the file is truncated, so simulate 'Terminator' - and reduce the datablock for safe landing */ - nextblock = buf[0] = 0; - if (l == -1) { - perror(name); - prg_exit(EXIT_FAILURE); - } - } - } - while (!nextblock) { /* this is a new block */ - if (in_buffer < sizeof(VocBlockType)) - goto __end; - bp = (VocBlockType *) data; - COUNT1(sizeof(VocBlockType)); - nextblock = VOC_DATALEN(bp); - if (output && !quiet_mode) - fprintf(stderr, "\n"); /* write /n after ASCII-out */ - output = 0; - switch (bp->type) { - case 0: -#if 0 - d_printf("Terminator\n"); -#endif - return; /* VOC-file stop */ - case 1: - vd = (VocVoiceData *) data; - COUNT1(sizeof(VocVoiceData)); - /* we need a SYNC, before we can set new SPEED, STEREO ... */ - - if (!was_extended) { - hwparams.rate = (int) (vd->tc); - hwparams.rate = 1000000 / (256 - hwparams.rate); -#if 0 - d_printf("Channel data %d Hz\n", dsp_speed); -#endif - if (vd->pack) { /* /dev/dsp can't it */ - error(_("can't play packed .voc files")); - return; - } - if (hwparams.channels == 2) /* if we are in Stereo-Mode, switch back */ - hwparams.channels = 1; - } else { /* there was extended block */ - hwparams.channels = 2; - was_extended = 0; - } - set_params(); - break; - case 2: /* nothing to do, pure data */ -#if 0 - d_printf("Channel continuation\n"); -#endif - break; - case 3: /* a silence block, no data, only a count */ - sp = (u_short *) data; - COUNT1(sizeof(u_short)); - hwparams.rate = (int) (*data); - COUNT1(1); - hwparams.rate = 1000000 / (256 - hwparams.rate); - set_params(); -#if 0 - { - size_t silence; - silence = (((size_t) * sp) * 1000) / hwparams.rate; - d_printf("Silence for %d ms\n", (int) silence); - } -#endif - voc_write_silence(*sp); - break; - case 4: /* a marker for syncronisation, no effect */ - sp = (u_short *) data; - COUNT1(sizeof(u_short)); -#if 0 - d_printf("Marker %d\n", *sp); -#endif - break; - case 5: /* ASCII text, we copy to stderr */ - output = 1; -#if 0 - d_printf("ASCII - text :\n"); -#endif - break; - case 6: /* repeat marker, says repeatcount */ - /* my specs don't say it: maybe this can be recursive, but - I don't think somebody use it */ - repeat = *(u_short *) data; - COUNT1(sizeof(u_short)); -#if 0 - d_printf("Repeat loop %d times\n", repeat); -#endif - if (filepos >= 0) { /* if < 0, one seek fails, why test another */ - if ((filepos = lseek64(fd, 0, 1)) < 0) { - error(_("can't play loops; %s isn't seekable\n"), name); - repeat = 0; - } else { - filepos -= in_buffer; /* set filepos after repeat */ - } - } else { - repeat = 0; - } - break; - case 7: /* ok, lets repeat that be rewinding tape */ - if (repeat) { - if (repeat != 0xFFFF) { -#if 0 - d_printf("Repeat loop %d\n", repeat); -#endif - --repeat; - } -#if 0 - else - d_printf("Neverending loop\n"); -#endif - lseek64(fd, filepos, 0); - in_buffer = 0; /* clear the buffer */ - goto Fill_the_buffer; - } -#if 0 - else - d_printf("End repeat loop\n"); -#endif - break; - case 8: /* the extension to play Stereo, I have SB 1.0 :-( */ - was_extended = 1; - eb = (VocExtBlock *) data; - COUNT1(sizeof(VocExtBlock)); - hwparams.rate = (int) (eb->tc); - hwparams.rate = 256000000L / (65536 - hwparams.rate); - hwparams.channels = eb->mode == VOC_MODE_STEREO ? 2 : 1; - if (hwparams.channels == 2) - hwparams.rate = hwparams.rate >> 1; - if (eb->pack) { /* /dev/dsp can't it */ - error(_("can't play packed .voc files")); - return; - } -#if 0 - d_printf("Extended block %s %d Hz\n", - (eb->mode ? "Stereo" : "Mono"), dsp_speed); -#endif - break; - default: - error(_("unknown blocktype %d. terminate."), bp->type); - return; - } /* switch (bp->type) */ - } /* while (! nextblock) */ - /* put nextblock data bytes to dsp */ - l = in_buffer; - if (nextblock < (size_t)l) - l = nextblock; - if (l) { - if (output && !quiet_mode) { - if (xwrite(2, data, l) != l) { /* to stderr */ - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - } else { - if (voc_pcm_write(data, l) != l) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - } - COUNT(l); - } - } /* while(1) */ - __end: - voc_pcm_flush(); - free(buf); -} -/* that was a big one, perhaps somebody split it :-) */ - -/* setting the globals for playing raw data */ -static void init_raw_data(void) -{ - hwparams = rhwparams; -} - -/* calculate the data count to read from/to dsp */ -static off64_t calc_count(void) -{ - off64_t count; - - if (timelimit == 0) - if (sampleslimit == 0) - count = pbrec_count; - else - count = snd_pcm_format_size(hwparams.format, sampleslimit * hwparams.channels); - else { - count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels); - count *= (off64_t)timelimit; - } - return count < pbrec_count ? count : pbrec_count; -} - -/* write a .VOC-header */ -static void begin_voc(int fd, size_t cnt) -{ - VocHeader vh; - VocBlockType bt; - VocVoiceData vd; - VocExtBlock eb; - - memcpy(vh.magic, VOC_MAGIC_STRING, 20); - vh.headerlen = LE_SHORT(sizeof(VocHeader)); - vh.version = LE_SHORT(VOC_ACTUAL_VERSION); - vh.coded_ver = LE_SHORT(0x1233 - VOC_ACTUAL_VERSION); - - if (xwrite(fd, &vh, sizeof(VocHeader)) != sizeof(VocHeader)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - if (hwparams.channels > 1) { - /* write an extended block */ - bt.type = 8; - bt.datalen = 4; - bt.datalen_m = bt.datalen_h = 0; - if (xwrite(fd, &bt, sizeof(VocBlockType)) != sizeof(VocBlockType)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - eb.tc = LE_SHORT(65536 - 256000000L / (hwparams.rate << 1)); - eb.pack = 0; - eb.mode = 1; - if (xwrite(fd, &eb, sizeof(VocExtBlock)) != sizeof(VocExtBlock)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - } - bt.type = 1; - cnt += sizeof(VocVoiceData); /* Channel_data block follows */ - bt.datalen = (u_char) (cnt & 0xFF); - bt.datalen_m = (u_char) ((cnt & 0xFF00) >> 8); - bt.datalen_h = (u_char) ((cnt & 0xFF0000) >> 16); - if (xwrite(fd, &bt, sizeof(VocBlockType)) != sizeof(VocBlockType)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - vd.tc = (u_char) (256 - (1000000 / hwparams.rate)); - vd.pack = 0; - if (xwrite(fd, &vd, sizeof(VocVoiceData)) != sizeof(VocVoiceData)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } -} - -/* write a WAVE-header */ -static void begin_wave(int fd, size_t cnt) -{ - WaveHeader h; - WaveFmtBody f; - WaveChunkHeader cf, cd; - int bits; - u_int tmp; - u_short tmp2; - - /* WAVE cannot handle greater than 32bit (signed?) int */ - if (cnt == (size_t)-2) - cnt = 0x7fffff00; - - bits = 8; - switch ((unsigned long) hwparams.format) { - case SND_PCM_FORMAT_U8: - bits = 8; - break; - case SND_PCM_FORMAT_S16_LE: - bits = 16; - break; - case SND_PCM_FORMAT_S32_LE: - case SND_PCM_FORMAT_FLOAT_LE: - bits = 32; - break; - case SND_PCM_FORMAT_S24_LE: - case SND_PCM_FORMAT_S24_3LE: - bits = 24; - break; - default: - error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format)); - prg_exit(EXIT_FAILURE); - } - h.magic = WAV_RIFF; - tmp = cnt + sizeof(WaveHeader) + sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + sizeof(WaveChunkHeader) - 8; - h.length = LE_INT(tmp); - h.type = WAV_WAVE; - - cf.type = WAV_FMT; - cf.length = LE_INT(16); - - if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE) - f.format = LE_SHORT(WAV_FMT_IEEE_FLOAT); - else - f.format = LE_SHORT(WAV_FMT_PCM); - f.channels = LE_SHORT(hwparams.channels); - f.sample_fq = LE_INT(hwparams.rate); -#if 0 - tmp2 = (samplesize == 8) ? 1 : 2; - f.byte_p_spl = LE_SHORT(tmp2); - tmp = dsp_speed * hwparams.channels * (u_int) tmp2; -#else - tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8; - f.byte_p_spl = LE_SHORT(tmp2); - tmp = (u_int) tmp2 * hwparams.rate; -#endif - f.byte_p_sec = LE_INT(tmp); - f.bit_p_spl = LE_SHORT(bits); - - cd.type = WAV_DATA; - cd.length = LE_INT(cnt); - - if (xwrite(fd, &h, sizeof(WaveHeader)) != sizeof(WaveHeader) || - xwrite(fd, &cf, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader) || - xwrite(fd, &f, sizeof(WaveFmtBody)) != sizeof(WaveFmtBody) || - xwrite(fd, &cd, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } -} - -/* write a Au-header */ -static void begin_au(int fd, size_t cnt) -{ - AuHeader ah; - - ah.magic = AU_MAGIC; - ah.hdr_size = BE_INT(24); - ah.data_size = BE_INT(cnt); - switch ((unsigned long) hwparams.format) { - case SND_PCM_FORMAT_MU_LAW: - ah.encoding = BE_INT(AU_FMT_ULAW); - break; - case SND_PCM_FORMAT_U8: - ah.encoding = BE_INT(AU_FMT_LIN8); - break; - case SND_PCM_FORMAT_S16_BE: - ah.encoding = BE_INT(AU_FMT_LIN16); - break; - default: - error(_("Sparc Audio doesn't support %s format..."), snd_pcm_format_name(hwparams.format)); - prg_exit(EXIT_FAILURE); - } - ah.sample_rate = BE_INT(hwparams.rate); - ah.channels = BE_INT(hwparams.channels); - if (xwrite(fd, &ah, sizeof(AuHeader)) != sizeof(AuHeader)) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } -} - -/* closing .VOC */ -static void end_voc(int fd) -{ - off64_t length_seek; - VocBlockType bt; - size_t cnt; - char dummy = 0; /* Write a Terminator */ - - if (xwrite(fd, &dummy, 1) != 1) { - error(_("write error")); - prg_exit(EXIT_FAILURE); - } - length_seek = sizeof(VocHeader); - if (hwparams.channels > 1) - length_seek += sizeof(VocBlockType) + sizeof(VocExtBlock); - bt.type = 1; - cnt = fdcount; - cnt += sizeof(VocVoiceData); /* Channel_data block follows */ - if (cnt > 0x00ffffff) - cnt = 0x00ffffff; - bt.datalen = (u_char) (cnt & 0xFF); - bt.datalen_m = (u_char) ((cnt & 0xFF00) >> 8); - bt.datalen_h = (u_char) ((cnt & 0xFF0000) >> 16); - if (lseek64(fd, length_seek, SEEK_SET) == length_seek) - xwrite(fd, &bt, sizeof(VocBlockType)); - if (fd != 1) - close(fd); -} - -static void end_wave(int fd) -{ /* only close output */ - WaveChunkHeader cd; - off64_t length_seek; - off64_t filelen; - u_int rifflen; - - length_seek = sizeof(WaveHeader) + - sizeof(WaveChunkHeader) + - sizeof(WaveFmtBody); - cd.type = WAV_DATA; - cd.length = fdcount > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(fdcount); - filelen = fdcount + 2*sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + 4; - rifflen = filelen > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(filelen); - if (lseek64(fd, 4, SEEK_SET) == 4) - xwrite(fd, &rifflen, 4); - if (lseek64(fd, length_seek, SEEK_SET) == length_seek) - xwrite(fd, &cd, sizeof(WaveChunkHeader)); - if (fd != 1) - close(fd); -} - -static void end_au(int fd) -{ /* only close output */ - AuHeader ah; - off64_t length_seek; - - length_seek = (char *)&ah.data_size - (char *)&ah; - ah.data_size = fdcount > 0xffffffff ? 0xffffffff : BE_INT(fdcount); - if (lseek64(fd, length_seek, SEEK_SET) == length_seek) - xwrite(fd, &ah.data_size, sizeof(ah.data_size)); - if (fd != 1) - close(fd); -} - -static void header(int rtype, char *name) -{ - if (!quiet_mode) { - if (! name) - name = (stream == SND_PCM_STREAM_PLAYBACK) ? "stdout" : "stdin"; - fprintf(stderr, "%s %s '%s' : ", - (stream == SND_PCM_STREAM_PLAYBACK) ? _("Playing") : _("Recording"), - gettext(fmt_rec_table[rtype].what), - name); - fprintf(stderr, "%s, ", snd_pcm_format_description(hwparams.format)); - fprintf(stderr, _("Rate %d Hz, "), hwparams.rate); - if (hwparams.channels == 1) - fprintf(stderr, _("Mono")); - else if (hwparams.channels == 2) - fprintf(stderr, _("Stereo")); - else - fprintf(stderr, _("Channels %i"), hwparams.channels); - fprintf(stderr, "\n"); - } -} - -/* playing raw data */ - -static void playback_go(int fd, size_t loaded, off64_t count, int rtype, char *name) -{ - int l, r; - off64_t written = 0; - off64_t c; - - header(rtype, name); - set_params(); - - while (loaded > chunk_bytes && written < count && !in_aborting) { - if (pcm_write(audiobuf + written, chunk_size) <= 0) - return; - written += chunk_bytes; - loaded -= chunk_bytes; - } - if (written > 0 && loaded > 0) - memmove(audiobuf, audiobuf + written, loaded); - - l = loaded; - while (written < count && !in_aborting) { - do { - c = count - written; - if (c > chunk_bytes) - c = chunk_bytes; - - /* c < l, there is more data loaded - * then we actually need to write - */ - if (c < l) - l = c; - - c -= l; - - if (c == 0) - break; - r = safe_read(fd, audiobuf + l, c); - if (r < 0) { - perror(name); - prg_exit(EXIT_FAILURE); - } - fdcount += r; - if (r == 0) - break; - l += r; - } while ((size_t)l < chunk_bytes); - l = l * 8 / bits_per_frame; - r = pcm_write(audiobuf, l); - if (r != l) - break; - r = r * bits_per_frame / 8; - written += r; - l = 0; - } - snd_pcm_nonblock(handle, 0); - snd_pcm_drain(handle); - snd_pcm_nonblock(handle, nonblock); -} - -static int read_header(int *loaded, int header_size) -{ - int ret; - struct stat buf; - - ret = fstat(fd, &buf); - if (ret < 0) { - perror("fstat"); - prg_exit(EXIT_FAILURE); - } - - /* don't be adventurous, get out if file size is smaller than - * requested header size */ - if (buf.st_size < header_size) - return -1; - - if (*loaded < header_size) { - header_size -= *loaded; - ret = safe_read(fd, audiobuf + *loaded, header_size); - if (ret != header_size) { - error(_("read error")); - prg_exit(EXIT_FAILURE); - } - *loaded += header_size; - } - return 0; -} - -static int playback_au(char *name, int *loaded) -{ - if (read_header(loaded, sizeof(AuHeader)) < 0) - return -1; - - if (test_au(fd, audiobuf) < 0) - return -1; - - rhwparams.format = hwparams.format; - pbrec_count = calc_count(); - playback_go(fd, *loaded - sizeof(AuHeader), pbrec_count, FORMAT_AU, name); - - return 0; -} - -static int playback_voc(char *name, int *loaded) -{ - int ofs; - - if (read_header(loaded, sizeof(VocHeader)) < 0) - return -1; - - if ((ofs = test_vocfile(audiobuf)) < 0) - return -1; - - pbrec_count = calc_count(); - voc_play(fd, ofs, name); - - return 0; -} - -static int playback_wave(char *name, int *loaded) -{ - ssize_t dtawave; - - if (read_header(loaded, sizeof(WaveHeader)) < 0) - return -1; - - if ((dtawave = test_wavefile(fd, audiobuf, *loaded)) < 0) - return -1; - - pbrec_count = calc_count(); - playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name); - - return 0; -} - -static int playback_raw(char *name, int *loaded) -{ - init_raw_data(); - pbrec_count = calc_count(); - playback_go(fd, *loaded, pbrec_count, FORMAT_RAW, name); - - return 0; -} - -/* - * let's play or capture it (capture_type says VOC/WAVE/raw) - */ - -static void playback(char *name) -{ - int loaded = 0; - - pbrec_count = LLONG_MAX; - fdcount = 0; - if (!name || !strcmp(name, "-")) { - fd = fileno(stdin); - name = "stdin"; - } else { - init_stdin(); - if ((fd = open(name, O_RDONLY, 0)) == -1) { - perror(name); - prg_exit(EXIT_FAILURE); - } - } - - switch(file_type) { - case FORMAT_AU: - playback_au(name, &loaded); - break; - case FORMAT_VOC: - playback_voc(name, &loaded); - break; - case FORMAT_WAVE: - playback_wave(name, &loaded); - break; - case FORMAT_RAW: - playback_raw(name, &loaded); - break; - default: - /* parse the file header */ - if (playback_au(name, &loaded) < 0 && - playback_voc(name, &loaded) < 0 && - playback_wave(name, &loaded) < 0) - playback_raw(name, &loaded); /* should be raw data */ - break; - } - - if (fd != 0) - close(fd); -} - -/** - * mystrftime - * - * Variant of strftime(3) that supports additional format - * specifiers in the format string. - * - * Parameters: - * - * s - destination string - * max - max number of bytes to write - * userformat - format string - * tm - time information - * filenumber - the number of the file, starting at 1 - * - * Returns: number of bytes written to the string s - */ -size_t mystrftime(char *s, size_t max, const char *userformat, - const struct tm *tm, const int filenumber) -{ - char formatstring[PATH_MAX] = ""; - char tempstring[PATH_MAX] = ""; - char *format, *tempstr; - const char *pos_userformat; - - format = formatstring; - - /* if mystrftime is called with userformat = NULL we return a zero length string */ - if (userformat == NULL) { - *s = '\0'; - return 0; - } - - for (pos_userformat = userformat; *pos_userformat; ++pos_userformat) { - if (*pos_userformat == '%') { - tempstr = tempstring; - tempstr[0] = '\0'; - switch (*++pos_userformat) { - - case '\0': // end of string - --pos_userformat; - break; - - case 'v': // file number - sprintf(tempstr, "%02d", filenumber); - break; - - default: // All other codes will be handled by strftime - *format++ = '%'; - *format++ = *pos_userformat; - continue; - } - - /* If a format specifier was found and used, copy the result. */ - if (tempstr[0]) { - while ((*format = *tempstr++) != '\0') - ++format; - continue; - } - } - - /* For any other character than % we simply copy the character */ - *format++ = *pos_userformat; - } - - *format = '\0'; - format = formatstring; - return strftime(s, max, format, tm); -} - -static int new_capture_file(char *name, char *namebuf, size_t namelen, - int filecount) -{ - char *s; - char buf[PATH_MAX+1]; - time_t t; - struct tm *tmp; - - if (use_strftime) { - t = time(NULL); - tmp = localtime(&t); - if (tmp == NULL) { - perror("localtime"); - prg_exit(EXIT_FAILURE); - } - if (mystrftime(namebuf, namelen, name, tmp, filecount+1) == 0) { - fprintf(stderr, "mystrftime returned 0"); - prg_exit(EXIT_FAILURE); - } - return filecount; - } - - /* get a copy of the original filename */ - strncpy(buf, name, sizeof(buf)); - - /* separate extension from filename */ - s = buf + strlen(buf); - while (s > buf && *s != '.' && *s != '/') - --s; - if (*s == '.') - *s++ = 0; - else if (*s == '/') - s = buf + strlen(buf); - - /* upon first jump to this if block rename the first file */ - if (filecount == 1) { - if (*s) - snprintf(namebuf, namelen, "%s-01.%s", buf, s); - else - snprintf(namebuf, namelen, "%s-01", buf); - remove(namebuf); - rename(name, namebuf); - filecount = 2; - } - - /* name of the current file */ - if (*s) - snprintf(namebuf, namelen, "%s-%02i.%s", buf, filecount, s); - else - snprintf(namebuf, namelen, "%s-%02i", buf, filecount); - - return filecount; -} - -/** - * create_path - * - * This function creates a file path, like mkdir -p. - * - * Parameters: - * - * path - the path to create - * - * Returns: 0 on success, -1 on failure - * On failure, a message has been printed to stderr. - */ -int create_path(const char *path) -{ - char *start; - mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - - if (path[0] == '/') - start = strchr(path + 1, '/'); - else - start = strchr(path, '/'); - - while (start) { - char *buffer = strdup(path); - buffer[start-path] = 0x00; - - if (mkdir(buffer, mode) == -1 && errno != EEXIST) { - fprintf(stderr, "Problem creating directory %s", buffer); - perror(" "); - free(buffer); - return -1; - } - free(buffer); - start = strchr(start + 1, '/'); - } - return 0; -} - -static int safe_open(const char *name) -{ - int fd; - - fd = open(name, O_WRONLY | O_CREAT, 0644); - if (fd == -1) { - if (errno != ENOENT || !use_strftime) - return -1; - if (create_path(name) == 0) - fd = open(name, O_WRONLY | O_CREAT, 0644); - } - return fd; -} - -static void capture(char *orig_name) -{ - int tostdout=0; /* boolean which describes output stream */ - int filecount=0; /* number of files written */ - char *name = orig_name; /* current filename */ - char namebuf[PATH_MAX+1]; - off64_t count, rest; /* number of bytes to capture */ - struct stat statbuf; - - /* get number of bytes to capture */ - count = calc_count(); - if (count == 0) - count = LLONG_MAX; - /* compute the number of bytes per file */ - max_file_size = (long long) max_file_time * - snd_pcm_format_size(hwparams.format, - hwparams.rate * hwparams.channels); - /* WAVE-file should be even (I'm not sure), but wasting one byte - isn't a problem (this can only be in 8 bit mono) */ - if (count < LLONG_MAX) - count += count % 2; - else - count -= count % 2; - - /* display verbose output to console */ - header(file_type, name); - - /* setup sound hardware */ - set_params(); - - /* write to stdout? */ - if (!name || !strcmp(name, "-")) { - fd = fileno(stdout); - name = "stdout"; - tostdout=1; - if (count > fmt_rec_table[file_type].max_filesize) - count = fmt_rec_table[file_type].max_filesize; - } - init_stdin(); - - do { - /* open a file to write */ - if(!tostdout) { - /* upon the second file we start the numbering scheme */ - if (filecount || use_strftime) { - filecount = new_capture_file(orig_name, namebuf, - sizeof(namebuf), - filecount); - name = namebuf; - } - - /* open a new file */ - if (!lstat(name, &statbuf)) { - if (S_ISREG(statbuf.st_mode)) - remove(name); - } - fd = safe_open(name); - if (fd < 0) { - perror(name); - prg_exit(EXIT_FAILURE); - } - filecount++; - } - - rest = count; - if (rest > fmt_rec_table[file_type].max_filesize) - rest = fmt_rec_table[file_type].max_filesize; - if (max_file_size && (rest > max_file_size)) - rest = max_file_size; - - /* setup sample header */ - if (fmt_rec_table[file_type].start) - fmt_rec_table[file_type].start(fd, rest); - - /* capture */ - fdcount = 0; - while (rest > 0 && recycle_capture_file == 0 && !in_aborting) { - size_t c = (rest <= (off64_t)chunk_bytes) ? - (size_t)rest : chunk_bytes; - size_t f = c * 8 / bits_per_frame; - if (pcm_read(audiobuf, f) != f) { - in_aborting = 1; - break; - } - if (xwrite(fd, audiobuf, c) != c) { - perror(name); - in_aborting = 1; - break; - } - count -= c; - rest -= c; - fdcount += c; - } - - /* re-enable SIGUSR1 signal */ - if (recycle_capture_file) { - recycle_capture_file = 0; - signal(SIGUSR1, signal_handler_recycle); - } - - /* finish sample container */ - if (fmt_rec_table[file_type].end && !tostdout) { - fmt_rec_table[file_type].end(fd); - fd = -1; - } - - if (in_aborting) - prg_exit(EXIT_FAILURE); - - /* repeat the loop when format is raw without timelimit or - * requested counts of data are recorded - */ - } while ((file_type == FORMAT_RAW && !timelimit && !sampleslimit) || count > 0); -} - -static void playbackv_go(int* fds, unsigned int channels, size_t loaded, off64_t count, int rtype, char **names) -{ - int r; - size_t vsize; - - unsigned int channel; - u_char *bufs[channels]; - - header(rtype, names[0]); - set_params(); - - vsize = chunk_bytes / channels; - - // Not yet implemented - assert(loaded == 0); - - for (channel = 0; channel < channels; ++channel) - bufs[channel] = audiobuf + vsize * channel; - - while (count > 0 && !in_aborting) { - size_t c = 0; - size_t expected = count / channels; - if (expected > vsize) - expected = vsize; - do { - r = safe_read(fds[0], bufs[0], expected); - if (r < 0) { - perror(names[channel]); - prg_exit(EXIT_FAILURE); - } - for (channel = 1; channel < channels; ++channel) { - if (safe_read(fds[channel], bufs[channel], r) != r) { - perror(names[channel]); - prg_exit(EXIT_FAILURE); - } - } - if (r == 0) - break; - c += r; - } while (c < expected); - c = c * 8 / bits_per_sample; - r = pcm_writev(bufs, channels, c); - if ((size_t)r != c) - break; - r = r * bits_per_frame / 8; - count -= r; - } - snd_pcm_nonblock(handle, 0); - snd_pcm_drain(handle); - snd_pcm_nonblock(handle, nonblock); -} - -static void capturev_go(int* fds, unsigned int channels, off64_t count, int rtype, char **names) -{ - size_t c; - ssize_t r; - unsigned int channel; - size_t vsize; - u_char *bufs[channels]; - - header(rtype, names[0]); - set_params(); - - vsize = chunk_bytes / channels; - - for (channel = 0; channel < channels; ++channel) - bufs[channel] = audiobuf + vsize * channel; - - while (count > 0 && !in_aborting) { - size_t rv; - c = count; - if (c > chunk_bytes) - c = chunk_bytes; - c = c * 8 / bits_per_frame; - if ((size_t)(r = pcm_readv(bufs, channels, c)) != c) - break; - rv = r * bits_per_sample / 8; - for (channel = 0; channel < channels; ++channel) { - if ((size_t)xwrite(fds[channel], bufs[channel], rv) != rv) { - perror(names[channel]); - prg_exit(EXIT_FAILURE); - } - } - r = r * bits_per_frame / 8; - count -= r; - fdcount += r; - } -} - -static void playbackv(char **names, unsigned int count) -{ - int ret = 0; - unsigned int channel; - unsigned int channels = rhwparams.channels; - int alloced = 0; - int fds[channels]; - for (channel = 0; channel < channels; ++channel) - fds[channel] = -1; - - if (count == 1 && channels > 1) { - size_t len = strlen(names[0]); - char format[1024]; - memcpy(format, names[0], len); - strcpy(format + len, ".%d"); - len += 4; - names = malloc(sizeof(*names) * channels); - for (channel = 0; channel < channels; ++channel) { - names[channel] = malloc(len); - sprintf(names[channel], format, channel); - } - alloced = 1; - } else if (count != channels) { - error(_("You need to specify %d files"), channels); - prg_exit(EXIT_FAILURE); - } - - for (channel = 0; channel < channels; ++channel) { - fds[channel] = open(names[channel], O_RDONLY, 0); - if (fds[channel] < 0) { - perror(names[channel]); - ret = EXIT_FAILURE; - goto __end; - } - } - /* should be raw data */ - init_raw_data(); - pbrec_count = calc_count(); - playbackv_go(fds, channels, 0, pbrec_count, FORMAT_RAW, names); - - __end: - for (channel = 0; channel < channels; ++channel) { - if (fds[channel] >= 0) - close(fds[channel]); - if (alloced) - free(names[channel]); - } - if (alloced) - free(names); - if (ret) - prg_exit(ret); -} - -static void capturev(char **names, unsigned int count) -{ - int ret = 0; - unsigned int channel; - unsigned int channels = rhwparams.channels; - int alloced = 0; - int fds[channels]; - for (channel = 0; channel < channels; ++channel) - fds[channel] = -1; - - if (count == 1) { - size_t len = strlen(names[0]); - char format[1024]; - memcpy(format, names[0], len); - strcpy(format + len, ".%d"); - len += 4; - names = malloc(sizeof(*names) * channels); - for (channel = 0; channel < channels; ++channel) { - names[channel] = malloc(len); - sprintf(names[channel], format, channel); - } - alloced = 1; - } else if (count != channels) { - error(_("You need to specify %d files"), channels); - prg_exit(EXIT_FAILURE); - } - - for (channel = 0; channel < channels; ++channel) { - fds[channel] = open(names[channel], O_WRONLY + O_CREAT, 0644); - if (fds[channel] < 0) { - perror(names[channel]); - ret = EXIT_FAILURE; - goto __end; - } - } - /* should be raw data */ - init_raw_data(); - pbrec_count = calc_count(); - capturev_go(fds, channels, pbrec_count, FORMAT_RAW, names); - - __end: - for (channel = 0; channel < channels; ++channel) { - if (fds[channel] >= 0) - close(fds[channel]); - if (alloced) - free(names[channel]); - } - if (alloced) - free(names); - if (ret) - prg_exit(ret); -} diff --git a/aplay/formats.h b/aplay/formats.h deleted file mode 100644 index ac0a2b0..0000000 --- a/aplay/formats.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef FORMATS_H -#define FORMATS_H 1 - -#include <endian.h> -#include <byteswap.h> - -/* Definitions for .VOC files */ - -#define VOC_MAGIC_STRING "Creative Voice File\x1A" -#define VOC_ACTUAL_VERSION 0x010A -#define VOC_SAMPLESIZE 8 - -#define VOC_MODE_MONO 0 -#define VOC_MODE_STEREO 1 - -#define VOC_DATALEN(bp) ((u_long)(bp->datalen) | \ - ((u_long)(bp->datalen_m) << 8) | \ - ((u_long)(bp->datalen_h) << 16) ) - -typedef struct voc_header { - u_char magic[20]; /* must be MAGIC_STRING */ - u_short headerlen; /* Headerlength, should be 0x1A */ - u_short version; /* VOC-file version */ - u_short coded_ver; /* 0x1233-version */ -} VocHeader; - -typedef struct voc_blocktype { - u_char type; - u_char datalen; /* low-byte */ - u_char datalen_m; /* medium-byte */ - u_char datalen_h; /* high-byte */ -} VocBlockType; - -typedef struct voc_voice_data { - u_char tc; - u_char pack; -} VocVoiceData; - -typedef struct voc_ext_block { - u_short tc; - u_char pack; - u_char mode; -} VocExtBlock; - -/* Definitions for Microsoft WAVE format */ - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) -#define LE_SHORT(v) (v) -#define LE_INT(v) (v) -#define BE_SHORT(v) bswap_16(v) -#define BE_INT(v) bswap_32(v) -#elif __BYTE_ORDER == __BIG_ENDIAN -#define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) -#define LE_SHORT(v) bswap_16(v) -#define LE_INT(v) bswap_32(v) -#define BE_SHORT(v) (v) -#define BE_INT(v) (v) -#else -#error "Wrong endian" -#endif - -/* Note: the following macros evaluate the parameter v twice */ -#define TO_CPU_SHORT(v, be) \ - ((be) ? BE_SHORT(v) : LE_SHORT(v)) -#define TO_CPU_INT(v, be) \ - ((be) ? BE_INT(v) : LE_INT(v)) - -#define WAV_RIFF COMPOSE_ID('R','I','F','F') -#define WAV_RIFX COMPOSE_ID('R','I','F','X') -#define WAV_WAVE COMPOSE_ID('W','A','V','E') -#define WAV_FMT COMPOSE_ID('f','m','t',' ') -#define WAV_DATA COMPOSE_ID('d','a','t','a') - -/* WAVE fmt block constants from Microsoft mmreg.h header */ -#define WAV_FMT_PCM 0x0001 -#define WAV_FMT_IEEE_FLOAT 0x0003 -#define WAV_FMT_DOLBY_AC3_SPDIF 0x0092 -#define WAV_FMT_EXTENSIBLE 0xfffe - -/* Used with WAV_FMT_EXTENSIBLE format */ -#define WAV_GUID_TAG "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71" - -/* it's in chunks like .voc and AMIGA iff, but my source say there - are in only in this combination, so I combined them in one header; - it works on all WAVE-file I have - */ -typedef struct { - u_int magic; /* 'RIFF' */ - u_int length; /* filelen */ - u_int type; /* 'WAVE' */ -} WaveHeader; - -typedef struct { - u_short format; /* see WAV_FMT_* */ - u_short channels; - u_int sample_fq; /* frequence of sample */ - u_int byte_p_sec; - u_short byte_p_spl; /* samplesize; 1 or 2 bytes */ - u_short bit_p_spl; /* 8, 12 or 16 bit */ -} WaveFmtBody; - -typedef struct { - WaveFmtBody format; - u_short ext_size; - u_short bit_p_spl; - u_int channel_mask; - u_short guid_format; /* WAV_FMT_* */ - u_char guid_tag[14]; /* WAV_GUID_TAG */ -} WaveFmtExtensibleBody; - -typedef struct { - u_int type; /* 'data' */ - u_int length; /* samplecount */ -} WaveChunkHeader; - -/* Definitions for Sparc .au header */ - -#define AU_MAGIC COMPOSE_ID('.','s','n','d') - -#define AU_FMT_ULAW 1 -#define AU_FMT_LIN8 2 -#define AU_FMT_LIN16 3 - -typedef struct au_header { - u_int magic; /* '.snd' */ - u_int hdr_size; /* size of header (min 24) */ - u_int data_size; /* size of data */ - u_int encoding; /* see to AU_FMT_XXXX */ - u_int sample_rate; /* sample rate */ - u_int channels; /* number of channels (voices) */ -} AuHeader; - -#endif /* FORMATS */ diff --git a/aplay/main.c b/aplay/main.c new file mode 100644 index 0000000..547cf2c --- /dev/null +++ b/aplay/main.c @@ -0,0 +1,178 @@ +/* + * main.c - an entry point for this program. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Originally, written by Michael Beck and Jaroslav Kysela. + * Copyright (c) by Jaroslav Kysela perex@perex.cz + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "main.h" +#include "version.h" + +#include <stdbool.h> + +static void print_version(const char *const cmdname) +{ + printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR); +} + +static void print_help(void) +{ + printf("help\n"); +} + +static void decide_subcmd(int argc, char *const *argv, enum subcmds *subcmd) +{ + static const char *const subcmds[] = { + [SUBCMD_TRANSFER] = "transfer", + [SUBCMD_LIST] = "list", + [SUBCMD_HELP] = "help", + [SUBCMD_VERSION] = "version", + }; + static const struct { + const char *const name; + enum subcmds subcmd; + } long_opts[] = { + {"--list-devices", SUBCMD_LIST}, + {"--list-pcms", SUBCMD_LIST}, + {"--help", SUBCMD_HELP}, + {"--version", SUBCMD_VERSION}, + }; + static const struct { + unsigned char c; + enum subcmds subcmd; + } short_opts[] = { + {'l', SUBCMD_LIST}, + {'L', SUBCMD_LIST}, + {'h', SUBCMD_HELP}, + }; + char *pos; + int i, j; + + /* sub-command system. */ + for (i = 0; i < ARRAY_SIZE(subcmds); ++i) { + if (!strcmp(argv[1], subcmds[i])) { + *subcmd = i; + return; + } + } + + /* Original command system. For long options. */ + for (i = 0; i < ARRAY_SIZE(long_opts); ++i) { + for (j = 0; j < argc; ++j) { + if (!strcmp(long_opts[i].name, argv[j])) { + *subcmd = long_opts[i].subcmd; + return; + } + } + } + + /* Original command system. For short options. */ + for (i = 1; i < argc; ++i) { + /* Pick up short options only. */ + if (argv[i][0] != '-' || argv[i][0] == '\0' || + argv[i][1] == '-' || argv[i][1] == '\0') + continue; + for (pos = argv[i]; *pos != '\0'; ++pos) { + for (j = 0; j < ARRAY_SIZE(short_opts); ++j) { + if (*pos == short_opts[j].c) { + *subcmd = short_opts[j].subcmd; + return; + } + } + } + } + + *subcmd = SUBCMD_TRANSFER; +} + +static bool decide_direction(int argc, char *const *argv, + snd_pcm_stream_t *direction) +{ + static const struct { + const char *const name; + snd_pcm_stream_t direction; + } long_opts[] = { + {"--capture", SND_PCM_STREAM_CAPTURE}, + {"--playback", SND_PCM_STREAM_PLAYBACK}, + }; + static const struct { + unsigned char c; + snd_pcm_stream_t direction; + } short_opts[] = { + {'C', SND_PCM_STREAM_CAPTURE}, + {'P', SND_PCM_STREAM_PLAYBACK}, + }; + static const char *const directions[] = { + [SND_PCM_STREAM_CAPTURE] = "arecord", + [SND_PCM_STREAM_PLAYBACK] = "aplay", + }; + int i, j; + char *pos; + + /* Original command system. For long options. */ + for (i = 0; i < ARRAY_SIZE(long_opts); ++i) { + for (j = 0; j < argc; ++j) { + if (!strcmp(long_opts[i].name, argv[j])) { + *direction = long_opts[i].direction; + return true; + } + } + } + + /* Original command system. For short options. */ + for (i = 1; i < argc; ++i) { + /* Pick up short options only. */ + if (argv[i][0] != '-' || argv[i][0] == '\0' || + argv[i][1] == '-' || argv[i][1] == '\0') + continue; + for (pos = argv[i]; *pos != '\0'; ++pos) { + for (j = 0; j < ARRAY_SIZE(short_opts); ++j) { + if (*pos == short_opts[j].c) { + *direction = short_opts[j].direction; + return true; + } + } + } + } + + /* If not decided yet, judge according to command name. */ + for (i = 0; i < ARRAY_SIZE(directions); ++i) { + for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; ++pos) { + if (strstr(pos, directions[i]) != NULL) { + *direction = i; + return true; + } + } + } + + return false; +} + +int main(int argc, char *const *argv) +{ + snd_pcm_stream_t direction; + enum subcmds subcmd; + int err = 0; + + if (!decide_direction(argc, argv, &direction)) + subcmd = SUBCMD_HELP; + else + decide_subcmd(argc, argv, &subcmd); + + if (subcmd == SUBCMD_TRANSFER) + err = subcmd_transfer(argc, argv, direction); + else if (subcmd == SUBCMD_LIST) + err = subcmd_list(argc, argv, direction); + else if (subcmd == SUBCMD_VERSION) + print_version(argv[0]); + else + print_help(); + if (err < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/aplay/subcmd-transfer.c b/aplay/subcmd-transfer.c index f163b05..9138558 100644 --- a/aplay/subcmd-transfer.c +++ b/aplay/subcmd-transfer.c @@ -7,7 +7,6 @@ */
#include "xfer.h" -#include "key-event.h"
#include <signal.h> #include <gettext.h> @@ -19,7 +18,6 @@ struct context { unsigned int cntr_count;
struct context_options opts; - struct keyevent_context key;
/* NOTE: To handling Unix signal. */ bool interrupted; @@ -197,8 +195,7 @@ static int capture_context_pre_process(struct context *ctx,
err = xfer_context_pre_process(&ctx->xfer, &sample_format, &samples_per_frame, &frames_per_second, - &access, &frames_per_buffer, - ctx->opts.chmap, ctx->opts.vu_mode); + &access, &frames_per_buffer); if (err < 0) return err;
@@ -269,8 +266,7 @@ static int playback_context_pre_process(struct context *ctx, /* Configure hardware with these parameters. */ err = xfer_context_pre_process(&ctx->xfer, &sample_format, &samples_per_frame, &frames_per_second, - &access, &frames_per_buffer, - ctx->opts.chmap, ctx->opts.vu_mode); + &access, &frames_per_buffer); if (err < 0) return err;
@@ -292,7 +288,6 @@ static int context_process_frames(struct context *ctx, { bool verbose = ctx->opts.verbose; unsigned int frame_count; - bool emitted; int i; int err;
diff --git a/configure.ac b/configure.ac index 0ff4fad..d202d70 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT(alsa-utils, 1.1.4) -AC_CONFIG_SRCDIR([aplay/aplay.c]) +AC_CONFIG_SRCDIR([aplay/main.c]) AC_PREFIX_DEFAULT(/usr) AM_INIT_AUTOMAKE
Current implementation of aplay has volume unit (vu) meter, by an option '--vumeter' (-V). When 'm' or 's' is given for the option, used terminal displays monaural or stereo meter via stderr.
This commit adds support for the similar function in different shape. Unfortunately, current implementation just supports meters up to 2 channel, as original implementation supports. If supporting more channels for multi-channel devices, temrinal control code could be used. --- aplay/Makefile.am | 7 +- aplay/options.c | 40 ++++++- aplay/options.h | 3 + aplay/vumeter.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++ aplay/vumeter.h | 50 ++++++++ aplay/xfer-alsa-io-mmap.c | 3 +- aplay/xfer-alsa-io-rw.c | 6 +- aplay/xfer-alsa.c | 8 +- aplay/xfer-alsa.h | 3 +- aplay/xfer.c | 25 +++- aplay/xfer.h | 5 +- 11 files changed, 436 insertions(+), 13 deletions(-) create mode 100644 aplay/vumeter.c create mode 100644 aplay/vumeter.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 5536c00..a0d498c 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -12,7 +12,8 @@ noinst_HEADERS = \ options.h \ xfer.h \ xfer-alsa.h \ - main.h + main.h \ + vumeter.h
aplay_SOURCES = \ container.h \ @@ -41,7 +42,9 @@ aplay_SOURCES = \ main.h \ subcmd-list.c \ subcmd-transfer.c \ - main.c + main.c \ + vumeter.h \ + vumeter.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/options.c b/aplay/options.c index efe0ce0..c16269d 100644 --- a/aplay/options.c +++ b/aplay/options.c @@ -115,6 +115,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; + } + } + + printf("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) @@ -174,7 +198,8 @@ static int apply_policies(struct context_options *opts, snd_pcm_stream_t direction, const char *cntr_format_literal, const char *node_literal, - const char *sample_format_literal) + const char *sample_format_literal, + const char *vu_mode_literal) { int err;
@@ -256,6 +281,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;
@@ -285,7 +316,7 @@ static int apply_policies(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 = "hqd:vt:D:c:f:r:MNF:A:R:T:B:I"; + static const char *s_opts = "hqid:vt:D:c:f:r:MNF:A:R:T:B:IV:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -321,11 +352,13 @@ int context_options_init(struct context_options *opts, int argc, {"test-nowait", 0, 0, OPT_TEST_NOWAIT}, {"dump-hw-params", 0, 0, OPT_DUMP_HWPARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, + {"vumeter", 1, 0, 'V'}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; const char *node_literal = NULL; const char *sample_format_literal = NULL; + const char *vu_mode_literal = NULL; int c; int err = 0;
@@ -407,7 +440,8 @@ int context_options_init(struct context_options *opts, int argc, return err;
return apply_policies(opts, direction, cntr_format_literal, - node_literal, sample_format_literal); + node_literal, sample_format_literal, + vu_mode_literal); }
/* diff --git a/aplay/options.h b/aplay/options.h index dc474d5..2536275 100644 --- a/aplay/options.h +++ b/aplay/options.h @@ -10,6 +10,7 @@ #define __ALSA_UTILS_APLAY_OPTIONS__H_
#include "container.h" +#include "vumeter.h"
struct context_options { bool help; @@ -51,6 +52,8 @@ struct context_options { bool dump_hw_params; bool fatal_errors;
+ enum vumeter_mode vu_mode; + char **paths; unsigned int path_count; }; diff --git a/aplay/vumeter.c b/aplay/vumeter.c new file mode 100644 index 0000000..da39261 --- /dev/null +++ b/aplay/vumeter.c @@ -0,0 +1,299 @@ +/* + * vumeter.c - Volume Unit meter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "vumeter.h" + +#include <stdio.h> +#include <string.h> +#include <endian.h> +#include <gettext.h> + +static void print_vu_meter_mono(struct vumeter_context *vu, int *perc) +{ + const int *maxperc = vu->maxperc; + const int bar_length = 50; + char line[80]; + int val; + + for (val = 0; val <= *perc * bar_length / 100 && val < bar_length; val++) + line[val] = '#'; + for (; val <= *maxperc * bar_length / 100 && val < bar_length; val++) + line[val] = ' '; + line[val] = '+'; + for (++val; val <= bar_length; val++) + line[val] = ' '; + if (*maxperc > 99) + sprintf(line + val, "| MAX"); + else + sprintf(line + val, "| %02i%%", *maxperc); + fputs(line, stderr); + if (*perc > 100) + fprintf(stderr, _(" !clip ")); +} + +static void print_vu_meter_stereo(struct vumeter_context *vu, int *perc) +{ + const int bar_length = 35; + const int *maxperc = vu->maxperc; + char line[80]; + int c; + + memset(line, ' ', sizeof(line) - 1); + line[bar_length + 3] = '|'; + + for (c = 0; c < 2; c++) { + int p = perc[c] * bar_length / 100; + char tmp[4]; + if (p > bar_length) + p = bar_length; + if (c) + memset(line + bar_length + 6 + 1, '#', p); + else + memset(line + bar_length - p - 1, '#', p); + p = maxperc[c] * bar_length / 100; + if (p > bar_length) + p = bar_length; + if (c) + line[bar_length + 6 + 1 + p] = '+'; + else + line[bar_length - p - 1] = '+'; + if (maxperc[c] > 99) + sprintf(tmp, "MAX"); + else + sprintf(tmp, "%02d%%", maxperc[c]); + if (c) + memcpy(line + bar_length + 3 + 1, tmp, 3); + else + memcpy(line + bar_length, tmp, 3); + } + line[bar_length * 2 + 6 + 2] = 0; + fputs(line, stderr); +} + +static void calculate_8bit_sample(struct vumeter_context *vu, + void *frame_buffer, + snd_pcm_uframes_t frame_count, + int max_peak[2]) +{ + enum vumeter_mode mode = vu->mode; + char *valp = frame_buffer; + char mask = vu->mask; + int val; + int c; + + c = 0; + while (frame_count-- > 0) { + val = *valp++ ^ mask; + val = abs(val); + if (max_peak[c] < val) + max_peak[c] = val; + if (mode == VUMETER_MODE_STEREO) + c = !c; + } +} + +static void calculate_16bit_sample(struct vumeter_context *vu, + void *frame_buffer, + snd_pcm_uframes_t frame_count, + int max_peak[2]) +{ + enum vumeter_mode mode = vu->mode; + signed short *valp = frame_buffer; + signed short mask = vu->mask; + signed short sval; + bool le = vu->le; + int c; + + frame_count /= 2; + c = 0; + while (frame_count-- > 0) { + if (le) + sval = le16toh(*valp); + else + sval = be16toh(*valp); + sval = abs(sval) ^ mask; + if (max_peak[c] < sval) + max_peak[c] = sval; + valp++; + if (mode == VUMETER_MODE_STEREO) + c = !c; + } +} + +static void calculate_24bit_sample(struct vumeter_context *vu, + void *frame_buffer, + snd_pcm_uframes_t frame_count, + int max_peak[2]) +{ + enum vumeter_mode mode = vu->mode; + char *valp = frame_buffer; + int mask = vu->mask; + bool le = vu->le; + int bits_per_sample = vu->bits_per_sample; + int val; + int c; + + frame_count /= 3; + c = 0; + while (frame_count-- > 0) { + if (le) { + val = valp[0] | + (valp[1] << 8) | + (valp[2] << 16); + } else { + val = (valp[0] << 16) | + (valp[1] << 8) | + valp[2]; + } + /* Correct signed bit in 32-bit value */ + if (val & (1 << (bits_per_sample - 1))) { + /* Negate upper bits too */ + val |= 0xff << 24; + } + val = abs(val) ^ mask; + if (max_peak[c] < val) + max_peak[c] = val; + valp += 3; + if (mode == VUMETER_MODE_STEREO) + c = !c; + } +} + +static void calculate_32bit_sample(struct vumeter_context *vu, + void *frame_buffer, + snd_pcm_uframes_t frame_count, + int max_peak[2]) +{ + enum vumeter_mode mode = vu->mode; + signed int *valp = (signed int *)frame_buffer; + signed int mask = vu->mask; + bool le = vu->le; + int val; + int c; + + frame_count /= 4; + c = 0; + while (frame_count-- > 0) { + if (le) + val = le32toh(*valp); + else + val = be32toh(*valp); + val = abs(val) ^ mask; + if (max_peak[c] < val) + max_peak[c] = val; + valp++; + if (mode == VUMETER_MODE_STEREO) + c = !c; + } +} + +/* peak handler */ +void vumeter_context_print(struct vumeter_context *vu, void *frame_buffer, + snd_pcm_uframes_t frame_count) +{ + signed int val, max, perc[2], max_peak[2]; + int ichans, c; + + memset(max_peak, 0, sizeof(max_peak)); + vu->calculator(vu, frame_buffer, frame_count, max_peak); + + if (vu->mode == VUMETER_MODE_STEREO) + ichans = 2; + else + ichans = 1; + + max = 1 << (vu->significant_bits_per_sample - 1); + if (max <= 0) + max = 0x7fffffff; + + for (c = 0; c < ichans; ++c) { + if (vu->bits_per_sample > 16) + perc[c] = max_peak[c] / (max / 100); + else + perc[c] = max_peak[c] * 100 / max; + } + + if (vu->interleaved && vu->verbose <= 2) { + const time_t now = time(NULL); + if(now > vu->prev) { + vu->prev = now; + vu->maxperc[0] = 0; + vu->maxperc[1] = 0; + } + for (c = 0; c < ichans; c++) + if (perc[c] > vu->maxperc[c]) + vu->maxperc[c] = perc[c]; + + putc('\r', stderr); + + if (vu->mode == VUMETER_MODE_STEREO) + print_vu_meter_stereo(vu, perc); + else + print_vu_meter_mono(vu, perc); + + fflush(stderr); + } else if (vu->verbose == 3) { + fprintf(stderr, + _("Max peak (%li samples): 0x%08x "), + frame_count, max_peak[0]); + for (val = 0; val < 20; val++) + if (val <= perc[0] / 5) + putc('#', stderr); + else + putc(' ', stderr); + fprintf(stderr, " %i%%\n", perc[0]); + fflush(stderr); + } +} + +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 verbose) +{ + vu->verbose = verbose; + + if ((access == SND_PCM_ACCESS_MMAP_INTERLEAVED) || + (access == SND_PCM_ACCESS_RW_INTERLEAVED)) + vu->interleaved = true; + else if ((access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) || + (access == SND_PCM_ACCESS_RW_NONINTERLEAVED)) + vu->interleaved = false; + else + return -EINVAL; + + vu->le = !!snd_pcm_format_little_endian(format); + vu->significant_bits_per_sample = snd_pcm_format_width(format); + + vu->bits_per_sample = snd_pcm_format_physical_width(format); + if (vu->bits_per_sample == 8) + vu->calculator = calculate_8bit_sample; + else if (vu->bits_per_sample == 16) + vu->calculator = calculate_16bit_sample; + else if (vu->bits_per_sample == 24) + vu->calculator = calculate_24bit_sample; + else if (vu->bits_per_sample == 32) + vu->calculator = calculate_32bit_sample; + else + return -EINVAL; + vu->mask = snd_pcm_format_silence_64(format); + + vu->samples_per_frame = samples_per_frame; + + if (mode == VUMETER_MODE_MONO) + vu->printer = print_vu_meter_mono; + else + vu->printer = print_vu_meter_stereo; + vu->mode = mode; + + return 0; +} + +void vumeter_context_destroy(struct vumeter_context *vu) +{ + return; +} diff --git a/aplay/vumeter.h b/aplay/vumeter.h new file mode 100644 index 0000000..d2fa8a4 --- /dev/null +++ b/aplay/vumeter.h @@ -0,0 +1,50 @@ +/* + * vumeter.h - Volume Unit meter. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef __ALSA_UTILS_APLAY_VUMETER__H_ +#define __ALSA_UTILS_APLAY_VUMETER__H_ + +#include <stddef.h> +#include <stdbool.h> +#include <stdint.h> +#include <time.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; + unsigned int verbose; + + bool le; + bool interleaved; + int significant_bits_per_sample; + int bits_per_sample; + unsigned int samples_per_frame; + + uint64_t mask; + int maxperc[2]; + time_t prev; + + void (*printer)(struct vumeter_context *vu, int *perc); + void (*calculator)(struct vumeter_context *vu, void *data, + snd_pcm_uframes_t frame_count, int max_peaks[2]); +}; + +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 verbose); +void vumeter_context_print(struct vumeter_context *vu, void *data, + snd_pcm_uframes_t frame_count); +void vumeter_context_destroy(struct vumeter_context *vu); + +#endif diff --git a/aplay/xfer-alsa-io-mmap.c b/aplay/xfer-alsa-io-mmap.c index 7ddb880..830d2f8 100644 --- a/aplay/xfer-alsa-io-mmap.c +++ b/aplay/xfer-alsa-io-mmap.c @@ -44,7 +44,8 @@ static int alsa_mmap_pre_process(struct alsa_state *state) static int alsa_mmap_process_frames(struct alsa_state *state, unsigned *frame_count, struct aligner_context *aligner, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t frame_offset; diff --git a/aplay/xfer-alsa-io-rw.c b/aplay/xfer-alsa-io-rw.c index a69d7e0..513ff91 100644 --- a/aplay/xfer-alsa-io-rw.c +++ b/aplay/xfer-alsa-io-rw.c @@ -146,7 +146,8 @@ static int alsa_rw_pre_process(struct alsa_state *state) static int alsa_r_process_frames(struct alsa_state *state, unsigned int *frame_count, struct aligner_context *aligner, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct frame_cache *cache = state->io_private_data; snd_pcm_sframes_t avail; @@ -240,7 +241,8 @@ error: static int alsa_w_process_frames(struct alsa_state *state, unsigned *frame_count, struct aligner_context *aligner, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct frame_cache *cache = state->io_private_data; snd_pcm_sframes_t avail; diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c index 05e6e7e..2092f3e 100644 --- a/aplay/xfer-alsa.c +++ b/aplay/xfer-alsa.c @@ -443,11 +443,15 @@ static int xfer_alsa_pre_process(struct xfer_context *xfer, static int xfer_alsa_process_frames(struct xfer_context *xfer, unsigned int *frame_count, struct aligner_context *aligner, - struct container_context *cntrs) + struct container_context *cntrs, + struct vumeter_context *vu) { struct alsa_state *state = xfer->private_data; int err; - err = state->io_ops->process_frames(state, frame_count, aligner, cntrs); + + /* TODO: vumeter in each backend. */ + err = state->io_ops->process_frames(state, frame_count, aligner, cntrs, + vu); if (err < 0) { if (err == -EPIPE) err = snd_pcm_prepare(state->handle); diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h index 9cc079d..8702685 100644 --- a/aplay/xfer-alsa.h +++ b/aplay/xfer-alsa.h @@ -45,7 +45,8 @@ struct xfer_alsa_io_ops { int (*process_frames)(struct alsa_state *state, unsigned int *frame_count, struct aligner_context *aligner, - struct container_context *cntrs); + struct container_context *cntrs, + struct vumeter_context *vu); void (*post_process)(struct alsa_state *state); unsigned int private_size; }; diff --git a/aplay/xfer.c b/aplay/xfer.c index 977f00a..02ed0a6 100644 --- a/aplay/xfer.c +++ b/aplay/xfer.c @@ -41,6 +41,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); }
@@ -67,6 +75,14 @@ int xfer_context_pre_process(struct xfer_context *xfer, if (err < 0) return err;
+ if (xfer->vu) { + err = vumeter_context_init(xfer->vu, xfer->vu->mode, *access, + *format, *samples_per_frame, + xfer->verbose); + if (err < 0) + return err; + } + if (xfer->verbose > 0) { printf("Transfer: %s\n", xfer_type_labels[xfer->type]); printf(" access: %s\n", snd_pcm_access_name(*access)); @@ -87,7 +103,8 @@ int xfer_context_process_frames(struct xfer_context *xfer, struct container_context *cntrs, unsigned int *frame_count) { - return xfer->ops->process_frames(xfer, frame_count, aligner, cntrs); + return xfer->ops->process_frames(xfer, frame_count, aligner, cntrs, + xfer->vu); }
void xfer_context_pause(struct xfer_context *xfer, bool enable) @@ -98,4 +115,10 @@ void xfer_context_pause(struct xfer_context *xfer, bool enable) void xfer_context_post_process(struct xfer_context *xfer) { xfer->ops->post_process(xfer); + + if (xfer->vu) { + vumeter_context_destroy(xfer->vu); + free(xfer->vu); + xfer->vu = NULL; + } } diff --git a/aplay/xfer.h b/aplay/xfer.h index 749fa6c..8a18712 100644 --- a/aplay/xfer.h +++ b/aplay/xfer.h @@ -23,6 +23,8 @@ struct xfer_context { const struct xfer_ops *ops; void *private_data;
+ struct vumeter_context *vu; + unsigned int verbose; };
@@ -55,7 +57,8 @@ struct xfer_ops { int (*process_frames)(struct xfer_context *xfer, unsigned int *frame_count, struct aligner_context *aligner, - 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);
Current implementation of aplay uses channel map API, by an option '--chmap' (-m). This API allows applications to get/set channel position by getting position array; e.g. 'FR,FL'.
However, current implementation of ALSA PCM cure disallows applications to set it as they prefer. This commit adds partly support for this API. --- aplay/options.c | 28 +++++++++++++++++++++++--- aplay/options.h | 1 + aplay/xfer-alsa.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- aplay/xfer-alsa.h | 2 ++ 4 files changed, 86 insertions(+), 5 deletions(-)
diff --git a/aplay/options.c b/aplay/options.c index c16269d..7663703 100644 --- a/aplay/options.c +++ b/aplay/options.c @@ -199,7 +199,8 @@ static int apply_policies(struct context_options *opts, const char *cntr_format_literal, const char *node_literal, const char *sample_format_literal, - const char *vu_mode_literal) + const char *vu_mode_literal, + const char *chmap_literal) { int err;
@@ -232,6 +233,20 @@ static int apply_policies(struct context_options *opts, return err; }
+ if (chmap_literal) { + snd_pcm_chmap_t *chmap; + chmap = snd_pcm_chmap_parse_string(chmap_literal); + if (chmap == NULL) { + printf(_("Unable to parse channel map string: '%s'\n"), + chmap_literal); + return -EINVAL; + } + free(chmap); + opts->chmap_literal = strdup(chmap_literal); + if (opts->chmap_literal == NULL) + return -ENOMEM; + } + if (opts->samples_per_frame > 0) { if (opts->samples_per_frame < 1 || opts->samples_per_frame > 256) { @@ -316,7 +331,7 @@ static int apply_policies(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 = "hqid:vt:D:c:f:r:MNF:A:R:T:B:IV:"; + static const char *s_opts = "hqid:vt:D:c:f:r:MNF:A:R:T:B:IV:m:"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -353,12 +368,14 @@ int context_options_init(struct context_options *opts, int argc, {"dump-hw-params", 0, 0, OPT_DUMP_HWPARAMS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"vumeter", 1, 0, 'V'}, + {"chmap", 1, 0, 'm'}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; const char *node_literal = NULL; const char *sample_format_literal = NULL; const char *vu_mode_literal = NULL; + const char *chmap_literal = NULL; int c; int err = 0;
@@ -428,6 +445,8 @@ int context_options_init(struct context_options *opts, int argc, opts->dump_hw_params = true; else if (c == OPT_FATAL_ERRORS) opts->fatal_errors = true; + else if (c == 'm') + chmap_literal = optarg; else continue;
@@ -441,7 +460,7 @@ int context_options_init(struct context_options *opts, int argc,
return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, - vu_mode_literal); + vu_mode_literal, chmap_literal); }
/* @@ -614,6 +633,9 @@ void context_options_destroy(struct context_options *opts) } if (opts->node) free(opts->node); + if (opts->chmap_literal) + free(opts->chmap_literal); opts->paths = NULL; opts->node = NULL; + opts->chmap_literal = NULL; } diff --git a/aplay/options.h b/aplay/options.h index 2536275..3bece52 100644 --- a/aplay/options.h +++ b/aplay/options.h @@ -53,6 +53,7 @@ struct context_options { bool fatal_errors;
enum vumeter_mode vu_mode; + char *chmap_literal;
char **paths; unsigned int path_count; diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c index 2092f3e..e939005 100644 --- a/aplay/xfer-alsa.c +++ b/aplay/xfer-alsa.c @@ -53,7 +53,18 @@ static int set_access_hw_param(snd_pcm_t *handle, return err; }
-static void dump_available_hw_params(snd_pcm_hw_params_t *hw_params, +static void dump_chmap(const snd_pcm_chmap_t *chmap) +{ + int i; + + printf(" channels: %u\n", chmap->channels); + for (i = 0; i < chmap->channels; ++i) + printf(" ch%u: %s\n", + i, snd_pcm_chmap_name(chmap->pos[i])); +} + +static void dump_available_hw_params(snd_pcm_t *handle, + snd_pcm_hw_params_t *hw_params, const char *const node) { unsigned int min_i, max_i; @@ -61,6 +72,7 @@ static void dump_available_hw_params(snd_pcm_hw_params_t *hw_params, snd_pcm_access_mask_t *access_mask; snd_pcm_format_mask_t *format_mask; snd_pcm_subformat_mask_t *subformat_mask; + snd_pcm_chmap_query_t **maps; int i; int err;
@@ -158,6 +170,19 @@ static void dump_available_hw_params(snd_pcm_hw_params_t *hw_params, continue; printf(" '%s'\n", snd_pcm_subformat_name(i)); } + + maps = snd_pcm_query_chmaps(handle); + if (maps == NULL) + return; + + printf(" available channel maps:\n"); + for (i = 0; maps[i] != NULL; ++i) { + printf(" %u: %s\n", i, + snd_pcm_chmap_type_name(maps[i]->type)); + dump_chmap(&maps[i]->map); + } + snd_pcm_free_chmaps(maps); + printf("\n"); }
@@ -204,8 +229,19 @@ static int xfer_alsa_init(struct xfer_context *xfer, if (err < 0) return err;
+ if (opts->chmap_literal != NULL) { + state->chmap = snd_pcm_chmap_parse_string(opts->chmap_literal); + if (state->chmap == NULL) + return -ENOMEM; + + if (xfer->verbose) { + printf("Chmap argument:\n"); + dump_chmap(state->chmap); + } + } + if (xfer->verbose) - dump_available_hw_params(state->hw_params, node); + dump_available_hw_params(state->handle, state->hw_params, node);
return set_access_hw_param(state->handle, state->hw_params, opts); } @@ -262,6 +298,14 @@ static int configure_requested_params(struct alsa_state *state, }
if (samples_per_frame > 0) { + if (state->chmap) { + if (samples_per_frame != state->chmap->channels) { + printf(_("Mismatch between channel number and " + "given map: %u %u\n"), + samples_per_frame, state->chmap->channels); + } + } + err = snd_pcm_hw_params_set_channels(state->handle, state->hw_params, samples_per_frame); @@ -319,6 +363,7 @@ static int retrieve_actual_params(snd_pcm_hw_params_t *hw_params, static void dump_sw_params(struct alsa_state *state) { snd_pcm_uframes_t val_l; + snd_pcm_chmap_t *chmap; int val_i; int err;
@@ -354,6 +399,13 @@ static void dump_sw_params(struct alsa_state *state) return; printf(" silence-size: %lu\n", val_l);
+ chmap = snd_pcm_get_chmap(state->handle); + if (chmap != NULL) { + printf(" current-chmap:\n"); + dump_chmap(chmap); + free(chmap); + } + printf("\n"); }
@@ -539,6 +591,10 @@ static void xfer_alsa_destroy(struct xfer_context *xfer) snd_pcm_sw_params_free(state->sw_params); state->hw_params = NULL; state->sw_params = NULL; + + if (state->chmap) + free(state->chmap); + state->chmap = NULL; }
const struct xfer_data xfer_alsa = { diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h index 8702685..0257702 100644 --- a/aplay/xfer-alsa.h +++ b/aplay/xfer-alsa.h @@ -38,6 +38,8 @@ struct alsa_state { void *private_data;
bool verbose; + + snd_pcm_chmap_t *chmap; };
struct xfer_alsa_io_ops {
In current implementation of aplay, when an option '--interactive' (-i) is given, users can pause data transmission by pushing 'space' or 'enter' key. The key event is handled via control terminal.
This commit adds support for this feature with a different shape. --- aplay/Makefile.am | 7 ++- aplay/key-event.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ aplay/key-event.h | 21 +++++++++ aplay/options.c | 19 +++++++- aplay/options.h | 1 + aplay/subcmd-transfer.c | 22 +++++++++ 6 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 aplay/key-event.c create mode 100644 aplay/key-event.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index a0d498c..cffa4bc 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -13,7 +13,8 @@ noinst_HEADERS = \ xfer.h \ xfer-alsa.h \ main.h \ - vumeter.h + vumeter.h \ + key-event.h
aplay_SOURCES = \ container.h \ @@ -44,7 +45,9 @@ aplay_SOURCES = \ subcmd-transfer.c \ main.c \ vumeter.h \ - vumeter.c + vumeter.c \ + key-event.h \ + key-event.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/key-event.c b/aplay/key-event.c new file mode 100644 index 0000000..df36740 --- /dev/null +++ b/aplay/key-event.c @@ -0,0 +1,120 @@ +/* + * key-event.c - a handler for key events via stdin. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "key-event.h" + +#include <stdio.h> +#include <termios.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <gettext.h> + +int keyevent_context_init(struct keyevent_context *key) +{ + struct termios term; + int err; + + key->fd = fileno(stdin); + if (!isatty(key->fd)) { + printf(_("isatty(3): %s\n"), + strerror(errno)); + return -EINVAL; + } + + err = tcgetattr(key->fd, &term); + if (err < 0) { + printf(_("tcgetattr(3): %s\n"), + strerror(errno)); + return -errno; + } + key->c_lflag = term.c_lflag; + + term.c_lflag &= ~ICANON; + err = tcsetattr(key->fd, TCSANOW, &term); + if (err < 0) { + printf(_("tcsetattr(3): %s\n"), + strerror(errno)); + return -errno; + } + + return 0; +} + +static int set_nonblock_flag(int fd, bool nonblock) +{ + int err; + + err = fcntl(fd, F_GETFL); + if (err < 0) { + printf(_("fcntl(2) with F_GETFL: %s\n"), + strerror(errno)); + return -errno; + } + + if (nonblock) + err |= O_NONBLOCK; + else + err &= ~O_NONBLOCK; + err = fcntl(fd, F_SETFL, err); + if (err < 0) { + printf(_("fcntl(2) with F_SETFL: %s\n"), + strerror(errno)); + return -errno; + } + + return 0; +} + +int keyevent_context_check(struct keyevent_context *key, + struct xfer_context *xfer) +{ + char b; + ssize_t len; + bool paused = false; + int err; + + while (1) { + err = set_nonblock_flag(key->fd, !paused); + if (err < 0) + return err; + + len = read(key->fd, &b, sizeof(b)); + if (len == -EINTR) + continue; + if (len == -EAGAIN) + break; + if (len < 0) + return len; + if (len == 0) + break; + if (b == ' ' || b == '\r') { + xfer_context_pause(xfer, !paused); + if (paused) + break; + paused = !paused; + } + } + + return 0; +} + +void keyevent_context_destroy(struct keyevent_context *key) +{ + struct termios term; + int err; + + tcgetattr(key->fd, &term); + term.c_lflag = key->c_lflag; + err = tcsetattr(key->fd, TCSANOW, &term); + if (err < 0) { + printf(_("tcsetattr(3): %s\n"), + strerror(errno)); + } +} diff --git a/aplay/key-event.h b/aplay/key-event.h new file mode 100644 index 0000000..967e6d0 --- /dev/null +++ b/aplay/key-event.h @@ -0,0 +1,21 @@ +/* + * key-event.h - a header to a handler for key events via stdin. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" + +#include <stdbool.h> + +struct keyevent_context { + int fd; + long c_lflag; +}; + +int keyevent_context_init(struct keyevent_context *key); +int keyevent_context_check(struct keyevent_context *key, + struct xfer_context *xfer); +void keyevent_context_destroy(struct keyevent_context *key); diff --git a/aplay/options.c b/aplay/options.c index 7663703..005a7bf 100644 --- a/aplay/options.c +++ b/aplay/options.c @@ -315,6 +315,20 @@ static int apply_policies(struct context_options *opts, } }
+ if (opts->interactive) { + /* + * In interactive mode, users can suspend/resume PCM substream + * by keyboard input, thus stdin cannot used for source of + * PCM frames. + */ + if (direction == SND_PCM_STREAM_PLAYBACK && + !strcmp(opts->paths[0], "-")) { + printf(_("An option for interactive mode is not " + "available with stdin.\n")); + return -EINVAL; + } + } + opts->sample_format = SND_PCM_FORMAT_UNKNOWN; if (sample_format_literal) { err = verify_sample_format(opts, sample_format_literal); @@ -331,7 +345,7 @@ static int apply_policies(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 = "hqid:vt:D:c:f:r:MNF:A:R:T:B:IV:m:"; + static const char *s_opts = "hqd:vt:D:c:f:r:MNF:A:R:T:B:IV:m:i"; static const struct option l_opts[] = { /* For generic purposes. */ {"help", 0, 0, 'h'}, @@ -369,6 +383,7 @@ int context_options_init(struct context_options *opts, int argc, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"vumeter", 1, 0, 'V'}, {"chmap", 1, 0, 'm'}, + {"interactive", 0, 0, 'i'}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; @@ -447,6 +462,8 @@ int context_options_init(struct context_options *opts, int argc, opts->fatal_errors = true; else if (c == 'm') chmap_literal = optarg; + else if (c == 'i') + opts->interactive = true; else continue;
diff --git a/aplay/options.h b/aplay/options.h index 3bece52..48ba859 100644 --- a/aplay/options.h +++ b/aplay/options.h @@ -54,6 +54,7 @@ struct context_options {
enum vumeter_mode vu_mode; char *chmap_literal; + bool interactive;
char **paths; unsigned int path_count; diff --git a/aplay/subcmd-transfer.c b/aplay/subcmd-transfer.c index 9138558..814c90f 100644 --- a/aplay/subcmd-transfer.c +++ b/aplay/subcmd-transfer.c @@ -7,6 +7,7 @@ */
#include "xfer.h" +#include "key-event.h"
#include <signal.h> #include <gettext.h> @@ -288,9 +289,19 @@ static int context_process_frames(struct context *ctx, { bool verbose = ctx->opts.verbose; unsigned int frame_count; + struct keyevent_context *key = NULL; int i; int err;
+ if (ctx->opts.interactive) { + key = malloc(sizeof(*key)); + if (key == NULL) + return -ENOMEM; + err = keyevent_context_init(key); + if (err < 0) + return err; + } + *actual_frame_count = 0; while (!ctx->interrupted) { /* Tell remains to expected frame count. */ @@ -316,6 +327,17 @@ static int context_process_frames(struct context *ctx, *actual_frame_count += frame_count; if (*actual_frame_count >= expected_frame_count) break; + + if (key) { + err = keyevent_context_check(key, &ctx->xfer); + if (err < 0 && err != -EAGAIN) + break; + } + } + + if (key) { + keyevent_context_destroy(key); + free(key); }
if (verbose) {
In current implementation of aplay, during transferring data frames, the program generates a file with PID in given path for an option '--process-id-file'.
This commit adds a parser for this option. Actual code to generate the ile is not implemented yet. --- aplay/options.c | 20 ++++++++++++++++++-- aplay/options.h | 1 + 2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/aplay/options.c b/aplay/options.c index 005a7bf..c9d0a13 100644 --- a/aplay/options.c +++ b/aplay/options.c @@ -27,6 +27,7 @@ enum no_short_opts { OPT_TEST_NOWAIT, OPT_DUMP_HWPARAMS, OPT_FATAL_ERRORS, + OPT_PROCESS_ID_FILE, };
static long parse_l(const char *str, int *err) @@ -200,7 +201,8 @@ static int apply_policies(struct context_options *opts, const char *node_literal, const char *sample_format_literal, const char *vu_mode_literal, - const char *chmap_literal) + const char *chmap_literal, + const char *pidfile_name_literal) { int err;
@@ -247,6 +249,15 @@ static int apply_policies(struct context_options *opts, return -ENOMEM; }
+ if (pidfile_name_literal) { + opts->pidfile_name = strdup(pidfile_name_literal); + if (opts->pidfile_name == NULL) { + printf(_("Fail to allocate for pidfile name: %s\n"), + pidfile_name_literal); + return -ENOMEM; + } + } + if (opts->samples_per_frame > 0) { if (opts->samples_per_frame < 1 || opts->samples_per_frame > 256) { @@ -384,6 +395,7 @@ int context_options_init(struct context_options *opts, int argc, {"vumeter", 1, 0, 'V'}, {"chmap", 1, 0, 'm'}, {"interactive", 0, 0, 'i'}, + {"process-id-file", 1, 0, OPT_PROCESS_ID_FILE}, {NULL, 0, 0, 0}, }; const char *cntr_format_literal = NULL; @@ -391,6 +403,7 @@ int context_options_init(struct context_options *opts, int argc, const char *sample_format_literal = NULL; const char *vu_mode_literal = NULL; const char *chmap_literal = NULL; + const char *pidfile_name_literal = NULL; int c; int err = 0;
@@ -464,6 +477,8 @@ int context_options_init(struct context_options *opts, int argc, chmap_literal = optarg; else if (c == 'i') opts->interactive = true; + else if (c == OPT_PROCESS_ID_FILE) + pidfile_name_literal = optarg; else continue;
@@ -477,7 +492,8 @@ int context_options_init(struct context_options *opts, int argc,
return apply_policies(opts, direction, cntr_format_literal, node_literal, sample_format_literal, - vu_mode_literal, chmap_literal); + vu_mode_literal, chmap_literal, + pidfile_name_literal); }
/* diff --git a/aplay/options.h b/aplay/options.h index 48ba859..0189432 100644 --- a/aplay/options.h +++ b/aplay/options.h @@ -58,6 +58,7 @@ struct context_options {
char **paths; unsigned int path_count; + char *pidfile_name; };
int context_options_init(struct context_options *opts, int argc,
On Thu, 17 Aug 2017 13:59:41 +0200, Takashi Sakamoto wrote:
Hi,
I think all of you in this mailing list might have used the command 'aplay', at least once. This patchset is my attempt to rewrite it because current shape of code is not enough good. Especially, ALSA PCM interface have got extensions since aplay was firstly developed. One of my motivation is to refine the tool to test such extensions.
Below diagram illustrates new internal design of this program:
<-> container <-> (file)
(device) <-> (alsa-lib) <-> xfer <-> aligner <-> container <-> (file) <-> container <-> (file)
For description of each modules, please read patch comment.
This patchset is early preview for developers to see the above design, thus some features of original aplay are not still implemented. Especially, some options have no actual effect even if they're parsed. Presently, I see below command lines run as I expected:
$ ./aplay -C (-v) (-M)(-N) -D [hw:0,0|default] record.wav $ ./aplay -P (-v) (-M)(-N) -D [hw:0,0|default] playback.wav $ ./aplay --list-devices $ ./aplay --list-pcm $ ./aplay --version
Here, I work with Intel HDA (hw:0,0) and pulse PCM plugin (default).
It's my pleasure if received some comments from you.
Thanks for the effort, the resultant codes look neat. But, my concern is that the incomplete implementation and the lack of regression test thereof.
The most common tactics for the code refactoring is "divide and conquer": i.e. split the lengthy codes at first, and rewrite each of them. The merit by this way is that you can keep the functionality and check the regression test step-by-step.
Meanwhile the approach you're taking is rather a complete rewrite from the scratch, and it's error-prone in that regard. OTOH, a scratch build may result in a cleaner, better structured code, eventually.
So, I find it's OK to go in this way, but we can put this as a different command at first. Once when the code reaches to the 100% full compatibility with the existing aplay, we can obsolete the old code and provide a symlink to the new command for the compatibility.
In that way, we can merge the codes gradually without too much worry.
About the code: I still had little time to read through the whole codes, but though a very quick glance:
- Please put the good explanation as in the cover letter into the changelog. A documentation for the big picture is missing.
- Try to avoid inventing a new term; e.g. aligner is a unique word, and difficult to understand (whether it's for teeth or what else).
thanks,
Takashi
Takashi Sakamoto (23): aplay: add an abstraction of container to parse/build audio-specific data format aplay: add an implementation of container for Microsoft/IBM RIFF/Wave format aplay: add an implementation of container for Sparc AU format aplay: add an implementation of container for Creative Tech. voice format aplay: add an implementation of container for raw format aplay: aligner: add an abstraction to align buffers with different data aplay: add an implementation of aligner for single target aplay: add an implementation of aligner for multiple target aplay: add an abstruction of waiter for I/O event notification aplay: add an implementation of waiter for poll(2) aplay: add an implementation of waiter for epoll(7) aplay: options: add a parser for command-line options aplay: add an abstraction for transferring of PCM frames aplay: add an implementation for transferring by ALSA PCM APIs aplay: add implementation of I/O aplay: add implementations to scheduling aplay: add a sub-command to print list of PCMs/devices aplay: add a sub-command to transfer data frames aplay: obsolete main routine and introduce sub-command style aplay: add an implementation for volume unit meter aplay: add a parser for channel map API aplay: add a handler for key events aplay: add a feature to generate PID file
aplay/Makefile.am | 48 +- aplay/aligner-multiple.c | 337 ++++ aplay/aligner-single.c | 256 +++ aplay/aligner.c | 122 ++ aplay/aligner.h | 88 ++ aplay/aplay.c | 3425 ----------------------------------------- aplay/container-au.c | 203 +++ aplay/container-riff-wave.c | 549 +++++++ aplay/container-voc.c | 736 +++++++++ aplay/container.c | 375 +++++ aplay/container.h | 127 ++ aplay/formats.h | 134 -- aplay/key-event.c | 120 ++ aplay/key-event.h | 21 + aplay/main.c | 178 +++ aplay/main.h | 32 + aplay/options.c | 674 ++++++++ aplay/options.h | 71 + aplay/subcmd-list.c | 233 +++ aplay/subcmd-transfer.c | 431 ++++++ aplay/vumeter.c | 299 ++++ aplay/vumeter.h | 50 + aplay/waiter-epoll.c | 76 + aplay/waiter-poll.c | 66 + aplay/waiter.c | 61 + aplay/waiter.h | 59 + aplay/xfer-alsa-io-mmap.c | 135 ++ aplay/xfer-alsa-io-rw.c | 370 +++++ aplay/xfer-alsa-sched-irq.c | 18 + aplay/xfer-alsa-sched-timer.c | 49 + aplay/xfer-alsa.c | 610 ++++++++ aplay/xfer-alsa.h | 67 + aplay/xfer.c | 124 ++ aplay/xfer.h | 74 + configure.ac | 2 +- 35 files changed, 6655 insertions(+), 3565 deletions(-) create mode 100644 aplay/aligner-multiple.c create mode 100644 aplay/aligner-single.c create mode 100644 aplay/aligner.c create mode 100644 aplay/aligner.h delete mode 100644 aplay/aplay.c create mode 100644 aplay/container-au.c create mode 100644 aplay/container-riff-wave.c create mode 100644 aplay/container-voc.c create mode 100644 aplay/container.c create mode 100644 aplay/container.h delete mode 100644 aplay/formats.h create mode 100644 aplay/key-event.c create mode 100644 aplay/key-event.h create mode 100644 aplay/main.c create mode 100644 aplay/main.h create mode 100644 aplay/options.c create mode 100644 aplay/options.h create mode 100644 aplay/subcmd-list.c create mode 100644 aplay/subcmd-transfer.c create mode 100644 aplay/vumeter.c create mode 100644 aplay/vumeter.h create mode 100644 aplay/waiter-epoll.c create mode 100644 aplay/waiter-poll.c create mode 100644 aplay/waiter.c create mode 100644 aplay/waiter.h create mode 100644 aplay/xfer-alsa-io-mmap.c create mode 100644 aplay/xfer-alsa-io-rw.c create mode 100644 aplay/xfer-alsa-sched-irq.c create mode 100644 aplay/xfer-alsa-sched-timer.c create mode 100644 aplay/xfer-alsa.c create mode 100644 aplay/xfer-alsa.h create mode 100644 aplay/xfer.c create mode 100644 aplay/xfer.h
-- 2.11.0
Hi,
Thanks for your comment but sorry to be late.
On Aug 22 2017 15:40, Takashi Iwai wrote:
On Thu, 17 Aug 2017 13:59:41 +0200, Takashi Sakamoto wrote:
Hi,
I think all of you in this mailing list might have used the command 'aplay', at least once. This patchset is my attempt to rewrite it because current shape of code is not enough good. Especially, ALSA PCM interface have got extensions since aplay was firstly developed. One of my motivation is to refine the tool to test such extensions.
Below diagram illustrates new internal design of this program:
<-> container <-> (file)
(device) <-> (alsa-lib) <-> xfer <-> aligner <-> container <-> (file) <-> container <-> (file)
For description of each modules, please read patch comment.
This patchset is early preview for developers to see the above design, thus some features of original aplay are not still implemented. Especially, some options have no actual effect even if they're parsed. Presently, I see below command lines run as I expected:
$ ./aplay -C (-v) (-M)(-N) -D [hw:0,0|default] record.wav $ ./aplay -P (-v) (-M)(-N) -D [hw:0,0|default] playback.wav $ ./aplay --list-devices $ ./aplay --list-pcm $ ./aplay --version
Here, I work with Intel HDA (hw:0,0) and pulse PCM plugin (default).
It's my pleasure if received some comments from you.
Thanks for the effort, the resultant codes look neat. But, my concern is that the incomplete implementation and the lack of regression test thereof.
Yep. It's my concern, too. I have a plan to add some unit tests for each functionality which this patchset divides from original code, but it's not necessarily enough to check the regression.
The most common tactics for the code refactoring is "divide and conquer": i.e. split the lengthy codes at first, and rewrite each of them. The merit by this way is that you can keep the functionality and check the regression test step-by-step.
Meanwhile the approach you're taking is rather a complete rewrite from the scratch, and it's error-prone in that regard. OTOH, a scratch build may result in a cleaner, better structured code, eventually.
At first step of this work, I attempted to apply the usual method for code refactoring, however as you know current implementation of aplay has no structured shape and I guess that usual approach takes me a lot of time against what I'd like to do. Then I decided to rewrite it from the scratch.
So, I find it's OK to go in this way, but we can put this as a different command at first. Once when the code reaches to the 100% full compatibility with the existing aplay, we can obsolete the old code and provide a symlink to the new command for the compatibility.
In that way, we can merge the codes gradually without too much worry.
Hm. A negative point of your idea is to force us to maintain two similar programs for a short term. But it sounds reasonable to me for this case.
Name of the new command is... 'axfer' is good, I think.
About the code: I still had little time to read through the whole codes, but though a very quick glance:
- Please put the good explanation as in the cover letter into the changelog. A documentation for the big picture is missing.
OK. I'll try it in next time. But actually the sources is more verbose.
- Try to avoid inventing a new term; e.g. aligner is a unique word, and difficult to understand (whether it's for teeth or what else).
It comes from 'laser aligner' in lithographic technology, but actually an unfamiliar word and it's better to use another work; e.g. mapper.
Thanks
Takashi Sakamoto
participants (2)
-
Takashi Iwai
-
Takashi Sakamoto