shithub: sox

Download patch

ref: af81c85a823c9ce0b2524e89faf6e42e3cbf4d0f
parent: dcef6ae58cb60543d48cfd4685c346c812d53632
author: rrt <rrt>
date: Mon Nov 13 19:37:30 EST 2006

Add patch from robs@users.sf.net:

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 cbagwell@users.sourceforge.net ##
+## --------------------------------------------- ##
+_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);