Opus file support

Ogg: Introduce new functions ogg_sync_fseek(), ogg_ftell(), ogg_next_page(),
ogg_stream_next_page(), ogg_sync_last_page_before(), and
ogg_stream_unpack_page(). Use ogg_sync_* for ogg_read_first_page(). Bump libogg
version requirement from 1.1.3 to 1.3.0 as LibOgg 1.3.0 is required for
ogg_stream_pageout_fill() and ogg_stream_flush_fill().

Opus: Add opus support. Document added commands
SFC_(GET|SET)_ORIGINAL_SAMPLERATE. Added or extended tests ogg_opus_test,
compression_size_test, floating_point_test, lossy_comp_test, string_test,
external_libs_test. Change Opus to non-experimental.
This commit is contained in:
Arthur Taylor 2018-07-09 14:37:06 -07:00 committed by Erik de Castro Lopo
parent 1a87c443fe
commit 326e4533f3
27 changed files with 3587 additions and 139 deletions

View File

@ -14,6 +14,7 @@ matrix:
- libvorbis-dev
- libflac-dev
- libasound2-dev
- libopus-dev
- os: linux
dist: xenial
compiler: gcc
@ -25,6 +26,7 @@ matrix:
- libvorbis-dev
- libflac-dev
- libasound2-dev
- libopus-dev
- os: osx
compiler: clang
@ -33,7 +35,7 @@ before_install:
- |
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
brew update
brew install autogen flac libogg libvorbis
brew install autogen flac libogg libvorbis opus
fi
install:

View File

