| /* | |
| ** 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/opus.h> | |
| #include <opus/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 |