summaryrefslogtreecommitdiff
path: root/thirdparty/opus/opusfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'thirdparty/opus/opusfile.c')
-rw-r--r--thirdparty/opus/opusfile.c3266
1 files changed, 0 insertions, 3266 deletions
diff --git a/thirdparty/opus/opusfile.c b/thirdparty/opus/opusfile.c
deleted file mode 100644
index b8b3a354cf..0000000000
--- a/thirdparty/opus/opusfile.c
+++ /dev/null
@@ -1,3266 +0,0 @@
-/********************************************************************
- * *
- * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. *
- * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
- * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
- * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
- * *
- * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 *
- * by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
- * *
- ********************************************************************
-
- function: stdio-based convenience library for opening/seeking/decoding
- last mod: $Id: vorbisfile.c 17573 2010-10-27 14:53:59Z xiphmont $
-
- ********************************************************************/
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "internal.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <limits.h>
-#include <string.h>
-#include <math.h>
-
-#include "opusfile.h"
-
-/*This implementation is largely based off of libvorbisfile.
- All of the Ogg bits work roughly the same, though I have made some
- "improvements" that have not been folded back there, yet.*/
-
-/*A 'chained bitstream' is an Ogg Opus bitstream that contains more than one
- logical bitstream arranged end to end (the only form of Ogg multiplexing
- supported by this library.
- Grouping (parallel multiplexing) is not supported, except to the extent that
- if there are multiple logical Ogg streams in a single link of the chain, we
- will ignore all but the first Opus stream we find.*/
-
-/*An Ogg Opus file can be played beginning to end (streamed) without worrying
- ahead of time about chaining (see opusdec from the opus-tools package).
- If we have the whole file, however, and want random access
- (seeking/scrubbing) or desire to know the total length/time of a file, we
- need to account for the possibility of chaining.*/
-
-/*We can handle things a number of ways.
- We can determine the entire bitstream structure right off the bat, or find
- pieces on demand.
- This library determines and caches structure for the entire bitstream, but
- builds a virtual decoder on the fly when moving between links in the chain.*/
-
-/*There are also different ways to implement seeking.
- Enough information exists in an Ogg bitstream to seek to sample-granularity
- positions in the output.
- Or, one can seek by picking some portion of the stream roughly in the desired
- area if we only want coarse navigation through the stream.
- We implement and expose both strategies.*/
-
-/*The maximum number of bytes in a page (including the page headers).*/
-#define OP_PAGE_SIZE_MAX (65307)
-/*The default amount to seek backwards per step when trying to find the
- previous page.
- This must be at least as large as the maximum size of a page.*/
-#define OP_CHUNK_SIZE (65536)
-/*The maximum amount to seek backwards per step when trying to find the
- previous page.*/
-#define OP_CHUNK_SIZE_MAX (1024*(opus_int32)1024)
-/*A smaller read size is needed for low-rate streaming.*/
-#define OP_READ_SIZE (2048)
-
-int op_test(OpusHead *_head,
- const unsigned char *_initial_data,size_t _initial_bytes){
- ogg_sync_state oy;
- char *data;
- int err;
- /*The first page of a normal Opus file will be at most 57 bytes (27 Ogg
- page header bytes + 1 lacing value + 21 Opus header bytes + 8 channel
- mapping bytes).
- It will be at least 47 bytes (27 Ogg page header bytes + 1 lacing value +
- 19 Opus header bytes using channel mapping family 0).
- If we don't have at least that much data, give up now.*/
- if(_initial_bytes<47)return OP_FALSE;
- /*Only proceed if we start with the magic OggS string.
- This is to prevent us spending a lot of time allocating memory and looking
- for Ogg pages in non-Ogg files.*/
- if(memcmp(_initial_data,"OggS",4)!=0)return OP_ENOTFORMAT;
- ogg_sync_init(&oy);
- data=ogg_sync_buffer(&oy,_initial_bytes);
- if(data!=NULL){
- ogg_stream_state os;
- ogg_page og;
- int ret;
- memcpy(data,_initial_data,_initial_bytes);
- ogg_sync_wrote(&oy,_initial_bytes);
- ogg_stream_init(&os,-1);
- err=OP_FALSE;
- do{
- ogg_packet op;
- ret=ogg_sync_pageout(&oy,&og);
- /*Ignore holes.*/
- if(ret<0)continue;
- /*Stop if we run out of data.*/
- if(!ret)break;
- ogg_stream_reset_serialno(&os,ogg_page_serialno(&og));
- ogg_stream_pagein(&os,&og);
- /*Only process the first packet on this page (if it's a BOS packet,
- it's required to be the only one).*/
- if(ogg_stream_packetout(&os,&op)==1){
- if(op.b_o_s){
- ret=opus_head_parse(_head,op.packet,op.bytes);
- /*If this didn't look like Opus, keep going.*/
- if(ret==OP_ENOTFORMAT)continue;
- /*Otherwise we're done, one way or another.*/
- err=ret;
- }
- /*We finished parsing the headers.
- There is no Opus to be found.*/
- else err=OP_ENOTFORMAT;
- }
- }
- while(err==OP_FALSE);
- ogg_stream_clear(&os);
- }
- else err=OP_EFAULT;
- ogg_sync_clear(&oy);
- return err;
-}
-
-/*Many, many internal helpers.
- The intention is not to be confusing.
- Rampant duplication and monolithic function implementation (though we do have
- some large, omnibus functions still) would be harder to understand anyway.
- The high level functions are last.
- Begin grokking near the end of the file if you prefer to read things
- top-down.*/
-
-/*The read/seek functions track absolute position within the stream.*/
-
-/*Read a little more data from the file/pipe into the ogg_sync framer.
- _nbytes: The maximum number of bytes to read.
- Return: A positive number of bytes read on success, 0 on end-of-file, or a
- negative value on failure.*/
-static int op_get_data(OggOpusFile *_of,int _nbytes){
- unsigned char *buffer;
- int nbytes;
- OP_ASSERT(_nbytes>0);
- buffer=(unsigned char *)ogg_sync_buffer(&_of->oy,_nbytes);
- nbytes=(int)(*_of->callbacks.read)(_of->source,buffer,_nbytes);
- OP_ASSERT(nbytes<=_nbytes);
- if(OP_LIKELY(nbytes>0))ogg_sync_wrote(&_of->oy,nbytes);
- return 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)){
- return OP_EREAD;
- }
- _of->offset=_offset;
- ogg_sync_reset(&_of->oy);
- return 0;
-}
-
-/*Get the current position indicator of the underlying source.
- This should be the same as the value reported by tell().*/
-static opus_int64 op_position(const OggOpusFile *_of){
- /*The current position indicator is _not_ simply offset.
- We may also have unprocessed, buffered data in the sync state.*/
- return _of->offset+_of->oy.fill-_of->oy.returned;
-}
-
-/*From the head of the stream, get the next page.
- _boundary specifies if the function is allowed to fetch more data from the
- stream (and how much) or only use internally buffered data.
- _boundary: -1: Unbounded search.
- 0: Read no additional data.
- Use only cached data.
- n: Search for the start of a new page up to file position n.
- Return: n>=0: Found a page at absolute offset n.
- OP_FALSE: Hit the _boundary limit.
- OP_EREAD: An underlying read operation failed.
- OP_BADLINK: We hit end-of-file before reaching _boundary.*/
-static opus_int64 op_get_next_page(OggOpusFile *_of,ogg_page *_og,
- opus_int64 _boundary){
- while(_boundary<=0||_of->offset<_boundary){
- int more;
- more=ogg_sync_pageseek(&_of->oy,_og);
- /*Skipped (-more) bytes.*/
- if(OP_UNLIKELY(more<0))_of->offset-=more;
- else if(more==0){
- int read_nbytes;
- int ret;
- /*Send more paramedics.*/
- if(!_boundary)return OP_FALSE;
- if(_boundary<0)read_nbytes=OP_READ_SIZE;
- else{
- opus_int64 position;
- position=op_position(_of);
- if(position>=_boundary)return OP_FALSE;
- read_nbytes=(int)OP_MIN(_boundary-position,OP_READ_SIZE);
- }
- ret=op_get_data(_of,read_nbytes);
- if(OP_UNLIKELY(ret<0))return OP_EREAD;
- if(OP_UNLIKELY(ret==0)){
- /*Only fail cleanly on EOF if we didn't have a known boundary.
- Otherwise, we should have been able to reach that boundary, and this
- is a fatal error.*/
- return OP_UNLIKELY(_boundary<0)?OP_FALSE:OP_EBADLINK;
- }
- }
- else{
- /*Got a page.
- Return the page start offset and advance the internal offset past the
- page end.*/
- opus_int64 page_offset;
- page_offset=_of->offset;
- _of->offset+=more;
- OP_ASSERT(page_offset>=0);
- return page_offset;
- }
- }
- return OP_FALSE;
-}
-
-static int op_add_serialno(const ogg_page *_og,
- ogg_uint32_t **_serialnos,int *_nserialnos,int *_cserialnos){
- ogg_uint32_t *serialnos;
- int nserialnos;
- int cserialnos;
- ogg_uint32_t s;
- s=ogg_page_serialno(_og);
- serialnos=*_serialnos;
- nserialnos=*_nserialnos;
- cserialnos=*_cserialnos;
- if(OP_UNLIKELY(nserialnos>=cserialnos)){
- if(OP_UNLIKELY(cserialnos>INT_MAX/(int)sizeof(*serialnos)-1>>1)){
- return OP_EFAULT;
- }
- cserialnos=2*cserialnos+1;
- OP_ASSERT(nserialnos<cserialnos);
- serialnos=(ogg_uint32_t *)_ogg_realloc(serialnos,
- sizeof(*serialnos)*cserialnos);
- if(OP_UNLIKELY(serialnos==NULL))return OP_EFAULT;
- }
- serialnos[nserialnos++]=s;
- *_serialnos=serialnos;
- *_nserialnos=nserialnos;
- *_cserialnos=cserialnos;
- return 0;
-}
-
-/*Returns nonzero if found.*/
-static int op_lookup_serialno(ogg_uint32_t _s,
- const ogg_uint32_t *_serialnos,int _nserialnos){
- int i;
- for(i=0;i<_nserialnos&&_serialnos[i]!=_s;i++);
- return i<_nserialnos;
-}
-
-static int op_lookup_page_serialno(const ogg_page *_og,
- const ogg_uint32_t *_serialnos,int _nserialnos){
- return op_lookup_serialno(ogg_page_serialno(_og),_serialnos,_nserialnos);
-}
-
-typedef struct OpusSeekRecord OpusSeekRecord;
-
-/*We use this to remember the pages we found while enumerating the links of a
- chained stream.
- We keep track of the starting and ending offsets, as well as the point we
- started searching from, so we know where to bisect.
- We also keep the serial number, so we can tell if the page belonged to the
- current link or not, as well as the granule position, to aid in estimating
- the start of the link.*/
-struct OpusSeekRecord{
- /*The earliest byte we know of such that reading forward from it causes
- capture to be regained at this page.*/
- opus_int64 search_start;
- /*The offset of this page.*/
- opus_int64 offset;
- /*The size of this page.*/
- opus_int32 size;
- /*The serial number of this page.*/
- ogg_uint32_t serialno;
- /*The granule position of this page.*/
- ogg_int64_t gp;
-};
-
-/*Find the last page beginning before _offset with a valid granule position.
- There is no '_boundary' parameter as it will always have to read more data.
- This is much dirtier than the above, as Ogg doesn't have any backward search
- linkage.
- This search prefers pages of the specified serial number.
- If a page of the specified serial number is spotted during the
- seek-back-and-read-forward, it will return the info of last page of the
- matching serial number, instead of the very last page, unless the very last
- page belongs to a different link than preferred serial number.
- If no page of the specified serial number is seen, it will return the info of
- the last page.
- [out] _sr: Returns information about the page that was found on success.
- _offset: The _offset before which to find a page.
- Any page returned will consist of data entirely before _offset.
- _serialno: The preferred serial number.
- If a page with this serial number is found, it will be returned
- even if another page in the same link is found closer to
- _offset.
- This is purely opportunistic: there is no guarantee such a page
- will be found if it exists.
- _serialnos: The list of serial numbers in the link that contains the
- preferred serial number.
- _nserialnos: The number of serial numbers in the current link.
- Return: 0 on success, or a negative value on failure.
- OP_EREAD: Failed to read more data (error or EOF).
- OP_EBADLINK: We couldn't find a page even after seeking back to the
- start of the stream.*/
-static int op_get_prev_page_serial(OggOpusFile *_of,OpusSeekRecord *_sr,
- opus_int64 _offset,ogg_uint32_t _serialno,
- const ogg_uint32_t *_serialnos,int _nserialnos){
- OpusSeekRecord preferred_sr;
- ogg_page og;
- opus_int64 begin;
- opus_int64 end;
- opus_int64 original_end;
- opus_int32 chunk_size;
- int preferred_found;
- original_end=end=begin=_offset;
- preferred_found=0;
- _offset=-1;
- chunk_size=OP_CHUNK_SIZE;
- do{
- opus_int64 search_start;
- int ret;
- OP_ASSERT(chunk_size>=OP_PAGE_SIZE_MAX);
- begin=OP_MAX(begin-chunk_size,0);
- ret=op_seek_helper(_of,begin);
- if(OP_UNLIKELY(ret<0))return ret;
- search_start=begin;
- while(_of->offset<end){
- opus_int64 llret;
- ogg_uint32_t serialno;
- llret=op_get_next_page(_of,&og,end);
- if(OP_UNLIKELY(llret<OP_FALSE))return (int)llret;
- else if(llret==OP_FALSE)break;
- serialno=ogg_page_serialno(&og);
- /*Save the information for this page.
- We're not interested in the page itself... just the serial number, byte
- offset, page size, and granule position.*/
- _sr->search_start=search_start;
- _sr->offset=_offset=llret;
- _sr->serialno=serialno;
- OP_ASSERT(_of->offset-_offset>=0);
- OP_ASSERT(_of->offset-_offset<=OP_PAGE_SIZE_MAX);
- _sr->size=(opus_int32)(_of->offset-_offset);
- _sr->gp=ogg_page_granulepos(&og);
- /*If this page is from the stream we're looking for, remember it.*/
- if(serialno==_serialno){
- preferred_found=1;
- *&preferred_sr=*_sr;
- }
- if(!op_lookup_serialno(serialno,_serialnos,_nserialnos)){
- /*We fell off the end of the link, which means we seeked back too far
- and shouldn't have been looking in that link to begin with.
- If we found the preferred serial number, forget that we saw it.*/
- preferred_found=0;
- }
- search_start=llret+1;
- }
- /*We started from the beginning of the stream and found nothing.
- This should be impossible unless the contents of the source changed out
- from under us after we read from it.*/
- if(OP_UNLIKELY(!begin)&&OP_UNLIKELY(_offset<0))return OP_EBADLINK;
- /*Bump up the chunk size.
- This is mildly helpful when seeks are very expensive (http).*/
- chunk_size=OP_MIN(2*chunk_size,OP_CHUNK_SIZE_MAX);
- /*Avoid quadratic complexity if we hit an invalid patch of the file.*/
- end=OP_MIN(begin+OP_PAGE_SIZE_MAX-1,original_end);
- }
- while(_offset<0);
- if(preferred_found)*_sr=*&preferred_sr;
- return 0;
-}
-
-/*Find the last page beginning before _offset with the given serial number and
- a valid granule position.
- Unlike the above search, this continues until it finds such a page, but does
- not stray outside the current link.
- We could implement it (inefficiently) by calling op_get_prev_page_serial()
- repeatedly until it returned a page that had both our preferred serial
- number and a valid granule position, but doing it with a separate function
- allows us to avoid repeatedly re-scanning valid pages from other streams as
- we seek-back-and-read-forward.
- [out] _gp: Returns the granule position of the page that was found on
- success.
- _offset: The _offset before which to find a page.
- Any page returned will consist of data entirely before _offset.
- _serialno: The target serial number.
- _serialnos: The list of serial numbers in the link that contains the
- preferred serial number.
- _nserialnos: The number of serial numbers in the current link.
- Return: The offset of the page on success, or a negative value on failure.
- OP_EREAD: Failed to read more data (error or EOF).
- OP_EBADLINK: We couldn't find a page even after seeking back past the
- beginning of the link.*/
-static opus_int64 op_get_last_page(OggOpusFile *_of,ogg_int64_t *_gp,
- opus_int64 _offset,ogg_uint32_t _serialno,
- const ogg_uint32_t *_serialnos,int _nserialnos){
- ogg_page og;
- ogg_int64_t gp;
- opus_int64 begin;
- opus_int64 end;
- opus_int64 original_end;
- opus_int32 chunk_size;
- /*The target serial number must belong to the current link.*/
- OP_ASSERT(op_lookup_serialno(_serialno,_serialnos,_nserialnos));
- original_end=end=begin=_offset;
- _offset=-1;
- /*We shouldn't have to initialize gp, but gcc is too dumb to figure out that
- ret>=0 implies we entered the if(page_gp!=-1) block at least once.*/
- gp=-1;
- chunk_size=OP_CHUNK_SIZE;
- do{
- int left_link;
- int ret;
- OP_ASSERT(chunk_size>=OP_PAGE_SIZE_MAX);
- begin=OP_MAX(begin-chunk_size,0);
- ret=op_seek_helper(_of,begin);
- if(OP_UNLIKELY(ret<0))return ret;
- left_link=0;
- while(_of->offset<end){
- opus_int64 llret;
- ogg_uint32_t serialno;
- llret=op_get_next_page(_of,&og,end);
- if(OP_UNLIKELY(llret<OP_FALSE))return llret;
- else if(llret==OP_FALSE)break;
- serialno=ogg_page_serialno(&og);
- if(serialno==_serialno){
- ogg_int64_t page_gp;
- /*The page is from the right stream...*/
- page_gp=ogg_page_granulepos(&og);
- if(page_gp!=-1){
- /*And has a valid granule position.
- Let's remember it.*/
- _offset=llret;
- gp=page_gp;
- }
- }
- else if(OP_UNLIKELY(!op_lookup_serialno(serialno,
- _serialnos,_nserialnos))){
- /*We fell off the start of the link, which means we don't need to keep
- seeking any farther back.*/
- left_link=1;
- }
- }
- /*We started from at or before the beginning of the link and found nothing.
- This should be impossible unless the contents of the source changed out
- from under us after we read from it.*/
- if((OP_UNLIKELY(left_link)||OP_UNLIKELY(!begin))&&OP_UNLIKELY(_offset<0)){
- return OP_EBADLINK;
- }
- /*Bump up the chunk size.
- This is mildly helpful when seeks are very expensive (http).*/
- chunk_size=OP_MIN(2*chunk_size,OP_CHUNK_SIZE_MAX);
- /*Avoid quadratic complexity if we hit an invalid patch of the file.*/
- end=OP_MIN(begin+OP_PAGE_SIZE_MAX-1,original_end);
- }
- while(_offset<0);
- *_gp=gp;
- return _offset;
-}
-
-/*Uses the local ogg_stream storage in _of.
- This is important for non-streaming input sources.*/
-static int op_fetch_headers_impl(OggOpusFile *_of,OpusHead *_head,
- OpusTags *_tags,ogg_uint32_t **_serialnos,int *_nserialnos,
- int *_cserialnos,ogg_page *_og){
- ogg_packet op;
- int ret;
- if(_serialnos!=NULL)*_nserialnos=0;
- /*Extract the serialnos of all BOS pages plus the first set of Opus headers
- we see in the link.*/
- while(ogg_page_bos(_og)){
- if(_serialnos!=NULL){
- if(OP_UNLIKELY(op_lookup_page_serialno(_og,*_serialnos,*_nserialnos))){
- /*A dupe serialnumber in an initial header packet set==invalid stream.*/
- return OP_EBADHEADER;
- }
- ret=op_add_serialno(_og,_serialnos,_nserialnos,_cserialnos);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- if(_of->ready_state<OP_STREAMSET){
- /*We don't have an Opus stream in this link yet, so begin prospective
- stream setup.
- We need a stream to get packets.*/
- ogg_stream_reset_serialno(&_of->os,ogg_page_serialno(_og));
- ogg_stream_pagein(&_of->os,_og);
- if(OP_LIKELY(ogg_stream_packetout(&_of->os,&op)>0)){
- ret=opus_head_parse(_head,op.packet,op.bytes);
- /*Found a valid Opus header.
- Continue setup.*/
- 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 or OP_EBADHEADER.*/
- if(OP_UNLIKELY(op_get_next_page(_of,_og,
- OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){
- return _of->ready_state<OP_STREAMSET?OP_ENOTFORMAT:OP_EBADHEADER;
- }
- }
- if(OP_UNLIKELY(_of->ready_state!=OP_STREAMSET))return OP_ENOTFORMAT;
- /*If the first non-header page belonged to our Opus stream, submit it.*/
- if(_of->os.serialno==ogg_page_serialno(_og))ogg_stream_pagein(&_of->os,_og);
- /*Loop getting packets.*/
- for(;;){
- switch(ogg_stream_packetout(&_of->os,&op)){
- case 0:{
- /*Loop getting pages.*/
- for(;;){
- /*No need to clamp the boundary offset against _of->end, as all
- errors become OP_EBADHEADER.*/
- if(OP_UNLIKELY(op_get_next_page(_of,_og,
- OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){
- return OP_EBADHEADER;
- }
- /*If this page belongs to the correct stream, go parse it.*/
- if(_of->os.serialno==ogg_page_serialno(_og)){
- ogg_stream_pagein(&_of->os,_og);
- break;
- }
- /*If the link ends before we see the Opus comment header, abort.*/
- if(OP_UNLIKELY(ogg_page_bos(_og)))return OP_EBADHEADER;
- /*Otherwise, keep looking.*/
- }
- }break;
- /*We shouldn't get a hole in the headers!*/
- case -1:return OP_EBADHEADER;
- default:{
- /*Got a packet.
- It should be the comment header.*/
- ret=opus_tags_parse(_tags,op.packet,op.bytes);
- if(OP_UNLIKELY(ret<0))return ret;
- /*Make sure the page terminated at the end of the comment header.
- If there is another packet on the page, or part of a packet, then
- reject the stream.
- Otherwise seekable sources won't be able to seek back to the start
- properly.*/
- ret=ogg_stream_packetout(&_of->os,&op);
- if(OP_UNLIKELY(ret!=0)
- ||OP_UNLIKELY(_og->header[_og->header_len-1]==255)){
- /*If we fail, the caller assumes our tags are uninitialized.*/
- opus_tags_clear(_tags);
- return OP_EBADHEADER;
- }
- return 0;
- }
- }
- }
-}
-
-static int op_fetch_headers(OggOpusFile *_of,OpusHead *_head,
- OpusTags *_tags,ogg_uint32_t **_serialnos,int *_nserialnos,
- int *_cserialnos,ogg_page *_og){
- ogg_page og;
- int ret;
- if(!_og){
- /*No need to clamp the boundary offset against _of->end, as all errors
- become OP_ENOTFORMAT.*/
- if(OP_UNLIKELY(op_get_next_page(_of,&og,
- OP_ADV_OFFSET(_of->offset,OP_CHUNK_SIZE))<0)){
- return OP_ENOTFORMAT;
- }
- _og=&og;
- }
- _of->ready_state=OP_OPENED;
- ret=op_fetch_headers_impl(_of,_head,_tags,_serialnos,_nserialnos,
- _cserialnos,_og);
- /*Revert back from OP_STREAMSET to OP_OPENED on failure, to prevent
- double-free of the tags in an unseekable stream.*/
- if(OP_UNLIKELY(ret<0))_of->ready_state=OP_OPENED;
- return ret;
-}
-
-/*Granule position manipulation routines.
- A granule position is defined to be an unsigned 64-bit integer, with the
- special value -1 in two's complement indicating an unset or invalid granule
- position.
- We are not guaranteed to have an unsigned 64-bit type, so we construct the
- following routines that
- a) Properly order negative numbers as larger than positive numbers, and
- b) Check for underflow or overflow past the special -1 value.
- This lets us operate on the full, valid range of granule positions in a
- consistent and safe manner.
- This full range is organized into distinct regions:
- [ -1 (invalid) ][ 0 ... OP_INT64_MAX ][ OP_INT64_MIN ... -2 ][-1 (invalid) ]
-
- No one should actually use granule positions so large that they're negative,
- even if they are technically valid, as very little software handles them
- correctly (including most of Xiph.Org's).
- This library also refuses to support durations so large they won't fit in a
- signed 64-bit integer (to avoid exposing this mess to the application, and
- to simplify a good deal of internal arithmetic), so the only way to use them
- successfully is if pcm_start is very large.
- This means there isn't anything you can do with negative granule positions
- that you couldn't have done with purely non-negative ones.
- The main purpose of these routines is to allow us to think very explicitly
- about the possible failure cases of all granule position manipulations.*/
-
-/*Safely adds a small signed integer to a valid (not -1) granule position.
- The result can use the full 64-bit range of values (both positive and
- negative), but will fail on overflow (wrapping past -1; wrapping past
- OP_INT64_MAX is explicitly okay).
- [out] _dst_gp: The resulting granule position.
- Only modified on success.
- _src_gp: The granule position to add to.
- This must not be -1.
- _delta: The amount to add.
- This is allowed to be up to 32 bits to support the maximum
- duration of a single Ogg page (255 packets * 120 ms per
- packet == 1,468,800 samples at 48 kHz).
- Return: 0 on success, or OP_EINVAL if the result would wrap around past -1.*/
-static int op_granpos_add(ogg_int64_t *_dst_gp,ogg_int64_t _src_gp,
- opus_int32 _delta){
- /*The code below handles this case correctly, but there's no reason we
- should ever be called with these values, so make sure we aren't.*/
- OP_ASSERT(_src_gp!=-1);
- if(_delta>0){
- /*Adding this amount to the granule position would overflow its 64-bit
- range.*/
- if(OP_UNLIKELY(_src_gp<0)&&OP_UNLIKELY(_src_gp>=-1-_delta))return OP_EINVAL;
- if(OP_UNLIKELY(_src_gp>OP_INT64_MAX-_delta)){
- /*Adding this amount to the granule position would overflow the positive
- half of its 64-bit range.
- Since signed overflow is undefined in C, do it in a way the compiler
- isn't allowed to screw up.*/
- _delta-=(opus_int32)(OP_INT64_MAX-_src_gp)+1;
- _src_gp=OP_INT64_MIN;
- }
- }
- else if(_delta<0){
- /*Subtracting this amount from the granule position would underflow its
- 64-bit range.*/
- if(_src_gp>=0&&OP_UNLIKELY(_src_gp<-_delta))return OP_EINVAL;
- if(OP_UNLIKELY(_src_gp<OP_INT64_MIN-_delta)){
- /*Subtracting this amount from the granule position would underflow the
- negative half of its 64-bit range.
- Since signed underflow is undefined in C, do it in a way the compiler
- isn't allowed to screw up.*/
- _delta+=(opus_int32)(_src_gp-OP_INT64_MIN)+1;
- _src_gp=OP_INT64_MAX;
- }
- }
- *_dst_gp=_src_gp+_delta;
- return 0;
-}
-
-/*Safely computes the difference between two granule positions.
- The difference must fit in a signed 64-bit integer, or the function fails.
- It correctly handles the case where the granule position has wrapped around
- from positive values to negative ones.
- [out] _delta: The difference between the granule positions.
- Only modified on success.
- _gp_a: The granule position to subtract from.
- This must not be -1.
- _gp_b: The granule position to subtract.
- This must not be -1.
- Return: 0 on success, or OP_EINVAL if the result would not fit in a signed
- 64-bit integer.*/
-static int op_granpos_diff(ogg_int64_t *_delta,
- ogg_int64_t _gp_a,ogg_int64_t _gp_b){
- int gp_a_negative;
- int gp_b_negative;
- /*The code below handles these cases correctly, but there's no reason we
- should ever be called with these values, so make sure we aren't.*/
- OP_ASSERT(_gp_a!=-1);
- OP_ASSERT(_gp_b!=-1);
- gp_a_negative=OP_UNLIKELY(_gp_a<0);
- gp_b_negative=OP_UNLIKELY(_gp_b<0);
- if(OP_UNLIKELY(gp_a_negative^gp_b_negative)){
- ogg_int64_t da;
- ogg_int64_t db;
- if(gp_a_negative){
- /*_gp_a has wrapped to a negative value but _gp_b hasn't: the difference
- should be positive.*/
- /*Step 1: Handle wrapping.*/
- /*_gp_a < 0 => da < 0.*/
- da=(OP_INT64_MIN-_gp_a)-1;
- /*_gp_b >= 0 => db >= 0.*/
- db=OP_INT64_MAX-_gp_b;
- /*Step 2: Check for overflow.*/
- if(OP_UNLIKELY(OP_INT64_MAX+da<db))return OP_EINVAL;
- *_delta=db-da;
- }
- else{
- /*_gp_b has wrapped to a negative value but _gp_a hasn't: the difference
- should be negative.*/
- /*Step 1: Handle wrapping.*/
- /*_gp_a >= 0 => da <= 0*/
- da=_gp_a+OP_INT64_MIN;
- /*_gp_b < 0 => db <= 0*/
- db=OP_INT64_MIN-_gp_b;
- /*Step 2: Check for overflow.*/
- if(OP_UNLIKELY(da<OP_INT64_MIN-db))return OP_EINVAL;
- *_delta=da+db;
- }
- }
- else *_delta=_gp_a-_gp_b;
- return 0;
-}
-
-static int op_granpos_cmp(ogg_int64_t _gp_a,ogg_int64_t _gp_b){
- /*The invalid granule position -1 should behave like NaN: neither greater
- than nor less than any other granule position, nor equal to any other
- granule position, including itself.
- However, that means there isn't anything we could sensibly return from this
- function for it.*/
- OP_ASSERT(_gp_a!=-1);
- OP_ASSERT(_gp_b!=-1);
- /*Handle the wrapping cases.*/
- if(OP_UNLIKELY(_gp_a<0)){
- if(_gp_b>=0)return 1;
- /*Else fall through.*/
- }
- else if(OP_UNLIKELY(_gp_b<0))return -1;
- /*No wrapping case.*/
- return (_gp_a>_gp_b)-(_gp_b>_gp_a);
-}
-
-/*Returns the duration of the packet (in samples at 48 kHz), or a negative
- value on error.*/
-static int op_get_packet_duration(const unsigned char *_data,int _len){
- int nframes;
- int frame_size;
- int nsamples;
- nframes=opus_packet_get_nb_frames(_data,_len);
- if(OP_UNLIKELY(nframes<0))return OP_EBADPACKET;
- frame_size=opus_packet_get_samples_per_frame(_data,48000);
- nsamples=nframes*frame_size;
- if(OP_UNLIKELY(nsamples>120*48))return OP_EBADPACKET;
- return nsamples;
-}
-
-/*This function more properly belongs in info.c, but we define it here to allow
- the static granule position manipulation functions to remain static.*/
-ogg_int64_t opus_granule_sample(const OpusHead *_head,ogg_int64_t _gp){
- opus_int32 pre_skip;
- pre_skip=_head->pre_skip;
- if(_gp!=-1&&op_granpos_add(&_gp,_gp,-pre_skip))_gp=-1;
- return _gp;
-}
-
-/*Grab all the packets currently in the stream state, and compute their
- durations.
- _of->op_count is set to the number of packets collected.
- [out] _durations: Returns the durations of the individual packets.
- Return: The total duration of all packets, or OP_HOLE if there was a hole.*/
-static opus_int32 op_collect_audio_packets(OggOpusFile *_of,
- int _durations[255]){
- opus_int32 total_duration;
- int op_count;
- /*Count the durations of all packets in the page.*/
- op_count=0;
- total_duration=0;
- for(;;){
- int ret;
- /*This takes advantage of undocumented libogg behavior that returned
- ogg_packet buffers are valid at least until the next page is
- submitted.
- Relying on this is not too terrible, as _none_ of the Ogg memory
- ownership/lifetime rules are well-documented.
- But I can read its code and know this will work.*/
- ret=ogg_stream_packetout(&_of->os,_of->op+op_count);
- if(!ret)break;
- if(OP_UNLIKELY(ret<0)){
- /*We shouldn't get holes in the middle of pages.*/
- OP_ASSERT(op_count==0);
- /*Set the return value and break out of the loop.
- We want to make sure op_count gets set to 0, because we've ingested a
- page, so any previously loaded packets are now invalid.*/
- total_duration=OP_HOLE;
- break;
- }
- /*Unless libogg is broken, we can't get more than 255 packets from a
- single page.*/
- OP_ASSERT(op_count<255);
- _durations[op_count]=op_get_packet_duration(_of->op[op_count].packet,
- _of->op[op_count].bytes);
- if(OP_LIKELY(_durations[op_count]>0)){
- /*With at most 255 packets on a page, this can't overflow.*/
- total_duration+=_durations[op_count++];
- }
- /*Ignore packets with an invalid TOC sequence.*/
- else if(op_count>0){
- /*But save the granule position, if there was one.*/
- _of->op[op_count-1].granulepos=_of->op[op_count].granulepos;
- }
- }
- _of->op_pos=0;
- _of->op_count=op_count;
- return total_duration;
-}
-
-/*Starting from current cursor position, get the initial PCM offset of the next
- page.
- This also validates the granule position on the first page with a completed
- audio data packet, as required by the spec.
- If this link is completely empty (no pages with completed packets), then this
- function sets pcm_start=pcm_end=0 and returns the BOS page of the next link
- (if any).
- In the seekable case, we initialize pcm_end=-1 before calling this function,
- so that later we can detect that the link was empty before calling
- op_find_final_pcm_offset().
- [inout] _link: The link for which to find pcm_start.
- [out] _og: Returns the BOS page of the next link if this link was empty.
- In the unseekable case, we can then feed this to
- op_fetch_headers() to start the next link.
- The caller may pass NULL (e.g., for seekable streams), in
- which case this page will be discarded.
- Return: 0 on success, 1 if there is a buffered BOS page available, or a
- negative value on unrecoverable error.*/
-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;
- ogg_uint32_t serialno;
- opus_int32 total_duration;
- int durations[255];
- int cur_page_eos;
- int op_count;
- int pi;
- if(_og==NULL)_og=&og;
- serialno=_of->os.serialno;
- op_count=0;
- /*We shouldn't have to initialize total_duration, but gcc is too dumb to
- figure out that op_count>0 implies we've been through the whole loop at
- least once.*/
- total_duration=0;
- do{
- 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(page_offset<0)){
- /*Fail if there was a read error.*/
- if(page_offset<OP_FALSE)return (int)page_offset;
- /*Fail if the pre-skip is non-zero, since it's asking us to skip more
- samples than exist.*/
- if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
- /*Set pcm_end and end_offset so we can skip the call to
- op_find_final_pcm_offset().*/
- _link->pcm_start=_link->pcm_end=0;
- _link->end_offset=_link->data_offset;
- return 0;
- }
- /*Similarly, if we hit the next link in the chain, we've gone too far.*/
- if(OP_UNLIKELY(ogg_page_bos(_og))){
- if(_link->head.pre_skip>0)return OP_EBADTIMESTAMP;
- /*Set pcm_end and end_offset so we can skip the call to
- op_find_final_pcm_offset().*/
- _link->pcm_end=_link->pcm_start=0;
- _link->end_offset=_link->data_offset;
- /*Tell the caller we've got a buffered page for them.*/
- return 1;
- }
- /*Ignore pages from other streams (not strictly necessary, because of the
- checks in ogg_stream_pagein(), but saves some work).*/
- if(serialno!=(ogg_uint32_t)ogg_page_serialno(_og))continue;
- ogg_stream_pagein(&_of->os,_og);
- /*Bitrate tracking: add the header's bytes here.
- The body bytes are counted when we consume the packets.*/
- _of->bytes_tracked+=_og->header_len;
- /*Count the durations of all packets in the page.*/
- do total_duration=op_collect_audio_packets(_of,durations);
- /*Ignore holes.*/
- while(OP_UNLIKELY(total_duration<0));
- op_count=_of->op_count;
- }
- while(op_count<=0);
- /*We found the first page with a completed audio data packet: actually look
- at the granule position.
- RFC 3533 says, "A special value of -1 (in two's complement) indicates that
- no packets finish on this page," which does not say that a granule
- position that is NOT -1 indicates that some packets DO finish on that page
- (even though this was the intention, libogg itself violated this intention
- for years before we fixed it).
- The Ogg Opus specification only imposes its start-time requirements
- on the granule position of the first page with completed packets,
- so we ignore any set granule positions until then.*/
- cur_page_gp=_of->op[op_count-1].granulepos;
- /*But getting a packet without a valid granule position on the page is not
- okay.*/
- if(cur_page_gp==-1)return OP_EBADTIMESTAMP;
- cur_page_eos=_of->op[op_count-1].e_o_s;
- if(OP_LIKELY(!cur_page_eos)){
- /*The EOS flag wasn't set.
- Work backwards from the provided granule position to get the starting PCM
- offset.*/
- if(OP_UNLIKELY(op_granpos_add(&pcm_start,cur_page_gp,-total_duration)<0)){
- /*The starting granule position MUST not be smaller than the amount of
- audio on the first page with completed packets.*/
- return OP_EBADTIMESTAMP;
- }
- }
- else{
- /*The first page with completed packets was also the last.*/
- if(OP_LIKELY(op_granpos_add(&pcm_start,cur_page_gp,-total_duration)<0)){
- /*If there's less audio on the page than indicated by the granule
- position, then we're doing end-trimming, and the starting PCM offset
- is zero by spec mandate.*/
- pcm_start=0;
- /*However, the end-trimming MUST not ask us to trim more samples than
- exist after applying the pre-skip.*/
- if(OP_UNLIKELY(op_granpos_cmp(cur_page_gp,_link->head.pre_skip)<0)){
- return OP_EBADTIMESTAMP;
- }
- }
- }
- /*Timestamp the individual packets.*/
- prev_packet_gp=pcm_start;
- for(pi=0;pi<op_count;pi++){
- if(cur_page_eos){
- ogg_int64_t diff;
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,cur_page_gp,prev_packet_gp));
- diff=durations[pi]-diff;
- /*If we have samples to trim...*/
- if(diff>0){
- /*If we trimmed the entire packet, stop (the spec says encoders
- shouldn't do this, but we support it anyway).*/
- if(OP_UNLIKELY(diff>durations[pi]))break;
- _of->op[pi].granulepos=prev_packet_gp=cur_page_gp;
- /*Move the EOS flag to this packet, if necessary, so we'll trim the
- samples.*/
- _of->op[pi].e_o_s=1;
- continue;
- }
- }
- /*Update the granule position as normal.*/
- OP_ALWAYS_TRUE(!op_granpos_add(&_of->op[pi].granulepos,
- prev_packet_gp,durations[pi]));
- prev_packet_gp=_of->op[pi].granulepos;
- }
- /*Update the packet count after end-trimming.*/
- _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;
-}
-
-/*Starting from current cursor position, get the final PCM offset of the
- previous page.
- This also validates the duration of the link, which, while not strictly
- required by the spec, we need to ensure duration calculations don't
- overflow.
- This is only done for seekable sources.
- We must validate that op_find_initial_pcm_offset() succeeded for this link
- before calling this function, otherwise it will scan the entire stream
- backwards until it reaches the start, and then fail.*/
-static int op_find_final_pcm_offset(OggOpusFile *_of,
- const ogg_uint32_t *_serialnos,int _nserialnos,OggOpusLink *_link,
- opus_int64 _offset,ogg_uint32_t _end_serialno,ogg_int64_t _end_gp,
- ogg_int64_t *_total_duration){
- ogg_int64_t total_duration;
- ogg_int64_t duration;
- ogg_uint32_t cur_serialno;
- /*For the time being, fetch end PCM offset the simple way.*/
- cur_serialno=_link->serialno;
- if(_end_serialno!=cur_serialno||_end_gp==-1){
- _offset=op_get_last_page(_of,&_end_gp,_offset,
- cur_serialno,_serialnos,_nserialnos);
- if(OP_UNLIKELY(_offset<0))return (int)_offset;
- }
- /*At worst we should have found the first page with completed packets.*/
- if(OP_UNLIKELY(_offset<_link->data_offset))return OP_EBADLINK;
- /*This implementation requires that the difference between the first and last
- granule positions in each link be representable in a signed, 64-bit
- number, and that each link also have at least as many samples as the
- pre-skip requires.*/
- if(OP_UNLIKELY(op_granpos_diff(&duration,_end_gp,_link->pcm_start)<0)
- ||OP_UNLIKELY(duration<_link->head.pre_skip)){
- return OP_EBADTIMESTAMP;
- }
- /*We also require that the total duration be representable in a signed,
- 64-bit number.*/
- duration-=_link->head.pre_skip;
- total_duration=*_total_duration;
- if(OP_UNLIKELY(OP_INT64_MAX-duration<total_duration))return OP_EBADTIMESTAMP;
- *_total_duration=total_duration+duration;
- _link->pcm_end=_end_gp;
- _link->end_offset=_offset;
- return 0;
-}
-
-/*Rescale the number _x from the range [0,_from] to [0,_to].
- _from and _to must be positive.*/
-static opus_int64 op_rescale64(opus_int64 _x,opus_int64 _from,opus_int64 _to){
- opus_int64 frac;
- opus_int64 ret;
- int i;
- if(_x>=_from)return _to;
- if(_x<=0)return 0;
- frac=0;
- for(i=0;i<63;i++){
- frac<<=1;
- OP_ASSERT(_x<=_from);
- if(_x>=_from>>1){
- _x-=_from-_x;
- frac|=1;
- }
- else _x<<=1;
- }
- ret=0;
- for(i=0;i<63;i++){
- if(frac&1)ret=(ret&_to&1)+(ret>>1)+(_to>>1);
- else ret>>=1;
- frac>>=1;
- }
- return ret;
-}
-
-/*The minimum granule position spacing allowed for making predictions.
- This corresponds to about 1 second of audio at 48 kHz for both Opus and
- Vorbis, or one keyframe interval in Theora with the default keyframe spacing
- of 256.*/
-#define OP_GP_SPACING_MIN (48000)
-
-/*Try to estimate the location of the next link using the current seek
- records, assuming the initial granule position of any streams we've found is
- 0.*/
-static opus_int64 op_predict_link_start(const OpusSeekRecord *_sr,int _nsr,
- opus_int64 _searched,opus_int64 _end_searched,opus_int32 _bias){
- opus_int64 bisect;
- int sri;
- int srj;
- /*Require that we be at least OP_CHUNK_SIZE from the end.
- We don't require that we be at least OP_CHUNK_SIZE from the beginning,
- because if we are we'll just scan forward without seeking.*/
- _end_searched-=OP_CHUNK_SIZE;
- if(_searched>=_end_searched)return -1;
- bisect=_end_searched;
- for(sri=0;sri<_nsr;sri++){
- ogg_int64_t gp1;
- ogg_int64_t gp2_min;
- ogg_uint32_t serialno1;
- opus_int64 offset1;
- /*If the granule position is negative, either it's invalid or we'd cause
- overflow.*/
- gp1=_sr[sri].gp;
- if(gp1<0)continue;
- /*We require some minimum distance between granule positions to make an
- estimate.
- We don't actually know what granule position scheme is being used,
- because we have no idea what kind of stream these came from.
- Therefore we require a minimum spacing between them, with the
- expectation that while bitrates and granule position increments might
- vary locally in quite complex ways, they are globally smooth.*/
- if(OP_UNLIKELY(op_granpos_add(&gp2_min,gp1,OP_GP_SPACING_MIN)<0)){
- /*No granule position would satisfy us.*/
- continue;
- }
- offset1=_sr[sri].offset;
- serialno1=_sr[sri].serialno;
- for(srj=sri;srj-->0;){
- ogg_int64_t gp2;
- opus_int64 offset2;
- opus_int64 num;
- ogg_int64_t den;
- ogg_int64_t ipart;
- gp2=_sr[srj].gp;
- if(gp2<gp2_min)continue;
- /*Oh, and also make sure these came from the same stream.*/
- if(_sr[srj].serialno!=serialno1)continue;
- offset2=_sr[srj].offset;
- /*For once, we can subtract with impunity.*/
- den=gp2-gp1;
- ipart=gp2/den;
- num=offset2-offset1;
- OP_ASSERT(num>0);
- if(ipart>0&&(offset2-_searched)/ipart<num)continue;
- offset2-=ipart*num;
- gp2-=ipart*den;
- offset2-=op_rescale64(gp2,den,num)-_bias;
- if(offset2<_searched)continue;
- bisect=OP_MIN(bisect,offset2);
- break;
- }
- }
- return bisect>=_end_searched?-1:bisect;
-}
-
-/*Finds each bitstream link, one at a time, using a bisection search.
- This has to begin by knowing the offset of the first link's initial page.*/
-static int op_bisect_forward_serialno(OggOpusFile *_of,
- opus_int64 _searched,OpusSeekRecord *_sr,int _csr,
- ogg_uint32_t **_serialnos,int *_nserialnos,int *_cserialnos){
- ogg_page og;
- OggOpusLink *links;
- int nlinks;
- int clinks;
- ogg_uint32_t *serialnos;
- int nserialnos;
- ogg_int64_t total_duration;
- int nsr;
- int ret;
- links=_of->links;
- nlinks=clinks=_of->nlinks;
- total_duration=0;
- /*We start with one seek record, for the last page in the file.
- We build up a list of records for places we seek to during link
- enumeration.
- This list is kept sorted in reverse order.
- We only care about seek locations that were _not_ in the current link,
- therefore we can add them one at a time to the end of the list as we
- improve the lower bound on the location where the next link starts.*/
- nsr=1;
- for(;;){
- opus_int64 end_searched;
- opus_int64 bisect;
- opus_int64 next;
- opus_int64 last;
- ogg_int64_t end_offset;
- ogg_int64_t end_gp;
- int sri;
- serialnos=*_serialnos;
- nserialnos=*_nserialnos;
- if(OP_UNLIKELY(nlinks>=clinks)){
- if(OP_UNLIKELY(clinks>INT_MAX-1>>1))return OP_EFAULT;
- clinks=2*clinks+1;
- OP_ASSERT(nlinks<clinks);
- links=(OggOpusLink *)_ogg_realloc(links,sizeof(*links)*clinks);
- if(OP_UNLIKELY(links==NULL))return OP_EFAULT;
- _of->links=links;
- }
- /*Invariants:
- We have the headers and serial numbers for the link beginning at 'begin'.
- We have the offset and granule position of the last page in the file
- (potentially not a page we care about).*/
- /*Scan the seek records we already have to save us some bisection.*/
- for(sri=0;sri<nsr;sri++){
- if(op_lookup_serialno(_sr[sri].serialno,serialnos,nserialnos))break;
- }
- /*Is the last page in our current list of serial numbers?*/
- if(sri<=0)break;
- /*Last page wasn't found.
- We have at least one more link.*/
- last=-1;
- end_searched=_sr[sri-1].search_start;
- next=_sr[sri-1].offset;
- end_gp=-1;
- if(sri<nsr){
- _searched=_sr[sri].offset+_sr[sri].size;
- if(_sr[sri].serialno==links[nlinks-1].serialno){
- end_gp=_sr[sri].gp;
- end_offset=_sr[sri].offset;
- }
- }
- nsr=sri;
- bisect=-1;
- /*If we've already found the end of at least one link, try to pick the
- first bisection point at twice the average link size.
- This is a good choice for files with lots of links that are all about the
- same size.*/
- if(nlinks>1){
- opus_int64 last_offset;
- opus_int64 avg_link_size;
- opus_int64 upper_limit;
- last_offset=links[nlinks-1].offset;
- avg_link_size=last_offset/(nlinks-1);
- upper_limit=end_searched-OP_CHUNK_SIZE-avg_link_size;
- if(OP_LIKELY(last_offset>_searched-avg_link_size)
- &&OP_LIKELY(last_offset<upper_limit)){
- bisect=last_offset+avg_link_size;
- if(OP_LIKELY(bisect<upper_limit))bisect+=avg_link_size;
- }
- }
- /*We guard against garbage separating the last and first pages of two
- links below.*/
- while(_searched<end_searched){
- opus_int32 next_bias;
- /*If we don't have a better estimate, use simple bisection.*/
- if(bisect==-1)bisect=_searched+(end_searched-_searched>>1);
- /*If we're within OP_CHUNK_SIZE of the start, scan forward.*/
- if(bisect-_searched<OP_CHUNK_SIZE)bisect=_searched;
- /*Otherwise we're skipping data.
- Forget the end page, if we saw one, as we might miss a later one.*/
- else end_gp=-1;
- ret=op_seek_helper(_of,bisect);
- if(OP_UNLIKELY(ret<0))return ret;
- last=op_get_next_page(_of,&og,_sr[nsr-1].offset);
- if(OP_UNLIKELY(last<OP_FALSE))return (int)last;
- next_bias=0;
- if(last==OP_FALSE)end_searched=bisect;
- else{
- ogg_uint32_t serialno;
- ogg_int64_t gp;
- serialno=ogg_page_serialno(&og);
- gp=ogg_page_granulepos(&og);
- if(!op_lookup_serialno(serialno,serialnos,nserialnos)){
- end_searched=bisect;
- next=last;
- /*In reality we should always have enough room, but be paranoid.*/
- if(OP_LIKELY(nsr<_csr)){
- _sr[nsr].search_start=bisect;
- _sr[nsr].offset=last;
- OP_ASSERT(_of->offset-last>=0);
- OP_ASSERT(_of->offset-last<=OP_PAGE_SIZE_MAX);
- _sr[nsr].size=(opus_int32)(_of->offset-last);
- _sr[nsr].serialno=serialno;
- _sr[nsr].gp=gp;
- nsr++;
- }
- }
- else{
- _searched=_of->offset;
- next_bias=OP_CHUNK_SIZE;
- if(serialno==links[nlinks-1].serialno){
- /*This page was from the stream we want, remember it.
- If it's the last such page in the link, we won't have to go back
- looking for it later.*/
- end_gp=gp;
- end_offset=last;
- }
- }
- }
- bisect=op_predict_link_start(_sr,nsr,_searched,end_searched,next_bias);
- }
- /*Bisection point found.
- Get the final granule position of the previous link, assuming
- op_find_initial_pcm_offset() didn't already determine the link was
- empty.*/
- if(OP_LIKELY(links[nlinks-1].pcm_end==-1)){
- if(end_gp==-1){
- /*If we don't know where the end page is, we'll have to seek back and
- look for it, starting from the end of the link.*/
- end_offset=next;
- /*Also forget the last page we read.
- It won't be available after the seek.*/
- last=-1;
- }
- ret=op_find_final_pcm_offset(_of,serialnos,nserialnos,
- links+nlinks-1,end_offset,links[nlinks-1].serialno,end_gp,
- &total_duration);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- if(last!=next){
- /*The last page we read was not the first page the next link.
- Move the cursor position to the offset of that first page.
- This only performs an actual seek if the first page of the next link
- does not start at the end of the last page from the current Opus
- stream with a valid granule position.*/
- ret=op_seek_helper(_of,next);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- ret=op_fetch_headers(_of,&links[nlinks].head,&links[nlinks].tags,
- _serialnos,_nserialnos,_cserialnos,last!=next?NULL:&og);
- if(OP_UNLIKELY(ret<0))return ret;
- links[nlinks].offset=next;
- links[nlinks].data_offset=_of->offset;
- links[nlinks].serialno=_of->os.serialno;
- links[nlinks].pcm_end=-1;
- /*This might consume a page from the next link, however the next bisection
- always starts with a seek.*/
- ret=op_find_initial_pcm_offset(_of,links+nlinks,NULL);
- if(OP_UNLIKELY(ret<0))return ret;
- _searched=_of->offset;
- /*Mark the current link count so it can be cleaned up on error.*/
- _of->nlinks=++nlinks;
- }
- /*Last page is in the starting serialno list, so we've reached the last link.
- Now find the last granule position for it (if we didn't the first time we
- looked at the end of the stream, and if op_find_initial_pcm_offset()
- didn't already determine the link was empty).*/
- if(OP_LIKELY(links[nlinks-1].pcm_end==-1)){
- ret=op_find_final_pcm_offset(_of,serialnos,nserialnos,
- links+nlinks-1,_sr[0].offset,_sr[0].serialno,_sr[0].gp,&total_duration);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- /*Trim back the links array if necessary.*/
- links=(OggOpusLink *)_ogg_realloc(links,sizeof(*links)*nlinks);
- if(OP_LIKELY(links!=NULL))_of->links=links;
- /*We also don't need these anymore.*/
- _ogg_free(*_serialnos);
- *_serialnos=NULL;
- *_cserialnos=*_nserialnos=0;
- return 0;
-}
-
-static void op_update_gain(OggOpusFile *_of){
- OpusHead *head;
- opus_int32 gain_q8;
- int li;
- /*If decode isn't ready, then we'll apply the gain when we initialize the
- decoder.*/
- if(_of->ready_state<OP_INITSET)return;
- gain_q8=_of->gain_offset_q8;
- li=_of->seekable?_of->cur_link:0;
- head=&_of->links[li].head;
- /*We don't have to worry about overflow here because the header gain and
- 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;
- 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);
- }
- gain_q8=OP_CLAMP(-32768,gain_q8,32767);
- OP_ASSERT(_of->od!=NULL);
-#if defined(OPUS_SET_GAIN)
- opus_multistream_decoder_ctl(_of->od,OPUS_SET_GAIN(gain_q8));
-#else
-/*A fallback that works with both float and fixed-point is a bunch of work,
- so just force people to use a sufficiently new version.
- This is deployed well enough at this point that this shouldn't be a burden.*/
-# error "libopus 1.0.1 or later required"
-#endif
-}
-
-static int op_make_decode_ready(OggOpusFile *_of){
- const OpusHead *head;
- int li;
- int stream_count;
- int coupled_count;
- int channel_count;
- if(_of->ready_state>OP_STREAMSET)return 0;
- if(OP_UNLIKELY(_of->ready_state<OP_STREAMSET))return OP_EFAULT;
- li=_of->seekable?_of->cur_link:0;
- head=&_of->links[li].head;
- stream_count=head->stream_count;
- coupled_count=head->coupled_count;
- channel_count=head->channel_count;
- /*Check to see if the current decoder is compatible with the current link.*/
- if(_of->od!=NULL&&_of->od_stream_count==stream_count
- &&_of->od_coupled_count==coupled_count&&_of->od_channel_count==channel_count
- &&memcmp(_of->od_mapping,head->mapping,
- sizeof(*head->mapping)*channel_count)==0){
- opus_multistream_decoder_ctl(_of->od,OPUS_RESET_STATE);
- }
- else{
- int err;
- opus_multistream_decoder_destroy(_of->od);
- _of->od=opus_multistream_decoder_create(48000,channel_count,
- stream_count,coupled_count,head->mapping,&err);
- if(_of->od==NULL)return OP_EFAULT;
- _of->od_stream_count=stream_count;
- _of->od_coupled_count=coupled_count;
- _of->od_channel_count=channel_count;
- memcpy(_of->od_mapping,head->mapping,sizeof(*head->mapping)*channel_count);
- }
- _of->ready_state=OP_INITSET;
- _of->bytes_tracked=0;
- _of->samples_tracked=0;
-#if !defined(OP_FIXED_POINT)
- _of->state_channel_count=0;
- /*Use the serial number for the PRNG seed to get repeatable output for
- straight play-throughs.*/
- _of->dither_seed=_of->links[li].serialno;
-#endif
- op_update_gain(_of);
- return 0;
-}
-
-static int op_open_seekable2_impl(OggOpusFile *_of){
- /*64 seek records should be enough for anybody.
- Actually, with a bisection search in a 63-bit range down to OP_CHUNK_SIZE
- granularity, much more than enough.*/
- OpusSeekRecord sr[64];
- opus_int64 data_offset;
- int ret;
- /*We can seek, so set out learning all about this file.*/
- (*_of->callbacks.seek)(_of->source,0,SEEK_END);
- _of->offset=_of->end=(*_of->callbacks.tell)(_of->source);
- if(OP_UNLIKELY(_of->end<0))return OP_EREAD;
- data_offset=_of->links[0].data_offset;
- if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK;
- /*Get the offset of the last page of the physical bitstream, or, if we're
- lucky, the last Opus page of the first link, as most Ogg Opus files will
- contain a single logical bitstream.*/
- ret=op_get_prev_page_serial(_of,sr,_of->end,
- _of->links[0].serialno,_of->serialnos,_of->nserialnos);
- if(OP_UNLIKELY(ret<0))return ret;
- /*If there's any trailing junk, forget about it.*/
- _of->end=sr[0].offset+sr[0].size;
- if(OP_UNLIKELY(_of->end<data_offset))return OP_EBADLINK;
- /*Now enumerate the bitstream structure.*/
- return op_bisect_forward_serialno(_of,data_offset,sr,sizeof(sr)/sizeof(*sr),
- &_of->serialnos,&_of->nserialnos,&_of->cserialnos);
-}
-
-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;
- /*We're partially open and have a first link header state in storage in _of.
- Save off that stream state so we can come back to it.
- It would be simpler to just dump all this state and seek back to
- links[0].data_offset when we're done.
- But we do the extra work to allow us to seek back to _exactly_ the same
- stream position we're at now.
- This allows, e.g., the HTTP backend to continue reading from the original
- connection (if it's still available), instead of opening a new one.
- This means we can open and start playing a normal Opus file with a single
- link and reasonable packet sizes using only two HTTP requests.*/
- start_op_count=_of->op_count;
- /*This is a bit too large to put on the stack unconditionally.*/
- op_start=(ogg_packet *)_ogg_malloc(sizeof(*op_start)*start_op_count);
- 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));
- ogg_sync_init(&_of->oy);
- ogg_stream_init(&_of->os,-1);
- ret=op_open_seekable2_impl(_of);
- /*Restore the old stream state.*/
- ogg_stream_clear(&_of->os);
- ogg_sync_clear(&_of->oy);
- *&_of->oy=*&oy_start;
- *&_of->os=*&os_start;
- _of->offset=start_offset;
- _of->op_count=start_op_count;
- 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.*/
- ret=(*_of->callbacks.seek)(_of->source,op_position(_of),SEEK_SET);
- return OP_UNLIKELY(ret<0)?OP_EREAD:0;
-}
-
-/*Clear out the current logical bitstream decoder.*/
-static void op_decode_clear(OggOpusFile *_of){
- /*We don't actually free the decoder.
- We might be able to re-use it for the next link.*/
- _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);
- }
- _of->ready_state=OP_OPENED;
-}
-
-static void op_clear(OggOpusFile *_of){
- OggOpusLink *links;
- _ogg_free(_of->od_buffer);
- if(_of->od!=NULL)opus_multistream_decoder_destroy(_of->od);
- links=_of->links;
- if(!_of->seekable){
- if(_of->ready_state>OP_OPENED||_of->ready_state==OP_PARTOPEN){
- opus_tags_clear(&links[0].tags);
- }
- }
- else if(OP_LIKELY(links!=NULL)){
- int nlinks;
- int link;
- nlinks=_of->nlinks;
- for(link=0;link<nlinks;link++)opus_tags_clear(&links[link].tags);
- }
- _ogg_free(links);
- _ogg_free(_of->serialnos);
- ogg_stream_clear(&_of->os);
- ogg_sync_clear(&_of->oy);
- if(_of->callbacks.close!=NULL)(*_of->callbacks.close)(_of->source);
-}
-
-static int op_open1(OggOpusFile *_of,
- void *_source,const OpusFileCallbacks *_cb,
- const unsigned char *_initial_data,size_t _initial_bytes){
- ogg_page og;
- ogg_page *pog;
- int seekable;
- int ret;
- memset(_of,0,sizeof(*_of));
- _of->end=-1;
- _of->source=_source;
- *&_of->callbacks=*_cb;
- /*At a minimum, we need to be able to read data.*/
- if(OP_UNLIKELY(_of->callbacks.read==NULL))return OP_EREAD;
- /*Initialize the framing state.*/
- ogg_sync_init(&_of->oy);
- /*Perhaps some data was previously read into a buffer for testing against
- other stream types.
- Allow initialization from this previously read data (especially as we may
- be reading from a non-seekable stream).
- This requires copying it into a buffer allocated by ogg_sync_buffer() and
- doesn't support seeking, so this is not a good mechanism to use for
- decoding entire files from RAM.*/
- if(_initial_bytes>0){
- char *buffer;
- buffer=ogg_sync_buffer(&_of->oy,_initial_bytes);
- memcpy(buffer,_initial_data,_initial_bytes*sizeof(*buffer));
- ogg_sync_wrote(&_of->oy,_initial_bytes);
- }
- /*Can we seek?
- Stevens suggests the seek test is portable.*/
- seekable=_cb->seek!=NULL&&(*_cb->seek)(_source,0,SEEK_CUR)!=-1;
- /*If seek is implemented, tell must also be implemented.*/
- if(seekable){
- opus_int64 pos;
- if(OP_UNLIKELY(_of->callbacks.tell==NULL))return OP_EINVAL;
- pos=(*_of->callbacks.tell)(_of->source);
- /*If the current position is not equal to the initial bytes consumed,
- absolute seeking will not work.*/
- if(OP_UNLIKELY(pos!=(opus_int64)_initial_bytes))return OP_EINVAL;
- }
- _of->seekable=seekable;
- /*Don't seek yet.
- Set up a 'single' (current) logical bitstream entry for partial open.*/
- _of->links=(OggOpusLink *)_ogg_malloc(sizeof(*_of->links));
- /*The serialno gets filled in later by op_fetch_headers().*/
- ogg_stream_init(&_of->os,-1);
- pog=NULL;
- for(;;){
- /*Fetch all BOS pages, store the Opus header and all seen serial numbers,
- and load subsequent Opus setup headers.*/
- ret=op_fetch_headers(_of,&_of->links[0].head,&_of->links[0].tags,
- &_of->serialnos,&_of->nserialnos,&_of->cserialnos,pog);
- if(OP_UNLIKELY(ret<0))break;
- _of->nlinks=1;
- _of->links[0].offset=0;
- _of->links[0].data_offset=_of->offset;
- _of->links[0].pcm_end=-1;
- _of->links[0].serialno=_of->os.serialno;
- /*Fetch the initial PCM offset.*/
- ret=op_find_initial_pcm_offset(_of,_of->links,&og);
- if(seekable||OP_LIKELY(ret<=0))break;
- /*This link was empty, but we already have the BOS page for the next one in
- og.
- We can't seek, so start processing the next link right now.*/
- opus_tags_clear(&_of->links[0].tags);
- _of->nlinks=0;
- if(!seekable)_of->cur_link++;
- pog=&og;
- }
- if(OP_LIKELY(ret>=0))_of->ready_state=OP_PARTOPEN;
- return ret;
-}
-
-static int op_open2(OggOpusFile *_of){
- int ret;
- OP_ASSERT(_of->ready_state==OP_PARTOPEN);
- if(_of->seekable){
- _of->ready_state=OP_OPENED;
- ret=op_open_seekable2(_of);
- }
- else ret=0;
- if(OP_LIKELY(ret>=0)){
- /*We have buffered packets from op_find_initial_pcm_offset().
- Move to OP_INITSET so we can use them.*/
- _of->ready_state=OP_STREAMSET;
- ret=op_make_decode_ready(_of);
- if(OP_LIKELY(ret>=0))return 0;
- }
- /*Don't auto-close the stream on failure.*/
- _of->callbacks.close=NULL;
- op_clear(_of);
- return ret;
-}
-
-OggOpusFile *op_test_callbacks(void *_source,const OpusFileCallbacks *_cb,
- const unsigned char *_initial_data,size_t _initial_bytes,int *_error){
- OggOpusFile *of;
- int ret;
- of=(OggOpusFile *)_ogg_malloc(sizeof(*of));
- ret=OP_EFAULT;
- if(OP_LIKELY(of!=NULL)){
- ret=op_open1(of,_source,_cb,_initial_data,_initial_bytes);
- if(OP_LIKELY(ret>=0)){
- if(_error!=NULL)*_error=0;
- return of;
- }
- /*Don't auto-close the stream on failure.*/
- of->callbacks.close=NULL;
- op_clear(of);
- _ogg_free(of);
- }
- if(_error!=NULL)*_error=ret;
- return NULL;
-}
-
-OggOpusFile *op_open_callbacks(void *_source,const OpusFileCallbacks *_cb,
- const unsigned char *_initial_data,size_t _initial_bytes,int *_error){
- OggOpusFile *of;
- of=op_test_callbacks(_source,_cb,_initial_data,_initial_bytes,_error);
- if(OP_LIKELY(of!=NULL)){
- int ret;
- ret=op_open2(of);
- if(OP_LIKELY(ret>=0))return of;
- if(_error!=NULL)*_error=ret;
- _ogg_free(of);
- }
- return NULL;
-}
-
-/*Convenience routine to clean up from failure for the open functions that
- create their own streams.*/
-static OggOpusFile *op_open_close_on_failure(void *_source,
- const OpusFileCallbacks *_cb,int *_error){
- OggOpusFile *of;
- if(OP_UNLIKELY(_source==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);
- return of;
-}
-
-OggOpusFile *op_open_file(const char *_path,int *_error){
- OpusFileCallbacks cb;
- return op_open_close_on_failure(op_fopen(&cb,_path,"rb"),&cb,_error);
-}
-
-OggOpusFile *op_open_memory(const unsigned char *_data,size_t _size,
- int *_error){
- OpusFileCallbacks cb;
- return op_open_close_on_failure(op_mem_stream_create(&cb,_data,_size),&cb,
- _error);
-}
-
-/*Convenience routine to clean up from failure for the open functions that
- create their own streams.*/
-static OggOpusFile *op_test_close_on_failure(void *_source,
- const OpusFileCallbacks *_cb,int *_error){
- OggOpusFile *of;
- if(OP_UNLIKELY(_source==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);
- return of;
-}
-
-OggOpusFile *op_test_file(const char *_path,int *_error){
- OpusFileCallbacks cb;
- return op_test_close_on_failure(op_fopen(&cb,_path,"rb"),&cb,_error);
-}
-
-OggOpusFile *op_test_memory(const unsigned char *_data,size_t _size,
- int *_error){
- OpusFileCallbacks cb;
- return op_test_close_on_failure(op_mem_stream_create(&cb,_data,_size),&cb,
- _error);
-}
-
-int op_test_open(OggOpusFile *_of){
- int ret;
- if(OP_UNLIKELY(_of->ready_state!=OP_PARTOPEN))return OP_EINVAL;
- ret=op_open2(_of);
- /*op_open2() will clear this structure on failure.
- Reset its contents to prevent double-frees in op_free().*/
- if(OP_UNLIKELY(ret<0))memset(_of,0,sizeof(*_of));
- return ret;
-}
-
-void op_free(OggOpusFile *_of){
- if(OP_LIKELY(_of!=NULL)){
- op_clear(_of);
- _ogg_free(_of);
- }
-}
-
-int op_seekable(const OggOpusFile *_of){
- return _of->seekable;
-}
-
-int op_link_count(const OggOpusFile *_of){
- return _of->nlinks;
-}
-
-ogg_uint32_t op_serialno(const OggOpusFile *_of,int _li){
- if(OP_UNLIKELY(_li>=_of->nlinks))_li=_of->nlinks-1;
- if(!_of->seekable)_li=0;
- return _of->links[_li<0?_of->cur_link:_li].serialno;
-}
-
-int op_channel_count(const OggOpusFile *_of,int _li){
- return op_head(_of,_li)->channel_count;
-}
-
-opus_int64 op_raw_total(const OggOpusFile *_of,int _li){
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED)
- ||OP_UNLIKELY(!_of->seekable)
- ||OP_UNLIKELY(_li>=_of->nlinks)){
- return OP_EINVAL;
- }
- if(_li<0)return _of->end-_of->links[0].offset;
- return (_li+1>=_of->nlinks?_of->end:_of->links[_li+1].offset)
- -_of->links[_li].offset;
-}
-
-ogg_int64_t op_pcm_total(const OggOpusFile *_of,int _li){
- OggOpusLink *links;
- ogg_int64_t diff;
- int nlinks;
- nlinks=_of->nlinks;
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED)
- ||OP_UNLIKELY(!_of->seekable)
- ||OP_UNLIKELY(_li>=nlinks)){
- return OP_EINVAL;
- }
- links=_of->links;
- /*We verify that the granule position differences are larger than the
- pre-skip and that the total duration does not overflow during link
- enumeration, so we don't have to check here.*/
- if(_li<0){
- ogg_int64_t pcm_total;
- int li;
- pcm_total=0;
- for(li=0;li<nlinks;li++){
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,
- links[li].pcm_end,links[li].pcm_start));
- pcm_total+=diff-links[li].head.pre_skip;
- }
- return pcm_total;
- }
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,
- links[_li].pcm_end,links[_li].pcm_start));
- return diff-links[_li].head.pre_skip;
-}
-
-const OpusHead *op_head(const OggOpusFile *_of,int _li){
- if(OP_UNLIKELY(_li>=_of->nlinks))_li=_of->nlinks-1;
- if(!_of->seekable)_li=0;
- return &_of->links[_li<0?_of->cur_link:_li].head;
-}
-
-const OpusTags *op_tags(const OggOpusFile *_of,int _li){
- if(OP_UNLIKELY(_li>=_of->nlinks))_li=_of->nlinks-1;
- if(!_of->seekable){
- if(_of->ready_state<OP_STREAMSET&&_of->ready_state!=OP_PARTOPEN){
- return NULL;
- }
- _li=0;
- }
- else if(_li<0)_li=_of->ready_state>=OP_STREAMSET?_of->cur_link:0;
- return &_of->links[_li].tags;
-}
-
-int op_current_link(const OggOpusFile *_of){
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- return _of->cur_link;
-}
-
-/*Compute an average bitrate given a byte and sample count.
- Return: The bitrate in bits per second.*/
-static opus_int32 op_calc_bitrate(opus_int64 _bytes,ogg_int64_t _samples){
- /*These rates are absurd, but let's handle them anyway.*/
- if(OP_UNLIKELY(_bytes>(OP_INT64_MAX-(_samples>>1))/(48000*8))){
- ogg_int64_t den;
- if(OP_UNLIKELY(_bytes/(OP_INT32_MAX/(48000*8))>=_samples)){
- return OP_INT32_MAX;
- }
- den=_samples/(48000*8);
- return (opus_int32)((_bytes+(den>>1))/den);
- }
- if(OP_UNLIKELY(_samples<=0))return OP_INT32_MAX;
- /*This can't actually overflow in normal operation: even with a pre-skip of
- 545 2.5 ms frames with 8 streams running at 1282*8+1 bytes per packet
- (1275 byte frames + Opus framing overhead + Ogg lacing values), that all
- produce a single sample of decoded output, we still don't top 45 Mbps.
- The only way to get bitrates larger than that is with excessive Opus
- padding, more encoded streams than output channels, or lots and lots of
- Ogg pages with no packets on them.*/
- return (opus_int32)OP_MIN((_bytes*48000*8+(_samples>>1))/_samples,
- OP_INT32_MAX);
-}
-
-opus_int32 op_bitrate(const OggOpusFile *_of,int _li){
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED)||OP_UNLIKELY(!_of->seekable)
- ||OP_UNLIKELY(_li>=_of->nlinks)){
- return OP_EINVAL;
- }
- return op_calc_bitrate(op_raw_total(_of,_li),op_pcm_total(_of,_li));
-}
-
-opus_int32 op_bitrate_instant(OggOpusFile *_of){
- ogg_int64_t samples_tracked;
- opus_int32 ret;
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- samples_tracked=_of->samples_tracked;
- if(OP_UNLIKELY(samples_tracked==0))return OP_FALSE;
- ret=op_calc_bitrate(_of->bytes_tracked,samples_tracked);
- _of->bytes_tracked=0;
- _of->samples_tracked=0;
- return ret;
-}
-
-/*Fetch and process a page.
- 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).
- Return: <0) Error, OP_HOLE (lost packet), or OP_EOF.
- 0) Got at least one audio data packet.*/
-static int op_fetch_and_process_page(OggOpusFile *_of,
- ogg_page *_og,opus_int64 _page_offset,int _spanp,int _ignore_holes){
- OggOpusLink *links;
- ogg_uint32_t cur_serialno;
- int seekable;
- int cur_link;
- int ret;
- /*We shouldn't get here if we have unprocessed packets.*/
- OP_ASSERT(_of->ready_state<OP_INITSET||_of->op_pos>=_of->op_count);
- seekable=_of->seekable;
- links=_of->links;
- cur_link=seekable?_of->cur_link:0;
- cur_serialno=links[cur_link].serialno;
- /*Handle one page.*/
- for(;;){
- ogg_page og;
- OP_ASSERT(_of->ready_state>=OP_OPENED);
- /*If we were given a page to use, use it.*/
- if(_og!=NULL){
- *&og=*_og;
- _og=NULL;
- }
- /*Keep reading until we get a page with the correct serialno.*/
- else _page_offset=op_get_next_page(_of,&og,_of->end);
- /*EOF: Leave uninitialized.*/
- if(_page_offset<0)return _page_offset<OP_FALSE?(int)_page_offset:OP_EOF;
- if(OP_LIKELY(_of->ready_state>=OP_STREAMSET)
- &&cur_serialno!=(ogg_uint32_t)ogg_page_serialno(&og)){
- /*Two possibilities:
- 1) Another stream is multiplexed into this logical section, or*/
- if(OP_LIKELY(!ogg_page_bos(&og)))continue;
- /* 2) Our decoding just traversed a bitstream boundary.*/
- if(!_spanp)return OP_EOF;
- if(OP_LIKELY(_of->ready_state>=OP_INITSET))op_decode_clear(_of);
- }
- /*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
- and cached.
- We just initialize the machine with it and continue on our merry way.
- In the non-seekable (streaming) case, we'll only be at a boundary if we
- just left the previous logical bitstream, and we're now nominally at the
- header of the next bitstream.*/
- if(OP_UNLIKELY(_of->ready_state<OP_STREAMSET)){
- if(seekable){
- ogg_uint32_t serialno;
- int nlinks;
- int li;
- serialno=ogg_page_serialno(&og);
- /*Match the serialno to bitstream section.
- We use this rather than offset positions to avoid problems near
- logical bitstream boundaries.*/
- nlinks=_of->nlinks;
- for(li=0;li<nlinks&&links[li].serialno!=serialno;li++);
- /*Not a desired Opus bitstream section.
- Keep trying.*/
- if(li>=nlinks)continue;
- cur_serialno=serialno;
- _of->cur_link=cur_link=li;
- ogg_stream_reset_serialno(&_of->os,serialno);
- _of->ready_state=OP_STREAMSET;
- /*If we're at the start of this link, initialize the granule position
- and pre-skip tracking.*/
- 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.*/
- _ignore_holes=1;
- }
- }
- else{
- do{
- /*We're streaming.
- Fetch the two header packets, build the info struct.*/
- ret=op_fetch_headers(_of,&links[0].head,&links[0].tags,
- NULL,NULL,NULL,&og);
- if(OP_UNLIKELY(ret<0))return ret;
- /*op_find_initial_pcm_offset() will suppress any initial hole for us,
- so no need to set _ignore_holes.*/
- ret=op_find_initial_pcm_offset(_of,links,&og);
- if(OP_UNLIKELY(ret<0))return ret;
- _of->links[0].serialno=cur_serialno=_of->os.serialno;
- _of->cur_link++;
- }
- /*If the link was empty, keep going, because we already have the
- BOS page of the next one in og.*/
- while(OP_UNLIKELY(ret>0));
- /*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.
- 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 0;
- }
- }
- /*The buffered page is the data we want, and we're ready for it.
- Add it to the stream state.*/
- if(OP_UNLIKELY(_of->ready_state==OP_STREAMSET)){
- ret=op_make_decode_ready(_of);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- /*Extract all the packets from the current page.*/
- ogg_stream_pagein(&_of->os,&og);
- if(OP_LIKELY(_of->ready_state>=OP_INITSET)){
- opus_int32 total_duration;
- int durations[255];
- int op_count;
- total_duration=op_collect_audio_packets(_of,durations);
- if(OP_UNLIKELY(total_duration<0)){
- /*Drain the packets from the page anyway.*/
- total_duration=op_collect_audio_packets(_of,durations);
- OP_ASSERT(total_duration>=0);
- /*Report holes to the caller.*/
- if(!_ignore_holes)return OP_HOLE;
- }
- op_count=_of->op_count;
- /*If we found at least one audio data packet, compute per-packet granule
- positions for them.*/
- if(op_count>0){
- ogg_int64_t diff;
- ogg_int64_t prev_packet_gp;
- ogg_int64_t cur_packet_gp;
- ogg_int64_t cur_page_gp;
- int cur_page_eos;
- int pi;
- cur_page_gp=_of->op[op_count-1].granulepos;
- cur_page_eos=_of->op[op_count-1].e_o_s;
- prev_packet_gp=_of->prev_packet_gp;
- if(OP_UNLIKELY(prev_packet_gp==-1)){
- opus_int32 cur_discard_count;
- /*This is the first call after a raw seek.
- Try to reconstruct prev_packet_gp from scratch.*/
- OP_ASSERT(seekable);
- if(OP_UNLIKELY(cur_page_eos)){
- /*If the first page we hit after our seek was the EOS page, and
- we didn't start from data_offset or before, we don't have
- enough information to do end-trimming.
- Proceed to the next link, rather than risk playing back some
- samples that shouldn't have been played.*/
- _of->op_count=0;
- continue;
- }
- /*By default discard 80 ms of data after a seek, unless we seek
- into the pre-skip region.*/
- cur_discard_count=80*48;
- cur_page_gp=_of->op[op_count-1].granulepos;
- /*Try to initialize prev_packet_gp.
- If the current page had packets but didn't have a granule
- position, or the granule position it had was too small (both
- illegal), just use the starting granule position for the link.*/
- prev_packet_gp=links[cur_link].pcm_start;
- if(OP_LIKELY(cur_page_gp!=-1)){
- op_granpos_add(&prev_packet_gp,cur_page_gp,-total_duration);
- }
- if(OP_LIKELY(!op_granpos_diff(&diff,
- prev_packet_gp,links[cur_link].pcm_start))){
- opus_int32 pre_skip;
- /*If we start at the beginning of the pre-skip region, or we're
- at least 80 ms from the end of the pre-skip region, we discard
- to the end of the pre-skip region.
- Otherwise, we still use the 80 ms default, which will discard
- past the end of the pre-skip region.*/
- pre_skip=links[cur_link].head.pre_skip;
- if(diff>=0&&diff<=OP_MAX(0,pre_skip-80*48)){
- cur_discard_count=pre_skip-(int)diff;
- }
- }
- _of->cur_discard_count=cur_discard_count;
- }
- if(OP_UNLIKELY(cur_page_gp==-1)){
- /*This page had completed packets but didn't have a valid granule
- position.
- This is illegal, but we'll try to handle it by continuing to count
- forwards from the previous page.*/
- if(op_granpos_add(&cur_page_gp,prev_packet_gp,total_duration)<0){
- /*The timestamp for this page overflowed.*/
- cur_page_gp=links[cur_link].pcm_end;
- }
- }
- /*If we hit the last page, handle end-trimming.*/
- if(OP_UNLIKELY(cur_page_eos)
- &&OP_LIKELY(!op_granpos_diff(&diff,cur_page_gp,prev_packet_gp))
- &&OP_LIKELY(diff<total_duration)){
- cur_packet_gp=prev_packet_gp;
- for(pi=0;pi<op_count;pi++){
- diff=durations[pi]-diff;
- /*If we have samples to trim...*/
- if(diff>0){
- /*If we trimmed the entire packet, stop (the spec says encoders
- shouldn't do this, but we support it anyway).*/
- if(OP_UNLIKELY(diff>durations[pi]))break;
- cur_packet_gp=cur_page_gp;
- /*Move the EOS flag to this packet, if necessary, so we'll trim
- the samples during decode.*/
- _of->op[pi].e_o_s=1;
- }
- else{
- /*Update the granule position as normal.*/
- OP_ALWAYS_TRUE(!op_granpos_add(&cur_packet_gp,
- cur_packet_gp,durations[pi]));
- }
- _of->op[pi].granulepos=cur_packet_gp;
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,cur_page_gp,cur_packet_gp));
- }
- }
- else{
- /*Propagate timestamps to earlier packets.
- 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, 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.
- They might be completely out of range for this link (we'll check
- that elsewhere), or non-monotonic between pages.*/
- if(OP_UNLIKELY(op_granpos_add(&prev_packet_gp,
- cur_page_gp,-total_duration)<0)){
- /*The starting timestamp for the first packet on this page
- underflowed.
- This is illegal, but we ignore it.*/
- prev_packet_gp=0;
- }
- for(pi=0;pi<op_count;pi++){
- if(OP_UNLIKELY(op_granpos_add(&cur_packet_gp,
- cur_page_gp,-total_duration)<0)){
- /*The start timestamp for this packet underflowed.
- This is illegal, but we ignore it.*/
- cur_packet_gp=0;
- }
- total_duration-=durations[pi];
- OP_ASSERT(total_duration>=0);
- OP_ALWAYS_TRUE(!op_granpos_add(&cur_packet_gp,
- cur_packet_gp,durations[pi]));
- _of->op[pi].granulepos=cur_packet_gp;
- }
- 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 0;
- }
- }
- }
-}
-
-int op_raw_seek(OggOpusFile *_of,opus_int64 _pos){
- int ret;
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- /*Don't dump the decoder state if we can't seek.*/
- if(OP_UNLIKELY(!_of->seekable))return OP_ENOSEEK;
- if(OP_UNLIKELY(_pos<0)||OP_UNLIKELY(_pos>_of->end))return OP_EINVAL;
- /*Clear out any buffered, decoded data.*/
- op_decode_clear(_of);
- _of->bytes_tracked=0;
- _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);
- /*If we hit EOF, op_fetch_and_process_page() leaves us uninitialized.
- Instead, jump to the end.*/
- if(ret==OP_EOF){
- int cur_link;
- op_decode_clear(_of);
- cur_link=_of->nlinks-1;
- _of->cur_link=cur_link;
- _of->prev_packet_gp=_of->links[cur_link].pcm_end;
- _of->cur_discard_count=0;
- ret=0;
- }
- return ret;
-}
-
-/*Convert a PCM offset relative to the start of the whole stream to a granule
- position in an individual link.*/
-static ogg_int64_t op_get_granulepos(const OggOpusFile *_of,
- ogg_int64_t _pcm_offset,int *_li){
- const OggOpusLink *links;
- ogg_int64_t duration;
- int nlinks;
- int li;
- OP_ASSERT(_pcm_offset>=0);
- nlinks=_of->nlinks;
- links=_of->links;
- for(li=0;OP_LIKELY(li<nlinks);li++){
- ogg_int64_t pcm_start;
- opus_int32 pre_skip;
- pcm_start=links[li].pcm_start;
- pre_skip=links[li].head.pre_skip;
- OP_ALWAYS_TRUE(!op_granpos_diff(&duration,links[li].pcm_end,pcm_start));
- duration-=pre_skip;
- if(_pcm_offset<duration){
- _pcm_offset+=pre_skip;
- if(OP_UNLIKELY(pcm_start>OP_INT64_MAX-_pcm_offset)){
- /*Adding this amount to the granule position would overflow the positive
- half of its 64-bit range.
- Since signed overflow is undefined in C, do it in a way the compiler
- isn't allowed to screw up.*/
- _pcm_offset-=OP_INT64_MAX-pcm_start+1;
- pcm_start=OP_INT64_MIN;
- }
- pcm_start+=_pcm_offset;
- *_li=li;
- return pcm_start;
- }
- _pcm_offset-=duration;
- }
- 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.*/
-#define OP_CUR_TIME_THRESH (120*48*(opus_int32)1000)
-
-/*Note: The OP_SMALL_FOOTPRINT #define doesn't (currently) save much code size,
- but it's meant to serve as documentation for portions of the seeking
- algorithm that are purely optional, to aid others learning from/porting this
- code to other contexts.*/
-/*#define OP_SMALL_FOOTPRINT (1)*/
-
-/*Search within link _li for the page with the highest granule position
- preceding (or equal to) _target_gp.
- There is a danger here: missing pages or incorrect frame number information
- in the bitstream could make our task impossible.
- Account for that (and report it as an error condition).*/
-static int op_pcm_seek_page(OggOpusFile *_of,
- ogg_int64_t _target_gp,int _li){
- const OggOpusLink *link;
- ogg_page og;
- ogg_int64_t pcm_pre_skip;
- ogg_int64_t pcm_start;
- ogg_int64_t pcm_end;
- ogg_int64_t best_gp;
- 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;
- link=_of->links+_li;
- best_gp=pcm_start=link->pcm_start;
- pcm_end=link->pcm_end;
- serialno=link->serialno;
- 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.*/
- if(OP_UNLIKELY(op_granpos_add(&_target_gp,_target_gp,-80*48)<0)
- ||OP_UNLIKELY(op_granpos_cmp(_target_gp,pcm_start)<0)){
- _target_gp=pcm_start;
- }
- /*Special case seeking to the start of the link.*/
- pre_skip=link->head.pre_skip;
- OP_ALWAYS_TRUE(!op_granpos_add(&pcm_pre_skip,pcm_start,pre_skip));
- if(op_granpos_cmp(_target_gp,pcm_pre_skip)<0)end=boundary=begin;
- else{
- end=boundary=link->end_offset;
-#if !defined(OP_SMALL_FOOTPRINT)
- /*If we were decoding from this link, we can narrow the range a bit.*/
- if(_li==_of->cur_link&&_of->ready_state>=OP_INITSET){
- opus_int64 offset;
- int op_count;
- op_count=_of->op_count;
- /*The only way the offset can be invalid _and_ we can fail the granule
- position checks below is if someone changed the contents of the last
- page since we read it.
- We'd be within our rights to just return OP_EBADLINK in that case, but
- we'll simply ignore the current position instead.*/
- offset=_of->offset;
- if(op_count>0&&OP_LIKELY(offset<=end)){
- ogg_int64_t gp;
- /*Make sure the timestamp is valid.
- The granule position might be -1 if we collected the packets from a
- page without a granule position after reporting a hole.*/
- gp=_of->op[op_count-1].granulepos;
- if(OP_LIKELY(gp!=-1)&&OP_LIKELY(op_granpos_cmp(pcm_start,gp)<0)
- &&OP_LIKELY(op_granpos_cmp(pcm_end,gp)>0)){
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,gp,_target_gp));
- /*We only actually use the current time if either
- a) We can cut off at least half the range, or
- b) We're seeking sufficiently close to the current position that
- it's likely to be informative.
- Otherwise it appears using the whole link range to estimate the
- first seek location gives better results, on average.*/
- if(diff<0){
- OP_ASSERT(offset>=begin);
- 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{
- ogg_int64_t prev_page_gp;
- /*We might get lucky and already have the packet with the target
- buffered.
- Worth checking.
- For very small files (with all of the data in a single page,
- 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)));
- 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);
- }
- /*No such luck.
- Check if we can cut off at least half the range, though.*/
- if(offset-begin<=end-begin>>1||diff<OP_CUR_TIME_THRESH){
- /*We really want the page start here, but this will do.*/
- end=boundary=offset;
- pcm_end=gp;
- }
- }
- }
- }
- }
-#endif
- }
- /*This code was originally based on the "new search algorithm by HB (Nicholas
- 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;
- while(begin<end){
- opus_int64 bisect;
- opus_int64 next_boundary;
- opus_int32 chunk_size;
- if(end-begin<OP_CHUNK_SIZE)bisect=begin;
- else{
- /*Update the interval size history.*/
- d0=d1>>1;
- d1=d2>>1;
- d2=end-begin>>1;
- if(force_bisect)bisect=begin+(end-begin>>1);
- else{
- 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.*/
- bisect=begin+op_rescale64(diff,diff2,end-begin)-OP_CHUNK_SIZE;
- }
- if(bisect-OP_CHUNK_SIZE<begin)bisect=begin;
- force_bisect=0;
- }
- if(bisect!=_of->offset){
- /*Discard any buffered continued packet data.*/
- if(buffering)ogg_stream_reset(&_of->os);
- buffering=0;
- page_offset=-1;
- ret=op_seek_helper(_of,bisect);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- chunk_size=OP_CHUNK_SIZE;
- next_boundary=boundary;
- /*Now scan forward and figure out where we landed.
- In the ideal case, we will see a page with a granule position at or
- before our target, followed by a page with a granule position after our
- target (or the end of the search interval).
- Then we can just drop out and will have all of the data we need with no
- additional seeking.
- If we landed too far before, or after, we'll break out and do another
- bisection.*/
- while(begin<end){
- page_offset=op_get_next_page(_of,&og,boundary);
- if(page_offset<0){
- if(page_offset<OP_FALSE)return (int)page_offset;
- /*There are no more pages in our interval from our stream with a valid
- timestamp that start at position bisect or later.*/
- /*If we scanned the whole interval, we're done.*/
- if(bisect<=begin+1)end=begin;
- else{
- /*Otherwise, back up one chunk.
- First, discard any data from a continued packet.*/
- if(buffering)ogg_stream_reset(&_of->os);
- buffering=0;
- bisect=OP_MAX(bisect-chunk_size,begin);
- ret=op_seek_helper(_of,bisect);
- if(OP_UNLIKELY(ret<0))return ret;
- /*Bump up the chunk size.*/
- chunk_size=OP_MIN(2*chunk_size,OP_CHUNK_SIZE_MAX);
- /*If we did find a page from another stream or without a timestamp,
- don't read past it.*/
- boundary=next_boundary;
- }
- }
- 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;
- 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.*/
- begin=_of->offset;
- if(OP_UNLIKELY(op_granpos_cmp(pcm_start,gp)>0)
- ||OP_UNLIKELY(op_granpos_cmp(pcm_end,gp)<0)){
- /*Don't let pcm_start get out of range!
- That could happen with an invalid timestamp.*/
- break;
- }
- /*Save the byte offset of the end of the page with this granule
- position.*/
- 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
- do another bisection.*/
- if(diff>48000)break;
- /*Otherwise, keep scanning forward (do NOT use begin+1).*/
- bisect=begin;
- }
- else{
- /*We found a page that ends after our target.*/
- /*If we scanned the whole interval before we found it, we're done.*/
- if(bisect<=begin+1)end=begin;
- else{
- end=bisect;
- /*In later iterations, don't read past the first page we found.*/
- boundary=next_boundary;
- /*If we're not making much progress shrinking the interval size,
- start forcing straight bisection to limit the worst case.*/
- force_bisect=end-begin>d0*2;
- /*Don't let pcm_end get out of range!
- That could happen with an invalid timestamp.*/
- if(OP_LIKELY(op_granpos_cmp(pcm_end,gp)>0)
- &&OP_LIKELY(op_granpos_cmp(pcm_start,gp)<=0)){
- pcm_end=gp;
- }
- break;
- }
- }
- }
- }
- }
- /*Found our page.*/
- OP_ASSERT(op_granpos_cmp(best_gp,pcm_start)>=0);
- /*Seek, if necessary.
- If we were buffering data from a continued packet, we should be able to
- continue to scan forward to get the rest of the data (even if
- page_offset==-1).
- Otherwise, we need to seek back to best_start.*/
- if(!buffering){
- if(best_start!=page_offset){
- page_offset=-1;
- ret=op_seek_helper(_of,best_start);
- if(OP_UNLIKELY(ret<0))return ret;
- }
- if(best_start<best){
- /*Retrieve the page at best_start, if we do not already have it.*/
- if(page_offset<0){
- page_offset=op_get_next_page(_of,&og,link->end_offset);
- if(OP_UNLIKELY(page_offset<OP_FALSE))return (int)page_offset;
- if(OP_UNLIKELY(page_offset!=best_start))return OP_EBADLINK;
- }
- op_buffer_continued_data(_of,&og);
- page_offset=-1;
- }
- }
- /*Update prev_packet_gp to allow per-packet granule position assignment.*/
- _of->prev_packet_gp=best_gp;
- _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;
-}
-
-int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset){
- const OggOpusLink *link;
- ogg_int64_t pcm_start;
- ogg_int64_t target_gp;
- ogg_int64_t prev_packet_gp;
- ogg_int64_t skip;
- ogg_int64_t diff;
- int op_count;
- int op_pos;
- int ret;
- int li;
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- if(OP_UNLIKELY(!_of->seekable))return OP_ENOSEEK;
- if(OP_UNLIKELY(_pcm_offset<0))return OP_EINVAL;
- target_gp=op_get_granulepos(_of,_pcm_offset,&li);
- if(OP_UNLIKELY(target_gp==-1))return OP_EINVAL;
- link=_of->links+li;
- pcm_start=link->pcm_start;
- OP_ALWAYS_TRUE(!op_granpos_diff(&_pcm_offset,target_gp,pcm_start));
-#if !defined(OP_SMALL_FOOTPRINT)
- /*For small (90 ms or less) forward seeks within the same link, just decode
- forward.
- This also optimizes the case of seeking to the current position.*/
- if(li==_of->cur_link&&_of->ready_state>=OP_INITSET){
- ogg_int64_t gp;
- gp=_of->prev_packet_gp;
- if(OP_LIKELY(gp!=-1)){
- int nbuffered;
- nbuffered=OP_MAX(_of->od_buffer_size-_of->od_buffer_pos,0);
- OP_ALWAYS_TRUE(!op_granpos_add(&gp,gp,-nbuffered));
- /*We do _not_ add cur_discard_count to gp.
- Otherwise the total amount to discard could grow without bound, and it
- would be better just to do a full seek.*/
- if(OP_LIKELY(!op_granpos_diff(&diff,gp,pcm_start))){
- ogg_int64_t discard_count;
- discard_count=_pcm_offset-diff;
- /*We use a threshold of 90 ms instead of 80, since 80 ms is the
- _minimum_ we would have discarded after a full seek.
- Assuming 20 ms frames (the default), we'd discard 90 ms on average.*/
- if(discard_count>=0&&OP_UNLIKELY(discard_count<90*48)){
- _of->cur_discard_count=(opus_int32)discard_count;
- return 0;
- }
- }
- }
- }
-#endif
- ret=op_pcm_seek_page(_of,target_gp,li);
- if(OP_UNLIKELY(ret<0))return ret;
- /*Now skip samples until we actually get to our target.*/
- /*Figure out where we should skip to.*/
- if(_pcm_offset<=link->head.pre_skip)skip=0;
- else skip=OP_MAX(_pcm_offset-80*48,0);
- OP_ASSERT(_pcm_offset-skip>=0);
- OP_ASSERT(_pcm_offset-skip<OP_INT32_MAX-120*48);
- /*Skip packets until we find one with samples past our skip target.*/
- for(;;){
- op_count=_of->op_count;
- prev_packet_gp=_of->prev_packet_gp;
- for(op_pos=_of->op_pos;op_pos<op_count;op_pos++){
- ogg_int64_t cur_packet_gp;
- cur_packet_gp=_of->op[op_pos].granulepos;
- if(OP_LIKELY(!op_granpos_diff(&diff,cur_packet_gp,pcm_start))
- &&diff>skip){
- break;
- }
- prev_packet_gp=cur_packet_gp;
- }
- _of->prev_packet_gp=prev_packet_gp;
- _of->op_pos=op_pos;
- if(op_pos<op_count)break;
- /*We skipped all the packets on this page.
- Fetch another.*/
- ret=op_fetch_and_process_page(_of,NULL,-1,0,1);
- if(OP_UNLIKELY(ret<0))return OP_EBADLINK;
- }
- OP_ALWAYS_TRUE(!op_granpos_diff(&diff,prev_packet_gp,pcm_start));
- /*We skipped too far.
- Either the timestamps were illegal or there was a hole in the data.*/
- if(diff>skip)return OP_EBADLINK;
- OP_ASSERT(_pcm_offset-diff<OP_INT32_MAX);
- /*TODO: If there are further holes/illegal timestamps, we still won't decode
- to the correct sample.
- However, at least op_pcm_tell() will report the correct value immediately
- after returning.*/
- _of->cur_discard_count=(opus_int32)(_pcm_offset-diff);
- return 0;
-}
-
-opus_int64 op_raw_tell(const OggOpusFile *_of){
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- return _of->offset;
-}
-
-/*Convert a granule position from a given link to a PCM offset relative to the
- start of the whole stream.
- For unseekable sources, this gets reset to 0 at the beginning of each link.*/
-static ogg_int64_t op_get_pcm_offset(const OggOpusFile *_of,
- ogg_int64_t _gp,int _li){
- const OggOpusLink *links;
- ogg_int64_t pcm_offset;
- ogg_int64_t delta;
- int li;
- links=_of->links;
- pcm_offset=0;
- OP_ASSERT(_li<_of->nlinks);
- for(li=0;li<_li;li++){
- OP_ALWAYS_TRUE(!op_granpos_diff(&delta,
- links[li].pcm_end,links[li].pcm_start));
- delta-=links[li].head.pre_skip;
- pcm_offset+=delta;
- }
- OP_ASSERT(_li>=0);
- if(_of->seekable&&OP_UNLIKELY(op_granpos_cmp(_gp,links[_li].pcm_end)>0)){
- _gp=links[_li].pcm_end;
- }
- if(OP_LIKELY(op_granpos_cmp(_gp,links[_li].pcm_start)>0)){
- if(OP_UNLIKELY(op_granpos_diff(&delta,_gp,links[_li].pcm_start)<0)){
- /*This means an unseekable stream claimed to have a page from more than
- 2 billion days after we joined.*/
- OP_ASSERT(!_of->seekable);
- return OP_INT64_MAX;
- }
- if(delta<links[_li].head.pre_skip)delta=0;
- else delta-=links[_li].head.pre_skip;
- /*In the seekable case, _gp was limited by pcm_end.
- In the unseekable case, pcm_offset should be 0.*/
- OP_ASSERT(pcm_offset<=OP_INT64_MAX-delta);
- pcm_offset+=delta;
- }
- return pcm_offset;
-}
-
-ogg_int64_t op_pcm_tell(const OggOpusFile *_of){
- ogg_int64_t gp;
- int nbuffered;
- int li;
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- gp=_of->prev_packet_gp;
- if(gp==-1)return 0;
- nbuffered=OP_MAX(_of->od_buffer_size-_of->od_buffer_pos,0);
- OP_ALWAYS_TRUE(!op_granpos_add(&gp,gp,-nbuffered));
- li=_of->seekable?_of->cur_link:0;
- if(op_granpos_add(&gp,gp,_of->cur_discard_count)<0){
- gp=_of->links[li].pcm_end;
- }
- return op_get_pcm_offset(_of,gp,li);
-}
-
-void op_set_decode_callback(OggOpusFile *_of,
- op_decode_cb_func _decode_cb,void *_ctx){
- _of->decode_cb=_decode_cb;
- _of->decode_cb_ctx=_ctx;
-}
-
-int op_set_gain_offset(OggOpusFile *_of,
- int _gain_type,opus_int32 _gain_offset_q8){
- if(_gain_type!=OP_HEADER_GAIN&&_gain_type!=OP_ALBUM_GAIN
- &&_gain_type!=OP_TRACK_GAIN&&_gain_type!=OP_ABSOLUTE_GAIN){
- return OP_EINVAL;
- }
- _of->gain_type=_gain_type;
- /*The sum of header gain and track gain lies in the range [-65536,65534].
- These bounds allow the offset to set the final value to anywhere in the
- range [-32768,32767], which is what we'll clamp it to before applying.*/
- _of->gain_offset_q8=OP_CLAMP(-98302,_gain_offset_q8,98303);
- op_update_gain(_of);
- return 0;
-}
-
-void op_set_dither_enabled(OggOpusFile *_of,int _enabled){
-#if !defined(OP_FIXED_POINT)
- _of->dither_disabled=!_enabled;
- if(!_enabled)_of->dither_mute=65;
-#endif
-}
-
-/*Allocate the decoder scratch buffer.
- This is done lazily, since if the user provides large enough buffers, we'll
- never need it.*/
-static int op_init_buffer(OggOpusFile *_of){
- int nchannels_max;
- if(_of->seekable){
- const OggOpusLink *links;
- int nlinks;
- int li;
- links=_of->links;
- nlinks=_of->nlinks;
- nchannels_max=1;
- for(li=0;li<nlinks;li++){
- nchannels_max=OP_MAX(nchannels_max,links[li].head.channel_count);
- }
- }
- else nchannels_max=OP_NCHANNELS_MAX;
- _of->od_buffer=(op_sample *)_ogg_malloc(
- sizeof(*_of->od_buffer)*nchannels_max*120*48);
- if(_of->od_buffer==NULL)return OP_EFAULT;
- return 0;
-}
-
-/*Decode a single packet into the target buffer.*/
-static int op_decode(OggOpusFile *_of,op_sample *_pcm,
- const ogg_packet *_op,int _nsamples,int _nchannels){
- int ret;
- /*First we try using the application-provided decode callback.*/
- if(_of->decode_cb!=NULL){
-#if defined(OP_FIXED_POINT)
- ret=(*_of->decode_cb)(_of->decode_cb_ctx,_of->od,_pcm,_op,
- _nsamples,_nchannels,OP_DEC_FORMAT_SHORT,_of->cur_link);
-#else
- ret=(*_of->decode_cb)(_of->decode_cb_ctx,_of->od,_pcm,_op,
- _nsamples,_nchannels,OP_DEC_FORMAT_FLOAT,_of->cur_link);
-#endif
- }
- else ret=OP_DEC_USE_DEFAULT;
- /*If the application didn't want to handle decoding, do it ourselves.*/
- if(ret==OP_DEC_USE_DEFAULT){
-#if defined(OP_FIXED_POINT)
- ret=opus_multistream_decode(_of->od,
- _op->packet,_op->bytes,_pcm,_nsamples,0);
-#else
- ret=opus_multistream_decode_float(_of->od,
- _op->packet,_op->bytes,_pcm,_nsamples,0);
-#endif
- OP_ASSERT(ret<0||ret==_nsamples);
- }
- /*If the application returned a positive value other than 0 or
- OP_DEC_USE_DEFAULT, fail.*/
- else if(OP_UNLIKELY(ret>0))return OP_EBADPACKET;
- if(OP_UNLIKELY(ret<0))return OP_EBADPACKET;
- return ret;
-}
-
-/*Read more samples from the stream, using the same API as op_read() or
- op_read_float().*/
-static int op_read_native(OggOpusFile *_of,
- op_sample *_pcm,int _buf_size,int *_li){
- if(OP_UNLIKELY(_of->ready_state<OP_OPENED))return OP_EINVAL;
- for(;;){
- int ret;
- if(OP_LIKELY(_of->ready_state>=OP_INITSET)){
- int nchannels;
- int od_buffer_pos;
- int nsamples;
- int op_pos;
- nchannels=_of->links[_of->seekable?_of->cur_link:0].head.channel_count;
- od_buffer_pos=_of->od_buffer_pos;
- nsamples=_of->od_buffer_size-od_buffer_pos;
- /*If we have buffered samples, return them.*/
- if(nsamples>0){
- if(nsamples*nchannels>_buf_size)nsamples=_buf_size/nchannels;
- memcpy(_pcm,_of->od_buffer+nchannels*od_buffer_pos,
- sizeof(*_pcm)*nchannels*nsamples);
- od_buffer_pos+=nsamples;
- _of->od_buffer_pos=od_buffer_pos;
- if(_li!=NULL)*_li=_of->cur_link;
- return nsamples;
- }
- /*If we have buffered packets, decode one.*/
- op_pos=_of->op_pos;
- if(OP_LIKELY(op_pos<_of->op_count)){
- const ogg_packet *pop;
- ogg_int64_t diff;
- opus_int32 cur_discard_count;
- int duration;
- int trimmed_duration;
- pop=_of->op+op_pos++;
- _of->op_pos=op_pos;
- cur_discard_count=_of->cur_discard_count;
- duration=op_get_packet_duration(pop->packet,pop->bytes);
- /*We don't buffer packets with an invalid TOC sequence.*/
- OP_ASSERT(duration>0);
- trimmed_duration=duration;
- /*Perform end-trimming.*/
- if(OP_UNLIKELY(pop->e_o_s)){
- if(OP_UNLIKELY(op_granpos_cmp(pop->granulepos,
- _of->prev_packet_gp)<=0)){
- trimmed_duration=0;
- }
- else if(OP_LIKELY(!op_granpos_diff(&diff,
- pop->granulepos,_of->prev_packet_gp))){
- trimmed_duration=(int)OP_MIN(diff,trimmed_duration);
- }
- }
- _of->prev_packet_gp=pop->granulepos;
- if(OP_UNLIKELY(duration*nchannels>_buf_size)){
- op_sample *buf;
- /*If the user's buffer is too small, decode into a scratch buffer.*/
- buf=_of->od_buffer;
- if(OP_UNLIKELY(buf==NULL)){
- ret=op_init_buffer(_of);
- if(OP_UNLIKELY(ret<0))return ret;
- buf=_of->od_buffer;
- }
- ret=op_decode(_of,buf,pop,duration,nchannels);
- if(OP_UNLIKELY(ret<0))return ret;
- /*Perform pre-skip/pre-roll.*/
- od_buffer_pos=(int)OP_MIN(trimmed_duration,cur_discard_count);
- cur_discard_count-=od_buffer_pos;
- _of->cur_discard_count=cur_discard_count;
- _of->od_buffer_pos=od_buffer_pos;
- _of->od_buffer_size=trimmed_duration;
- /*Update bitrate tracking based on the actual samples we used from
- what was decoded.*/
- _of->bytes_tracked+=pop->bytes;
- _of->samples_tracked+=trimmed_duration-od_buffer_pos;
- }
- else{
- /*Otherwise decode directly into the user's buffer.*/
- ret=op_decode(_of,_pcm,pop,duration,nchannels);
- if(OP_UNLIKELY(ret<0))return ret;
- if(OP_LIKELY(trimmed_duration>0)){
- /*Perform pre-skip/pre-roll.*/
- od_buffer_pos=(int)OP_MIN(trimmed_duration,cur_discard_count);
- cur_discard_count-=od_buffer_pos;
- _of->cur_discard_count=cur_discard_count;
- trimmed_duration-=od_buffer_pos;
- if(OP_LIKELY(trimmed_duration>0)
- &&OP_UNLIKELY(od_buffer_pos>0)){
- memmove(_pcm,_pcm+od_buffer_pos*nchannels,
- sizeof(*_pcm)*trimmed_duration*nchannels);
- }
- /*Update bitrate tracking based on the actual samples we used from
- what was decoded.*/
- _of->bytes_tracked+=pop->bytes;
- _of->samples_tracked+=trimmed_duration;
- if(OP_LIKELY(trimmed_duration>0)){
- if(_li!=NULL)*_li=_of->cur_link;
- return trimmed_duration;
- }
- }
- }
- /*Don't grab another page yet.
- This one might have more packets, or might have buffered data now.*/
- continue;
- }
- }
- /*Suck in another page.*/
- 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;
- }
- if(OP_UNLIKELY(ret<0))return ret;
- }
-}
-
-/*A generic filter to apply to the decoded audio data.
- _src is non-const because we will destructively modify the contents of the
- source buffer that we consume in some cases.*/
-typedef int (*op_read_filter_func)(OggOpusFile *_of,void *_dst,int _dst_sz,
- op_sample *_src,int _nsamples,int _nchannels);
-
-/*Decode some samples and then apply a custom filter to them.
- This is used to convert to different output formats.*/
-static int op_filter_read_native(OggOpusFile *_of,void *_dst,int _dst_sz,
- op_read_filter_func _filter,int *_li){
- int ret;
- /*Ensure we have some decoded samples in our buffer.*/
- ret=op_read_native(_of,NULL,0,_li);
- /*Now apply the filter to them.*/
- if(OP_LIKELY(ret>=0)&&OP_LIKELY(_of->ready_state>=OP_INITSET)){
- int od_buffer_pos;
- od_buffer_pos=_of->od_buffer_pos;
- ret=_of->od_buffer_size-od_buffer_pos;
- if(OP_LIKELY(ret>0)){
- int nchannels;
- nchannels=_of->links[_of->seekable?_of->cur_link:0].head.channel_count;
- ret=(*_filter)(_of,_dst,_dst_sz,
- _of->od_buffer+nchannels*od_buffer_pos,ret,nchannels);
- OP_ASSERT(ret>=0);
- OP_ASSERT(ret<=_of->od_buffer_size-od_buffer_pos);
- od_buffer_pos+=ret;
- _of->od_buffer_pos=od_buffer_pos;
- }
- }
- return ret;
-}
-
-#if !defined(OP_FIXED_POINT)||!defined(OP_DISABLE_FLOAT_API)
-
-/*Matrices for downmixing from the supported channel counts to stereo.
- The matrices with 5 or more channels are normalized to a total volume of 2.0,
- since most mixes sound too quiet if normalized to 1.0 (as there is generally
- little volume in the side/rear channels).*/
-static const float OP_STEREO_DOWNMIX[OP_NCHANNELS_MAX-2][OP_NCHANNELS_MAX][2]={
- /*3.0*/
- {
- {0.5858F,0.0F},{0.4142F,0.4142F},{0.0F,0.5858F}
- },
- /*quadrophonic*/
- {
- {0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F}
- },
- /*5.0*/
- {
- {0.651F,0.0F},{0.46F,0.46F},{0.0F,0.651F},{0.5636F,0.3254F},
- {0.3254F,0.5636F}
- },
- /*5.1*/
- {
- {0.529F,0.0F},{0.3741F,0.3741F},{0.0F,0.529F},{0.4582F,0.2645F},
- {0.2645F,0.4582F},{0.3741F,0.3741F}
- },
- /*6.1*/
- {
- {0.4553F,0.0F},{0.322F,0.322F},{0.0F,0.4553F},{0.3943F,0.2277F},
- {0.2277F,0.3943F},{0.2788F,0.2788F},{0.322F,0.322F}
- },
- /*7.1*/
- {
- {0.3886F,0.0F},{0.2748F,0.2748F},{0.0F,0.3886F},{0.3366F,0.1943F},
- {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F},{0.2748F,0.2748F}
- }
-};
-
-#endif
-
-#if defined(OP_FIXED_POINT)
-
-/*Matrices for downmixing from the supported channel counts to stereo.
- The matrices with 5 or more channels are normalized to a total volume of 2.0,
- since most mixes sound too quiet if normalized to 1.0 (as there is generally
- little volume in the side/rear channels).
- Hence we keep the coefficients in Q14, so the downmix values won't overflow a
- 32-bit number.*/
-static const opus_int16 OP_STEREO_DOWNMIX_Q14
- [OP_NCHANNELS_MAX-2][OP_NCHANNELS_MAX][2]={
- /*3.0*/
- {
- {9598,0},{6786,6786},{0,9598}
- },
- /*quadrophonic*/
- {
- {6924,0},{0,6924},{5996,3464},{3464,5996}
- },
- /*5.0*/
- {
- {10666,0},{7537,7537},{0,10666},{9234,5331},{5331,9234}
- },
- /*5.1*/
- {
- {8668,0},{6129,6129},{0,8668},{7507,4335},{4335,7507},{6129,6129}
- },
- /*6.1*/
- {
- {7459,0},{5275,5275},{0,7459},{6460,3731},{3731,6460},{4568,4568},
- {5275,5275}
- },
- /*7.1*/
- {
- {6368,0},{4502,4502},{0,6368},{5515,3183},{3183,5515},{5515,3183},
- {3183,5515},{4502,4502}
- }
-};
-
-int op_read(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size,int *_li){
- return op_read_native(_of,_pcm,_buf_size,_li);
-}
-
-static int op_stereo_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
- op_sample *_src,int _nsamples,int _nchannels){
- (void)_of;
- _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
- if(_nchannels==2)memcpy(_dst,_src,_nsamples*2*sizeof(*_src));
- else{
- opus_int16 *dst;
- int i;
- dst=(opus_int16 *)_dst;
- if(_nchannels==1){
- for(i=0;i<_nsamples;i++)dst[2*i+0]=dst[2*i+1]=_src[i];
- }
- else{
- for(i=0;i<_nsamples;i++){
- opus_int32 l;
- opus_int32 r;
- int ci;
- l=r=0;
- for(ci=0;ci<_nchannels;ci++){
- opus_int32 s;
- s=_src[_nchannels*i+ci];
- l+=OP_STEREO_DOWNMIX_Q14[_nchannels-3][ci][0]*s;
- r+=OP_STEREO_DOWNMIX_Q14[_nchannels-3][ci][1]*s;
- }
- /*TODO: For 5 or more channels, we should do soft clipping here.*/
- dst[2*i+0]=(opus_int16)OP_CLAMP(-32768,l+8192>>14,32767);
- dst[2*i+1]=(opus_int16)OP_CLAMP(-32768,r+8192>>14,32767);
- }
- }
- }
- return _nsamples;
-}
-
-int op_read_stereo(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size){
- return op_filter_read_native(_of,_pcm,_buf_size,op_stereo_filter,NULL);
-}
-
-# if !defined(OP_DISABLE_FLOAT_API)
-
-static int op_short2float_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
- op_sample *_src,int _nsamples,int _nchannels){
- float *dst;
- int i;
- (void)_of;
- dst=(float *)_dst;
- if(OP_UNLIKELY(_nsamples*_nchannels>_dst_sz))_nsamples=_dst_sz/_nchannels;
- _dst_sz=_nsamples*_nchannels;
- for(i=0;i<_dst_sz;i++)dst[i]=(1.0F/32768)*_src[i];
- return _nsamples;
-}
-
-int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li){
- return op_filter_read_native(_of,_pcm,_buf_size,op_short2float_filter,_li);
-}
-
-static int op_short2float_stereo_filter(OggOpusFile *_of,
- void *_dst,int _dst_sz,op_sample *_src,int _nsamples,int _nchannels){
- float *dst;
- int i;
- dst=(float *)_dst;
- _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
- if(_nchannels==1){
- _nsamples=op_short2float_filter(_of,dst,_nsamples,_src,_nsamples,1);
- for(i=_nsamples;i-->0;)dst[2*i+0]=dst[2*i+1]=dst[i];
- }
- else if(_nchannels<5){
- /*For 3 or 4 channels, we can downmix in fixed point without risk of
- clipping.*/
- if(_nchannels>2){
- _nsamples=op_stereo_filter(_of,_src,_nsamples*2,
- _src,_nsamples,_nchannels);
- }
- return op_short2float_filter(_of,dst,_dst_sz,_src,_nsamples,2);
- }
- else{
- /*For 5 or more channels, we convert to floats and then downmix (so that we
- don't risk clipping).*/
- for(i=0;i<_nsamples;i++){
- float l;
- float r;
- int ci;
- l=r=0;
- for(ci=0;ci<_nchannels;ci++){
- float s;
- s=(1.0F/32768)*_src[_nchannels*i+ci];
- l+=OP_STEREO_DOWNMIX[_nchannels-3][ci][0]*s;
- r+=OP_STEREO_DOWNMIX[_nchannels-3][ci][1]*s;
- }
- dst[2*i+0]=l;
- dst[2*i+1]=r;
- }
- }
- return _nsamples;
-}
-
-int op_read_float_stereo(OggOpusFile *_of,float *_pcm,int _buf_size){
- return op_filter_read_native(_of,_pcm,_buf_size,
- op_short2float_stereo_filter,NULL);
-}
-
-# endif
-
-#else
-
-# if defined(OP_HAVE_LRINTF)
-# include <math.h>
-# define op_float2int(_x) (lrintf(_x))
-# else
-# define op_float2int(_x) ((int)((_x)+((_x)<0?-0.5F:0.5F)))
-# endif
-
-/*The dithering code here is adapted from opusdec, part of opus-tools.
- It was originally written by Greg Maxwell.*/
-
-static opus_uint32 op_rand(opus_uint32 _seed){
- return _seed*96314165+907633515&0xFFFFFFFFU;
-}
-
-/*This implements 16-bit quantization with full triangular dither and IIR noise
- shaping.
- The noise shaping filters were designed by Sebastian Gesemann, and are based
- on the LAME ATH curves with flattening to limit their peak gain to 20 dB.
- Everyone else's noise shaping filters are mildly crazy.
- The 48 kHz version of this filter is just a warped version of the 44.1 kHz
- filter and probably could be improved by shifting the HF shelf up in
- frequency a little bit, since 48 kHz has a bit more room and being more
- conservative against bat-ears is probably more important than more noise
- suppression.
- This process can increase the peak level of the signal (in theory by the peak
- error of 1.5 +20 dB, though that is unobservably rare).
- To avoid clipping, the signal is attenuated by a couple thousandths of a dB.
- Initially, the approach taken here was to only attenuate by the 99.9th
- percentile, making clipping rare but not impossible (like SoX), but the
- limited gain of the filter means that the worst case was only two
- thousandths of a dB more, so this just uses the worst case.
- The attenuation is probably also helpful to prevent clipping in the DAC
- reconstruction filters or downstream resampling, in any case.*/
-
-# define OP_GAIN (32753.0F)
-
-# define OP_PRNG_GAIN (1.0F/0xFFFFFFFF)
-
-/*48 kHz noise shaping filter, sd=2.34.*/
-
-static const float OP_FCOEF_B[4]={
- 2.2374F,-0.7339F,-0.1251F,-0.6033F
-};
-
-static const float OP_FCOEF_A[4]={
- 0.9030F,0.0116F,-0.5853F,-0.2571F
-};
-
-static int op_float2short_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
- float *_src,int _nsamples,int _nchannels){
- opus_int16 *dst;
- int ci;
- int i;
- dst=(opus_int16 *)_dst;
- if(OP_UNLIKELY(_nsamples*_nchannels>_dst_sz))_nsamples=_dst_sz/_nchannels;
-# if defined(OP_SOFT_CLIP)
- if(_of->state_channel_count!=_nchannels){
- for(ci=0;ci<_nchannels;ci++)_of->clip_state[ci]=0;
- }
- opus_pcm_soft_clip(_src,_nsamples,_nchannels,_of->clip_state);
-# endif
- if(_of->dither_disabled){
- for(i=0;i<_nchannels*_nsamples;i++){
- dst[i]=op_float2int(OP_CLAMP(-32768,32768.0F*_src[i],32767));
- }
- }
- else{
- opus_uint32 seed;
- int mute;
- seed=_of->dither_seed;
- mute=_of->dither_mute;
- if(_of->state_channel_count!=_nchannels)mute=65;
- /*In order to avoid replacing digital silence with quiet dither noise, we
- mute if the output has been silent for a while.*/
- if(mute>64)memset(_of->dither_a,0,sizeof(*_of->dither_a)*4*_nchannels);
- for(i=0;i<_nsamples;i++){
- int silent;
- silent=1;
- for(ci=0;ci<_nchannels;ci++){
- float r;
- float s;
- float err;
- int si;
- int j;
- s=_src[_nchannels*i+ci];
- silent&=s==0;
- s*=OP_GAIN;
- err=0;
- for(j=0;j<4;j++){
- err+=OP_FCOEF_B[j]*_of->dither_b[ci*4+j]
- -OP_FCOEF_A[j]*_of->dither_a[ci*4+j];
- }
- for(j=3;j-->0;)_of->dither_a[ci*4+j+1]=_of->dither_a[ci*4+j];
- for(j=3;j-->0;)_of->dither_b[ci*4+j+1]=_of->dither_b[ci*4+j];
- _of->dither_a[ci*4]=err;
- s-=err;
- if(mute>16)r=0;
- else{
- seed=op_rand(seed);
- r=seed*OP_PRNG_GAIN;
- seed=op_rand(seed);
- r-=seed*OP_PRNG_GAIN;
- }
- /*Clamp in float out of paranoia that the input will be > 96 dBFS and
- wrap if the integer is clamped.*/
- si=op_float2int(OP_CLAMP(-32768,s+r,32767));
- dst[_nchannels*i+ci]=(opus_int16)si;
- /*Including clipping in the noise shaping is generally disastrous: the
- futile effort to restore the clipped energy results in more clipping.
- However, small amounts---at the level which could normally be created
- by dither and rounding---are harmless and can even reduce clipping
- somewhat due to the clipping sometimes reducing the dither + rounding
- error.*/
- _of->dither_b[ci*4]=mute>16?0:OP_CLAMP(-1.5F,si-s,1.5F);
- }
- mute++;
- if(!silent)mute=0;
- }
- _of->dither_mute=OP_MIN(mute,65);
- _of->dither_seed=seed;
- }
- _of->state_channel_count=_nchannels;
- return _nsamples;
-}
-
-int op_read(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size,int *_li){
- return op_filter_read_native(_of,_pcm,_buf_size,op_float2short_filter,_li);
-}
-
-int op_read_float(OggOpusFile *_of,float *_pcm,int _buf_size,int *_li){
- _of->state_channel_count=0;
- return op_read_native(_of,_pcm,_buf_size,_li);
-}
-
-static int op_stereo_filter(OggOpusFile *_of,void *_dst,int _dst_sz,
- op_sample *_src,int _nsamples,int _nchannels){
- (void)_of;
- _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
- if(_nchannels==2)memcpy(_dst,_src,_nsamples*2*sizeof(*_src));
- else{
- float *dst;
- int i;
- dst=(float *)_dst;
- if(_nchannels==1){
- for(i=0;i<_nsamples;i++)dst[2*i+0]=dst[2*i+1]=_src[i];
- }
- else{
- for(i=0;i<_nsamples;i++){
- float l;
- float r;
- int ci;
- l=r=0;
- for(ci=0;ci<_nchannels;ci++){
- l+=OP_STEREO_DOWNMIX[_nchannels-3][ci][0]*_src[_nchannels*i+ci];
- r+=OP_STEREO_DOWNMIX[_nchannels-3][ci][1]*_src[_nchannels*i+ci];
- }
- dst[2*i+0]=l;
- dst[2*i+1]=r;
- }
- }
- }
- return _nsamples;
-}
-
-static int op_float2short_stereo_filter(OggOpusFile *_of,
- void *_dst,int _dst_sz,op_sample *_src,int _nsamples,int _nchannels){
- opus_int16 *dst;
- dst=(opus_int16 *)_dst;
- if(_nchannels==1){
- int i;
- _nsamples=op_float2short_filter(_of,dst,_dst_sz>>1,_src,_nsamples,1);
- for(i=_nsamples;i-->0;)dst[2*i+0]=dst[2*i+1]=dst[i];
- }
- else{
- if(_nchannels>2){
- _nsamples=OP_MIN(_nsamples,_dst_sz>>1);
- _nsamples=op_stereo_filter(_of,_src,_nsamples*2,
- _src,_nsamples,_nchannels);
- }
- _nsamples=op_float2short_filter(_of,dst,_dst_sz,_src,_nsamples,2);
- }
- return _nsamples;
-}
-
-int op_read_stereo(OggOpusFile *_of,opus_int16 *_pcm,int _buf_size){
- return op_filter_read_native(_of,_pcm,_buf_size,
- op_float2short_stereo_filter,NULL);
-}
-
-int op_read_float_stereo(OggOpusFile *_of,float *_pcm,int _buf_size){
- _of->state_channel_count=0;
- return op_filter_read_native(_of,_pcm,_buf_size,op_stereo_filter,NULL);
-}
-
-#endif