shithub: opus-tools

Download patch

ref: e9bbfacf0a1bbade70005fa730cc50e838baaa32
parent: 0731a61ea0551a8da04f681fb8703f9ad710c892
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Fri Nov 17 20:58:30 EST 2017

opusdec: Convert to use libopusfile

This adds a dependency, but also a number of features, such as the
 ability read from http[s] sources or downmix output to stereo.
It also allows opusdec to automatically pick a consistent output
 sample rate and channel count for seekable chained files, and
 gives nicer output for METADATA_BLOCK_PICTURE tags (we parse the
 tags and give a summary, instead of dumping a huge BASE64 string).

Unfortunately, we still have to do our own dithering, as this needs
 to be done after resampling, which happens outside of libopusfile,
 but at least we can re-use libopus's soft clipping implementation.

Signed-off-by: Mark Harris <mark.hsj@gmail.com>

--- a/Makefile.am
+++ b/Makefile.am
@@ -61,7 +61,8 @@
 
 opusdec_SOURCES = src/opus_header.c src/wav_io.c src/wave_out.c src/opusdec.c src/resample.c src/diag_range.c win32/unicode_support.c
 opusdec_CPPFLAGS = $(AM_CPPFLAGS) $(resampler_CPPFLAGS)
-opusdec_LDADD = $(OPUS_LIBS) $(OGG_LIBS) $(LIBM)
+opusdec_CFLAGS = $(AM_CFLAGS) $(OPUSURL_CFLAGS)
+opusdec_LDADD = $(OPUSURL_LIBS) $(OPUS_LIBS) $(LIBM)
 opusdec_MANS = man/opusdec.1
 
 opusinfo_SOURCES = src/opus_header.c src/opusinfo.c src/info_opus.c src/picture.c win32/unicode_support.c
--- a/configure.ac
+++ b/configure.ac
@@ -146,6 +146,19 @@
   ]))
  ])
 
