[ioquake3] 39/136: Update opusfile from 0.5 to 0.8

Simon McVittie smcv at debian.org
Thu Jun 15 09:09:04 UTC 2017


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch debian/master
in repository ioquake3.

commit 7139094355ee951c0d2daf9524ae15809750d6aa
Author: Zack Middleton <zack at cloemail.com>
Date:   Tue May 23 11:03:07 2017 -0500

    Update opusfile from 0.5 to 0.8
---
 code/opusfile-0.5/include/opusfile.h | 102 ++++++++++--
 code/opusfile-0.5/src/http.c         | 116 +++++++++++---
 code/opusfile-0.5/src/info.c         | 117 +++++++++++---
 code/opusfile-0.5/src/internal.h     |   5 +
 code/opusfile-0.5/src/opusfile.c     | 293 +++++++++++++++++++++++------------
 5 files changed, 479 insertions(+), 154 deletions(-)

diff --git a/code/opusfile-0.5/include/opusfile.h b/code/opusfile-0.5/include/opusfile.h
index 850cd6b..4bf2fba 100644
--- a/code/opusfile-0.5/include/opusfile.h
+++ b/code/opusfile-0.5/include/opusfile.h
@@ -503,6 +503,22 @@ int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value)
 int opus_tags_add_comment(OpusTags *_tags,const char *_comment)
  OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
 
+/**Replace the binary suffix data at the end of the packet (if any).
+   \param _tags An initialized #OpusTags structure.
+   \param _data A buffer of binary data to append after the encoded user
+                 comments.
+                The least significant bit of the first byte of this data must
+                 be set (to ensure the data is preserved by other editors).
+   \param _len  The number of bytes of binary data to append.
+                This may be zero to remove any existing binary suffix data.
+   \return 0 on success, or a negative value on error.
+   \retval #OP_EINVAL \a _len was negative, or \a _len was positive but
+                       \a _data was <code>NULL</code> or the least significant
+                       bit of the first byte was not set.
+   \retval #OP_EFAULT An internal memory allocation failed.*/
+int opus_tags_set_binary_suffix(OpusTags *_tags,
+ const unsigned char *_data,int _len) OP_ARG_NONNULL(1);
+
 /**Look up a comment value by its tag.
    \param _tags  An initialized #OpusTags structure.
    \param _tag   The tag to look up.
@@ -531,6 +547,32 @@ const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count)
 int opus_tags_query_count(const OpusTags *_tags,const char *_tag)
  OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
 
+/**Retrieve the binary suffix data at the end of the packet (if any).
+   \param      _tags An initialized #OpusTags structure.
+   \param[out] _len  Returns the number of bytes of binary suffix data returned.
+   \return A pointer to the binary suffix data, or <code>NULL</code> if none
+            was present.*/
+const unsigned char *opus_tags_get_binary_suffix(const OpusTags *_tags,
+ int *_len) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
+
+/**Get the album gain from an R128_ALBUM_GAIN tag, if one was specified.
+   This searches for the first R128_ALBUM_GAIN tag with a valid signed,
+    16-bit decimal integer value and returns the value.
+   This routine is exposed merely for convenience for applications which wish
+    to do something special with the album gain (i.e., display it).
+   If you simply wish to apply the album gain instead of the header gain, you
+    can use op_set_gain_offset() with an #OP_ALBUM_GAIN type and no offset.
+   \param      _tags    An initialized #OpusTags structure.
+   \param[out] _gain_q8 The album gain, in 1/256ths of a dB.
+                        This will lie in the range [-32768,32767], and should
+                         be applied in <em>addition</em> to the header gain.
+                        On error, no value is returned, and the previous
+                         contents remain unchanged.
+   \return 0 on success, or a negative value on error.
+   \retval #OP_FALSE There was no album gain available in the given tags.*/
+int opus_tags_get_album_gain(const OpusTags *_tags,int *_gain_q8)
+ OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
+
 /**Get the track gain from an R128_TRACK_GAIN tag, if one was specified.
    This searches for the first R128_TRACK_GAIN tag with a valid signed,
     16-bit decimal integer value and returns the value.
@@ -1453,6 +1495,10 @@ int op_channel_count(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
 /**Get the total (compressed) size of the stream, or of an individual link in
     a (possibly-chained) Ogg Opus stream, including all headers and Ogg muxing
     overhead.
+   \warning If the Opus stream (or link) is concurrently multiplexed with other
+    logical streams (e.g., video), this returns the size of the entire stream
+    (or link), not just the number of bytes in the first logical Opus stream.
+   Returning the latter would require scanning the entire file.
    \param _of The \c OggOpusFile from which to retrieve the compressed size.
    \param _li The index of the link whose compressed size should be computed.
               Use a negative number to get the compressed size of the entire
@@ -1537,13 +1583,22 @@ const OpusTags *op_tags(const OggOpusFile *_of,int _li) OP_ARG_NONNULL(1);
    \retval #OP_EINVAL The stream was only partially open.*/
 int op_current_link(const OggOpusFile *_of) OP_ARG_NONNULL(1);
 
-/**Computes the bitrate for a given link in a (possibly chained) Ogg Opus
-    stream.
+/**Computes the bitrate of the stream, or of an individual link in a
+    (possibly-chained) Ogg Opus stream.
    The stream must be seekable to compute the bitrate.
    For unseekable streams, use op_bitrate_instant() to get periodic estimates.
+   \warning If the Opus stream (or link) is concurrently multiplexed with other
+    logical streams (e.g., video), this uses the size of the entire stream (or
+    link) to compute the bitrate, not just the number of bytes in the first
+    logical Opus stream.
+   Returning the latter requires scanning the entire file, but this may be done
+    by decoding the whole file and calling op_bitrate_instant() once at the
+    end.
+   Install a trivial decoding callback with op_set_decode_callback() if you
+    wish to skip actual decoding during this process.
    \param _of The \c OggOpusFile from which to retrieve the bitrate.
    \param _li The index of the link whose bitrate should be computed.
-              USe a negative number to get the bitrate of the whole stream.
+              Use a negative number to get the bitrate of the whole stream.
    \return The bitrate on success, or a negative value on error.
    \retval #OP_EINVAL The stream was only partially open, the stream was not
                        seekable, or \a _li was larger than the number of
@@ -1658,11 +1713,11 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
    These downmix multichannel files to two channels, so they can always return
     samples in the same format for every link in a chained file.
 
-   If the rest of your audio processing chain can handle floating point, those
-    routines should be preferred, as floating point output avoids introducing
-    clipping and other issues which might be avoided entirely if, e.g., you
-    scale down the volume at some other stage.
-   However, if you intend to direct consume 16-bit samples, the conversion in
+   If the rest of your audio processing chain can handle floating point, the
+    floating-point routines should be preferred, as they prevent clipping and
+    other issues which might be avoided entirely if, e.g., you scale down the
+    volume at some other stage.
+   However, if you intend to consume 16-bit samples directly, the conversion in
     <tt>libopusfile</tt> provides noise-shaping dithering and, if compiled
     against <tt>libopus</tt> 1.1 or later, soft-clipping prevention.
 
@@ -1715,26 +1770,35 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
                       #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
+           Any error codes should be the same as those returned by
             opus_multistream_decode() or opus_multistream_decode_float().
+           Success codes are as follows:
    \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.*/
+                               <tt>libopusfile</tt> should do the decoding
+                                by itself 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.
+   If set, this is called once for each packet that needs to be decoded.
+   This can be used by advanced applications to do additional processing on the
+    compressed or uncompressed data.
+   For example, an application might save the final entropy coder state for
+    debugging and testing purposes, or it might apply additional filters
+    before the downmixing, dithering, or soft-clipping performed by
+    <tt>libopusfile</tt>, so long as these filters do not introduce any
+    latency.
+
    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.
