UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
testcard.c
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2014-2023 CESNET, z. s. p. o.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#define MOD_NAME "[Audio testcard] "
39
40#ifdef HAVE_CONFIG_H
41#include "config.h"
42#include "config_unix.h"
43#include "config_win32.h"
44#endif
45
46#include "audio/audio_capture.h"
47#include "audio/types.h"
48#include "audio/utils.h"
49#include "audio/wav_reader.h"
50#include "compat/misc.h"
51#include "compat/usleep.h"
52#include "debug.h"
53#include "host.h"
54#include "lib_common.h"
55#include "tv.h"
56#include "utils/color_out.h"
57#include "utils/fs.h"
58#include "utils/macros.h"
59#include "utils/misc.h"
60
61#include <assert.h>
62#include <string.h>
63
64#define AUDIO_CAPTURE_TESTCARD_MAGIC 0xf4b3c9c9u
65
66#define DEFAULT_AUDIO_BPS 2
67#define DEFAULT_AUDIO_SAMPLE_RATE 48000
68
69#define CHUNKS_PER_SEC 25 // 1 video frame time @25 fps
70 // has to be divisor of AUDIO_SAMLE_RATE
71
72#define DEFAULT_FREQUENCY 1000
73#define DEFAULT_VOLUME -18.0
74
93
102
103static void audio_cap_testcard_probe(struct device_info **available_devices, int *count, void (**deleter)(void *))
104{
105 *deleter = free;
106 *available_devices = calloc(1, sizeof(struct device_info));
107 strcpy((*available_devices)[0].dev, "");
108 strcpy((*available_devices)[0].name, "Testing 1 kHZ signal");
109 *count = 1;
110}
111
113static char *get_sine_signal(int sample_rate, int bps, int channels, int frequency, double volume, int crescendo_spd) {
114 char *data = (char *) calloc(1, (size_t) sample_rate * channels * bps);
115 assert(data != NULL);
116 double scale = pow(10.0, volume / 20.0) * sqrt(2.0);
117 bool dither = sample_rate % frequency != 0 && get_commandline_param("no-dither") == NULL;
118
119 for (int i = 0; i < (int) sample_rate; i += 1) {
120 for (int channel = 0; channel < channels; ++channel) {
121 double sine = sin(((double) i / ((double) sample_rate / frequency)) * M_PI * 2. );
122 if (crescendo_spd != 0) {
123 double multiplier = ((double) ((i * crescendo_spd) % sample_rate) / sample_rate);
124 sine *= 2 * multiplier; // up to 2x amplitude
125 }
126 int32_t val = CLAMP(sine * INT32_MAX * scale, (double) INT32_MIN, (double) INT32_MAX);
127 change_bps2(data + ((ptrdiff_t) i) * bps * channels + ((ptrdiff_t) bps) * channel,
128 bps, (char *) &val, sizeof val, 1 * sizeof val, dither);
129 }
130 }
131
132 return data;
133}
134
138static char *get_ebu_signal(int sample_rate, int bps, int channels, int frequency, double volume, size_t *total_samples) {
139 *total_samples = ((size_t) 3 + channels + 1 + 3) * sample_rate;
140 char *ret = (char *) calloc(1, *total_samples * channels * bps);
141 assert(ret != NULL);
142 double scale = pow(10.0, volume / 20.0) * sqrt(2.0);
143
144 char* data = ret;
145 for (int i = 0; i < (int) sample_rate * 3; i += 1)
146 {
147 for (int channel = 0; channel < channels; ++channel) {
148 const int32_t val =
149 round(sin(((double) i /
150 ((double) sample_rate / frequency)) *
151 M_PI * 2.) *
152 INT32_MAX * scale);
153 //fprintf(stderr, "%ld ", val);
154 STORE_I32_SAMPLE(data + (ptrdiff_t) i * bps * channels +
155 (ptrdiff_t) bps * channel,
156 val, bps);
157 }
158 }
159 data += sample_rate * 3 * bps * channels;
160
161 for (int channel = 0; channel < channels; ++channel) {
162 memset(data, 0, sample_rate * bps * channels);
163 data += sample_rate * bps * channels / 2;
164 for (int i=0; i < (int) sample_rate / 2; i += 1)
165 {
166 const int64_t val =
167 sin(((double) i /
168 ((double) sample_rate / frequency)) *
169 M_PI * 2.) *
170 INT32_MAX * scale;
171 STORE_I32_SAMPLE(data + (ptrdiff_t) i * bps * channels +
172 (ptrdiff_t) bps * channel,
173 val, bps);
174 }
175 data += sample_rate * bps * channels / 2;
176 }
177
178 memset(data, 0, sample_rate * bps * channels);
180
181 memcpy(data, ret, sample_rate * 3 * bps * channels);
182
183 return ret;
184}
185
186static char *get_noise(size_t len) {
187 unsigned char *ret = malloc(len);
188 assert(ret != NULL);
189 for (size_t i = 0; i < len; ++i) {
190 ret[i] = (unsigned char) (rand() % 256);
191 }
192 return (char *) ret;
193}
194
195static bool audio_testcard_read_wav(const char *wav_filename, struct audio_frame *audio_frame,
196 char **audio_samples, size_t *total_samples,
197 unsigned long long int *chunk_size) {
198 FILE *wav = fopen(wav_filename, "rb");
199 if(!wav) {
200 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to open WAV file: %s.\n", wav_filename);
201 return false;
202 }
203 struct wav_metadata metadata;
204 int ret = read_wav_header(wav, &metadata);
205 if(ret != WAV_HDR_PARSE_OK) {
207 fclose(wav);
208 return false;
209 }
211 audio_frame->ch_count = metadata.ch_count;
213 if (*chunk_size == 0) {
214 *chunk_size = audio_frame->sample_rate / CHUNKS_PER_SEC;
215 }
216
217 *total_samples = metadata.data_size * 8ULL / metadata.ch_count / metadata.bits_per_sample;
218 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "%zu samples read from file %s\n", *total_samples, wav_filename);
219
220 const int headroom = (*chunk_size - 1) * audio_frame->ch_count * audio_frame->bps;
221 const int samples_data_size = *total_samples * audio_frame->ch_count * audio_frame->bps;
222
223 const int audio_samples_size = samples_data_size + headroom;
224 *audio_samples = (char *) calloc(1, audio_samples_size);
225 assert(*audio_samples != NULL);
226
227 char *read_to;
228 char *tmp = NULL;
229 if ((unsigned) audio_frame->bps == metadata.bits_per_sample / 8) {
230 read_to = *audio_samples;
231 } else {
232 tmp = (char *) calloc(1, metadata.data_size);
233 read_to = tmp;
234 }
235 unsigned int samples = wav_read(read_to, *total_samples, wav, &metadata);
236 int bytes = samples * (metadata.bits_per_sample / 8) * metadata.ch_count;
237 if (samples != *total_samples) {
238 log_msg(samples > 0 ? LOG_LEVEL_WARNING : LOG_LEVEL_ERROR, MOD_NAME "Warning: premature end of WAV file (%d read, %lld expected)!\n", bytes, metadata.data_size);
239 if (samples == 0) {
240 fclose(wav);
241 return false;
242 }
243 *total_samples = samples;
244 }
245 fclose(wav);
246
247 if ((unsigned) audio_frame->bps != metadata.bits_per_sample / 8){
248 change_bps(*audio_samples, audio_frame->bps, tmp, metadata.bits_per_sample / 8, bytes);
249 }
250 free(tmp);
251
252 memcpy(*audio_samples + samples_data_size, *audio_samples, headroom);
253
254 return true;
255}
256
257static bool
259 char wav_file[static MAX_PATH_SIZE], double *volume, int *frequency,
260 enum pattern *pattern)
261{
262 char *item = NULL;
263 char *save_ptr = NULL;
264 errno = 0;
265 while ((item = strtok_r(fmt, ":", &save_ptr))) {
266 if (strncasecmp(item, "vol=", strlen("vol=")) == 0 ||
267 strncasecmp(item, "volume=", strlen("volume=")) == 0) {
268 *volume = strtod(strchr(item, '=') + 1, NULL);
269 assert(*volume > 0.0);
270 } else if (strncasecmp(item, "file=", strlen("file=")) == 0) {
271 wav_file[0] = '\0';
272 strncat(wav_file, strchr(item, '=') + 1, MAX_PATH_SIZE - 1);
273 *pattern = WAV;
274 } else if (strncasecmp(item, "frames=", strlen("frames=")) ==
275 0) {
276 s->chunk_size = strtoll(item + strlen("frames="), NULL, 10);
277 } else if (strncasecmp(
278 item, "frequency=", strlen("frequency=")) == 0) {
279 long val = strtol(item + strlen("frequency="), NULL, 10);
280 assert(val > 0 && val <= INT_MAX);
281 *frequency = (int) val;
282 } else if (strstr(item, "crescendo") == item) {
284 char *val = strchr(item, '=');
285 if (val) {
286 val += 1;
287 if (strcmp(val, "help") == 0) {
288 printf("-s "
289 "testcard:crescendo=<val>\n\t<"
290 "val> - crescendo duration "
291 "multiplier (default 1)\n");
292 return false;
293 break;
294 }
295 s->crescendo_speed = (int) strtol(val, NULL, 0);
296 assert(s->crescendo_speed > 0);
297 }
298 } else if (strcasecmp(item, "ebu") == 0) {
299 *pattern = EBU;
300 } else if (strcasecmp(item, "noise") == 0) {
301 *pattern = NOISE;
302 } else if (strcasecmp(item, "silence") == 0) {
303 *pattern = SILENCE;
304 } else {
306 MOD_NAME "Unknown option: %s\n", item);
307 return false;
308 break;
309 }
310
311 fmt = NULL;
312 }
313 if (errno != 0) {
314 perror("");
315 return false;
316 }
317 return true;
318}
319
320static bool
322 int frequency, double volume)
323{
330 s->audio.bps =
332 if (s->chunk_size == 0) {
334 }
335 assert(s->chunk_size > 0);
336 const char *pattern_name = "(unknown)";
337 switch (pattern) {
338 case CRESCENDO:
339 case SINE:
340 pattern_name = pattern == SINE ? "sine" : "crescendo";
343 frequency, volume,
344 pattern == CRESCENDO ? s->crescendo_speed : 0);
346 break;
347 case EBU:
348 pattern_name = "EBU tone";
351 frequency, volume, &s->total_samples);
352 break;
353 case SILENCE:
354 pattern_name = "silence";
356 s->audio_samples = (char *) calloc(
357 1, (s->total_samples * s->audio.ch_count * s->audio.bps) +
358 s->chunk_size - 1);
359 break;
360 case NOISE:
361 pattern_name = "noise";
364 (s->total_samples * s->audio.ch_count * s->audio.bps) +
365 s->chunk_size - 1);
366 break;
367 default:
368 abort();
369 }
370
372 MOD_NAME "Generating %d Hz (%.2f RMS dBFS) %s (%s, "
373 "samples per frame: %llu).\n",
374 frequency, volume, pattern_name,
376 s->chunk_size);
377
378 // add padding if s->total_samples % s->chunk_size != 0
379 s->audio_samples = (char *) realloc(
380 s->audio_samples,
381 (s->total_samples * s->audio.ch_count * s->audio.bps) +
382 s->chunk_size - 1);
383 memcpy(s->audio_samples +
384 s->total_samples * s->audio.bps * s->audio.ch_count,
385 s->audio_samples, s->chunk_size - 1);
386 return true;
387}
388
389static void *audio_cap_testcard_init(struct module *parent, const char *cfg)
390{
391 UNUSED(parent);
392 char wav_file[MAX_PATH_SIZE] = "";
393
394 double volume = DEFAULT_VOLUME;
395 int frequency = DEFAULT_FREQUENCY;
396 enum pattern pattern = SINE;
397
398 if (strcmp(cfg, "help") == 0) {
399 struct key_val options[] = {
400 { "volume=<vol>", "a volume in dBFS (default " TOSTRING(DEFAULT_VOLUME) ")" },
401 { "file=<wav>", "a wav file to be played" },
402 { "frames=<nf>", "sets number of audio frames per packet" },
403 { "frequency=<f>", "frequency of sinusoide" },
404 { "ebu", "use EBU sound" },
405 { "silence", "emit silence" },
406 { "crescendo[=<spd>]", "produce amplying sinusoide (optionally accelerated)" },
407 { "noise", "emit noise" },
408 { NULL, NULL }
409 };
410 print_module_usage("-s testcard", options, NULL, false);
411 color_printf("\nYou can also consider using " TBOLD("sdl_mixer") " audio capture card to generate a more complex pattern. "
412 "(It already includes a MIDI that can be played immediately.)\n");
413 return INIT_NOERR;
414 }
415
416 struct state_audio_capture_testcard *s = calloc(1, sizeof *s);
417 assert(s != NULL);
419 s->crescendo_speed = 1;
420
421 char *tmp = strdup(cfg);
422 bool ret = parse_fmt(s, tmp, wav_file, &volume, &frequency, &pattern);
423 free(tmp);
424 if (!ret) {
425 free(s);
426 return NULL;
427 }
428 ret = pattern == WAV
429 ? audio_testcard_read_wav(wav_file, &s->audio,
430 &s->audio_samples,
431 &s->total_samples, &s->chunk_size)
432 : generate_signal(s, pattern, frequency, volume);
433 if (!ret) {
434 free(s);
435 return NULL;
436 }
437
438 s->audio.data_len = s->chunk_size * s->audio.bps * s->audio.ch_count;
440 s->audio.data = (char *) calloc(1, s->audio.max_size);
441
442 s->samples_played = 0;
443
445
446 return s;
447}
448
450{
452 s = (struct state_audio_capture_testcard *) state;
453 time_ns_t curr_time = get_time_in_ns();
454
455 if( s->next_audio_time > curr_time) {
456 usleep((s->next_audio_time - curr_time) / US_IN_NS);
457 } else {
458 // we missed more than 2 "frame times", in that case, just drop the packages
459 if ((curr_time - s->next_audio_time) > (long long int) (2 * NS_IN_SEC * s->chunk_size / s->audio.sample_rate)) {
460 s->next_audio_time = curr_time;
461 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: skipping some samples (late grab call).\n");
462 }
463 }
464
466
467 size_t samples = s->chunk_size;
468 if (s->samples_played + s->chunk_size > s->total_samples) {
469 samples = s->total_samples - s->samples_played;
470 }
471 size_t len = samples * s->audio.bps * s->audio.ch_count;
472 memcpy(s->audio.data, s->audio_samples + s->audio.bps * s->samples_played * s->audio.ch_count, len);
473 if (samples < s->chunk_size) {
474 memcpy(s->audio.data + len, s->audio_samples, s->chunk_size * s->audio.bps * s->audio.ch_count - len);
475 }
476
478
479 return &s->audio;
480}
481
483{
485
487
488 free(s->audio_samples);
489 free(s->audio.data);
490
491 free(s);
492}
493
500
502
#define UNUSED(x)
void ** state
int void * val
int int int sample_rate
int int channels
const char * fmt
int void size_t * len
int void(** deleter)(void *))
static struct audio_frame * audio_cap_testcard_read(void *state)
Definition testcard.c:449
static const struct audio_capture_info acap_testcard_info
Definition testcard.c:494
#define DEFAULT_FREQUENCY
Definition testcard.c:72
static char * get_sine_signal(int sample_rate, int bps, int channels, int frequency, double volume, int crescendo_spd)
Definition testcard.c:113
static bool parse_fmt(struct state_audio_capture_testcard *s, char *fmt, char wav_file[static MAX_PATH_SIZE], double *volume, int *frequency, enum pattern *pattern)
Definition testcard.c:258
#define DEFAULT_VOLUME
Definition testcard.c:73
#define DEFAULT_AUDIO_SAMPLE_RATE
Definition testcard.c:67
static bool audio_testcard_read_wav(const char *wav_filename, struct audio_frame *audio_frame, char **audio_samples, size_t *total_samples, unsigned long long int *chunk_size)
Definition testcard.c:195
static char * get_ebu_signal(int sample_rate, int bps, int channels, int frequency, double volume, size_t *total_samples)
Definition testcard.c:138
static void * audio_cap_testcard_init(struct module *parent, const char *cfg)
Definition testcard.c:389
static void audio_cap_testcard_done(void *state)
Definition testcard.c:482
pattern
Definition testcard.c:94
@ SILENCE
Definition testcard.c:98
@ SINE
Definition testcard.c:95
@ WAV
Definition testcard.c:97
@ EBU
Definition testcard.c:96
@ CRESCENDO
Definition testcard.c:99
@ NOISE
Definition testcard.c:100
static bool generate_signal(struct state_audio_capture_testcard *s, enum pattern pattern, int frequency, double volume)
Definition testcard.c:321
static char * get_noise(size_t len)
Definition testcard.c:186
#define MOD_NAME
Definition testcard.c:38
#define AUDIO_CAPTURE_TESTCARD_MAGIC
Definition testcard.c:64
static void audio_cap_testcard_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
Definition testcard.c:103
#define CHUNKS_PER_SEC
Definition testcard.c:69
#define DEFAULT_AUDIO_BPS
Definition testcard.c:66
This file contains definition of some common audio types.
#define STORE_I32_SAMPLE(ptr, val, out_bps)
stores given int32_t sample at required out_bps
Definition utils.h:143
#define AUDIO_CAPTURE_ABI_VERSION
int color_printf(const char *format,...)
#define TBOLD(x)
Definition color_out.h:70
static __device__ vec3 round(const vec3 &v)
Definition cuda_dxt.cu:122
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define MAX_PATH_SIZE
Definition fs.h:48
int count
Definition hd-rum.c:44
unsigned int audio_capture_bps
Definition host.cpp:164
const char * get_commandline_param(const char *key)
Definition host.cpp:894
unsigned int audio_capture_sample_rate
Definition host.cpp:165
unsigned int audio_capture_channels
Definition host.cpp:163
#define DEFAULT_AUDIO_CAPTURE_CHANNELS
Definition host.h:89
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_AUDIO_CAPTURE
Definition lib_common.h:76
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define CLAMP(x, low, high)
Definition macros.h:67
#define TOSTRING(A)
Definition macros.h:49
void print_module_usage(const char *module_name, const struct key_val *options, const struct key_val *options_full, bool print_full_help)
Definition misc.cpp:242
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
struct used to comunicate with audio capture/playback device
Definition types.h:99
int sample_rate
Definition types.h:101
char * data
Definition types.h:102
int ch_count
Definition types.h:104
int data_len
Definition types.h:103
int bps
Definition types.h:100
int max_size
Definition types.h:105
Definition hd-rum.c:27
Definition misc.h:68
Definition rtp.c:253
struct audio_frame audio
Definition testcard.c:79
unsigned int samples_played
Definition testcard.c:87
unsigned long long int chunk_size
Definition testcard.c:78
unsigned int ch_count
Definition wav_reader.h:47
unsigned int sample_rate
Definition wav_reader.h:48
unsigned int bits_per_sample
Definition wav_reader.h:49
long long data_size
size of data, -1 if unknown
Definition wav_reader.h:54
long long time_ns_t
Definition tv.h:81
#define US_IN_NS
Definition tv.h:90
#define NS_IN_SEC
Definition tv.h:92
static time_ns_t get_time_in_ns()
Definition tv.h:101
int usleep(unsigned int usec)
Definition usleep.c:47
struct audio_desc audio_desc_from_frame(const struct audio_frame *frame)
Definition utils.cpp:196
void change_bps2(char *out, int out_bps, const char *in, int in_bps, int in_len, bool dither)
changes bps with option to specify whether or not do the dithering
Definition utils.cpp:263
void change_bps(char *out, int out_bps, const char *in, int in_bps, int in_len)
Definition utils.cpp:258
const char * audio_desc_to_cstring(struct audio_desc desc)
Definition utils.cpp:644
constexpr size_t headroom
const char * get_wav_error(int errcode)
Definition wav_reader.c:300
size_t wav_read(void *buffer, size_t sample_count, FILE *wav_file, struct wav_metadata *metadata)
Definition wav_reader.c:291
int read_wav_header(FILE *wav_file, struct wav_metadata *metadata)
Definition wav_reader.c:215
#define WAV_HDR_PARSE_OK
Definition wav_reader.h:57