@ -55,7 +55,7 @@ include(SndFileChecks)
cmake_dependent_option (BUILD_REGTEST "Build regtest" ON "SQLITE3_FOUND" OFF)
cmake_dependent_option (ENABLE_EXTERNAL_LIBS "Enable FLAC and Vorbis codecs" ON "VORBISENC_FOUND;FLAC_FOUND" OFF)
cmake_dependent_option (ENABLE_EXTERNAL_LIBS "Enable FLAC, Vorbis, and Opus codecs" ON "VORBISENC_FOUND;FLAC_FOUND;OPUS_FOUND" OFF)
cmake_dependent_option (ENABLE_CPU_CLIP "Enable tricky cpu specific clipper" ON "CPU_CLIPS_POSITIVE;CPU_CLIPS_NEGATIVE" OFF)
if (NOT ENABLE_CPU_CLIP)
set (CPU_CLIPS_POSITIVE FALSE)
@ -70,9 +70,10 @@ set (HAVE_SNDIO_H ${SNDIO_FOUND})
set (ENABLE_EXPERIMENTAL_CODE ${ENABLE_EXPERIMENTAL})
set (HAVE_SPEEX ${ENABLE_EXPERIMENTAL})
set (HAVE_OPUS ${ENABLE_EXPERIMENTAL})
add_feature_info (BUILD_SHARED_LIBS BUILD_SHARED_LIBS "build shared libraries")
add_feature_info (ENABLE_EXTERNAL_LIBS ENABLE_EXTERNAL_LIBS "enable FLAC and Vorbis codecs")
add_feature_info (ENABLE_EXTERNAL_LIBS ENABLE_EXTERNAL_LIBS "enable FLAC, Vorbis, and Opus codecs")
add_feature_info (ENABLE_EXPERIMENTAL ENABLE_EXPERIMENTAL "enable experimental code")
add_feature_info (BUILD_TESTING BUILD_TESTING "build tests")
add_feature_info (BUILD_REGTEST BUILD_REGTEST "build regtest")
@ -92,7 +93,7 @@ set_package_properties (Ogg PROPERTIES
TYPE RECOMMENDED
URL "www.xiph.org/ogg/"
DESCRIPTION "library for manipulating ogg bitstreams"
PURPOSE "Required to enable Vorbis, Speex and Opus support"
PURPOSE "Required to enable Vorbis, Speex, and Opus support"
)
set_package_properties (VorbisEnc PROPERTIES
TYPE RECOMMENDED
@ -106,6 +107,12 @@ set_package_properties (FLAC PROPERTIES
DESCRIPTION "Free Lossless Audio Codec Library"
PURPOSE "Enables FLAC support"
)
set_package_properties(Opus PROPERTIES
TYPE RECOMMENDED
URL "www.opus-codec.org/"
DESCRIPTION "Standardized open source low-latency fullband codec"
PURPOSE "Enables experimental Opus support"
)
set_package_properties(Speex PROPERTIES TYPE OPTIONAL
URL "www.speex.org/"
DESCRIPTION "an audio codec tuned for speech"
@ -230,6 +237,7 @@ add_library (sndfile
src/ogg_speex.c
src/ogg_pcm.c
src/ogg_opus.c
src/ogg_vcomment.c
src/nms_adpcm.c
src/GSM610/config.h
src/GSM610/gsm.h
@ -288,6 +296,7 @@ target_link_libraries (sndfile
$<$<BOOL:${HAVE_EXTERNAL_XIPH_LIBS}>:Vorbis::VorbisEnc>
$<$<BOOL:${HAVE_EXTERNAL_XIPH_LIBS}>:FLAC::FLAC>
$<$<AND:$<BOOL:${ENABLE_EXPERIMENTAL}>,$<BOOL:${HAVE_EXTERNAL_XIPH_LIBS}>,$<BOOL:${HAVE_SPEEX}>>:Speex::Speex>
$<$<BOOL:${HAVE_EXTERNAL_XIPH_LIBS}>:Opus::Opus>
)
set_target_properties (sndfile PROPERTIES
PUBLIC_HEADER "${sndfile_HDRS}"
@ -1022,6 +1031,14 @@ if (BUILD_TESTING)
$<$<BOOL:${LIBM_REQUIRED}>:m>
)
add_executable (ogg_opus_test tests/ogg_opus_test.c)
target_link_libraries (ogg_opus_test
PRIVATE
sndfile
test_utils
$<$<BOOL:${LIBM_REQUIRED}>:m>
)
add_executable (stdin_test tests/stdin_test.c)
target_link_libraries (stdin_test
PRIVATE
@ -1242,6 +1259,12 @@ if (BUILD_TESTING)
add_test (string_test_ogg string_test ogg)
add_test (misc_test_ogg misc_test ogg)
### opus-tests ###
add_test (ogg_opus_test ogg_opus_test)
add_test (compression_size_test_opus compression_size_test opus)
add_test (lossy_comp_test_ogg_opus lossy_comp_test ogg_opus)
add_test (string_test_opus string_test opus)
### io-tests
add_test (stdio_test stdio_test)
add_test (pipe_test pipe_test)

View File

@ -21,7 +21,8 @@ cmake_files = cmake/ClipMode.cmake cmake/FindFLAC.cmake \
cmake/FindOgg.cmake cmake/FindVorbis.cmake cmake/FindSndio.cmake \
cmake/FindSpeex.cmake cmake/FindSQLite3.cmake cmake/FindVorbisEnc.cmake \
cmake/SndFileChecks.cmake cmake/TestInline.cmake \
cmake/TestLargeFiles.cmake cmake/TestInline.c.in
cmake/TestLargeFiles.cmake cmake/TestInline.c.in \
cmake/FindOpus.cmake
pkgconfig_DATA = sndfile.pc
@ -64,8 +65,8 @@ src_libsndfile_la_LDFLAGS = -no-undefined -version-info $(SHARED_VERSION_INFO) $
src_libsndfile_la_SOURCES = src/sndfile.c src/aiff.c src/au.c src/avr.c src/caf.c src/dwd.c src/flac.c src/g72x.c src/htk.c src/ircam.c \
src/macos.c src/mat4.c src/mat5.c src/nist.c src/paf.c src/pvf.c src/raw.c src/rx2.c src/sd2.c \
src/sds.c src/svx.c src/txw.c src/voc.c src/wve.c src/w64.c src/wavlike.c src/wav.c src/xi.c src/mpc2k.c src/rf64.c \
src/ogg_vorbis.c src/ogg_speex.c src/ogg_pcm.c src/ogg_opus.c src/common.h src/sfconfig.h src/sfendian.h src/wavlike.h \
src/sf_unistd.h src/ogg.h src/chanmap.h
src/ogg_vorbis.c src/ogg_speex.c src/ogg_pcm.c src/ogg_opus.c src/ogg_vcomment.c \
src/common.h src/sfconfig.h src/sfendian.h src/wavlike.h src/sf_unistd.h src/ogg.h src/chanmap.h src/ogg_vcomment.h
nodist_src_libsndfile_la_SOURCES = $(nodist_include_HEADERS)
src_libsndfile_la_LIBADD = src/GSM610/libgsm.la src/G72x/libg72x.la src/ALAC/libalac.la \
src/libcommon.la $(EXTERNAL_XIPH_LIBS) -lm
@ -217,7 +218,7 @@ check_PROGRAMS += tests/sfversion tests/floating_point_test tests/write_read_tes
tests/locale_test tests/win32_ordinal_test tests/ogg_test tests/compression_size_test \
tests/checksum_test tests/external_libs_test tests/rdwr_test tests/format_check_test $(CPP_TEST) \
tests/channel_test tests/long_read_write_test tests/stdin_test tests/stdout_test \
tests/dither_test tests/fix_this tests/largefile_test tests/benchmark
tests/dither_test tests/fix_this tests/largefile_test tests/benchmark tests/ogg_opus_test
BUILT_SOURCES += \
tests/write_read_test.c \
@ -339,6 +340,9 @@ tests_virtual_io_test_LDADD = src/libsndfile.la
tests_ogg_test_SOURCES = tests/ogg_test.c tests/utils.c tests/utils.h
tests_ogg_test_LDADD = src/libsndfile.la
tests_ogg_opus_test_SOURCES = tests/ogg_opus_test.c tests/utils.c tests/utils.h
tests_ogg_opus_test_LDADD = src/libsndfile.la
tests_compression_size_test_SOURCES = tests/compression_size_test.c tests/utils.c tests/utils.h tests/dft_cmp.h
tests_compression_size_test_LDADD = src/libsndfile.la

View File

@ -7,7 +7,7 @@ platform:
install:
- if %platform%==Win32 set VCPKG_TRIPLET=x86-windows
- if %platform%==x64 set VCPKG_TRIPLET=x64-windows
- vcpkg install libogg:%VCPKG_TRIPLET% libvorbis:%VCPKG_TRIPLET% libflac:%VCPKG_TRIPLET% sqlite3:%VCPKG_TRIPLET%
- vcpkg install libogg:%VCPKG_TRIPLET% libvorbis:%VCPKG_TRIPLET% libflac:%VCPKG_TRIPLET% sqlite3:%VCPKG_TRIPLET% opus:%VCPKG_TRIPLET%
before_build:
- mkdir CMakeBuild
- cd CMakeBuild

View File

@ -11,7 +11,7 @@ if (OGG_INCLUDE_DIR)
endif ()
find_package (PkgConfig QUIET)
pkg_check_modules (PC_OGG QUIET ogg)
pkg_check_modules (PC_OGG QUIET ogg>=1.3.0)
set (OGG_VERSION ${PC_OGG_VERSION})

67
cmake/FindOpus.cmake Normal file
View File

@ -0,0 +1,67 @@
# - Find opus
# Find the native opus includes and libraries
#
# OPUS_INCLUDE_DIRS - where to find opus.h, etc.
# OPUS_LIBRARIES - List of libraries when using opus.
# OPUS_FOUND - True if Opus found.
if (OPUS_INCLUDE_DIR)
# Already in cache, be silent
set(OPUS_FIND_QUIETLY TRUE)
endif ()
find_package (Ogg QUIET)
find_package (PkgConfig QUIET)
pkg_check_modules(PC_OPUS QUIET opus>=1.1)
set (OPUS_VERSION ${PC_OPUS_VERSION})
find_path (OPUS_INCLUDE_DIR opus/opus.h
HINTS
${PC_OPUS_INCLUDEDIR}
${PC_OPUS_INCLUDE_DIRS}
${OPUS_ROOT}
)
# MSVC built opus may be named opus_static.
# The provided project files name the library with the lib prefix.
find_library (OPUS_LIBRARY
NAMES
opus
opus_static
libopus
libopus_static
HINTS
${PC_OPUS_LIBDIR}
${PC_OPUS_LIBRARY_DIRS}
${OPUS_ROOT}
)
# Handle the QUIETLY and REQUIRED arguments and set OPUS_FOUND
# to TRUE if all listed variables are TRUE.
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args (Opus
REQUIRED_VARS
OPUS_LIBRARY
OPUS_INCLUDE_DIR
OGG_FOUND
VERSION_VAR
OPUS_VERSION
)
if (OPUS_FOUND)
set (OPUS_LIBRARIES ${OPUS_LIBRARY})
set (OPUS_INCLUDE_DIRS ${OPUS_INCLUDE_DIR})
if (NOT TARGET Opus::Opus)
add_library (Opus::Opus UNKNOWN IMPORTED)
set_target_properties (Opus::Opus PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${OPUS_INCLUDE_DIRS}"
IMPORTED_LOCATION "${OPUS_LIBRARIES}"
)
endif ()
endif ()
mark_as_advanced(OPUS_INCLUDE_DIR OPUS_LIBRARY)

View File

@ -29,7 +29,8 @@ endif ()
find_package (VorbisEnc)
find_package (FLAC)
if (VORBIS_FOUND AND FLAC_FOUND)
find_package (Opus)
if (VORBIS_FOUND AND FLAC_FOUND AND OPUS_FOUND)
set (HAVE_EXTERNAL_XIPH_LIBS 1)
else ()
set (HAVE_EXTERNAL_XIPH_LIBS 0)

View File

@ -336,7 +336,7 @@ AS_IF([test -n "$PKG_CONFIG"], [
dnl Make sure the FLAC_CFLAGS value is sane.
FLAC_CFLAGS=`echo $FLAC_CFLAGS | $SED "s|include/FLAC|include|"`
PKG_CHECK_MOD_VERSION(OGG, ogg >= 1.1.3, ac_cv_ogg=yes, ac_cv_ogg=no)
PKG_CHECK_MOD_VERSION(OGG, ogg >= 1.3.0, ac_cv_ogg=yes, ac_cv_ogg=no)
AS_IF([test "x$enable_experimental" = "xyes"], [
PKG_CHECK_MOD_VERSION(SPEEX, speex >= 1.2, ac_cv_speex=yes, ac_cv_speex=no)
@ -350,15 +350,17 @@ AS_IF([test -n "$PKG_CONFIG"], [
dnl See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=549899
PKG_CHECK_MOD_VERSION(VORBIS, vorbis >= 1.2.3, ac_cv_vorbis=yes, ac_cv_vorbis=no)
PKG_CHECK_MOD_VERSION(VORBISENC, vorbisenc >= 1.2.3, ac_cv_vorbisenc=yes, ac_cv_vorbisenc=no)
PKG_CHECK_MOD_VERSION(OPUS, opus >= 1.1, ac_cv_opus=yes, ac_cv_opus=no)
enable_external_libs=yes
])
AS_IF([test "x$ac_cv_flac$ac_cv_ogg$ac_cv_vorbis$ac_cv_vorbisenc" = "xyesyesyesyes"], [
AS_IF([test "x$ac_cv_flac$ac_cv_ogg$ac_cv_vorbis$ac_cv_vorbisenc$ac_cv_opus" = "xyesyesyesyesyes"], [
HAVE_EXTERNAL_XIPH_LIBS=1
enable_external_libs=yes
EXTERNAL_XIPH_CFLAGS="$FLAC_CFLAGS $OGG_CFLAGS $VORBIS_CFLAGS $VORBISENC_CFLAGS $SPEEX_CFLAGS"
EXTERNAL_XIPH_LIBS="$FLAC_LIBS $OGG_LIBS $VORBIS_LIBS $VORBISENC_LIBS $SPEEX_LIBS "
EXTERNAL_XIPH_CFLAGS="$FLAC_CFLAGS $OGG_CFLAGS $VORBIS_CFLAGS $VORBISENC_CFLAGS $SPEEX_CFLAGS $OPUS_CFLAGS"
EXTERNAL_XIPH_LIBS="$FLAC_LIBS $OGG_LIBS $VORBIS_LIBS $VORBISENC_LIBS $SPEEX_LIBS $OPUS_LIBS "
], [
AS_ECHO([""])
AC_MSG_WARN([[*** One or more of the external libraries (ie libflac, libogg and]])
@ -372,7 +374,7 @@ AS_IF([test -n "$PKG_CONFIG"], [
])
])
AC_DEFINE_UNQUOTED([HAVE_EXTERNAL_XIPH_LIBS], [$HAVE_EXTERNAL_XIPH_LIBS], [Will be set to 1 if flac, ogg and vorbis are available.])
AC_DEFINE_UNQUOTED([HAVE_EXTERNAL_XIPH_LIBS], [$HAVE_EXTERNAL_XIPH_LIBS], [Will be set to 1 if flac, ogg, vorbis, and opus are available.])
dnl ====================================================================================
dnl Check for libsqlite3 (only used in regtest).
@ -702,7 +704,7 @@ AC_MSG_RESULT([
Experimental code : ................... ${enable_experimental:-no}
Using ALSA in example programs : ...... ${enable_alsa:-no}
External FLAC/Ogg/Vorbis : ............ ${enable_external_libs:-no}
External FLAC/Ogg/Vorbis/Opus : ....... ${enable_external_libs:-no}
Building Octave interface : ........... ${OCTAVE_BUILD}
Tools :

View File

@ -269,7 +269,14 @@
<TD><A HREF="#SFC_RF64_AUTO_DOWNGRADE">SFC_RF64_AUTO_DOWNGRADE</A></TD>
<TD>Enable auto downgrade from RF64 to WAV</TD>
</TR>
<TR>
<TD><A HREF="#SFC_GET_ORIGINAL_SAMPLERATE">SFC_GET_ORIGINAL_SAMPLERATE</A></TD>
<TD>Get original samplerate</TD>
</TR>
<TR>
<TD><A HREF="#SFC_SET_ORIGINAL_SAMPLERATE">SFC_SET_ORIGINAL_SAMPLERATE</A></TD>
<TD>Set original samplerate</TD>
</TR>
<!--
<TR>
<TD><A HREF="#add-dither">add dither</A></TD>
@ -1948,6 +1955,99 @@ Example:
</DL>
<!-- ========================================================================= -->
<A NAME="SFC_GET_ORIGINAL_SAMPLERATE"></A>
<H2><BR><B>SFC_GET_ORIGINAL_SAMPLERATE</B></H2>
<P>
Get original samplerate metadata.
</P>
<P>
The Opus audio codec stores audio data independent of samplerate, but only
supports encoding or decoding at 8000Hz, 12000Hz, 16000Hz, 24000HZ or 48000Hz.
Opus includes a header field to record the original source input samplerate, and
a samplerate converter may be used if needed.
</p>
<P>
This command gets the original samplerate header field. It does not enable any
(non-existent) samplerate conversion, nor change the current decoder samplerate.
</P>
<p>
Parameters:
</p>
<PRE>
sndfile : A valid SNDFILE* pointer
cmd : SFC_GET_ORIGINAL_SAMPLERATE
data : pointer to an integer
datasize : sizeof (int)
</PRE>
<P>
Example:
</P>
<PRE>
/* Get the original sample rate */
int original_samplerate ;
sf_command (sndfile, SFC_GET_ORIGINAL_SAMPLERATE, &amp;original_samplerate, sizeof (original_samplerate)) ;
</PRE>
<DL>
<DT>Return value:</DT>
<dd>Returns SF_TRUE on success, SF_FALSE otherwise.
<dd>The passed integer is set to the value of the original samplerate.
</DL>
<!-- ========================================================================= -->
<A NAME="SFC_SET_ORIGINAL_SAMPLERATE"></A>
<H2><BR><B>SFC_SET_ORIGINAL_SAMPLERATE</B></H2>
<P>
Set original samplerate metadata.
</P>
<P>
The Opus audio codec stores audio data independent of samplerate, but only
supports encoding or decoding at 8000Hz, 12000Hz, 16000Hz, 24000HZ or 48000Hz.
Opus includes a header field to record the original source input samplerate, and
a samplerate converter may be used if needed.
</p>
<p>
When writing an Opus file this command sets the original samplerate header field
to the provided value, which is then stored in the file. This has no effect on
the current encoder samplerate.
</p>
<p>
When reading an Opus file this command overrides the original samplerate value
as read from the file. libsndfile uses this value to choose what samplerate
to decode at, rounding up to the nearest valid Opus samplerate. After a
successful call, the file samplerate and frames count may have changed.
</p>
<p>
Note: This command should be issued before the first bit of audio data has been
read from or written to the file.
</p>
<p>
Parameters:
</p>
<PRE>
sndfile : A valid SNDFILE* pointer
cmd : SFC_SET_ORIGINAL_SAMPLERATE
data : pointer to an integer
datasize : sizeof (int)
</PRE>
<P>
Example:
</P>
<PRE>
/* Store the original sample rate as 44100 */
int original_samplerate 44100;
sf_command (sndfile, SFC_SET_ORIGINAL_SAMPLERATE, &amp;original_samplerate, sizeof (input_samplerate)) ;
</PRE>
<DL>
<DT>Return value:</DT>
<dd>Returns SF_TRUE on success, SF_FALSE otherwise.
<dd>On write, can only succeed if no data has been written.
<dd>On read, if successful, <a HREF="#SFC_GET_CURRENT_SF_INFO">SFC_GET_CURRENT_SF_INFO</a>
should be called to determine the new frames count and samplerate
</DL>
<!-- ========================================================================= -->

View File

@ -66,6 +66,10 @@ static SF_FORMAT_INFO const simple_formats [] =
},
#if HAVE_EXTERNAL_XIPH_LIBS
{ SF_FORMAT_OGG | SF_FORMAT_OPUS,
"Ogg Opus (Xiph Foundation)", "opus"
},
{ SF_FORMAT_OGG | SF_FORMAT_VORBIS,
"Ogg Vorbis (Xiph Foundation)", "oga"
},
@ -208,6 +212,7 @@ static SF_FORMAT_INFO subtype_formats [] =
#if HAVE_EXTERNAL_XIPH_LIBS
{ SF_FORMAT_VORBIS, "Vorbis", NULL },
{ SF_FORMAT_OPUS, "Opus", NULL },
#endif
{ SF_FORMAT_ALAC_16, "16 bit ALAC", NULL },

View File

@ -758,6 +758,8 @@ enum
SFE_FILENAME_TOO_LONG,
SFE_NEGATIVE_RW_LEN,
SFE_OPUS_BAD_SAMPLERATE,
SFE_MAX_ERROR /* This must be last in list. */
} ;

712
src/ogg.c
View File

@ -1,6 +1,7 @@
/*
** Copyright (C) 2002-2016 Erik de Castro Lopo <erikd@mega-nerd.com>
** Copyright (C) 2007 John ffitch
** 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
@ -17,6 +18,40 @@
** 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.
*/
#include "sfconfig.h"
#include <stdio.h>
@ -42,13 +77,78 @@
#include "ogg.h"
static int ogg_close (SF_PRIVATE *psf) ;
static int ogg_stream_classify (SF_PRIVATE *psf, OGG_PRIVATE * odata) ;
static int ogg_page_classify (SF_PRIVATE * psf, const ogg_page * og) ;
#define OGG_SYNC_READ_SIZE (2048)
#define OGG_PAGE_SIZE_MAX (65307)
#define OGG_CHUNK_SIZE (65536)
#define OGG_CHUNK_SIZE_MAX (1024*1024)
int ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
{ char *buffer ;
int bytes ;
/*
* The Ogg container may seem overly complicated, particularly when used for a
* on-disk audio file format. This is probably because Ogg is designed with
* streaming rather than storage as a priority, and can handle multiple codec
* payloads multiplexed together, then possibly chained on top of that.
* Ogg achieves its goals well, but it does lend to a bit of a learning curve,
* with many internal structures to push data around in compared to most sound
* file formats which only have a header and raw data.
*
* See
* - [https://xiph.org/ogg/doc/oggstream.html]
* - [https://xiph.org/ogg/doc/framing.html]
*
* libogg Memory Management
* ===========================================================================
*
* libOgg's memory management is documented in code, not in headers or external
* documentation. What follows is not an attempt to completely document it, but
* an explanation of the basics.
*
* libOgg has two data structures which allocate and manage data buffers: The
* ogg_sync_state structure and the ogg_stream_state structure. The remaining
* structures of ogg_page and ogg_packet are views into the buffers managed by
* the previous structures.
*
* ogg_sync_state is used for reading purposes. It takes a physical bitstream
* and searches for, validates, and returns complete Ogg Pages. The
* ogg_sync_state buffers the returned page data, holding at most one
* complete page at a time. A returned Ogg page remains valid until any
* operation other than ogg_sync_check() is called.
*
* ogg_stream_state is used for both reading and writing. For reading, the
* contents of an ogg_page is copied into the stream state. This data is
* buffered to be split or joined as necessary into complete ogg_packets. If,
* after copying an ogg_page into an ogg_stream_state, packets are available to
* be read, then all of those packets remain in memory and valid until either
* the ogg_stream_state is reset, destroyed, or a new ogg_page is read into it.
* As the maximum number of packets an Ogg Page may contain is 255, at most 255
* packets may be available from an ogg_stream_state at one time.
*
* For writing, the life cycle of a buffer pointed to by a ogg_packet is the
* responsibility of the caller. Packets written into an ogg_stream_state are
* buffered until a complete page is ready for writing. Pages for writing out
* remain in the ogg_stream_state's buffer and valid until either the
* ogg_stream_state is reset, cleared, destroyed. Writing another packet into
* the ogg_stream_state might also invalidate such pages, but writing in
* packets when a page is ready to be written out is a caller bug anyways.
*/
/*-----------------------------------------------------------------------------------------------
** Private function prototypes.
*/
static int ogg_close (SF_PRIVATE *psf) ;
static int ogg_stream_classify (SF_PRIVATE *psf, OGG_PRIVATE * odata) ;
static int ogg_page_classify (SF_PRIVATE * psf, const ogg_page * og) ;
static uint64_t ogg_page_search_do_rescale (uint64_t x, uint64_t from, uint64_t to) ;
static void ogg_page_search_continued_data (OGG_PRIVATE *odata, ogg_page *page) ;
/*-----------------------------------------------------------------------------------------------
** Exported functions.
*/
int
ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
{ int ret ;
char *buffer ;
/*
** The ogg standard requires that the first pages of a physical ogg
@ -60,47 +160,28 @@ int ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
** beyond the scope of this library.
*/
/* Weird stuff happens if these aren't called. */
ogg_stream_reset (&odata->ostream) ;
ogg_sync_reset (&odata->osync) ;
ret = ogg_sync_fseek (psf, psf->header.indx, SEEK_SET) ;
if (ret < 0)
return SFE_NOT_SEEKABLE ;
/* Expose the buffer */
buffer = ogg_sync_buffer (&odata->osync, 4096L) ;
buffer = ogg_sync_buffer (&odata->osync, psf->header.indx) ;
memcpy (buffer, psf->header.ptr, psf->header.indx) ;
ogg_sync_wrote (&odata->osync, psf->header.indx) ;
/*
** Grab some data. Beginning-of-stream Ogg pages are guarenteed to be
** small. 4096 bytes ought to be enough.
*/
ret = ogg_sync_next_page (psf, &odata->opage, SF_MAX (0l, 4096 - psf->header.indx), NULL) ;
/* Avoid seeking if the file has just been opened. */
if (psf_ftell (psf) == psf->header.indx)
{ /* Grab the part of the header that has already been read. */
memcpy (buffer, psf->header.ptr, psf->header.indx) ;
bytes = psf->header.indx ;
bytes += psf_fread (buffer + psf->header.indx, 1, 4096 - psf->header.indx, psf) ;
}
else
{ if (psf_fseek (psf, 0, SEEK_SET) != 0)
return SFE_NOT_SEEKABLE ;
bytes = psf_fread (buffer, 1, 4096, psf) ;
}
/* Have we simply run out of data? If so, we're done. */
if (ret == 0)
return 0 ;
if (ret < 0)
return psf->error ;
ogg_sync_wrote (&odata->osync, bytes) ;
/* Get the first page. Check for Beginning-of-stream bit */
if (ogg_sync_pageout (&odata->osync, &odata->opage) != 1 ||
ogg_page_bos (&odata->opage) == 0)
{
/* Have we simply run out of data? If so, we're done. */
if (bytes < 4096)
return 0 ;
/*
if (!ogg_page_bos (&odata->opage))
{ /*
** Error case. Either must not be an Ogg bitstream, or is in the
** middle of a bitstream (live capture), or in the middle of a
** bitstream and no complete page was in the buffer.
*/
psf_log_printf (psf, "Input does not appear to be the start of an Ogg bitstream.\n") ;
return SFE_MALFORMED_FILE ;
} ;
@ -109,8 +190,7 @@ int ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
** Get the serial number and set up the rest of decode.
** Serialno first ; use it to set up a logical stream.
*/
ogg_stream_clear (&odata->ostream) ;
ogg_stream_init (&odata->ostream, ogg_page_serialno (&odata->opage)) ;
ogg_stream_reset_serialno (&odata->ostream, ogg_page_serialno (&odata->opage)) ;
if (ogg_stream_pagein (&odata->ostream, &odata->opage) < 0)
{ /* Error ; stream version mismatch perhaps. */
@ -125,7 +205,502 @@ int ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
} ;
return 0 ;
}
} /* ogg_read_first_page */
int
ogg_write_page (SF_PRIVATE *psf, ogg_page *page)
{ int bytes ;
bytes = psf_fwrite (page->header, 1, page->header_len, psf) ;
bytes += psf_fwrite (page->body, 1, page->body_len, psf) ;
return bytes == page->header_len + page->body_len ;
} /* ogg_write_page */
sf_count_t
ogg_sync_ftell (SF_PRIVATE *psf)
{ OGG_PRIVATE* odata = (OGG_PRIVATE *) psf->container_data ;
sf_count_t position ;
position = psf_ftell (psf) ;
if (position >= 0)
{ /* success */
if (position < odata->osync.fill)
{ /* Really, this should be an assert. */
psf->error = SFE_INTERNAL ;
return -1 ;
}
position += (sf_count_t) (odata->osync.returned - odata->osync.fill) ;
}
return position ;
} /* ogg_sync_ftell */
sf_count_t
ogg_sync_fseek (SF_PRIVATE *psf, sf_count_t offset, int whence)
{ OGG_PRIVATE* odata = (OGG_PRIVATE *) psf->container_data ;
sf_count_t ret ;
ret = psf_fseek (psf, offset, whence) ;
if (ret >= 0)
{ /* success */
odata->eos = 0 ;
ogg_sync_reset (&odata->osync) ;
}
return ret ;
} /* ogg_sync_fseek */
int
ogg_sync_next_page (SF_PRIVATE * psf, ogg_page *og, sf_count_t readmax, sf_count_t *offset)
{ OGG_PRIVATE* odata = (OGG_PRIVATE *) psf->container_data ;
sf_count_t position, nb_read, read_ret ;
unsigned char *buffer ;
int synced ;
int report_hole = 0 ;
for (position = 0 ; readmax <= 0 || readmax > position ; )
{ synced = ogg_sync_pageseek (&odata->osync, og) ;
if (synced < 0)
{ /*
** Skipped -synced bytes before finding the start of a page.
** If seeking, we have just landed in the middle of a page.
** Otherwise, warn about junk in the bitstream.
** Page might not yet be ready, hence the continue.
*/
if (!offset)
report_hole = 1 ;
position -= synced ;
continue ;
} ;
if (report_hole)
{ psf_log_printf (psf, "Ogg : Skipped %d bytes looking for the next page. Corrupted bitstream?!\n", position) ;
report_hole = 0 ;
} ;
if (synced > 0)
{ /* Have a page */
if (offset)
*offset += position ;
return og->header_len + og->body_len ;
} ;
/*
** Else readmax == 0, Out of data. Try to read more in without
** invalidating our boundary (readmax) constraint.
*/
if (readmax == 0)
return 0 ;
if (readmax > 0)
nb_read = SF_MIN ((sf_count_t) OGG_SYNC_READ_SIZE, readmax - position) ;
else
nb_read = OGG_SYNC_READ_SIZE ;
buffer = (unsigned char *) ogg_sync_buffer (&odata->osync, nb_read) ;
read_ret = psf_fread (buffer, 1, nb_read, psf) ;
if (read_ret == 0)
return psf->error ? -1 : 0 ;
ogg_sync_wrote (&odata->osync, read_ret) ;
} ;
return 0 ;
} /* ogg_sync_next_page */
int
ogg_stream_next_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
{ int nn ;
if (odata->eos)
return 0 ;
for ( ; ; )
{ nn = ogg_sync_next_page (psf, &odata->opage, -1, NULL) ;
if (nn == 0)
{ psf_log_printf (psf, "Ogg : File ended unexpectedly without an End-Of-Stream flag set.\n") ;
odata->eos = 1 ;
}
if (nn <= 0)
return nn ;
if (ogg_page_serialno (&odata->opage) == odata->ostream.serialno)
break ;
} ;
if (ogg_page_eos (&odata->opage))
odata->eos = 1 ;
if (ogg_stream_pagein (&odata->ostream, &odata->opage) < 0)
{ psf->error = SFE_INTERNAL ;
return -1 ;
}
return 1 ;
} /* ogg_stream_next_page */
int
ogg_stream_unpack_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
{ int nn ;
int i ;
int found_hole = 0 ;
ogg_packet *ppkt = odata->pkt ;
odata->pkt_indx = 0 ;
nn = ogg_stream_packetout (&odata->ostream, ppkt) ;
if (nn == 0)
{ /*
** Steam is out of packets. Read in more pages until there is one, or
** the stream ends, or an error occurs.
*/
for ( ; nn == 0 ; nn = ogg_stream_packetout (&odata->ostream, ppkt))
{ nn = ogg_stream_next_page (psf, odata) ;
if (nn <= 0)
{ odata->pkt_len = 0 ;
return nn ;
}
}
/*
** In the case of the for loop exiting because
** ogg_stream_packetout() == -1, fall-through.
*/
}
if (nn == -1)
{ /*
** libOgg found a hole. That is, the next packet found was out of
** sequence. As such, "flush" the hole marker by removing the invalid
** packet, as the valid packets are queued behind it.
*/
psf_log_printf (psf, "Ogg : Warning, libogg reports a hole at %d bytes.\n", ogg_sync_ftell (psf)) ;
nn = ogg_stream_packetout (&odata->ostream, ppkt) ;
found_hole = 1 ;
}
/*
** Unpack all the packets on the page. It is undocumented (like much of
** libOgg behavior) but all packets from a page read into the stream are
** guarenteed to remain valid in memory until a new page is read into the
** stream.
*/
for (i = 1 ; ; i++)
{ /* Not an off-by-one, there are 255 not 256 packets max. */
if (i == 255)
{ if (ogg_stream_packetpeek (&odata->ostream, NULL) == 1)
{ psf->error = SFE_INTERNAL ;
return -1 ;
}
break ;
}
if (ogg_stream_packetout (&odata->ostream, ++ ppkt) != 1)
break ;
}
odata->pkt_len = i ;
/* 1 = ok, 2 = ok, and found a hole. */
return 1 + found_hole ;
} /* ogg_stream_unpack_page */
sf_count_t
ogg_sync_last_page_before (SF_PRIVATE *psf, OGG_PRIVATE *odata, uint64_t *gp_out, sf_count_t offset, int32_t serialno)
{ sf_count_t begin, end, original_end, chunk_size, ret ;
sf_count_t position = 0 ;
uint64_t gp = -1 ;
int left_link ;
/* Based on code from Xiph.org's Opusfile */
original_end = end = begin = offset ;
offset = -1 ;
chunk_size = OGG_CHUNK_SIZE ;
do
{ begin = SF_MAX (begin - chunk_size, (sf_count_t) 0) ;
position = ogg_sync_fseek (psf, begin, SEEK_SET) ;
if (position < 0)
return position ;
left_link = 0 ;
while (position < end)
{ ret = ogg_sync_next_page (psf, &odata->opage, end - position, &position) ;
if (ret < 0)
return -1 ;
if (ret == 0)
break ;
if (ogg_page_serialno (&odata->opage) == serialno)
{ uint64_t page_gp = ogg_page_granulepos (&odata->opage) ;
if (page_gp != (uint64_t) -1)
{ offset = position ;
gp = page_gp ;
}
}
else
left_link = 1 ;
position += ret ;
}
if ((left_link || !begin) && offset < 0)
{ psf->error = SFE_MALFORMED_FILE ;
return -1 ;
}
chunk_size = SF_MIN (2 * chunk_size, (sf_count_t) OGG_CHUNK_SIZE_MAX) ;
end = SF_MIN (begin + OGG_PAGE_SIZE_MAX - 1, original_end) ;
}
while (offset < 0) ;
*gp_out = gp ;
return offset ;
} /* ogg_sync_last_page_before */
int
ogg_stream_seek_page_search (SF_PRIVATE *psf, OGG_PRIVATE *odata, uint64_t target_gp, uint64_t pcm_start, uint64_t pcm_end, uint64_t *best_gp, sf_count_t begin, sf_count_t end)
{ ogg_page page ;
uint64_t gp ;
sf_count_t d0, d1, d2 ;
sf_count_t best ;
sf_count_t best_start ;
sf_count_t boundary ;
sf_count_t next_boundary ;
sf_count_t page_offset = -1 ;
sf_count_t seek_pos = -1 ;
sf_count_t bisect ;
sf_count_t chunk_size ;
int buffering = SF_FALSE ;
int force_bisect = SF_FALSE ;
int ret ;
int has_packets ;
*best_gp = pcm_start ;
best = best_start = begin ;
boundary = end ;
ogg_stream_reset_serialno (&odata->ostream, odata->ostream.serialno) ;
/*
** This code is based on op_pcm_seek_page() from Opusfile, which is in turn
** based on "new search algorithm by Nicholas Vinen" from libvorbisfile.
*/
d2 = d1 = d0 = end - begin ;
while (begin < end)
{ /*
** Figure out if and where to try and seek in the file.
*/
if (end - begin < OGG_CHUNK_SIZE)
bisect = begin ;
else
{ /* Update the interval size history */
d0 = d1 >> 1 ;
d1 = d2 >> 1 ;
d2 = (end - begin) >> 1 ;
if (force_bisect == SF_TRUE)
bisect = begin + ((end - begin) >> 1) ;
else
{ /* Take a decent guess. */
bisect = begin + ogg_page_search_do_rescale (target_gp - pcm_start, pcm_end - pcm_start, end - begin) ;
}
if (bisect - OGG_CHUNK_SIZE < begin)
bisect = begin ;
else
bisect -= OGG_CHUNK_SIZE ;
force_bisect = SF_FALSE ;
}
/*
** Avoid an actual fseek if we can (common for final iterations.)
*/
if (seek_pos != bisect)
{ if (buffering == SF_TRUE)
ogg_stream_reset (&odata->ostream) ;
buffering = SF_FALSE ;
page_offset = -1 ;
seek_pos = ogg_sync_fseek (psf, bisect, SEEK_SET) ;
if (seek_pos < 0)
return seek_pos ;
}
chunk_size = OGG_CHUNK_SIZE ;
next_boundary = boundary ;
/*
** Scan forward, figure out where we landed.
** The ideal case is we see a page that ends before our target followed
** by a page that ends after our target.
** If we are too far before or after, breaking out will bisect what we
** have found so far.
*/
while (begin < end)
{ ret = ogg_sync_next_page (psf, &page, boundary - seek_pos, &seek_pos) ;
if (ret < 0)
return ret ;
page_offset = seek_pos ;
if (ret == 0)
{ /*
** There are no more pages in this interval from our stream
** with a granulepos less than our target.
*/
if (bisect <= begin + 1)
{ /* Scanned the whole interval, so we are done. */
end = begin ;
}
else
{ /*
** Otherwise, back up one chunk. First discard any data
** from a continued packet.
*/
if (buffering)
ogg_stream_reset (&odata->ostream) ;
buffering = SF_FALSE ;
bisect = SF_MAX (bisect - chunk_size, begin) ;
seek_pos = ogg_sync_fseek (psf, bisect, SEEK_SET) ;
if (seek_pos < 0)
return seek_pos ;
/* Bump up the chunk size. */
chunk_size = SF_MIN (2 * chunk_size, (sf_count_t) OGG_CHUNK_SIZE_MAX) ;
/*
** If we did find a page from another stream or without a
** timestamp, don't read past it.
*/
boundary = next_boundary ;
}
continue ;
}
/* Found a page. Advance seek_pos past it */
seek_pos += page.header_len + page.body_len ;
/*
** Save the offset of the first page we found after the seek,
** regardless of the stream it came from or whether or not it has a
** timestamp.
*/
next_boundary = SF_MIN (page_offset, next_boundary) ;
/* If not from our stream, continue. */
if (odata->ostream.serialno != (uint32_t) ogg_page_serialno (&page))
continue ;
/*
** The Ogg spec says that a page with a granule pos of -1 must not
** contain and packets which complete, but the lack of biconditional
** wording means that /technically/ a packet which does not complete
** any packets can have a granule pos other than -1. To make matters
** worse, older versions of libogg did just that.
*/
has_packets = ogg_page_packets (&page) > 0 ;
gp = has_packets ? ogg_page_granulepos (&page) : -1 ;
if (gp == (uint64_t) -1)
{ if (buffering == SF_TRUE)
{ if (!has_packets)
ogg_stream_pagein (&odata->ostream, &page) ;
else
{ /*
** If packets did end on this page, but we still didn't
** have a valid granule position (in violation of the
** spec!), stop buffering continued packet data.
** Otherwise we might continue past the packet we
** actually wanted.
*/
ogg_stream_reset (&odata->ostream) ;
buffering = SF_FALSE ;
}
}
continue ;
}
if (gp < target_gp)
{ /*
** We found a page that ends before our target. Advance to
** the raw offset of the next page.
*/
begin = seek_pos ;
if (pcm_start > gp || pcm_end < gp)
break ;
/* Save the byte offset of after this page. */
best = best_start = begin ;
if (buffering)
ogg_stream_reset (&odata->ostream) ;
/* Check to see if the last packet continues. */
if (page.header [27 + page.header [26] - 1] == 255)
{ ogg_page_search_continued_data (odata, &page) ;
/*
** If we have a continued packet, remember the offset of
** this page's start, so that if we do wind up having to
** seek back here later, we can prime the stream with the
** continued packet data. With no continued packet, we
** remember the end of the page.
*/
best_start = page_offset ;
} ;
/*
** Then force buffering on, so that if a packet starts (but
** does not end) on the next page, we still avoid the extra
** seek back.
*/
buffering = SF_TRUE ;
*best_gp = pcm_start = gp ;
if (target_gp - gp > 48000)
{ /* Out by over a second. Try another bisection. */
break ;
}
/* Otherwise, keep scanning forward (do NOT use begin+1). */
bisect = begin ;
}
else
{ /*
** Found a page that ends after our target. If we had just
** scanned the whole interval before we found it, we're good.
*/
if (bisect <= begin + 1)
end = begin ;
else
{ end = bisect ;
/*
** In later iterations, don't read past the first page we
** found.
*/
boundary = next_boundary ;
/*
** If we're not making much progress shrinking the interval
** size, start forcing straight bisection to limit the
** worst case.
*/
force_bisect = end - begin > d0 * 2 ? SF_TRUE : SF_FALSE ;
/*
** Don't let pcm_end get out of range! That could happen
** with an invalid timestamp.
*/
if (pcm_end > gp && pcm_start <= gp)
pcm_end = gp ;
}
break ;
}
}
}
/*
** If we are buffering, the page we want is currently buffered in the
** Ogg stream structure, or in the Ogg page which has not been submitted.
** If not, we need to seek back and load it again.
*/
if (buffering == SF_FALSE)
{ if (best_start != page_offset)
{ page_offset = -1 ;
seek_pos = ogg_sync_fseek (psf, best_start, SEEK_SET) ;
if (seek_pos < 0)
return seek_pos ;
}
if (best_start < best)
{ if (page_offset < 0)
{ ret = ogg_sync_next_page (psf, &page, -1, &seek_pos) ;
if (seek_pos != best_start)
return -1 ;
}
ogg_page_search_continued_data (odata, &page) ;
page_offset = -1 ;
}
} ;
if (page_offset >= 0)
ogg_stream_pagein (&odata->ostream, &page) ;
return 0 ;
} /* ogg_stream_seek_page_search */
int
ogg_open (SF_PRIVATE *psf)
@ -160,6 +735,9 @@ ogg_open (SF_PRIVATE *psf)
psf->container_close = NULL ;
return flac_open (psf) ;
case SF_FORMAT_OGG | SF_FORMAT_OPUS :
return ogg_opus_open (psf) ;
#if ENABLE_EXPERIMENTAL_CODE
case SF_FORMAT_OGG | SF_FORMAT_SPEEX :
return ogg_speex_open (psf) ;
@ -177,6 +755,9 @@ ogg_open (SF_PRIVATE *psf)
return SFE_INTERNAL ;
} /* ogg_open */
/*==============================================================================
** Private functions.
*/
static int
ogg_close (SF_PRIVATE *psf)
@ -194,6 +775,7 @@ ogg_stream_classify (SF_PRIVATE *psf, OGG_PRIVATE* odata)
/* Call this here so it only gets called once, so no memory is leaked. */
ogg_sync_init (&odata->osync) ;
ogg_stream_init (&odata->ostream, 0) ;
/* Load the first page in the physical bitstream. */
if ((error = ogg_read_first_page (psf, odata)) != 0)
@ -215,6 +797,10 @@ ogg_stream_classify (SF_PRIVATE *psf, OGG_PRIVATE* odata)
psf->sf.format = SF_FORMAT_OGG | SF_FORMAT_SPEEX ;
return 0 ;
case OGG_OPUS :
psf->sf.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
return 0 ;
case OGG_PCM :
psf_log_printf (psf, "Detected Ogg/PCM data. This is not supported yet.\n") ;
return SFE_UNIMPLEMENTED ;
@ -241,6 +827,7 @@ static struct
{ "PCM ", "PCM", 8, OGG_PCM },
{ "Speex", "Speex", 5, OGG_SPEEX },
{ "\001vorbis", "Vorbis", 7, OGG_VORBIS },
{ "OpusHead", "Opus", 8, OGG_OPUS },
} ;
static int
@ -271,6 +858,47 @@ ogg_page_classify (SF_PRIVATE * psf, const ogg_page * og)
return 0 ;
} /* ogg_page_classify */
/*
** Scale x from the range [0, from] to the range [0, to]
*/
static uint64_t
ogg_page_search_do_rescale (uint64_t x, uint64_t from, uint64_t to)
{ uint64_t frac ;
uint64_t ret ;
int i ;
/* I should have paid more attention in CSc 349A: Numerical Analysis */
if (x >= from)
return to ;
if (x == 0)
return 0 ;
frac = 0 ;
for (i = 0 ; i < 63 ; i++)
{ frac <<= 1 ;
if (x >= from >> 1)
{ x -= from - x ;
frac |= 1 ;
}
else
x <<= 1 ;
}
ret = 0 ;
for (i = 0 ; i < 63 ; i++)
{ if (frac & 1)
ret = (ret & to & 1) + (ret >> 1) + (to >> 1) ;
else
ret >>= 1 ;
frac >>= 1 ;
}
return ret ;
} /* ogg_page_search_do_rescale */
static void
ogg_page_search_continued_data (OGG_PRIVATE *odata, ogg_page *page)
{ ogg_stream_pagein (&odata->ostream, page) ;
while (ogg_stream_packetout (&odata->ostream, &odata->opacket)) ;
} /* ogg_page_search_continued_data */
#else /* HAVE_EXTERNAL_XIPH_LIBS */
int

View File

@ -1,5 +1,6 @@
/*
** Copyright (C) 2008-2011 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
@ -17,6 +18,7 @@
*/
#ifndef SF_SRC_OGG_H
#define SF_SRC_OGG_H
enum
{ OGG_ANNODEX = 300,
@ -26,6 +28,7 @@ enum
OGG_PCM,
OGG_SPEEX,
OGG_VORBIS,
OGG_OPUS,
} ;
typedef struct
@ -37,6 +40,14 @@ typedef struct
ogg_page opage ;
/* One raw packet of data for decode */
ogg_packet opacket ;
/* Unpacked packets. 255 is max there can ever be in one page. */
ogg_packet pkt [255] ;
/* How many packets */
int pkt_len ;
/* Current packet */
int pkt_indx ;
int eos ;
int codec ;
} OGG_PRIVATE ;
@ -49,4 +60,75 @@ typedef struct
int ogg_read_first_page (SF_PRIVATE *, OGG_PRIVATE *) ;
/*
** Write the whole Ogg page out. Convenience function as the ogg_page struct
** splits header and body data into separate buffers.
*/
int ogg_write_page (SF_PRIVATE *, ogg_page *) ;
/*
** Wrapper around psf_ftell() that returns the current offset in the file after
** the most recent page that has been returned by ogg_sync_pageout().
*/
sf_count_t ogg_sync_ftell (SF_PRIVATE *) ;
/*
** Wrapper around psf_fseek() that on success resets the ogg_sync_state struct
** so that it doesn't get corrupted.
*/
sf_count_t ogg_sync_fseek (SF_PRIVATE *, sf_count_t offset, int whence) ;
/*
** Get the next page from the physical bitstream, reading in data as necessary.
** Pays no attention to Ogg BOS/EOS markers or stream serial numbers.
** The page is buffered in the ogg_sync_state struct, (replacing any other
** buffered there) and also returned in *og. readmax sets a boundary for how
** many bytes more may be read from the file, use already buffered only, or
** unlimited reading in the case of a positive, zero or negative argument
** respectively. If a pointer to a sf_count_t is passed in offset, then it will
** be incremented by how many bytes were skipped to find the next page header.
** (Useful for seeking, normally zero.) Returns the page size in bytes on
** success, 0 on out-of-data (be it end of file or readmax reached) and -1 on
** error with psf->error set appropriately.
*/
int ogg_sync_next_page (SF_PRIVATE * psf, ogg_page *og, sf_count_t readmax, sf_count_t *offset) ;
/*
** Load the last page of a stream before the provided file offset. Searches the
** physical bitstream, and selects a page of the passed serialno. The page
** found is loaded in the sync buffer and exposed in odata->opage, and not
** loaded into the ogg_stream_state. If found, the granulepos is returned in
** *gp_out. Returns the file offset *before* the last page on success, or -1 on
** error, setting psf->error as appropriate.
*/
sf_count_t ogg_sync_last_page_before (SF_PRIVATE *psf, OGG_PRIVATE *odata, uint64_t *gp_out, sf_count_t offset, int32_t serialno) ;
/*
** Load the next page from the virtual bitstream, reading data as necessary.
** Reads in pages from the physical bitstream, skipping pages until one of the
** virtual bitstream of interest is found, and then feeds it into the
** ogg_stream_state of odata->ostream, where it is buffered. Heeds EOS markers.
** Returns 1 on success, 0 on end of stream, and -1 on fatal error.
*/
int ogg_stream_next_page (SF_PRIVATE * psf, OGG_PRIVATE *odata) ;
/*
** Loads the next page using ogg_stream_next_page() and unpacks all packets
** into the array odata->pkt, updating odata->pkt_len and setting
** odata->pkt_indx to 0. Returns 1 if okay, 2 if okay but a hole was found
** in the bitstream, 0 if on end of stream, and -1 on fatal error.
*/
int ogg_stream_unpack_page (SF_PRIVATE *psf, OGG_PRIVATE *odata) ;
/*
** Seek within the Ogg virtual bitstream for a page containing target_gp.
** Preforms a bisection search. If not found exactly, the best result is
** returned in *best_gp. Found page is loaded into the virtual bitstream,
** ready for unpacking. Arguments pcm_start and pcm_end are the highest and
** lowest granule positions of the file. begin and end are the file offsets.
*/
int ogg_stream_seek_page_search (SF_PRIVATE *psf, OGG_PRIVATE *odata,
uint64_t target_gp, uint64_t pcm_start, uint64_t pcm_end,
uint64_t *best_gp, sf_count_t begin, sf_count_t end) ;
#endif /* SF_SRC_OGG_H */

File diff suppressed because it is too large Load Diff

269
src/ogg_vcomment.c Normal file
View File

@ -0,0 +1,269 @@
/*
** Copyright (C) 2008-2018 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.
*/
#include "sfconfig.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "sndfile.h"
#include "sfendian.h"
#include "common.h"
#if HAVE_EXTERNAL_XIPH_LIBS
#include <ogg/ogg.h>
#include "ogg_vcomment.h"
typedef struct
{ int id ;
const char *name ;
} STR_PAIR ;
/* See https://xiph.org/vorbis/doc/v-comment.html */
static STR_PAIR vorbiscomment_mapping [] =
{ { SF_STR_TITLE, "TITLE" },
{ SF_STR_COPYRIGHT, "COPYRIGHT", },
{ SF_STR_SOFTWARE, "ENCODER", },
{ SF_STR_ARTIST, "ARTIST" },
{ SF_STR_COMMENT, "COMMENT" },
{ SF_STR_DATE, "DATE", },
{ SF_STR_ALBUM, "ALBUM" },
{ SF_STR_LICENSE, "LICENSE", },
{ SF_STR_TRACKNUMBER, "TRACKNUMBER", },
{ SF_STR_GENRE, "GENRE", },
{ 0, NULL, },
} ;
/*-----------------------------------------------------------------------------------------------
** Private function prototypes.
*/
static int vorbiscomment_lookup_id (const char *name) ;
static const char * vorbiscomment_lookup_name (int id) ;
/*-----------------------------------------------------------------------------------------------
** Exported functions.
*/
int
vorbiscomment_read_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident)
{ unsigned char *p, *ep ;
char *tag, *c ;
size_t tag_size, tag_len = 0 ;
unsigned int ntags, i = 0 ;
int id, ret = 0 ;
/*
** The smallest possible header is the ident string length plus two 4-byte
** integers, (vender string length, tags count.)
*/
if (packet->bytes < (ident ? ident->length : 0) + 4 + 4)
return SFE_MALFORMED_FILE ;
/* Our working pointer. */
p = packet->packet ;
/* Our end pointer for bound checking. */
ep = p + packet->bytes ;
if (ident)
{ if (memcmp (p, ident->ident, ident->length) != 0)
{ psf_log_printf (psf, "Expected comment packet identifier missing.\n") ;
return SFE_MALFORMED_FILE ;
} ;
p += ident->length ;
} ;
tag_size = 1024 ;
tag = malloc (tag_size) ;
/* Unlikely */
if (!tag)
return SFE_MALLOC_FAILED ;
psf_log_printf (psf, "VorbisComment Metadata\n") ;
/*
** Vendor tag, manditory, no field name.
*/
tag_len = LE2H_32_PTR (p) ; p += 4 ;
if (tag_len > 0)
{ /* Bound checking. 4 bytes for remaining manditory fields. */
if (p + tag_len + 4 > ep)
{ ret = SFE_MALFORMED_FILE ;
goto free_tag_out ;
} ;
if (tag_len > tag_size - 1)
{ free (tag) ;
tag_size = tag_len + 1 ;
tag = malloc (tag_size) ;
/* Unlikely */
if (!tag)
return SFE_MALLOC_FAILED ;
} ;
memcpy (tag, p, tag_len) ; p += tag_len ;
tag [tag_len] = '\0' ;
psf_log_printf (psf, " Vendor: %s\n", tag) ;
} ;
/*
** List of tags of the form NAME=value
** Allowable characters for NAME are the same as shell variable names.
*/
ntags = LE2H_32_PTR (p) ; p += 4 ;
for (i = 0 ; i < ntags ; i++)
{ if (p + 4 > ep)
{ ret = SFE_MALFORMED_FILE ;
goto free_tag_out ;
} ;
tag_len = LE2H_32_PTR (p) ; p += 4 ;
if (p + tag_len > ep)
{ ret = SFE_MALFORMED_FILE ;
goto free_tag_out ;
} ;
if (tag_len > tag_size - 1)
{ free (tag) ;
tag_size = tag_len + 1 ;
tag = malloc (tag_size) ;
/* Unlikely */
if (!tag)
return SFE_MALLOC_FAILED ;
} ;
memcpy (tag, p, tag_len) ; p += tag_len ;
tag [tag_len] = '\0' ;
psf_log_printf (psf, " %s\n", tag) ;
for (c = tag ; *c ; c++)
{ if (*c == '=')
break ;
*c = toupper (*c) ;
} ;
if (!c)
{ psf_log_printf (psf, "Malformed Vorbis comment, no '=' found.\n") ;
continue ;
} ;
*c = '\0' ;
if ((id = vorbiscomment_lookup_id (tag)) != 0)
psf_store_string (psf, id, c + 1) ;
} ;
free_tag_out:
if (tag != NULL)
free (tag) ;
return ret ;
} /* vorbiscomment_read_tags */
int
vorbiscomment_write_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident, const char *vendor, int targetsize)
{ int i, ntags ;
int tags_start ;
const char *tag_name ;
int tag_name_len, tag_body_len ;
psf->header.ptr [0] = 0 ;
psf->header.indx = 0 ;
/* Packet identifier */
if (ident)
psf_binheader_writef (psf, "eb", BHWv (ident->ident), BHWz (ident->length)) ;
/* Manditory Vendor Tag */
tag_name_len = vendor ? strlen (vendor) : 0 ;
psf_binheader_writef (psf, "e4b", BHW4 (tag_name_len), BHWv (vendor), BHWz (tag_name_len)) ;
/* Tags Count. Skip for now, write after. */
tags_start = psf->header.indx ;
psf_binheader_writef (psf, "j", BHWj (4)) ;
ntags = 0 ;
/* Write each tag */
for (i = 0 ; i < SF_MAX_STRINGS ; i++)
{ if (psf->strings.data [i].type == 0)
continue ;
tag_name = vorbiscomment_lookup_name (psf->strings.data [i].type) ;
if (tag_name == NULL)
continue ;
tag_name_len = strlen (tag_name) ;
tag_body_len = strlen (psf->strings.storage + psf->strings.data [i].offset) ;
if (targetsize > 0 && tag_name_len + tag_body_len + psf->header.indx > targetsize)
{ /* If we are out of space, stop now. */
return SFE_STR_MAX_DATA ;
}
psf_binheader_writef (psf, "e4b1b",
BHW4 (tag_name_len + 1 + tag_body_len),
BHWv (tag_name), BHWz (tag_name_len),
BHW1 ('='),
BHWv (psf->strings.storage + psf->strings.data [i].offset), BHWz (tag_body_len)) ;
ntags++ ;
} ;
if (targetsize < 0)
{ /*
** Padding.
**
** Pad to a minimum of -targetsize, but make sure length % 255
** = 254 so that we get the most out of the ogg segment lacing.
*/
psf_binheader_writef (psf, "z", BHWz ((psf->header.indx + -targetsize + 255) / 255 * 255 - 1)) ;
}
else if (targetsize > 0)
psf_binheader_writef (psf, "z", BHWz (targetsize - psf->header.indx)) ;
packet->packet = psf->header.ptr ;
packet->bytes = psf->header.indx ;
packet->b_o_s = 0 ;
packet->e_o_s = 0 ;
/* Seek back and write the tag count. */
psf_binheader_writef (psf, "eo4", BHWo (tags_start), BHW4 (ntags)) ;
return 0 ;
} /* vorbiscomment_write_tags */
/*==============================================================================
** Private functions.
*/
static int
vorbiscomment_lookup_id (const char * name)
{ STR_PAIR *p ;
for (p = vorbiscomment_mapping ; p->id ; p++)
{ if (!strcmp (name, p->name))
return p->id ;
} ;
return 0 ;
} /* vorbiscomment_lookup_id */
static const char *
vorbiscomment_lookup_name (int id)
{ STR_PAIR *p ;
for (p = vorbiscomment_mapping ; p->id ; p++)
{ if (p->id == id)
return p->name ;
} ;
return NULL ;
} /* vorbiscomment_lookup_name */
#endif /* HAVE_EXTERNAL_XIPH_LIBS */

45
src/ogg_vcomment.h Normal file
View File

@ -0,0 +1,45 @@
/*
** Copyright (C) 2008-2018 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.
*/
#ifndef SF_SRC_OGG_VCOMMENT_H
#define SF_SRC_OGG_VCOMMENT_H
/*
** Voriscomment identifier. Some Ogg stream embedding schemes require it.
*/
typedef struct
{ const char *ident ;
int length ;
} vorbiscomment_ident ;
/*
** Read all vorbiscomment tags from *packet. Tags which match ones used
** by libsndfile strings are loaded into *psf. Ogg streams which require an
** identifier for the tags packet should pass it in *ident.
*/
int vorbiscomment_read_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident) ;
/*
** Write metadata strings stored in *psf to *packet. The packet is optionally
** prefixed with *ident. The always-present vendor field should be the library
** used for encoding the audio data.
*/
int vorbiscomment_write_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident, const char *vendor, int targetsize) ;
#endif /* SF_SRC_OGG_VCOMMENT_H */

View File

@ -156,6 +156,8 @@ ENDSWAP_64 (uint64_t x)
#error "Target CPU endian-ness unknown. May need to hand edit src/sfconfig.h"
#endif
#define LE2H_32_PTR(x) (((x) [0]) + ((x) [1] << 8) + ((x) [2] << 16) + ((x) [3] << 24))
#define LET2H_16_PTR(x) ((x) [1] + ((x) [2] << 8))
#define LET2H_32_PTR(x) (((x) [0] << 8) + ((x) [1] << 16) + ((x) [2] << 24))

View File

@ -271,6 +271,8 @@ ErrorStruct SndfileErrors [] =
{ SFE_FILENAME_TOO_LONG , "Error : Supplied filename too long." },
{ SFE_NEGATIVE_RW_LEN , "Error : Length parameter passed to read/write is negative." },
{ SFE_OPUS_BAD_SAMPLERATE , "Error : Opus only supports sample rates of 8000, 12000, 16000, 24000 and 48000." },
{ SFE_MAX_ERROR , "Maximum error number." },
{ SFE_MAX_ERROR + 1 , NULL }
} ;
@ -842,6 +844,8 @@ sf_format_check (const SF_INFO *info)
return 0 ;
if (subformat == SF_FORMAT_VORBIS)
return 1 ;
if (subformat == SF_FORMAT_OPUS)
return 1 ;
break ;
case SF_FORMAT_MPC2K :

View File

@ -109,6 +109,7 @@ enum
SF_FORMAT_DPCM_16 = 0x0051, /* 16 bit differential PCM (XI only) */
SF_FORMAT_VORBIS = 0x0060, /* Xiph Vorbis encoding. */
SF_FORMAT_OPUS = 0x0064, /* Xiph/Skype Opus encoding. */
SF_FORMAT_ALAC_16 = 0x0070, /* Apple Lossless Audio Codec (16 bit). */
SF_FORMAT_ALAC_20 = 0x0071, /* Apple Lossless Audio Codec (20 bit). */
@ -218,6 +219,10 @@ enum
SFC_SET_CART_INFO = 0x1400,
SFC_GET_CART_INFO = 0x1401,
/* Opus files original samplerate metadata */
SFC_SET_ORIGINAL_SAMPLERATE = 0x1500,
SFC_GET_ORIGINAL_SAMPLERATE = 0x1501,
/* Following commands for testing only. */
SFC_TEST_IEEE_FLOAT_REPLACE = 0x6001,

View File

@ -177,6 +177,7 @@ main (int argc, char *argv [])
" Where <test> is one of:\n"
" vorbis - test Ogg/Vorbis\n"
" flac - test FLAC\n"
" opus - test Opus\n"
" all - perform all tests\n",
argv [0]) ;
exit (0) ;
@ -201,5 +202,10 @@ main (int argc, char *argv [])
tests ++ ;
} ;
if (all_tests || strcmp (argv [1], "opus") == 0)
{ compression_size_test (SF_FORMAT_OGG | SF_FORMAT_OPUS, "opus.opus") ;
tests ++ ;
} ;
return 0 ;
} /* main */

View File

@ -84,7 +84,7 @@ major_format_test (void)
static void
subtype_format_test (void)
{ SF_FORMAT_INFO info ;
int have_vorbis = 0 ;
int have_vorbis = 0 , have_opus = 0 ;
int s, subtype_count ;
print_test_name (__func__, NULL) ;
@ -96,12 +96,17 @@ subtype_format_test (void)
sf_command (NULL, SFC_GET_FORMAT_SUBTYPE, &info, sizeof (info)) ;
have_vorbis = info.format == SF_FORMAT_VORBIS ? 1 : have_vorbis ;
have_opus = info.format == SF_FORMAT_OPUS ? 1 : have_opus ;
} ;
if (HAVE_EXTERNAL_XIPH_LIBS)
exit_if_true (have_vorbis == 0, "\n\nLine %d : Ogg/Vorbis should be available.\n\n", __LINE__) ;
{ exit_if_true (have_vorbis == 0, "\n\nLine %d : Ogg/Vorbis should be available.\n\n", __LINE__) ;
exit_if_true (have_opus == 0, "\n\nLine %d : Ogg/Opus should be available.\n\n", __LINE__) ;
}
else
exit_if_true (have_vorbis, "\n\nLine %d : Ogg/Vorbis should not be available.\n\n", __LINE__) ;
{ exit_if_true (have_vorbis, "\n\nLine %d : Ogg/Vorbis should not be available.\n\n", __LINE__) ;
exit_if_true (have_opus, "\n\nLine %d : Ogg/Opus should not be available.\n\n", __LINE__) ;
} ;
puts ("ok") ;
} /* subtype_format_test */
@ -109,7 +114,7 @@ subtype_format_test (void)
static void
simple_format_test (void)
{ SF_FORMAT_INFO info ;
int have_flac = 0, have_ogg = 0, have_vorbis = 0 ;
int have_flac = 0, have_ogg = 0, have_vorbis = 0, have_opus = 0 ;
int s, simple_count ;
print_test_name (__func__, NULL) ;
@ -138,6 +143,10 @@ simple_format_test (void)
have_vorbis = 1 ;
break ;
case SF_FORMAT_OPUS :
have_opus = 1 ;
break ;
default :
break ;
} ;
@ -148,11 +157,13 @@ simple_format_test (void)
{ exit_if_true (have_flac == 0, "\n\nLine %d : FLAC should be available.\n\n", __LINE__) ;
exit_if_true (have_ogg == 0, "\n\nLine %d : Ogg/Vorbis should be available.\n\n", __LINE__) ;
exit_if_true (have_vorbis == 0, "\n\nLine %d : Ogg/Vorbis should be available.\n\n", __LINE__) ;
exit_if_true (have_opus == 0, "\n\nLine %d : Ogg/Opus should be available.\n\n", __LINE__) ;
}
else
{ exit_if_true (have_flac, "\n\nLine %d : FLAC should not be available.\n\n", __LINE__) ;
exit_if_true (have_ogg, "\n\nLine %d : Ogg/Vorbis should not be available.\n\n", __LINE__) ;
exit_if_true (have_vorbis, "\n\nLine %d : Ogg/Vorbis should not be available.\n\n", __LINE__) ;
exit_if_true (have_opus, "\n\nLine %d : Ogg/Opus should not be available.\n\n", __LINE__) ;
} ;
puts ("ok") ;

