mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-03-24 14:49:27 +00:00
added libraries: opus flac libsndfile updated: libvorbis libogg openal - Everything works as expected for now. Bare in mind libsndfile needed the check for whether or not it could find the xiph libraries removed in order for this to work.
1823 lines
59 KiB
C
1823 lines
59 KiB
C
/*
|
|
** Copyright (C) 2013-2020 Erik de Castro Lopo <erikd@mega-nerd.com>
|
|
** Copyright (C) 2018 Arthur Taylor <art@ified.ca>
|
|
**
|
|
** This program is free software ; you can redistribute it and/or modify
|
|
** it under the terms of the GNU Lesser General Public License as published by
|
|
** the Free Software Foundation ; either version 2.1 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY ; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU Lesser General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU Lesser General Public License
|
|
** along with this program ; if not, write to the Free Software
|
|
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
** This file contains code based on OpusFile and Opus-Tools, both by
|
|
** Xiph.Org. COPYING from each is identical and is as follows:
|
|
**
|
|
** Copyright (c) 1994-2013 Xiph.Org Foundation and contributors
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** - Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
**
|
|
** - Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
**
|
|
** - Neither the name of the Xiph.Org Foundation nor the names of its
|
|
** contributors may be used to endorse or promote products derived from
|
|
** this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
** ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
|
|
** OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
** TODO:
|
|
** - Channel mapping modification / reporting
|
|
** - connect psf->channel_map and Opus channel mapping somehow?
|
|
** - Gain parameters and their mappings
|
|
*/
|
|
|
|
/*
|
|
** Opus Sample, Frame, and Samples/Channel Terminology
|
|
**
|
|
** libsndfile refers to one PCM value as a 'sample,' and a group of samples of
|
|
** the same sample time, one for each channel, as a 'frame.' This differs from
|
|
** Opus, which has no corresponding name for sample, and refers to a group of
|
|
** PCM values, one per channel (aka libsndfile frames) as 'samples.'
|
|
** Further, Opus has an object called a 'frame' that is made up of multiple
|
|
** Opus-samples.
|
|
** All this means that one has to be careful with what is meant by each term.
|
|
** In an attempt to avoid ambiguity, this file adopts the following terms:
|
|
** - Samples shall refer to discrete PCM values, regardless of any channel
|
|
** considerations. This is the same as what libsndfile calls samples.
|
|
** - Samples/channel shall refer to groups of samples, one for each channel.
|
|
** This is what Opus calles samples, and what libsndfile calles frames. It
|
|
** has the advantage that its name is also the formula to calculate it.
|
|
**
|
|
**
|
|
** Opus vs OggOpus
|
|
**
|
|
** In this file a distinction is made between Opus and OggOpus. Opus refers to
|
|
** the codec alone, support for which is by libopus. OggOpus refers to an Opus
|
|
** payload encapsulated in an Ogg stream. This is also know as an "Opus file."
|
|
** The OggOpus spec includes information on header and granule position
|
|
** interpretation, which is outside of the scope of the Opus spec. As such, an
|
|
** attempt here is made to refer to either Opus or OggOpus depending on which
|
|
** spec is being referenced. See https://wiki.xiph.org/OggOpus
|
|
**
|
|
**
|
|
** Opus Sample Rates
|
|
**
|
|
** Opus only supports a fixed number of sample rates: 48kHz, 24kHz, 16kHz,
|
|
** 12kHz, 8kHz. Audio may be decoded or encoded at any of these rates,
|
|
** independent of the rate it was encoded at or to be decoded at respectively.
|
|
** Other sample rates must be converted to one of these rates.
|
|
**
|
|
** As 44.1kHz (CD sample rate) and 22.5kHz are popular sample rates, and to
|
|
** support any other sample rate there may be, the Opus header includes a field
|
|
** to save the input (original) sample rate before converting it to a supported
|
|
** one. Implementations are recommended by the Opus spec to do a sample rate
|
|
** conversion at encode, but decode at 48kHz if outputting to hardware, or do
|
|
** the reverse sample rate conversion if outputting to file.
|
|
**
|
|
** Heretofore libsndfile does not contain a sample rate converter, so doing the
|
|
** sample rate conversion is not supported. Instead audio must be provided by
|
|
** the user at a supported rate. However, the input sample rate field can be
|
|
** set and retrieved by the user using sf_command(). At decode we choose to
|
|
** decode at the lowest valid rate that is greater than or equal to the input
|
|
** sample rate.
|
|
**
|
|
**
|
|
** OggOpus Granule Positions
|
|
**
|
|
** Ogg streams include a strictly increasing granule position value. The
|
|
** interpretation of this value is dependent on the payload type. For Opus
|
|
** streams the granule position is the count of samples in the stream when
|
|
** encoding/decoding at 48kHz. Note that the actual position of the output
|
|
** sample relative to the granule position is offset by the preskip amount.
|
|
** That is, if a packet ends with a granule position of x, the last sample
|
|
** output when decoding is actually sample (x - preskip).
|
|
**
|
|
** Further, to allow for clipping off of the front of a stream without
|
|
** rewriting all following granule positions, an Opus stream granule position
|
|
** may be offset by a constant amount. This amount is evident by comparing the
|
|
** granule position of the first page of an Opus stream on which an audio
|
|
** packet completes is greater than the sum of the samples of all audio
|
|
** packets completed on the page. Only the first such page is allows to have an
|
|
** 'excessive' granule position, and only if it is not also the last page of
|
|
** the stream (e_o_s bit is not set.)
|
|
**
|
|
** The granule position is an unsigned 64-bit integer, with the special value
|
|
** of UINT64_MAX/-1 being treated as invalid. However, as not all platforms
|
|
** support unsigned 64-bit integers, libOgg uses signed 64-bit integers for the
|
|
** granule position.
|
|
**
|
|
** Remembering that signed integer overflow/underflow is explicitly undefined
|
|
** in C, and as we already assume support for unsigned 64-bit integers, the
|
|
** easiest way to deal with this problem is to modify granule positions as
|
|
** unsigned integers.
|
|
*/
|
|
|
|
|
|
#include "sfconfig.h"
|
|
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
#if HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#else
|
|
#include "sf_unistd.h"
|
|
#endif
|
|
|
|
#include "sndfile.h"
|
|
#include "sfendian.h"
|
|
#include "common.h"
|
|
|
|
#if HAVE_EXTERNAL_XIPH_LIBS
|
|
|
|
#include <ogg/ogg.h>
|
|
#include <opus.h>
|
|
#include <opus_multistream.h>
|
|
|
|
#include "ogg.h"
|
|
#include "ogg_vcomment.h"
|
|
|
|
#define OGG_OPUS_COMMENT_PAD (512) /* Same as oggenc default */
|
|
|
|
/*
|
|
** When encoding, we can choose the size of the Opus frames.
|
|
** Valid values are 2.5, 5, 10, 20, 40, and 60 milliseconds.
|
|
**
|
|
** Frames smaller than 10ms can't use CELT (MDCT) mode.
|
|
** Frames larger than 20ms "are only interesting at fairly low bitrates."
|
|
**
|
|
** We choose the suggested default of 20ms for high-fidelity audio, however,
|
|
** maybe this could be user-selected, or triggered by bitrate command.
|
|
** default for non-realtime of 20ms. While longer packets reduce the overhead
|
|
** data somewhat, it also decreases the quality.
|
|
*/
|
|
#define OGG_OPUS_ENCODE_PACKET_LEN(samplerate) ((20 * (samplerate)) / 1000)
|
|
|
|
/*
|
|
** The pre-roll is how long it takes for the decoder to converge. It converges
|
|
** pretty quickly, to within -40db within 80ms. However, this also depends on
|
|
** the signal. From experimentation, use the conservative pre-roll amount of
|
|
** 660ms after which the output is 32-bit-exact with high probability.
|
|
*/
|
|
#define OGG_OPUS_PREROLL (660 * 48) /* 660 milliseconds (33 packets of 20ms) */
|
|
|
|
typedef struct
|
|
{ uint8_t version ;
|
|
|
|
/* Number of channels, 1...255 */
|
|
uint8_t channels ;
|
|
|
|
/* Encoder latency, the amount to skip before valid data comes out. */
|
|
uint16_t preskip ;
|
|
|
|
/* The sample rate of a the encoded source, as it may have been converted. */
|
|
int32_t input_samplerate ;
|
|
|
|
/* 'baked-in' gain to apply, dB S7.8 format. Should be zero when possible. */
|
|
int16_t gain ;
|
|
|
|
/* Channel mapping type. See OggOpus spec */
|
|
uint8_t channel_mapping ;
|
|
|
|
/* The rest is only used if channel_mapping != 0 */
|
|
/* How many streams are there? */
|
|
uint8_t nb_streams ;
|
|
|
|
/* How man of those streams are coupled? (aka stereo) */
|
|
uint8_t nb_coupled ;
|
|
|
|
/* Mapping of opus streams to output channels */
|
|
uint8_t stream_map [255] ;
|
|
} OpusHeader ;
|
|
|
|
typedef struct
|
|
{ uint32_t serialno ;
|
|
OpusHeader header ;
|
|
|
|
/* Encode: Granule position after the previous packet.
|
|
* Decode: Granule position after the current packet */
|
|
uint64_t pkt_pos ;
|
|
|
|
/* Encode: Granule position at the end of the previous page.
|
|
* Decode: Granule position at the end of the current page. */
|
|
uint64_t pg_pos ;
|
|
|
|
/* integer coefficient of (current sample rate) / 48000Hz */
|
|
int sr_factor ;
|
|
|
|
/* Current position in buffer expressed as samples/channel */
|
|
int loc ;
|
|
|
|
/* Current data fill (decode) or target (encode) of buffer expressed in samples/channel */
|
|
int len ;
|
|
|
|
/* Size of the buffer storage, in sizeof (float) * channels */
|
|
int buffersize ;
|
|
|
|
/* Samples, either decoded from a packet, or assembling for encode. */
|
|
float *buffer ;
|
|
|
|
union {
|
|
/* decode only members */
|
|
struct {
|
|
OpusMSDecoder *state ;
|
|
uint64_t gp_start ;
|
|
uint64_t gp_end ;
|
|
sf_count_t last_offset ;
|
|
} decode ;
|
|
|
|
/* encode only members */
|
|
struct {
|
|
OpusMSEncoder *state ;
|
|
|
|
/* How many Ogg page segments are in Ogg page currently being assembled. */
|
|
int last_segments ;
|
|
|
|
int bitrate ;
|
|
unsigned long latency ;
|
|
|
|
/* Least significant bit of the source (aka bitwidth) */
|
|
int lsb ;
|
|
int lsb_last ;
|
|
} encode ;
|
|
} u ;
|
|
} OPUS_PRIVATE ;
|
|
|
|
/*-----------------------------------------------------------------------------------------------
|
|
** Private function prototypes.
|
|
*/
|
|
|
|
static int ogg_opus_close (SF_PRIVATE *psf) ;
|
|
static void opus_print_header (SF_PRIVATE *psf, OpusHeader *h) ;
|
|
static int opus_read_header_packet (SF_PRIVATE *psf, OpusHeader *h, ogg_packet *opacket) ;
|
|
static int ogg_opus_read_header (SF_PRIVATE * psf) ;
|
|
static int ogg_opus_setup_decoder (SF_PRIVATE *psf, int input_samplerate) ;
|
|
|
|
static int ogg_opus_setup_encoder (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ;
|
|
static int ogg_opus_write_header (SF_PRIVATE * psf, int calc_length) ;
|
|
static void ogg_opus_flush (SF_PRIVATE *psf) ;
|
|
static int ogg_opus_unpack_next_page (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ;
|
|
static int ogg_opus_calculate_page_duration (OGG_PRIVATE *odata) ;
|
|
static int ogg_opus_read_refill (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ;
|
|
static int ogg_opus_write_out (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ;
|
|
|
|
static sf_count_t ogg_opus_read_s (SF_PRIVATE *psf, short *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_read_i (SF_PRIVATE *psf, int *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_read_f (SF_PRIVATE *psf, float *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_read_d (SF_PRIVATE *psf, double *ptr, sf_count_t len) ;
|
|
|
|
static sf_count_t ogg_opus_write_s (SF_PRIVATE *psf, const short *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_write_i (SF_PRIVATE *psf, const int *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_write_f (SF_PRIVATE *psf, const float *ptr, sf_count_t len) ;
|
|
static sf_count_t ogg_opus_write_d (SF_PRIVATE *psf, const double *ptr, sf_count_t len) ;
|
|
|
|
static sf_count_t ogg_opus_seek (SF_PRIVATE *psf, int mode, sf_count_t offset) ;
|
|
static sf_count_t ogg_opus_null_read (SF_PRIVATE *psf, sf_count_t offset) ;
|
|
static sf_count_t ogg_opus_page_seek_manual (SF_PRIVATE *psf, uint64_t target_gp) ;
|
|
static int ogg_opus_page_seek_search (SF_PRIVATE *psf, uint64_t target_gp) ;
|
|
|
|
static int ogg_opus_analyze_file (SF_PRIVATE *psf) ;
|
|
static int ogg_opus_command (SF_PRIVATE *psf, int command, void *data, int datasize) ;
|
|
static int ogg_opus_byterate (SF_PRIVATE *psf) ;
|
|
|
|
/*-----------------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
static vorbiscomment_ident opustags_ident = { "OpusTags", 8 } ;
|
|
|
|
/*-----------------------------------------------------------------------------------------------
|
|
** Exported functions.
|
|
*/
|
|
|
|
int
|
|
ogg_opus_open (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE* odata = psf->container_data ;
|
|
OPUS_PRIVATE* oopus = calloc (1, sizeof (OPUS_PRIVATE)) ;
|
|
int error = 0 ;
|
|
|
|
if (odata == NULL)
|
|
{ psf_log_printf (psf, "%s : odata is NULL???\n", __func__) ;
|
|
free (oopus) ;
|
|
return SFE_INTERNAL ;
|
|
} ;
|
|
|
|
psf->codec_data = oopus ;
|
|
if (oopus == NULL)
|
|
return SFE_MALLOC_FAILED ;
|
|
|
|
if (psf->file.mode == SFM_RDWR)
|
|
return SFE_BAD_MODE_RW ;
|
|
|
|
psf_log_printf (psf, "Opus library version: %s\n", opus_get_version_string ()) ;
|
|
|
|
psf->codec_close = ogg_opus_close ;
|
|
if (psf->file.mode == SFM_READ)
|
|
{ if ((error = ogg_opus_read_header (psf)))
|
|
return error ;
|
|
if ((error = ogg_opus_analyze_file (psf)))
|
|
return error ;
|
|
|
|
psf->read_short = ogg_opus_read_s ;
|
|
psf->read_int = ogg_opus_read_i ;
|
|
psf->read_float = ogg_opus_read_f ;
|
|
psf->read_double = ogg_opus_read_d ;
|
|
} ;
|
|
|
|
if (psf->file.mode == SFM_WRITE)
|
|
{ if ((error = ogg_opus_setup_encoder (psf, odata, oopus)))
|
|
return error ;
|
|
|
|
psf->write_header = ogg_opus_write_header ;
|
|
psf->write_short = ogg_opus_write_s ;
|
|
psf->write_int = ogg_opus_write_i ;
|
|
psf->write_float = ogg_opus_write_f ;
|
|
psf->write_double = ogg_opus_write_d ;
|
|
|
|
psf->sf.frames = SF_COUNT_MAX ; /* Unknown really */
|
|
psf->strings.flags = SF_STR_ALLOW_START ;
|
|
psf->datalength = 0 ;
|
|
psf->dataoffset = 0 ; /* will be updated */
|
|
} ;
|
|
|
|
psf->seek = ogg_opus_seek ;
|
|
psf->command = ogg_opus_command ;
|
|
psf->byterate = ogg_opus_byterate ;
|
|
psf->sf.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
|
|
|
|
return error ;
|
|
} /* ogg_opus_open */
|
|
|
|
/*==============================================================================
|
|
** Private functions.
|
|
*/
|
|
|
|
static int
|
|
ogg_opus_close (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
|
|
if (!oopus)
|
|
return 0 ;
|
|
|
|
if (psf->file.mode == SFM_WRITE)
|
|
{ if (psf->have_written)
|
|
ogg_opus_flush (psf) ;
|
|
else {
|
|
/* Write a header... it is expected. */
|
|
ogg_opus_write_header (psf, 0) ;
|
|
} ;
|
|
ogg_packet_clear (&odata->opacket) ;
|
|
if (oopus->u.encode.state)
|
|
{ opus_multistream_encoder_destroy (oopus->u.encode.state) ;
|
|
oopus->u.encode.state = NULL ;
|
|
} ;
|
|
}
|
|
else if (psf->file.mode == SFM_READ)
|
|
{ if (oopus->u.decode.state)
|
|
{ opus_multistream_decoder_destroy (oopus->u.decode.state) ;
|
|
oopus->u.decode.state = NULL ;
|
|
} ;
|
|
} ;
|
|
|
|
psf->codec_data = NULL ;
|
|
if (oopus->buffer)
|
|
free (oopus->buffer) ;
|
|
free (oopus) ;
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_close */
|
|
|
|
static void
|
|
opus_print_header (SF_PRIVATE *psf, OpusHeader *h)
|
|
{ psf_log_printf (psf, "Opus Header Metadata\n") ;
|
|
psf_log_printf (psf, " OggOpus version : %d\n", (int) h->version) ;
|
|
psf_log_printf (psf, " Channels : %d\n", (int) h->channels) ;
|
|
psf_log_printf (psf, " Preskip : %d samples @48kHz\n", (int) h->preskip) ;
|
|
psf_log_printf (psf, " Input Samplerate : %d Hz\n", (int) h->input_samplerate) ;
|
|
psf_log_printf (psf, " Gain : %d.%d\n", (int) arith_shift_right (h->gain & 0xF0, 8), h->gain & 0x0F) ;
|
|
psf_log_printf (psf, " Channel Mapping : ") ;
|
|
switch (h->channel_mapping)
|
|
{ case 0 : psf_log_printf (psf, "0 (mono or stereo)\n") ; break ;
|
|
case 1 : psf_log_printf (psf, "1 (surround, AC3 channel order)\n") ; break ;
|
|
case 255 : psf_log_printf (psf, "255 (no channel order)\n") ; break ;
|
|
default : psf_log_printf (psf, "%d (unknown or unsupported)\n", (int) h->channel_mapping) ; break ;
|
|
} ;
|
|
|
|
if (h->channel_mapping > 0)
|
|
{ int i ;
|
|
psf_log_printf (psf, " streams total : %d\n", (int) h->nb_streams) ;
|
|
psf_log_printf (psf, " streams coupled : %d\n", (int) h->nb_coupled) ;
|
|
psf_log_printf (psf, " stream mapping : [") ;
|
|
for (i = 0 ; i < h->channels - 1 ; i++)
|
|
psf_log_printf (psf, "%d,", (int) (h->stream_map [i])) ;
|
|
psf_log_printf (psf, "%d]\n", (int) (h->stream_map [i])) ;
|
|
} ;
|
|
} /* opus_print_header */
|
|
|
|
static int
|
|
opus_read_header_packet (SF_PRIVATE *psf, OpusHeader *h, ogg_packet *opacket)
|
|
{ int count, i ;
|
|
|
|
/*
|
|
** Opus headers are 19 bytes, in the case of type 0 channel mapping,
|
|
** or 19 + 2 + (1 * channel count) bytes for other channel mappings, to a
|
|
** maximum of 276 (255 channels).
|
|
*/
|
|
|
|
if (opacket->bytes < 19 || opacket->bytes > 276)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
if (memcmp (opacket->packet, "OpusHead", 8) != 0)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
/*
|
|
** Copy the header page into the binheader so we can use binheader
|
|
** functions to safely unpack it.
|
|
*/
|
|
count = psf_binheader_writef (psf, "ob", BHWo (0), BHWv (opacket->packet), BHWz (opacket->bytes)) ;
|
|
psf->header.end = count ;
|
|
|
|
count = psf_binheader_readf (psf, "ep1", 8, &h->version) ;
|
|
if (! (h->version == 1 || h->version == 0))
|
|
{ psf_log_printf (psf, "Opus : Unknown / unsupported embedding scheme version: %d.\n", (int) h->version) ;
|
|
return SFE_UNIMPLEMENTED ;
|
|
} ;
|
|
|
|
count += psf_binheader_readf (psf, "e12421", &h->channels, &h->preskip,
|
|
&h->input_samplerate, &h->gain, &h->channel_mapping) ;
|
|
|
|
if (h->channel_mapping == 0)
|
|
{ if (h->channels > 2)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
/*
|
|
** Setup the stream mapping, so we can use the multistream decoder,
|
|
** rather than have to deal with two decoder pointer types
|
|
*/
|
|
h->nb_streams = 1 ;
|
|
h->nb_coupled = h->channels - 1 ;
|
|
h->stream_map [0] = 0 ;
|
|
h->stream_map [1] = 1 ;
|
|
}
|
|
else
|
|
{ if (opacket->bytes < 19 + 2 + h->channels)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
if (h->channel_mapping == 1 && h->channels > 8)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
count += psf_binheader_readf (psf, "11", &h->nb_streams, &h->nb_coupled) ;
|
|
|
|
if (h->nb_streams < 1 ||
|
|
h->nb_coupled > h->nb_streams ||
|
|
h->nb_coupled + h->nb_streams > 255)
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
for (i = 0 ; i < h->channels ; i++)
|
|
{ count += psf_binheader_readf (psf, "1", &(h->stream_map [i])) ;
|
|
if (h->stream_map [i] > h->nb_streams + h->nb_coupled && h->stream_map [i] != 255)
|
|
return SFE_MALFORMED_FILE ;
|
|
} ;
|
|
} ;
|
|
|
|
if (count != opacket->bytes)
|
|
{ /* OggOpus spec mandates that this is a hard error. */
|
|
psf_log_printf (psf, "Opus : Error, extra data in Ogg Opus header.\n") ;
|
|
return SFE_MALFORMED_FILE ;
|
|
} ;
|
|
|
|
opus_print_header (psf, h) ;
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_read_header_packet */
|
|
|
|
static int
|
|
ogg_opus_read_header (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
int error ;
|
|
|
|
/*
|
|
** First page is already loaded by the ogg container code when it
|
|
** classified the stream, no need to re-load it now.
|
|
*/
|
|
|
|
if (ogg_page_packets (&odata->opage) != 1 || !ogg_page_bos (&odata->opage))
|
|
return SFE_MALFORMED_FILE ;
|
|
|
|
oopus->serialno = ogg_page_serialno (&odata->opage) ;
|
|
if ((error = opus_read_header_packet (psf, &oopus->header, &odata->opacket)))
|
|
return error ;
|
|
|
|
/*
|
|
** The comment header MUST be next. It is one packet, that packet MUST begin
|
|
** on the second page of the stream, but it MAY span multiple pages.
|
|
*/
|
|
|
|
while (ogg_stream_packetout (&odata->ostream, &odata->opacket) != 1)
|
|
{ if (ogg_stream_next_page (psf, odata) != 1)
|
|
{ /* out of data... technically that's malformed. */
|
|
return psf->error ? psf->error : SFE_MALFORMED_FILE ;
|
|
} ;
|
|
} ;
|
|
|
|
if ((error = vorbiscomment_read_tags (psf, &odata->opacket, &opustags_ident)))
|
|
return error ;
|
|
|
|
return ogg_opus_setup_decoder (psf, oopus->header.input_samplerate) ;
|
|
} /* ogg_opus_read_header */
|
|
|
|
static int
|
|
ogg_opus_setup_decoder (SF_PRIVATE *psf, int input_samplerate)
|
|
{ OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
OpusMSDecoder *decoder ;
|
|
int sr_factor ;
|
|
int error ;
|
|
|
|
/*
|
|
** Decide what sample rate to decode at. We choose the lowest valid rate
|
|
** that is greater or equal to the original rate.
|
|
**
|
|
** Opus documentation recommends always decoding at 48000Hz if the file is
|
|
** being decoded for playback, since most hardware will resample it back to
|
|
** 48000Hz anyways. We don't know if that's true, maybe the user is
|
|
** decoding for editing or transcoding purposes.
|
|
*/
|
|
if (input_samplerate > 24000)
|
|
sr_factor = 1 ;
|
|
else if (input_samplerate > 16000)
|
|
sr_factor = 2 ;
|
|
else if (input_samplerate > 12000)
|
|
sr_factor = 3 ;
|
|
else if (input_samplerate > 8000)
|
|
sr_factor = 4 ;
|
|
else
|
|
sr_factor = 6 ;
|
|
|
|
decoder = opus_multistream_decoder_create (
|
|
48000 / sr_factor,
|
|
oopus->header.channels,
|
|
oopus->header.nb_streams,
|
|
oopus->header.nb_coupled,
|
|
oopus->header.stream_map,
|
|
&error) ;
|
|
|
|
if (error != OPUS_OK)
|
|
{ psf_log_printf (psf, "Opus : Failed to create multistream decoder: %s\n",
|
|
opus_strerror (error)) ;
|
|
return SFE_INTERNAL ;
|
|
}
|
|
|
|
/*
|
|
** Replace the decoder, if one was already initialized (see
|
|
** SFC_GET_ORIGINAL_SAMPLERATE)
|
|
*/
|
|
if (oopus->u.decode.state)
|
|
opus_multistream_decoder_destroy (oopus->u.decode.state) ;
|
|
oopus->u.decode.state = decoder ;
|
|
|
|
oopus->sr_factor = sr_factor ;
|
|
psf->sf.samplerate = 48000 / sr_factor ;
|
|
psf->sf.channels = oopus->header.channels ;
|
|
oopus->loc = oopus->len = 0 ;
|
|
|
|
/*
|
|
** The Opus decoder can do our gain for us. The OggOpus header contains a
|
|
** gain field. This field, unlike various gain-related tags, is intended to
|
|
** be a perminent baked-in gain applied before any user-configurable gain
|
|
** (eg replay-gain.) This is so the gain of track can be set without having
|
|
** to re-encode.
|
|
**
|
|
** Both the header.gain field and the parameter are in the Q7.8 format.
|
|
**
|
|
** TODO: Make this configurable? Include other gain sources too?
|
|
*/
|
|
opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_SET_GAIN (oopus->header.gain)) ;
|
|
|
|
/*
|
|
** Opus packets can vary in length, with the legal values being 2.5, 5, 10,
|
|
** 20, 40 or 60ms. The recommended default for non-realtime is 20ms. As
|
|
** such, allocate a buffer of that size now, we'll realloc later if a
|
|
** larger one is needed.
|
|
**
|
|
** buffersize is expressed in samples/channel, as that is what opus_decode
|
|
** expects.
|
|
*/
|
|
if (oopus->buffer)
|
|
{ free (oopus->buffer) ;
|
|
oopus->buffer = NULL ;
|
|
} ;
|
|
oopus->buffersize = 20 * psf->sf.samplerate / 1000 ;
|
|
oopus->buffer = malloc (sizeof (float) * psf->sf.channels * oopus->buffersize) ;
|
|
if (oopus->buffer == NULL)
|
|
return SFE_MALLOC_FAILED ;
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_setup_decoder */
|
|
|
|
static int
|
|
ogg_opus_setup_encoder (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus)
|
|
{ int error ;
|
|
int lookahead ;
|
|
int nb_streams ;
|
|
int nb_coupled ;
|
|
|
|
/* default page latency value (1000ms) */
|
|
oopus->u.encode.latency = 1000 * 48 ;
|
|
|
|
switch (psf->sf.samplerate)
|
|
{ case 8000 :
|
|
case 12000 :
|
|
case 16000 :
|
|
case 24000 :
|
|
case 48000 :
|
|
oopus->sr_factor = 48000 / psf->sf.samplerate ;
|
|
break ;
|
|
default :
|
|
return SFE_OPUS_BAD_SAMPLERATE ;
|
|
} ;
|
|
|
|
if (psf->sf.channels <= 2)
|
|
{ oopus->header.channel_mapping = 0 ;
|
|
nb_streams = 1 ;
|
|
nb_coupled = psf->sf.channels - 1 ;
|
|
oopus->header.stream_map [0] = 0 ;
|
|
oopus->header.stream_map [1] = 1 ;
|
|
|
|
oopus->u.encode.state = opus_multistream_encoder_create (
|
|
psf->sf.samplerate,
|
|
psf->sf.channels,
|
|
nb_streams,
|
|
nb_coupled,
|
|
oopus->header.stream_map,
|
|
OPUS_APPLICATION_AUDIO,
|
|
&error) ;
|
|
}
|
|
else
|
|
{ if (psf->sf.channels <= 8)
|
|
{ /* Use Vorbis/AC3 channel mappings for surround. */
|
|
oopus->header.channel_mapping = 1 ;
|
|
}
|
|
else
|
|
{ /* There is no channel mapping, just audio, in parallel, good luck */
|
|
oopus->header.channel_mapping = 255 ;
|
|
}
|
|
|
|
oopus->u.encode.state = opus_multistream_surround_encoder_create (
|
|
psf->sf.samplerate,
|
|
psf->sf.channels,
|
|
oopus->header.channel_mapping,
|
|
&nb_streams,
|
|
&nb_coupled,
|
|
oopus->header.stream_map,
|
|
OPUS_APPLICATION_AUDIO,
|
|
&error) ;
|
|
|
|
}
|
|
|
|
if (error != OPUS_OK)
|
|
{ psf_log_printf (psf, "Opus : Error, opus_multistream_encoder_create returned %s\n", opus_strerror (error)) ;
|
|
return SFE_BAD_OPEN_FORMAT ;
|
|
} ;
|
|
oopus->header.nb_streams = nb_streams ;
|
|
oopus->header.nb_coupled = nb_coupled ;
|
|
|
|
opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_GET_BITRATE (&oopus->u.encode.bitrate)) ;
|
|
psf_log_printf (psf, "Encoding at target bitrate of %dbps\n", oopus->u.encode.bitrate) ;
|
|
|
|
/* TODO: Make configurable? */
|
|
error = opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_COMPLEXITY (10)) ;
|
|
if (error != OPUS_OK)
|
|
{ /* Non-fatal */
|
|
psf_log_printf (psf, "Opus : OPUS_SET_COMPLEXITY returned: %s\n", opus_strerror (error)) ;
|
|
}
|
|
|
|
/*
|
|
** Get the encoder delay. This can vary depending on implementation and
|
|
** encoder configuration.
|
|
** GOTCHA: This returns the preskip at the encoder samplerate, not the
|
|
** granulepos rate of 48000Hz needed for header.preskip.
|
|
*/
|
|
error = opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_GET_LOOKAHEAD (&lookahead)) ;
|
|
if (error != OPUS_OK)
|
|
{ psf_log_printf (psf, "Opus : OPUS_GET_LOOKAHEAD returned: %s\n", opus_strerror (error)) ;
|
|
return SFE_BAD_OPEN_FORMAT ;
|
|
} ;
|
|
oopus->header.preskip = lookahead * oopus->sr_factor ;
|
|
|
|
oopus->len = OGG_OPUS_ENCODE_PACKET_LEN (psf->sf.samplerate) ;
|
|
oopus->buffer = malloc (sizeof (float) * psf->sf.channels * oopus->len) ;
|
|
if (oopus->buffer == NULL)
|
|
return SFE_MALLOC_FAILED ;
|
|
|
|
/*
|
|
** Set up the resident ogg packet structure, ready for writing into.
|
|
** 1275 * 3 + 7 bytes of packet per stream is from opusenc from opus-tools
|
|
*/
|
|
ogg_packet_clear (&odata->opacket) ;
|
|
oopus->buffersize = (1275 * 3 + 7) * oopus->header.nb_streams ;
|
|
odata->opacket.packet = malloc (oopus->buffersize) ;
|
|
odata->opacket.packetno = 2 ;
|
|
if (odata->opacket.packet == NULL)
|
|
return SFE_MALLOC_FAILED ;
|
|
|
|
oopus->serialno = psf_rand_int32 () ;
|
|
ogg_stream_init (&odata->ostream, oopus->serialno) ;
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_setup_encoder */
|
|
|
|
static int
|
|
ogg_opus_write_header (SF_PRIVATE *psf, int UNUSED (calc_length))
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
int nn ;
|
|
ogg_packet op ;
|
|
|
|
oopus->header.version = 1 ;
|
|
oopus->header.channels = psf->sf.channels ;
|
|
|
|
/* FIXME: Allow the user to set this ?! */
|
|
oopus->header.gain = 0 ;
|
|
|
|
if (psf->dataoffset > 0)
|
|
{ if (psf->have_written)
|
|
{ /*
|
|
** Might be possible to deal with this, but it's difficult as we
|
|
** have to take Ogg Page header sizes in to account, not just
|
|
** packet sizes.
|
|
*/
|
|
return SFE_UNIMPLEMENTED ;
|
|
}
|
|
if (psf_is_pipe (psf))
|
|
return SFE_NOT_SEEKABLE ;
|
|
if (psf_fseek (psf, 0, SEEK_SET) < 0)
|
|
return SFE_SEEK_FAILED ;
|
|
ogg_stream_reset_serialno (&odata->ostream, oopus->serialno) ;
|
|
psf->dataoffset = 0 ;
|
|
}
|
|
else
|
|
opus_print_header (psf, &oopus->header) ;
|
|
|
|
psf->header.ptr [0] = 0 ;
|
|
psf->header.indx = 0 ;
|
|
|
|
/* Opus Header Marker */
|
|
psf_binheader_writef (psf, "eb", BHWv ("OpusHead"), BHWz (8)) ;
|
|
|
|
/* Ogg Embedding scheme version, Channel Count, Preskip Samples */
|
|
psf_binheader_writef (psf, "e112", BHW1 (oopus->header.version), BHW1 (psf->sf.channels), BHW2 (oopus->header.preskip)) ;
|
|
|
|
/*
|
|
** If an original samplerate has not been set by the user command
|
|
** SFC_SET_ORIGINAL_SAMPLERATE, write the current samplerate.
|
|
*/
|
|
if (oopus->header.input_samplerate)
|
|
psf_binheader_writef (psf, "e4", BHW4 (oopus->header.input_samplerate)) ;
|
|
else
|
|
psf_binheader_writef (psf, "e4", BHW4 (psf->sf.samplerate)) ;
|
|
|
|
/* Input Sample Rate, Gain (S7.8 format), Channel Mapping Type */
|
|
psf_binheader_writef (psf, "e21", BHW2 (oopus->header.gain), BHW1 (oopus->header.channel_mapping)) ;
|
|
|
|
/* Channel mappings, required if not using type 0 (mono/stereo) */
|
|
if (oopus->header.channel_mapping > 0)
|
|
{ psf_binheader_writef (psf, "11", BHW1 (oopus->header.nb_streams), BHW1 (oopus->header.nb_coupled)) ;
|
|
for (nn = 0 ; nn < oopus->header.channels ; nn++)
|
|
psf_binheader_writef (psf, "1", BHW1 (oopus->header.stream_map [nn])) ;
|
|
} ;
|
|
|
|
op.packet = psf->header.ptr ;
|
|
op.bytes = psf->header.indx ;
|
|
op.b_o_s = 1 ;
|
|
op.e_o_s = 0 ;
|
|
op.granulepos = 0 ;
|
|
op.packetno = 1 ;
|
|
|
|
/* The first page MUST only contain the header, so flush it out now */
|
|
ogg_stream_packetin (&odata->ostream, &op) ;
|
|
for ( ; (nn = ogg_stream_flush (&odata->ostream, &odata->opage)) ; )
|
|
{ if (! (nn = ogg_write_page (psf, &odata->opage)))
|
|
{ psf_log_printf (psf, "Opus : Failed to write header!\n") ;
|
|
if (psf->error)
|
|
return psf->error ;
|
|
return SFE_INTERNAL ;
|
|
} ;
|
|
psf->dataoffset += nn ;
|
|
}
|
|
|
|
/*
|
|
** Metadata Tags (manditory)
|
|
**
|
|
** All tags must be in one packet, which may span pages, and these pages
|
|
** must not contain any other packets, so flush. The vendor string should
|
|
** be the libopus library version, as it is doing the actual encoding. We
|
|
** put the libsndfile identifier in the ENCODER tag.
|
|
**
|
|
** See: https://wiki.xiph.org/VorbisComment#ENCODER
|
|
*/
|
|
vorbiscomment_write_tags (psf, &op, &opustags_ident, opus_get_version_string (), - (OGG_OPUS_COMMENT_PAD)) ;
|
|
op.packetno = 2 ;
|
|
ogg_stream_packetin (&odata->ostream, &op) ;
|
|
for ( ; (nn = ogg_stream_flush (&odata->ostream, &odata->opage)) ; )
|
|
{ if (! (nn = ogg_write_page (psf, &odata->opage)))
|
|
{ psf_log_printf (psf, "Opus : Failed to write comments!\n") ;
|
|
if (psf->error)
|
|
return psf->error ;
|
|
return SFE_INTERNAL ;
|
|
} ;
|
|
psf->dataoffset += nn ;
|
|
}
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_write_header */
|
|
|
|
static void
|
|
ogg_opus_flush (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
uint64_t last_granulepos ;
|
|
int nbytes ;
|
|
int len ;
|
|
int last_packet ;
|
|
|
|
/*
|
|
** Need to flush both samples waiting for a complete packet and samples
|
|
** currently 'inside' the encoder because of its latency. In the case of
|
|
** the latter, we need to encode an equivalent amount of silence to push
|
|
** them out.
|
|
**
|
|
** Note that the last packet's granule position might be less than the
|
|
** total number of samples completed in it. This is how Ogg embedded Opus
|
|
** encodes the amount of appended padding to truncate for gapless playback.
|
|
*/
|
|
|
|
last_granulepos = oopus->pkt_pos + (oopus->sr_factor * oopus->loc) + oopus->header.preskip ;
|
|
last_packet = SF_FALSE ;
|
|
memset (&(oopus->buffer [oopus->loc * psf->sf.channels]), 0, sizeof (float) * psf->sf.channels * (oopus->len - oopus->loc)) ;
|
|
|
|
for (last_packet = SF_FALSE ; last_packet == SF_FALSE ; )
|
|
{ oopus->pkt_pos += oopus->len * oopus->sr_factor ;
|
|
if (oopus->pkt_pos >= last_granulepos)
|
|
{ last_packet = SF_TRUE ;
|
|
/*
|
|
** Try to shorten the last packet to the smallest valid packet size
|
|
** to minimize padding samples.
|
|
*/
|
|
len = (oopus->len * oopus->sr_factor) - (oopus->pkt_pos - last_granulepos) ;
|
|
if (len <= 120) /* 2.5 ms */
|
|
len = 120 / oopus->sr_factor ;
|
|
else if (len <= 240) /* 5 ms */
|
|
len = 240 / oopus->sr_factor ;
|
|
else if (len <= 480) /* 10 ms */
|
|
len = 480 / oopus->sr_factor ;
|
|
else
|
|
len = oopus->len ;
|
|
}
|
|
else
|
|
len = oopus->len ;
|
|
|
|
nbytes = opus_multistream_encode_float (oopus->u.encode.state, oopus->buffer,
|
|
len, odata->opacket.packet, oopus->buffersize) ;
|
|
|
|
if (nbytes < 0)
|
|
{ psf_log_printf (psf, "Opus : opus_multistream_encode_float returned: %s\n", opus_strerror (nbytes)) ;
|
|
break ;
|
|
}
|
|
|
|
odata->opacket.bytes = nbytes ;
|
|
odata->opacket.packetno++ ;
|
|
if (last_packet)
|
|
{ odata->opacket.granulepos = (ogg_int64_t) last_granulepos ;
|
|
odata->opacket.e_o_s = 1 ;
|
|
}
|
|
else
|
|
odata->opacket.granulepos = (ogg_int64_t) oopus->pkt_pos ;
|
|
|
|
ogg_stream_packetin (&odata->ostream, &odata->opacket) ;
|
|
while (ogg_stream_pageout (&odata->ostream, &odata->opage))
|
|
ogg_write_page (psf, &odata->opage) ;
|
|
} ;
|
|
|
|
while (ogg_stream_flush (&odata->ostream, &odata->opage))
|
|
ogg_write_page (psf, &odata->opage) ;
|
|
} /* ogg_opus_flush */
|
|
|
|
static int
|
|
ogg_opus_calculate_page_duration (OGG_PRIVATE *odata)
|
|
{ int i, samples, duration ;
|
|
ogg_packet *ppkt ;
|
|
|
|
duration = 0 ;
|
|
for (i = 0 , ppkt = odata->pkt ; i < odata->pkt_len ; i++, ppkt++)
|
|
{ /* Use 48kHz to get the sample count for use with granule positions. */
|
|
samples = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 48000) ;
|
|
if (samples > 0)
|
|
duration += samples ;
|
|
} ;
|
|
return duration ;
|
|
} /* ogg_opus_calculate_page_duration */
|
|
|
|
static int
|
|
ogg_opus_unpack_next_page (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus)
|
|
{ int nn ;
|
|
|
|
nn = ogg_stream_unpack_page (psf, odata) ;
|
|
|
|
if (nn == 1)
|
|
{ oopus->pkt_pos = oopus->pg_pos ;
|
|
oopus->pg_pos = odata->pkt [odata->pkt_len - 1].granulepos ;
|
|
}
|
|
else if (nn == 2)
|
|
{ uint64_t gp, last_page ;
|
|
|
|
/* Found a hole. Need to recalculated pkt_pos from pg_pos */
|
|
last_page = oopus->pg_pos ;
|
|
oopus->pg_pos = odata->pkt [odata->pkt_len - 1].granulepos ;
|
|
gp = ogg_opus_calculate_page_duration (odata) ;
|
|
oopus->pkt_pos = oopus->pg_pos - gp ;
|
|
psf_log_printf (psf, "Opus : Hole found appears to be of length %D samples.\n",
|
|
(oopus->pkt_pos - last_page) / (uint64_t) oopus->sr_factor) ;
|
|
/*
|
|
** Could save the hole size here, and have ogg_opus_read_refill()
|
|
** do packet loss concealment until the hole is gone, but libopus does
|
|
** PLC by generating white-noise for the duration of the hole. That is
|
|
** the correct thing for use in telephony, but it isn't generally
|
|
** appropriate here. It actually sounds better with no PLC, as the
|
|
** lapped nature of full-width Opus means the two edges of the hole
|
|
** will be blended together.
|
|
*/
|
|
return 1 ;
|
|
}
|
|
|
|
return nn ;
|
|
} /* ogg_opus_unpack_next_page */
|
|
|
|
static int
|
|
ogg_opus_read_refill (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus)
|
|
{ uint64_t pkt_granulepos ;
|
|
int nn, nsamp ;
|
|
ogg_packet *ppkt ;
|
|
|
|
if (odata->pkt_indx == odata->pkt_len)
|
|
{ nn = ogg_opus_unpack_next_page (psf, odata, oopus) ;
|
|
if (nn <= 0)
|
|
return nn ;
|
|
}
|
|
|
|
if (odata->pkt_indx == odata->pkt_len)
|
|
return 0 ;
|
|
|
|
ppkt = odata->pkt + odata->pkt_indx ;
|
|
nsamp = opus_multistream_decode_float (oopus->u.decode.state,
|
|
ppkt->packet, ppkt->bytes, oopus->buffer, oopus->buffersize, 0) ;
|
|
|
|
if (nsamp == OPUS_BUFFER_TOO_SMALL)
|
|
{ nsamp = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, psf->sf.samplerate) ;
|
|
psf_log_printf (psf, "Growing decode buffer to hold %d samples from %d\n",
|
|
nsamp, oopus->buffersize) ;
|
|
if (nsamp > 5760)
|
|
{ psf_log_printf (psf, "Packet is larger than maximum allowable of 120ms!? Skipping.\n") ;
|
|
return 0 ;
|
|
} ;
|
|
oopus->buffersize = nsamp ;
|
|
|
|
free (oopus->buffer) ;
|
|
oopus->buffer = NULL ;
|
|
oopus->buffer = malloc (sizeof (float) * oopus->buffersize * psf->sf.channels) ;
|
|
if (oopus->buffer == NULL)
|
|
{ psf->error = SFE_MALLOC_FAILED ;
|
|
oopus->buffersize = 0 ;
|
|
return -1 ;
|
|
} ;
|
|
|
|
nsamp = opus_multistream_decode_float (oopus->u.decode.state,
|
|
ppkt->packet, ppkt->bytes, oopus->buffer, oopus->buffersize, 0) ;
|
|
} ;
|
|
odata->pkt_indx ++ ;
|
|
|
|
if (nsamp < 0)
|
|
{ psf_log_printf (psf, "Opus : opus_multistream_decode returned: %s\n",
|
|
opus_strerror (nsamp)) ;
|
|
psf->error = SFE_INTERNAL ;
|
|
return nsamp ;
|
|
} ;
|
|
|
|
/*
|
|
** Check for if this decoded packet is the last of the stream, in
|
|
** which case a page granule position which is shorter than the
|
|
** sample count of all packets in the page indicates that the last
|
|
** samples are padding and should be dropped.
|
|
*/
|
|
pkt_granulepos = oopus->pkt_pos + (nsamp * oopus->sr_factor) ;
|
|
if (pkt_granulepos <= oopus->pg_pos)
|
|
{ oopus->len = nsamp ;
|
|
}
|
|
else
|
|
{ if (ogg_page_eos (&odata->opage))
|
|
{ /*
|
|
** Possible for pg_pos < pkt_pos if there is a trailing
|
|
** packet. It's not supposed to happen, but could.
|
|
*/
|
|
oopus->len = SF_MAX ((int) (oopus->pg_pos - oopus->pkt_pos) / oopus->sr_factor, 0) ;
|
|
}
|
|
else
|
|
{ /*
|
|
** From https://wiki.xiph.org/OggOpus#Granule_Position
|
|
** A decoder MUST reject as invalid any stream where the granule
|
|
** position is smaller than the number of samples contained in
|
|
** packets that complete on the first page with a completed
|
|
** packet, unless that page has the 'end of stream' flag set. It
|
|
** MAY defer this action until it decodes the last packet
|
|
** completed on that page.
|
|
*/
|
|
psf_log_printf (psf, "Opus : Mid-stream page's granule position %D is less than total samples of %D\n", oopus->pg_pos, pkt_granulepos) ;
|
|
psf->error = SFE_MALFORMED_FILE ;
|
|
return -1 ;
|
|
} ;
|
|
} ;
|
|
|
|
if (oopus->len > oopus->buffersize)
|
|
{ free (oopus->buffer) ;
|
|
oopus->buffersize = oopus->len ;
|
|
oopus->buffer = malloc (sizeof (float) * oopus->buffersize * psf->sf.channels) ;
|
|
if (oopus->buffer == NULL)
|
|
{ psf->error = SFE_MALLOC_FAILED ;
|
|
oopus->buffersize = 0 ;
|
|
return -1 ;
|
|
} ;
|
|
} ;
|
|
|
|
/*
|
|
** Check for if this decoded packet contains samples from before the pre-
|
|
** skip point, indicating that these samples are padding to get the decoder
|
|
** to converge and should be dropped.
|
|
*/
|
|
if (oopus->pkt_pos < (unsigned) oopus->header.preskip)
|
|
oopus->loc = SF_MIN ((oopus->header.preskip - (int) oopus->pkt_pos) / oopus->sr_factor, oopus->len) ;
|
|
else
|
|
oopus->loc = 0 ;
|
|
|
|
oopus->pkt_pos = pkt_granulepos ;
|
|
|
|
return nsamp ;
|
|
} /* ogg_opus_read_refill */
|
|
|
|
static int
|
|
ogg_opus_write_out (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus)
|
|
{ int nbytes ;
|
|
|
|
if (oopus->u.encode.lsb != oopus->u.encode.lsb_last)
|
|
opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_LSB_DEPTH (oopus->u.encode.lsb)) ;
|
|
|
|
nbytes = opus_multistream_encode_float (oopus->u.encode.state,
|
|
oopus->buffer, oopus->len,
|
|
odata->opacket.packet, oopus->buffersize) ;
|
|
|
|
if (nbytes < 0)
|
|
{ psf_log_printf (psf, "Opus : Error, opus_multistream_encode_float returned: %s\n", opus_strerror (nbytes)) ;
|
|
psf->error = SFE_INTERNAL ;
|
|
return nbytes ;
|
|
} ;
|
|
|
|
oopus->u.encode.last_segments += (nbytes + 255) / 255 ;
|
|
oopus->pkt_pos += oopus->len * oopus->sr_factor ;
|
|
odata->opacket.bytes = nbytes ;
|
|
odata->opacket.granulepos = oopus->pkt_pos ;
|
|
odata->opacket.packetno++ ;
|
|
|
|
/*
|
|
** Decide whether to flush the Ogg page *before* adding the new packet to
|
|
** it. Check both for if there is more than 1 second of audio (our default
|
|
** Ogg page latency, this latency can be modified using sf_command())
|
|
** or if adding the packet would cause a continued page,
|
|
** in which case we might as well make a new page anyways.
|
|
*/
|
|
for ( ; ; )
|
|
{ if (oopus->pkt_pos - oopus->pg_pos >= oopus->u.encode.latency || oopus->u.encode.last_segments >= 255)
|
|
nbytes = ogg_stream_flush_fill (&odata->ostream, &odata->opage, 255 * 255) ;
|
|
else
|
|
nbytes = ogg_stream_pageout_fill (&odata->ostream, &odata->opage, 255 * 255) ;
|
|
if (nbytes > 0)
|
|
{ oopus->u.encode.last_segments -= ogg_page_segments (&odata->opage) ;
|
|
oopus->pg_pos = oopus->pkt_pos ;
|
|
ogg_write_page (psf, &odata->opage) ;
|
|
}
|
|
else
|
|
break ;
|
|
} ;
|
|
|
|
ogg_stream_packetin (&odata->ostream, &odata->opacket) ;
|
|
oopus->loc = 0 ;
|
|
oopus->u.encode.lsb_last = oopus->u.encode.lsb ;
|
|
oopus->u.encode.lsb = 0 ;
|
|
|
|
return 1 ;
|
|
} /* ogg_opus_write_out */
|
|
|
|
static sf_count_t
|
|
ogg_opus_read_s (SF_PRIVATE *psf, short *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total = 0 ;
|
|
sf_count_t readlen, i ;
|
|
float *iptr ;
|
|
|
|
while (total < len)
|
|
{ if (oopus->loc == oopus->len)
|
|
{ if (ogg_opus_read_refill (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (readlen > 0)
|
|
{ iptr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += readlen ;
|
|
|
|
if (psf->float_int_mult)
|
|
{ float inverse = 1.0 / psf->float_max ;
|
|
for ( ; i < total ; i++)
|
|
{ ptr [i] = psf_lrintf (((*(iptr++)) * inverse) * 32767.0f) ;
|
|
} ;
|
|
}
|
|
else
|
|
{ for ( ; i < total ; i++)
|
|
{ ptr [i] = psf_lrintf ((*(iptr++)) * 32767.0f) ;
|
|
} ;
|
|
} ;
|
|
oopus->loc += (readlen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_read_s */
|
|
|
|
static sf_count_t
|
|
ogg_opus_read_i (SF_PRIVATE *psf, int *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total = 0 ;
|
|
sf_count_t readlen, i ;
|
|
float *iptr ;
|
|
|
|
while (total < len)
|
|
{ if (oopus->loc == oopus->len)
|
|
{ if (ogg_opus_read_refill (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (readlen > 0)
|
|
{ iptr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += readlen ;
|
|
|
|
if (psf->float_int_mult)
|
|
{ float inverse = 1.0 / psf->float_max ;
|
|
for ( ; i < total ; i++)
|
|
{ ptr [i] = psf_lrintf (((*(iptr++)) * inverse) * 2147483647.0f) ;
|
|
}
|
|
}
|
|
else
|
|
{ for ( ; i < total ; i++)
|
|
{ ptr [i] = psf_lrintf ((*(iptr++)) * 2147483647.0f) ;
|
|
}
|
|
} ;
|
|
oopus->loc += (readlen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_read_i */
|
|
|
|
static sf_count_t
|
|
ogg_opus_read_f (SF_PRIVATE *psf, float *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total = 0 ;
|
|
sf_count_t readlen ;
|
|
|
|
while (total < len)
|
|
{ if (oopus->loc == oopus->len)
|
|
{ if (ogg_opus_read_refill (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (readlen > 0)
|
|
{ memcpy (&(ptr [total]), &(oopus->buffer [oopus->loc * psf->sf.channels]), sizeof (float) * readlen) ;
|
|
total += readlen ;
|
|
oopus->loc += (readlen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_read_f */
|
|
|
|
static sf_count_t
|
|
ogg_opus_read_d (SF_PRIVATE *psf, double *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total = 0 ;
|
|
sf_count_t readlen, i ;
|
|
float *fptr ;
|
|
|
|
while (total < len)
|
|
{ if (oopus->loc >= oopus->len)
|
|
{ if (ogg_opus_read_refill (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
|
|
if (readlen > 0)
|
|
{ fptr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += readlen ;
|
|
for ( ; i < total ; i++)
|
|
{ ptr [i] = *fptr++ ;
|
|
} ;
|
|
oopus->loc += readlen / psf->sf.channels ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_read_d */
|
|
|
|
static sf_count_t
|
|
ogg_opus_write_s (SF_PRIVATE *psf, const short *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total, i ;
|
|
int writelen ;
|
|
float *optr ;
|
|
|
|
if (oopus->u.encode.lsb < 16)
|
|
oopus->u.encode.lsb = 16 ;
|
|
|
|
for (total = 0 ; total < len ; )
|
|
{ if (oopus->loc >= oopus->len)
|
|
{ /* Need to encode the buffer */
|
|
if (ogg_opus_write_out (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (writelen)
|
|
{ optr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += writelen ;
|
|
for ( ; i < total ; i++)
|
|
{ *optr++ = (float) (ptr [i]) / 32767.0f ;
|
|
}
|
|
oopus->loc += (writelen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_write_s */
|
|
|
|
static sf_count_t
|
|
ogg_opus_write_i (SF_PRIVATE *psf, const int *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total, i ;
|
|
int writelen ;
|
|
float *optr ;
|
|
|
|
if (oopus->u.encode.lsb < 24)
|
|
oopus->u.encode.lsb = 24 ;
|
|
|
|
for (total = 0 ; total < len ; )
|
|
{ if (oopus->loc >= oopus->len)
|
|
{ /* Need to encode the buffer */
|
|
if (ogg_opus_write_out (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (writelen)
|
|
{ optr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += writelen ;
|
|
for ( ; i < total ; i++)
|
|
{ *optr++ = (float) (ptr [i]) / 2147483647.0f ;
|
|
} ;
|
|
oopus->loc += (writelen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_write_i */
|
|
|
|
static sf_count_t
|
|
ogg_opus_write_f (SF_PRIVATE *psf, const float *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total ;
|
|
int writelen ;
|
|
|
|
if (oopus->u.encode.lsb < 24)
|
|
oopus->u.encode.lsb = 24 ;
|
|
|
|
for (total = 0 ; total < len ; )
|
|
{ if (oopus->loc >= oopus->len)
|
|
{ /* Need to encode the buffer */
|
|
if (ogg_opus_write_out (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (writelen)
|
|
{ memcpy (&(oopus->buffer [oopus->loc * psf->sf.channels]), &(ptr [total]), sizeof (float) * writelen) ;
|
|
total += writelen ;
|
|
oopus->loc += (writelen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_write_f */
|
|
|
|
static sf_count_t
|
|
ogg_opus_write_d (SF_PRIVATE *psf, const double *ptr, sf_count_t len)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total, i ;
|
|
int writelen ;
|
|
float *optr ;
|
|
|
|
if (oopus->u.encode.lsb < 24)
|
|
oopus->u.encode.lsb = 24 ;
|
|
|
|
for (total = 0 ; total < len ; )
|
|
{ if (oopus->loc >= oopus->len)
|
|
{ /* Need to encode the buffer */
|
|
if (ogg_opus_write_out (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
} ;
|
|
|
|
writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ;
|
|
if (writelen)
|
|
{ optr = oopus->buffer + oopus->loc * psf->sf.channels ;
|
|
i = total ;
|
|
total += writelen ;
|
|
for ( ; i < total ; i++)
|
|
{ *optr++ = (float) (ptr [i]) ;
|
|
} ;
|
|
oopus->loc += (writelen / psf->sf.channels) ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_write_d */
|
|
|
|
static int
|
|
ogg_opus_analyze_file (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
uint64_t gp ;
|
|
sf_count_t saved_offset, last_page ;
|
|
int error ;
|
|
|
|
psf->sf.sections = 1 ;
|
|
psf->sf.frames = SF_COUNT_MAX ;
|
|
oopus->u.decode.gp_end = (uint64_t) -1 ;
|
|
oopus->u.decode.last_offset = SF_COUNT_MAX ;
|
|
|
|
psf->dataoffset = ogg_sync_ftell (psf) ;
|
|
if (psf->filelength != SF_COUNT_MAX)
|
|
psf->datalength = psf->filelength - psf->dataoffset ;
|
|
else
|
|
psf->datalength = SF_COUNT_MAX ;
|
|
|
|
/*
|
|
** Calculate the start granule position offset
|
|
**
|
|
** OggOpus streams are allowed to start with a granule position other than
|
|
** zero. This allows for cutting the beginning off of streams without
|
|
** having to modify all following granule positions, or for recording/
|
|
** joining a live stream in the middle. To figure out the offset, we need
|
|
** to sum up how many samples are in all the packets that complete in the
|
|
** page and subtract it from the page granule position.
|
|
**
|
|
** If this is the last page of the steam (EOS set), this is not possible,
|
|
** as the granule position may be /less/ than the number of samples, to
|
|
** indicate how many samples are end-padding. In this case the granule
|
|
** position offset of the file must be 0, as otherwise it is considered
|
|
** malformed.
|
|
*/
|
|
error = ogg_opus_unpack_next_page (psf, odata, oopus) ;
|
|
if (error < 0 && psf->error)
|
|
return psf->error ;
|
|
|
|
gp = ogg_opus_calculate_page_duration (odata) ;
|
|
if (gp <= 0)
|
|
{ psf_log_printf (psf, "Opus : Page duration of zero!\n") ;
|
|
return SFE_MALFORMED_FILE ;
|
|
} ;
|
|
|
|
if (!ogg_page_eos (&odata->opage))
|
|
{ if (gp > oopus->pg_pos)
|
|
{ psf_log_printf (psf, "Opus : First data page's granule position is less than total number of samples on the page!\n") ;
|
|
return SFE_MALFORMED_FILE ;
|
|
}
|
|
oopus->pkt_pos = oopus->pg_pos - gp ;
|
|
}
|
|
else if (gp < oopus->pg_pos)
|
|
{ psf_log_printf (psf, "Opus : First data page is also the last, and granule position has an (ambigious) offset.\n") ;
|
|
return SFE_MALFORMED_FILE ;
|
|
} ;
|
|
oopus->u.decode.gp_start = oopus->pkt_pos ;
|
|
|
|
if (!psf->sf.seekable)
|
|
return 0 ;
|
|
|
|
/*
|
|
** Find the last page and fetch the last granule position.
|
|
** First, save were we are now.
|
|
*/
|
|
saved_offset = ogg_sync_ftell (psf) ;
|
|
|
|
/* This uses the sync page buffer, the stream page buffer is untouched. */
|
|
last_page = ogg_sync_last_page_before (psf, odata, &oopus->u.decode.gp_end, psf->filelength, oopus->serialno) ;
|
|
if (last_page > 0)
|
|
{ if (!ogg_page_eos (&odata->opage))
|
|
psf_log_printf (psf, "Ogg : Last page lacks an end-of-stream bit.\n") ;
|
|
if (last_page + odata->opage.header_len + odata->opage.body_len < psf->filelength)
|
|
psf_log_printf (psf, "Ogg : Junk after the last page.\n") ;
|
|
oopus->u.decode.last_offset = last_page ;
|
|
|
|
if (oopus->u.decode.gp_end != (uint64_t) -1)
|
|
{ psf->sf.frames = (oopus->u.decode.gp_end - oopus->u.decode.gp_start
|
|
- oopus->header.preskip) / oopus->sr_factor ;
|
|
} ;
|
|
} ;
|
|
|
|
|
|
psf_log_printf (psf, " Granule pos offset : %D\n", oopus->u.decode.gp_start) ;
|
|
if (oopus->u.decode.gp_end != (uint64_t) -1)
|
|
psf_log_printf (psf, " Last Granule pos : %D\n", oopus->u.decode.gp_end) ;
|
|
|
|
/* Go back to where we left off. */
|
|
ogg_sync_fseek (psf, saved_offset, SEEK_SET) ;
|
|
return 0 ;
|
|
} /* ogg_opus_analyze_file */
|
|
|
|
/*
|
|
** ogg_opus_null_read
|
|
**
|
|
** Decode samples, doing nothing with them, until the desired granule position
|
|
** is reached.
|
|
*/
|
|
static sf_count_t
|
|
ogg_opus_null_read (SF_PRIVATE *psf, sf_count_t offset)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t total ;
|
|
|
|
total = (oopus->pkt_pos / oopus->sr_factor) - (oopus->len - oopus->loc) ;
|
|
for ( ; total < offset ; )
|
|
{ sf_count_t readlen = SF_MIN ((int) (offset - total), (oopus->len - oopus->loc)) ;
|
|
if (readlen > 0)
|
|
{ total += readlen ;
|
|
oopus->loc += readlen ;
|
|
} ;
|
|
if (oopus->loc == oopus->len)
|
|
{ if (ogg_opus_read_refill (psf, odata, oopus) <= 0)
|
|
return total ;
|
|
/*
|
|
** Ignore pre-skip skipping. The preskip was accounted for in the
|
|
** arugment to offset, so we need to count it.
|
|
*/
|
|
oopus->loc = 0 ;
|
|
} ;
|
|
} ;
|
|
return total ;
|
|
} /* ogg_opus_null_read */
|
|
|
|
/*
|
|
** ogg_opus_page_seek_search
|
|
**
|
|
** Search within the file for the page with the highest granule position at or
|
|
** before our target.
|
|
*/
|
|
static int
|
|
ogg_opus_page_seek_search (SF_PRIVATE *psf, uint64_t target_gp)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
uint64_t pcm_start ;
|
|
uint64_t pcm_end ;
|
|
uint64_t best_gp ;
|
|
sf_count_t begin ;
|
|
sf_count_t end ;
|
|
sf_count_t old_pos ;
|
|
int ret ;
|
|
|
|
best_gp = pcm_start = oopus->u.decode.gp_start ;
|
|
pcm_end = oopus->u.decode.gp_end ;
|
|
begin = psf->dataoffset ;
|
|
end = oopus->u.decode.last_offset ;
|
|
|
|
/* Search the Ogg stream for such a page */
|
|
old_pos = ogg_sync_ftell (psf) ;
|
|
ret = ogg_stream_seek_page_search (psf, odata, target_gp, pcm_start, pcm_end, &best_gp, begin, end, 48000) ;
|
|
if (ret != 0)
|
|
{ ogg_sync_fseek (psf, old_pos, SEEK_SET) ;
|
|
return ret ;
|
|
} ;
|
|
|
|
/* Load the page that contains our pre-roll target */
|
|
oopus->loc = 0 ;
|
|
oopus->len = 0 ;
|
|
if ((ret = ogg_opus_unpack_next_page (psf, odata, oopus)) != 1)
|
|
return ret ;
|
|
oopus->pkt_pos = best_gp ;
|
|
|
|
/* Reset the decoder (gain settings survive the reset) */
|
|
opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_RESET_STATE) ;
|
|
|
|
return 0 ;
|
|
} /* ogg_opus_page_seek_search */
|
|
|
|
/*
|
|
** ogg_opus_page_seek_manual
|
|
**
|
|
** Seek to the beginning of the Ogg stream and read pages until we find one with
|
|
** a granule position at or before our target.
|
|
*/
|
|
static sf_count_t
|
|
ogg_opus_page_seek_manual (SF_PRIVATE *psf, uint64_t target_gp)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
sf_count_t pos ;
|
|
int nn ;
|
|
|
|
if (oopus->pg_pos > target_gp)
|
|
{ ogg_stream_reset (&odata->ostream) ;
|
|
pos = ogg_sync_fseek (psf, psf->dataoffset, SEEK_SET) ;
|
|
if (pos < 0)
|
|
return pos ;
|
|
oopus->pg_pos = oopus->u.decode.gp_start ;
|
|
opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_RESET_STATE) ;
|
|
} ;
|
|
|
|
while (oopus->pg_pos < target_gp)
|
|
{ nn = ogg_opus_unpack_next_page (psf, odata, oopus) ;
|
|
if (nn <= 0)
|
|
return nn ;
|
|
} ;
|
|
|
|
return 1 ;
|
|
} /* ogg_opus_page_seek_manual */
|
|
|
|
static sf_count_t
|
|
ogg_opus_seek (SF_PRIVATE *psf, int mode, sf_count_t offset)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
uint64_t target_gp, current_gp ;
|
|
int ret ;
|
|
|
|
/* Only support seeking in read mode. */
|
|
if (mode != SFM_READ || psf->file.mode != SFM_READ)
|
|
{ psf->error = SFE_BAD_SEEK ;
|
|
return PSF_SEEK_ERROR ;
|
|
} ;
|
|
|
|
/* Figure out the current position granule pos. Use the start of the
|
|
* current buffer, to avoid backwards seeking if the target is on the page
|
|
* but before the current locaiton. */
|
|
oopus->loc = 0 ;
|
|
current_gp = oopus->pkt_pos - (uint64_t) (oopus->len * oopus->sr_factor) ;
|
|
|
|
/* Calculate the target granule pos. This includes the decoder delay and
|
|
* the file granule position offset. */
|
|
target_gp = offset * oopus->sr_factor ;
|
|
target_gp += oopus->u.decode.gp_start ;
|
|
target_gp += oopus->header.preskip ;
|
|
|
|
/* Check if we need to do a page seek. */
|
|
if (target_gp < current_gp || target_gp - current_gp > OGG_OPUS_PREROLL)
|
|
{ uint64_t preroll_gp ;
|
|
|
|
/* For a page seek, use an earlier target granule pos, giving the
|
|
* decoder samples to converge before the actual target. */
|
|
if (target_gp >= OGG_OPUS_PREROLL + oopus->u.decode.gp_start + (uint64_t) oopus->header.preskip)
|
|
{ preroll_gp = target_gp - OGG_OPUS_PREROLL ;
|
|
}
|
|
else
|
|
{ preroll_gp = oopus->u.decode.gp_start + (uint64_t) oopus->header.preskip ;
|
|
} ;
|
|
|
|
if (oopus->u.decode.gp_end == (uint64_t) -1)
|
|
{ /*
|
|
** Don't know the end of the file. Could be a chained file we don't yet
|
|
** support. Oh well, just do it manually.
|
|
*/
|
|
ogg_opus_page_seek_manual (psf, preroll_gp) ;
|
|
}
|
|
else
|
|
{ ret = ogg_opus_page_seek_search (psf, preroll_gp) ;
|
|
if (ret < 0)
|
|
{ /*
|
|
** Page seek failed, what to do? Could be bad data. We can
|
|
** either fall-back to manual seeking or bail. Manaul seeking
|
|
** from the beginning has the advantage of finding where the
|
|
** file goes bad.
|
|
*/
|
|
ret = ogg_opus_page_seek_manual (psf, preroll_gp) ;
|
|
if (ret < 0)
|
|
{ /*
|
|
** If were here, and there is no error, we can be pretty
|
|
** sure that it's the file that is to blame.
|
|
*/
|
|
if (!psf->error)
|
|
psf->error = SFE_MALFORMED_FILE ;
|
|
return ret ;
|
|
} ;
|
|
} ;
|
|
} ;
|
|
|
|
/*
|
|
** Skip over packets on the found page that are before our pre-roll
|
|
** target to avoid unnecessary decoding, and make decoder convergence
|
|
** independent of page boundaries for more visible errors.
|
|
*/
|
|
for ( ; odata->pkt_indx != odata->pkt_len ; )
|
|
{ ogg_packet *ppkt = &odata->pkt [odata->pkt_indx] ;
|
|
int nsamp = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 48000) ;
|
|
if (oopus->pkt_pos + nsamp < preroll_gp)
|
|
{ oopus->pkt_pos += nsamp ;
|
|
odata->pkt_indx++ ;
|
|
}
|
|
else
|
|
break ;
|
|
} ;
|
|
} ;
|
|
|
|
/*
|
|
** We've seeked or skipped through pages until just before our target,
|
|
** now decode until we hit it.
|
|
*/
|
|
offset = ogg_opus_null_read (psf, target_gp / oopus->sr_factor) ;
|
|
return offset - ((oopus->header.preskip + oopus->u.decode.gp_start) / oopus->sr_factor) ;
|
|
|
|
} /* ogg_opus_seek */
|
|
|
|
static int
|
|
ogg_opus_command (SF_PRIVATE *psf, int command, void *data, int datasize)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
double quality ;
|
|
double latency ;
|
|
int error ;
|
|
|
|
switch (command)
|
|
{ case SFC_SET_CHANNEL_MAP_INFO :
|
|
/* TODO: figure this out */
|
|
break ;
|
|
|
|
case SFC_SET_OGG_PAGE_LATENCY :
|
|
/*
|
|
** Argument: double, range 50 to 1600.
|
|
** Average length of OGG page in ms.
|
|
** This length drive the flush of pages.
|
|
*/
|
|
if (data == NULL || datasize != SIGNED_SIZEOF (double))
|
|
return SFE_BAD_COMMAND_PARAM ;
|
|
|
|
latency = *((double *) data) ;
|
|
if (latency < 50)
|
|
latency = 50 ;
|
|
|
|
if (latency > 1600)
|
|
latency = 1600 ;
|
|
|
|
oopus->u.encode.latency = ((unsigned long) latency) * 48 ;
|
|
break ;
|
|
|
|
case SFC_SET_COMPRESSION_LEVEL :
|
|
/*
|
|
** Argument: double, range 0.0 (lest compressed, best quality) to
|
|
** 1.0 (most compressed, worst quality)
|
|
*/
|
|
if (data == NULL || datasize != SIGNED_SIZEOF (double))
|
|
return SFE_BAD_COMMAND_PARAM ;
|
|
|
|
/* Usable bitrate range is [6, 256] kbps per channel. */
|
|
quality = *((double *) data) ;
|
|
oopus->u.encode.bitrate = (int) (((1.0 - quality) * (250000.0)) + 6000.0) * psf->sf.channels ;
|
|
if (opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_BITRATE (oopus->u.encode.bitrate)) == OPUS_OK)
|
|
{ psf_log_printf (psf, "User changed encoding target bitrate to %dbps\n", oopus->u.encode.bitrate) ;
|
|
return SF_TRUE ;
|
|
}
|
|
psf_log_printf (psf, "Failed to set user encoding target bitrate of %dbps\n", oopus->u.encode.bitrate) ;
|
|
return SF_FALSE ;
|
|
break ;
|
|
|
|
case SFC_SET_ORIGINAL_SAMPLERATE :
|
|
if (data == NULL || datasize != SIGNED_SIZEOF (int))
|
|
return SFE_BAD_COMMAND_PARAM ;
|
|
/*
|
|
** Only allow changing the input samplerate if at the beginning
|
|
** of the stream, because while it might be possible to change
|
|
** samplerate mid-decode, or to re-write the header for encode,
|
|
** ain't nobody got time to implement and test that.
|
|
*/
|
|
if (psf->file.mode == SFM_WRITE)
|
|
{ if (psf->have_written)
|
|
return SF_FALSE ;
|
|
oopus->header.input_samplerate = *((int *) data) ;
|
|
}
|
|
else {
|
|
if (oopus->pkt_pos > oopus->u.decode.gp_start || oopus->loc > 0)
|
|
return SF_FALSE ;
|
|
if ((error = ogg_opus_setup_decoder (psf, *((int *) data))))
|
|
return error ;
|
|
odata->pkt_indx = 0 ;
|
|
/* Adjust file frames count. */
|
|
if (oopus->u.decode.gp_end != (uint64_t) -1)
|
|
psf->sf.frames = (oopus->u.decode.gp_end - oopus->u.decode.gp_start
|
|
- oopus->header.preskip) / oopus->sr_factor ;
|
|
} ;
|
|
return SF_TRUE ;
|
|
|
|
case SFC_GET_ORIGINAL_SAMPLERATE :
|
|
if (data == NULL || datasize != SIGNED_SIZEOF (int))
|
|
return SFE_BAD_COMMAND_PARAM ;
|
|
*((int *) data) = oopus->header.input_samplerate ;
|
|
return SF_TRUE ;
|
|
|
|
case SFC_GET_OGG_STREAM_SERIALNO :
|
|
if (data == NULL || datasize != sizeof (int32_t))
|
|
return SF_FALSE ;
|
|
|
|
*((int32_t *) data) = odata->ostream.serialno ;
|
|
return SF_TRUE ;
|
|
|
|
default :
|
|
break ;
|
|
}
|
|
|
|
return SF_FALSE ;
|
|
} /* ogg_opus_command */
|
|
|
|
static int
|
|
ogg_opus_byterate (SF_PRIVATE *psf)
|
|
{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ;
|
|
OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ;
|
|
|
|
if (psf->file.mode == SFM_READ)
|
|
{ if (odata->pkt_indx == odata->pkt_len)
|
|
{ if (ogg_opus_unpack_next_page (psf, odata, oopus) < 0)
|
|
return -1 ;
|
|
} ;
|
|
|
|
if (odata->pkt_indx < odata->pkt_len)
|
|
{ ogg_packet *ppkt = &odata->pkt [odata->pkt_indx] ;
|
|
return (ppkt->bytes * 8000) / opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 8000) ;
|
|
} ;
|
|
|
|
if (psf->datalength != SF_COUNT_MAX)
|
|
return (psf->datalength * psf->sf.samplerate) / psf->sf.frames ;
|
|
} ;
|
|
|
|
if (psf->file.mode == SFM_WRITE && oopus->u.encode.state != NULL)
|
|
return (oopus->u.encode.bitrate + 7) / 8 ;
|
|
|
|
return -1 ;
|
|
} /* ogg_opus_byterate */
|
|
|
|
#else /* HAVE_EXTERNAL_XIPH_LIBS */
|
|
|
|
int
|
|
ogg_opus_open (SF_PRIVATE *psf)
|
|
{
|
|
psf_log_printf (psf, "This version of libsndfile was compiled without Ogg/Opus support.\n") ;
|
|
return SFE_UNIMPLEMENTED ;
|
|
} /* ogg_opus_open */
|
|
|
|
#endif
|