Skip to content

Commit 7f4367b

Browse files
committed
add acap/wav
+ wav_seek (wav_reader): handle unseekable files + wav_reader: encose wrong chunk ID in '' + acap/testcard: fix late call message - no frames skipped, actually
1 parent 9486755 commit 7f4367b

4 files changed

Lines changed: 182 additions & 4 deletions

File tree

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3585,6 +3585,7 @@ fi
35853585
if test "$build_default" != no || test "$req_files" = all; then
35863586
ULTRAGRID_OBJS="$ULTRAGRID_OBJS
35873587
src/audio/capture/passive.o
3588+
src/audio/capture/wav.o
35883589
src/audio/playback/dump.o
35893590
src/audio/playback/mixer.o
35903591
src/audio/filter/delay.o

src/audio/capture/testcard.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,10 +460,10 @@ static struct audio_frame *audio_cap_testcard_read(void *state)
460460
curr_time },
461461
nullptr);
462462
} else {
463-
// we missed more than 2 "frame times", in that case, just drop the packages
463+
// we missed more than 2 "frame times"
464464
if ((curr_time - s->next_audio_time) > (long long int) (2 * NS_IN_SEC * s->chunk_size / s->audio.sample_rate)) {
465465
s->next_audio_time = curr_time;
466-
log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: skipping some samples (late grab call).\n");
466+
MSG(WARNING, "Warning: late grab call!\n");
467467
}
468468
}
469469

src/audio/capture/wav.c

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2026 CESNET, zájmové sdružení právických osob
3+
/**
4+
* @file audio/capture/wav.c
5+
* @author Martin Pulec <martin.pulec@cesnet.cz>
6+
*/
7+
8+
#include <assert.h> // for assert
9+
#include <errno.h> // for errno
10+
#include <stdint.h> // for uint32_t
11+
#include <stdio.h> // for FILE, SEEK_SET, fclose, feof, fopen
12+
#include <stdlib.h> // for free, calloc, malloc
13+
#include <string.h> // for strchr, strcmp
14+
#include <time.h> // for nanosleep, timespec
15+
16+
#include "audio/audio_capture.h" // for AUDIO_CAPTURE_ABI_VERSION, audio_ca...
17+
#include "audio/types.h" // for audio_frame
18+
#include "audio/wav_reader.h" // for wav_metadata, WAV_HDR_PARSE_OK, get...
19+
#include "compat/c23.h" // IWYU pragma: keep
20+
#include "debug.h" // for MSG, LOG_LEVEL_ERROR, LOG_LEVEL_WAR...
21+
#include "host.h" // for INIT_NOERR
22+
#include "lib_common.h" // for REGISTER_MODULE, library_class
23+
#include "tv.h" // for NS_IN_SEC, get_time_in_ns, time_ns_t
24+
#include "utils/color_out.h" // for TBOLD, color_printf, TRED
25+
#include "utils/macros.h" // for to_fourcc, IS_KEY_PREFIX
26+
#include "utils/misc.h" // for ug_strerror
27+
28+
struct device_info;
29+
struct module;
30+
31+
#define AUDIO_CAPTURE_WAV_MAGIC to_fourcc('A', 'C', 'w', 'a')
32+
#define CHUNK_SIZE 512
33+
#define MOD_NAME "[acap/wav] "
34+
35+
struct state_audio_capture_wav {
36+
uint32_t magic;
37+
FILE *wav_file;
38+
struct wav_metadata wav_metadata;
39+
struct audio_frame audio_frame;
40+
time_ns_t next_grab_time;
41+
};
42+
43+
static void audio_cap_wav_done(void *state);
44+
45+
static void
46+
audio_cap_wav_probe(struct device_info **available_devices, int *count,
47+
void (**deleter)(void *))
48+
{
49+
*deleter = free;
50+
*available_devices = nullptr;
51+
*count = 0;
52+
}
53+
54+
static void
55+
usage()
56+
{
57+
color_printf("Audio capture " TBOLD("wav")
58+
" is capable to play a WAV file.\n\n");
59+
color_printf("Usage:\n\t" TBOLD(TRED("wav") ":file=<filename>")
60+
"\n\n");
61+
color_printf("Input WAV file is looped infinitely.\n\n");
62+
}
63+
64+
static const char *
65+
parse_fmt(const char *fmt)
66+
{
67+
if (!IS_KEY_PREFIX(fmt, "file")) {
68+
usage();
69+
return nullptr;
70+
}
71+
return strchr(fmt, '=') + 1;
72+
}
73+
74+
static void *
75+
audio_cap_wav_init(struct module * /*parent */, const char *cfg)
76+
{
77+
if (strcmp(cfg, "help") == 0) {
78+
usage();
79+
return INIT_NOERR;
80+
}
81+
const char *filename = parse_fmt(cfg);
82+
if (filename == nullptr) {
83+
return nullptr;
84+
}
85+
struct state_audio_capture_wav *s = calloc(1, sizeof *s);
86+
assert(s != nullptr);
87+
s->magic = AUDIO_CAPTURE_WAV_MAGIC;
88+
s->wav_file = fopen(filename, "rb");
89+
if (s->wav_file == nullptr) {
90+
MSG(ERROR, "Cannot open input file %s: %s\n", filename,
91+
ug_strerror(errno));
92+
audio_cap_wav_done(s);
93+
return nullptr;
94+
}
95+
int ret = read_wav_header(s->wav_file, &s->wav_metadata);
96+
if (ret != WAV_HDR_PARSE_OK) {
97+
MSG(ERROR, "%s!\n", get_wav_error(ret));
98+
audio_cap_wav_done(s);
99+
return nullptr;
100+
}
101+
102+
s->audio_frame.ch_count = s->wav_metadata.ch_count;
103+
s->audio_frame.sample_rate = s->wav_metadata.sample_rate;
104+
s->audio_frame.bps = s->wav_metadata.bits_per_sample / 8;
105+
s->audio_frame.max_size =
106+
s->audio_frame.bps * s->audio_frame.ch_count * CHUNK_SIZE;
107+
s->audio_frame.data = malloc(s->audio_frame.max_size);
108+
109+
s->next_grab_time = get_time_in_ns();
110+
111+
return s;
112+
}
113+
114+
static struct audio_frame *
115+
audio_cap_wav_read(void *state)
116+
{
117+
struct state_audio_capture_wav *s = state;
118+
119+
time_ns_t curr_time = get_time_in_ns();
120+
if (s->next_grab_time > curr_time) {
121+
nanosleep(&(struct timespec){ .tv_nsec = s->next_grab_time -
122+
curr_time },
123+
nullptr);
124+
} else {
125+
// we missed more than 2 "frame times"
126+
if ((curr_time - s->next_grab_time) >
127+
(long long int) (2 * NS_IN_SEC * CHUNK_SIZE /
128+
s->audio_frame.sample_rate)) {
129+
s->next_grab_time = curr_time;
130+
MSG(WARNING, "Warning: late grab call!\n");
131+
}
132+
}
133+
s->next_grab_time +=
134+
NS_IN_SEC * CHUNK_SIZE / s->audio_frame.sample_rate;
135+
136+
unsigned frame_size = s->audio_frame.bps * s->audio_frame.ch_count;
137+
size_t frames_read = wav_read(s->audio_frame.data, CHUNK_SIZE,
138+
s->wav_file, &s->wav_metadata);
139+
if (frames_read == 0) {
140+
if (feof(s->wav_file)) {
141+
MSG(INFO, "File ended, rewinding...\n");
142+
wav_seek(s->wav_file, 0, SEEK_SET, &s->wav_metadata);
143+
}
144+
return nullptr;
145+
}
146+
s->audio_frame.data_len = frames_read * frame_size;
147+
148+
return &s->audio_frame;
149+
}
150+
151+
static void
152+
audio_cap_wav_done(void *state)
153+
{
154+
struct state_audio_capture_wav *s = state;
155+
assert(s->magic == AUDIO_CAPTURE_WAV_MAGIC);
156+
157+
if (s->wav_file != nullptr) {
158+
fclose(s->wav_file);
159+
}
160+
free(s);
161+
}
162+
163+
static const struct audio_capture_info acap_wav_info = {
164+
.probe = audio_cap_wav_probe,
165+
.init = audio_cap_wav_init,
166+
.read = audio_cap_wav_read,
167+
.done = audio_cap_wav_done
168+
};
169+
170+
REGISTER_MODULE(wav, &acap_wav_info, LIBRARY_CLASS_AUDIO_CAPTURE,
171+
AUDIO_CAPTURE_ABI_VERSION);

