Update opusfile from 0.5 to 0.8

This commit is contained in:
Zack Middleton 2017-05-23 11:03:07 -05:00
parent ef8ad54421
commit 7139094355
5 changed files with 479 additions and 154 deletions

View file

@ -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) int opus_tags_add_comment(OpusTags *_tags,const char *_comment)
OP_ARG_NONNULL(1) OP_ARG_NONNULL(2); 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. /**Look up a comment value by its tag.
\param _tags An initialized #OpusTags structure. \param _tags An initialized #OpusTags structure.
\param _tag The tag to look up. \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) int opus_tags_query_count(const OpusTags *_tags,const char *_tag)
OP_ARG_NONNULL(1) OP_ARG_NONNULL(2); 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. /**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, This searches for the first R128_TRACK_GAIN tag with a valid signed,
16-bit decimal integer value and returns the value. 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 /**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 a (possibly-chained) Ogg Opus stream, including all headers and Ogg muxing
overhead. 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 _of The \c OggOpusFile from which to retrieve the compressed size.
\param _li The index of the link whose compressed size should be computed. \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 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.*/ \retval #OP_EINVAL The stream was only partially open.*/
int op_current_link(const OggOpusFile *_of) OP_ARG_NONNULL(1); int op_current_link(const OggOpusFile *_of) OP_ARG_NONNULL(1);
/**Computes the bitrate for a given link in a (possibly chained) Ogg Opus /**Computes the bitrate of the stream, or of an individual link in a
stream. (possibly-chained) Ogg Opus stream.
The stream must be seekable to compute the bitrate. The stream must be seekable to compute the bitrate.
For unseekable streams, use op_bitrate_instant() to get periodic estimates. 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 _of The \c OggOpusFile from which to retrieve the bitrate.
\param _li The index of the link whose bitrate should be computed. \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. \return The bitrate on success, or a negative value on error.
\retval #OP_EINVAL The stream was only partially open, the stream was not \retval #OP_EINVAL The stream was only partially open, the stream was not
seekable, or \a _li was larger than the number of 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 These downmix multichannel files to two channels, so they can always return
samples in the same format for every link in a chained file. 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 If the rest of your audio processing chain can handle floating point, the
routines should be preferred, as floating point output avoids introducing floating-point routines should be preferred, as they prevent clipping and
clipping and other issues which might be avoided entirely if, e.g., you other issues which might be avoided entirely if, e.g., you scale down the
scale down the volume at some other stage. volume at some other stage.
However, if you intend to direct consume 16-bit samples, the conversion in However, if you intend to consume 16-bit samples directly, the conversion in
<tt>libopusfile</tt> provides noise-shaping dithering and, if compiled <tt>libopusfile</tt> provides noise-shaping dithering and, if compiled
against <tt>libopus</tt>&nbsp;1.1 or later, soft-clipping prevention. against <tt>libopus</tt>&nbsp;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. #OP_DEC_FORMAT_FLOAT.
\param _li The index of the link from which this packet was decoded. \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. \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(). opus_multistream_decode() or opus_multistream_decode_float().
Success codes are as follows:
\retval 0 Decoding was successful. \retval 0 Decoding was successful.
The application has filled the buffer with The application has filled the buffer with
exactly <code>\a _nsamples*\a exactly <code>\a _nsamples*\a
_nchannels</code> samples in the requested _nchannels</code> samples in the requested
format. format.
\retval #OP_DEC_USE_DEFAULT No decoding was done. \retval #OP_DEC_USE_DEFAULT No decoding was done.
<tt>libopusfile</tt> should decode normally <tt>libopusfile</tt> should do the decoding
instead.*/ by itself instead.*/
typedef int (*op_decode_cb_func)(void *_ctx,OpusMSDecoder *_decoder,void *_pcm, typedef int (*op_decode_cb_func)(void *_ctx,OpusMSDecoder *_decoder,void *_pcm,
const ogg_packet *_op,int _nsamples,int _nchannels,int _format,int _li); const ogg_packet *_op,int _nsamples,int _nchannels,int _format,int _li);
/**Sets the packet decode callback function. /**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 A call to this function is no guarantee that the audio will eventually be
delivered to the application. delivered to the application.
Some or all of the data from the packet may be discarded (i.e., at the <tt>libopusfile</tt> may discard some or all of the decoded audio data
beginning or end of a link, or after a seek), however the callback is (i.e., at the beginning or end of a link, or after a seek), however the
required to provide all of it. callback is still required to provide all of it.
\param _of The \c OggOpusFile on which to set the decode callback. \param _of The \c OggOpusFile on which to set the decode callback.
\param _decode_cb The callback function to call. \param _decode_cb The callback function to call.
This may be <code>NULL</code> to disable calling the This may be <code>NULL</code> to disable calling the
@ -1749,6 +1813,10 @@ void op_set_decode_callback(OggOpusFile *_of,
This is the default.*/ This is the default.*/
#define OP_HEADER_GAIN (0) #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 /**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.*/ R128_TRACK_GAIN value (if any), in addition to the header gain.*/
#define OP_TRACK_GAIN (3008) #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 It is meant for setting a target volume level, rather than applying smooth
fades, etc. fades, etc.
\param _of The \c OggOpusFile on which to set the gain offset. \param _of The \c OggOpusFile on which to set the gain offset.
\param _gain_type One of #OP_HEADER_GAIN, #OP_TRACK_GAIN, or \param _gain_type One of #OP_HEADER_GAIN, #OP_ALBUM_GAIN,
#OP_ABSOLUTE_GAIN. #OP_TRACK_GAIN, or #OP_ABSOLUTE_GAIN.
\param _gain_offset_q8 The gain offset to apply, in 1/256ths of a dB. \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. \return 0 on success or a negative value on error.
\retval #OP_EINVAL The \a _gain_type was unrecognized.*/ \retval #OP_EINVAL The \a _gain_type was unrecognized.*/

View file

@ -231,7 +231,7 @@ typedef SOCKET op_sock;
# define POLLRDBAND (0x0200) # define POLLRDBAND (0x0200)
/*There is data to read.*/ /*There is data to read.*/
# define POLLIN (POLLRDNORM|POLLRDBAND) # define POLLIN (POLLRDNORM|POLLRDBAND)
/* There is urgent data to read.*/ /*There is urgent data to read.*/
# define POLLPRI (0x0400) # define POLLPRI (0x0400)
/*Equivalent to POLLOUT.*/ /*Equivalent to POLLOUT.*/
# define POLLWRNORM (0x0010) # define POLLWRNORM (0x0010)
@ -721,7 +721,7 @@ static struct addrinfo *op_resolve(const char *_host,unsigned _port){
char service[6]; char service[6];
memset(&hints,0,sizeof(hints)); memset(&hints,0,sizeof(hints));
hints.ai_socktype=SOCK_STREAM; hints.ai_socktype=SOCK_STREAM;
#if !defined(_WIN32) #if defined(AI_NUMERICSERV)
hints.ai_flags=AI_NUMERICSERV; hints.ai_flags=AI_NUMERICSERV;
#endif #endif
OP_ASSERT(_port<=65535U); 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. /*Close the connection and move it to the free list.
_stream: The stream containing the free list. _stream: The stream containing the free list.
_conn: The connection to close. _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.*/ _gracefully: Whether or not to shut down cleanly.*/
static void op_http_conn_close(OpusHTTPStream *_stream,OpusHTTPConn *_conn, static void op_http_conn_close(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
OpusHTTPConn **_pnext,int _gracefully){ OpusHTTPConn **_pnext,int _gracefully){
@ -1517,10 +1517,17 @@ static long op_bio_retry_ctrl(BIO *_b,int _cmd,long _num,void *_ptr){
return ret; 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){ 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->num=0;
_b->ptr=NULL; # endif
BIO_set_data(_b,NULL);
return 1; return 1;
} }
@ -1528,6 +1535,7 @@ static int op_bio_retry_free(BIO *_b){
return _b!=NULL; return _b!=NULL;
} }
# if OPENSSL_VERSION_NUMBER<0x10100000L
/*This is not const because OpenSSL doesn't allow it, even though it won't /*This is not const because OpenSSL doesn't allow it, even though it won't
write to it.*/ write to it.*/
static BIO_METHOD op_bio_retry_method={ static BIO_METHOD op_bio_retry_method={
@ -1542,11 +1550,15 @@ static BIO_METHOD op_bio_retry_method={
op_bio_retry_free, op_bio_retry_free,
NULL NULL
}; };
# endif
/*Establish a CONNECT tunnel and pipeline the start of the TLS handshake for /*Establish a CONNECT tunnel and pipeline the start of the TLS handshake for
proxying https URL requests.*/ proxying https URL requests.*/
static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream, static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream,
OpusHTTPConn *_conn,op_sock _fd,SSL *_ssl_conn,BIO *_ssl_bio){ 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; BIO *retry_bio;
char *status_code; char *status_code;
char *next; char *next;
@ -1557,13 +1569,32 @@ static int op_http_conn_establish_tunnel(OpusHTTPStream *_stream,
ret=op_http_conn_write_fully(_conn, ret=op_http_conn_write_fully(_conn,
_stream->proxy_connect.buf,_stream->proxy_connect.nbuf); _stream->proxy_connect.buf,_stream->proxy_connect.nbuf);
if(OP_UNLIKELY(ret<0))return ret; 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); retry_bio=BIO_new(&op_bio_retry_method);
if(OP_UNLIKELY(retry_bio==NULL))return OP_EFAULT; if(OP_UNLIKELY(retry_bio==NULL))return OP_EFAULT;
# endif
SSL_set_bio(_ssl_conn,retry_bio,_ssl_bio); SSL_set_bio(_ssl_conn,retry_bio,_ssl_bio);
SSL_set_connect_state(_ssl_conn); SSL_set_connect_state(_ssl_conn);
/*This shouldn't succeed, since we can't read yet.*/ /*This shouldn't succeed, since we can't read yet.*/
OP_ALWAYS_TRUE(SSL_connect(_ssl_conn)<0); OP_ALWAYS_TRUE(SSL_connect(_ssl_conn)<0);
SSL_set_bio(_ssl_conn,_ssl_bio,_ssl_bio); 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 /*Only now do we disable write coalescing, to allow the CONNECT
request and the start of the TLS handshake to be combined.*/ request and the start of the TLS handshake to be combined.*/
op_sock_set_tcp_nodelay(_fd,1); 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.*/ /*Initialize the SSL library if necessary.*/
if(OP_URL_IS_SSL(&_stream->url)&&_stream->ssl_ctx==NULL){ if(OP_URL_IS_SSL(&_stream->url)&&_stream->ssl_ctx==NULL){
SSL_CTX *ssl_ctx; 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. /*The documentation says SSL_library_init() is not reentrant.
We don't want to add our own depenencies on a threading library, and it 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 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 calling SSL_library_init() at the same time, but there's not much we
can do about that.*/ can do about that.*/
CRYPTO_w_lock(CRYPTO_LOCK_SSL); CRYPTO_w_lock(CRYPTO_LOCK_SSL);
# endif # endif
SSL_library_init(); SSL_library_init();
/*Needed to get SHA2 algorithms with old OpenSSL versions.*/ /*Needed to get SHA2 algorithms with old OpenSSL versions.*/
OpenSSL_add_ssl_algorithms(); OpenSSL_add_ssl_algorithms();
# if !defined(OPENSSL_NO_LOCKING) # if !defined(OPENSSL_NO_LOCKING)
CRYPTO_w_unlock(CRYPTO_LOCK_SSL); 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 # endif
ssl_ctx=SSL_CTX_new(SSLv23_client_method()); ssl_ctx=SSL_CTX_new(SSLv23_client_method());
if(ssl_ctx==NULL)return OP_EFAULT; 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, Otherwise, we'd need a _pnext pointer if we needed to close the connection,
and re-opening it would re-organize the lists.*/ and re-opening it would re-organize the lists.*/
OP_ASSERT(_stream->lru_head==_conn); 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); OP_ASSERT(_buf_size>0);
pos=_conn->pos; pos=_conn->pos;
end_pos=_conn->end_pos; end_pos=_conn->end_pos;
@ -3273,8 +3309,22 @@ static void *op_url_stream_create_impl(OpusFileCallbacks *_cb,const char *_url,
#endif #endif
} }
void *op_url_stream_vcreate(OpusFileCallbacks *_cb, /*The actual implementation of op_url_stream_vcreate().
const char *_url,va_list _ap){ 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; int skip_certificate_check;
const char *proxy_host; const char *proxy_host;
opus_int32 proxy_port; 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 /*If the caller has requested server information, proxy it to a local copy to
simplify error handling.*/ simplify error handling.*/
*_pinfo=NULL;
if(pinfo!=NULL){ if(pinfo!=NULL){
OpusServerInfo info; void *ret;
void *ret; opus_server_info_init(_info);
opus_server_info_init(&info);
ret=op_url_stream_create_impl(_cb,_url,skip_certificate_check, ret=op_url_stream_create_impl(_cb,_url,skip_certificate_check,
proxy_host,proxy_port,proxy_user,proxy_pass,&info); proxy_host,proxy_port,proxy_user,proxy_pass,_info);
if(ret!=NULL)*pinfo=*&info; if(ret!=NULL)*_pinfo=pinfo;
else opus_server_info_clear(&info); else opus_server_info_clear(_info);
return ret; return ret;
} }
return op_url_stream_create_impl(_cb,_url,skip_certificate_check, return op_url_stream_create_impl(_cb,_url,skip_certificate_check,
proxy_host,proxy_port,proxy_user,proxy_pass,NULL); 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, void *op_url_stream_create(OpusFileCallbacks *_cb,
const char *_url,...){ const char *_url,...){
va_list ap; 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){ OggOpusFile *op_vopen_url(const char *_url,int *_error,va_list _ap){
OpusFileCallbacks cb; OpusFileCallbacks cb;
OggOpusFile *of; OggOpusFile *of;
OpusServerInfo info;
OpusServerInfo *pinfo;
void *source; 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)){ if(OP_UNLIKELY(source==NULL)){
OP_ASSERT(pinfo==NULL);
if(_error!=NULL)*_error=OP_EFAULT; if(_error!=NULL)*_error=OP_EFAULT;
return NULL; return NULL;
} }
of=op_open_callbacks(source,&cb,NULL,0,_error); 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; 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){ OggOpusFile *op_vtest_url(const char *_url,int *_error,va_list _ap){
OpusFileCallbacks cb; OpusFileCallbacks cb;
OggOpusFile *of; OggOpusFile *of;
OpusServerInfo info;
OpusServerInfo *pinfo;
void *source; 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)){ if(OP_UNLIKELY(source==NULL)){
OP_ASSERT(pinfo==NULL);
if(_error!=NULL)*_error=OP_EFAULT; if(_error!=NULL)*_error=OP_EFAULT;
return NULL; return NULL;
} }
of=op_test_callbacks(source,&cb,NULL,0,_error); 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; return of;
} }

View file

@ -28,11 +28,13 @@ static int op_parse_int16le(const unsigned char *_data){
} }
static opus_uint32 op_parse_uint32le(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){ 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){ 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){ void opus_tags_clear(OpusTags *_tags){
int ncomments;
int ci; 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->user_comments);
_ogg_free(_tags->comment_lengths); _ogg_free(_tags->comment_lengths);
_ogg_free(_tags->vendor); _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){ static int op_tags_ensure_capacity(OpusTags *_tags,size_t _ncomments){
char **user_comments; char **user_comments;
int *comment_lengths; int *comment_lengths;
int cur_ncomments;
char *binary_suffix_data;
int binary_suffix_len;
size_t size; size_t size;
if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT; if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT;
size=sizeof(*_tags->comment_lengths)*(_ncomments+1); size=sizeof(*_tags->comment_lengths)*(_ncomments+1);
if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT; 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); comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size);
if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT; if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT;
comment_lengths[_ncomments]=0; comment_lengths[_ncomments]=binary_suffix_len;
_tags->comment_lengths=comment_lengths; _tags->comment_lengths=comment_lengths;
size=sizeof(*_tags->user_comments)*(_ncomments+1); size=sizeof(*_tags->user_comments)*(_ncomments+1);
if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT; 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); user_comments=(char **)_ogg_realloc(_tags->user_comments,size);
if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT; if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT;
user_comments[_ncomments]=NULL; user_comments[_ncomments]=binary_suffix_data;
_tags->user_comments=user_comments; _tags->user_comments=user_comments;
return 0; return 0;
} }
@ -186,10 +199,22 @@ static int opus_tags_parse_impl(OpusTags *_tags,
if(_tags->user_comments[ci]==NULL)return OP_EFAULT; if(_tags->user_comments[ci]==NULL)return OP_EFAULT;
_tags->comment_lengths[ci]=(int)count; _tags->comment_lengths[ci]=(int)count;
_tags->comments=ci+1; _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; _data+=count;
len-=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; return 0;
} }
@ -230,6 +255,16 @@ static int opus_tags_copy_impl(OpusTags *_dst,const OpusTags *_src){
_dst->comment_lengths[ci]=len; _dst->comment_lengths[ci]=len;
_dst->comments=ci+1; _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; return 0;
} }
@ -255,29 +290,49 @@ int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value){
tag_len=strlen(_tag); tag_len=strlen(_tag);
value_len=strlen(_value); value_len=strlen(_value);
/*+2 for '=' and '\0'.*/ /*+2 for '=' and '\0'.*/
_tags->comment_lengths[ncomments]=0; comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
_tags->user_comments[ncomments]=comment=
(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
if(OP_UNLIKELY(comment==NULL))return OP_EFAULT; if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
_tags->comment_lengths[ncomments]=tag_len+value_len+1;
memcpy(comment,_tag,sizeof(*comment)*tag_len); memcpy(comment,_tag,sizeof(*comment)*tag_len);
comment[tag_len]='='; comment[tag_len]='=';
memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1)); 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; return 0;
} }
int opus_tags_add_comment(OpusTags *_tags,const char *_comment){ int opus_tags_add_comment(OpusTags *_tags,const char *_comment){
int comment_len; char *comment;
int ncomments; int comment_len;
int ret; int ncomments;
int ret;
ncomments=_tags->comments; ncomments=_tags->comments;
ret=op_tags_ensure_capacity(_tags,ncomments+1); ret=op_tags_ensure_capacity(_tags,ncomments+1);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
comment_len=(int)strlen(_comment); comment_len=(int)strlen(_comment);
_tags->comment_lengths[ncomments]=0; comment=op_strdup_with_len(_comment,comment_len);
_tags->user_comments[ncomments]=op_strdup_with_len(_comment,comment_len); if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT; _tags->user_comments[ncomments]=comment;
_tags->comment_lengths[ncomments]=comment_len; _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; return 0;
} }
@ -328,19 +383,31 @@ int opus_tags_query_count(const OpusTags *_tags,const char *_tag){
return found; 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; char **comments;
int ncomments; int ncomments;
int ci; int ci;
comments=_tags->user_comments; comments=_tags->user_comments;
ncomments=_tags->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++){ 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; char *p;
opus_int32 gain_q8; opus_int32 gain_q8;
int negative; int negative;
p=comments[ci]+16; p=comments[ci]+_tag_len+1;
negative=0; negative=0;
if(*p=='-'){ if(*p=='-'){
negative=-1; negative=-1;
@ -354,7 +421,7 @@ int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
p++; p++;
} }
/*This didn't look like a signed 16-bit decimal integer. /*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; if(*p!='\0')continue;
*_gain_q8=(int)(gain_q8+negative^negative); *_gain_q8=(int)(gain_q8+negative^negative);
return 0; return 0;
@ -363,6 +430,14 @@ int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){
return OP_FALSE; 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){ 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 return _buf_sz>=11&&memcmp(_buf,"\xFF\xD8\xFF\xE0",4)==0
&&(_buf[4]<<8|_buf[5])>=16&&memcmp(_buf+6,"JFIF",5)==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; i+=4;
/*If one of these is set, they all must be, but colors==0 is a valid value.*/ /*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; 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); data_length=op_parse_uint32be(_buf+i);
i+=4; i+=4;
if(data_length>_buf_sz-i)return OP_ENOTFORMAT; if(data_length>_buf_sz-i)return OP_ENOTFORMAT;

View file

@ -186,6 +186,11 @@ struct OggOpusFile{
opus_int32 cur_discard_count; opus_int32 cur_discard_count;
/*The granule position of the previous packet (current packet start time).*/ /*The granule position of the previous packet (current packet start time).*/
ogg_int64_t prev_packet_gp; 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.*/ /*The number of bytes read since the last bitrate query, including framing.*/
opus_int64 bytes_tracked; opus_int64 bytes_tracked;
/*The number of samples decoded since the last bitrate query.*/ /*The number of samples decoded since the last bitrate query.*/

View file

@ -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.*/ /*Save a tiny smidge of verbosity to make the code more readable.*/
static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){ static int op_seek_helper(OggOpusFile *_of,opus_int64 _offset){
if(_offset==_of->offset)return 0; if(_offset==_of->offset)return 0;
if(_of->callbacks.seek==NULL|| if(_of->callbacks.seek==NULL
(*_of->callbacks.seek)(_of->source,_offset,SEEK_SET)){ ||(*_of->callbacks.seek)(_of->source,_offset,SEEK_SET)){
return OP_EREAD; return OP_EREAD;
} }
_of->offset=_offset; _of->offset=_offset;
@ -237,7 +237,9 @@ static int op_add_serialno(const ogg_page *_og,
nserialnos=*_nserialnos; nserialnos=*_nserialnos;
cserialnos=*_cserialnos; cserialnos=*_cserialnos;
if(OP_UNLIKELY(nserialnos>=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; cserialnos=2*cserialnos+1;
OP_ASSERT(nserialnos<cserialnos); OP_ASSERT(nserialnos<cserialnos);
serialnos=(ogg_uint32_t *)_ogg_realloc(serialnos, serialnos=(ogg_uint32_t *)_ogg_realloc(serialnos,
@ -317,7 +319,7 @@ struct OpusSeekRecord{
static int op_get_prev_page_serial(OggOpusFile *_of,OpusSeekRecord *_sr, static int op_get_prev_page_serial(OggOpusFile *_of,OpusSeekRecord *_sr,
opus_int64 _offset,ogg_uint32_t _serialno, opus_int64 _offset,ogg_uint32_t _serialno,
const ogg_uint32_t *_serialnos,int _nserialnos){ const ogg_uint32_t *_serialnos,int _nserialnos){
OpusSeekRecord preferred_sr={0}; OpusSeekRecord preferred_sr;
ogg_page og; ogg_page og;
opus_int64 begin; opus_int64 begin;
opus_int64 end; opus_int64 end;
@ -496,30 +498,26 @@ static int op_fetch_headers_impl(OggOpusFile *_of,OpusHead *_head,
ogg_stream_pagein(&_of->os,_og); ogg_stream_pagein(&_of->os,_og);
if(OP_LIKELY(ogg_stream_packetout(&_of->os,&op)>0)){ if(OP_LIKELY(ogg_stream_packetout(&_of->os,&op)>0)){
ret=opus_head_parse(_head,op.packet,op.bytes); 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. /*Found a valid Opus header.
Continue setup.*/ 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. /*Get the next page.
No need to clamp the boundary offset against _of->end, as all errors 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, if(OP_UNLIKELY(op_get_next_page(_of,_og,
OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){ OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){
return OP_ENOTFORMAT; return _of->ready_state<OP_STREAMSET?OP_ENOTFORMAT:OP_EBADHEADER;
}
/*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;
} }
} }
if(OP_UNLIKELY(_of->ready_state!=OP_STREAMSET))return OP_ENOTFORMAT; 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.*/ /*Loop getting packets.*/
for(;;){ for(;;){
switch(ogg_stream_packetout(&_of->os,&op)){ 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, static int op_find_initial_pcm_offset(OggOpusFile *_of,
OggOpusLink *_link,ogg_page *_og){ OggOpusLink *_link,ogg_page *_og){
ogg_page og; ogg_page og;
opus_int64 page_offset;
ogg_int64_t pcm_start; ogg_int64_t pcm_start;
ogg_int64_t prev_packet_gp; ogg_int64_t prev_packet_gp;
ogg_int64_t cur_page_gp; ogg_int64_t cur_page_gp;
@ -847,13 +846,12 @@ static int op_find_initial_pcm_offset(OggOpusFile *_of,
least once.*/ least once.*/
total_duration=0; total_duration=0;
do{ do{
opus_int64 llret; page_offset=op_get_next_page(_of,_og,_of->end);
llret=op_get_next_page(_of,_og,_of->end);
/*We should get a page unless the file is truncated or mangled. /*We should get a page unless the file is truncated or mangled.
Otherwise there are no audio data packets in the whole logical stream.*/ 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.*/ /*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 /*Fail if the pre-skip is non-zero, since it's asking us to skip more
samples than exist.*/ samples than exist.*/
if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP; 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->op_count=pi;
_of->cur_discard_count=_link->head.pre_skip; _of->cur_discard_count=_link->head.pre_skip;
_of->prev_packet_gp=_link->pcm_start=pcm_start; _of->prev_packet_gp=_link->pcm_start=pcm_start;
_of->prev_page_offset=page_offset;
return 0; return 0;
} }
@ -1128,7 +1127,7 @@ static int op_bisect_forward_serialno(OggOpusFile *_of,
opus_int64 bisect; opus_int64 bisect;
opus_int64 next; opus_int64 next;
opus_int64 last; opus_int64 last;
ogg_int64_t end_offset=0; ogg_int64_t end_offset;
ogg_int64_t end_gp; ogg_int64_t end_gp;
int sri; int sri;
serialnos=*_serialnos; 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 track gain must lie in the range [-32768,32767], and the user-supplied
offset has been pre-clamped to [-98302,98303].*/ offset has been pre-clamped to [-98302,98303].*/
switch(_of->gain_type){ 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:{ case OP_TRACK_GAIN:{
int track_gain_q8; int track_gain_q8;
track_gain_q8=0; track_gain_q8=0;
opus_tags_get_track_gain(&_of->links[li].tags,&track_gain_q8); opus_tags_get_track_gain(&_of->links[li].tags,&track_gain_q8);
gain_q8+=track_gain_q8; gain_q8+=track_gain_q8;
} gain_q8+=head->output_gain;
/*Fall through.*/ }break;
case OP_HEADER_GAIN:gain_q8+=head->output_gain;break; case OP_HEADER_GAIN:gain_q8+=head->output_gain;break;
case OP_ABSOLUTE_GAIN:break; case OP_ABSOLUTE_GAIN:break;
default:OP_ASSERT(0); default:OP_ASSERT(0);
@ -1407,6 +1413,7 @@ static int op_open_seekable2(OggOpusFile *_of){
ogg_sync_state oy_start; ogg_sync_state oy_start;
ogg_stream_state os_start; ogg_stream_state os_start;
ogg_packet *op_start; ogg_packet *op_start;
opus_int64 prev_page_offset;
opus_int64 start_offset; opus_int64 start_offset;
int start_op_count; int start_op_count;
int ret; int ret;
@ -1426,6 +1433,7 @@ static int op_open_seekable2(OggOpusFile *_of){
if(op_start==NULL)return OP_EFAULT; if(op_start==NULL)return OP_EFAULT;
*&oy_start=_of->oy; *&oy_start=_of->oy;
*&os_start=_of->os; *&os_start=_of->os;
prev_page_offset=_of->prev_page_offset;
start_offset=_of->offset; start_offset=_of->offset;
memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count); memcpy(op_start,_of->op,sizeof(*op_start)*start_op_count);
OP_ASSERT((*_of->callbacks.tell)(_of->source)==op_position(_of)); 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); memcpy(_of->op,op_start,sizeof(*_of->op)*start_op_count);
_ogg_free(op_start); _ogg_free(op_start);
_of->prev_packet_gp=_of->links[0].pcm_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; _of->cur_discard_count=_of->links[0].head.pre_skip;
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
/*And restore the position indicator.*/ /*And restore the position indicator.*/
@ -1456,6 +1465,7 @@ static void op_decode_clear(OggOpusFile *_of){
_of->op_count=0; _of->op_count=0;
_of->od_buffer_size=0; _of->od_buffer_size=0;
_of->prev_packet_gp=-1; _of->prev_packet_gp=-1;
_of->prev_page_offset=-1;
if(!_of->seekable){ if(!_of->seekable){
OP_ASSERT(_of->ready_state>=OP_INITSET); OP_ASSERT(_of->ready_state>=OP_INITSET);
opus_tags_clear(&_of->links[0].tags); 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 This handles the case where we're at a bitstream boundary and dumps the
decoding machine. decoding machine.
If the decoding machine is unloaded, it loads it. 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 It also keeps prev_packet_gp up to date (seek and read both use this).
uses a special hack with _readp).
Return: <0) Error, OP_HOLE (lost packet), or OP_EOF. Return: <0) Error, OP_HOLE (lost packet), or OP_EOF.
0) Need more data (only if _readp==0). 0) Got at least one audio data packet.*/
1) Got at least one audio data packet.*/
static int op_fetch_and_process_page(OggOpusFile *_of, 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; OggOpusLink *links;
ogg_uint32_t cur_serialno; ogg_uint32_t cur_serialno;
int seekable; int seekable;
@ -1828,7 +1836,6 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
int ret; int ret;
/*We shouldn't get here if we have unprocessed packets.*/ /*We shouldn't get here if we have unprocessed packets.*/
OP_ASSERT(_of->ready_state<OP_INITSET||_of->op_pos>=_of->op_count); OP_ASSERT(_of->ready_state<OP_INITSET||_of->op_pos>=_of->op_count);
if(!_readp)return 0;
seekable=_of->seekable; seekable=_of->seekable;
links=_of->links; links=_of->links;
cur_link=seekable?_of->cur_link:0; cur_link=seekable?_of->cur_link:0;
@ -1837,36 +1844,27 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
for(;;){ for(;;){
ogg_page og; ogg_page og;
OP_ASSERT(_of->ready_state>=OP_OPENED); OP_ASSERT(_of->ready_state>=OP_OPENED);
/*This loop is not strictly necessary, but there's no sense in doing the /*If we were given a page to use, use it.*/
extra checks of the larger loop for the common case in a multiplexed if(_og!=NULL){
bistream where the page is simply part of a different logical *&og=*_og;
bitstream.*/ _og=NULL;
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;
} }
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_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);
}
/*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? /*Do we need to load a new machine before submitting the page?
This is different in the seekable and non-seekable cases. This is different in the seekable and non-seekable cases.
In the seekable case, we already have all the header information loaded 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; _of->ready_state=OP_STREAMSET;
/*If we're at the start of this link, initialize the granule position /*If we're at the start of this link, initialize the granule position
and pre-skip tracking.*/ 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_packet_gp=links[cur_link].pcm_start;
_of->prev_page_offset=-1;
_of->cur_discard_count=links[cur_link].head.pre_skip; _of->cur_discard_count=links[cur_link].head.pre_skip;
/*Ignore a hole at the start of a new link (this is common for /*Ignore a hole at the start of a new link (this is common for
streams joined in the middle) or after seeking.*/ 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(), /*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).*/ keep going (this is possible if end-trimming trimmed them all).*/
if(_of->op_count<=0)continue; 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); ret=op_make_decode_ready(_of);
if(OP_UNLIKELY(ret<0))return ret; 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. /*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) op_granpos_add(&prev_packet_gp,prev_packet_gp,total_duration)
should succeed and give prev_packet_gp==cur_page_gp. 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 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 The only thing we guarantee is that the start and end granule
positions of the packets are valid, and that they are monotonic positions of the packets are valid, and that they are monotonic
within a page. within a page.
@ -2073,9 +2075,10 @@ static int op_fetch_and_process_page(OggOpusFile *_of,
OP_ASSERT(total_duration==0); OP_ASSERT(total_duration==0);
} }
_of->prev_packet_gp=prev_packet_gp; _of->prev_packet_gp=prev_packet_gp;
_of->prev_page_offset=_page_offset;
_of->op_count=pi; _of->op_count=pi;
/*If end-trimming didn't trim all the packets, we're done.*/ /*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; _of->samples_tracked=0;
ret=op_seek_helper(_of,_pos); ret=op_seek_helper(_of,_pos);
if(OP_UNLIKELY(ret<0))return OP_EREAD; 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. /*If we hit EOF, op_fetch_and_process_page() leaves us uninitialized.
Instead, jump to the end.*/ Instead, jump to the end.*/
if(ret==OP_EOF){ if(ret==OP_EOF){
@ -2105,7 +2108,6 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _pos){
_of->cur_discard_count=0; _of->cur_discard_count=0;
ret=0; ret=0;
} }
else if(ret>0)ret=0;
return ret; return ret;
} }
@ -2146,6 +2148,27 @@ static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
return -1; 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 /*This controls how close the target has to be to use the current stream
position to subdivide the initial range. position to subdivide the initial range.
Two minutes seems to be a good default.*/ 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_start;
ogg_int64_t pcm_end; ogg_int64_t pcm_end;
ogg_int64_t best_gp; ogg_int64_t best_gp;
ogg_int64_t diff=0; ogg_int64_t diff;
ogg_uint32_t serialno; ogg_uint32_t serialno;
opus_int32 pre_skip; opus_int32 pre_skip;
opus_int64 begin; opus_int64 begin;
opus_int64 end; opus_int64 end;
opus_int64 boundary; opus_int64 boundary;
opus_int64 best; opus_int64 best;
opus_int64 best_start;
opus_int64 page_offset; opus_int64 page_offset;
opus_int64 d0; opus_int64 d0;
opus_int64 d1; opus_int64 d1;
opus_int64 d2; opus_int64 d2;
int force_bisect; int force_bisect;
int buffering;
int ret; int ret;
_of->bytes_tracked=0; _of->bytes_tracked=0;
_of->samples_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; best_gp=pcm_start=link->pcm_start;
pcm_end=link->pcm_end; pcm_end=link->pcm_end;
serialno=link->serialno; serialno=link->serialno;
best=begin=link->data_offset; best=best_start=begin=link->data_offset;
page_offset=-1; page_offset=-1;
buffering=0;
/*We discard the first 80 ms of data after a seek, so seek back that much /*We discard the first 80 ms of data after a seek, so seek back that much
farther. farther.
If we can't, simply seek to the beginning of the link.*/ 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){ if(offset-begin>=end-begin>>1||diff>-OP_CUR_TIME_THRESH){
best=begin=offset; best=begin=offset;
best_gp=pcm_start=gp; 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{ else{
@ -2247,13 +2285,14 @@ static int op_pcm_seek_page(OggOpusFile *_of,
generally 1 second or less), we can loop them continuously generally 1 second or less), we can loop them continuously
without seeking at all.*/ without seeking at all.*/
OP_ALWAYS_TRUE(!op_granpos_add(&prev_page_gp,_of->op[0].granulepos, 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){ if(op_granpos_cmp(prev_page_gp,_target_gp)<=0){
/*Don't call op_decode_clear(), because it will dump our /*Don't call op_decode_clear(), because it will dump our
packets.*/ packets.*/
_of->op_pos=0; _of->op_pos=0;
_of->od_buffer_size=0; _of->od_buffer_size=0;
_of->prev_packet_gp=prev_page_gp; _of->prev_packet_gp=prev_page_gp;
/*_of->prev_page_offset already points to the right place.*/
_of->ready_state=OP_STREAMSET; _of->ready_state=OP_STREAMSET;
return op_make_decode_ready(_of); return op_make_decode_ready(_of);
} }
@ -2274,6 +2313,9 @@ static int op_pcm_seek_page(OggOpusFile *_of,
Vinen)" from libvorbisfile. Vinen)" from libvorbisfile.
It has been modified substantially since.*/ It has been modified substantially since.*/
op_decode_clear(_of); 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.*/ /*Initialize the interval size history.*/
d2=d1=d0=end-begin; d2=d1=d0=end-begin;
force_bisect=0; force_bisect=0;
@ -2289,7 +2331,7 @@ static int op_pcm_seek_page(OggOpusFile *_of,
d2=end-begin>>1; d2=end-begin>>1;
if(force_bisect)bisect=begin+(end-begin>>1); if(force_bisect)bisect=begin+(end-begin>>1);
else{ 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(&diff,_target_gp,pcm_start));
OP_ALWAYS_TRUE(!op_granpos_diff(&diff2,pcm_end,pcm_start)); OP_ALWAYS_TRUE(!op_granpos_diff(&diff2,pcm_end,pcm_start));
/*Take a (pretty decent) guess.*/ /*Take a (pretty decent) guess.*/
@ -2299,12 +2341,23 @@ static int op_pcm_seek_page(OggOpusFile *_of,
force_bisect=0; force_bisect=0;
} }
if(bisect!=_of->offset){ if(bisect!=_of->offset){
/*Discard any buffered continued packet data.*/
if(buffering)ogg_stream_reset(&_of->os);
buffering=0;
page_offset=-1; page_offset=-1;
ret=op_seek_helper(_of,bisect); ret=op_seek_helper(_of,bisect);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
} }
chunk_size=OP_CHUNK_SIZE; chunk_size=OP_CHUNK_SIZE;
next_boundary=boundary; 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){ while(begin<end){
page_offset=op_get_next_page(_of,&og,boundary); page_offset=op_get_next_page(_of,&og,boundary);
if(page_offset<0){ 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 we scanned the whole interval, we're done.*/
if(bisect<=begin+1)end=begin; if(bisect<=begin+1)end=begin;
else{ 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); bisect=OP_MAX(bisect-chunk_size,begin);
ret=op_seek_helper(_of,bisect); ret=op_seek_helper(_of,bisect);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
@ -2327,12 +2383,32 @@ static int op_pcm_seek_page(OggOpusFile *_of,
} }
else{ else{
ogg_int64_t gp; ogg_int64_t gp;
int has_packets;
/*Save the offset of the first page we found after the seek, regardless /*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.*/ of the stream it came from or whether or not it has a timestamp.*/
next_boundary=OP_MIN(page_offset,next_boundary); next_boundary=OP_MIN(page_offset,next_boundary);
if(serialno!=(ogg_uint32_t)ogg_page_serialno(&og))continue; if(serialno!=(ogg_uint32_t)ogg_page_serialno(&og))continue;
gp=ogg_page_granulepos(&og); has_packets=ogg_page_packets(&og)>0;
if(gp==-1)continue; /*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){ if(op_granpos_cmp(gp,_target_gp)<0){
/*We found a page that ends before our target. /*We found a page that ends before our target.
Advance to the raw offset of the next page.*/ 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 /*Save the byte offset of the end of the page with this granule
position.*/ 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; best_gp=pcm_start=gp;
OP_ALWAYS_TRUE(!op_granpos_diff(&diff,_target_gp,pcm_start)); 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 /*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. /*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;
}
OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0); OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0);
_of->cur_link=_li; /*Seek, if necessary.
_of->ready_state=OP_STREAMSET; 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; _of->prev_packet_gp=best_gp;
ogg_stream_reset_serialno(&_of->os,serialno); _of->prev_page_offset=best_start;
ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,1,0,1); ret=op_fetch_and_process_page(_of,page_offset<0?NULL:&og,page_offset,0,1);
if(OP_UNLIKELY(ret<=0))return OP_EBADLINK; if(OP_UNLIKELY(ret<0))return OP_EBADLINK;
/*Verify result.*/ /*Verify result.*/
if(OP_UNLIKELY(op_granpos_cmp(_of->prev_packet_gp,_target_gp)>0)){ if(OP_UNLIKELY(op_granpos_cmp(_of->prev_packet_gp,_target_gp)>0)){
return OP_EBADLINK; return OP_EBADLINK;
} }
/*Our caller will set cur_discard_count to handle pre-roll.*/
return 0; return 0;
} }
@ -2475,8 +2578,8 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset){
if(op_pos<op_count)break; if(op_pos<op_count)break;
/*We skipped all the packets on this page. /*We skipped all the packets on this page.
Fetch another.*/ Fetch another.*/
ret=op_fetch_and_process_page(_of,NULL,-1,1,0,1); ret=op_fetch_and_process_page(_of,NULL,-1,0,1);
if(OP_UNLIKELY(ret<=0))return OP_EBADLINK; if(OP_UNLIKELY(ret<0))return OP_EBADLINK;
} }
OP_ALWAYS_TRUE(!op_granpos_diff(&diff,prev_packet_gp,pcm_start)); OP_ALWAYS_TRUE(!op_granpos_diff(&diff,prev_packet_gp,pcm_start));
/*We skipped too far. /*We skipped too far.
@ -2559,8 +2662,8 @@ void op_set_decode_callback(OggOpusFile *_of,
int op_set_gain_offset(OggOpusFile *_of, int op_set_gain_offset(OggOpusFile *_of,
int _gain_type,opus_int32 _gain_offset_q8){ int _gain_type,opus_int32 _gain_offset_q8){
if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_TRACK_GAIN if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_ALBUM_GAIN
&&_gain_type!=OP_ABSOLUTE_GAIN){ &&_gain_type!=OP_TRACK_GAIN&&_gain_type!=OP_ABSOLUTE_GAIN){
return OP_EINVAL; return OP_EINVAL;
} }
_of->gain_type=_gain_type; _of->gain_type=_gain_type;
@ -2740,7 +2843,7 @@ static int op_read_native(OggOpusFile *_of,
} }
} }
/*Suck in another page.*/ /*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(OP_UNLIKELY(ret==OP_EOF)){
if(_li!=NULL)*_li=_of->cur_link; if(_li!=NULL)*_li=_of->cur_link;
return 0; return 0;