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
......@@ -54,6 +54,7 @@ typedef struct {
FLAC__bool is_aiff_out;
FLAC__bool is_wave_out;
FLAC__bool continue_through_decode_errors;
FLAC__bool channel_map_none;
struct {
replaygain_synthesis_spec_t spec;
......@@ -87,6 +88,7 @@ typedef struct {
unsigned bps;
unsigned channels;
unsigned sample_rate;
FLAC__uint32 channel_mask;
union {
FLAC__StreamDecoder *flac;
......@@ -105,7 +107,7 @@ static FLAC__bool is_big_endian_host_;
/*
* local routines
*/
static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename);
static FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename);
static void DecoderSession_destroy(DecoderSession *d, FLAC__bool error_occurred);
static FLAC__bool DecoderSession_init_decoder(DecoderSession *d, decode_options_t decode_options, const char *infilename);
static FLAC__bool DecoderSession_process(DecoderSession *d);
......@@ -145,6 +147,7 @@ int flac__decode_aiff(const char *infilename, const char *outfilename, FLAC__boo
/*is_aiff_out=*/true,
/*is_wave_out=*/false,
options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec,
analysis_mode,
aopts,
......@@ -181,6 +184,7 @@ int flac__decode_wav(const char *infilename, const char *outfilename, FLAC__bool
/*is_aiff_out=*/false,
/*is_wave_out=*/true,
options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec,
analysis_mode,
aopts,
......@@ -220,6 +224,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
/*is_aiff_out=*/false,
/*is_wave_out=*/false,
options.common.continue_through_decode_errors,
options.common.channel_map_none,
options.common.replaygain_synthesis_spec,
analysis_mode,
aopts,
......@@ -241,7 +246,7 @@ int flac__decode_raw(const char *infilename, const char *outfilename, FLAC__bool
return DecoderSession_finish_ok(&decoder_session);
}
FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename)
FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__bool is_aiff_out, FLAC__bool is_wave_out, FLAC__bool continue_through_decode_errors, FLAC__bool channel_map_none, replaygain_synthesis_spec_t replaygain_synthesis_spec, FLAC__bool analysis_mode, analysis_options aopts, utils__SkipUntilSpecification *skip_specification, utils__SkipUntilSpecification *until_specification, utils__CueSpecification *cue_specification, const char *infilename, const char *outfilename)
{
#ifdef FLAC__HAS_OGG
d->is_ogg = is_ogg;
......@@ -252,6 +257,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->is_aiff_out = is_aiff_out;
d->is_wave_out = is_wave_out;
d->continue_through_decode_errors = continue_through_decode_errors;
d->channel_map_none = channel_map_none;
d->replaygain.spec = replaygain_synthesis_spec;
d->replaygain.apply = false;
d->replaygain.scale = 0.0;
......@@ -279,6 +285,7 @@ FLAC__bool DecoderSession_construct(DecoderSession *d, FLAC__bool is_ogg, FLAC__
d->bps = 0;
d->channels = 0;
d->sample_rate = 0;
d->channel_mask = 0;
d->decoder.flac = 0;
#ifdef FLAC__HAS_OGG
......@@ -409,6 +416,36 @@ FLAC__bool DecoderSession_process(DecoderSession *d)
if(d->abort_flag)
return false;
/* set channel mapping */
if(!d->channel_map_none) {
/* currently FLAC order matches SMPTE/WAVEFORMATEXTENSIBLE order, so no reordering is necessary; see encode.c */
/* only the channel mask must be set if it was not already picked up from the WAVEFORMATEXTENSIBLE_CHANNEL_MASK tag */
if(d->channels == 1) {
if(d->channel_mask == 0)
d->channel_mask = 0x0001;
}
else if(d->channels == 2) {
if(d->channel_mask == 0)
d->channel_mask = 0x0003;
}
else if(d->channels == 3) {
if(d->channel_mask == 0)
d->channel_mask = 0x0007;
}
else if(d->channels == 4) {
if(d->channel_mask == 0)
d->channel_mask = 0x0033;
}
else if(d->channels == 5) {
if(d->channel_mask == 0)
d->channel_mask = 0x0607;
}
else if(d->channels == 6) {
if(d->channel_mask == 0)
d->channel_mask = 0x060f;
}
}
/* write the WAVE/AIFF headers if necessary */
if(!d->analysis_mode && !d->test_only && (d->is_wave_out || d->is_aiff_out)) {
if(!write_iff_headers(d->fout, d, d->total_samples)) {
......@@ -610,6 +647,7 @@ FLAC__bool canonicalize_until_specification(utils__SkipUntilSpecification *spec,
FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uint64 samples)
{
const char *fmt_desc = decoder_session->is_wave_out? "WAVE" : "AIFF";
const FLAC__bool is_waveformatextensible = decoder_session->is_wave_out && (decoder_session->channel_mask == 2 || decoder_session->channel_mask > 3 || decoder_session->bps%8 || decoder_session->channels > 2);
FLAC__uint64 data_size = samples * decoder_session->channels * ((decoder_session->bps+7)/8);
const FLAC__uint32 aligned_data_size = (FLAC__uint32)((data_size+1) & (~1U)); /* we'll check for overflow later */
if(samples == 0) {
......@@ -630,16 +668,16 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin
if(flac__utils_fwrite("RIFF", 1, 4, f) != 4)
return false;
if(!write_little_endian_uint32(f, aligned_data_size+36)) /* filesize-8 */
if(!write_little_endian_uint32(f, aligned_data_size+(is_waveformatextensible?60:36))) /* filesize-8 */
return false;
if(flac__utils_fwrite("WAVEfmt ", 1, 8, f) != 8)
return false;
if(flac__utils_fwrite("\020\000\000\000", 1, 4, f) != 4) /* chunk size = 16 */
if(!write_little_endian_uint32(f, is_waveformatextensible? 40 : 16)) /* chunk size */
return false;
if(flac__utils_fwrite("\001\000", 1, 2, f) != 2) /* compression code == 1 */
if(!write_little_endian_uint16(f, (FLAC__uint16)(is_waveformatextensible? 65534 : 1))) /* compression code */
return false;
if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels)))
......@@ -654,9 +692,24 @@ FLAC__bool write_iff_headers(FILE *f, DecoderSession *decoder_session, FLAC__uin
if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->channels * ((decoder_session->bps+7) / 8)))) /* block align */
return false;
if(!write_little_endian_uint16(f, (FLAC__uint16)(decoder_session->bps))) /* bits per sample */
if(!write_little_endian_uint16(f, (FLAC__uint16)(((decoder_session->bps+7)/8)*8))) /* bits per sample */
return false;
if(is_waveformatextensible) {
if(!write_little_endian_uint16(f, (FLAC__uint16)22)) /* cbSize */
return false;
if(!write_little_endian_uint16(f, (FLAC__uint16)decoder_session->bps)) /* validBitsPerSample */
return false;
if(!write_little_endian_uint32(f, decoder_session->channel_mask))
return false;
/* GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}} */
if(flac__utils_fwrite("\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71", 1, 16, f) != 16)
return false;
}
if(flac__utils_fwrite("data", 1, 4, f) != 4)
return false;
......@@ -801,6 +854,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
DecoderSession *decoder_session = (DecoderSession*)client_data;
FILE *fout = decoder_session->fout;
const unsigned bps = frame->header.bits_per_sample, channels = frame->header.channels;
const unsigned shift = (decoder_session->is_wave_out && (bps%8)? 8-(bps%8): 0);
FLAC__bool is_big_endian = (decoder_session->is_aiff_out? true : (decoder_session->is_wave_out? false : decoder_session->is_big_endian));
FLAC__bool is_unsigned_samples = (decoder_session->is_aiff_out? false : (decoder_session->is_wave_out? bps<=8 : decoder_session->is_unsigned_samples));
unsigned wide_samples = frame->header.blocksize, wide_sample, sample, channel, byte;
......@@ -916,7 +970,12 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
flac__analyze_frame(frame, decoder_session->frame_counter-1, decoder_session->aopts, fout);
}
else if(!decoder_session->test_only) {
if (decoder_session->replaygain.apply) {
if(shift && !decoder_session->replaygain.apply) {
for(wide_sample = 0; wide_sample < wide_samples; wide_sample++)
for(channel = 0; channel < channels; channel++)
/*@@@@@@bad un-const:fix@@@@@@*/((FLAC__int32**)buffer)[channel][wide_sample] <<= shift;
}
if(decoder_session->replaygain.apply) {
bytes_to_write = FLAC__replaygain_synthesis__apply_gain(
u8buffer,
!is_big_endian,
......@@ -925,7 +984,7 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
wide_samples,
channels,
bps, /* source_bps */
bps, /* target_bps */
bps+shift, /* target_bps */
decoder_session->replaygain.scale,
decoder_session->replaygain.spec.limiter == RGSS_LIMIT__HARD, /* hard_limit */
decoder_session->replaygain.spec.noise_shaping != NOISE_SHAPING_NONE, /* do_dithering */
......@@ -1014,6 +1073,9 @@ FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder *decoder
}
else {
FLAC__ASSERT(0);
/* double protection */
decoder_session->abort_flag = true;
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
}
}
......@@ -1117,6 +1179,7 @@ void metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMet
flac__utils_printf(stderr, 1, "%s: WARNING: applying ReplayGain is not lossless\n", decoder_session->inbasefilename);
}
}
(void)flac__utils_get_channel_mask_tag(metadata, &decoder_session->channel_mask);
}
}
......
......@@ -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