src/audio/wav_reader.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @author Martin Pulec <martin.pulec@cesnet.cz>
44
*/
55
/*
6-
* Copyright (c) 2013-2022 CESNET, z. s. p. o.
6+
* Copyright (c) 2013-2026 CESNET, zájmové sdružení právnických osob
77
* All rights reserved.
88
*
99
* Redistribution and use in source and binary forms, with or without
@@ -224,7 +224,8 @@ int read_wav_header(FILE *wav_file, struct wav_metadata *metadata)
224224
log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Using %4.4s WAV file.\n", buffer);
225225
rf64 = 1;
226226
} else if (strncmp(buffer, "RIFF", 4) != 0) {
227-
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Expected RIFF or RF64/BW64 chunk, %.4s given.\n", buffer);
227+
MSG(ERROR, "Expected RIFF or RF64/BW64 chunk, '%.4s' given.\n",
228+
buffer);
228229
return WAV_HDR_PARSE_WRONG_FORMAT;
229230
}
230231

@@ -322,6 +323,11 @@ int wav_seek(FILE *wav_file, long offset, int whence, struct wav_metadata *metad
322323
return fseek(wav_file, offset, whence);
323324
}
324325
assert(whence == SEEK_SET);
326+
if (metadata->data_offset == -1) {
327+
MSG(WARNING, "Input file has no data offset defined!\n");
328+
errno = ESPIPE;
329+
return -1;
330+
}
325331
return fseek(wav_file, metadata->data_offset + offset, SEEK_SET);
326332
}
327333

0 commit comments

Comments
 (0)