mirror of
https://gitee.com/openharmony/third_party_libsnd
synced 2024-11-26 19:40:24 +00:00
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:
parent
1a87c443fe
commit
326e4533f3
@ -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:
|
||||
|
@ -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)
|
||||
|
12
Makefile.am
12
Makefile.am
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
67
cmake/FindOpus.cmake
Normal 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)
|
@ -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)
|
||||
|
14
configure.ac
14
configure.ac
@ -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 :
|
||||
|
102
doc/command.html
102
doc/command.html
@ -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, &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, &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>
|
||||
|
||||
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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. */
|
||||
} ;
|
||||
|
||||
|
700
src/ogg.c
700
src/ogg.c
@ -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"
|
||||
|
||||
#define OGG_SYNC_READ_SIZE (2048)
|
||||
#define OGG_PAGE_SIZE_MAX (65307)
|
||||
#define OGG_CHUNK_SIZE (65536)
|
||||
#define OGG_CHUNK_SIZE_MAX (1024*1024)
|
||||
|
||||
/*
|
||||
* 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) ;
|
||||
|
||||
int ogg_read_first_page (SF_PRIVATE *psf, OGG_PRIVATE *odata)
|
||||
{ char *buffer ;
|
||||
int bytes ;
|
||||
/*-----------------------------------------------------------------------------------------------
|
||||
** 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) ;
|
||||
|
||||
/* Expose the buffer */
|
||||
buffer = ogg_sync_buffer (&odata->osync, 4096L) ;
|
||||
|
||||
/*
|
||||
** Grab some data. Beginning-of-stream Ogg pages are guarenteed to be
|
||||
** small. 4096 bytes ought to be enough.
|
||||
*/
|
||||
|
||||
/* 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)
|
||||
ret = ogg_sync_fseek (psf, psf->header.indx, SEEK_SET) ;
|
||||
if (ret < 0)
|
||||
return SFE_NOT_SEEKABLE ;
|
||||
bytes = psf_fread (buffer, 1, 4096, psf) ;
|
||||
}
|
||||
|
||||
ogg_sync_wrote (&odata->osync, bytes) ;
|
||||
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) ;
|
||||
|
||||
ret = ogg_sync_next_page (psf, &odata->opage, SF_MAX (0l, 4096 - psf->header.indx), NULL) ;
|
||||
|
||||
/* 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)
|
||||
if (ret == 0)
|
||||
return 0 ;
|
||||
if (ret < 0)
|
||||
return psf->error ;
|
||||
|
||||
/*
|
||||
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
|
||||
|
82
src/ogg.h
82
src/ogg.h
@ -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 */
|
||||
|
1650
src/ogg_opus.c
1650
src/ogg_opus.c
File diff suppressed because it is too large
Load Diff
269
src/ogg_vcomment.c
Normal file
269
src/ogg_vcomment.c
Normal 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
45
src/ogg_vcomment.h
Normal 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 */
|
@ -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))
|
||||
|
||||
|
@ -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 :
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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") ;
|
||||
|
@ -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) ;
|
||||
|
@ -107,6 +107,10 @@ 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 ;
|
||||
|
||||
/* 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) ;
|
||||
|
@ -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)
|
||||
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 ;
|
||||
else
|
||||
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)
|
||||
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 ;
|
||||
else
|
||||
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
424
tests/ogg_opus_test.c
Normal 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 */
|
@ -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__) ;
|
||||
|
||||
|
@ -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@
|
||||
|
Loading…
Reference in New Issue
Block a user