Commit cc5c1d88 authored by Josh Coalson's avatar Josh Coalson

complete WAVEFORMATEXTENSIBLE support, multichannel assignments in format and...

complete WAVEFORMATEXTENSIBLE support, multichannel assignments in format and documentation, multichannel support for AIFF and WAVE, channel mapping, new --channel-map=none option to flac, saving and restoring of WAVEFORMATEXTENSIBLE channel mask to/from tag, robust handling of "odd" sample resolutions (i.e. not multiple of 8 bits) for AIFF and WAVE
parent b73fff6e
This diff is collapsed.
...@@ -47,6 +47,7 @@ typedef struct { ...@@ -47,6 +47,7 @@ typedef struct {
utils__SkipUntilSpecification until_specification; utils__SkipUntilSpecification until_specification;
FLAC__bool has_cue_specification; FLAC__bool has_cue_specification;
utils__CueSpecification cue_specification; utils__CueSpecification cue_specification;
FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
} decode_options_t; } decode_options_t;
/* used for AIFF also */ /* used for AIFF also */
......
This diff is collapsed.
...@@ -59,6 +59,7 @@ typedef struct { ...@@ -59,6 +59,7 @@ typedef struct {
int num_requested_seek_points; int num_requested_seek_points;
const char *cuesheet_filename; const char *cuesheet_filename;
FLAC__bool cued_seekpoints; FLAC__bool cued_seekpoints;
FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
/* options related to --replay-gain and --sector-align */ /* options related to --replay-gain and --sector-align */
FLAC__bool is_first_file; FLAC__bool is_first_file;
......
...@@ -106,6 +106,7 @@ static struct share__option long_options_[] = { ...@@ -106,6 +106,7 @@ static struct share__option long_options_[] = {
{ "output-name" , share__required_argument, 0, 'o' }, { "output-name" , share__required_argument, 0, 'o' },
{ "skip" , share__required_argument, 0, 0 }, { "skip" , share__required_argument, 0, 0 },
{ "until" , share__required_argument, 0, 0 }, { "until" , share__required_argument, 0, 0 },
{ "channel-map" , share__required_argument, 0, 0 }, /* undocumented */
/* /*
* decoding options * decoding options
...@@ -255,6 +256,7 @@ static struct { ...@@ -255,6 +256,7 @@ static struct {
int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */ int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */
const char *cuesheet_filename; const char *cuesheet_filename;
FLAC__bool cued_seekpoints; FLAC__bool cued_seekpoints;
FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
unsigned num_files; unsigned num_files;
char **filenames; char **filenames;
...@@ -604,6 +606,7 @@ FLAC__bool init_options() ...@@ -604,6 +606,7 @@ FLAC__bool init_options()
option_values.num_requested_seek_points = -1; option_values.num_requested_seek_points = -1;
option_values.cuesheet_filename = 0; option_values.cuesheet_filename = 0;
option_values.cued_seekpoints = true; option_values.cued_seekpoints = true;
option_values.channel_map_none = false;;
option_values.num_files = 0; option_values.num_files = 0;
option_values.filenames = 0; option_values.filenames = 0;
...@@ -734,6 +737,11 @@ int parse_option(int short_option, const char *long_option, const char *option_a ...@@ -734,6 +737,11 @@ int parse_option(int short_option, const char *long_option, const char *option_a
} }
} }
} }
else if(0 == strcmp(long_option, "channel-map")) {
if (0 == option_argument || strcmp(option_argument, "none"))
return usage_error("ERROR: only --channel-map=none currently supported\n");
option_values.channel_map_none = true;
}
else if(0 == strcmp(long_option, "cuesheet")) { else if(0 == strcmp(long_option, "cuesheet")) {
FLAC__ASSERT(0 != option_argument); FLAC__ASSERT(0 != option_argument);
option_values.cuesheet_filename = option_argument; option_values.cuesheet_filename = option_argument;
...@@ -1752,6 +1760,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_ ...@@ -1752,6 +1760,7 @@ int encode_file(const char *infilename, FLAC__bool is_first_file, FLAC__bool is_
common_options.num_requested_seek_points = option_values.num_requested_seek_points; common_options.num_requested_seek_points = option_values.num_requested_seek_points;
common_options.cuesheet_filename = option_values.cuesheet_filename; common_options.cuesheet_filename = option_values.cuesheet_filename;
common_options.cued_seekpoints = option_values.cued_seekpoints; common_options.cued_seekpoints = option_values.cued_seekpoints;
common_options.channel_map_none = option_values.channel_map_none;
common_options.is_first_file = is_first_file; common_options.is_first_file = is_first_file;
common_options.is_last_file = is_last_file; common_options.is_last_file = is_last_file;
common_options.align_reservoir = align_reservoir; common_options.align_reservoir = align_reservoir;
...@@ -1919,6 +1928,7 @@ int decode_file(const char *infilename) ...@@ -1919,6 +1928,7 @@ int decode_file(const char *infilename)
common_options.use_first_serial_number = !option_values.has_serial_number; common_options.use_first_serial_number = !option_values.has_serial_number;
common_options.serial_number = option_values.serial_number; common_options.serial_number = option_values.serial_number;
#endif #endif
common_options.channel_map_none = option_values.channel_map_none;
if(!option_values.force_raw_format) { if(!option_values.force_raw_format) {
wav_decode_options_t options; wav_decode_options_t options;
......
...@@ -22,11 +22,14 @@ ...@@ -22,11 +22,14 @@
#include "utils.h" #include "utils.h"
#include "FLAC/assert.h" #include "FLAC/assert.h"
#include "FLAC/metadata.h"
#include <math.h> #include <math.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
int flac__utils_verbosity_ = 2; int flac__utils_verbosity_ = 2;
static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value)
...@@ -269,3 +272,40 @@ void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *c ...@@ -269,3 +272,40 @@ void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *c
else else
until_spec->value.samples = total_samples; until_spec->value.samples = total_samples;
} }
FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask)
{
FLAC__StreamMetadata_VorbisComment_Entry entry = { 0, 0 };
char tag[128];
FLAC__ASSERT(object);
FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC__ASSERT(strlen(CHANNEL_MASK_TAG+1+2+16+1) <= sizeof(tag)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */
entry.entry = (FLAC__byte*)tag;
if((entry.length = snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (unsigned)channel_mask)) >= sizeof(tag))
return false;
if(!FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, /*all=*/true, /*copy=*/true))
return false;
return true;
}
FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask)
{
int offset;
unsigned val;
char *p;
FLAC__ASSERT(object);
FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG)))
return false;
if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG)+4)
return false;
if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */
return false;
if(strncmp(p, "=0x", 3))
return false;
if(sscanf(p+3, "%x", &val) != 1)
return false;
*channel_mask = val;
return true;
}
...@@ -53,4 +53,7 @@ void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecifica ...@@ -53,4 +53,7 @@ void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecifica
FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec); FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec);
void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec); void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec);
FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask);
FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask);
#endif #endif
...@@ -614,8 +614,9 @@ foo: ...@@ -614,8 +614,9 @@ foo:
return false; return false;
} }
static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bytes_per_sample, unsigned samples) static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsigned channels, unsigned bytes_per_sample, unsigned samples, FLAC__bool strict)
{ {
const FLAC__bool waveformatextensible = strict && channels > 2;
const unsigned true_size = channels * bytes_per_sample * samples; const unsigned true_size = channels * bytes_per_sample * samples;
const unsigned padded_size = (true_size + 1) & (~1u); const unsigned padded_size = (true_size + 1) & (~1u);
FILE *f; FILE *f;
...@@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig ...@@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
return false; return false;
if(fwrite("RIFF", 1, 4, f) < 4) if(fwrite("RIFF", 1, 4, f) < 4)
goto foo; goto foo;
if(!write_little_endian_uint32(f, padded_size + 36)) if(!write_little_endian_uint32(f, padded_size + (waveformatextensible?60:36)))
goto foo; goto foo;
if(fwrite("WAVEfmt \020\000\000\000\001\000", 1, 14, f) < 14) if(fwrite("WAVEfmt ", 1, 8, f) < 8)
goto foo;
if(!write_little_endian_uint32(f, waveformatextensible?40:16))
goto foo;
if(!write_little_endian_uint16(f, waveformatextensible?65534:1))
goto foo; goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)channels)) if(!write_little_endian_uint16(f, (FLAC__uint16)channels))
goto foo; goto foo;
...@@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig ...@@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
goto foo; goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample))) if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample)))
goto foo; goto foo;
if(waveformatextensible) {
if(!write_little_endian_uint16(f, (FLAC__uint16)22)) /* cbSize */
goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample))) /* validBitsPerSample */
goto foo;
if(!write_little_endian_uint32(f, 0)) /* channelMask */
goto foo;
/* GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} */
if(fwrite("\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71", 1, 16, f) != 16)
goto foo;
}
if(fwrite("data", 1, 4, f) < 4) if(fwrite("data", 1, 4, f) < 4)
goto foo; goto foo;
if(!write_little_endian_uint32(f, true_size)) if(!write_little_endian_uint32(f, true_size))
...@@ -815,7 +831,7 @@ int main(int argc, char *argv[]) ...@@ -815,7 +831,7 @@ int main(int argc, char *argv[])
return 1; return 1;
sprintf(fn, "rt-%u-%u-%u.wav", channels, bytes_per_sample, nsamples[samples]); sprintf(fn, "rt-%u-%u-%u.wav", channels, bytes_per_sample, nsamples[samples]);
if(!generate_wav(fn, 44100, channels, bytes_per_sample, nsamples[samples])) if(!generate_wav(fn, 44100, channels, bytes_per_sample, nsamples[samples], /*strict=*/true))
return 1; return 1;
} }
} }
......
...@@ -159,9 +159,9 @@ rt_test_wav () ...@@ -159,9 +159,9 @@ rt_test_wav ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f) encode... " echo -n "round-trip test ($f) encode... "
run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR"
echo -n "decode... " echo -n "decode... "
run_flac $SILENT --force --decode -o rt.wav rt.flac || die "ERROR" run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt.flac || die "ERROR"
echo -n "compare... " echo -n "compare... "
cmp $f rt.wav || die "ERROR: file mismatch" cmp $f rt.wav || die "ERROR: file mismatch"
echo "OK" echo "OK"
...@@ -172,9 +172,9 @@ rt_test_aiff () ...@@ -172,9 +172,9 @@ rt_test_aiff ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f) encode... " echo -n "round-trip test ($f) encode... "
run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR"
echo -n "decode... " echo -n "decode... "
run_flac $SILENT --force --decode -o rt.aiff rt.flac || die "ERROR" run_flac $SILENT --force --decode --channel-map=none -o rt.aiff rt.flac || die "ERROR"
echo -n "compare... " echo -n "compare... "
cmp $f rt.aiff || die "ERROR: file mismatch" cmp $f rt.aiff || die "ERROR: file mismatch"
echo "OK" echo "OK"
...@@ -186,11 +186,11 @@ rt_test_flac () ...@@ -186,11 +186,11 @@ rt_test_flac ()
{ {
f="$1" f="$1"
echo -n "round-trip test ($f->flac->flac->wav) encode... " echo -n "round-trip test ($f->flac->flac->wav) encode... "
run_flac $SILENT --force --verify $f -o rt.flac || die "ERROR" run_flac $SILENT --force --verify --channel-map=none $f -o rt.flac || die "ERROR"
echo -n "re-encode... " echo -n "re-encode... "
run_flac $SILENT --force --verify -o rt2.flac rt.flac || die "ERROR" run_flac $SILENT --force --verify -o rt2.flac rt.flac || die "ERROR"
echo -n "decode... " echo -n "decode... "
run_flac $SILENT --force --decode -o rt.wav rt2.flac || die "ERROR" run_flac $SILENT --force --decode --channel-map=none -o rt.wav rt2.flac || die "ERROR"
echo -n "compare... " echo -n "compare... "
cmp $f rt.wav || die "ERROR: file mismatch" cmp $f rt.wav || die "ERROR: file mismatch"
echo "OK" echo "OK"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment