Add an application decoding callback API.
authorTimothy B. Terriberry <tterribe@xiph.org>
Thu, 29 Aug 2013 06:37:31 +0000 (23:37 -0700)
committerTimothy B. Terriberry <tterribe@xiph.org>
Tue, 3 Sep 2013 22:02:53 +0000 (15:02 -0700)
This is needed to allow advanced usage, like that of opusdec in
 opus-tools, which can simulate packet loss or save the range coder
 state for decoder verification.
It could also be used in a pinch to use libopusfile for access to
 the raw Ogg packets, though this is somewhat of a hack.

include/opusfile.h
src/internal.h
src/opusfile.c

index 6c28248..78cfde4 100644 (file)
@@ -1684,6 +1684,66 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
     appropriately.*/
 /*@{*/
 
+/**Indicates that the decoding callback should produce signed 16-bit
+    native-endian output samples.*/
+#define OP_DEC_FORMAT_SHORT (7008)
+/**Indicates that the decoding callback should produce 32-bit native-endian
+    float samples.*/
+#define OP_DEC_FORMAT_FLOAT (7040)
+
+/**Indicates that the decoding callback did not decode anything, and that
+    <tt>libopusfile</tt> should decode normally instead.*/
+#define OP_DEC_USE_DEFAULT  (6720)
+
+/**Called to decode an Opus packet.
+   This should invoke the functional equivalent of opus_multistream_decode() or
+    opus_multistream_decode_float(), except that it returns 0 on success
+    instead of the number of decoded samples (which is known a priori).
+   \param _ctx       The application-provided callback context.
+   \param _decoder   The decoder to use to decode the packet.
+   \param[out] _pcm  The buffer to decode into.
+                     This will always have enough room for \a _nchannels of
+                      \a _nsamples samples, which should be placed into this
+                      buffer interleaved.
+   \param _op        The packet to decode.
+                     This will always have its granule position set to a valid
+                      value.
+   \param _nsamples  The number of samples expected from the packet.
+   \param _nchannels The number of channels expected from the packet.
+   \param _format    The desired sample output format.
+                     This is either #OP_DEC_FORMAT_SHORT or
+                      #OP_DEC_FORMAT_FLOAT.
+   \param _li        The index of the link from which this packet was decoded.
+   \return A non-negative value on success, or a negative value on error.
+           The error codes should be the same as those returned by
+            opus_multistream_decode() or opus_multistream_decode_float().
+   \retval 0                   Decoding was successful.
+                               The application has filled the buffer with
+                                exactly <code>\a _nsamples*\a
+                                _nchannels</code> samples in the requested
+                                format.
+   \retval #OP_DEC_USE_DEFAULT No decoding was done.
+                               <tt>libopusfile</tt> should decode normally
+                                instead.*/
+typedef int (*op_decode_cb_func)(void *_ctx,OpusMSDecoder *_decoder,void *_pcm,
+ const ogg_packet *_op,int _nsamples,int _nchannels,int _format,int _li);
+
+/**Sets the packet decode callback function.
+   This is called once for each packet that needs to be decoded.
+   A call to this function is no guarantee that the audio will eventually be
+    delivered to the application.
+   Some or all of the data from the packet may be discarded (i.e., at the
+    beginning or end of a link, or after a seek), however the callback is
+    required to provide all of it.
+   \param _of        The \c OggOpusFile on which to set the decode callback.
+   \param _decode_cb The callback function to call.
+                     This may be <code>NULL</code> to disable calling the
+                      callback.
+   \param _ctx       The application-provided context pointer to pass to the
+                      callback on each call.*/
+void op_set_decode_callback(OggOpusFile *_of,
+ op_decode_cb_func _decode_cb,void *_ctx) OP_ARG_NONNULL(1);
+
 /**Gain offset type that indicates that the provided offset is relative to the
     header gain.
    This is the default.*/