+dnl check for opusfile (specifically, we need libopusurl)
+AS_IF([test "$HAVE_PKG_CONFIG" = "yes"],
+ [PKG_CHECK_MODULES([OPUSURL],[opusurl >= 0.5])],
+ [
+  dnl fall back to the old school test
+  XIPH_PATH_OPUSFILE(, AC_MSG_ERROR([
+    libopusfile is required to build this package!
+    Please see https://opus-codec.org/ for how to obtain a copy.
+  ]))
+  OPUSURL_CFLAGS="$OPUSFILE_CFLAGS"
+  OPUSURL_LIBS="$OPUSFILE_LIBS -lopusurl"
+ ])
+
 dnl check for OSS
 HAVE_OSS=no
 AC_CHECK_HEADERS([sys/soundcard.h soundcard.h machine/soundcard.h],[
--- /dev/null
+++ b/m4/opusfile.m4
@@ -1,0 +1,118 @@
+# Configure paths for libopusfile
+# Timothy B. Terriberry <tterribe@xiph.org> 17-11-2017
+# Shamelessly stolen from Gregory Maxwell <greg@xiph.org> 08-30-2012 who
+# Shamelessly stole from Jack Moffitt (libogg) who
+# Shamelessly stole from Owen Taylor and Manish Singh
+
+dnl XIPH_PATH_OPUSFILE([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
+dnl Test for libopusfile OPUSFILE_CFLAGS and OPUSFILE_LIBS
+dnl
+AC_DEFUN([XIPH_PATH_OPUSFILE],
+[dnl
+dnl Get the cflags and libraries
+dnl
+AC_ARG_WITH(opusfile,AC_HELP_STRING([--with-opusfile=PFX],[Prefix where opusfile is installed (optional)]), opusfile_prefix="$withval", opusfile_prefix="")
+AC_ARG_WITH(opusfile-libraries,AC_HELP_STRING([--with-opusfile-libraries=DIR],[Directory where the opusfile library is installed (optional)]), opusfile_libraries="$withval", opusfile_libraries="")
+AC_ARG_WITH(opusfile-includes,AC_HELP_STRING([--with-opusfile-includes=DIR],[Directory where the opusfile header files are installed (optional)]), opusfile_includes="$withval", opusfile_includes="")
+AC_ARG_ENABLE(opusfiletest,AC_HELP_STRING([--disable-opusfiletest],[Do not try to compile and run a test opusfile program]),, enable_opusfiletest=yes)
+
+  if test "x$opusfile_libraries" != "x" ; then
+    OPUSFILE_LIBS="-L$opusfile_libraries"
+  elif test "x$opusfile_prefix" = "xno" || test "x$opusfile_prefix" = "xyes" ; then
+    OPUSFILE_LIBS=""
+  elif test "x$opusfile_prefix" != "x" ; then
+    OPUSFILE_LIBS="-L$opusfile_prefix/lib"
+  elif test "x$prefix" != "xNONE" ; then
+    OPUSFILE_LIBS="-L$prefix/lib"
+  fi
+
+  if test "x$opusfile_prefix" != "xno" ; then
+    OPUSFILE_LIBS="$OPUSFILE_LIBS -lopusfile"
+  fi
+
+  if test "x$opusfile_includes" != "x" ; then
+    OPUSFILE_CFLAGS="-I$opusfile_includes"
+  elif test "x$opusfile_prefix" = "xno" || test "x$opusfile_prefix" = "xyes" ; then
+    OPUSFILE_CFLAGS=""
+  elif test "x$opusfile_prefix" != "x" ; then
+    OPUSFILE_CFLAGS="-I$opusfile_prefix/include/opus"
+  elif test "x$prefix" != "xNONE"; then
+    OPUSFILE_CFLAGS="-I$prefix/include/opus"
+  fi
+
+  AC_MSG_CHECKING(for libopusfile)
+  if test "x$opusfile_prefix" = "xno" ; then
+    no_opusfile="disabled"
+    enable_opusfiletest="no"
+  else
+    no_opusfile=""
+  fi
+
+
+  if test "x$enable_opusfiletest" = "xyes" ; then
+    ac_save_CFLAGS="$CFLAGS"
+    ac_save_LIBS="$LIBS"
+    CFLAGS="$CFLAGS $OPUSFILE_CFLAGS $OGG_CFLAGS $OPUS_CFLAGS"
+    LIBS="$LIBS $OPUSFILE_LIBS $OGG_LIBS $OPUS_LIBS"
+dnl
+dnl Now check if the installed libopusfile is sufficiently new.
+dnl
+      rm -f conf.opusfiletest
+      AC_TRY_RUN([
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <opusfile.h>
+
+int main ()
+{
+  system("touch conf.opusfiletest");
+  return 0;
+}
+
+],, no_opusfile=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+  fi
+
+  if test "x$no_opusfile" = "xdisabled" ; then
+     AC_MSG_RESULT(no)
+     ifelse([$2], , :, [$2])
+  elif test "x$no_opusfile" = "x" ; then
+     AC_MSG_RESULT(yes)
+     ifelse([$1], , :, [$1])
+  else
+     AC_MSG_RESULT(no)
+     if test -f conf.opusfiletest ; then
+       :
+     else
+       echo "*** Could not run libopusfile test program, checking why..."
+       CFLAGS="$CFLAGS $OPUSFILE_CFLAGS"
+       LIBS="$LIBS $OPUSFILE_LIBS"
+       AC_TRY_LINK([
+#include <stdio.h>
+#include <opusfile.h>
+],     [ return 0; ],
+       [ echo "*** The test program compiled, but did not run. This usually means"
+       echo "*** that the run-time linker is not finding libopusfile or finding the wrong"
+       echo "*** version of libopusfile. If it is not finding libopusfile, you'll need to set"
+       echo "*** your LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+       echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+       echo "*** is required on your system"
+       echo "***"
+       echo "*** If you have an old version installed, it is best to remove it, although"
+       echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
+       [ echo "*** The test program failed to compile or link. See the file config.log for the"
+       echo "*** exact error that occured. This usually means libopusfile was incorrectly"
+       echo "*** installed or that you have moved libopusfile since it was installed." ])
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+     OPUSFILE_CFLAGS=""
+     OPUSFILE_LIBS=""
+     ifelse([$2], , :, [$2])
+  fi
+  AC_SUBST(OPUSFILE_CFLAGS)
+  AC_SUBST(OPUSFILE_LIBS)
+  rm -f conf.opusfiletest
+])
--- a/man/opusdec.1
+++ b/man/opusdec.1
@@ -15,6 +15,8 @@
 ] [
 .B --rate Hz
 ] [
+.B --force-stereo
+] [
 .B --gain dB
 ] [
 .B --no-dither
@@ -60,9 +62,12 @@
 .IP "--rate"
 .br
 Force decoding at sampling rate n Hz
+.IP "--force-stereo"
+.br
+Force decoding to stereo
 .IP "--gain"
 .br
-Adjust the output volume n dB, negative values make the signal quieter.
+Adjust the output volume n dB, negative values make the signal quieter
 .IP "--no-dither"
 Do not dither 16-bit output
 .IP "--float"
--- a/src/opusdec.c
+++ b/src/opusdec.c
@@ -43,9 +43,15 @@
 #include <ctype.h> /*tolower()*/
 
 #include <opus.h>
-#include <opus_multistream.h>
-#include <ogg/ogg.h>
+#include <opusfile.h>
 
+/*We're using this define to test for libopus 1.1 or later until libopus
+  provides a better mechanism.*/
+#if defined(OPUS_GET_EXPERT_FRAME_DURATION_REQUEST)
+/*Enable soft clipping prevention.*/
+#define HAVE_SOFT_CLIP (1)
+#endif
+
 #if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
 # include "unicode_support.h"
 # include "wave_out.h"
@@ -220,69 +226,57 @@
   _ss->mute=MINI(mute,960);
 }
 
-static void print_comments(char *comments, int length)
+static void print_comments(const OpusTags *_tags)
 {
-   char *c=comments;
-   int len, i, nb_fields, err=0;
-
-   if (length<(8+4+4))
-   {
-      fprintf (stderr, "Invalid/corrupted comments\n");
-      return;
-   }
-   if (strncmp(c, "OpusTags", 8) != 0)
-   {
-      fprintf (stderr, "Invalid/corrupted comments\n");
-      return;
-   }
-   c += 8;
-   fprintf(stderr, "Encoded with ");
-   len=readint(c, 0);
-   c+=4;
-   if (len < 0 || len>(length-16))
-   {
-      fprintf (stderr, "Invalid/corrupted comments\n");
-      return;
-   }
-   err&=fwrite(c, 1, len, stderr)!=(unsigned)len;
-   c+=len;
-   fprintf (stderr, "\n");
-   /*The -16 check above makes sure we can read this.*/
-   nb_fields=readint(c, 0);
-   c+=4;
-   length-=16+len;
-   if (nb_fields < 0 || nb_fields>(length>>2))
-   {
-      fprintf (stderr, "Invalid/corrupted comments\n");
-      return;
-   }
-   for (i=0;i<nb_fields;i++)
-   {
-      if (length<4)
-      {
-         fprintf (stderr, "Invalid/corrupted comments\n");
-         return;
+   int i;
+   int ncomments;
+   fprintf(stderr, "Encoded with %s\n", _tags->vendor);
+   ncomments = _tags->comments;
+   for (i=0;i<ncomments;i++) {
+      char *comment;
+      comment=_tags->user_comments[i];
+      if (opus_tagncompare("METADATA_BLOCK_PICTURE",22,comment)==0) {
+         OpusPictureTag pic;
+         int            err;
+         err=opus_picture_tag_parse(&pic, comment);
+         fprintf(stderr, "%.23s", comment);
+         if (err<0) {
+            fprintf(stderr, "<error parsing picture tag>\n");
+         } else {
+            fprintf(stderr, "%u|%s|%s|%ux%ux%u", pic.type, pic.mime_type,
+             pic.description, pic.width, pic.height, pic.depth);
+            if (pic.colors != 0) {
+               fprintf(stderr, "/%u", pic.colors);
+            }
+            if (pic.format==OP_PIC_FORMAT_URL) {
+               fprintf(stderr, "|%s\n", pic.data);
+            } else {
+               /*We use separate strings for each of these to simplify i18n in
+                 the future someday.*/
+               static const char *PIC_FORMAT_STR[4] = {
+                  "|<%u bytes of image data>\n",
+                  "|<%u bytes of JPEG data>\n",
+                  "|<%u bytes of PNG data>\n",
+                  "|<%u bytes of GIF data>\n"
+               };
+               int format_idx;
+               format_idx = pic.format < 1 || pic.format >= 4 ? 0 : pic.format;
+               fprintf(stderr, PIC_FORMAT_STR[format_idx], pic.data_length);
+            }
+            opus_picture_tag_clear(&pic);
+         }
+      } else {
+         fprintf(stderr, "%s\n", comment);
       }
-      len=readint(c, 0);
-      c+=4;
-      length-=4;
-      if (len < 0 || len>length)
-      {
-         fprintf (stderr, "Invalid/corrupted comments\n");
-         return;
-      }
-      err&=fwrite(c, 1, len, stderr)!=(unsigned)len;
-      c+=len;
-      length-=len;
-      fprintf (stderr, "\n");
    }
 }
 
-FILE *out_file_open(char *outFile, int *wav_format, int rate, int mapping_family, int *channels, int fp)
+FILE *out_file_open(char *outFile, int file_output, int *wav_format,
+ int rate, int mapping_family, int *channels, int fp)
 {
    FILE *fout=NULL;
    /*Open output file*/
-   if (strlen(outFile)==0)
+   if (!file_output)
    {
 #if defined HAVE_LIBSNDIO
       struct sio_par par;
@@ -302,9 +296,15 @@
 
       if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par) ||
         par.sig != 1 || par.bits != 16 || par.rate != rate) {
-          fprintf(stderr, "could not set sndio parameters\n");
-          quit(1);
+         fprintf(stderr, "could not set sndio parameters\n");
+         quit(1);
       }
+      /*We allow the channel count to be forced to stereo, but not anything
+        else.*/
+      if (*channels!=par.pchan && par.pchan!=2) {
+         fprintf(stderr, "could not set sndio channel count\n");
+         quit(1);
+      }
       *channels = par.pchan;
       if (!sio_start(hdl)) {
           fprintf(stderr, "could not start sndio\n");
@@ -331,12 +331,11 @@
       {
         /* There doesn't seem to be a way to get or set the channel
          * matrix with the sys/soundcard api, so we can't support
-         * multichannel. We should fall back to stereo downmix.
+         * multichannel. We fall back to stereo downmix.
          */
         fprintf(stderr, "Cannot configure multichannel playback."
-                        " Try decoding to a file instead.\n");
-        close(audio_fd);
-        quit(1);
+                        " Falling back to stereo.\n");
+        *channels=2;
       }
       stereo=0;
       if (*channels==2)
@@ -350,7 +349,7 @@
       if (stereo!=0)
       {
          if (*channels==1)
-            fprintf (stderr, "Cannot set mono mode, will decode in stereo\n");
+            fprintf(stderr, "Cannot set mono mode, will decode in stereo\n");
          *channels=2;
       }
 
@@ -388,7 +387,7 @@
 
       if (ioctl(audio_fd, AUDIO_SETINFO, &info) < 0)
       {
-         perror ("AUDIO_SETINFO");
+         perror("AUDIO_SETINFO");
          quit(1);
       }
       fout = fdopen(audio_fd, "w");
@@ -400,14 +399,14 @@
 #elif defined WIN32 || defined _WIN32
       {
          unsigned int opus_channels = *channels;
-         if (Set_WIN_Params (INVALID_FILEDESC, rate, SAMPLE_SIZE, opus_channels))
+         if (Set_WIN_Params(INVALID_FILEDESC, rate, SAMPLE_SIZE, opus_channels))
          {
-            fprintf (stderr, "Can't access %s\n", "WAVE OUT");
+            fprintf(stderr, "Can't access %s\n", "WAVE OUT");
             quit(1);
          }
       }
 #else
-      fprintf (stderr, "No soundcard support\n");
+      fprintf(stderr, "No soundcard support\n");
       quit(1);
 #endif
    } else {
@@ -432,7 +431,7 @@
          *wav_format = write_wav_header(fout, rate, mapping_family, *channels, fp);
          if (*wav_format < 0)
          {
-            fprintf (stderr, "Error writing WAV header.\n");
+            fprintf(stderr, "Error writing WAV header.\n");
             quit(1);
          }
       }
@@ -442,32 +441,33 @@
 
 void usage(void)
 {
-   printf ("Usage: opusdec [options] input_file.opus [output_file]\n");
-   printf ("\n");
-   printf ("Decodes a Opus file and produce a WAV file or raw file\n");
-   printf ("\n");
-   printf ("input_file can be:\n");
-   printf ("  filename.opus        regular Opus file\n");
-   printf ("  -                    stdin\n");
-   printf ("\n");
-   printf ("output_file can be:\n");
-   printf ("  filename.wav         Wav file\n");
-   printf ("  filename.*           Raw PCM file (any extension other than .wav)\n");
-   printf ("  -                    stdout (raw; unless --force-wav)\n");
-   printf ("  (nothing)            Will be played to soundcard\n");
-   printf ("\n");
-   printf ("Options:\n");
-   printf (" --rate n              Force decoding at sampling rate n Hz\n");
-   printf (" --gain n              Manually adjust gain by n.nn dB (0 default)\n");
-   printf (" --no-dither           Do not dither 16-bit output\n");
-   printf (" --float               32-bit floating-point output\n");
-   printf (" --force-wav           Force wav header on output\n");
-   printf (" --packet-loss n       Simulate n %% random packet loss\n");
-   printf (" --save-range file     Saves check values for every frame to a file\n");
-   printf (" -h, --help            This help\n");
-   printf (" -V, --version         Version information\n");
-   printf (" --quiet               Quiet mode\n");
-   printf ("\n");
+   printf("Usage: opusdec [options] input_file.opus [output_file]\n");
+   printf("\n");
+   printf("Decodes a Opus file and produce a WAV file or raw file\n");
+   printf("\n");
+   printf("input_file can be:\n");
+   printf("  filename.opus        regular Opus file\n");
+   printf("  -                    stdin\n");
+   printf("\n");
+   printf("output_file can be:\n");
+   printf("  filename.wav         Wav file\n");
+   printf("  filename.*           Raw PCM file (any extension other than .wav)\n");
+   printf("  -                    stdout (raw; unless --force-wav)\n");
+   printf("  (nothing)            Will be played to soundcard\n");
+   printf("\n");
+   printf("Options:\n");
+   printf(" --rate n              Force decoding at sampling rate n Hz\n");
+   printf(" --force-stereo        Force decoding to stereo\n");
+   printf(" --gain n              Manually adjust gain by n.nn dB (0 default)\n");
+   printf(" --no-dither           Do not dither 16-bit output\n");
+   printf(" --float               32-bit floating-point output\n");
+   printf(" --force-wav           Force wav header on output\n");
+   printf(" --packet-loss n       Simulate n %% random packet loss\n");
+   printf(" --save-range file     Saves check values for every frame to a file\n");
+   printf(" -h, --help            This help\n");
+   printf(" -V, --version         Version information\n");
+   printf(" --quiet               Quiet mode\n");
+   printf("\n");
 }
 
 void version(void)
@@ -481,88 +481,14 @@
    version();
 }
 
-/*Process an Opus header and setup the opus decoder based on it.
-  It takes several pointers for header values which are needed
-  elsewhere in the code.*/
-static OpusMSDecoder *process_header(ogg_packet *op, opus_int32 *rate,
-       int *mapping_family, int *channels, int *preskip, float *gain,
-       float manual_gain, int *streams, int wav_format, int quiet)
+opus_int64 audio_write(float *pcm, int channels, int frame_size, FILE *fout,
+ SpeexResamplerState *resampler, float *clipmem, shapestate *shapemem,
+ int file, int rate, opus_int64 link_read, opus_int64 link_out, int fp)
 {
-   int err;
-   OpusMSDecoder *st;
-   OpusHeader header;
-
-   if (opus_header_parse(op->packet, op->bytes, &header)==0)
-   {
-      fprintf(stderr, "Cannot parse header\n");
-      return NULL;
-   }
-
-   *mapping_family = header.channel_mapping;
-   *channels = header.channels;
-   if(wav_format)adjust_wav_mapping(*mapping_family, *channels, header.stream_map);
-
-   if(!*rate)*rate=header.input_sample_rate;
-   /*If the rate is unspecified we decode to 48000*/
-   if(*rate==0)*rate=48000;
-   if(*rate<8000||*rate>192000){
-     fprintf(stderr,"Warning: Crazy input_rate %d, decoding to 48000 instead.\n",*rate);
-     *rate=48000;
-   }
-
-   *preskip = header.preskip;
-   st = opus_multistream_decoder_create(48000, header.channels, header.nb_streams, header.nb_coupled, header.stream_map, &err);
-   if(err != OPUS_OK){
-     fprintf(stderr, "Cannot create decoder: %s\n", opus_strerror(err));
-     return NULL;
-   }
-   if (!st)
-   {
-      fprintf (stderr, "Decoder initialization failed: %s\n", opus_strerror(err));
-      return NULL;
-   }
-
-   *streams=header.nb_streams;
-
-   if(header.gain!=0 || manual_gain!=0)
-   {
-      /*Gain API added in a newer libopus version, if we don't have it
-        we apply the gain ourselves. We also add in a user provided
-        manual gain at the same time.*/
-      int gainadj = (int)(manual_gain*256.)+header.gain;
-#ifdef OPUS_SET_GAIN
-      err=opus_multistream_decoder_ctl(st,OPUS_SET_GAIN(gainadj));
-      if(err==OPUS_UNIMPLEMENTED)
-      {
-#endif
-         *gain = pow(10., gainadj/5120.);
-#ifdef OPUS_SET_GAIN
-      } else if (err!=OPUS_OK)
-      {
-         fprintf (stderr, "Error setting gain: %s\n", opus_strerror(err));
-         return NULL;
-      }
-#endif
-   }
-
-   if (!quiet)
-   {
-      fprintf(stderr, "Decoding to %d Hz (%d channel%s)", *rate,
-        *channels, *channels>1?"s":"");
-      if(header.version!=1)fprintf(stderr, ", Header v%d",header.version);
-      fprintf(stderr, "\n");
-      if (header.gain!=0)fprintf(stderr,"Playback gain: %f dB\n", header.gain/256.);
-      if (manual_gain!=0)fprintf(stderr,"Manual gain: %f dB\n", manual_gain);
-   }
-
-   return st;
-}
-
-opus_int64 audio_write(float *pcm, int channels, int frame_size, FILE *fout, SpeexResamplerState *resampler,
-                       int *skip, shapestate *shapemem, int file, opus_int64 maxout, int fp)
-{
    opus_int64 sampout=0;
-   int i,ret,tmp_skip;
+   opus_int64 maxout;
+   int ret;
+   int i;
    unsigned out_len;
    short *out;
    float *buf;
@@ -569,25 +495,21 @@
    float *output;
    out=alloca(sizeof(short)*MAX_FRAME_SIZE*channels);
    buf=alloca(sizeof(float)*MAX_FRAME_SIZE*channels);
+   maxout=((link_read/48000)*rate + (link_read%48000)*rate/48000) - link_out;
    maxout=maxout<0?0:maxout;
    do {
-     if (skip){
-       tmp_skip = (*skip>frame_size) ? (int)frame_size : *skip;
-       *skip -= tmp_skip;
-     } else {
-       tmp_skip = 0;
-     }
      if (resampler){
        unsigned in_len;
        output=buf;
-       in_len = frame_size-tmp_skip;
+       in_len = frame_size;
        out_len = 1024<maxout?1024:maxout;
-       speex_resampler_process_interleaved_float(resampler, pcm+channels*tmp_skip, &in_len, buf, &out_len);
-       pcm += channels*(in_len+tmp_skip);
-       frame_size -= in_len+tmp_skip;
+       speex_resampler_process_interleaved_float(resampler,
+        pcm, &in_len, buf, &out_len);
+       pcm += channels*(in_len);
+       frame_size -= in_len;
      } else {
-       output=pcm+channels*tmp_skip;
-       out_len=frame_size-tmp_skip;
+       output=pcm;
+       out_len=frame_size;
        frame_size=0;
      }
 
@@ -594,6 +516,11 @@
      if(!file||!fp)
      {
         /*Convert to short and save to output file*/
+#if defined(HAVE_SOFT_CLIP)
+        opus_pcm_soft_clip(output,out_len,channels,clipmem);
+#else
+        (void)clipmem;
+#endif
         if (shapemem){
           shape_dither_toshort(shapemem,out,output,out_len,channels);
         }else{
@@ -610,18 +537,20 @@
      {
 #if defined WIN32 || defined _WIN32
        if(!file){
-         ret=WIN_Play_Samples (out, sizeof(short) * channels * (out_len<maxout?out_len:maxout));
+         ret=WIN_Play_Samples(out, sizeof(short) * channels * (out_len<maxout?out_len:maxout));
          if(ret>0)ret/=sizeof(short)*channels;
          else fprintf(stderr, "Error playing audio.\n");
        }else
 #elif defined HAVE_LIBSNDIO
        if(!file){
-         ret=sio_write (hdl, out, sizeof(short) * channels * (out_len<maxout?out_len:maxout));
+         ret=sio_write(hdl, out, sizeof(short) * channels * (out_len<maxout?out_len:maxout));
          if(ret>0)ret/=sizeof(short)*channels;
          else fprintf(stderr, "Error playing audio.\n");
        }else
 #endif
-         ret=fwrite(fp?(char *)output:(char *)out, (fp?4:2)*channels, out_len<maxout?out_len:maxout, fout);
+         ret=fwrite(fp?(char *)output:(char *)out,
+          (fp?sizeof(float):sizeof(short))*channels,
+          out_len<maxout?out_len:maxout, fout);
        sampout+=ret;
        maxout-=ret;
      }
@@ -629,21 +558,122 @@
    return sampout;
 }
 
+typedef struct decode_cb_ctx decode_cb_ctx;
+struct decode_cb_ctx{
+   FILE *frange;
+   float loss_percent;
+};
+
+static int decode_cb(decode_cb_ctx *ctx, OpusMSDecoder *decoder, void *pcm,
+ const ogg_packet *op, int nsamples, int nchannels, int format, int li)
+{
+   int lost;
+   int ret;
+   (void)nchannels;
+   (void)li;
+   lost = ctx->loss_percent>0
+    && 100*((float)rand())/RAND_MAX<ctx->loss_percent;
+   switch(format)
+   {
+      case OP_DEC_FORMAT_SHORT:
+      {
+         if (lost)
+         {
+            ret = opus_multistream_decode(decoder,
+             NULL, 0, pcm, nsamples, 0);
+         } else {
+            ret = opus_multistream_decode(decoder,
+             op->packet, op->bytes, pcm, nsamples, 0);
+         }
+         break;
+      }
+      case OP_DEC_FORMAT_FLOAT:
+      {
+         if (lost)
+         {
+            ret = opus_multistream_decode_float(decoder,
+             NULL, 0, pcm, nsamples, 0);
+         } else {
+            ret = opus_multistream_decode_float(decoder,
+             op->packet, op->bytes, pcm, nsamples, 0);
+         }
+         break;
+      }
+      default:
+      {
+         return OPUS_BAD_ARG;
+      }
+   }
+   /*On success, either we got as many samples as we wanted, or something went
+     wrong.*/
+   if (ret >= 0)
+   {
+      ret=ret==nsamples?0:OPUS_INTERNAL_ERROR;
+      if (ret==0 && ctx->frange!=NULL)
+      {
+         OpusDecoder *od;
+         opus_uint32 rngs[256];
+         int err;
+         int si;
+         /*If we're collecting --save-range debugging data, collect it now.*/
+         for (si=0;si<255;si++)
+         {
+            err=opus_multistream_decoder_ctl(decoder,
+             OPUS_MULTISTREAM_GET_DECODER_STATE(si, &od));
+            /*This will fail with OPUS_BAD_ARG the first time we ask for a
+              stream that isn't there, which is currently the only way to find
+              out how many streams there are using the libopus API.*/
+            if(err<0)break;
+            opus_decoder_ctl(od,OPUS_GET_FINAL_RANGE(&rngs[si]));
+         }
+         save_range(ctx->frange, nsamples, op->packet, op->bytes, rngs, si);
+      }
+   }
+   return ret;
+}
+
+static void drain_resampler(FILE *fout, int file_output,
+ SpeexResamplerState *resampler, int channels, int rate,
+ opus_int64 link_read, opus_int64 link_out, float *clipmem,
+ shapestate *shapemem, opus_int64 *audio_size, int fp)
+{
+   float *zeros;
+   int drain;
+   zeros=(float *)calloc(100*channels,sizeof(float));
+   drain=speex_resampler_get_input_latency(resampler);
+   do
+   {
+      opus_int64 outsamp;
+      int tmp=MINI(drain, 100);
+      outsamp=audio_write(zeros, channels, tmp, fout, resampler, clipmem,
+       shapemem, file_output, rate, link_read, link_out, fp);
+      link_out+=outsamp;
+      (*audio_size)+=(fp?sizeof(float):sizeof(short))*outsamp*channels;
+      drain-=tmp;
+   } while (drain>0);
+   free(zeros);
+}
+
 int main(int argc, char **argv)
 {
+   unsigned char channel_map[OPUS_CHANNEL_COUNT_MAX];
+   float clipmem[8]={0};
    int c;
    int option_index = 0;
    char *inFile, *outFile;
-   FILE *fin, *fout=NULL, *frange=NULL;
+   FILE *fout=NULL, *frange=NULL;
    float *output;
-   int frame_size=0;
-   OpusMSDecoder *st=NULL;
-   opus_int64 packet_count=0;
-   int total_links=0;
-   int stream_init = 0;
+   float *permuted_output;
+   OggOpusFile *st=NULL;
+   const OpusHead *head;
+   decode_cb_ctx cb_ctx;
+   int file_output;
+   int old_li=-1;
+   int li;
    int quiet = 0;
    int forcewav = 0;
-   ogg_int64_t page_granule=0;
+   ogg_int64_t nb_read_total=0;
+   ogg_int64_t link_read=0;
    ogg_int64_t link_out=0;
    struct option long_options[] =
    {
@@ -652,6 +682,7 @@
       {"version", no_argument, NULL, 0},
       {"version-short", no_argument, NULL, 0},
       {"rate", required_argument, NULL, 0},
+      {"force-stereo", no_argument, NULL, 0},
       {"gain", required_argument, NULL, 0},
       {"no-dither", no_argument, NULL, 0},
       {"float", no_argument, NULL, 0},
@@ -660,31 +691,20 @@
       {"save-range", required_argument, NULL, 0},
       {0, 0, 0, 0}
    };
-   ogg_sync_state oy;
-   ogg_page       og;
-   ogg_packet     op;
-   ogg_stream_state os;
-   int close_in=0;
-   int eos=0;
-   ogg_int64_t audio_size=0;
+   opus_int64 audio_size=0;
    double last_coded_seconds=0;
    float loss_percent=-1;
    float manual_gain=0;
+   int force_rate=0;
+   int force_stereo=0;
+   int requested_channels=-1;
    int channels=-1;
-   int mapping_family;
    int rate=0;
    int wav_format=0;
-   int preskip=0;
-   int gran_offset=0;
-   int has_opus_stream=0;
-   int has_tags_packet=0;
-   ogg_int32_t opus_serialno;
    int dither=1;
    int fp=0;
    shapestate shapemem;
    SpeexResamplerState *resampler=NULL;
-   float gain=1;
-   int streams=0;
    size_t last_spin=0;
 #ifdef WIN_UNICODE
    int argc_utf8;
@@ -713,7 +733,7 @@
    /*Process options*/
    while(1)
    {
-      c = getopt_long (argc_utf8, argv_utf8, "hV",
+      c = getopt_long(argc_utf8, argv_utf8, "hV",
                        long_options, &option_index);
       if (c==-1)
          break;
@@ -747,10 +767,13 @@
             forcewav=1;
          } else if (strcmp(long_options[option_index].name,"rate")==0)
          {
-            rate=atoi (optarg);
+            rate=atoi(optarg);
+         } else if (strcmp(long_options[option_index].name,"force-stereo")==0)
+         {
+            force_stereo=1;
          } else if (strcmp(long_options[option_index].name,"gain")==0)
          {
-            manual_gain=atof (optarg);
+            manual_gain=atof(optarg);
          }else if(strcmp(long_options[option_index].name,"save-range")==0){
           frange=fopen_utf8(optarg,"w");
           if(frange==NULL){
@@ -786,7 +809,8 @@
    inFile=argv_utf8[optind];
 
    /*Output to a file or playback?*/
-   if (argc_utf8-optind==2){
+   file_output=argc_utf8-optind==2;
+   if (file_output){
      /*If we're outputting to a file, should we apply a wav header?*/
      int i;
      char *ext;
@@ -817,335 +841,356 @@
    /*Open input file*/
    if (strcmp(inFile, "-")==0)
    {
+      OpusFileCallbacks cb={NULL,NULL,NULL,NULL};
+      int fd;
 #if defined WIN32 || defined _WIN32
-      _setmode(_fileno(stdin), _O_BINARY);
+      fd = _fileno(stdin);
+      _setmode(fd, _O_BINARY);
+#else
+      fd = fileno(stdin);
 #endif
-      fin=stdin;
+      st=op_open_callbacks(op_fdopen(&cb, fd, "rb"), &cb, NULL, 0, NULL);
    }
    else
    {
-      fin = fopen_utf8(inFile, "rb");
-      if (!fin)
+      st=op_open_url(inFile,NULL,NULL);
+      if (st==NULL)
       {
-         perror(inFile);
-         quit(1);
+         st=op_open_file(inFile,NULL);
       }
-      close_in=1;
    }
+   if (st==NULL)
+   {
+      fprintf(stderr, "Failed to open '%s'.\n", inFile);
+      quit(1);
+   }
 
-   /* .opus files use the Ogg container to provide framing and timekeeping.
-    * http://tools.ietf.org/html/draft-terriberry-oggopus
-    * The easiest way to decode the Ogg container is to use libogg, so
-    *  thats what we do here.
-    * Using libogg is fairly straight forward-- you take your stream of bytes
-    *  and feed them to ogg_sync_ and it periodically returns Ogg pages, you
-    *  check if the pages belong to the stream you're decoding then you give
-    *  them to libogg and it gives you packets. You decode the packets. The
-    *  pages also provide timing information.*/
-   ogg_sync_init(&oy);
-
-   /*Main decoding loop*/
-   while (1)
+   if (manual_gain != 0.F)
    {
-      char *data;
-      int i, nb_read;
-      /*Get the ogg buffer for writing*/
-      data = ogg_sync_buffer(&oy, 200);
-      /*Read bitstream from input file*/
-      nb_read = fread(data, sizeof(char), 200, fin);
-      ogg_sync_wrote(&oy, nb_read);
+       op_set_gain_offset(st, OP_HEADER_GAIN, float2int(manual_gain*256.F));
+   }
 
-      /*Loop for all complete pages we got (most likely only one)*/
-      while (ogg_sync_pageout(&oy, &og)==1)
+   head = op_head(st, 0);
+   if (op_seekable(st))
+   {
+      int nlinks;
+      /*If we have a seekable file, we can make some intelligent decisions
+        about how to decode.*/
+      nlinks = op_link_count(st);
+      if (rate==0)
       {
-         if (stream_init == 0) {
-            ogg_stream_init(&os, ogg_page_serialno(&og));
-            stream_init = 1;
-         }
-         if (ogg_page_serialno(&og) != os.serialno) {
-            /* so all streams are read. */
-            ogg_stream_reset_serialno(&os, ogg_page_serialno(&og));
-         }
-         /*Add page to the bitstream*/
-         ogg_stream_pagein(&os, &og);
-         page_granule = ogg_page_granulepos(&og);
-         /*Extract all available packets*/
-         while (ogg_stream_packetout(&os, &op) == 1)
+         opus_uint32 initial_rate;
+         initial_rate=head->input_sample_rate;
+         /*We decode unknown rates at 48 kHz, so don't complain about a
+           mismatch between 48 kHz and "unknown".*/
+         if (initial_rate==0)
          {
-            /*OggOpus streams are identified by a magic string in the initial
-              stream header.*/
-            if (op.b_o_s && op.bytes>=8 && !memcmp(op.packet, "OpusHead", 8)) {
-               if(has_opus_stream && has_tags_packet)
-               {
-                 /*If we're seeing another BOS OpusHead now it means
-                   the stream is chained without an EOS.*/
-                 has_opus_stream=0;
-                 if(st)opus_multistream_decoder_destroy(st);
-                 st=NULL;
-                 fprintf(stderr,"\nWarning: stream %" I64FORMAT " ended without EOS and a new stream began.\n",(long long)os.serialno);
-               }
-               if(!has_opus_stream)
-               {
-                 if(packet_count>0 && opus_serialno==os.serialno)
-                 {
-                   fprintf(stderr,"\nError: Apparent chaining without changing serial number (%" I64FORMAT "==%" I64FORMAT ").\n",
-                     (long long)opus_serialno,(long long)os.serialno);
-                   quit(1);
-                 }
-                 opus_serialno = os.serialno;
-                 has_opus_stream = 1;
-                 has_tags_packet = 0;
-                 link_out = 0;
-                 packet_count = 0;
-                 eos = 0;
-                 total_links++;
-               } else {
-                 fprintf(stderr,"\nWarning: ignoring opus stream %" I64FORMAT "\n",(long long)os.serialno);
-               }
+            initial_rate=48000;
+         }
+         for (li=1;li<nlinks;li++) {
+            opus_uint32 cur_rate;
+            cur_rate = op_head(st, li)->input_sample_rate;
+            if (cur_rate==0)
+            {
+               cur_rate=48000;
             }
-            if (!has_opus_stream || os.serialno != opus_serialno)
+            if (initial_rate!=cur_rate)
+            {
+               fprintf(stderr,
+                "Warning: Chained stream with multiple input sample rates: "
+                "forcing decode to 48 kHz.\n");
+               rate=48000;
                break;
-            /*If first packet in a logical stream, process the Opus header*/
-            if (packet_count==0)
+            }
+         }
+      }
+      if (!force_stereo)
+      {
+         int initial_channels;
+         initial_channels = head->channel_count;
+         for (li=1;li<nlinks;li++) {
+            int cur_channels;
+            cur_channels = op_head(st, li)->channel_count;
+            if (initial_channels!=cur_channels)
             {
-               int old_channels = channels;
-               st = process_header(&op, &rate, &mapping_family, &channels, &preskip, &gain, manual_gain, &streams, wav_format, quiet);
-               if (!st)
-                  quit(1);
+               fprintf(stderr,
+                "Warning: Chained stream with multiple channel counts: "
+                "forcing decode to stereo.\n");
+               force_stereo=1;
+               break;
+            }
+         }
+      }
+   }
 
-               if (output && channels!=old_channels)
-               {
-                   fprintf(stderr,"\nError: Apparent chaining changes channel count from %d to %d.\n",
-                     old_channels,channels);
-                   fprintf(stderr,"This is currently unhandled by opusdec.\n");
-                   quit(1);
-               }
+   if (rate==0)
+   {
+      rate=head->input_sample_rate;
+      /*If the rate is unspecified, we decode to 48000.*/
+      if (rate==0)
+      {
+         rate=48000;
+      }
+   } else {
+      /*Remember that we forced the rate, so we don't complain if it changes in
+        an unseekable chained stream.*/
+      force_rate=1;
+   }
+   if (rate<8000||rate>192000)
+   {
+      fprintf(stderr,
+       "Warning: Crazy input_rate %d, decoding to 48000 instead.\n", rate);
+      rate=48000;
+      force_rate=1;
+   }
 
-               if(ogg_stream_packetout(&os, &op)!=0 || og.header[og.header_len-1]==255)
-               {
-                  /*The format specifies that the initial header and tags packets are on their
-                    own pages. To aid implementors in discovering that their files are wrong
-                    we reject them explicitly here. In some player designs files like this would
-                    fail even without an explicit test.*/
-                  fprintf(stderr, "Extra packets on initial header page. Invalid stream.\n");
-                  quit(1);
-               }
+   requested_channels=force_stereo?2:head->channel_count;
+   /*TODO: For seekable sources, write the output length in the WAV header.*/
+   channels=requested_channels;
+   fout=out_file_open(outFile, file_output,
+    &wav_format, rate, head->mapping_family, &channels, fp);
+   if(channels!=requested_channels)force_stereo=1;
+   /*Setup the memory for the dithered output*/
+   if(!shapemem.a_buf)
+   {
+      shapemem.a_buf=calloc(channels,sizeof(float)*4);
+      shapemem.b_buf=calloc(channels,sizeof(float)*4);
+      shapemem.fs=rate;
+   }
+   output=malloc(sizeof(float)*MAX_FRAME_SIZE*channels);
+   permuted_output=NULL;
+   if(wav_format&&(channels==3||channels>4))
+   {
+      int ci;
+      for (ci=0;ci<channels;ci++)
+      {
+         channel_map[ci]=ci;
+      }
+      adjust_wav_mapping(head->mapping_family, channels, channel_map);
+      permuted_output=malloc(sizeof(float)*MAX_FRAME_SIZE*channels);
+      if(!permuted_output)
+      {
+         fprintf(stderr, "Memory allocation failure.\n");
+         quit(1);
+      }
+   }
 
-               /*Remember how many samples at the front we were told to skip
-                 so that we can adjust the timestamp counting.*/
-               gran_offset=preskip;
+   /*If we're simulating packet loss or saving range data, then we need to
+     install a decoder callback.*/
+   if (loss_percent>0 || frange!=NULL)
+   {
+      cb_ctx.loss_percent=loss_percent;
+      cb_ctx.frange=frange;
+      op_set_decode_callback(st, (op_decode_cb_func)decode_cb, &cb_ctx);
+   }
 
-               /*Setup the memory for the dithered output*/
-               if(!shapemem.a_buf)
-               {
-                  shapemem.a_buf=calloc(channels,sizeof(float)*4);
-                  shapemem.b_buf=calloc(channels,sizeof(float)*4);
-                  shapemem.fs=rate;
-               }
-               if(!output)output=malloc(sizeof(float)*MAX_FRAME_SIZE*channels);
-               if(!shapemem.a_buf||!shapemem.b_buf||!output)
-               {
-                  fprintf(stderr, "Memory allocation failure.\n");
-                  quit(1);
-               }
-
-               /*Normal players should just play at 48000 or their maximum rate,
-                 as described in the OggOpus spec.  But for commandline tools
-                 like opusdec it can be desirable to exactly preserve the original
-                 sampling rate and duration, so we have a resampler here.*/
-               if (rate != 48000 && resampler==NULL)
-               {
-                  int err;
-                  resampler = speex_resampler_init(channels, 48000, rate, 5, &err);
-                  if (err!=0)
-                     fprintf(stderr, "resampler error: %s\n", speex_resampler_strerror(err));
-                  speex_resampler_skip_zeros(resampler);
-               }
-               if(!fout)fout=out_file_open(outFile, &wav_format, rate, mapping_family, &channels, fp);
-            } else if (packet_count==1)
-            {
-               if (!quiet)
-                  print_comments((char*)op.packet, op.bytes);
-               has_tags_packet=1;
-               if(ogg_stream_packetout(&os, &op)!=0 || og.header[og.header_len-1]==255)
-               {
-                  fprintf(stderr, "Extra packets on initial tags page. Invalid stream.\n");
-                  quit(1);
-               }
-            } else {
-               int ret;
-               opus_int64 maxout;
-               opus_int64 outsamp;
-               int lost=0;
-               if (loss_percent>0 && 100*((float)rand())/RAND_MAX<loss_percent)
-                  lost=1;
-
-               /*End of stream condition*/
-               if (op.e_o_s && os.serialno == opus_serialno)eos=1; /* don't care for anything except opus eos */
-
-               /*Are we simulating loss for this packet?*/
-               if (!lost){
-                  /*Decode Opus packet*/
-                  ret = opus_multistream_decode_float(st, (unsigned char*)op.packet, op.bytes, output, MAX_FRAME_SIZE, 0);
-               } else {
-                  /*Extract the original duration.
-                    Normally you wouldn't have it for a lost packet, but normally the
-                    transports used on lossy channels will effectively tell you.
-                    This avoids opusdec squaking when the decoded samples and
-                    granpos mismatches.*/
-                  opus_int32 lost_size;
-                  lost_size = MAX_FRAME_SIZE;
-                  if(op.bytes>0){
-                    opus_int32 spp;
-                    spp=opus_packet_get_nb_frames(op.packet,op.bytes);
-                    if(spp>0){
-                      spp*=opus_packet_get_samples_per_frame(op.packet,48000/*decoding_rate*/);
-                      if(spp>0)lost_size=spp;
-                    }
-                  }
-                  /*Invoke packet loss concealment.*/
-                  ret = opus_multistream_decode_float(st, NULL, 0, output, lost_size, 0);
-               }
-
-               if(!quiet){
-                  /*Display a progress spinner while decoding.*/
-                  static const char spinner[]="|/-\\";
-                  double coded_seconds = (double)audio_size/(channels*rate*(fp?4:2));
-                  if(coded_seconds>=last_coded_seconds+1){
-                     fprintf(stderr,"\r[%c] %02d:%02d:%02d", spinner[last_spin&3],
-                             (int)(coded_seconds/3600),(int)(coded_seconds/60)%60,
-                             (int)(coded_seconds)%60);
-                     fflush(stderr);
-                     last_spin++;
-                     last_coded_seconds=coded_seconds;
-                  }
-               }
-
-               /*If the decoder returned less than zero, we have an error.*/
-               if (ret<0)
-               {
-                  fprintf (stderr, "Decoding error: %s\n", opus_strerror(ret));
-                  break;
-               }
-               frame_size = ret;
-
-               /*If we're collecting --save-range debugging data, collect it now.*/
-               if(frange!=NULL){
-                 OpusDecoder *od;
-                 opus_uint32 rngs[256];
-                 for(i=0;i<streams;i++){
-                   ret=opus_multistream_decoder_ctl(st,OPUS_MULTISTREAM_GET_DECODER_STATE(i,&od));
-                   ret=opus_decoder_ctl(od,OPUS_GET_FINAL_RANGE(&rngs[i]));
-                 }
-                 save_range(frange,frame_size*(48000/48000/*decoding_rate*/),op.packet,op.bytes,
-                            rngs,streams);
-               }
-
-               /*Apply header gain, if we're not using an opus library new
-                 enough to do this internally.*/
-               if (gain!=0){
-                 for (i=0;i<frame_size*channels;i++)
-                    output[i] *= gain;
-               }
-
-               /*This handles making sure that our output duration respects
-                 the final end-trim by not letting the output sample count
-                 get ahead of the granpos indicated value.*/
-               maxout=((page_granule-gran_offset)*rate/48000)-link_out;
-               outsamp=audio_write(output, channels, frame_size, fout, resampler, &preskip, dither?&shapemem:0, strlen(outFile)!=0,0>maxout?0:maxout,fp);
-               link_out+=outsamp;
-               audio_size+=(fp?4:2)*outsamp*channels;
-            }
-            packet_count++;
+   /*Main decoding loop*/
+   while (1)
+   {
+      opus_int64 outsamp;
+      int nb_read;
+      int i;
+      if (force_stereo)
+      {
+         nb_read=op_read_float_stereo(st,
+          output, MAX_FRAME_SIZE*channels);
+         li = op_current_link(st);
+      } else {
+         nb_read=op_read_float(st,
+          output, MAX_FRAME_SIZE*channels, &li);
+      }
+      if (nb_read<0) {
+         if (nb_read==OP_HOLE) {
+            /*TODO: At...?*/
+            fprintf(stderr, "Warning: Hole in data.\n");
+            continue;
+         } else {
+            fprintf(stderr, "Decoding error.\n");
+            break;
+         }
+      }
+      if (nb_read==0)
+      {
+         if (!quiet)
+         {
+            fprintf(stderr, "\rDecoding complete.        \n");
+            fflush(stderr);
+         }
+         break;
+      }
+      if (li!=old_li)
+      {
+         /*Drain and reset the resampler to be sure we get an accurate number
+           of output samples.*/
+         if (resampler!=NULL)
+         {
+            drain_resampler(fout, file_output, resampler, channels, rate,
+             link_read, link_out, clipmem, dither?&shapemem:NULL, &audio_size,
+             fp);
+            /*Neither speex_resampler_reset_mem() nor
+              speex_resampler_skip_zeros() clear the number of fractional
+              samples properly, so we just destroy it. It will get re-created
+              below.*/
+            speex_resampler_destroy(resampler);
+            resampler=NULL;
+         }
+         /*We've encountered a new link.*/
+         link_read=link_out=0;
+         head=op_head(st, li);
+         if (!force_stereo && channels!=head->channel_count)
+         {
+            /*In theory if the first link was stereo, we could downmix the
+              remaining links, but we've already decoded the first packet, and
+              this stream is unseekable, so we'd have to write our own downmix
+              code. That's more trouble than it's worth.*/
+            fprintf(stderr,
+             "Error: channel count changed in a chained stream: "
+             "aborting.\n");
+            break;
          }
-         /*We're done, drain the resampler if we were using it.*/
-         if(eos && resampler)
+         if (!force_rate
+          && (opus_uint32)rate!=
+          (head->input_sample_rate==0?48000:head->input_sample_rate))
          {
-            float *zeros;
-            int drain;
-
-            zeros=(float *)calloc(100*channels,sizeof(float));
-            if(!zeros)
+            fprintf(stderr,
+             "Warning: input sampling rate changed in a chained stream: "
+             "resampling remaining links to %d. Use --rate to override.\n",
+             rate);
+         }
+         if (!quiet)
+         {
+            if (old_li >= 0)
             {
-                fprintf(stderr, "Memory allocation failure.\n");
-                quit(1);
+               /*Clear the progress indicator from the previous link.*/
+               fprintf(stderr, "\r");
             }
-            drain = speex_resampler_get_input_latency(resampler);
-            do {
-               opus_int64 outsamp;
-               int tmp = drain;
-               if (tmp > 100)
-                  tmp = 100;
-               outsamp=audio_write(zeros, channels, tmp, fout, resampler, NULL, &shapemem, strlen(outFile)!=0, ((page_granule-gran_offset)*rate/48000)-link_out,fp);
-               link_out+=outsamp;
-               audio_size+=(fp?4:2)*outsamp*channels;
-               drain -= tmp;
-            } while (drain>0);
-            free(zeros);
-            speex_resampler_destroy(resampler);
-            resampler=NULL;
+            fprintf(stderr, "Decoding to %d Hz (%d %s)", rate,
+              channels, channels>1?"channels":"channel");
+            if (head->version!=1)
+            {
+               fprintf(stderr, ", Header v%d",head->version);
+            }
+            fprintf(stderr, "\n");
+            if (head->output_gain!=0)
+            {
+               fprintf(stderr,"Playback gain: %f dB\n", head->output_gain/256.);
+            }
+            if (manual_gain!=0)
+            {
+               fprintf(stderr,"Manual gain: %f dB\n", manual_gain);
+            }
+            print_comments(op_tags(st, li));
          }
-         if(eos)
+      }
+      nb_read_total+=nb_read;
+      link_read+=nb_read;
+      if (!quiet)
+      {
+         /*Display a progress spinner while decoding.*/
+         static const char spinner[]="|/-\\";
+         double coded_seconds=nb_read_total/(double)rate;
+         if (coded_seconds>=last_coded_seconds+1 || li!=old_li)
          {
-            has_opus_stream=0;
-            if(st)opus_multistream_decoder_destroy(st);
-            st=NULL;
+            fprintf(stderr,"\r[%c] %02d:%02d:%02d", spinner[last_spin&3],
+             (int)(coded_seconds/3600), (int)(coded_seconds/60)%60,
+             (int)(coded_seconds)%60);
+            fflush(stderr);
          }
+         if (coded_seconds>=last_coded_seconds+1)
+         {
+            last_spin++;
+            last_coded_seconds=coded_seconds;
+         }
       }
-      if (feof(fin)) {
-         if(!quiet) {
-           fprintf(stderr, "\rDecoding complete.        \n");
-           fflush(stderr);
+      old_li=li;
+      if (permuted_output!=NULL)
+      {
+         int ci;
+         for(i=0;i<nb_read;i++)
+         {
+            for(ci=0;ci<channels;ci++)
+            {
+               permuted_output[i*channels+ci]=
+                output[i*channels+channel_map[ci]];
+            }
          }
-         break;
       }
+      /*Normal players should just play at 48000 or their maximum rate,
+        as described in the OggOpus spec.  But for commandline tools
+        like opusdec it can be desirable to exactly preserve the original
+        sampling rate and duration, so we have a resampler here.*/
+      if (rate!=48000 && resampler==NULL)
+      {
+         int err;
+         resampler = speex_resampler_init(channels, 48000, rate, 5, &err);
+         if (err!=0)
+         {
+            fprintf(stderr, "resampler error: %s\n",
+             speex_resampler_strerror(err));
+         }
+         speex_resampler_skip_zeros(resampler);
+      }
+      outsamp=audio_write(permuted_output?permuted_output:output, channels,
+       nb_read, fout, resampler, clipmem, dither?&shapemem:0, file_output,
+       rate, link_read, link_out, fp);
+      link_out+=outsamp;
+      audio_size+=(fp?sizeof(float):sizeof(short))*outsamp*channels;
    }
 
+   if (resampler!=NULL)
+   {
+      drain_resampler(fout, file_output, resampler, channels, rate,
+       link_read, link_out, clipmem, dither?&shapemem:NULL, &audio_size, fp);
+      speex_resampler_destroy(resampler);
+   }
+
    /*If we were writing wav, go set the duration.*/
-   if (strlen(outFile)!=0 && fout && wav_format>0 && audio_size<0x7FFFFFFF)
+   if (file_output && fout && wav_format>0 && audio_size<0x7FFFFFFF)
    {
       if (fseek(fout,4,SEEK_SET)==0)
       {
          int tmp;
-         tmp = le_int(audio_size+20+wav_format);
-         if(fwrite(&tmp,4,1,fout)!=1)fprintf(stderr,"Error writing end length.\n");
+         tmp=le_int(audio_size+20+wav_format);
+         if (fwrite(&tmp,4,1,fout)!=1)
+         {
+            fprintf(stderr,"Error writing end length.\n");
+         }
          if (fseek(fout,16+wav_format,SEEK_CUR)==0)
          {
-            tmp = le_int(audio_size);
-            if(fwrite(&tmp,4,1,fout)!=1)fprintf(stderr,"Error writing header length.\n");
-         } else
-         {
-            fprintf (stderr, "First seek worked, second didn't\n");
+            tmp=le_int(audio_size);
+            if (fwrite(&tmp,4,1,fout)!=1)
+            {
+               fprintf(stderr,"Error writing header length.\n");
+            }
+         } else {
+            fprintf(stderr, "First seek worked, second didn't\n");
          }
       } else {
-         fprintf (stderr, "Cannot seek on wav file output, wav size chunk will be incorrect\n");
+         fprintf(stderr,
+          "Cannot seek on wav file output, wav size chunk will be incorrect\n");
       }
    }
 
-   /*Did we make it to the end without recovering ANY opus logical streams?*/
-   if(!total_links)fprintf (stderr, "This doesn't look like a Opus file\n");
-
-   if (stream_init)
-      ogg_stream_clear(&os);
-   ogg_sync_clear(&oy);
-
 #if defined WIN32 || defined _WIN32
-   if (strlen(outFile)==0)
-      WIN_Audio_close ();
+   if (!file_output)
+      WIN_Audio_close();
 #endif
 
-   if(shapemem.a_buf)free(shapemem.a_buf);
-   if(shapemem.b_buf)free(shapemem.b_buf);
-
-   if(output)free(output);
-
-   if(frange)fclose(frange);
-
-   if (close_in)
-      fclose(fin);
-   if (fout != NULL)
+   free(shapemem.a_buf);
+   free(shapemem.b_buf);
+   free(output);
+   if (permuted_output!=NULL)
+   {
+      free(permuted_output);
+   }
+   if (frange!=NULL)
+   {
+      fclose(frange);
+   }
+   if (fout!=NULL)
+   {
       fclose(fout);
-
+   }
 #ifdef WIN_UNICODE
    free_commandline_arguments_utf8(&argc_utf8, &argv_utf8);
    uninit_console_utf8();