ref: af81c85a823c9ce0b2524e89faf6e42e3cbf4d0f
parent: dcef6ae58cb60543d48cfd4685c346c812d53632
author: rrt <rrt>
date: Mon Nov 13 19:37:30 EST 2006
Add patch from [email protected]: Adds support for FLAC input and output (if FLAC libs are available). Allows vorbis encoding quality to be specified. Warns that with mp3, encoding quality cannot be specified. Fixes a bug in deemph that prevented its use with ogg, flac, etc. A couple of const-correctness fixes that were needed for flac. Documentation for the above.
--- a/configure
+++ b/configure
@@ -1271,6 +1271,7 @@
--disable-oss-dsp Disable detection of OSS
--disable-sun-audio Disable detection of SUN-style audio
--disable-ogg-vorbis Disable detection of Ogg Vorbis
+ --disable-flac Disable detection of FLAC
--disable-mad Disable detection of MAD (MP3 Audio Decoder)
--disable-lame Disable detection of LAME (LAME Ain't an MP3 Encoder)
--disable-samplerate Detection of libsamplerate (aka Secret Rabbit Code)
@@ -1796,6 +1797,14 @@
fi
+# Check whether --enable-flac was given.
+if test "${enable_flac+set}" = set; then
+ enableval=$enable_flac; enable_flac=$enableval
+else
+ enable_flac=yes
+fi
+
+
# Check whether --enable-mad was given.
if test "${enable_mad+set}" = set; then
enableval=$enable_mad; enable_mad=$enableval
@@ -5424,6 +5433,263 @@
fi
+if test "$enable_flac" = yes
+then
+ if test "${ac_cv_header_FLAC_file_encoder_h+set}" = set; then
+ { echo "$as_me:$LINENO: checking for FLAC/file_encoder.h" >&5
+echo $ECHO_N "checking for FLAC/file_encoder.h... $ECHO_C" >&6; }
+if test "${ac_cv_header_FLAC_file_encoder_h+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_header_FLAC_file_encoder_h" >&5
+echo "${ECHO_T}$ac_cv_header_FLAC_file_encoder_h" >&6; }
+else
+ # Is the header compilable?
+{ echo "$as_me:$LINENO: checking FLAC/file_encoder.h usability" >&5
+echo $ECHO_N "checking FLAC/file_encoder.h usability... $ECHO_C" >&6; }
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_includes_default
+#include <FLAC/file_encoder.h>
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_compile") 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
+ { (case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_header_compiler=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_header_compiler=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
+echo "${ECHO_T}$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ echo "$as_me:$LINENO: checking FLAC/file_encoder.h presence" >&5
+echo $ECHO_N "checking FLAC/file_encoder.h presence... $ECHO_C" >&6; }
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <FLAC/file_encoder.h>
+_ACEOF
+if { (ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } >/dev/null; then
+ if test -s conftest.err; then
+ ac_cpp_err=$ac_c_preproc_warn_flag
+ ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
+ else
+ ac_cpp_err=
+ fi
+else
+ ac_cpp_err=yes
+fi
+if test -z "$ac_cpp_err"; then
+ ac_header_preproc=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_header_preproc=no
+fi
+
+rm -f conftest.err conftest.$ac_ext
+{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
+echo "${ECHO_T}$ac_header_preproc" >&6; }
+
+# So? What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
+ yes:no: )
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: accepted by the compiler, rejected by the preprocessor!" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: accepted by the compiler, rejected by the preprocessor!" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: proceeding with the compiler's result" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: proceeding with the compiler's result" >&2;}
+ ac_header_preproc=yes
+ ;;
+ no:yes:* )
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: present but cannot be compiled" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: present but cannot be compiled" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: check for missing prerequisite headers?" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: check for missing prerequisite headers?" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: see the Autoconf documentation" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: see the Autoconf documentation" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: section \"Present But Cannot Be Compiled\"" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: section \"Present But Cannot Be Compiled\"" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: proceeding with the preprocessor's result" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: proceeding with the preprocessor's result" >&2;}
+ { echo "$as_me:$LINENO: WARNING: FLAC/file_encoder.h: in the future, the compiler will take precedence" >&5
+echo "$as_me: WARNING: FLAC/file_encoder.h: in the future, the compiler will take precedence" >&2;}
+ ( cat <<\_ASBOX
+## --------------------------------------------- ##
+## Report this to [email protected] ##
+## --------------------------------------------- ##
+_ASBOX
+ ) | sed "s/^/$as_me: WARNING: /" >&2
+ ;;
+esac
+{ echo "$as_me:$LINENO: checking for FLAC/file_encoder.h" >&5
+echo $ECHO_N "checking for FLAC/file_encoder.h... $ECHO_C" >&6; }
+if test "${ac_cv_header_FLAC_file_encoder_h+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_header_FLAC_file_encoder_h=$ac_header_preproc
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_header_FLAC_file_encoder_h" >&5
+echo "${ECHO_T}$ac_cv_header_FLAC_file_encoder_h" >&6; }
+
+fi
+if test $ac_cv_header_FLAC_file_encoder_h = yes; then
+ found_flac=yes
+else
+ enable_flac=no
+fi
+
+
+ if test "$found_flac" = yes
+ then
+ { echo "$as_me:$LINENO: checking for FLAC__file_encoder_new in -lFLAC" >&5
+echo $ECHO_N "checking for FLAC__file_encoder_new in -lFLAC... $ECHO_C" >&6; }
+if test "${ac_cv_lib_FLAC_FLAC__file_encoder_new+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lFLAC $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char FLAC__file_encoder_new ();
+int
+main ()
+{
+return FLAC__file_encoder_new ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_link") 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
+ { (case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_try") 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_FLAC_FLAC__file_encoder_new=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_cv_lib_FLAC_FLAC__file_encoder_new=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ echo "$as_me:$LINENO: result: $ac_cv_lib_FLAC_FLAC__file_encoder_new" >&5
+echo "${ECHO_T}$ac_cv_lib_FLAC_FLAC__file_encoder_new" >&6; }
+if test $ac_cv_lib_FLAC_FLAC__file_encoder_new = yes; then
+ LIBS="-lFLAC $LIBS"
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_LIBFLAC 1
+_ACEOF
+
+else
+ enable_flac=no
+fi
+
+ fi
+fi
+
+
if test "$enable_mad" = yes
then
if test "${ac_cv_header_mad_h+set}" = set; then
@@ -9520,6 +9786,7 @@
echo "OSS Driver........................ $enable_oss_dsp"
echo "SUN /dev/audio.................... $enable_sun_audio"
echo "Ogg Vorbis support................ $enable_ogg_vorbis"
+echo "FLAC support...................... $enable_flac"
echo "MAD MP3 Decoder................... $enable_mad"
echo "LAME MP3 Encoder.................. $enable_lame"
echo "Secret Rabbit Code resampling..... $enable_samplerate"
--- a/configure.in
+++ b/configure.in
@@ -51,6 +51,11 @@
[enable_ogg_vorbis=$enableval],
[enable_ogg_vorbis=yes])
+AC_ARG_ENABLE(flac,
+ [ --disable-flac Disable detection of FLAC],
+ [enable_flac=$enableval],
+ [enable_flac=yes])
+
AC_ARG_ENABLE(mad,
[ --disable-mad Disable detection of MAD (MP3 Audio Decoder)],
[enable_mad=$enableval],
@@ -153,6 +158,21 @@
fi
fi
+dnl Test for FLAC libraries.
+
+if test "$enable_flac" = yes
+then
+ AC_CHECK_HEADER(FLAC/file_encoder.h, found_flac=yes, enable_flac=no)
+ if test "$found_flac" = yes
+ then
+ AC_CHECK_LIB(FLAC, FLAC__file_encoder_new,
+ LIBS="-lFLAC $LIBS"
+ AC_DEFINE([HAVE_LIBFLAC], 1,
+ [Define if you have FLAC Library installed]),
+ enable_flac=no)
+ fi
+fi
+
dnl Test for MAD libraries.
if test "$enable_mad" = yes
@@ -302,6 +322,7 @@
echo "OSS Driver........................ $enable_oss_dsp"
echo "SUN /dev/audio.................... $enable_sun_audio"
echo "Ogg Vorbis support................ $enable_ogg_vorbis"
+echo "FLAC support...................... $enable_flac"
echo "MAD MP3 Decoder................... $enable_mad"
echo "LAME MP3 Encoder.................. $enable_lame"
echo "Secret Rabbit Code resampling..... $enable_samplerate"
--- a/sox.1
+++ b/sox.1
@@ -169,6 +169,12 @@
avg effect must be used. If the avg effect is not specified on the
command line it will be invoked internally with default parameters.
.TP 10
+\fB-C \fIcompression-factor\fR
+The compression factor for variably compressing output file formats.
+If this option is not given, then a default compression factor will apply.
+The compression factor is interpreted differently for different compressing file formats.
+See the description of the file formats that use this parameter for more information.
+.TP 10
\fB-e\fR
When specified after the last input file name (so that it applies
to the output file)
@@ -347,6 +353,53 @@
a file in this format back into one of the other file
formats.
.TP 10
+.B .flac
+Free Lossless Audio Codec compressed audio
+.br
+FLAC is an open, patent-free CODEC designed for compressing
+music. It is similar to mp3 and Ogg Vorbis, but lossless,
+meaning that audio is compressed in FLAC without any loss in
+quality.
+.ti +3
+.B SoX
+can decode native FLAC files (.flac) but not Ogg FLAC files (.ogg).
+[But see
+.B .ogg
+below for information relating to support for Ogg
+Vorbis files.]
+.ti +3
+.B SoX
+has rudimentary support for writing FLAC files: it can encode to
+native FLAC using compression levels 0 to 8. 8 is the default
+compression level and gives the best (but slowest) compression;
+0 gives the least (but fastest) compression. The compression
+level can be selected using the
+.B -C
+option (see above) with a whole number from 0 to 8.
+.ti +3
+Note that Replay Gain information is not used by
+.B SoX
+if present in FLAC input files and is not generated by
+.B SoX
+for FLAC
+output files, however
+.B SoX
+will copy input file "comments" (which can be used to hold Replay
+Gain information) to output files that
+support comments, so FLAC output files may contain Replay Gain
+information if some was present in the input file. In this case the
+Replay Gain information in the output file is likely to be incorrect and so should
+be recalculated using a tool that supports this (not
+.B SoX
+).
+.br
+.ti +3
+FLAC support in
+.B SoX
+is optional and requires access to external FLAC libraries. To
+see if there is support for FLAC run \fBsox -h\fR and look for
+it under the list of supported file formats as "flac".
+.TP 10
.B .gsm
GSM 06.10 Lossy Speech Compression.
A standard for compressing speech which is used in the
@@ -379,7 +432,11 @@
in mono and stereo.
.TP 10
.B .mp3
-MP3 Compressed Audio. MP3 audio files come from the MPEG standards for audio and video compression. They are a lossy compression format that achieves good compression rates with a minimum amount of quality loss. Also see Ogg Vorbis for a similar format.
+MP3 Compressed Audio. MP3 (MPEG Layer 3) is part of the
+MPEG standards for audio and video compression. It is a lossy
+compression format that achieves good compression rates with little
+quality loss. Also see Ogg Vorbis for a similar format.
+.ti +3
MP3 support in
.B SoX
is optional and requires access to either or both the external
@@ -398,12 +455,22 @@
.B .ogg
Ogg Vorbis Compressed Audio.
Ogg Vorbis is a open, patent-free CODEC designed for compressing music
-and streaming audio. It is similar to MP3, VQF, AAC, and other lossy
-formats.
+and streaming audio. It is a lossy compression format (similar to MP3,
+VQF & AAC) that achieves good compression rates with a minimum amount of
+quality loss. Also see MP3 for a similar format.
+.ti +3
.B SoX
-can decode all types of Ogg Vorbis files, but can only encode at 128 kbps.
+can decode all types of Ogg Vorbis files, and can encode at different
+compression levels/qualities given as a number from -1 (highest
+compression/lowest quality) to 10 (lowest compression, highest quality).
+By default the encoding quality level is 3 (which gives an encoded rate
+of approx. 112kbps), but this can be changed using the
+.B -C
+option (see above) with a number from -1 to 10; fractional numbers (e.g.
+3.6) are also allowed.
+.ti +3
Decoding is somewhat CPU intensive and encoding is very CPU intensive.
-.br
+.ti +3
Ogg Vorbis in
.B SoX
is optional and requires access to external Ogg Vorbis libraries. To
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -42,7 +42,7 @@
# Objects.
FOBJ = 8svx.o adpcm.o aiff.o au.o auto.o avr.o cdr.o \
- cvsd.o dat.o g711.o g721.o g723_16.o g723_24.o g723_40.o \
+ cvsd.o dat.o flac.o g711.o g721.o g723_16.o g723_24.o g723_40.o \
g72x.o gsm.o hcom.o ima_rw.o maud.o mp3.o nulfile.o prc.o \
raw.o sf.o smp.o sndrtool.o sphere.o tx16w.o voc.o vorbis.o \
vox.o wav.o wve.o
--- a/src/auto.c
+++ b/src/auto.c
@@ -126,7 +126,11 @@
else if (strncmp(header, "Ogg", 3) == 0)
{
type = "ogg";
- }
+ }
+ else if (strncmp(header, "fLaC", 4) == 0)
+ {
+ type = "flac";
+ }
} /* read 4-byte header */
/* If we didn't find type yet then start looking for file
--- a/src/deemphas.c
+++ b/src/deemphas.c
@@ -137,13 +137,18 @@
int st_deemph_start(eff_t effp)
{
/* check the input format */
- if (effp->ininfo.encoding != ST_ENCODING_SIGN2
- || effp->ininfo.rate != 44100
- || effp->ininfo.size != ST_SIZE_WORD)
+
+ /* This used to check the input file sample encoding method and size
+ * but these are irrelevant as effects always work with the ST internal
+ * long-integer format regardless of the input format.
+ * The only parameter that is important for the deemph effect is
+ * sampling rate as this has been harded coded into the pre-calculated
+ * filter coefficients.
+ */
+ if (effp->ininfo.rate != 44100)
{
- st_fail("The deemphasis effect works only with audio cd like samples.\nThe input format however has %d Hz sample rate and %d-byte%s signed linearly coded samples.",
- effp->ininfo.rate, effp->ininfo.size,
- effp->ininfo.encoding != ST_ENCODING_SIGN2 ? ", but not" : "");
+ st_fail("The deemphasis effect works only with audio-CD-like samples.\nThe input format however has %d Hz sample rate.",
+ effp->ininfo.rate);
return (ST_EOF);
}
else
--- /dev/null
+++ b/src/flac.c
@@ -1,0 +1,472 @@
+/*
+ * Sound Tools File Format: FLAC
+ *
+ * Support for FLAC input and rudimentary support for FLAC output.
+ *
+ * This implementation (c) 2006 aquegg
+ *
+ * See LICENSE file for further copyright information.
+ */
+
+
+
+#include "st_i.h"
+
+#if defined(HAVE_LIBFLAC)
+
+#include <math.h>
+#include <string.h>
+
+#include <FLAC/all.h>
+
+/* FIXME
+ * There are things that should be const but can't be because
+ * of lack of const correctness elsewhere in SoX.
+ * Once Sox has been fixed up, then replace all occurences of
+ * const_ with const and remove this comment and the following
+ * line.
+ */
+#define const_
+
+
+
+typedef struct
+{
+ /* Info: */
+ unsigned bits_per_sample;
+ unsigned channels;
+ unsigned sample_rate;
+ unsigned total_samples;
+
+ /* Decode buffer: */
+ FLAC__int32 const * const * decoded_wide_samples;
+ unsigned number_of_wide_samples;
+ unsigned wide_sample_number;
+
+ FLAC__FileDecoder * flac;
+ FLAC__bool eof;
+} Decoder;
+
+
+
+assert_static(sizeof(Decoder) <= ST_MAX_FILE_PRIVSIZE, /* else */ Decoder__PRIVSIZE_too_big);
+
+
+
+static void FLAC__decoder_metadata_callback(FLAC__FileDecoder const * const flac, FLAC__StreamMetadata const * const metadata, void * const client_data)
+{
+ ft_t format = (ft_t) client_data;
+ Decoder * decoder = (Decoder *) format->priv;
+
+ (void) flac;
+
+ if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
+ {
+ decoder->bits_per_sample = metadata->data.stream_info.bits_per_sample;
+ decoder->channels = metadata->data.stream_info.channels;
+ decoder->sample_rate = metadata->data.stream_info.sample_rate;
+ decoder->total_samples = metadata->data.stream_info.total_samples;
+ }
+ else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
+ {
+ int i;
+ int comment_size = 0;
+
+ if (metadata->data.vorbis_comment.num_comments == 0)
+ {
+ return;
+ }
+
+ if (format->comment != NULL)
+ {
+ st_warn("FLAC: multiple Vorbis comment block ignored");
+ return;
+ }
+
+ for (i = 0; i < metadata->data.vorbis_comment.num_comments; ++i)
+ {
+ comment_size += metadata->data.vorbis_comment.comments[i].length + 1;
+ }
+
+ if ((format->comment = (char *) calloc(comment_size, sizeof(char))) == NULL)
+ {
+ st_fail_errno(format, ST_ENOMEM, "FLAC: Could not allocate memory");
+ decoder->eof = true;
+ return;
+ }
+
+ for (i = 0; i < metadata->data.vorbis_comment.num_comments; ++i)
+ {
+ strcat(format->comment, (char const *) metadata->data.vorbis_comment.comments[i].entry);
+ if (i != metadata->data.vorbis_comment.num_comments - 1)
+ {
+ strcat(format->comment, "\n");
+ }
+ }
+ }
+}
+
+
+
+static void FLAC__decoder_error_callback(FLAC__FileDecoder const * const flac, FLAC__StreamDecoderErrorStatus const status, void * const client_data)
+{
+ ft_t format = (ft_t) client_data;
+
+ (void) flac;
+
+ st_fail_errno(format, ST_EINVAL, "FLAC ERROR %i: %s", status, FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+
+
+static FLAC__StreamDecoderWriteStatus FLAC__frame_decode_callback(FLAC__FileDecoder const * const flac, FLAC__Frame const * const frame, FLAC__int32 const * const buffer[], void * const client_data)
+{
+ ft_t format = (ft_t) client_data;
+ Decoder * decoder = (Decoder *) format->priv;
+
+ (void) flac;
+
+ if (frame->header.bits_per_sample != decoder->bits_per_sample || frame->header.channels != decoder->channels || frame->header.sample_rate != decoder->sample_rate)
+ {
+ st_fail_errno(format, ST_EINVAL, "FLAC ERROR: parameters differ between frame and header");
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+ }
+
+ decoder->decoded_wide_samples = buffer;
+ decoder->number_of_wide_samples = frame->header.blocksize;
+ decoder->wide_sample_number = 0;
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+
+
+static int st_format_start_read(ft_t const format)
+{
+ Decoder * decoder = (Decoder *) format->priv;
+
+ memset(decoder, 0, sizeof(*decoder));
+ decoder->flac = FLAC__file_decoder_new();
+ if (decoder->flac == NULL)
+ {
+ st_fail_errno(format, ST_ENOMEM, "FLAC ERROR creating the decoder instance");
+ return ST_EOF;
+ }
+
+ FLAC__file_decoder_set_md5_checking(decoder->flac, true);
+ FLAC__file_decoder_set_filename(decoder->flac, format->filename);
+ FLAC__file_decoder_set_write_callback(decoder->flac, FLAC__frame_decode_callback);
+ FLAC__file_decoder_set_metadata_callback(decoder->flac, FLAC__decoder_metadata_callback);
+ FLAC__file_decoder_set_error_callback(decoder->flac, FLAC__decoder_error_callback);
+ FLAC__file_decoder_set_metadata_respond_all(decoder->flac);
+ FLAC__file_decoder_set_client_data(decoder->flac, format);
+
+ if (FLAC__file_decoder_init(decoder->flac) != FLAC__FILE_DECODER_OK)
+ {
+ st_fail_errno(format, ST_EHDR, "FLAC ERROR initialising decoder");
+ return ST_EOF;
+ }
+
+ if (!FLAC__file_decoder_process_until_end_of_metadata(decoder->flac))
+ {
+ st_fail_errno(format, ST_EHDR, "FLAC ERROR whilst decoding metadata");
+ return ST_EOF;
+ }
+
+ if (FLAC__file_decoder_get_state(decoder->flac) != FLAC__FILE_DECODER_OK && FLAC__file_decoder_get_state(decoder->flac) != FLAC__FILE_DECODER_END_OF_FILE)
+ {
+ st_fail_errno(format, ST_EHDR, "FLAC ERROR during metadata decoding");
+ return ST_EOF;
+ }
+
+ format->info.encoding = ST_ENCODING_FLAC;
+ format->info.rate = decoder->sample_rate;
+ format->info.size = decoder->bits_per_sample >> 3;
+ format->info.channels = decoder->channels;
+ format->length = decoder->total_samples * decoder->channels;
+ return ST_SUCCESS;
+}
+
+
+static st_ssize_t st_format_read(ft_t const format, st_sample_t * sampleBuffer, st_ssize_t const requested)
+{
+ Decoder * decoder = (Decoder *) format->priv;
+ int actual = 0;
+
+ while (!decoder->eof && actual < requested)
+ {
+ if (decoder->wide_sample_number >= decoder->number_of_wide_samples)
+ {
+ FLAC__file_decoder_process_single(decoder->flac);
+ }
+ if (decoder->wide_sample_number >= decoder->number_of_wide_samples)
+ {
+ decoder->eof = true;
+ }
+ else
+ {
+ unsigned channel;
+
+ for (channel = 0; channel < decoder->channels; ++channel)
+ {
+ *sampleBuffer++ = decoder->decoded_wide_samples[channel][decoder->wide_sample_number] << (32 - decoder->bits_per_sample);
+ ++actual;
+ }
+ ++decoder->wide_sample_number;
+ }
+ }
+ return actual;
+}
+
+
+
+static int st_format_stop_read(ft_t const format)
+{
+ Decoder * decoder = (Decoder *) format->priv;
+
+ int result = FLAC__file_decoder_finish(decoder->flac) ? ST_SUCCESS : ST_EOF;
+
+ if (result == ST_SUCCESS)
+ {
+ FLAC__file_decoder_delete(decoder->flac);
+ }
+ return result;
+}
+
+
+
+typedef struct
+{
+ /* Info: */
+ unsigned bits_per_sample;
+
+ /* Encode buffer: */
+ FLAC__int32 * decoded_samples;
+ unsigned number_of_samples;
+
+ FLAC__StreamEncoder * flac;
+} Encoder;
+
+
+
+assert_static(sizeof(Encoder) <= ST_MAX_FILE_PRIVSIZE, /* else */ Encoder__PRIVSIZE_too_big);
+
+
+
+FLAC__StreamEncoderWriteStatus flac_stream_encoder_write_callback(FLAC__StreamEncoder const * const flac, const FLAC__byte buffer[], unsigned const bytes, unsigned const samples, unsigned const current_frame, void * const client_data)
+{
+ ft_t const format = (ft_t) client_data;
+ (void) flac, (void) samples, (void) current_frame;
+
+ return st_writebuf(format, buffer, 1, bytes) == bytes ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
+}
+
+
+
+void flac_stream_encoder_metadata_callback(FLAC__StreamEncoder const * encoder, FLAC__StreamMetadata const * metadata, void * client_data)
+{
+ (void) encoder, (void) metadata, (void) client_data;
+}
+
+
+
+static int st_format_start_write(ft_t const format)
+{
+ Encoder * encoder = (Encoder *) format->priv;
+
+ memset(encoder, 0, sizeof(*encoder));
+ encoder->flac = FLAC__stream_encoder_new();
+ encoder->decoded_samples = malloc(ST_BUFSIZ * sizeof(FLAC__int32));
+ if (encoder->flac == NULL || encoder->decoded_samples == NULL)
+ {
+ st_fail_errno(format, ST_ENOMEM, "FLAC ERROR creating the encoder instance");
+ return ST_EOF;
+ }
+
+ { /* Select and set FLAC encoder options: */
+ static struct
+ {
+ int blocksize;
+ FLAC__bool do_exhaustive_model_search;
+ FLAC__bool do_mid_side_stereo;
+ FLAC__bool loose_mid_side_stereo;
+ unsigned max_lpc_order;
+ int max_residual_partition_order;
+ int min_residual_partition_order;
+ } const options[] = {
+ {1152, false, false, false, 0, 2, 2},
+ {1152, false, true, true, 0, 2, 2},
+ {1152, false, true, false, 0, 3, 0},
+ {4608, false, false, false, 6, 3, 3},
+ {4608, false, true, true, 8, 3, 3},
+ {4608, false, true, false, 8, 3, 3},
+ {4608, false, true, false, 8, 4, 0},
+ {4608, true, true, false, 8, 6, 0},
+ {4608, true, true, false, 12, 6, 0},
+ };
+ unsigned compression_level = array_length(options) - 1; /* Default to "best" */
+
+ if (format->info.compression != HUGE_VAL)
+ {
+ compression_level = format->info.compression;
+ if (compression_level != format->info.compression ||
+ compression_level >= array_length(options))
+ {
+ st_fail_errno(format, ST_EINVAL,
+ "FLAC compression level must be a whole number from 0 to %i",
+ array_length(options) - 1);
+ return ST_EOF;
+ }
+ }
+
+#define SET_OPTION(x) st_report("FLAC "#x" = %i", options[compression_level].x); \
+ FLAC__stream_encoder_set_##x(encoder->flac, options[compression_level].x)
+ SET_OPTION(blocksize);
+ SET_OPTION(do_exhaustive_model_search);
+ SET_OPTION(do_mid_side_stereo);
+ SET_OPTION(loose_mid_side_stereo);
+ SET_OPTION(max_lpc_order);
+ SET_OPTION(max_residual_partition_order);
+ SET_OPTION(min_residual_partition_order);
+#undef SET_OPTION
+ }
+
+ encoder->bits_per_sample = (format->info.size > 3 ? 3 : format->info.size) << 3;
+ st_report("FLAC encoding at %i bits per sample", encoder->bits_per_sample);
+
+ FLAC__stream_encoder_set_channels(encoder->flac, format->info.channels);
+ FLAC__stream_encoder_set_bits_per_sample(encoder->flac, encoder->bits_per_sample);
+ FLAC__stream_encoder_set_sample_rate(encoder->flac, format->info.rate);
+
+ if (format->length != 0)
+ {
+ FLAC__stream_encoder_set_total_samples_estimate(encoder->flac, format->length);
+ }
+
+ if (format->comment != NULL && * format->comment != '\0')
+ {
+ FLAC__StreamMetadata * metadata[1];
+ FLAC__StreamMetadata_VorbisComment_Entry entry;
+ char * comments, * comment, * end_of_comment;
+
+ /* FIXME This is never deleted; does it matter? */
+ metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+
+ /* Check if there is a FIELD=value pair already in the comment; if not, add one */
+ if (strchr(format->comment, '=') == NULL)
+ {
+ static const char prepend[] = "COMMENT=";
+ comments = malloc(strlen(format->comment) + sizeof(prepend));
+ if (comments != NULL)
+ {
+ strcpy(comments, prepend);
+ strcat(comments, format->comment);
+ }
+ }
+ else
+ {
+ comments = strdup(format->comment);
+ }
+ if (comments == NULL)
+ {
+ st_fail_errno(format, ST_ENOMEM, "FLAC ERROR initialising encoder");
+ return ST_EOF;
+ }
+ comment = comments;
+
+ do
+ {
+ entry.entry = (FLAC__byte *) comment;
+ end_of_comment = strchr(comment, '\n');
+ if (end_of_comment != NULL)
+ {
+ *end_of_comment = '\0';
+ comment = end_of_comment + 1;
+ }
+ entry.length = strlen((char const *) entry.entry);
+
+ FLAC__metadata_object_vorbiscomment_append_comment(metadata[0], entry, /*copy= */ true);
+ } while (end_of_comment != NULL);
+
+ FLAC__stream_encoder_set_metadata(encoder->flac, metadata, 1);
+ free(comments);
+ }
+
+ FLAC__stream_encoder_set_write_callback(encoder->flac, flac_stream_encoder_write_callback);
+ FLAC__stream_encoder_set_metadata_callback(encoder->flac, flac_stream_encoder_metadata_callback);
+ FLAC__stream_encoder_set_client_data(encoder->flac, format);
+
+ if (FLAC__stream_encoder_init(encoder->flac) != FLAC__STREAM_ENCODER_OK)
+ {
+ st_fail_errno(format, ST_EHDR, "FLAC ERROR initialising encoder");
+ return ST_EOF;
+ }
+ return ST_SUCCESS;
+}
+
+
+
+static st_ssize_t st_format_write(ft_t const format, st_sample_t const_ * const sampleBuffer, st_ssize_t const len)
+{
+ Encoder * encoder = (Encoder *) format->priv;
+ unsigned i;
+
+ for (i = 0; i < len; ++i)
+ {
+ encoder->decoded_samples[i] = sampleBuffer[i] >> (32 - encoder->bits_per_sample);
+ }
+ FLAC__stream_encoder_process_interleaved(encoder->flac, encoder->decoded_samples, len / format->info.channels);
+ return FLAC__stream_encoder_get_state(encoder->flac) == FLAC__STREAM_ENCODER_OK ? len : -1;
+}
+
+
+
+static int st_format_stop_write(ft_t const format)
+{
+ Encoder * encoder = (Encoder *) format->priv;
+ FLAC__StreamEncoderState state = FLAC__stream_encoder_get_state(encoder->flac);
+
+ FLAC__stream_encoder_finish(encoder->flac);
+ FLAC__stream_encoder_delete(encoder->flac);
+ free(encoder->decoded_samples);
+ if (state != FLAC__STREAM_ENCODER_OK)
+ {
+ st_fail_errno(format, ST_EINVAL, "FLAC ERROR: failed to encode to end of stream");
+ return ST_EOF;
+ }
+ return ST_SUCCESS;
+}
+
+
+
+static char const_ * const_ st_format_names[] =
+{
+ "flac",
+ NULL
+};
+
+
+
+static st_format_t const st_format =
+{
+ st_format_names,
+ NULL,
+ ST_FILE_STEREO,
+ st_format_start_read,
+ st_format_read,
+ st_format_stop_read,
+ st_format_start_write,
+ st_format_write,
+ st_format_stop_write,
+ st_format_nothing_seek
+};
+
+
+
+st_format_t const * st_flac_format_fn(void)
+{
+ return &st_format;
+}
+
+
+
+#endif /* HAVE_LIBFLAC */
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -28,6 +28,9 @@
st_cvsd_format_fn,
st_dat_format_fn,
st_dvms_format_fn,
+#ifdef HAVE_LIBFLAC
+ st_flac_format_fn,
+#endif
#ifdef ENABLE_GSM
st_gsm_format_fn,
#endif
--- a/src/misc.c
+++ b/src/misc.c
@@ -68,7 +68,8 @@
"inversed u-law",
"inversed A-law",
"MPEG audio (layer I, II or III)",
- "Vorbis"
+ "Vorbis",
+ "FLAC"
};
static const char readerr[] = "Premature EOF while reading sample file.";
@@ -89,7 +90,7 @@
* Returns number of elements writen, not bytes writen.
*/
-st_ssize_t st_writebuf(ft_t ft, void *buf, size_t size, st_ssize_t len)
+st_ssize_t st_writebuf(ft_t ft, void const *buf, size_t size, st_ssize_t len)
{
return fwrite(buf, size, len, ft->fp);
}
--- a/src/mp3.c
+++ b/src/mp3.c
@@ -22,6 +22,7 @@
#if defined(HAVE_LAME)
#include <lame/lame.h>
+#include <math.h>
#endif
#ifndef MIN
@@ -418,6 +419,11 @@
/* The bitrate, mode, quality and other settings are the default ones,
since SoX's command line options do not allow to set them */
+ /* FIXME: Someone who knows about lame could implement adjustable compression
+ here. E.g. by using the -C value as an index into a table of params or
+ as a compressed bit-rate. */
+ if (ft->info.compression != HUGE_VAL)
+ st_warn("-C option not supported for mp3; using default compression rate");
if (lame_init_params(p->gfp) < 0){
st_fail_errno(ft,ST_EOF,"LAME initialization failed");
return(ST_EOF);
--- a/src/sox.c
+++ b/src/sox.c
@@ -27,6 +27,7 @@
*/
#include "st_i.h"
+#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> /* for malloc() */
@@ -204,6 +205,7 @@
fo->info.size = -1;
fo->info.encoding = -1;
fo->info.channels = -1;
+ fo->info.compression = HUGE_VAL;
fo->volume = 1.0;
file_opts[file_count++] = fo;
@@ -251,6 +253,13 @@
*/
exit(2);
}
+
+ if (file_opts[i]->info.compression != HUGE_VAL)
+ {
+ st_fail("-C only allowed for output file");
+ cleanup();
+ exit(1);
+ }
}
/* Loop through the reset of the arguments looking for effects */
@@ -276,7 +285,7 @@
return(0);
}
-static char *getoptstr = "+r:v:t:c:phsuUAaigbwlfdxVSq";
+static char *getoptstr = "+r:v:t:c:C:phsuUAaigbwlfdxVSq";
static struct option long_options[] =
{
@@ -369,6 +378,17 @@
}
fo->info.channels = i;
break;
+
+ case 'C':
+ str = optarg;
+ if (!sscanf(str, "%lf", &fo->info.compression))
+ {
+ st_fail("-C must be given a number");
+ cleanup();
+ exit(1);
+ }
+ break;
+
case 'b':
fo->info.size = ST_SIZE_BYTE;
break;
@@ -1680,6 +1700,7 @@
"file unless overriden on the command line.\n"
"\n"
"-c channels number of channels in audio data\n"
+"-C compression compression factor for variably compressing output formats\n"
"-e skip processing of this filename. useful only\n"
" on output filename to prevent writing data.\n"
"-r rate sample rate of audio\n"
--- a/src/st.h
+++ b/src/st.h
@@ -23,6 +23,18 @@
#define ST_LIB_VERSION_CODE 0x0c1202
#define ST_LIB_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+/* C language enhancements: */
+
+/* Boolean type, compatible with C++ */
+typedef enum {false, true} bool;
+
+/* Compile-time ("static") assertion */
+/* e.g. assert_static(sizeof(int) >= 4, int_type_too_small) */
+#define assert_static(e,f) enum {assert_static__##f = 1/(e)}
+
+/* Array-length operator */
+#define array_length(a) (sizeof(a)/sizeof(a[0]))
+
typedef int32_t st_sample_t;
typedef uint32_t st_size_t;
typedef int32_t st_ssize_t;
@@ -105,6 +117,7 @@
signed char encoding; /* format of sample numbers */
signed char channels; /* number of sound channels */
char swap; /* do byte- or word-swap */
+ double compression; /* compression factor (where applicable) */
} st_signalinfo_t;
/* Loop parameters */
@@ -236,7 +249,8 @@
#define ST_ENCODING_INV_ALAW 10/* Inversed bit-order A-law */
#define ST_ENCODING_MP3 11/* MP3 compression */
#define ST_ENCODING_VORBIS 12/* Vorbis compression */
-#define ST_ENCODING_MAX 12
+#define ST_ENCODING_FLAC 13/* FLAC compression */
+#define ST_ENCODING_MAX 13
/* declared in misc.c */
extern const char *st_sizes_str[];
--- a/src/st_i.h
+++ b/src/st_i.h
@@ -68,7 +68,7 @@
*/
/* declared in misc.c */
st_ssize_t st_readbuf(ft_t ft, void *buf, size_t size, st_ssize_t len);
-st_ssize_t st_writebuf(ft_t ft, void *buf, size_t size, st_ssize_t len);
+st_ssize_t st_writebuf(ft_t ft, void const *buf, size_t size, st_ssize_t len);
int st_reads(ft_t ft, char *c, st_ssize_t len);
int st_writes(ft_t ft, char *c);
int st_readb(ft_t ft, uint8_t *ub);
@@ -162,6 +162,9 @@
extern const st_format_t *st_cvsd_format_fn(void);
extern const st_format_t *st_dvms_format_fn(void);
extern const st_format_t *st_dat_format_fn(void);
+#ifdef HAVE_LIBFLAC
+extern const st_format_t *st_flac_format_fn(void);
+#endif
#ifdef ENABLE_GSM
extern const st_format_t *st_gsm_format_fn(void);
#endif
--- a/src/stconfig.h.in
+++ b/src/stconfig.h.in
@@ -24,6 +24,9 @@
/* Define to 1 if you have the <fcntl.h> header file. */
#undef HAVE_FCNTL_H
+/* Define if you have FLAC Library installed */
+#undef HAVE_LIBFLAC
+
/* Define to 1 if you have the `getopt_long' function. */
#undef HAVE_GETOPT_LONG
--- a/src/vorbis.c
+++ b/src/vorbis.c
@@ -350,6 +350,7 @@
vorbis_t vb = (vorbis_t) ft->priv;
vorbis_enc_t *ve;
long rate;
+ double quality = 3; /* Default compression quality gives ~112kbps */
ft->info.size = ST_SIZE_16BIT;
ft->info.encoding = ST_ENCODING_VORBIS;
@@ -372,8 +373,18 @@
st_fail_errno(ft, ST_EHDR, "Error setting up Ogg Vorbis encorder - make sure you've specied a sane rate and number of channels");
}
- /* Set encoding to average bit rate of 112kbps VBR */
- vorbis_encode_init_vbr(&ve->vi, ft->info.channels, ft->info.rate, 0.3f);
+ /* Use encoding to average bit rate of VBR as specified by the -C option */
+ if (ft->info.compression != HUGE_VAL)
+ {
+ if (ft->info.compression < -1 || ft->info.compression > 10)
+ {
+ st_fail_errno(ft,ST_EINVAL,
+ "Vorbis compression quality nust be between -1 and 10");
+ return ST_EOF;
+ }
+ quality = ft->info.compression;
+ }
+ vorbis_encode_init_vbr(&ve->vi, ft->info.channels, ft->info.rate, quality / 10);
vorbis_analysis_init(&ve->vd, &ve->vi);
vorbis_block_init(&ve->vd, &ve->vb);