index ae0c8bd..bed5718 100644 (file)
@@ -203,6 +203,10 @@ struct OggOpusFile{
   int                op_count;
   /*Central working state for the packet-to-PCM decoder.*/
   OpusMSDecoder     *od;
+  /*The application-provided packet decode callback.*/
+  op_decode_cb_func  decode_cb;
+  /*The application-provided packet decode callback context.*/
+  void              *decode_cb_ctx;
   /*The stream count used to initialize the decoder.*/
   int                od_stream_count;
   /*The coupled stream count used to initialize the decoder.*/
index 1558d10..a49aa0d 100644 (file)
@@ -2548,6 +2548,12 @@ ogg_int64_t op_pcm_tell(OggOpusFile *_of){
   return op_get_pcm_offset(_of,gp,li);
 }
 
+void op_set_decode_callback(OggOpusFile *_of,
+ op_decode_cb_func _decode_cb,void *_ctx){
+  _of->decode_cb=_decode_cb;
+  _of->decode_cb_ctx=_ctx;
+}
+
 int op_set_gain_offset(OggOpusFile *_of,
  int _gain_type,opus_int32 _gain_offset_q8){
   if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_TRACK_GAIN
@@ -2586,6 +2592,39 @@ static int op_init_buffer(OggOpusFile *_of){
   return 0;
 }
 
+/*Decode a single packet into the target buffer.*/
+static int op_decode(OggOpusFile *_of,op_sample *_pcm,
+ const ogg_packet *_op,int _nsamples,int _nchannels){
+  int ret;
+  /*First we try using the application-provided decode callback.*/
+  if(_of->decode_cb!=NULL){
+#if defined(OP_FIXED_POINT)
+    ret=(*_of->decode_cb)(_of->decode_cb_ctx,_of->od,_pcm,_op,
+     _nsamples,_nchannels,OP_DEC_FORMAT_SHORT,_of->cur_link);
+#else
+    ret=(*_of->decode_cb)(_of->decode_cb_ctx,_of->od,_pcm,_op,
+     _nsamples,_nchannels,OP_DEC_FORMAT_FLOAT,_of->cur_link);
+#endif
+  }
+  else ret=OP_DEC_USE_DEFAULT;
+  /*If the application didn't want to handle decoding, do it ourselves.*/
+  if(ret==OP_DEC_USE_DEFAULT){
+#if defined(OP_FIXED_POINT)
+    ret=opus_multistream_decode(_of->od,
+     _op->packet,_op->bytes,_pcm,_nsamples,0);
+#else
+    ret=opus_multistream_decode_float(_of->od,
+     _op->packet,_op->bytes,_pcm,_nsamples,0);
+#endif
+    OP_ASSERT(ret<0||ret==_nsamples);
+  }
+  /*If the application returned a positive value other than 0 or
+     OP_DEC_USE_DEFAULT, fail.*/
+  else if(OP_UNLIKELY(ret>0))return OP_EBADPACKET;
+  if(OP_UNLIKELY(ret<0))return OP_EBADPACKET;
+  return ret;
+}
+
 /*Read more samples from the stream, using the same API as op_read() or
    op_read_float().*/
 static int op_read_native(OggOpusFile *_of,
@@ -2649,15 +2688,8 @@ static int op_read_native(OggOpusFile *_of,
             if(OP_UNLIKELY(ret<0))return ret;
             buf=_of->od_buffer;
           }
-#if defined(OP_FIXED_POINT)
-          ret=opus_multistream_decode(_of->od,
-           pop->packet,pop->bytes,buf,120*48,0);
-#else
-          ret=opus_multistream_decode_float(_of->od,
-           pop->packet,pop->bytes,buf,120*48,0);
-#endif
-          if(OP_UNLIKELY(ret<0))return OP_EBADPACKET;
-          OP_ASSERT(ret==duration);
+          ret=op_decode(_of,buf,pop,duration,nchannels);
+          if(OP_UNLIKELY(ret<0))return ret;
           /*Perform pre-skip/pre-roll.*/
           od_buffer_pos=(int)OP_MIN(trimmed_duration,cur_discard_count);
           cur_discard_count-=od_buffer_pos;
@@ -2673,15 +2705,8 @@ static int op_read_native(OggOpusFile *_of,
         }
         else{
           /*Otherwise decode directly into the user's buffer.*/
-#if defined(OP_FIXED_POINT)
-          ret=opus_multistream_decode(_of->od,pop->packet,pop->bytes,
-           _pcm,_buf_size/nchannels,0);
-#else
-          ret=opus_multistream_decode_float(_of->od,pop->packet,pop->bytes,
-           _pcm,_buf_size/nchannels,0);
-#endif
-          if(OP_UNLIKELY(ret<0))return OP_EBADPACKET;
-          OP_ASSERT(ret==duration);
+          ret=op_decode(_of,_pcm,pop,duration,nchannels);
+          if(OP_UNLIKELY(ret<0))return ret;
           if(OP_LIKELY(trimmed_duration>0)){
             /*Perform pre-skip/pre-roll.*/
             od_buffer_pos=(int)OP_MIN(trimmed_duration,cur_discard_count);