UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
wav_reader.c File Reference
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include "audio/utils.h"
#include "audio/wav_reader.h"
#include "debug.h"
#include "utils/macros.h"
#include "utils/misc.h"
Include dependency graph for wav_reader.c:

Go to the source code of this file.

Macros

#define CHECK(cmd, retval)
 
#define GUID_PCM   { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }
 
#define MOD_NAME   "[WAV reader] "
 
#define READ_N(buf, len)
 
#define WAV_MAX_BIT_DEPTH   64
 
#define WAV_MAX_CHANNELS   128
 

Functions

static int fskip (FILE *f, long off)
 
const char * get_wav_error (int errcode)
 
static _Bool is_member (const char *needle, const char **haystack)
 
static _Bool process_data_chunk (FILE *wav_file, uint32_t chunk_size, struct wav_metadata *metadata, _Bool found_ds64_chunk)
 
static _Bool read_ds64 (FILE *wav_file, uint32_t chunk_size, struct wav_metadata *metadata)
 
static int read_fmt_chunk (FILE *wav_file, struct wav_metadata *metadata, size_t chunk_size)
 
int read_wav_header (FILE *wav_file, struct wav_metadata *metadata)
 
size_t wav_read (void *buffer, size_t sample_count, FILE *wav_file, struct wav_metadata *metadata)
 
int wav_seek (FILE *wav_file, long offset, int whence, struct wav_metadata *metadata)
 same as fseek but WAV header presence is respected (for SEEK SET)
 

Detailed Description

Macro Definition Documentation

◆ CHECK

#define CHECK ( cmd,
retval )
Value:
if (cmd == -1) return retval;

Definition at line 214 of file wav_reader.c.

Referenced by read_wav_header().

◆ GUID_PCM

#define GUID_PCM   { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }

Definition at line 65 of file wav_reader.c.

Referenced by read_fmt_chunk().

◆ MOD_NAME

#define MOD_NAME   "[WAV reader] "

◆ READ_N