View File

@ -121,6 +121,8 @@ main (int argc, char *argv [])
float_scaled_test ("flac_24.flac", allow_exit, SF_FALSE, SF_FORMAT_FLAC | SF_FORMAT_PCM_24, -138.0) ;
float_scaled_test ("vorbis.oga", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_VORBIS, -31.0) ;
float_scaled_test ("opus.opus", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_OPUS, -32.0) ;
#endif
float_scaled_test ("replace_float.raw", allow_exit, SF_TRUE, SF_ENDIAN_LITTLE | SF_FORMAT_RAW | SF_FORMAT_FLOAT, -163.0) ;
@ -178,6 +180,7 @@ main (int argc, char *argv [])
double_scaled_test ("flac_24.flac", allow_exit, SF_FALSE, SF_FORMAT_FLAC | SF_FORMAT_PCM_24, -138.0) ;
double_scaled_test ("vorbis.oga", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_VORBIS, -29.0) ;
double_scaled_test ("opus.opus", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_OPUS, -32.0) ;
#endif
double_scaled_test ("replace_double.raw", allow_exit, SF_TRUE, SF_FORMAT_RAW | SF_FORMAT_DOUBLE, -201.0) ;

View File

@ -107,7 +107,11 @@ format_combo_test (void)
subtype_fmt_info.format = codec ;
subtype_is_valid = sf_command (NULL, SFC_GET_FORMAT_SUBTYPE, &subtype_fmt_info, sizeof (subtype_fmt_info)) == 0 ;
sf_info_setup (&info, major_fmt_info.format | subtype_fmt_info.format, 22050, 1) ;
/* Opus only works with a fixed set of sample rates. */
if (subtype_fmt_info.format == SF_FORMAT_OPUS)
sf_info_setup (&info, major_fmt_info.format | subtype_fmt_info.format, 24000, 1) ;
else
sf_info_setup (&info, major_fmt_info.format | subtype_fmt_info.format, 22050, 1) ;
check_is_valid = sf_format_check (&info) ;

