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 {
utils__SkipUntilSpecification until_specification;
FLAC__bool has_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;
/* used for AIFF also */
......
This diff is collapsed.
......@@ -59,6 +59,7 @@ typedef struct {
int num_requested_seek_points;
const char *cuesheet_filename;
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 */
FLAC__bool is_first_file;
......
......@@ -106,6 +106,7 @@ static struct share__option long_options_[] = {
{ "output-name" , share__required_argument, 0, 'o' },
{ "skip" , share__required_argument, 0, 0 },
{ "until" , share__required_argument, 0, 0 },
{ "channel-map" , share__required_argument, 0, 0 }, /* undocumented */
/*
* decoding options
......@@ -255,6 +256,7 @@ static struct {
int num_requested_seek_points; /* -1 => no -S options were given, 0 => -S- was given */
const char *cuesheet_filename;
FLAC__bool cued_seekpoints;
FLAC__bool channel_map_none; /* --channel-map=none specified, eventually will expand to take actual channel map */
unsigned num_files;
char **filenames;
......@@ -604,6 +606,7 @@ FLAC__bool init_options()
option_values.num_requested_seek_points = -1;
option_values.cuesheet_filename = 0;
option_values.cued_seekpoints = true;
option_values.channel_map_none = false;;
option_values.num_files = 0;
option_values.filenames = 0;
......@@ -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")) {
FLAC__ASSERT(0 != 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_
common_options.num_requested_seek_points = option_values.num_requested_seek_points;
common_options.cuesheet_filename = option_values.cuesheet_filename;
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_last_file = is_last_file;
common_options.align_reservoir = align_reservoir;
......@@ -1919,6 +1928,7 @@ int decode_file(const char *infilename)
common_options.use_first_serial_number = !option_values.has_serial_number;
common_options.serial_number = option_values.serial_number;
#endif
common_options.channel_map_none = option_values.channel_map_none;
if(!option_values.force_raw_format) {
wav_decode_options_t options;
......
......@@ -22,11 +22,14 @@
#include "utils.h"
#include "FLAC/assert.h"
#include "FLAC/metadata.h"
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
int flac__utils_verbosity_ = 2;
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
else
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
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);
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
......@@ -614,8 +614,9 @@ foo:
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 padded_size = (true_size + 1) & (~1u);
FILE *f;
......@@ -625,9 +626,13 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
return false;
if(fwrite("RIFF", 1, 4, f) < 4)
goto foo;
if(!write_little_endian_uint32(f, padded_size + 36))
if(!write_little_endian_uint32(f, padded_size + (waveformatextensible?60:36)))
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;
if(!write_little_endian_uint16(f, (FLAC__uint16)channels))
goto foo;
......@@ -639,6 +644,17 @@ static FLAC__bool generate_wav(const char *filename, unsigned sample_rate, unsig
goto foo;
if(!write_little_endian_uint16(f, (FLAC__uint16)(8 * bytes_per_sample)))
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)
goto foo;
if(!write_little_endian_uint32(f, true_size))
......@@ -815,7 +831,7 @@ int main(int argc, char *argv[])
return 1;
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;
}
}
......
......@@ -159,9 +159,9 @@ rt_test_wav ()
{
f="$1"
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... "
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... "
cmp $f rt.wav || die "ERROR: file mismatch"
echo "OK"
......@@ -172,9 +172,9 @@ rt_test_aiff ()
{
f="$1"
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... "
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... "
cmp $f rt.aiff || die "ERROR: file mismatch"
echo "OK"
......@@ -186,11 +186,11 @@ rt_test_flac ()
{
f="$1"
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... "
run_flac $SILENT --force --verify -o rt2.flac rt.flac || die "ERROR"
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... "
cmp $f rt.wav || die "ERROR: file mismatch"
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