#define READ_N ( buf,
len )
Value:
if (fread(buf, len, 1, wav_file) != 1) {\
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Read error: %s.\n", ug_strerror(errno));\
}
int void size_t * len
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
const char * ug_strerror(int errnum)
Definition misc.cpp:203
#define MOD_NAME
Definition wav_reader.c:55
#define WAV_HDR_PARSE_READ_ERROR
Definition wav_reader.h:58

Definition at line 59 of file wav_reader.c.

59#define READ_N(buf, len) \
60 if (fread(buf, len, 1, wav_file) != 1) {\
61 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Read error: %s.\n", ug_strerror(errno));\
62 return WAV_HDR_PARSE_READ_ERROR;\
63 }

Referenced by read_ds64(), read_fmt_chunk(), and read_wav_header().

◆ WAV_MAX_BIT_DEPTH

#define WAV_MAX_BIT_DEPTH   64

Definition at line 56 of file wav_reader.c.

Referenced by read_fmt_chunk().

◆ WAV_MAX_CHANNELS

#define WAV_MAX_CHANNELS   128

Definition at line 57 of file wav_reader.c.

Referenced by read_fmt_chunk().

Function Documentation

◆ fskip()

static int fskip ( FILE * f,
long off )
static

Definition at line 147 of file wav_reader.c.

147 {
148 if (_fseeki64(f, off, SEEK_CUR) == 0) {
149 return 1;
150 }
151 while (off-- > 0) {
152 if (getc(f) == EOF) {
153 return -1;
154 }
155 }
156 return 1;
157}
#define _fseeki64

References _fseeki64.

Referenced by read_ds64(), and read_wav_header().

◆ get_wav_error()

const char * get_wav_error ( int errcode)

Definition at line 300 of file wav_reader.c.

301{
302 switch(errcode) {
303 case WAV_HDR_PARSE_OK:
304 return "Wav header OK";
306 return "Premature end of WAV file";
308 return "Wav header in wrong format";
310 return "Wav not in PCM";
312 return "Wrong/unsupported value in header";
313 default:
314 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown error code %d passed!\n", errcode);
315 return "Unknown error";
316 }
317}
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define WAV_HDR_PARSE_WRONG_FORMAT
Definition wav_reader.h:59
#define WAV_HDR_PARSE_OK
Definition wav_reader.h:57
#define WAV_HDR_PARSE_INVALID_PARAM
Definition wav_reader.h:61
#define WAV_HDR_PARSE_NOT_PCM
Definition wav_reader.h:60

References LOG_LEVEL_ERROR, log_msg(), MOD_NAME, WAV_HDR_PARSE_INVALID_PARAM, WAV_HDR_PARSE_NOT_PCM, WAV_HDR_PARSE_OK, WAV_HDR_PARSE_READ_ERROR, and WAV_HDR_PARSE_WRONG_FORMAT.

Referenced by audio_testcard_read_wav(), and init_audio().

Here is the call graph for this function:

◆ is_member()

static _Bool is_member ( const char * needle,
const char ** haystack )
static

Definition at line 137 of file wav_reader.c.

137 {
138 while (*haystack != NULL) {
139 if (strncmp(needle, *haystack, 4) == 0) {
140 return 1;
141 }
142 haystack++;
143 }
144 return 0;
145}

Referenced by read_wav_header().

◆ process_data_chunk()

static _Bool process_data_chunk ( FILE * wav_file,
uint32_t chunk_size,
struct wav_metadata * metadata,
_Bool found_ds64_chunk )
static

Sets data_offset and data_size to metadata (from file position and actual chunk size) Return 1 (true) also for non-seekable > 4GB streams (caller may read it sequentially).

Definition at line 161 of file wav_reader.c.

161 {
162 if (!found_ds64_chunk) { // RF64 has this chunk size always -1, value from ds64 is used instead
163 metadata->data_size = chunk_size;
164 }
165 metadata->data_offset = _ftelli64(wav_file);
166 if (metadata->data_size != UINT32_MAX || metadata->data_offset == -1) {
167 return 1;
168 }
169
170 // for UINT32_MAX deduce the length from file length (as FFmpeg does)
171 if (_fseeki64(wav_file, 0, SEEK_END) != 0) {
172 return 1;
173 }
174 int64_t data_end = _ftelli64(wav_file);
175 if (data_end == -1) {
176 ug_perror(MOD_NAME "cannot get length of file");
177 return 0;
178 }
179 if (_fseeki64(wav_file, metadata->data_offset, SEEK_SET) != 0) {
180 ug_perror(MOD_NAME "cannot seek back to data chunk");
181 return 0;
182 }
183 metadata->data_size = data_end - metadata->data_offset;
184 return 1;
185}
#define _ftelli64
void ug_perror(const char *s)
Definition misc.cpp:213
long long data_offset
offset from beginning, -1 if unknown (typically unseekable)
Definition wav_reader.h:53
long long data_size
size of data, -1 if unknown
Definition wav_reader.h:54

References _fseeki64, _ftelli64, wav_metadata::data_offset, wav_metadata::data_size, MOD_NAME, and ug_perror().

Referenced by read_wav_header().

Here is the call graph for this function:

◆ read_ds64()

static _Bool read_ds64 ( FILE * wav_file,
uint32_t chunk_size,
struct wav_metadata * metadata )
static

Definition at line 187 of file wav_reader.c.

187 {
188 if (chunk_size < 28) {
189 log_msg(LOG_LEVEL_ERROR, MOD_NAME "ds64 chunk size must be at least 28 but %" PRIu32 " was presented!\n", chunk_size);
190 return 0;
191 }
192 uint64_t tmp = 0;
193 READ_N(&tmp, 8); // RF64 chunk size - unused
194 READ_N(&tmp, 8);
195 assert(tmp <= LLONG_MAX);
196 metadata->data_size = tmp;
197 READ_N(&tmp, 8); // dummy - should be ignored
198 uint32_t table_count = 0;
199 const size_t table_sz = (sizeof(uint32_t) /*ID*/ + sizeof(uint64_t) /*size*/);
200 READ_N(&table_count, 4); // number of table entries for non-'data' chunks
201 assert(table_sz * table_count <= MIN(sizeof(uint32_t), sizeof(long))); // check overflows
202 if (chunk_size - 28 != table_sz * table_count) {
203 log_msg(LOG_LEVEL_ERROR, MOD_NAME "incorrect table count %" PRIu32 " given, ds64 chunk len %" PRIu32 "\n",
204 table_count, chunk_size);
205 return 0;
206 }
207 if (!fskip(wav_file, (long)(table_count * table_sz))) { // just skip the table contents
208 ug_perror(MOD_NAME "ds64 - cannot skip");
209 return 0;
210 }
211 return 1;
212}
#define MIN(a, b)
Definition macros.h:63
static int fskip(FILE *f, long off)
Definition wav_reader.c:147
#define READ_N(buf, len)
Definition wav_reader.c:59

References wav_metadata::data_size, fskip(), LOG_LEVEL_ERROR, log_msg(), MIN, MOD_NAME, READ_N, and ug_perror().

Referenced by read_wav_header().

Here is the call graph for this function:

◆ read_fmt_chunk()

static int read_fmt_chunk ( FILE * wav_file,
struct wav_metadata * metadata,
size_t chunk_size )
static

Definition at line 67 of file wav_reader.c.

68{
69 if (chunk_size != 16 && chunk_size != 18 && chunk_size != 40) {
70 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Expected fmt chunk size 16, 18 or 40, %zd given.\n", chunk_size);
72 }
73
74 uint16_t format;
75 READ_N(&format, 2);
76 if (format != 0x0001 && format != 0xFFFE) {
77 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Expected format 0x0001, 0x%04d given.\n", format);
79 }
80
81 uint16_t ch_count;
82 READ_N(&ch_count, 2);
83 if (ch_count == 0 || ch_count > WAV_MAX_CHANNELS) {
85 }
86 metadata->ch_count = ch_count;
87
88 uint32_t sample_rate;
90 if (sample_rate > 192000) {
92 }
93 metadata->sample_rate = sample_rate;
94
95 uint32_t avg_bytes_per_sec;
96 READ_N(&avg_bytes_per_sec, sizeof(avg_bytes_per_sec));
97
98 uint16_t block_align_offset;
99 READ_N(&block_align_offset, sizeof(block_align_offset));
100
101 uint16_t bits_per_sample;
102 READ_N(&bits_per_sample, sizeof(bits_per_sample));
103 if (bits_per_sample == 0 || bits_per_sample > WAV_MAX_BIT_DEPTH) {
105 }
106 metadata->bits_per_sample = bits_per_sample;
107
108 if (chunk_size >= 18) {
109 uint16_t ext_size;
110 READ_N(&ext_size, 2);
111 if (ext_size != chunk_size - 18) {
112 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unexpected ext size %" PRIu16 ", remaining chunk size %zu.\n", ext_size, chunk_size - 18);
114 }
115 if (ext_size == 22) {
116 READ_N(&metadata->valid_bits, sizeof metadata->valid_bits);
117 READ_N(&metadata->channel_mask, sizeof metadata->channel_mask);
118 char buffer[16];
119 READ_N(buffer, 16);
120 const char guid[] = GUID_PCM;
121 if (memcmp(guid, buffer, 16) != 0) {
122 log_msg(LOG_LEVEL_ERROR, MOD_NAME "GUID is not PCM!\n");
124 }
125 format = 0x0001;
126 }
127 }
128
129 if (format == 0xFFFE) {
130 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Subtype GUID not found!\n");
132 }
133
134 return WAV_HDR_PARSE_OK;
135}
int int int sample_rate
unsigned int ch_count
Definition wav_reader.h:47
unsigned int sample_rate
Definition wav_reader.h:48
uint32_t channel_mask
Definition wav_reader.h:51
unsigned int bits_per_sample
Definition wav_reader.h:49
uint16_t valid_bits
Definition wav_reader.h:50
#define WAV_MAX_BIT_DEPTH
Definition wav_reader.c:56
#define GUID_PCM
Definition wav_reader.c:65
#define WAV_MAX_CHANNELS
Definition wav_reader.c:57

References wav_metadata::bits_per_sample, wav_metadata::ch_count, wav_metadata::channel_mask, GUID_PCM, LOG_LEVEL_ERROR, log_msg(), MOD_NAME, READ_N, sample_rate, wav_metadata::sample_rate, wav_metadata::valid_bits, WAV_HDR_PARSE_INVALID_PARAM, WAV_HDR_PARSE_NOT_PCM, WAV_HDR_PARSE_OK, WAV_HDR_PARSE_READ_ERROR, WAV_HDR_PARSE_WRONG_FORMAT, WAV_MAX_BIT_DEPTH, and WAV_MAX_CHANNELS.

Referenced by read_wav_header().

Here is the call graph for this function:

◆ read_wav_header()

int read_wav_header ( FILE * wav_file,
struct wav_metadata * metadata )

This function reads wav header

If read successfully, it leaves read file position at the beginning of data. Currently, only interleaved PCM is supported.

Return values
WAV_HDR_PARSE_OKif ok
WAV_HDR_PARSE_READ_ERRORin case of file read error
WAV_HDR_PARSE_WRONG_FORMATif unsupported wav format
WAV_HDR_PARSE_NOT_PCMnon-PCM WAV detected

< RF64 or BW64

Definition at line 215 of file wav_reader.c.

216{
217 char buffer[16];
218 uint32_t chunk_size;
219 rewind(wav_file);
220
221 _Bool rf64 = 0;
222 READ_N(buffer, 4);
223 if (strncmp(buffer, "RF64", 4) == 0 || strncmp(buffer, "BW64", 4) == 0) {
224 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Using %4.4s WAV file.\n", buffer);
225 rf64 = 1;
226 } 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);
229 }
230
231 // this is the length of the rest of the file, we may ignore
232 READ_N(&chunk_size, 4);
233
234 READ_N(buffer, 4);
235 if (strncmp(buffer, "WAVE", 4) != 0) {
236 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Expected WAVE chunk, %.4s given.\n", buffer);
238 }
239
240 bool found_data_chunk = false;
241 bool found_ds64_chunk = false;
242 bool found_fmt_chunk = false;
243 while (fread(buffer, 4, 1, wav_file) == 1) {
244 READ_N(&chunk_size, 4);
245 if (strncmp(buffer, "data", 4) == 0) {
246 if (!process_data_chunk(wav_file, chunk_size, metadata, found_ds64_chunk)) {
248 }
249 found_data_chunk = true;
250 break;
251 }
252 if (strncmp(buffer, "ds64", 4) == 0) {
253 if (!read_ds64(wav_file, chunk_size, metadata)) {
255 }
256 found_ds64_chunk = true;
257 } else if (strncmp(buffer, "fmt ", 4) == 0) {
258 found_fmt_chunk = true;
259 int rc = read_fmt_chunk(wav_file, metadata, chunk_size);
260 if (rc != WAV_HDR_PARSE_OK) {
261 return rc;
262 }
263 } else { // other tags
264 const char *known_tags[] = { "JUNK", "LIST", "id3 ", NULL }; // "olym" ?
265 int level = is_member(buffer, known_tags) ? LOG_LEVEL_VERBOSE : LOG_LEVEL_WARNING;
266 log_msg(level, MOD_NAME "Skipping chunk \"%4.4s\" sized %" PRIu32 " B!\n", buffer, chunk_size);
267 CHECK(fskip(wav_file, chunk_size), WAV_HDR_PARSE_READ_ERROR);
268 }
269 }
270
271 if (!found_data_chunk) {
272 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Data chunk not found!\n");
274 }
275
276 if (!found_fmt_chunk) {
277 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Fmt chunk not found!\n");
279 }
280
281 if (rf64 && !found_ds64_chunk) { // buggy, but we may try to continue witn implicit data size
282 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Broken WAV - a RF64 file detected but no ds64 chunk found!\n");
283 }
284
285 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "File parsed correctly - length %lld bytes, data offset %lld.\n",
286 metadata->data_size, metadata->data_offset);
287
288 return WAV_HDR_PARSE_OK;
289}
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
static _Bool read_ds64(FILE *wav_file, uint32_t chunk_size, struct wav_metadata *metadata)
Definition wav_reader.c:187
static _Bool process_data_chunk(FILE *wav_file, uint32_t chunk_size, struct wav_metadata *metadata, _Bool found_ds64_chunk)
Definition wav_reader.c:161
static _Bool is_member(const char *needle, const char **haystack)
Definition wav_reader.c:137
#define CHECK(cmd, retval)
Definition wav_reader.c:214
static int read_fmt_chunk(FILE *wav_file, struct wav_metadata *metadata, size_t chunk_size)
Definition wav_reader.c:67