View File

@ -70,6 +70,8 @@ static void check_comment (SNDFILE * file, int format, int lineno) ;
static int is_lossy (int filetype) ;
static int check_opus_version (SNDFILE *file) ;
/*
** Force the start of these buffers to be double aligned. Sparc-solaris will
** choke if they are not.
@ -463,6 +465,20 @@ main (int argc, char *argv [])
test_count++ ;
} ;
if (do_all || strcmp (argv [1], "ogg_opus") == 0)
{ if (HAVE_EXTERNAL_XIPH_LIBS)
{ /* Don't do lcomp_test_XXX as the errors are too big. */
sdlcomp_test_short ("opus.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS, 1, 0.57) ;
sdlcomp_test_int ("opus.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS, 1, 0.54) ;
sdlcomp_test_float ("opus.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS, 1, 0.55) ;
sdlcomp_test_double ("opus.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS, 1, 0.55) ;
}
else
puts (" No Ogg/Opus tests because Ogg/Opus support was not compiled in.") ;
test_count++ ;
} ;
/* Lite remove start */
if (do_all || strcmp (argv [1], "ircam_ulaw") == 0)
{ lcomp_test_short ("ulaw.ircam", SF_ENDIAN_LITTLE | SF_FORMAT_IRCAM | SF_FORMAT_ULAW, 2, 0.04) ;
@ -1428,23 +1444,35 @@ channels = 1 ;
/* The Vorbis encoder has a bug on PowerPC and X86-64 with sample rates
** <= 22050. Increasing the sample rate to 32000 avoids triggering it.
** See https://trac.xiph.org/ticket/1229
**
** Opus only supports discrete sample rates. Choose supported 12000.
*/
if ((file = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
{ const char * errstr ;
errstr = sf_strerror (NULL) ;
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") == NULL)
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") != NULL)
{ printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
}
else if (strstr (errstr, "Opus only supports sample rates of") != NULL)
{ printf ("\n Sample rate -> 12kHz ") ;
sfinfo.samplerate = 12000 ;
}
else
{ printf ("Line %d: sf_open_fd (SFM_WRITE) failed : %s\n", __LINE__, errstr) ;
dump_log_buffer (NULL) ;
exit (1) ;
} ;
printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_TRUE, __LINE__) ;
} ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_OPUS && !check_opus_version (file))
{ sf_close (file) ;
return ;
} ;
test_write_short_or_die (file, 0, orig, datalen, __LINE__) ;
sf_set_string (file, SF_STR_COMMENT, long_comment) ;
sf_close (file) ;
@ -1636,23 +1664,35 @@ channels = 1 ;
/* The Vorbis encoder has a bug on PowerPC and X86-64 with sample rates
** <= 22050. Increasing the sample rate to 32000 avoids triggering it.
** See https://trac.xiph.org/ticket/1229
**
** Opus only supports discrete sample rates. Choose supported 12000.
*/
if ((file = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
{ const char * errstr ;
errstr = sf_strerror (NULL) ;
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") == NULL)
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") != NULL)
{ printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
}
else if (strstr (errstr, "Opus only supports sample rates of") != NULL)
{ printf ("\n Sample rate -> 12kHz ") ;
sfinfo.samplerate = 12000 ;
}
else
{ printf ("Line %d: sf_open_fd (SFM_WRITE) failed : %s\n", __LINE__, errstr) ;
dump_log_buffer (NULL) ;
exit (1) ;
} ;
printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_TRUE, __LINE__) ;
} ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_OPUS && !check_opus_version (file))
{ sf_close (file) ;
return ;
} ;
test_writef_int_or_die (file, 0, orig, datalen, __LINE__) ;
sf_set_string (file, SF_STR_COMMENT, long_comment) ;
sf_close (file) ;
@ -1827,15 +1867,28 @@ channels = 1 ;
print_test_name ("sdlcomp_test_float", filename) ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_VORBIS)
/* Vorbis starts to loose fidelity with floating point values outside
** the range of approximately [-2000.0, 2000.0] (Determined
** experimentally, not know if it is a limitation of Vorbis or
** libvorbis.)
*/
scale = 16.0 ;
else
scale = 1.0 ;
switch ((filetype & SF_FORMAT_SUBMASK))
{ case SF_FORMAT_VORBIS :
/* Vorbis starts to loose fidelity with floating point values outside
** the range of approximately [-2000.0, 2000.0] (Determined
** experimentally, not know if it is a limitation of Vorbis or
** libvorbis.)
*/
scale = 16.0 ; /* 32000/16 = 2000 */
break ;
case SF_FORMAT_OPUS :
/* The Opus spec says that non-normalized floating point value
** support (extended dynamic range in its terms) is optional and
** cannot be relied upon.
*/
scale = 32000.0 ; /* 32000/32000 = 1 */
break ;
default :
scale = 1.0 ;
break ;
} ;
datalen = BUFFER_SIZE ;
@ -1852,7 +1905,39 @@ channels = 1 ;
sfinfo.channels = channels ;
sfinfo.format = filetype ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
/* The Vorbis encoder has a bug on PowerPC and X86-64 with sample rates
** <= 22050. Increasing the sample rate to 32000 avoids triggering it.
** See https://trac.xiph.org/ticket/1229
**
** Opus only supports discrete sample rates. Choose supported 12000.
*/
if ((file = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
{ const char * errstr ;
errstr = sf_strerror (NULL) ;
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") != NULL)
{ printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
}
else if (strstr (errstr, "Opus only supports sample rates of") != NULL)
{ printf ("\n Sample rate -> 12kHz ") ;
sfinfo.samplerate = 12000 ;
}
else
{ printf ("Line %d: sf_open_fd (SFM_WRITE) failed : %s\n", __LINE__, errstr) ;
dump_log_buffer (NULL) ;
exit (1) ;
} ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_TRUE, __LINE__) ;
} ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_OPUS && !check_opus_version (file))
{ sf_close (file) ;
return ;
} ;
sf_command (file, SFC_SET_NORM_FLOAT, NULL, SF_FALSE) ;
test_write_float_or_die (file, 0, orig, datalen, __LINE__) ;
sf_set_string (file, SF_STR_COMMENT, long_comment) ;
@ -2025,15 +2110,28 @@ sdlcomp_test_double (const char *filename, int filetype, int channels, double ma
channels = 1 ;
print_test_name ("sdlcomp_test_double", filename) ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_VORBIS)
/* Vorbis starts to loose fidelity with floating point values outside
** the range of approximately [-2000.0, 2000.0] (Determined
** experimentally, not know if it is a limitation of Vorbis or
** libvorbis.)
*/
scale = 16.0 ;
else
scale = 1.0 ;
switch ((filetype & SF_FORMAT_SUBMASK))
{ case SF_FORMAT_VORBIS :
/* Vorbis starts to loose fidelity with floating point values outside
** the range of approximately [-2000.0, 2000.0] (Determined
** experimentally, not know if it is a limitation of Vorbis or
** libvorbis.)
*/
scale = 16.0 ; /* 32000/16 = 2000 */
break ;
case SF_FORMAT_OPUS :
/* The Opus spec says that non-normalized floating point value
** support (extended dynamic range in its terms) is optional and
** cannot be relied upon.
*/
scale = 32000.0 ; /* 32000/32000 = 1 */
break ;
default :
scale = 1.0 ;
break ;
} ;
datalen = BUFFER_SIZE ;
@ -2048,7 +2146,38 @@ channels = 1 ;
sfinfo.channels = channels ;
sfinfo.format = filetype ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
/* The Vorbis encoder has a bug on PowerPC and X86-64 with sample rates
** <= 22050. Increasing the sample rate to 32000 avoids triggering it.
** See https://trac.xiph.org/ticket/1229
**
** Opus only supports discrete sample rates. Choose supported 12000.
*/
if ((file = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
{ const char * errstr ;
errstr = sf_strerror (NULL) ;
if (strstr (errstr, "Sample rate chosen is known to trigger a Vorbis") != NULL)
{ printf ("\n Sample rate -> 32kHz ") ;
sfinfo.samplerate = 32000 ;
}
else if (strstr (errstr, "Opus only supports sample rates of") != NULL)
{ printf ("\n Sample rate -> 12kHz ") ;
sfinfo.samplerate = 12000 ;
}
else
{ printf ("Line %d: sf_open_fd (SFM_WRITE) failed : %s\n", __LINE__, errstr) ;
dump_log_buffer (NULL) ;
exit (1) ;
} ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_TRUE, __LINE__) ;
} ;
if ((filetype & SF_FORMAT_SUBMASK) == SF_FORMAT_OPUS && !check_opus_version (file))
{ sf_close (file) ;
return ;
} ;
sf_command (file, SFC_SET_NORM_DOUBLE, NULL, SF_FALSE) ;
test_write_double_or_die (file, 0, orig, datalen, __LINE__) ;
sf_set_string (file, SF_STR_COMMENT, long_comment) ;
@ -2455,3 +2584,34 @@ is_lossy (int filetype)
return 1 ;
} /* is_lossy */
static int
check_opus_version (SNDFILE *file)
{ char log_buf [256] ;
char *str, *p ;
const char *str_libopus = "Opus library version: " ;
int ver_major, ver_minor ;
sf_command (file, SFC_GET_LOG_INFO, log_buf, sizeof (log_buf)) ;
str = strstr (log_buf, str_libopus) ;
if (str)
{ str += strlen (str_libopus) ;
if ((p = strchr (str, '\n')))
*p = '\0' ;
if (sscanf (str, "libopus %d.%d", &ver_major, &ver_minor) == 2)
{ /* Reject versions prior to 1.3 */
if (ver_major > 1 || (ver_major == 1 && ver_minor >= 3))
{ /*
** Make sure that the libopus in use is not fixed-point, as it
** sacrifices accuracy. libopus API documentation explicitly
** allows checking for this suffix to determine if it is.
*/
if (!strstr (str, "-fixed"))
return 1 ;
} ;
} ;
} ;
printf ("skipping (%s)\n", str ? str : "unknown libopus version") ;
return 0 ;
} /* check_opus_version */

424
tests/ogg_opus_test.c Normal file
View File

@ -0,0 +1,424 @@
/*
** Copyright (C) 2007-2018 Erik de Castro Lopo <erikd@mega-nerd.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 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 General Public License for more details.
**
** You should have received a copy of the GNU 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.
*/
#include "sfconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#else
#include "sf_unistd.h"
#endif
#include <math.h>
#include <inttypes.h>
#include <sndfile.h>
#include "utils.h"
#define SAMPLE_RATE 48000
#define DATA_LENGTH (SAMPLE_RATE / 8)
typedef union
{ double d [DATA_LENGTH] ;
float f [DATA_LENGTH] ;
int i [DATA_LENGTH] ;
short s [DATA_LENGTH] ;
} BUFFER ;
static BUFFER data_out ;
static BUFFER data_in ;
static void
ogg_opus_short_test (void)
{ const char * filename = "ogg_opus_short.opus" ;
SNDFILE * file ;
SF_INFO sfinfo ;
short seek_data [10] ;
unsigned k ;
print_test_name ("ogg_opus_short_test", filename) ;
/* Generate float data. */
gen_windowed_sine_float (data_out.f, ARRAY_LEN (data_out.f), 1.0 * 0x7F00) ;
/* Convert to short. */
for (k = 0 ; k < ARRAY_LEN (data_out.s) ; k++)
data_out.s [k] = lrintf (data_out.f [k]) ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
sfinfo.channels = 1 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
test_write_short_or_die (file, 0, data_out.s, ARRAY_LEN (data_out.s), __LINE__) ;
sf_close (file) ;
/* Read the file in again. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_read_short_or_die (file, 0, data_in.s, ARRAY_LEN (data_in.s), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
/* Test seeking. */
print_test_name ("ogg_opus_seek_test", filename) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_seek_or_die (file, 10, SEEK_SET, 10, sfinfo.channels, __LINE__) ;
test_read_short_or_die (file, 0, seek_data, ARRAY_LEN (seek_data), __LINE__) ;
compare_short_or_die (seek_data, data_in.s + 10, ARRAY_LEN (seek_data), __LINE__) ;
/* Test seek to end of file. */
test_seek_or_die (file, 0, SEEK_END, sfinfo.frames, sfinfo.channels, __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_short_test */
static void
ogg_opus_int_test (void)
{ const char * filename = "ogg_opus_int.opus" ;
SNDFILE * file ;
SF_INFO sfinfo ;
int seek_data [10] ;
unsigned k ;
print_test_name ("ogg_opus_int_test", filename) ;
/* Generate float data. */
gen_windowed_sine_float (data_out.f, ARRAY_LEN (data_out.f), 1.0 * 0x7FFF0000) ;
/* Convert to integer. */
for (k = 0 ; k < ARRAY_LEN (data_out.i) ; k++)
data_out.i [k] = lrintf (data_out.f [k]) ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
sfinfo.channels = 1 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
test_write_int_or_die (file, 0, data_out.i, ARRAY_LEN (data_out.i), __LINE__) ;
sf_close (file) ;
/* Read the file in again. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_read_int_or_die (file, 0, data_in.i, ARRAY_LEN (data_in.i), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
/* Test seeking. */
print_test_name ("ogg_opus_seek_test", filename) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_seek_or_die (file, 10, SEEK_SET, 10, sfinfo.channels, __LINE__) ;
test_read_int_or_die (file, 0, seek_data, ARRAY_LEN (seek_data), __LINE__) ;
compare_int_or_die (seek_data, data_in.i + 10, ARRAY_LEN (seek_data), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_int_test */
static void
ogg_opus_float_test (void)
{ const char * filename = "ogg_opus_float.opus" ;
SNDFILE * file ;
SF_INFO sfinfo ;
float seek_data [10] ;
print_test_name ("ogg_opus_float_test", filename) ;
gen_windowed_sine_float (data_out.f, ARRAY_LEN (data_out.f), 0.95) ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
sfinfo.channels = 1 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
test_write_float_or_die (file, 0, data_out.f, ARRAY_LEN (data_out.f), __LINE__) ;
sf_close (file) ;
/* Read the file in again. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_read_float_or_die (file, 0, data_in.f, ARRAY_LEN (data_in.f), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
/* Test seeking. */
print_test_name ("ogg_opus_seek_test", filename) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_seek_or_die (file, 10, SEEK_SET, 10, sfinfo.channels, __LINE__) ;
test_read_float_or_die (file, 0, seek_data, ARRAY_LEN (seek_data), __LINE__) ;
compare_float_or_die (seek_data, data_in.f + 10, ARRAY_LEN (seek_data), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_float_test */
static void
ogg_opus_double_test (void)
{ const char * filename = "ogg_opus_double.opus" ;
SNDFILE * file ;
SF_INFO sfinfo ;
double seek_data [10] ;
print_test_name ("ogg_opus_double_test", filename) ;
gen_windowed_sine_double (data_out.d, ARRAY_LEN (data_out.d), 0.95) ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
sfinfo.channels = 1 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
test_write_double_or_die (file, 0, data_out.d, ARRAY_LEN (data_out.d), __LINE__) ;
sf_close (file) ;
/* Read the file in again. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_read_double_or_die (file, 0, data_in.d, ARRAY_LEN (data_in.d), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
/* Test seeking. */
print_test_name ("ogg_opus_seek_test", filename) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
test_seek_or_die (file, 10, SEEK_SET, 10, sfinfo.channels, __LINE__) ;
test_read_double_or_die (file, 0, seek_data, ARRAY_LEN (seek_data), __LINE__) ;
compare_double_or_die (seek_data, data_in.d + 10, ARRAY_LEN (seek_data), __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_double_test */
static void
ogg_opus_stereo_seek_test (const char * filename, int format)
{ static float data [SAMPLE_RATE] ;
static float stereo_out [SAMPLE_RATE * 2] ;
SNDFILE * file ;
SF_INFO sfinfo ;
sf_count_t pos ;
unsigned k ;
print_test_name (__func__, filename) ;
gen_windowed_sine_float (data, ARRAY_LEN (data), 0.95) ;
for (k = 0 ; k < ARRAY_LEN (data) ; k++)
{ stereo_out [2 * k] = data [k] ;
stereo_out [2 * k + 1] = data [ARRAY_LEN (data) - k - 1] ;
} ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = format ;
sfinfo.channels = 2 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
test_write_float_or_die (file, 0, stereo_out, ARRAY_LEN (stereo_out), __LINE__) ;
sf_close (file) ;
/* Open file in again for reading. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
/* Read in the whole file. */
test_read_float_or_die (file, 0, stereo_out, ARRAY_LEN (stereo_out), __LINE__) ;
/* Now hammer seeking code. */
test_seek_or_die (file, 234, SEEK_SET, 234, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + (234 * sfinfo.channels), 10, __LINE__) ;
test_seek_or_die (file, 442, SEEK_SET, 442, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + (442 * sfinfo.channels), 10, __LINE__) ;
test_seek_or_die (file, 12, SEEK_CUR, 442 + 10 + 12, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + ((442 + 10 + 12) * sfinfo.channels), 10, __LINE__) ;
test_seek_or_die (file, 12, SEEK_CUR, 442 + 20 + 24, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + ((442 + 20 + 24) * sfinfo.channels), 10, __LINE__) ;
pos = 500 - sfinfo.frames ;
test_seek_or_die (file, pos, SEEK_END, 500, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + (500 * sfinfo.channels), 10, __LINE__) ;
pos = 10 - sfinfo.frames ;
test_seek_or_die (file, pos, SEEK_END, 10, sfinfo.channels, __LINE__) ;
test_readf_float_or_die (file, 0, data, 10, __LINE__) ;
compare_float_or_die (data, stereo_out + (10 * sfinfo.channels), 10, __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_stereo_seek_test */
static void
ogg_opus_original_samplerate_test (void)
{ const char * filename = "ogg_opus_original_samplerate.opus" ;
SNDFILE * file ;
SF_INFO sfinfo ;
int original_samplerate = 54321 ;
sf_count_t frames ;
print_test_name ("ogg_opus_original_samplerate_test", filename) ;
gen_windowed_sine_double (data_out.d, ARRAY_LEN (data_out.d), 0.95) ;
memset (&sfinfo, 0, sizeof (sfinfo)) ;
/* Set up output file type. */
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ;
sfinfo.channels = 1 ;
sfinfo.samplerate = SAMPLE_RATE ;
/* Write the output file. */
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_FALSE, __LINE__) ;
if (sf_command (file, SFC_SET_ORIGINAL_SAMPLERATE, &original_samplerate, sizeof (original_samplerate)) != SF_TRUE)
{ printf ("\nCommand SFC_SET_ORIGINAL_SAMPLERATE failed!\n") ;
exit (1) ;
} ;
test_write_double_or_die (file, 0, data_out.d, ARRAY_LEN (data_out.d), __LINE__) ;
if (sf_command (file, SFC_SET_ORIGINAL_SAMPLERATE, &original_samplerate, sizeof (original_samplerate)) != SF_FALSE)
{ printf ("\nCommand SFC_SET_ORIGINAL_SAMPLERATE succeeded when it should have failed!") ;
exit (1) ;
} ;
sf_close (file) ;
/* Read the file in again. */
memset (&sfinfo, 0, sizeof (sfinfo)) ;
original_samplerate = 0 ;
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
if (sf_command (file, SFC_GET_ORIGINAL_SAMPLERATE, &original_samplerate, sizeof (original_samplerate)) != SF_TRUE
|| original_samplerate != 54321)
{ printf ("\nCommand SFC_GET_ORIGINAL_SAMPLERATE failed!\n") ;
exit (1) ;
} ;
test_read_double_or_die (file, 0, data_in.d, 8, __LINE__) ;
if (sf_command (file, SFC_SET_ORIGINAL_SAMPLERATE, &original_samplerate, sizeof (original_samplerate)) == SF_TRUE)
{ printf ("\nCommand SFC_SET_ORIGINAL_SAMPLERATE succeeded when it should have failed!\n") ;
exit (1) ;
} ;
sf_close (file) ;
/* Test changing the decoder. */
file = test_open_file_or_die (filename, SFM_READ, &sfinfo, SF_FALSE, __LINE__) ;
frames = sfinfo.frames ;
original_samplerate = 16000 ;
if (sf_command (file, SFC_SET_ORIGINAL_SAMPLERATE, &original_samplerate, sizeof (original_samplerate)) != SF_TRUE)
{ printf ("\nCommand SFC_SET_ORIGINAL_SAMPLERATE failed!\n") ;
exit (1) ;
} ;
if (sf_command (file, SFC_GET_CURRENT_SF_INFO, &sfinfo, sizeof (sfinfo)))
{ printf ("\nCommand SFC_GET_CURRENT_SF_INFO failed!\n") ;
exit (1) ;
} ;
if (frames / (48000 / 16000) != sfinfo.frames)
{ printf ("\nIncorrect frame count! (%" PRId64 " vs %" PRId64")\n", frames / (48000 / 16000), sfinfo.frames) ;
exit (1) ;
} ;
test_read_double_or_die (file, 0, data_out.d, sfinfo.frames, __LINE__) ;
sf_close (file) ;
puts ("ok") ;
unlink (filename) ;
} /* ogg_opus_original_samplerate_test */
int
main (void)
{
if (HAVE_EXTERNAL_XIPH_LIBS)
{ ogg_opus_short_test () ;
ogg_opus_int_test () ;
ogg_opus_float_test () ;
ogg_opus_double_test () ;
ogg_opus_stereo_seek_test ("ogg_opus_seek.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS) ;
ogg_opus_original_samplerate_test () ;
}
else
puts (" No Ogg/Opus tests because Ogg/Opus support was not compiled in.") ;
return 0 ;
} /* main */

View File

@ -61,6 +61,7 @@ main (int argc, char *argv [])
printf (" aiff - test adding strings to AIFF files\n") ;
printf (" flac - test adding strings to FLAC files\n") ;
printf (" ogg - test adding strings to OGG files\n") ;
printf (" opus - test adding strings to OPUS files\n") ;
printf (" all - perform all tests\n") ;
exit (1) ;
} ;
@ -114,12 +115,20 @@ main (int argc, char *argv [])
if (do_all || ! strcmp (argv [1], "ogg"))
{ if (HAVE_EXTERNAL_XIPH_LIBS)
string_start_test ("vorbis.oga", SF_FORMAT_OGG) ;
string_start_test ("vorbis.oga", SF_FORMAT_OGG | SF_FORMAT_VORBIS) ;
else
puts (" No Ogg/Vorbis tests because Ogg/Vorbis support was not compiled in.") ;
test_count++ ;
} ;
if (do_all || ! strcmp (argv [1], "opus"))
{ if (HAVE_EXTERNAL_XIPH_LIBS)
string_start_test ("opus.opus", SF_FORMAT_OGG | SF_FORMAT_OPUS) ;
else
puts (" No Ogg/Opus tests because Ogg/Opus support was not compiled in.") ;
test_count++ ;
} ;
if (do_all || ! strcmp (argv [1], "caf"))
{ string_start_test ("strings.caf", SF_FORMAT_CAF) ;
string_start_end_test ("strings.caf", SF_FORMAT_CAF) ;
@ -340,11 +349,12 @@ string_start_end_test (const char *filename, int typemajor)
} /* string_start_end_test */
static void
string_start_test (const char *filename, int typemajor)
string_start_test (const char *filename, int formattype)
{ const char *cptr ;
SNDFILE *file ;
SF_INFO sfinfo ;
int errors = 0 ;
int typemajor = SF_FORMAT_TYPEMASK & formattype ;
print_test_name ("string_start_test", filename) ;
@ -353,15 +363,20 @@ string_start_test (const char *filename, int typemajor)
sfinfo.channels = 1 ;
sfinfo.frames = 0 ;
switch (typemajor)
{ case SF_FORMAT_OGG :
sfinfo.format = typemajor | SF_FORMAT_VORBIS ;
switch (formattype)
{ case SF_FORMAT_OGG | SF_FORMAT_OPUS :
/* Opus only supports some discrete sample rates. */
sfinfo.samplerate = 48000 ;
break ;
case SF_FORMAT_OGG | SF_FORMAT_VORBIS :
break ;
default :
sfinfo.format = typemajor | SF_FORMAT_PCM_16 ;
formattype |= SF_FORMAT_PCM_16 ;
break ;
} ;
sfinfo.format = formattype ;
file = test_open_file_or_die (filename, SFM_WRITE, &sfinfo, SF_TRUE, __LINE__) ;

View File

@ -342,6 +342,16 @@ echo "----------------------------------------------------------------------"
echo " $sfversion passed tests on OGG/VORBIS files."
echo "----------------------------------------------------------------------"
# opus-tests
./tests/ogg_opus_test@EXEEXT@
./tests/compression_size_test@EXEEXT@ opus
./tests/lossy_comp_test@EXEEXT@ ogg_opus
./tests/string_test@EXEEXT@ opus
echo "----------------------------------------------------------------------"
echo " $sfversion passed tests on OPUS files."
echo "----------------------------------------------------------------------"
# io-tests
./tests/stdio_test@EXEEXT@
./tests/pipe_test@EXEEXT@