+   <tt>libopusfile</tt> may discard some or all of the decoded audio data
+    (i.e., at the beginning or end of a link, or after a seek), however the
+    callback is still 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
@@ -1750,6 +1814,10 @@ void op_set_decode_callback(OggOpusFile *_of,
 #define OP_HEADER_GAIN   (0)
 
 /**Gain offset type that indicates that the provided offset is relative to the
+    R128_ALBUM_GAIN value (if any), in addition to the header gain.*/
+#define OP_ALBUM_GAIN    (3007)
+
+/**Gain offset type that indicates that the provided offset is relative to the
     R128_TRACK_GAIN value (if any), in addition to the header gain.*/
 #define OP_TRACK_GAIN    (3008)
 
@@ -1769,8 +1837,8 @@ void op_set_decode_callback(OggOpusFile *_of,
    It is meant for setting a target volume level, rather than applying smooth
     fades, etc.
    \param _of             The \c OggOpusFile on which to set the gain offset.
-   \param _gain_type      One of #OP_HEADER_GAIN, #OP_TRACK_GAIN, or
-                           #OP_ABSOLUTE_GAIN.
+   \param _gain_type      One of #OP_HEADER_GAIN, #OP_ALBUM_GAIN,
+                           #OP_TRACK_GAIN, or #OP_ABSOLUTE_GAIN.
    \param _gain_offset_q8 The gain offset to apply, in 1/256ths of a dB.
    \return 0 on success or a negative value on error.
    \retval #OP_EINVAL The \a _gain_type was unrecognized.*/
diff --git a/code/opusfile-0.5/src/http.c b/code/opusfile-0.5/src/http.c
index 4a9eaf5..22d75d4 100644
--- a/code/opusfile-0.5/src/http.c
+++ b/code/opusfile-0.5/src/http.c
@@ -231,7 +231,7 @@ typedef SOCKET op_sock;
 #   define POLLRDBAND (0x0200)
 /*There is data to read.*/
 #   define POLLIN     (POLLRDNORM|POLLRDBAND)
-/* There is urgent data to read.*/
+/*There is urgent data to read.*/
 #   define POLLPRI    (0x0400)
 /*Equivalent to POLLOUT.*/
 #   define POLLWRNORM (0x0010)
@@ -721,7 +721,7 @@ static struct addrinfo *op_resolve(const char *_host,unsigned _port){
   char             service[6];
   memset(&hints,0,sizeof(hints));
   hints.ai_socktype=SOCK_STREAM;
-#if !defined(_WIN32)
+#if defined(AI_NUMERICSERV)
   hints.ai_flags=AI_NUMERICSERV;
 #endif
   OP_ASSERT(_port<=65535U);
@@ -894,7 +894,7 @@ static void op_http_stream_init(OpusHTTPStream *_stream){
 /*Close the connection and move it to the free list.
   _stream:     The stream containing the free list.
   _conn:       The connection to close.
-  _penxt:      The linked-list pointer currently pointing to this connection.
+  _pnext:      The linked-list pointer currently pointing to this connection.
   _gracefully: Whether or not to shut down cleanly.*/
 static void op_http_conn_close(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
  OpusHTTPConn **_pnext,int _gracefully){
@@ -1517,10 +1517,17 @@ static long op_bio_retry_ctrl(BIO *_b,int _cmd,long _num,void *_ptr){
   return ret;
 }
 
+# if OPENSSL_VERSION_NUMBER<0x10100000L
+#  define BIO_set_data(_b,_ptr) ((_b)->ptr=(_ptr))
+#  define BIO_set_init(_b,_init) ((_b)->init=(_init))
+# endif
+
 static int op_bio_retry_new(BIO *_b){
-  _b->init=1;
+  BIO_set_init(_b,1);
+# if OPENSSL_VERSION_NUMBER<0x10100000L
   _b->num=0;
-  _b->ptr=NULL;
+# endif
+  BIO_set_data(_b,NULL);
   return 1;
 }
 
@@ -1528,6 +1535,7 @@ static int op_bio_retry_free(BIO *_b){
   return _b!=NULL;
 }
 
+# if OPENSSL_VERSION_NUMBER<0x10100000L
 /*This is not const because OpenSSL doesn't allow it, even though it won't
    write to it.*/
 static BIO_METHOD op_bio_retry_method={
@@ -1542,11 +1550,15 @@ static BIO_METHOD op_bio_retry_method={
   op_bio_retry_free,
   NULL
 };
+# endif
 
 /*Establish a CONNECT tunnel and pipeline the start of the TLS handshake for
    proxying https URL requests.*/
 static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream,
  OpusHTTPConn *_conn,op_sock _fd,SSL *_ssl_conn,BIO *_ssl_bio){
+# if OPENSSL_VERSION_NUMBER>=0x10100000L
+  BIO_METHOD *bio_retry_method;
+# endif
   BIO  *retry_bio;
   char *status_code;
   char *next;
@@ -1557,13 +1569,32 @@ static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream,
   ret=op_http_conn_write_fully(_conn,
    _stream->proxy_connect.buf,_stream->proxy_connect.nbuf);
   if(OP_UNLIKELY(ret<0))return ret;
+# if OPENSSL_VERSION_NUMBER>=0x10100000L
+  bio_retry_method=BIO_meth_new(BIO_TYPE_NULL,"retry");
+  if(bio_retry_method==NULL)return OP_EFAULT;
+  BIO_meth_set_write(bio_retry_method,op_bio_retry_write);
+  BIO_meth_set_read(bio_retry_method,op_bio_retry_read);
+  BIO_meth_set_puts(bio_retry_method,op_bio_retry_puts);
+  BIO_meth_set_ctrl(bio_retry_method,op_bio_retry_ctrl);
+  BIO_meth_set_create(bio_retry_method,op_bio_retry_new);
+  BIO_meth_set_destroy(bio_retry_method,op_bio_retry_free);
+  retry_bio=BIO_new(bio_retry_method);
+  if(OP_UNLIKELY(retry_bio==NULL)){
+    BIO_meth_free(bio_retry_method);
+    return OP_EFAULT;
+  }
+# else
   retry_bio=BIO_new(&op_bio_retry_method);
   if(OP_UNLIKELY(retry_bio==NULL))return OP_EFAULT;
+# endif
   SSL_set_bio(_ssl_conn,retry_bio,_ssl_bio);
   SSL_set_connect_state(_ssl_conn);
   /*This shouldn't succeed, since we can't read yet.*/
   OP_ALWAYS_TRUE(SSL_connect(_ssl_conn)<0);
   SSL_set_bio(_ssl_conn,_ssl_bio,_ssl_bio);
+# if OPENSSL_VERSION_NUMBER>=0x10100000L
+  BIO_meth_free(bio_retry_method);
+# endif
   /*Only now do we disable write coalescing, to allow the CONNECT
      request and the start of the TLS handshake to be combined.*/
   op_sock_set_tcp_nodelay(_fd,1);
@@ -2200,7 +2231,8 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
     /*Initialize the SSL library if necessary.*/
     if(OP_URL_IS_SSL(&_stream->url)&&_stream->ssl_ctx==NULL){
       SSL_CTX *ssl_ctx;
-# if !defined(OPENSSL_NO_LOCKING)
+# if OPENSSL_VERSION_NUMBER<0x10100000L
+#  if !defined(OPENSSL_NO_LOCKING)
       /*The documentation says SSL_library_init() is not reentrant.
         We don't want to add our own depenencies on a threading library, and it
          appears that it's safe to call OpenSSL's locking functions before the
@@ -2210,12 +2242,16 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
          calling SSL_library_init() at the same time, but there's not much we
          can do about that.*/
       CRYPTO_w_lock(CRYPTO_LOCK_SSL);
-# endif
+#  endif
       SSL_library_init();
       /*Needed to get SHA2 algorithms with old OpenSSL versions.*/
       OpenSSL_add_ssl_algorithms();
-# if !defined(OPENSSL_NO_LOCKING)
+#  if !defined(OPENSSL_NO_LOCKING)
       CRYPTO_w_unlock(CRYPTO_LOCK_SSL);
+#  endif
+# else
+      /*Finally, OpenSSL does this for us, but as penance, it can now fail.*/
+      if(!OPENSSL_init_ssl(0,NULL))return OP_EFAULT;
 # endif
       ssl_ctx=SSL_CTX_new(SSLv23_client_method());
       if(ssl_ctx==NULL)return OP_EFAULT;
@@ -2779,7 +2815,7 @@ static int op_http_conn_read_body(OpusHTTPStream *_stream,
     Otherwise, we'd need a _pnext pointer if we needed to close the connection,
      and re-opening it would re-organize the lists.*/
   OP_ASSERT(_stream->lru_head==_conn);
-  /*We should have filterd out empty reads by this point.*/
+  /*We should have filtered out empty reads by this point.*/
   OP_ASSERT(_buf_size>0);
   pos=_conn->pos;
   end_pos=_conn->end_pos;
@@ -3273,8 +3309,22 @@ static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
 #endif
 }
 
-void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
- const char *_url,va_list _ap){
+/*The actual implementation of op_url_stream_vcreate().
+  We have to do a careful dance here to avoid potential memory leaks if
+   OpusServerInfo is requested, since this function is also used by
+   op_vopen_url() and op_vtest_url().
+  Even if this function succeeds, those functions might ultimately fail.
+  If they do, they should return without having touched the OpusServerInfo
+   passed by the application.
+  Therefore, if this function succeeds and OpusServerInfo is requested, the
+   actual info will be stored in *_info and a pointer to the application's
+   storage will be placed in *_pinfo.
+  If this function fails or if the application did not request OpusServerInfo,
+   *_pinfo will be NULL.
+  Our caller is responsible for copying *_info to **_pinfo if it ultimately
+   succeeds, or for clearing *_info if it ultimately fails.*/
+void *op_url_stream_vcreate_impl(OpusFileCallbacks *_cb,
+ const char *_url,OpusServerInfo *_info,OpusServerInfo **_pinfo,va_list _ap){
   int             skip_certificate_check;
   const char     *proxy_host;
   opus_int32      proxy_port;
@@ -3318,20 +3368,30 @@ void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
   }
   /*If the caller has requested server information, proxy it to a local copy to
      simplify error handling.*/
+  *_pinfo=NULL;
   if(pinfo!=NULL){
-    OpusServerInfo  info;
-    void           *ret;
-    opus_server_info_init(&info);
+    void *ret;
+    opus_server_info_init(_info);
     ret=op_url_stream_create_impl(_cb,_url,skip_certificate_check,
-     proxy_host,proxy_port,proxy_user,proxy_pass,&info);
-    if(ret!=NULL)*pinfo=*&info;
-    else opus_server_info_clear(&info);
+     proxy_host,proxy_port,proxy_user,proxy_pass,_info);
+    if(ret!=NULL)*_pinfo=pinfo;
+    else opus_server_info_clear(_info);
     return ret;
   }
   return op_url_stream_create_impl(_cb,_url,skip_certificate_check,
    proxy_host,proxy_port,proxy_user,proxy_pass,NULL);
 }
 
+void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
+ const char *_url,va_list _ap){
+  OpusServerInfo   info;
+  OpusServerInfo *pinfo=NULL;
+  void *ret;
+  ret=op_url_stream_vcreate_impl(_cb,_url,&info,&pinfo,_ap);
+  if(pinfo!=NULL)*pinfo=*&info;
+  return ret;
+}
+
 void *op_url_stream_create(OpusFileCallbacks *_cb,
  const char *_url,...){
   va_list  ap;
@@ -3347,14 +3407,21 @@ void *op_url_stream_create(OpusFileCallbacks *_cb,
 OggOpusFile *op_vopen_url(const char *_url,int *_error,va_list _ap){
   OpusFileCallbacks  cb;
   OggOpusFile       *of;
+  OpusServerInfo     info;
+  OpusServerInfo    *pinfo;
   void              *source;
-  source=op_url_stream_vcreate(&cb,_url,_ap);
+  source=op_url_stream_vcreate_impl(&cb,_url,&info,&pinfo,_ap);
   if(OP_UNLIKELY(source==NULL)){
+    OP_ASSERT(pinfo==NULL);
     if(_error!=NULL)*_error=OP_EFAULT;
     return NULL;
   }
   of=op_open_callbacks(source,&cb,NULL,0,_error);
-  if(OP_UNLIKELY(of==NULL))(*cb.close)(source);
+  if(OP_UNLIKELY(of==NULL)){
+    if(pinfo!=NULL)opus_server_info_clear(&info);
+    (*cb.close)(source);
+  }
+  else if(pinfo!=NULL)*pinfo=*&info;
   return of;
 }
 
@@ -3370,14 +3437,21 @@ OggOpusFile *op_open_url(const char *_url,int *_error,...){
 OggOpusFile *op_vtest_url(const char *_url,int *_error,va_list _ap){
   OpusFileCallbacks  cb;
   OggOpusFile       *of;
+  OpusServerInfo     info;
+  OpusServerInfo    *pinfo;
   void              *source;
-  source=op_url_stream_vcreate(&cb,_url,_ap);
+  source=op_url_stream_vcreate_impl(&cb,_url,&info,&pinfo,_ap);
   if(OP_UNLIKELY(source==NULL)){
+    OP_ASSERT(pinfo==NULL);
     if(_error!=NULL)*_error=OP_EFAULT;
     return NULL;
   }
   of=op_test_callbacks(source,&cb,NULL,0,_error);
-  if(OP_UNLIKELY(of==NULL))(*cb.close)(source);
+  if(OP_UNLIKELY(of==NULL)){
+    if(pinfo!=NULL)opus_server_info_clear(&info);
+    (*cb.close)(source);
+  }
+  else if(pinfo!=NULL)*pinfo=*&info;
   return of;
 }
 
diff --git a/code/opusfile-0.5/src/info.c b/code/opusfile-0.5/src/info.c
index 6cf9851..c36f9a9 100644
--- a/code/opusfile-0.5/src/info.c
+++ b/code/opusfile-0.5/src/info.c
@@ -28,11 +28,13 @@ static int op_parse_int16le(const unsigned char *_data){
 }
 
 static opus_uint32 op_parse_uint32le(const unsigned char *_data){
-  return _data[0]|_data[1]<<8|_data[2]<<16|_data[3]<<24;
+  return _data[0]|(opus_uint32)_data[1]<<8|
+   (opus_uint32)_data[2]<<16|(opus_uint32)_data[3]<<24;
 }
 
 static opus_uint32 op_parse_uint32be(const unsigned char *_data){
-  return _data[3]|_data[2]<<8|_data[1]<<16|_data[0]<<24;
+  return _data[3]|(opus_uint32)_data[2]<<8|
+   (opus_uint32)_data[1]<<16|(opus_uint32)_data[0]<<24;
 }
 
 int opus_head_parse(OpusHead *_head,const unsigned char *_data,size_t _len){
@@ -90,8 +92,11 @@ void opus_tags_init(OpusTags *_tags){
 }
 
 void opus_tags_clear(OpusTags *_tags){
+  int ncomments;
   int ci;
-  for(ci=_tags->comments;ci-->0;)_ogg_free(_tags->user_comments[ci]);
+  ncomments=_tags->comments;
+  if(_tags->user_comments!=NULL)ncomments++;
+  for(ci=ncomments;ci-->0;)_ogg_free(_tags->user_comments[ci]);
   _ogg_free(_tags->user_comments);
   _ogg_free(_tags->comment_lengths);
   _ogg_free(_tags->vendor);
@@ -101,19 +106,27 @@ void opus_tags_clear(OpusTags *_tags){
 static int op_tags_ensure_capacity(OpusTags *_tags,size_t _ncomments){
   char   **user_comments;
   int     *comment_lengths;
+  int      cur_ncomments;
+  char    *binary_suffix_data;
+  int      binary_suffix_len;
   size_t   size;
   if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT;
   size=sizeof(*_tags->comment_lengths)*(_ncomments+1);
   if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT;
+  cur_ncomments=_tags->comments;
+  comment_lengths=_tags->comment_lengths;
+  binary_suffix_len=comment_lengths==NULL?0:comment_lengths[cur_ncomments];
   comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size);
   if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT;
-  comment_lengths[_ncomments]=0;
+  comment_lengths[_ncomments]=binary_suffix_len;
   _tags->comment_lengths=comment_lengths;
   size=sizeof(*_tags->user_comments)*(_ncomments+1);
   if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT;
+  user_comments=_tags->user_comments;
+  binary_suffix_data=user_comments==NULL?NULL:user_comments[cur_ncomments];
   user_comments=(char **)_ogg_realloc(_tags->user_comments,size);
   if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT;
-  user_comments[_ncomments]=NULL;
+  user_comments[_ncomments]=binary_suffix_data;
   _tags->user_comments=user_comments;
   return 0;
 }
@@ -186,10 +199,22 @@ static int opus_tags_parse_impl(OpusTags *_tags,
       if(_tags->user_comments[ci]==NULL)return OP_EFAULT;
       _tags->comment_lengths[ci]=(int)count;
       _tags->comments=ci+1;
+      /*Needed by opus_tags_clear() if we fail before parsing the (optional)
+         binary metadata.*/
+      _tags->user_comments[ci+1]=NULL;
     }
     _data+=count;
     len-=count;
   }
+  if(len>0&&(_data[0]&1)){
+    if(len>(opus_uint32)INT_MAX)return OP_EFAULT;
+    if(_tags!=NULL){
+      _tags->user_comments[ncomments]=(char *)_ogg_malloc(len);
+      if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT;
+      memcpy(_tags->user_comments[ncomments],_data,len);
+      _tags->comment_lengths[ncomments]=(int)len;
+    }
+  }
   return 0;
 }
 
@@ -230,6 +255,16 @@ static int opus_tags_copy_impl(OpusTags *_dst,const OpusTags *_src){
     _dst->comment_lengths[ci]=len;
     _dst->comments=ci+1;
   }
+  if(_src->comment_lengths!=NULL){
+    int len;
+    len=_src->comment_lengths[ncomments];
+    if(len>0){
+      _dst->user_comments[ncomments]=(char *)_ogg_malloc(len);
+      if(OP_UNLIKELY(_dst->user_comments[ncomments]==NULL))return OP_EFAULT;
+      memcpy(_dst->user_comments[ncomments],_src->user_comments[ncomments],len);
+      _dst->comment_lengths[ncomments]=len;
+    }
+  }
   return 0;
 }
 
@@ -255,29 +290,49 @@ int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value){
   tag_len=strlen(_tag);
   value_len=strlen(_value);
   /*+2 for '=' and '\0'.*/
-  _tags->comment_lengths[ncomments]=0;
-  _tags->user_comments[ncomments]=comment=
-   (char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
+  comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
   if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
-  _tags->comment_lengths[ncomments]=tag_len+value_len+1;
   memcpy(comment,_tag,sizeof(*comment)*tag_len);
   comment[tag_len]='=';
   memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1));
+  _tags->user_comments[ncomments]=comment;
+  _tags->comment_lengths[ncomments]=tag_len+value_len+1;
+  _tags->comments=ncomments+1;
   return 0;
 }
 
 int opus_tags_add_comment(OpusTags *_tags,const char *_comment){
-  int comment_len;
-  int ncomments;
-  int ret;
+  char *comment;
+  int   comment_len;
+  int   ncomments;
+  int   ret;
   ncomments=_tags->comments;
   ret=op_tags_ensure_capacity(_tags,ncomments+1);
   if(OP_UNLIKELY(ret<0))return ret;
   comment_len=(int)strlen(_comment);
-  _tags->comment_lengths[ncomments]=0;
-  _tags->user_comments[ncomments]=op_strdup_with_len(_comment,comment_len);
-  if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT;
+  comment=op_strdup_with_len(_comment,comment_len);
+  if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
+  _tags->user_comments[ncomments]=comment;
   _tags->comment_lengths[ncomments]=comment_len;
+  _tags->comments=ncomments+1;
+  return 0;
+}
+
+int opus_tags_set_binary_suffix(OpusTags *_tags,
+ const unsigned char *_data,int _len){
+  unsigned char *binary_suffix_data;
+  int            ncomments;
+  int            ret;
+  if(_len<0||_len>0&&(_data==NULL||!(_data[0]&1)))return OP_EINVAL;
+  ncomments=_tags->comments;
+  ret=op_tags_ensure_capacity(_tags,ncomments);
+  if(OP_UNLIKELY(ret<0))return ret;
+  binary_suffix_data=
+   (unsigned char *)_ogg_realloc(_tags->user_comments[ncomments],_len);
+  if(OP_UNLIKELY(binary_suffix_data==NULL))return OP_EFAULT;
+  memcpy(binary_suffix_data,_data,_len);
+  _tags->user_comments[ncomments]=(char *)binary_suffix_data;
+  _tags->comment_lengths[ncomments]=_len;
   return 0;
 }
 
@@ -328,19 +383,31 @@ int opus_tags_query_count(const OpusTags *_tags,const char *_tag){
   return found;
 }
 
-int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
+const unsigned char *opus_tags_get_binary_suffix(const OpusTags *_tags,
+ int *_len){
+  int ncomments;
+  int len;
+  ncomments=_tags->comments;
+  len=_tags->comment_lengths==NULL?0:_tags->comment_lengths[ncomments];
+  *_len=len;
+  OP_ASSERT(len==0||_tags->user_comments!=NULL);
+  return len>0?(const unsigned char *)_tags->user_comments[ncomments]:NULL;
+}
+
+static int opus_tags_get_gain(const OpusTags *_tags,int *_gain_q8,
+ const char *_tag_name,size_t _tag_len){
   char **comments;
   int    ncomments;
   int    ci;
   comments=_tags->user_comments;
   ncomments=_tags->comments;
-  /*Look for the first valid R128_TRACK_GAIN tag and use that.*/
+  /*Look for the first valid tag with the name _tag_name and use that.*/
   for(ci=0;ci<ncomments;ci++){
-    if(opus_tagncompare("R128_TRACK_GAIN",15,comments[ci])==0){
+    if(opus_tagncompare(_tag_name,_tag_len,comments[ci])==0){
       char       *p;
       opus_int32  gain_q8;
       int         negative;
-      p=comments[ci]+16;
+      p=comments[ci]+_tag_len+1;
       negative=0;
       if(*p=='-'){
         negative=-1;
@@ -354,7 +421,7 @@ int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
         p++;
       }
       /*This didn't look like a signed 16-bit decimal integer.
-        Not a valid R128_TRACK_GAIN tag.*/
+        Not a valid gain tag.*/
       if(*p!='\0')continue;
       *_gain_q8=(int)(gain_q8+negative^negative);
       return 0;
@@ -363,6 +430,14 @@ int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
   return OP_FALSE;
 }
 
+int opus_tags_get_album_gain(const OpusTags *_tags,int *_gain_q8){
+  return opus_tags_get_gain(_tags,_gain_q8,"R128_ALBUM_GAIN",15);
+}
+
+int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
+  return opus_tags_get_gain(_tags,_gain_q8,"R128_TRACK_GAIN",15);
+}
+
 static int op_is_jpeg(const unsigned char *_buf,size_t _buf_sz){
   return _buf_sz>=11&&memcmp(_buf,"\xFF\xD8\xFF\xE0",4)==0
    &&(_buf[4]<<8|_buf[5])>=16&&memcmp(_buf+6,"JFIF",5)==0;
@@ -560,7 +635,7 @@ static int opus_picture_tag_parse_impl(OpusPictureTag *_pic,const char *_tag,
   i+=4;
   /*If one of these is set, they all must be, but colors==0 is a valid value.*/
   colors_set=width!=0||height!=0||depth!=0||colors!=0;
-  if(width==0||height==0||depth==0&&colors_set)return OP_ENOTFORMAT;
+  if((width==0||height==0||depth==0)&&colors_set)return OP_ENOTFORMAT;
   data_length=op_parse_uint32be(_buf+i);
   i+=4;
   if(data_length>_buf_sz-i)return OP_ENOTFORMAT;
diff --git a/code/opusfile-0.5/src/internal.h b/code/opusfile-0.5/src/internal.h
index 0811491..ee48ea3 100644
--- a/code/opusfile-0.5/src/internal.h
+++ b/code/opusfile-0.5/src/internal.h
@@ -186,6 +186,11 @@ struct OggOpusFile{
   opus_int32         cur_discard_count;
   /*The granule position of the previous packet (current packet start time).*/
   ogg_int64_t        prev_packet_gp;
+  /*The stream offset of the most recent page with completed packets, or -1.
+    This is only needed to recover continued packet data in the seeking logic,
+     when we use the current position as one of our bounds, only to later
+     discover it was the correct starting point.*/
+  opus_int64         prev_page_offset;
   /*The number of bytes read since the last bitrate query, including framing.*/
   opus_int64         bytes_tracked;
   /*The number of samples decoded since the last bitrate query.*/
diff --git a/code/opusfile-0.5/src/opusfile.c b/code/opusfile-0.5/src/opusfile.c
index 73b95de..7c3691b 100644
--- a/code/opusfile-0.5/src/opusfile.c
+++ b/code/opusfile-0.5/src/opusfile.c
@@ -156,8 +156,8 @@ static int op_get_data(OggOpusFile *_of,int _nbytes){
 /*Save a tiny smidge of verbosity to make the code more readable.*/
 static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){
   if(_offset==_of->offset)return 0;
-  if(_of->callbacks.seek==NULL||
-   (*_of->callbacks.seek)(_of->source,_offset,SEEK_SET)){
+  if(_of->callbacks.seek==NULL
+   ||(*_of->callbacks.seek)(_of->source,_offset,SEEK_SET)){
     return OP_EREAD;
   }
   _of->offset=_offset;
@@ -237,7 +237,9 @@ static int op_add_serialno(const ogg_page *_og,
   nserialnos=*_nserialnos;
   cserialnos=*_cserialnos;
   if(OP_UNLIKELY(nserialnos>=cserialnos)){
-    if(OP_UNLIKELY(cserialnos>INT_MAX-1>>1))return OP_EFAULT;
+    if(OP_UNLIKELY(cserialnos>INT_MAX/(int)sizeof(*serialnos)-1>>1)){
+      return OP_EFAULT;
+    }
     cserialnos=2*cserialnos+1;
     OP_ASSERT(nserialnos<cserialnos);
     serialnos=(ogg_uint32_t *)_ogg_realloc(serialnos,
@@ -317,7 +319,7 @@ struct OpusSeekRecord{
 static int op_get_prev_page_serial(OggOpusFile *_of,OpusSeekRecord *_sr,
  opus_int64 _offset,ogg_uint32_t _serialno,
  const ogg_uint32_t *_serialnos,int _nserialnos){
-  OpusSeekRecord preferred_sr={0};
+  OpusSeekRecord preferred_sr;
   ogg_page       og;
   opus_int64     begin;
   opus_int64     end;
@@ -496,30 +498,26 @@ static int op_fetch_headers_impl(OggOpusFile *_of,OpusHead *_head,
       ogg_stream_pagein(&_of->os,_og);
       if(OP_LIKELY(ogg_stream_packetout(&_of->os,&op)>0)){
         ret=opus_head_parse(_head,op.packet,op.bytes);
-        /*If it's just a stream type we don't recognize, ignore it.*/
-        if(ret==OP_ENOTFORMAT)continue;
-        /*Everything else is fatal.*/
-        if(OP_UNLIKELY(ret<0))return ret;
         /*Found a valid Opus header.
           Continue setup.*/
-        _of->ready_state=OP_STREAMSET;
+        if(OP_LIKELY(ret>=0))_of->ready_state=OP_STREAMSET;
+        /*If it's just a stream type we don't recognize, ignore it.
+          Everything else is fatal.*/
+        else if(ret!=OP_ENOTFORMAT)return ret;
       }
+      /*TODO: Should a BOS page with no packets be an error?*/
     }
     /*Get the next page.
       No need to clamp the boundary offset against _of->end, as all errors
-       become OP_ENOTFORMAT.*/
+       become OP_ENOTFORMAT or OP_EBADHEADER.*/
     if(OP_UNLIKELY(op_get_next_page(_of,_og,
      OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){
-      return OP_ENOTFORMAT;
-    }
-    /*If this page also belongs to our Opus stream, submit it and break.*/
-    if(_of->ready_state==OP_STREAMSET
-     &&_of->os.serialno==ogg_page_serialno(_og)){
-      ogg_stream_pagein(&_of->os,_og);
-      break;
+      return _of->ready_state<OP_STREAMSET?OP_ENOTFORMAT:OP_EBADHEADER;
     }
   }
   if(OP_UNLIKELY(_of->ready_state!=OP_STREAMSET))return OP_ENOTFORMAT;
+  /*If the first non-header page belonged to our Opus stream, submit it.*/
+  if(_of->os.serialno==ogg_page_serialno(_og))ogg_stream_pagein(&_of->os,_og);
   /*Loop getting packets.*/
   for(;;){
     switch(ogg_stream_packetout(&_of->os,&op)){
@@ -830,6 +828,7 @@ static opus_int32 op_collect_audio_packets(OggOpusFile *_of,
 static int op_find_initial_pcm_offset(OggOpusFile *_of,
  OggOpusLink *_link,ogg_page *_og){
   ogg_page     og;
+  opus_int64   page_offset;
   ogg_int64_t  pcm_start;
   ogg_int64_t  prev_packet_gp;
   ogg_int64_t  cur_page_gp;
@@ -847,13 +846,12 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
      least once.*/
   total_duration=0;
   do{
-    opus_int64 llret;
-    llret=op_get_next_page(_of,_og,_of->end);
+    page_offset=op_get_next_page(_of,_og,_of->end);
     /*We should get a page unless the file is truncated or mangled.
       Otherwise there are no audio data packets in the whole logical stream.*/
-    if(OP_UNLIKELY(llret<0)){
+    if(OP_UNLIKELY(page_offset<0)){
       /*Fail if there was a read error.*/
-      if(llret<OP_FALSE)return (int)llret;
+      if(page_offset<OP_FALSE)return (int)page_offset;
       /*Fail if the pre-skip is non-zero, since it's asking us to skip more
          samples than exist.*/
       if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
@@ -954,6 +952,7 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
   _of->op_count=pi;
   _of->cur_discard_count=_link->head.pre_skip;
   _of->prev_packet_gp=_link->pcm_start=pcm_start;
+  _of->prev_page_offset=page_offset;
   return 0;
 }
 
@@ -1128,7 +1127,7 @@ static int op_bisect_forward_serialno(OggOpusFile *_of,
     opus_int64  bisect;
     opus_int64  next;
     opus_int64  last;
-    ogg_int64_t end_offset=0;
+    ogg_int64_t end_offset;
     ogg_int64_t end_gp;
     int         sri;
     serialnos=*_serialnos;
@@ -1309,13 +1308,20 @@ static void op_update_gain(OggOpusFile *_of){
      track gain must lie in the range [-32768,32767], and the user-supplied
      offset has been pre-clamped to [-98302,98303].*/
   switch(_of->gain_type){
+    case OP_ALBUM_GAIN:{
+      int album_gain_q8;
+      album_gain_q8=0;
+      opus_tags_get_album_gain(&_of->links[li].tags,&album_gain_q8);
+      gain_q8+=album_gain_q8;
+      gain_q8+=head->output_gain;
+    }break;
     case OP_TRACK_GAIN:{
       int track_gain_q8;
       track_gain_q8=0;
       opus_tags_get_track_gain(&_of->links[li].tags,&track_gain_q8);
       gain_q8+=track_gain_q8;
-    }
-    /*Fall through.*/
+      gain_q8+=head->output_gain;
+    }break;
     case OP_HEADER_GAIN:gain_q8+=head->output_gain;break;
     case OP_ABSOLUTE_GAIN:break;
     default:OP_ASSERT(0);
@@ -1407,6 +1413,7 @@ static int op_open_seekable2(OggOpusFile *_of){
   ogg_sync_state    oy_start;
   ogg_stream_state  os_start;
   ogg_packet       *op_start;
+  opus_int64        prev_page_offset;
   opus_int64        start_offset;
   int               start_op_count;
   int               ret;
@@ -1426,6 +1433,7 @@ static int op_open_seekable2(OggOpusFile *_of){
   if(op_start==NULL)return OP_EFAULT;
   *&oy_start=_of->oy;
   *&os_start=_of->os;
+  prev_page_offset=_of->prev_page_offset;
   start_offset=_of->offset;
   memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count);
   OP_ASSERT((*_of->callbacks.tell)(_of->source)==op_position(_of));
@@ -1442,6 +1450,7 @@ static int op_open_seekable2(OggOpusFile *_of){
   memcpy(_of->op,op_start,sizeof(*_of->op)*start_op_count);
   _ogg_free(op_start);
   _of->prev_packet_gp=_of->links[0].pcm_start;
+  _of->prev_page_offset=prev_page_offset;
   _of->cur_discard_count=_of->links[0].head.pre_skip;
   if(OP_UNLIKELY(ret<0))return ret;
   /*And restore the position indicator.*/
@@ -1456,6 +1465,7 @@ static void op_decode_clear(OggOpusFile *_of){
   _of->op_count=0;
   _of->od_buffer_size=0;
   _of->prev_packet_gp=-1;
+  _of->prev_page_offset=-1;
   if(!_of->seekable){
     OP_ASSERT(_of->ready_state>=OP_INITSET);
     opus_tags_clear(&_of->links[0].tags);
@@ -1814,13 +1824,11 @@ opus_int32 op_bitrate_instant(OggOpusFile *_of){
   This handles the case where we're at a bitstream boundary and dumps the
    decoding machine.
   If the decoding machine is unloaded, it loads it.
-  It also keeps prev_packet_gp up to date (seek and read both use this; seek
-   uses a special hack with _readp).
+  It also keeps prev_packet_gp up to date (seek and read both use this).
   Return: <0) Error, OP_HOLE (lost packet), or OP_EOF.
-           0) Need more data (only if _readp==0).
-           1) Got at least one audio data packet.*/
+           0) Got at least one audio data packet.*/
 static int op_fetch_and_process_page(OggOpusFile *_of,
- ogg_page *_og,opus_int64 _page_pos,int _readp,int _spanp,int _ignore_holes){
+ ogg_page *_og,opus_int64 _page_offset,int _spanp,int _ignore_holes){
   OggOpusLink  *links;
   ogg_uint32_t  cur_serialno;
   int           seekable;
@@ -1828,7 +1836,6 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
   int           ret;
   /*We shouldn't get here if we have unprocessed packets.*/
   OP_ASSERT(_of->ready_state<OP_INITSET||_of->op_pos>=_of->op_count);
-  if(!_readp)return 0;
   seekable=_of->seekable;
   links=_of->links;
   cur_link=seekable?_of->cur_link:0;
@@ -1837,36 +1844,27 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
   for(;;){
     ogg_page og;
     OP_ASSERT(_of->ready_state>=OP_OPENED);
-    /*This loop is not strictly necessary, but there's no sense in doing the
-       extra checks of the larger loop for the common case in a multiplexed
-       bistream where the page is simply part of a different logical
-       bitstream.*/
-    do{
-      /*If we were given a page to use, use it.*/
-      if(_og!=NULL){
-        *&og=*_og;
-        _og=NULL;
-      }
-      /*Keep reading until we get a page with the correct serialno.*/
-      else _page_pos=op_get_next_page(_of,&og,_of->end);
-      /*EOF: Leave uninitialized.*/
-      if(_page_pos<0)return _page_pos<OP_FALSE?(int)_page_pos:OP_EOF;
-      if(OP_LIKELY(_of->ready_state>=OP_STREAMSET)){
-        if(cur_serialno!=(ogg_uint32_t)ogg_page_serialno(&og)){
-          /*Two possibilities:
-             1) Another stream is multiplexed into this logical section, or*/
-          if(OP_LIKELY(!ogg_page_bos(&og)))continue;
-          /* 2) Our decoding just traversed a bitstream boundary.*/
-          if(!_spanp)return OP_EOF;
-          if(OP_LIKELY(_of->ready_state>=OP_INITSET))op_decode_clear(_of);
-          break;
-        }
-      }
-      /*Bitrate tracking: add the header's bytes here.
-        The body bytes are counted when we consume the packets.*/
-      _of->bytes_tracked+=og.header_len;
+    /*If we were given a page to use, use it.*/
+    if(_og!=NULL){
+      *&og=*_og;
+      _og=NULL;
+    }
+    /*Keep reading until we get a page with the correct serialno.*/
+    else _page_offset=op_get_next_page(_of,&og,_of->end);
+    /*EOF: Leave uninitialized.*/
+    if(_page_offset<0)return _page_offset<OP_FALSE?(int)_page_offset:OP_EOF;
+    if(OP_LIKELY(_of->ready_state>=OP_STREAMSET)
+     &&cur_serialno!=(ogg_uint32_t)ogg_page_serialno(&og)){
+      /*Two possibilities:
+         1) Another stream is multiplexed into this logical section, or*/
+      if(OP_LIKELY(!ogg_page_bos(&og)))continue;
+      /* 2) Our decoding just traversed a bitstream boundary.*/
+      if(!_spanp)return OP_EOF;
+      if(OP_LIKELY(_of->ready_state>=OP_INITSET))op_decode_clear(_of);
     }
-    while(0);
+    /*Bitrate tracking: add the header's bytes here.
+      The body bytes are counted when we consume the packets.*/
+    else _of->bytes_tracked+=og.header_len;
     /*Do we need to load a new machine before submitting the page?
       This is different in the seekable and non-seekable cases.
       In the seekable case, we already have all the header information loaded
@@ -1895,8 +1893,9 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
         _of->ready_state=OP_STREAMSET;
         /*If we're at the start of this link, initialize the granule position
            and pre-skip tracking.*/
-        if(_page_pos<=links[cur_link].data_offset){
+        if(_page_offset<=links[cur_link].data_offset){
           _of->prev_packet_gp=links[cur_link].pcm_start;
+          _of->prev_page_offset=-1;
           _of->cur_discard_count=links[cur_link].head.pre_skip;
           /*Ignore a hole at the start of a new link (this is common for
              streams joined in the middle) or after seeking.*/
@@ -1923,10 +1922,12 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
         /*If we didn't get any packets out of op_find_initial_pcm_offset(),
            keep going (this is possible if end-trimming trimmed them all).*/
         if(_of->op_count<=0)continue;
-        /*Otherwise, we're done.*/
+        /*Otherwise, we're done.
+          TODO: This resets bytes_tracked, which misses the header bytes
+           already processed by op_find_initial_pcm_offset().*/
         ret=op_make_decode_ready(_of);
         if(OP_UNLIKELY(ret<0))return ret;
-        return 1;
+        return 0;
       }
     }
     /*The buffered page is the data we want, and we're ready for it.
@@ -2044,7 +2045,8 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
             op_granpos_add(&prev_packet_gp,prev_packet_gp,total_duration)
              should succeed and give prev_packet_gp==cur_page_gp.
             But we don't bother to check that, as there isn't much we can do
-             if it's not true.
+             if it's not true, and it actually will not be true on the first
+             page after a seek, if there was a continued packet.
             The only thing we guarantee is that the start and end granule
              positions of the packets are valid, and that they are monotonic
              within a page.
@@ -2073,9 +2075,10 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
           OP_ASSERT(total_duration==0);
         }
         _of->prev_packet_gp=prev_packet_gp;
+        _of->prev_page_offset=_page_offset;
         _of->op_count=pi;
         /*If end-trimming didn't trim all the packets, we're done.*/
-        if(OP_LIKELY(pi>0))return 1;
+        if(OP_LIKELY(pi>0))return 0;
       }
     }
   }
@@ -2093,7 +2096,7 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _pos){
   _of->samples_tracked=0;
   ret=op_seek_helper(_of,_pos);
   if(OP_UNLIKELY(ret<0))return OP_EREAD;
-  ret=op_fetch_and_process_page(_of,NULL,-1,1,1,1);
+  ret=op_fetch_and_process_page(_of,NULL,-1,1,1);
   /*If we hit EOF, op_fetch_and_process_page() leaves us uninitialized.
     Instead, jump to the end.*/
   if(ret==OP_EOF){
@@ -2105,7 +2108,6 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _pos){
     _of->cur_discard_count=0;
     ret=0;
   }
-  else if(ret>0)ret=0;
   return ret;
 }
 
@@ -2146,6 +2148,27 @@ static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
   return -1;
 }
 
+/*A small helper to determine if an Ogg page contains data that continues onto
+   a subsequent page.*/
+static int op_page_continues(const ogg_page *_og){
+  int nlacing;
+  OP_ASSERT(_og->header_len>=27);
+  nlacing=_og->header[26];
+  OP_ASSERT(_og->header_len>=27+nlacing);
+  /*This also correctly handles the (unlikely) case of nlacing==0, because
+     0!=255.*/
+  return _og->header[27+nlacing-1]==255;
+}
+
+/*A small helper to buffer the continued packet data from a page.*/
+static void op_buffer_continued_data(OggOpusFile *_of,ogg_page *_og){
+  ogg_packet op;
+  ogg_stream_pagein(&_of->os,_og);
+  /*Drain any packets that did end on this page (and ignore holes).
+    We only care about the continued packet data.*/
+  while(ogg_stream_packetout(&_of->os,&op));
+}
+
 /*This controls how close the target has to be to use the current stream
    position to subdivide the initial range.
   Two minutes seems to be a good default.*/
@@ -2170,18 +2193,20 @@ static int op_pcm_seek_page(OggOpusFile *_of,
   ogg_int64_t        pcm_start;
   ogg_int64_t        pcm_end;
   ogg_int64_t        best_gp;
-  ogg_int64_t        diff=0;
+  ogg_int64_t        diff;
   ogg_uint32_t       serialno;
   opus_int32         pre_skip;
   opus_int64         begin;
   opus_int64         end;
   opus_int64         boundary;
   opus_int64         best;
+  opus_int64         best_start;
   opus_int64         page_offset;
   opus_int64         d0;
   opus_int64         d1;
   opus_int64         d2;
   int                force_bisect;
+  int                buffering;
   int                ret;
   _of->bytes_tracked=0;
   _of->samples_tracked=0;
@@ -2189,8 +2214,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
   best_gp=pcm_start=link->pcm_start;
   pcm_end=link->pcm_end;
   serialno=link->serialno;
-  best=begin=link->data_offset;
+  best=best_start=begin=link->data_offset;
   page_offset=-1;
+  buffering=0;
   /*We discard the first 80 ms of data after a seek, so seek back that much
      farther.
     If we can't, simply seek to the beginning of the link.*/
@@ -2236,6 +2262,18 @@ static int op_pcm_seek_page(OggOpusFile *_of,
             if(offset-begin>=end-begin>>1||diff>-OP_CUR_TIME_THRESH){
               best=begin=offset;
               best_gp=pcm_start=gp;
+              /*If we have buffered data from a continued packet, remember the
+                 offset of the previous page's start, so that if we do wind up
+                 having to seek back here later, we can prime the stream with
+                 the continued packet data.
+                With no continued packet, we remember the end of the page.*/
+              best_start=_of->os.body_returned<_of->os.body_fill?
+               _of->prev_page_offset:best;
+              /*If there's completed packets and data in the stream state,
+                 prev_page_offset should always be set.*/
+              OP_ASSERT(best_start>=0);
+              /*Buffer any continued packet data starting from here.*/
+              buffering=1;
             }
           }
           else{
@@ -2247,13 +2285,14 @@ static int op_pcm_seek_page(OggOpusFile *_of,
                generally 1 second or less), we can loop them continuously
                without seeking at all.*/
             OP_ALWAYS_TRUE(!op_granpos_add(&prev_page_gp,_of->op[0].granulepos,
-             op_get_packet_duration(_of->op[0].packet,_of->op[0].bytes)));
+             -op_get_packet_duration(_of->op[0].packet,_of->op[0].bytes)));
             if(op_granpos_cmp(prev_page_gp,_target_gp)<=0){
               /*Don't call op_decode_clear(), because it will dump our
                  packets.*/
               _of->op_pos=0;
               _of->od_buffer_size=0;
               _of->prev_packet_gp=prev_page_gp;
+              /*_of->prev_page_offset already points to the right place.*/
               _of->ready_state=OP_STREAMSET;
               return op_make_decode_ready(_of);
             }
@@ -2274,6 +2313,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
      Vinen)" from libvorbisfile.
     It has been modified substantially since.*/
   op_decode_clear(_of);
+  if(!buffering)ogg_stream_reset_serialno(&_of->os,serialno);
+  _of->cur_link=_li;
+  _of->ready_state=OP_STREAMSET;
   /*Initialize the interval size history.*/
   d2=d1=d0=end-begin;
   force_bisect=0;
@@ -2289,7 +2331,7 @@ static int op_pcm_seek_page(OggOpusFile *_of,
       d2=end-begin>>1;
       if(force_bisect)bisect=begin+(end-begin>>1);
       else{
-        ogg_int64_t diff2=0;
+        ogg_int64_t diff2;
         OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start));
         OP_ALWAYS_TRUE(!op_granpos_diff(&diff2,pcm_end,pcm_start));
         /*Take a (pretty decent) guess.*/
@@ -2299,12 +2341,23 @@ static int op_pcm_seek_page(OggOpusFile *_of,
       force_bisect=0;
     }
     if(bisect!=_of->offset){
+      /*Discard any buffered continued packet data.*/
+      if(buffering)ogg_stream_reset(&_of->os);
+      buffering=0;
       page_offset=-1;
       ret=op_seek_helper(_of,bisect);
       if(OP_UNLIKELY(ret<0))return ret;
     }
     chunk_size=OP_CHUNK_SIZE;
     next_boundary=boundary;
+    /*Now scan forward and figure out where we landed.
+      In the ideal case, we will see a page with a granule position at or
+       before our target, followed by a page with a granule position after our
+       target (or the end of the search interval).
+      Then we can just drop out and will have all of the data we need with no
+       additional seeking.
+      If we landed too far before, or after, we'll break out and do another
+       bisection.*/
     while(begin<end){
       page_offset=op_get_next_page(_of,&og,boundary);
       if(page_offset<0){
@@ -2314,7 +2367,10 @@ static int op_pcm_seek_page(OggOpusFile *_of,
         /*If we scanned the whole interval, we're done.*/
         if(bisect<=begin+1)end=begin;
         else{
-          /*Otherwise, back up one chunk.*/
+          /*Otherwise, back up one chunk.
+            First, discard any data from a continued packet.*/
+          if(buffering)ogg_stream_reset(&_of->os);
+          buffering=0;
           bisect=OP_MAX(bisect-chunk_size,begin);
           ret=op_seek_helper(_of,bisect);
           if(OP_UNLIKELY(ret<0))return ret;
@@ -2327,12 +2383,32 @@ static int op_pcm_seek_page(OggOpusFile *_of,
       }
       else{
         ogg_int64_t gp;
+        int         has_packets;
         /*Save the offset of the first page we found after the seek, regardless
            of the stream it came from or whether or not it has a timestamp.*/
         next_boundary=OP_MIN(page_offset,next_boundary);
         if(serialno!=(ogg_uint32_t)ogg_page_serialno(&og))continue;
-        gp=ogg_page_granulepos(&og);
-        if(gp==-1)continue;
+        has_packets=ogg_page_packets(&og)>0;
+        /*Force the gp to -1 (as it should be per spec) if no packets end on
+           this page.
+          Otherwise we might get confused when we try to pull out a packet
+           with that timestamp and can't find it.*/
+        gp=has_packets?ogg_page_granulepos(&og):-1;
+        if(gp==-1){
+          if(buffering){
+            if(OP_LIKELY(!has_packets))ogg_stream_pagein(&_of->os,&og);
+            else{
+              /*If packets did end on this page, but we still didn't have a
+                 valid granule position (in violation of the spec!), stop
+                 buffering continued packet data.
+                Otherwise we might continue past the packet we actually
+                 wanted.*/
+              ogg_stream_reset(&_of->os);
+              buffering=0;
+            }
+          }
+          continue;
+        }
         if(op_granpos_cmp(gp,_target_gp)<0){
           /*We found a page that ends before our target.
             Advance to the raw offset of the next page.*/
@@ -2345,7 +2421,22 @@ static int op_pcm_seek_page(OggOpusFile *_of,
           }
           /*Save the byte offset of the end of the page with this granule
              position.*/
-          best=begin;
+          best=best_start=begin;
+          /*Buffer any data from a continued packet, if necessary.
+            This avoids the need to seek back here if the next timestamp we
+             encounter while scanning forward lies after our target.*/
+          if(buffering)ogg_stream_reset(&_of->os);
+          if(op_page_continues(&og)){
+            op_buffer_continued_data(_of,&og);
+            /*If we have a continued packet, remember the offset of this
+               page's start, so that if we do wind up having to seek back here
+               later, we can prime the stream with the continued packet data.
+              With no continued packet, we remember the end of the page.*/
+            best_start=page_offset;
+          }
+          /*Then force buffering on, so that if a packet starts (but does not
+             end) on the next page, we still avoid the extra seek back.*/
+          buffering=1;
           best_gp=pcm_start=gp;
           OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start));
           /*If we're more than a second away from our target, break out and
@@ -2377,28 +2468,40 @@ static int op_pcm_seek_page(OggOpusFile *_of,
       }
     }
   }
-  /*Found our page.
-    Seek to the end of it and update prev_packet_gp.
-    Our caller will set cur_discard_count.
-    This is an easier case than op_raw_seek(), as we don't need to keep any
-     packets from the page we found.*/
-  /*Seek, if necessary.*/
-  if(best!=page_offset){
-    page_offset=-1;
-    ret=op_seek_helper(_of,best);
-    if(OP_UNLIKELY(ret<0))return ret;
-  }
+  /*Found our page.*/
   OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0);
-  _of->cur_link=_li;
-  _of->ready_state=OP_STREAMSET;
+  /*Seek, if necessary.
+    If we were buffering data from a continued packet, we should be able to
+     continue to scan forward to get the rest of the data (even if
+     page_offset==-1).
+    Otherwise, we need to seek back to best_start.*/
+  if(!buffering){
+    if(best_start!=page_offset){
+      page_offset=-1;
+      ret=op_seek_helper(_of,best_start);
+      if(OP_UNLIKELY(ret<0))return ret;
+    }
+    if(best_start<best){
+      /*Retrieve the page at best_start, if we do not already have it.*/
+      if(page_offset<0){
+        page_offset=op_get_next_page(_of,&og,link->end_offset);
+        if(OP_UNLIKELY(page_offset<OP_FALSE))return (int)page_offset;
+        if(OP_UNLIKELY(page_offset!=best_start))return OP_EBADLINK;
+      }
+      op_buffer_continued_data(_of,&og);
+      page_offset=-1;
+    }
+  }
+  /*Update prev_packet_gp to allow per-packet granule position assignment.*/
   _of->prev_packet_gp=best_gp;
-  ogg_stream_reset_serialno(&_of->os,serialno);
-  ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,1,0,1);
-  if(OP_UNLIKELY(ret<=0))return OP_EBADLINK;
+  _of->prev_page_offset=best_start;
+  ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,0,1);
+  if(OP_UNLIKELY(ret<0))return OP_EBADLINK;
   /*Verify result.*/
   if(OP_UNLIKELY(op_granpos_cmp(_of->prev_packet_gp,_target_gp)>0)){
     return OP_EBADLINK;
   }
+  /*Our caller will set cur_discard_count to handle pre-roll.*/
   return 0;
 }
 
@@ -2475,8 +2578,8 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset){
     if(op_pos<op_count)break;
     /*We skipped all the packets on this page.
       Fetch another.*/
-    ret=op_fetch_and_process_page(_of,NULL,-1,1,0,1);
-    if(OP_UNLIKELY(ret<=0))return OP_EBADLINK;
+    ret=op_fetch_and_process_page(_of,NULL,-1,0,1);
+    if(OP_UNLIKELY(ret<0))return OP_EBADLINK;
   }
   OP_ALWAYS_TRUE(!op_granpos_diff(&diff,prev_packet_gp,pcm_start));
   /*We skipped too far.
@@ -2559,8 +2662,8 @@ void op_set_decode_callback(OggOpusFile *_of,
 
 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
-   &&_gain_type!=OP_ABSOLUTE_GAIN){
+  if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_ALBUM_GAIN
+   &&_gain_type!=OP_TRACK_GAIN&&_gain_type!=OP_ABSOLUTE_GAIN){
     return OP_EINVAL;
   }
   _of->gain_type=_gain_type;
@@ -2740,7 +2843,7 @@ static int op_read_native(OggOpusFile *_of,
       }
     }
     /*Suck in another page.*/
-    ret=op_fetch_and_process_page(_of,NULL,-1,1,1,0);
+    ret=op_fetch_and_process_page(_of,NULL,-1,1,0);
     if(OP_UNLIKELY(ret==OP_EOF)){
       if(_li!=NULL)*_li=_of->cur_link;
       return 0;

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/ioquake3.git



More information about the Pkg-games-commits mailing list