Added flac and mp3 output format.

This commit is contained in:
Johannes Findeisen 2026-10-15 02:49:31 +02:00
commit 3ff9ea07f5
4 changed files with 481 additions and 46 deletions

4
.gitignore vendored
View file

@ -14,8 +14,10 @@ recordings/
# Vim swap files
*.swp
# Misc
# Recordings
*.wav
*.flac
*.mp3
# Temporary files
tmp/

View file

@ -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)

View file

@ -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 <secs>` | Stop automatically after `<secs>` seconds |
| `-p <prefix>` | Filename prefix instead of `tuxrecord` |
| `-D <device>` | ALSA capture device (default: `default`; `pipewire` on PipeWire systems) |
| `-f, --format <fmt>` | 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 `<prefix>__<timestamp>.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 `<prefix>__<timestamp>.<ext>` where `<ext>` 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
`<prefix>__<timestamp>.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 `<prefix>__<timestamp>.<ext>` naming and can be stopped with `q` or
**Ctrl+C**. The default setting can be restored later with
`pactl set-default-source <your-input-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`):

View file

@ -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 <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
@ -18,6 +20,8 @@
#include <alsa/asoundlib.h>
#include <alloca.h>
#include <FLAC/stream_encoder.h>
#include <lame/lame.h>
#include <errno.h>
#include <limits.h>
@ -30,6 +34,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
@ -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 <secs> Stop automatically after <secs> seconds (default: until q or Ctrl+C)\n"
" -p <prefix> Filename prefix instead of \"%s\" (default: %s)\n"
" -D <device> ALSA capture device (default: \"%s\")\n"
" -f, --format <fmt> Output format: wav, flac or mp3 (default: wav)\n"
" -h, --help Show this help\n"
"\n"
"The file is written as <prefix>__<timestamp>.wav\n"
"The file is written as <prefix>__<timestamp>.<ext> where <ext> 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
* "<dir>/<prefix>__<timestamp>.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 "<dir>/<prefix>__<timestamp>.<ext>" 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);