diff --git a/.gitignore b/.gitignore index c5eb7e9..b5898e5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,10 @@ recordings/ # Vim swap files *.swp -# Misc +# Recordings *.wav +*.flac +*.mp3 # Temporary files tmp/ diff --git a/Makefile b/Makefile index 8668fd6..6314198 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,21 @@ CFLAGS = -std=c99 -O2 -Wall -Wextra PKGCONF = pkg-config ALSA_CFLAGS := $(shell $(PKGCONF) --cflags alsa 2>/dev/null) ALSA_LIBS := $(shell $(PKGCONF) --libs alsa 2>/dev/null) +FLAC_CFLAGS := $(shell $(PKGCONF) --cflags flac 2>/dev/null) +FLAC_LIBS := $(shell $(PKGCONF) --libs flac 2>/dev/null) +LAME_CFLAGS := $(shell $(PKGCONF) --cflags lame 2>/dev/null) +LAME_LIBS := $(shell $(PKGCONF) --libs lame 2>/dev/null) ifeq ($(strip $(ALSA_LIBS)),) ALSA_LIBS := -lasound endif -LDLIBS = $(ALSA_LIBS) -lm +ifeq ($(strip $(FLAC_LIBS)),) +FLAC_LIBS := -lFLAC +endif +ifeq ($(strip $(LAME_LIBS)),) +LAME_LIBS := -lmp3lame +endif +CPPFLAGS += $(ALSA_CFLAGS) $(FLAC_CFLAGS) $(LAME_CFLAGS) +LDLIBS = $(ALSA_LIBS) $(FLAC_LIBS) $(LAME_LIBS) -lm PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin @@ -17,7 +28,7 @@ SRC = tuxrecord.c all: $(BINARY) $(BINARY): $(SRC) - $(CC) $(CFLAGS) $(ALSA_CFLAGS) -o $@ $< $(LDLIBS) + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(LDLIBS) install: $(BINARY) install -d $(DESTDIR)$(BINDIR) diff --git a/README.md b/README.md index 118c4b9..f027b31 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # tuxrecord -Record audio to a WAV file on **Linux** using **ALSA** as the recording source. +Record audio to WAV, FLAC or MP3 files on **Linux** using **ALSA** as the +recording source. `tuxrecord` is the Linux port of the former macOS-only **macrecord** tool. The Swift/AVFoundation code was rewritten in **C99** and now uses the native ALSA -capture API (`libasound`); it builds with **GCC**. +capture API (`libasound`); it builds with **GCC**. WAV output is written +directly, while FLAC and MP3 are encoded in real time through the system +`libFLAC` and `libmp3lame` libraries, so no intermediate WAV file is written. macrecord was created by my good friend Armin. You can find his code here: [https://codeberg.org/armin/macrecord](https://codeberg.org/armin/macrecord) @@ -13,6 +16,11 @@ macrecord was created by my good friend Armin. You can find his code here: - GCC and GNU make - ALSA development headers (`libasound2-dev` on Debian/Ubuntu, `alsa-lib-devel` on Fedora, `alsa-lib` on Arch) +- FLAC and LAME (MP3) development headers, used for the real-time encoders + (`libflac-dev` and `libmp3lame-dev` on Debian/Ubuntu, `flac-devel` and + `lame-devel` on Fedora, `flac` and `lame` on Arch). They are only needed to + build; at run time the shared `libFLAC` / `libmp3lame` libraries are used. +- `pkg-config` (used to locate ALSA, FLAC and LAME) ## Usage @@ -32,6 +40,10 @@ tuxrecord -p myrec # Use a specific ALSA capture device tuxrecord -D hw:1,0 +# Encode to FLAC (lossless) or MP3 (lossy) in real time +tuxrecord -f flac +tuxrecord --format mp3 + # Record from PipeWire (the PipeWire sound server) tuxrecord -D pipewire ``` @@ -44,6 +56,7 @@ tuxrecord -D pipewire | `-d ` | Stop automatically after `` seconds | | `-p ` | Filename prefix instead of `tuxrecord` | | `-D ` | ALSA capture device (default: `default`; `pipewire` on PipeWire systems) | +| `-f, --format ` | Output format: `wav`, `flac` or `mp3` (default: `wav`) | | `-h, --help` | Show help | Run `arecord -l` to list available ALSA capture devices. On systems with @@ -55,9 +68,27 @@ or `wpctl status` (see "Recording from PipeWire" below). - **q** - Stop recording and save the file - **Ctrl+C** - Stop recording and save the file -The file is written as `__.wav` (default prefix -`tuxrecord`). The VU meter shows the input level of the left and right channels -in real time when stderr is a terminal. +The file is written as `__.` where `` is `wav`, +`flac` or `mp3` depending on `-f/--format` (default prefix `tuxrecord`, default +format `wav`). The VU meter shows the input level of the left and right +channels in real time when stderr is a terminal. + +## Output formats + +Audio is captured once and handed to the selected encoder as it arrives, so +FLAC and MP3 are produced **in real time** — no intermediate WAV file is +written and no second conversion pass runs afterwards. A small reusable buffer +converts the captured 16-bit samples to the layout the encoder expects. + +- **wav** (default) — uncompressed 16-bit little-endian PCM in a RIFF/WAVE + container, written directly by tuxrecord. +- **flac** — lossless compression via libFLAC (streaming, default + compression level 5). +- **mp3** — lossy compression via LAME at a constant 192 kbit/s, quality 2. + +`wav` and `flac` preserve the captured stream exactly; `mp3` is lossy. The +classic 4 GiB RIFF limit applies to `wav` only (the program stops cleanly +before exceeding it); `flac` and `mp3` have no such limit. ## Recording system audio @@ -128,9 +159,9 @@ tuxrecord -D pipewire ``` The `.monitor` source carries exactly what the sink is playing, so anything -your players output ends up in the WAV file. Every capture follows the same -`__.wav` naming and can be stopped with `q` or **Ctrl+C**. -The default setting can be restored later with +your players output ends up in the recorded file. Every capture follows the +same `__.` naming and can be stopped with `q` or +**Ctrl+C**. The default setting can be restored later with `pactl set-default-source `. ### Troubleshooting @@ -150,7 +181,8 @@ The default setting can be restored later with ```bash make # or directly: -# gcc -std=c99 -O2 -Wall -Wextra tuxrecord.c -o tuxrecord $(pkg-config --libs alsa) -lm +# gcc -std=c99 -O2 -Wall -Wextra tuxrecord.c -o tuxrecord \ +# $(pkg-config --cflags --libs alsa flac lame) -lm ``` Install (installs to `$(PREFIX)/bin`, default `/usr/local/bin`): diff --git a/tuxrecord.c b/tuxrecord.c index 1f0cee9..e835df0 100644 --- a/tuxrecord.c +++ b/tuxrecord.c @@ -2,12 +2,14 @@ * This file is part of tuxrecord. * https://xw3.org/hanez/tuxrecord * - * tuxrecord - record audio to a WAV file using ALSA + * tuxrecord - record audio to WAV, FLAC or MP3 using ALSA * * A Linux port of the former macOS-only "macrecord" tool, rewritten in C99 - * and using the ALSA capture API (libasound) as the recording source. - * macrecord was created by my good friend Armin. You can find his code here: - * https://codeberg.org/armin/macrecord + * and using the ALSA capture API (libasound) as the recording source. WAV + * output is written directly, while FLAC and MP3 are encoded in real time + * through the system libFLAC and libmp3lame libraries, so no intermediate + * WAV file is needed. macrecord was created by my good friend Armin. You can + * find his code here: https://codeberg.org/armin/macrecord * * Copyright 2026 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. @@ -18,6 +20,8 @@ #include #include +#include +#include #include #include @@ -30,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +53,12 @@ #define HEADER_LENGTH 44 #define WAV_DATA_MAX ((uint64_t)UINT32_MAX - 36u) /* classic 32-bit RIFF limit */ +#define FLAC_COMPRESSION_LEVEL 5u /* libFLAC level (0 = fastest, 8 = best) */ +#define MP3_BITRATE_KBPS 192 /* constant bitrate for MP3 output */ +#define MP3_QUALITY 2 /* LAME quality (0 = best, 9 = worst) */ +#define MP3_OUTPUT_SLOP 7200u /* extra space for the LAME output buffer */ +#define MP3_MAX_SAMPLES_PER_CALL 100000u /* LAME's per-call frame limit */ + static volatile sig_atomic_t g_stop = 0; /* @@ -103,7 +114,7 @@ static void logmsg(const char *fmt, ...) static void usage(void) { printf( - "tuxrecord - record audio to a WAV file using ALSA\n" + "tuxrecord - record audio to WAV, FLAC or MP3 using ALSA\n" "\n" "USAGE\n" " tuxrecord [options]\n" @@ -113,9 +124,11 @@ static void usage(void) " -d Stop automatically after seconds (default: until q or Ctrl+C)\n" " -p Filename prefix instead of \"%s\" (default: %s)\n" " -D ALSA capture device (default: \"%s\")\n" + " -f, --format Output format: wav, flac or mp3 (default: wav)\n" " -h, --help Show this help\n" "\n" - "The file is written as __.wav\n" + "The file is written as __. where is wav,\n" + "flac or mp3 depending on -f/--format\n" "\n" "By default the ALSA capture device \"default\" is used, which is usually\n" "the microphone. To record system audio (everything playing on the\n" @@ -289,6 +302,62 @@ static void draw_meter(double db_l, double db_r, bool first) fflush(stderr); } +/* ------------------------------------------------------------------------ */ +/* Output format */ +/* ------------------------------------------------------------------------ */ + +/* Container/codec selected with -f/--format (default: WAV). */ +typedef enum { + FORMAT_WAV = 0, + FORMAT_FLAC, + FORMAT_MP3 +} Format; + +/* + * Return the file extension (without the dot) for a format. + * + * Parameters: + * format - the selected output format. + * + * Returns: + * "wav", "flac" or "mp3". + */ +static const char *format_extension(Format format) +{ + switch (format) { + case FORMAT_FLAC: + return "flac"; + case FORMAT_MP3: + return "mp3"; + case FORMAT_WAV: + default: + return "wav"; + } +} + +/* + * Parse a user-supplied format name (case-insensitive). + * + * Parameters: + * name - the value given to -f/--format, e.g. "wav", "FLAC" or "Mp3". + * out - receives the parsed format on success. + * + * Returns: + * true if the name is one of the supported formats, false otherwise. + */ +static bool format_parse(const char *name, Format *out) +{ + if (strcasecmp(name, "wav") == 0) + *out = FORMAT_WAV; + else if (strcasecmp(name, "flac") == 0) + *out = FORMAT_FLAC; + else if (strcasecmp(name, "mp3") == 0) + *out = FORMAT_MP3; + else + return false; + return true; +} + /* ------------------------------------------------------------------------ */ /* WAV file writer */ /* ------------------------------------------------------------------------ */ @@ -447,6 +516,325 @@ fail: return -1; } +/* ------------------------------------------------------------------------ */ +/* Output encoders */ +/* ------------------------------------------------------------------------ */ + +/* + * Streaming output encoder selected by -f/--format. + * + * WAV data is appended to a plain file descriptor whose header is patched on + * close. FLAC and MP3 are handed the captured buffers as they arrive and + * encode them in real time: libFLAC expands the S16 samples into a reusable + * i32 conversion buffer, LAME encodes into a reusable byte buffer. No + * intermediate WAV file is ever written. All resources are released by + * encoder_close(), which also finalizes the stream. + */ +typedef struct { + Format format; + unsigned channels; + bool finished; + + /* WAV (and MP3) output file */ + int fd; + + /* FLAC stream encoder and its reusable conversion buffer */ + FLAC__StreamEncoder *flac; + FLAC__int32 *flac_pcm; + size_t flac_pcm_cap; + + /* LAME encoder and its reusable encoded-output buffer */ + lame_global_flags *lame; + unsigned char *mp3_buf; + size_t mp3_buf_cap; +} Encoder; + +/* + * Create the output encoder and open the output file. + * + * The WAV and MP3 branches open the file themselves, while libFLAC opens its + * own file from the path passed to FLAC__stream_encoder_init_file(). On + * failure a descriptive error is printed and -1 is returned; nothing is + * leaked (caller must not call encoder_close() after a failure). + * + * Parameters: + * enc - encoder to initialize. + * format - selected output format. + * path - output file path. + * rate - negotiated sample rate in Hz. + * channels - negotiated channel count. + * bits - bit depth per sample. + * + * Returns: + * 0 on success, -1 on failure. + */ +static int encoder_open(Encoder *enc, Format format, const char *path, + unsigned rate, unsigned channels, unsigned bits) +{ + memset(enc, 0, sizeof *enc); + enc->format = format; + enc->channels = channels; + enc->fd = -1; + + switch (format) { + case FORMAT_WAV: + enc->fd = wav_open(path, rate, channels, bits); + if (enc->fd < 0) { + fprintf(stderr, "Error: cannot open output file: %s: %s\n", + path, strerror(errno)); + return -1; + } + return 0; + + case FORMAT_FLAC: { + FLAC__StreamEncoder *fe = FLAC__stream_encoder_new(); + FLAC__StreamEncoderInitStatus status; + + if (fe == NULL) { + logmsg("Error: could not allocate the FLAC encoder"); + return -1; + } + if (!FLAC__stream_encoder_set_compression_level(fe, FLAC_COMPRESSION_LEVEL) || + !FLAC__stream_encoder_set_channels(fe, channels) || + !FLAC__stream_encoder_set_bits_per_sample(fe, bits) || + !FLAC__stream_encoder_set_sample_rate(fe, rate)) { + logmsg("Error: could not configure the FLAC encoder"); + FLAC__stream_encoder_delete(fe); + return -1; + } + status = FLAC__stream_encoder_init_file(fe, path, NULL, NULL); + if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + logmsg("Error: cannot open the FLAC output file %s: %s", path, + FLAC__StreamEncoderInitStatusString[status]); + FLAC__stream_encoder_delete(fe); + return -1; + } + enc->flac = fe; + return 0; + } + + case FORMAT_MP3: { + lame_global_flags *gfp; + + if (channels != 1 && channels != 2) { + logmsg("Error: MP3 supports only 1 or 2 channels " + "(device negotiated %u)", channels); + return -1; + } + gfp = lame_init(); + if (gfp == NULL) { + logmsg("Error: could not allocate the LAME encoder"); + return -1; + } + if (lame_set_in_samplerate(gfp, (int)rate) < 0 || + lame_set_num_channels(gfp, (int)channels) < 0 || + lame_set_brate(gfp, MP3_BITRATE_KBPS) < 0 || + lame_set_quality(gfp, MP3_QUALITY) < 0) { + logmsg("Error: could not configure the LAME encoder"); + lame_close(gfp); + return -1; + } + if (lame_init_params(gfp) < 0) { + logmsg("Error: could not initialize the LAME encoder"); + lame_close(gfp); + return -1; + } + enc->fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (enc->fd < 0) { + fprintf(stderr, "Error: cannot open output file: %s: %s\n", + path, strerror(errno)); + lame_close(gfp); + return -1; + } + enc->lame = gfp; + return 0; + } + } + return -1; +} + +/* + * Upper bound on the number of audio bytes the format can hold. + * + * Only the WAV container has a hard limit (the classic 32-bit RIFF size + * field); the streaming FLAC and MP3 encoders have none. + * + * Returns: + * WAV_DATA_MAX for WAV, UINT64_MAX (effectively unlimited) otherwise. + */ +static uint64_t encoder_max_data_bytes(const Encoder *enc) +{ + return enc->format == FORMAT_WAV ? WAV_DATA_MAX : UINT64_MAX; +} + +/* + * Encode one buffer of interleaved S16_LE samples in real time. + * + * WAV writes the raw little-endian bytes, FLAC converts them to i32 in a + * reusable buffer and hands the whole buffer to libFLAC, and MP3 passes the + * samples to LAME in chunks (LAME caps the frames per call) using a reusable + * output buffer. On failure an error has been printed. + * + * Parameters: + * enc - open encoder. + * samples - buffer of interleaved S16_LE samples. + * frames - number of sample frames (samples per channel). + * + * Returns: + * 0 on success, -1 on failure. + */ +static int encoder_write(Encoder *enc, const int16_t *samples, size_t frames) +{ + size_t n_samples = frames * enc->channels; + + switch (enc->format) { + case FORMAT_WAV: + if (write_all(enc->fd, samples, n_samples * sizeof(int16_t)) != 0) { + logmsg("Error: failed to write audio: %s", strerror(errno)); + return -1; + } + return 0; + + case FORMAT_FLAC: { + size_t i; + + if (n_samples == 0) + return 0; + if (enc->flac_pcm_cap < n_samples) { + FLAC__int32 *p = realloc(enc->flac_pcm, n_samples * sizeof *p); + if (p == NULL) { + logmsg("Error: out of memory"); + return -1; + } + enc->flac_pcm = p; + enc->flac_pcm_cap = n_samples; + } + for (i = 0; i < n_samples; i++) + enc->flac_pcm[i] = (FLAC__int32)samples[i]; + if (!FLAC__stream_encoder_process_interleaved(enc->flac, enc->flac_pcm, + (unsigned)frames)) { + logmsg("Error: FLAC encoding failed"); + return -1; + } + return 0; + } + + case FORMAT_MP3: { + size_t offset = 0; + + while (offset < frames) { + size_t chunk = frames - offset; + size_t cap; + int written; + + if (chunk > MP3_MAX_SAMPLES_PER_CALL) + chunk = MP3_MAX_SAMPLES_PER_CALL; + cap = chunk + chunk / 4 + MP3_OUTPUT_SLOP; + if (enc->mp3_buf_cap < cap) { + unsigned char *p = realloc(enc->mp3_buf, cap); + if (p == NULL) { + logmsg("Error: out of memory"); + return -1; + } + enc->mp3_buf = p; + enc->mp3_buf_cap = cap; + } + /* LAME declares the input as mutable but does not modify it. */ + written = lame_encode_buffer_interleaved(enc->lame, + (short int *)(samples + offset * enc->channels), + (int)chunk, enc->mp3_buf, (int)cap); + if (written < 0) { + logmsg("Error: MP3 encoding failed (code %d)", written); + return -1; + } + if (write_all(enc->fd, enc->mp3_buf, (size_t)written) != 0) { + logmsg("Error: failed to write audio: %s", strerror(errno)); + return -1; + } + offset += chunk; + } + return 0; + } + } + return -1; +} + +/* + * Finalize the stream and release all encoder resources. + * + * For WAV the RIFF/data sizes are patched and the file fsynced and closed; + * FLAC is finished (writing the final STREAMINFO block); MP3 is flushed + * (writing LAME's encoder delay) and the file closed. Safe to call once and + * only after a successful encoder_open(). + * + * Parameters: + * enc - open encoder. + * data_bytes - total number of PCM bytes captured (used to patch WAV). + * + * Returns: + * 0 on success, -1 if finalizing failed. + */ +static int encoder_close(Encoder *enc, uint64_t data_bytes) +{ + int rc = 0; + + if (enc->finished) + return 0; + enc->finished = true; + + switch (enc->format) { + case FORMAT_WAV: + if (wav_finalize(enc->fd, data_bytes) != 0) + rc = -1; + enc->fd = -1; + break; + + case FORMAT_FLAC: + if (!FLAC__stream_encoder_finish(enc->flac)) + rc = -1; + FLAC__stream_encoder_delete(enc->flac); + enc->flac = NULL; + break; + + case FORMAT_MP3: + if (enc->mp3_buf_cap < MP3_OUTPUT_SLOP) { + unsigned char *p = realloc(enc->mp3_buf, MP3_OUTPUT_SLOP); + if (p == NULL) { + logmsg("Error: out of memory"); + rc = -1; + } else { + enc->mp3_buf = p; + enc->mp3_buf_cap = MP3_OUTPUT_SLOP; + } + } + if (enc->mp3_buf_cap >= MP3_OUTPUT_SLOP) { + int written = lame_encode_flush(enc->lame, enc->mp3_buf, + (int)enc->mp3_buf_cap); + if (written < 0) { + logmsg("Error: could not flush the MP3 stream (code %d)", written); + rc = -1; + } else if (write_all(enc->fd, enc->mp3_buf, (size_t)written) != 0) { + logmsg("Error: failed to write audio: %s", strerror(errno)); + rc = -1; + } + } + lame_close(enc->lame); + enc->lame = NULL; + if (enc->fd >= 0 && close(enc->fd) != 0) + rc = -1; + enc->fd = -1; + break; + } + + free(enc->flac_pcm); + free(enc->mp3_buf); + enc->flac_pcm = NULL; + enc->mp3_buf = NULL; + enc->flac_pcm_cap = 0; + enc->mp3_buf_cap = 0; + return rc; +} + /* ------------------------------------------------------------------------ */ /* ALSA capture */ /* ------------------------------------------------------------------------ */ @@ -599,17 +987,17 @@ static void restore_terminal(void) /* ------------------------------------------------------------------------ */ /* - * Program entry point: parse options, record audio, write a WAV file. + * Program entry point: parse options, record audio, encode the output. * * Parses the command line (-o dir, -d seconds, -p prefix, -D device, - * -h/--help), validates the output directory, builds the output filename - * "/__.wav" and opens the ALSA capture device - * and the WAV file. It then installs SIGINT/SIGTERM handlers and runs a - * poll() loop that reads captured PCM frames from ALSA and appends them - * to the file, redrawing the VU meter on a tty stderr at most every - * VU_INTERVAL_S. The loop stops on 'q' (raw mode), Ctrl+C/SIGINT, - * SIGTERM, the -d duration elapsing, or on error. Finally the WAV header - * is patched, the terminal restored and the result announced. + * -f/--format, -h/--help), validates the output directory, builds the output + * filename "/__." and opens the ALSA capture + * device and the output encoder. It then installs SIGINT/SIGTERM handlers + * and runs a poll() loop that reads captured PCM frames from ALSA and feeds + * them to the encoder as they arrive, redrawing the VU meter on a tty + * stderr at most every VU_INTERVAL_S. The loop stops on 'q' (raw mode), + * Ctrl+C/SIGINT, SIGTERM, the -d duration elapsing, or on error. Finally the + * output is finalized, the terminal restored and the result announced. * * Returns: * 0 on success, 1 on a runtime error, 2 on a command-line usage error. @@ -619,6 +1007,7 @@ int main(int argc, char **argv) const char *output_dir = "."; const char *prefix = DEFAULT_PREFIX; const char *device = DEFAULT_DEVICE; + Format format = FORMAT_WAV; double duration = 0.0; /* 0 = record until stopped */ struct stat st; char path[4096]; @@ -631,7 +1020,8 @@ int main(int argc, char **argv) unsigned channels = DEFAULT_CHANNELS; snd_pcm_sframes_t got; struct sigaction sa; - int fd, i, result = 0; + Encoder enc; + int i, result = 0; bool stderr_tty, use_stdin; uint64_t data_bytes = 0; double start_t, last_draw; @@ -671,6 +1061,11 @@ int main(int argc, char **argv) return 2; } device = argv[i]; + } else if (strcmp(a, "-f") == 0 || strcmp(a, "--format") == 0) { + if (++i >= argc || !format_parse(argv[i], &format)) { + usage(); + return 2; + } } else if (strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) { usage(); return 0; @@ -694,23 +1089,20 @@ int main(int argc, char **argv) strcpy(stamp, "00000000_000000"); { - int need = snprintf(path, sizeof path, "%s/%s__%s.wav", - output_dir, prefix, stamp); + int need = snprintf(path, sizeof path, "%s/%s__%s.%s", + output_dir, prefix, stamp, format_extension(format)); if (need < 0 || need >= (int)sizeof path) { fputs("Error: output path too long\n", stderr); return 1; } } - /* ---- audio device and output file -------------------------------- */ + /* ---- audio device and output encoder ----------------------------- */ pcm = open_capture(device, &rate, &channels, &period); if (pcm == NULL) return 1; - fd = wav_open(path, rate, channels, DEFAULT_BITS); - if (fd < 0) { - fprintf(stderr, "Error: cannot open output file: %s: %s\n", - path, strerror(errno)); + if (encoder_open(&enc, format, path, rate, channels, DEFAULT_BITS) != 0) { snd_pcm_close(pcm); return 1; } @@ -725,13 +1117,13 @@ int main(int argc, char **argv) use_stdin = isatty(STDIN_FILENO); logmsg("Recording to %s", path); - logmsg("Using ALSA device '%s' at %u Hz, %u channel(s), %d-bit", - device, rate, channels, DEFAULT_BITS); + logmsg("Using ALSA device '%s' at %u Hz, %u channel(s), %d-bit, %s format", + device, rate, channels, DEFAULT_BITS, format_extension(format)); logmsg("Press q or Ctrl+C to stop."); if (snd_pcm_start(pcm) < 0) { logmsg("Error: could not start capture on '%s'", device); - close(fd); + encoder_close(&enc, data_bytes); snd_pcm_close(pcm); return 1; } @@ -752,7 +1144,7 @@ int main(int argc, char **argv) if (samples == NULL) { logmsg("Error: out of memory"); restore_terminal(); - wav_finalize(fd, data_bytes); + encoder_close(&enc, data_bytes); snd_pcm_close(pcm); return 1; } @@ -765,7 +1157,7 @@ int main(int argc, char **argv) logmsg("Error: out of memory"); free(samples); restore_terminal(); - wav_finalize(fd, data_bytes); + encoder_close(&enc, data_bytes); snd_pcm_close(pcm); return 1; } @@ -849,13 +1241,11 @@ int main(int argc, char **argv) size_t bytes = (size_t)got * channels * sizeof(int16_t); double meter_now; - if (data_bytes + bytes > WAV_DATA_MAX) { + if (data_bytes + bytes > encoder_max_data_bytes(&enc)) { logmsg("Warning: 4 GiB WAV limit reached, stopping."); break; } - if (write_all(fd, samples, bytes) != 0) { - logmsg("Error: failed to write audio: %s", - strerror(errno)); + if (encoder_write(&enc, samples, (size_t)got) != 0) { result = 1; break; } @@ -881,8 +1271,8 @@ int main(int argc, char **argv) restore_terminal(); logmsg("\nRecording ended."); - if (wav_finalize(fd, data_bytes) != 0) { - logmsg("Error: could not finalize WAV file %s", path); + if (encoder_close(&enc, data_bytes) != 0) { + logmsg("Error: could not finalize output file %s", path); result = 1; } else if (result == 0) { logmsg("Saved %s", path);