diff --git a/code/opusfile-0.5/include/opusfile.h b/code/opusfile-0.5/include/opusfile.h
index 850cd6b9..4bf2fba9 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 NULL
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 NULL
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 addition 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
libopusfile provides noise-shaping dithering and, if compiled
against libopus 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 \a _nsamples*\a
_nchannels
samples in the requested
format.
\retval #OP_DEC_USE_DEFAULT No decoding was done.
- libopusfile should decode normally
- instead.*/
+ libopusfile 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
+ libopusfile, 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.
+ libopusfile 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 NULL
to disable calling the
@@ -1749,6 +1813,10 @@ void op_set_decode_callback(OggOpusFile *_of,
This is the default.*/
#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 4a9eaf59..22d75d4b 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 6cf98516..c36f9a9e 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=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 08114919..ee48ea34 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 73b95de6..7c3691b4 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(nserialnosos,_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_stateready_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(llrethead.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_stateop_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_posready_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;
}
- while(0);
+ /*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_offsetready_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);
+ }
+ /*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(beginos);
+ 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_startend_offset);
+ if(OP_UNLIKELY(page_offsetprev_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_posgain_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;