References CHECK, wav_metadata::data_offset, wav_metadata::data_size, fskip(), is_member(), LOG_LEVEL_ERROR, LOG_LEVEL_VERBOSE, LOG_LEVEL_WARNING, log_msg(), MOD_NAME, process_data_chunk(), read_ds64(), read_fmt_chunk(), READ_N, WAV_HDR_PARSE_OK, WAV_HDR_PARSE_READ_ERROR, and WAV_HDR_PARSE_WRONG_FORMAT.

Referenced by audio_testcard_read_wav(), and init_audio().

Here is the call graph for this function:

◆ wav_read()

size_t wav_read ( void * buffer,
size_t sample_count,
FILE * wav_file,
struct wav_metadata * metadata )

Reads sample_count samples from wav_file according to metadata.

Note
conversion to signed is performed if wav sample type is u8

Definition at line 291 of file wav_reader.c.

292{
293 size_t samples = fread(buffer, metadata->ch_count * metadata->bits_per_sample / 8, sample_count, wav_file);
294 if (metadata->bits_per_sample == 8) {
295 signed2unsigned(buffer, buffer, samples * metadata->ch_count * metadata->bits_per_sample / 8);
296 }
297 return samples;
298}
void signed2unsigned(char *out, const char *in, int in_len)
Definition utils.cpp:515

References wav_metadata::bits_per_sample, wav_metadata::ch_count, and signed2unsigned().

Referenced by audio_reading_thread(), and audio_testcard_read_wav().

Here is the call graph for this function:

◆ wav_seek()

int wav_seek ( FILE * wav_file,
long offset,
int whence,
struct wav_metadata * metadata )

same as fseek but WAV header presence is respected (for SEEK SET)

Definition at line 319 of file wav_reader.c.

320{
321 if (whence == SEEK_CUR || whence == SEEK_END) {
322 return fseek(wav_file, offset, whence);
323 }
324 assert(whence == SEEK_SET);
325 return fseek(wav_file, metadata->data_offset + offset, SEEK_SET);
326}
static __device__ const float offset
Definition cuda_dxt.cu:139

References wav_metadata::data_offset, and offset.

Referenced by audio_reading_thread(), and reset_import().