Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions include/tinycompress/tinywave.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#ifndef __TINYWAVE_H
#define __TINYWAVE_H

#include <stdio.h>

struct riff_chunk {
char desc[4];
uint32_t size;
Expand All @@ -34,10 +36,71 @@ struct wave_header {
} __attribute__((__packed__)) data;
} __attribute__((__packed__));

/* WAVE format types */
#define WAVE_FORMAT_PCM 0x0001
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE

/* WAV channel mask - speaker position bit flags (Microsoft standard) */
#define WAV_SPEAKER_FRONT_LEFT 0x00000001
#define WAV_SPEAKER_FRONT_RIGHT 0x00000002
#define WAV_SPEAKER_FRONT_CENTER 0x00000004
#define WAV_SPEAKER_LOW_FREQUENCY 0x00000008
#define WAV_SPEAKER_BACK_LEFT 0x00000010
#define WAV_SPEAKER_BACK_RIGHT 0x00000020
#define WAV_SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
#define WAV_SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
#define WAV_SPEAKER_BACK_CENTER 0x00000100
#define WAV_SPEAKER_SIDE_LEFT 0x00000200
#define WAV_SPEAKER_SIDE_RIGHT 0x00000400
#define WAV_SPEAKER_TOP_CENTER 0x00000800
#define WAV_SPEAKER_TOP_FRONT_LEFT 0x00001000
#define WAV_SPEAKER_TOP_FRONT_CENTER 0x00002000
#define WAV_SPEAKER_TOP_FRONT_RIGHT 0x00004000
#define WAV_SPEAKER_TOP_BACK_LEFT 0x00008000
#define WAV_SPEAKER_TOP_BACK_CENTER 0x00010000
#define WAV_SPEAKER_TOP_BACK_RIGHT 0x00020000

/* Standard channel masks for common multi-channel configurations */
#define WAV_CHANNEL_MASK_MONO (WAV_SPEAKER_FRONT_CENTER)
#define WAV_CHANNEL_MASK_STEREO (WAV_SPEAKER_FRONT_LEFT | \
WAV_SPEAKER_FRONT_RIGHT)
#define WAV_CHANNEL_MASK_5_1 (WAV_SPEAKER_FRONT_LEFT | \
WAV_SPEAKER_FRONT_RIGHT | \
WAV_SPEAKER_FRONT_CENTER | \
WAV_SPEAKER_LOW_FREQUENCY | \
WAV_SPEAKER_BACK_LEFT | \
WAV_SPEAKER_BACK_RIGHT)
#define WAV_CHANNEL_MASK_7_1 (WAV_SPEAKER_FRONT_LEFT | \
WAV_SPEAKER_FRONT_RIGHT | \
WAV_SPEAKER_FRONT_CENTER | \
WAV_SPEAKER_LOW_FREQUENCY | \
WAV_SPEAKER_BACK_LEFT | \
WAV_SPEAKER_BACK_RIGHT | \
WAV_SPEAKER_SIDE_LEFT | \
WAV_SPEAKER_SIDE_RIGHT)

void init_wave_header(struct wave_header *header, uint16_t channels,
uint32_t rate, uint16_t samplebits);
void size_wave_header(struct wave_header *header, uint32_t size);

int parse_wave_header(struct wave_header *header, unsigned int *channels,
unsigned int *rate, unsigned int *format);

