From 65837e669bf8b02819f02db9d7d21a47babe1356 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 26 May 2009 03:32:26 +0200 Subject: add proper multichannel support (includes of WAVEX files) --- src/pulse.c | 51 ++++++++++++++++++++- src/read-sound-file.c | 9 ++++ src/read-sound-file.h | 24 ++++++++++ src/read-vorbis.c | 37 +++++++++++++++ src/read-vorbis.h | 3 ++ src/read-wav.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++---- src/read-wav.h | 1 + 7 files changed, 236 insertions(+), 10 deletions(-) diff --git a/src/pulse.c b/src/pulse.c index c959719..47dc96f 100644 --- a/src/pulse.c +++ b/src/pulse.c @@ -688,6 +688,45 @@ static const pa_sample_format_t sample_type_table[] = { [CA_SAMPLE_U8] = PA_SAMPLE_U8 }; +static const pa_channel_position_t channel_table[_CA_CHANNEL_POSITION_MAX] = { + [CA_CHANNEL_MONO] = PA_CHANNEL_POSITION_MONO, + [CA_CHANNEL_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [CA_CHANNEL_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [CA_CHANNEL_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [CA_CHANNEL_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [CA_CHANNEL_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [CA_CHANNEL_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER, + [CA_CHANNEL_LFE] = PA_CHANNEL_POSITION_LFE, + [CA_CHANNEL_FRONT_LEFT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + [CA_CHANNEL_FRONT_RIGHT_OF_CENTER] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + [CA_CHANNEL_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT, + [CA_CHANNEL_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT, + [CA_CHANNEL_TOP_CENTER] = PA_CHANNEL_POSITION_TOP_CENTER, + [CA_CHANNEL_TOP_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [CA_CHANNEL_TOP_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [CA_CHANNEL_TOP_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [CA_CHANNEL_TOP_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [CA_CHANNEL_TOP_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [CA_CHANNEL_TOP_REAR_CENTER] = PA_CHANNEL_POSITION_TOP_REAR_CENTER +}; + +static ca_bool_t convert_channel_map(ca_sound_file *f, pa_channel_map *cm) { + const ca_channel_position_t *positions; + unsigned c; + + ca_assert(f); + ca_assert(cm); + + if (!(positions = ca_sound_file_get_channel_map(f))) + return FALSE; + + cm->channels = ca_sound_file_get_nchannels(f); + for (c = 0; c < cm->channels; c++) + cm->map[c] = channel_table[positions[c]]; + + return TRUE; +} + int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_callback_t cb, void *userdata) { struct private *p; pa_proplist *l = NULL; @@ -701,6 +740,8 @@ int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_cal ca_bool_t volume_set = FALSE; pa_cvolume cvol; pa_sample_spec ss; + pa_channel_map cm; + ca_bool_t cm_good; ca_cache_control_t cache_control = CA_CACHE_CONTROL_NEVER; struct outstanding *out = NULL; int try = 3; @@ -841,6 +882,8 @@ int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_cal ss.channels = (uint8_t) ca_sound_file_get_nchannels(out->file); ss.rate = ca_sound_file_get_rate(out->file); + cm_good = convert_channel_map(out->file, &cm); + if (!name) { if (!(n = pa_proplist_gets(l, CA_PROP_MEDIA_NAME))) if (!(n = pa_proplist_gets(l, CA_PROP_MEDIA_NAME))) @@ -851,7 +894,7 @@ int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_cal pa_threaded_mainloop_lock(p->mainloop); - if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l))) { + if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, cm_good ? &cm : NULL, l))) { ret = translate_error(pa_context_errno(p->context)); pa_threaded_mainloop_unlock(p->mainloop); goto finish; @@ -997,6 +1040,8 @@ int driver_cache(ca_context *c, ca_proplist *proplist) { const char *n, *ct; char *name = NULL; pa_sample_spec ss; + pa_channel_map cm; + ca_bool_t cm_good; ca_cache_control_t cache_control = CA_CACHE_CONTROL_PERMANENT; struct outstanding *out; int ret; @@ -1062,6 +1107,8 @@ int driver_cache(ca_context *c, ca_proplist *proplist) { ss.channels = (uint8_t) ca_sound_file_get_nchannels(out->file); ss.rate = ca_sound_file_get_rate(out->file); + cm_good = convert_channel_map(out->file, &cm); + pa_threaded_mainloop_lock(p->mainloop); if (!p->context) { @@ -1070,7 +1117,7 @@ int driver_cache(ca_context *c, ca_proplist *proplist) { goto finish; } - if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, NULL, l))) { + if (!(out->stream = pa_stream_new_with_proplist(p->context, name, &ss, cm_good ? &cm : NULL, l))) { ret = translate_error(pa_context_errno(p->context)); pa_threaded_mainloop_unlock(p->mainloop); diff --git a/src/read-sound-file.c b/src/read-sound-file.c index bbadb02..ef20010 100644 --- a/src/read-sound-file.c +++ b/src/read-sound-file.c @@ -121,6 +121,15 @@ ca_sample_type_t ca_sound_file_get_sample_type(ca_sound_file *f) { return f->type; } +const ca_channel_position_t* ca_sound_file_get_channel_map(ca_sound_file *f) { + ca_assert(f); + + if (f->wav) + return ca_wav_get_channel_map(f->wav); + else + return ca_vorbis_get_channel_map(f->vorbis); +} + int ca_sound_file_read_int16(ca_sound_file *f, int16_t *d, size_t *n) { ca_return_val_if_fail(f, CA_ERROR_INVALID); ca_return_val_if_fail(d, CA_ERROR_INVALID); diff --git a/src/read-sound-file.h b/src/read-sound-file.h index 2107c56..d221324 100644 --- a/src/read-sound-file.h +++ b/src/read-sound-file.h @@ -30,6 +30,29 @@ typedef enum ca_sample_type { CA_SAMPLE_U8 } ca_sample_type_t; +typedef enum ca_channel_position { + CA_CHANNEL_MONO, + CA_CHANNEL_FRONT_LEFT, + CA_CHANNEL_FRONT_RIGHT, + CA_CHANNEL_FRONT_CENTER, + CA_CHANNEL_REAR_LEFT, + CA_CHANNEL_REAR_RIGHT, + CA_CHANNEL_REAR_CENTER, + CA_CHANNEL_LFE, + CA_CHANNEL_FRONT_LEFT_OF_CENTER, + CA_CHANNEL_FRONT_RIGHT_OF_CENTER, + CA_CHANNEL_SIDE_LEFT, + CA_CHANNEL_SIDE_RIGHT, + CA_CHANNEL_TOP_CENTER, + CA_CHANNEL_TOP_FRONT_LEFT, + CA_CHANNEL_TOP_FRONT_RIGHT, + CA_CHANNEL_TOP_FRONT_CENTER, + CA_CHANNEL_TOP_REAR_LEFT, + CA_CHANNEL_TOP_REAR_RIGHT, + CA_CHANNEL_TOP_REAR_CENTER, + _CA_CHANNEL_POSITION_MAX +} ca_channel_position_t; + typedef struct ca_sound_file ca_sound_file; int ca_sound_file_open(ca_sound_file **f, const char *fn); @@ -38,6 +61,7 @@ void ca_sound_file_close(ca_sound_file *f); unsigned ca_sound_file_get_nchannels(ca_sound_file *f); unsigned ca_sound_file_get_rate(ca_sound_file *f); ca_sample_type_t ca_sound_file_get_sample_type(ca_sound_file *f); +const ca_channel_position_t* ca_sound_file_get_channel_map(ca_sound_file *f); off_t ca_sound_file_get_size(ca_sound_file *f); diff --git a/src/read-vorbis.c b/src/read-vorbis.c index e450cb7..4d1fcb0 100644 --- a/src/read-vorbis.c +++ b/src/read-vorbis.c @@ -35,6 +35,7 @@ struct ca_vorbis { OggVorbis_File ovf; off_t size; + ca_channel_position_t channel_map[6]; }; static int convert_error(int or) { @@ -130,6 +131,42 @@ unsigned ca_vorbis_get_rate(ca_vorbis *v) { return (unsigned) vi->rate; } +const ca_channel_position_t* ca_vorbis_get_channel_map(ca_vorbis *v) { + + /* See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */ + + switch (ca_vorbis_get_nchannels(v)) { + + case 6: + v->channel_map[5] = CA_CHANNEL_LFE; + /* fall through */ + + case 5: + v->channel_map[3] = CA_CHANNEL_REAR_LEFT; + v->channel_map[4] = CA_CHANNEL_REAR_RIGHT; + /* fall through */ + + case 3: + v->channel_map[0] = CA_CHANNEL_FRONT_LEFT; + v->channel_map[1] = CA_CHANNEL_FRONT_CENTER; + v->channel_map[2] = CA_CHANNEL_FRONT_RIGHT; + return v->channel_map; + + case 4: + v->channel_map[2] = CA_CHANNEL_REAR_LEFT; + v->channel_map[3] = CA_CHANNEL_REAR_RIGHT; + /* fall through */ + + case 1: + v->channel_map[0] = CA_CHANNEL_FRONT_LEFT; + v->channel_map[1] = CA_CHANNEL_FRONT_RIGHT; + return v->channel_map; + + } + + return NULL; +} + int ca_vorbis_read_s16ne(ca_vorbis *v, int16_t *d, size_t *n){ long r; int section; diff --git a/src/read-vorbis.h b/src/read-vorbis.h index c98c7e5..6edbcaf 100644 --- a/src/read-vorbis.h +++ b/src/read-vorbis.h @@ -24,6 +24,8 @@ #include #include +#include "read-sound-file.h" + typedef struct ca_vorbis ca_vorbis; int ca_vorbis_open(ca_vorbis **v, FILE *f); @@ -31,6 +33,7 @@ void ca_vorbis_close(ca_vorbis *v); unsigned ca_vorbis_get_nchannels(ca_vorbis *v); unsigned ca_vorbis_get_rate(ca_vorbis *v); +const ca_channel_position_t* ca_vorbis_get_channel_map(ca_vorbis *v); int ca_vorbis_read_s16ne(ca_vorbis *v, int16_t *d, size_t *n); diff --git a/src/read-wav.c b/src/read-wav.c index 0313a3b..95c46c8 100644 --- a/src/read-wav.c +++ b/src/read-wav.c @@ -29,6 +29,50 @@ #define FILE_SIZE_MAX (64U*1024U*1024U) +/* Stores the bit indexes in dwChannelMask */ +enum { + BIT_FRONT_LEFT, + BIT_FRONT_RIGHT, + BIT_FRONT_CENTER, + BIT_LOW_FREQUENCY, + BIT_BACK_LEFT, + BIT_BACK_RIGHT, + BIT_FRONT_LEFT_OF_CENTER, + BIT_FRONT_RIGHT_OF_CENTER, + BIT_BACK_CENTER, + BIT_SIDE_LEFT, + BIT_SIDE_RIGHT, + BIT_TOP_CENTER, + BIT_TOP_FRONT_LEFT, + BIT_TOP_FRONT_CENTER, + BIT_TOP_FRONT_RIGHT, + BIT_TOP_BACK_LEFT, + BIT_TOP_BACK_CENTER, + BIT_TOP_BACK_RIGHT, + _BIT_MAX +}; + +static const ca_channel_position_t channel_table[_BIT_MAX] = { + [BIT_FRONT_LEFT] = CA_CHANNEL_FRONT_LEFT, + [BIT_FRONT_RIGHT] = CA_CHANNEL_FRONT_RIGHT, + [BIT_FRONT_CENTER] = CA_CHANNEL_FRONT_CENTER, + [BIT_LOW_FREQUENCY] = CA_CHANNEL_LFE, + [BIT_BACK_LEFT] = CA_CHANNEL_REAR_LEFT, + [BIT_BACK_RIGHT] = CA_CHANNEL_REAR_RIGHT, + [BIT_FRONT_LEFT_OF_CENTER] = CA_CHANNEL_FRONT_LEFT_OF_CENTER, + [BIT_FRONT_RIGHT_OF_CENTER] = CA_CHANNEL_FRONT_RIGHT_OF_CENTER, + [BIT_BACK_CENTER] = CA_CHANNEL_REAR_CENTER, + [BIT_SIDE_LEFT] = CA_CHANNEL_SIDE_LEFT, + [BIT_SIDE_RIGHT] = CA_CHANNEL_SIDE_RIGHT, + [BIT_TOP_CENTER] = CA_CHANNEL_TOP_CENTER, + [BIT_TOP_FRONT_LEFT] = CA_CHANNEL_TOP_FRONT_LEFT, + [BIT_TOP_FRONT_CENTER] = CA_CHANNEL_TOP_FRONT_CENTER, + [BIT_TOP_FRONT_RIGHT] = CA_CHANNEL_TOP_FRONT_RIGHT, + [BIT_TOP_BACK_LEFT] = CA_CHANNEL_TOP_REAR_LEFT, + [BIT_TOP_BACK_CENTER] = CA_CHANNEL_TOP_REAR_CENTER, + [BIT_TOP_BACK_RIGHT] = CA_CHANNEL_TOP_REAR_RIGHT +}; + struct ca_wav { FILE *file; @@ -36,6 +80,17 @@ struct ca_wav { unsigned nchannels; unsigned rate; unsigned depth; + uint32_t channel_mask; + + ca_channel_position_t channel_map[_BIT_MAX]; +}; + +#define CHUNK_ID_DATA 0x61746164U +#define CHUNK_ID_FMT 0x20746d66U + +static const uint8_t pcm_guid[16] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; static int skip_to_chunk(ca_wav *w, uint32_t id, uint32_t *size) { @@ -77,10 +132,12 @@ fail_io: } int ca_wav_open(ca_wav **_w, FILE *f) { - uint32_t header[3], fmt_chunk[4]; + uint32_t header[3], fmt_chunk[10]; int ret; ca_wav *w; uint32_t file_size, fmt_size, data_size; + ca_bool_t extensible; + uint32_t format; ca_return_val_if_fail(_w, CA_ERROR_INVALID); ca_return_val_if_fail(f, CA_ERROR_INVALID); @@ -107,22 +164,47 @@ int ca_wav_open(ca_wav **_w, FILE *f) { } /* Skip to the fmt chunk */ - if ((ret = skip_to_chunk(w, 0x20746d66U, &fmt_size)) < 0) + if ((ret = skip_to_chunk(w, CHUNK_ID_FMT, &fmt_size)) < 0) goto fail; - if (fmt_size != 16) { - ret = CA_ERROR_NOTSUPPORTED; - goto fail; + switch (fmt_size) { + + case 14: /* WAVEFORMAT */ + case 16: + case 18: /* WAVEFORMATEX */ + extensible = FALSE; + break; + + case 40: /* WAVEFORMATEXTENSIBLE */ + extensible = TRUE; + break; + + default: + ret = CA_ERROR_NOTSUPPORTED; + goto fail; } - if (fread(fmt_chunk, sizeof(uint32_t), CA_ELEMENTSOF(fmt_chunk), f) != CA_ELEMENTSOF(fmt_chunk)) + if (fread(fmt_chunk, 1, fmt_size, f) != fmt_size) goto fail_io; - if ((CA_UINT32_FROM_LE(fmt_chunk[0]) & 0xFFFF) != 1) { + /* PCM? or WAVEX? */ + format = (CA_UINT32_FROM_LE(fmt_chunk[0]) & 0xFFFF); + if ((!extensible && format != 0x0001) || + (extensible && format != 0xFFFE)) { ret = CA_ERROR_NOTSUPPORTED; goto fail; } + if (extensible) { + if (memcmp(fmt_chunk + 6, pcm_guid, 16) != 0) { + ret = CA_ERROR_NOTSUPPORTED; + goto fail; + } + + w->channel_mask = CA_UINT32_FROM_LE(fmt_chunk[5]); + } else + w->channel_mask = 0; + w->nchannels = CA_UINT32_FROM_LE(fmt_chunk[0]) >> 16; w->rate = CA_UINT32_FROM_LE(fmt_chunk[1]); w->depth = CA_UINT32_FROM_LE(fmt_chunk[3]) >> 16; @@ -138,7 +220,7 @@ int ca_wav_open(ca_wav **_w, FILE *f) { } /* Skip to the data chunk */ - if ((ret = skip_to_chunk(w, 0x61746164U, &data_size)) < 0) + if ((ret = skip_to_chunk(w, CHUNK_ID_DATA, &data_size)) < 0) goto fail; w->data_size = (off_t) data_size; @@ -186,6 +268,29 @@ unsigned ca_wav_get_rate(ca_wav *w) { return w->rate; } +const ca_channel_position_t* ca_wav_get_channel_map(ca_wav *w) { + unsigned c; + ca_channel_position_t *p; + + ca_assert(w); + + if (!w->channel_mask) + return NULL; + + p = w->channel_map; + + for (c = 0; c < _BIT_MAX; c++) + if ((w->channel_mask & (1 << c))) + *(p++) = channel_table[c]; + + ca_assert(p <= w->channel_map + _BIT_MAX); + + if (p != w->channel_map + w->nchannels) + return NULL; + + return w->channel_map; +} + ca_sample_type_t ca_wav_get_sample_type(ca_wav *w) { ca_assert(w); diff --git a/src/read-wav.h b/src/read-wav.h index 00708c3..e7c6178 100644 --- a/src/read-wav.h +++ b/src/read-wav.h @@ -33,6 +33,7 @@ void ca_wav_close(ca_wav *f); unsigned ca_wav_get_nchannels(ca_wav *f); unsigned ca_wav_get_rate(ca_wav *f); ca_sample_type_t ca_wav_get_sample_type(ca_wav *f); +const ca_channel_position_t* ca_wav_get_channel_map(ca_wav *f); int ca_wav_read_u8(ca_wav *f, uint8_t *d, size_t *n); int ca_wav_read_s16le(ca_wav *f, int16_t *d, size_t *n); -- cgit