/**
* parse_wave_file() - Parse WAV file with proper chunk scanning
* @file: FILE pointer positioned at beginning of file
* @channels: output - number of channels
* @rate: output - sample rate in Hz
* @format: output - ALSA PCM format (SNDRV_PCM_FORMAT_*)
* @channel_mask: output - WAV channel mask (speaker position bitmask),
* 0 if not available (basic PCM format infers default mapping)
*
* Handles both basic PCM (type 0x0001) and WAVE_FORMAT_EXTENSIBLE (type 0xFFFE)
* formats. Properly scans chunks to find the "data" chunk, leaving the file
* pointer positioned at the start of the audio data.
*
* Returns 0 on success, -1 on error.
*/
int parse_wave_file(FILE *file, unsigned int *channels, unsigned int *rate,
unsigned int *format, unsigned int *channel_mask);
#endif
15 changes: 4 additions & 11 deletions src/utils/cplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,21 +533,14 @@ int main(int argc, char **argv)
void get_codec_pcm(FILE *file, struct compr_config *config,
struct snd_codec *codec)
{
size_t read;
struct wave_header header;
unsigned int channels, rate, format;
unsigned int channels, rate, format, channel_mask;

read = fread(&header, 1, sizeof(header), file);
if (read != sizeof(header)) {
fprintf(stderr, "Unable to read header \n");
if (parse_wave_file(file, &channels, &rate, &format, &channel_mask) == -1) {
fclose(file);
exit(EXIT_FAILURE);
}

if (parse_wave_header(&header, &channels, &rate, &format) == -1) {
fclose(file);
exit(EXIT_FAILURE);
}
/* File pointer is now at start of audio data */

codec->id = SND_AUDIOCODEC_PCM;
codec->ch_in = channels;
Expand All @@ -562,7 +555,7 @@ void get_codec_pcm(FILE *file, struct compr_config *config,
codec->rate_control = 0;
codec->profile = SND_AUDIOCODEC_PCM;
codec->level = 0;
codec->ch_mode = 0;
codec->ch_mode = channel_mask;
codec->format = format;
}
#endif
Expand Down
262 changes: 262 additions & 0 deletions src/utils/wave.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,265 @@ int parse_wave_header(struct wave_header *header, unsigned int *channels,

return 0;
}

/* WAVE_FORMAT_EXTENSIBLE sub-format GUIDs (first 2 bytes are the format tag) */
static const uint8_t pcm_subformat_guid[16] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
};

/**
* get_default_channel_mask() - Get default WAV channel mask for a given
* channel count (per Microsoft WAV specification)
*/
static unsigned int get_default_channel_mask(unsigned int channels)
{
switch (channels) {
case 1:
return WAV_CHANNEL_MASK_MONO;
case 2:
return WAV_CHANNEL_MASK_STEREO;
case 3:
return WAV_SPEAKER_FRONT_LEFT | WAV_SPEAKER_FRONT_RIGHT |
WAV_SPEAKER_FRONT_CENTER;
case 4:
return WAV_SPEAKER_FRONT_LEFT | WAV_SPEAKER_FRONT_RIGHT |
WAV_SPEAKER_BACK_LEFT | WAV_SPEAKER_BACK_RIGHT;
case 5:
return WAV_SPEAKER_FRONT_LEFT | WAV_SPEAKER_FRONT_RIGHT |
WAV_SPEAKER_FRONT_CENTER |
WAV_SPEAKER_BACK_LEFT | WAV_SPEAKER_BACK_RIGHT;
case 6:
return WAV_CHANNEL_MASK_5_1;
case 7:
return WAV_SPEAKER_FRONT_LEFT | WAV_SPEAKER_FRONT_RIGHT |
WAV_SPEAKER_FRONT_CENTER | WAV_SPEAKER_LOW_FREQUENCY |
WAV_SPEAKER_BACK_LEFT | WAV_SPEAKER_BACK_RIGHT |
WAV_SPEAKER_BACK_CENTER;
case 8:
return WAV_CHANNEL_MASK_7_1;
default:
return 0;
}
}

static const char *speaker_name(unsigned int mask)
{
switch (mask) {
case WAV_SPEAKER_FRONT_LEFT: return "FL";
case WAV_SPEAKER_FRONT_RIGHT: return "FR";
case WAV_SPEAKER_FRONT_CENTER: return "FC";
case WAV_SPEAKER_LOW_FREQUENCY: return "LFE";
case WAV_SPEAKER_BACK_LEFT: return "BL";
case WAV_SPEAKER_BACK_RIGHT: return "BR";
case WAV_SPEAKER_FRONT_LEFT_OF_CENTER: return "FLC";
case WAV_SPEAKER_FRONT_RIGHT_OF_CENTER: return "FRC";
case WAV_SPEAKER_BACK_CENTER: return "BC";
case WAV_SPEAKER_SIDE_LEFT: return "SL";
case WAV_SPEAKER_SIDE_RIGHT: return "SR";
default: return "?";
}
}

static void print_channel_map(unsigned int channels, unsigned int channel_mask)
{
unsigned int i, ch = 0;

fprintf(stderr, "Channel map (%u ch, mask 0x%04x): ", channels,
channel_mask);

for (i = 0; i < 18 && ch < channels; i++) {
unsigned int bit = 1u << i;

if (channel_mask & bit) {
if (ch > 0)
fprintf(stderr, ", ");
fprintf(stderr, "ch%u=%s", ch, speaker_name(bit));
ch++;
}
}
fprintf(stderr, "\n");
}

int parse_wave_file(FILE *file, unsigned int *channels, unsigned int *rate,
unsigned int *format, unsigned int *channel_mask)
{
struct riff_chunk chunk;
char riff_format[4];
uint16_t fmt_type, fmt_channels, fmt_blockalign, fmt_samplebits;
uint32_t fmt_rate, fmt_byterate;
uint16_t fmt_cb_size;
uint16_t fmt_valid_bits;
uint32_t fmt_channel_mask;
uint8_t fmt_subformat[16];
uint32_t fmt_chunk_size;
long fmt_end;
int found_fmt = 0, found_data = 0;
size_t nread;

/* Seek to beginning */
if (fseek(file, 0, SEEK_SET) < 0) {
fprintf(stderr, "Failed to seek to start of file\n");
return -1;
}

/* Read RIFF header */
nread = fread(&chunk, 1, sizeof(chunk), file);
if (nread != sizeof(chunk)) {
fprintf(stderr, "Failed to read RIFF header\n");
return -1;
}

if (strncmp(chunk.desc, "RIFF", 4) != 0) {
fprintf(stderr, "RIFF magic not found\n");
return -1;
}

nread = fread(riff_format, 1, sizeof(riff_format), file);
if (nread != sizeof(riff_format)) {
fprintf(stderr, "Failed to read RIFF format\n");
return -1;
}

if (strncmp(riff_format, "WAVE", 4) != 0) {
fprintf(stderr, "WAVE magic not found\n");
return -1;
}

/* Scan chunks until we find both "fmt " and "data" */
while (!found_data) {
nread = fread(&chunk, 1, sizeof(chunk), file);
if (nread != sizeof(chunk)) {
fprintf(stderr, "Unexpected end of file while scanning chunks\n");
return -1;
}

if (strncmp(chunk.desc, "fmt ", 4) == 0) {
fmt_chunk_size = chunk.size;
fmt_end = ftell(file) + fmt_chunk_size;

/* Read basic fmt fields (16 bytes) */
if (fmt_chunk_size < 16) {
fprintf(stderr, "fmt chunk too small (%u)\n",
fmt_chunk_size);
return -1;
}

nread = fread(&fmt_type, 1, sizeof(fmt_type), file);
nread += fread(&fmt_channels, 1, sizeof(fmt_channels), file);
nread += fread(&fmt_rate, 1, sizeof(fmt_rate), file);
nread += fread(&fmt_byterate, 1, sizeof(fmt_byterate), file);
nread += fread(&fmt_blockalign, 1, sizeof(fmt_blockalign), file);
nread += fread(&fmt_samplebits, 1, sizeof(fmt_samplebits), file);
if (nread != 16) {
fprintf(stderr, "Failed to read fmt fields\n");
return -1;
}

fmt_channel_mask = 0;

if (fmt_type == WAVE_FORMAT_EXTENSIBLE) {
/* Need at least 2 (cbSize) + 2 (validBits) +
* 4 (channelMask) + 16 (subformat) = 24 extra bytes
*/
if (fmt_chunk_size < 40) {
fprintf(stderr,
"WAVE_FORMAT_EXTENSIBLE fmt chunk too small (%u, need 40)\n",
fmt_chunk_size);
return -1;
}

nread = fread(&fmt_cb_size, 1, sizeof(fmt_cb_size), file);
nread += fread(&fmt_valid_bits, 1, sizeof(fmt_valid_bits), file);
nread += fread(&fmt_channel_mask, 1, sizeof(fmt_channel_mask), file);
nread += fread(fmt_subformat, 1, sizeof(fmt_subformat), file);
if (nread != 24) {
fprintf(stderr,
"Failed to read extensible fmt fields\n");
return -1;
}

/* Verify subformat GUID is PCM */
if (memcmp(fmt_subformat, pcm_subformat_guid,
sizeof(pcm_subformat_guid)) != 0) {
fprintf(stderr,
"Unsupported subformat (not PCM)\n");
return -1;
}

/* Use valid bits for format selection */
fmt_samplebits = fmt_valid_bits;

fprintf(stderr,
"WAVE_FORMAT_EXTENSIBLE: %u ch, %u Hz, %u bits, channel mask 0x%04x\n",
fmt_channels, fmt_rate, fmt_samplebits,
fmt_channel_mask);
} else if (fmt_type == WAVE_FORMAT_PCM) {
/* Basic PCM - use default channel mask */
fmt_channel_mask = get_default_channel_mask(fmt_channels);

if (fmt_channels > 2)
fprintf(stderr,
"Basic PCM with %u channels, using default channel mask 0x%04x\n",
fmt_channels, fmt_channel_mask);
} else {
fprintf(stderr,
"Unsupported WAVE format type: 0x%04x\n",
fmt_type);
return -1;
}

/* Seek to end of fmt chunk (skip any remaining bytes) */
if (fseek(file, fmt_end, SEEK_SET) < 0) {
fprintf(stderr, "Failed to seek past fmt chunk\n");
return -1;
}

found_fmt = 1;

} else if (strncmp(chunk.desc, "data", 4) == 0) {
if (!found_fmt) {
fprintf(stderr,
"data chunk found before fmt chunk\n");
return -1;
}
/* File pointer is now at start of audio data */
found_data = 1;

} else {
/* Unknown chunk - skip it */
if (fseek(file, chunk.size, SEEK_CUR) < 0) {
fprintf(stderr,
"Failed to skip chunk '%.4s' (%u bytes)\n",
chunk.desc, chunk.size);
return -1;
}
}
}

*channels = fmt_channels;
*rate = fmt_rate;
*channel_mask = fmt_channel_mask;

switch (fmt_samplebits) {
case 8:
*format = SNDRV_PCM_FORMAT_U8;
break;
case 16:
*format = SNDRV_PCM_FORMAT_S16_LE;
break;
case 24:
*format = SNDRV_PCM_FORMAT_S24_LE;
break;
case 32:
*format = SNDRV_PCM_FORMAT_S32_LE;
break;
default:
fprintf(stderr, "Unsupported sample bits %d\n", fmt_samplebits);
return -1;
}

print_channel_map(fmt_channels, fmt_channel_mask);

return 0;
}