mirror of
https://gitee.com/openharmony/third_party_libsnd
synced 2024-11-23 01:49:53 +00:00
mpeg: Add MPEG Encode, Decode and Tests
Use SF_FORMAT_MP3 major and SF_FORMAT_MPEG_LAYER_(I|II|III) subformats.
This commit is contained in:
parent
be637260f7
commit
663a59aa6e
12
.github/workflows/action.yml
vendored
12
.github/workflows/action.yml
vendored
@ -28,24 +28,28 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
autotools-options: --enable-werror
|
||||
build-system: autotools
|
||||
|
||||
- name: ubuntu-clang-autotools
|
||||
os: ubuntu-latest
|
||||
cc: clang
|
||||
cxx: clang++
|
||||
autotools-options: --enable-werror
|
||||
build-system: autotools
|
||||
|
||||
- name: ubuntu-gcc-ossfuzz
|
||||
os: ubuntu-latest
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
autotools-options: --enable-werror
|
||||
build-system: ossfuzz
|
||||
|
||||
- name: macos-autotools
|
||||
os: macos-latest
|
||||
cc: clang
|
||||
cxx: clang++
|
||||
autotools-options: --enable-werror
|
||||
build-system: autotools
|
||||
|
||||
- name: ubuntu-gcc-cmake
|
||||
@ -179,16 +183,16 @@ jobs:
|
||||
- name: Install MacOS dependencies
|
||||
if: startsWith(matrix.os,'macos')
|
||||
run: |
|
||||
brew install automake autogen speex
|
||||
brew install automake autogen speex mpg123
|
||||
|
||||
- name: Install Lunux dependencies
|
||||
- name: Install Linux dependencies
|
||||
if: startsWith(matrix.os,'ubuntu')
|
||||
run: sudo apt-get install -y autogen ninja-build libogg-dev libvorbis-dev libflac-dev libopus-dev libasound2-dev libsqlite3-dev libspeex-dev
|
||||
run: sudo apt-get install -y autogen ninja-build libogg-dev libvorbis-dev libflac-dev libopus-dev libasound2-dev libsqlite3-dev libspeex-dev libmp3lame-dev libmpg123-dev
|
||||
|
||||
- name: Install Windows dependencies
|
||||
if: startsWith(matrix.os,'windows')
|
||||
run: |
|
||||
vcpkg install libvorbis libflac opus sqlite3 speex --triplet ${{matrix.triplet}}
|
||||
vcpkg install libvorbis libflac opus sqlite3 speex mp3lame mpg123 --triplet ${{matrix.triplet}}
|
||||
|
||||
- name: Configure, build and test with Autotools
|
||||
env:
|
||||
|
@ -78,6 +78,7 @@ include(SndFileChecks)
|
||||
|
||||
cmake_dependent_option (BUILD_REGTEST "Build regtest" ON "SQLITE3_FOUND" OFF)
|
||||
cmake_dependent_option (ENABLE_EXTERNAL_LIBS "Enable FLAC, Vorbis, and Opus codecs" ON "Vorbis_FOUND;FLAC_FOUND;OPUS_FOUND" OFF)
|
||||
cmake_dependent_option (ENABLE_MPEG "Enable MPEG codecs" ON "LAME_FOUND;MPG123_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)
|
||||
@ -104,11 +105,12 @@ set (HAVE_ALSA_ASOUNDLIB_H ${ALSA_FOUND})
|
||||
set (HAVE_SNDIO_H ${SNDIO_FOUND})
|
||||
|
||||
set (ENABLE_EXPERIMENTAL_CODE ${ENABLE_EXPERIMENTAL})
|
||||
set (HAVE_MPEG_LIBS ${ENABLE_MPEG})
|
||||
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, Vorbis, and Opus codecs")
|
||||
add_feature_info (ENABLE_MPEG ENABLE_MPEG "enable MPEG audio (including mp3) 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")
|
||||
@ -147,6 +149,18 @@ set_package_properties (FLAC PROPERTIES
|
||||
DESCRIPTION "Free Lossless Audio Codec Library"
|
||||
PURPOSE "Enables FLAC support"
|
||||
)
|
||||
set_package_properties (LAME PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
URL "https://lame.sourceforge.io/"
|
||||
DESCRIPTION "High quality MPEG Audio Layer III (MP3) encoder"
|
||||
PURPOSE "Enables MPEG layer III (MP3) writing support"
|
||||
)
|
||||
set_package_properties (MPG123 PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
URL "https://www.mpg123.de/"
|
||||
DESCRIPTION "MPEG Audio Layer I/II/III decoder"
|
||||
PURPOSE "Enables MPEG Audio reading support"
|
||||
)
|
||||
set_package_properties(Opus PROPERTIES
|
||||
TYPE RECOMMENDED
|
||||
URL "www.opus-codec.org/"
|
||||
@ -247,6 +261,7 @@ add_library (sndfile
|
||||
src/ogg.c
|
||||
src/chanmap.h
|
||||
src/chanmap.c
|
||||
src/id3.h
|
||||
src/id3.c
|
||||
$<$<BOOL:${WIN32}>:src/windows.c>
|
||||
src/sndfile.c
|
||||
@ -284,8 +299,12 @@ add_library (sndfile
|
||||
src/ogg_speex.c
|
||||
src/ogg_pcm.c
|
||||
src/ogg_opus.c
|
||||
src/ogg_vcomment.h
|
||||
src/ogg_vcomment.c
|
||||
src/nms_adpcm.c
|
||||
src/mp3.c
|
||||
src/mpeg_decode.c
|
||||
src/mpeg_l3_encode.c
|
||||
src/GSM610/config.h
|
||||
src/GSM610/gsm.h
|
||||
src/GSM610/gsm610_priv.h
|
||||
@ -349,6 +368,8 @@ target_link_libraries (sndfile
|
||||
$<$<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>
|
||||
$<$<BOOL:${HAVE_MPEG_LIBS}>:MPG123::libmpg123>
|
||||
$<$<BOOL:${HAVE_MPEG_LIBS}>:Lame::Lame>
|
||||
)
|
||||
set_target_properties (sndfile PROPERTIES
|
||||
PUBLIC_HEADER "${sndfile_HDRS}"
|
||||
@ -1187,6 +1208,14 @@ if (BUILD_TESTING)
|
||||
$<$<BOOL:${LIBM_REQUIRED}>:m>
|
||||
)
|
||||
|
||||
add_executable (mp3_test tests/mp3_test.c)
|
||||
target_link_libraries (mp3_test
|
||||
PRIVATE
|
||||
sndfile
|
||||
test_utils
|
||||
$<$<BOOL:${LIBM_REQUIRED}>:m>
|
||||
)
|
||||
|
||||
add_executable (stdin_test tests/stdin_test.c)
|
||||
target_link_libraries (stdin_test
|
||||
PRIVATE
|
||||
@ -1418,6 +1447,10 @@ if (BUILD_TESTING)
|
||||
add_test (lossy_comp_test_ogg_opus lossy_comp_test ogg_opus)
|
||||
add_test (string_test_opus string_test opus)
|
||||
|
||||
### mp3-tests ###
|
||||
add_test (mp3_test mp3_test)
|
||||
add_test (compression_size_test_mpeg compression_size_test mpeg)
|
||||
|
||||
### io-tests
|
||||
add_test (stdio_test stdio_test)
|
||||
add_test (pipe_test pipe_test)
|
||||
|
20
Makefile.am
20
Makefile.am
@ -62,26 +62,26 @@ endif
|
||||
lib_LTLIBRARIES = src/libsndfile.la
|
||||
include_HEADERS = include/sndfile.hh
|
||||
nodist_include_HEADERS = include/sndfile.h
|
||||
src_libsndfile_la_CFLAGS = $(EXTERNAL_XIPH_CFLAGS)
|
||||
src_libsndfile_la_CFLAGS = $(EXTERNAL_XIPH_CFLAGS) $(MPEG_CFLAGS)
|
||||
# MinGW requires -no-undefined if a DLL is to be built.
|
||||
src_libsndfile_la_LDFLAGS = -no-undefined -version-info $(SHARED_VERSION_INFO) $(SHLIB_VERSION_ARG)
|
||||
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/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
|
||||
src/ogg.c src/ogg.h src/ogg_vorbis.c src/ogg_speex.c src/ogg_pcm.c src/ogg_opus.c src/ogg_vcomment.c src/ogg_vcomment.h \
|
||||
src/common.h src/sfconfig.h src/sfendian.h src/wavlike.h src/sf_unistd.h src/chanmap.h src/mp3.c
|
||||
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
|
||||
src/libcommon.la $(EXTERNAL_XIPH_LIBS) -lm $(MPEG_LIBS)
|
||||
EXTRA_src_libsndfile_la_DEPENDENCIES = $(SYMBOL_FILES)
|
||||
|
||||
noinst_LTLIBRARIES = src/libcommon.la
|
||||
src_libcommon_la_CFLAGS = $(EXTERNAL_XIPH_CFLAGS)
|
||||
src_libcommon_la_CFLAGS = $(EXTERNAL_XIPH_CFLAGS) $(MPEG_CFLAGS)
|
||||
src_libcommon_la_SOURCES = src/common.c src/file_io.c src/command.c src/pcm.c src/ulaw.c src/alaw.c \
|
||||
src/float32.c src/double64.c src/ima_adpcm.c src/ms_adpcm.c src/gsm610.c src/dwvw.c src/vox_adpcm.c \
|
||||
src/interleave.c src/strings.c src/dither.c src/cart.c src/broadcast.c src/audio_detect.c \
|
||||
src/ima_oki_adpcm.c src/ima_oki_adpcm.h src/alac.c src/chunk.c src/ogg.c src/chanmap.c \
|
||||
src/windows.c src/id3.c src/nms_adpcm.c $(WIN_VERSION_FILE)
|
||||
src/ima_oki_adpcm.c src/ima_oki_adpcm.h src/alac.c src/chunk.c src/chanmap.c \
|
||||
src/windows.c src/id3.c src/id3.h src/nms_adpcm.c src/mpeg_decode.c src/mpeg_l3_encode.c src/mpeg.h $(WIN_VERSION_FILE)
|
||||
|
||||
check_PROGRAMS = src/test_main
|
||||
src_test_main_SOURCES = src/test_main.c src/test_main.h src/test_conversions.c src/test_float.c src/test_endswap.c \
|
||||
@ -221,7 +221,8 @@ 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/ogg_opus_test
|
||||
tests/dither_test tests/fix_this tests/largefile_test tests/benchmark tests/ogg_opus_test \
|
||||
tests/mp3_test
|
||||
|
||||
BUILT_SOURCES += \
|
||||
tests/write_read_test.c \
|
||||
@ -377,6 +378,9 @@ tests_cpp_test_LDADD = src/libsndfile.la
|
||||
tests_checksum_test_SOURCES = tests/checksum_test.c tests/utils.c tests/utils.h
|
||||
tests_checksum_test_LDADD = src/libsndfile.la
|
||||
|
||||
tests_mp3_test_SOURCES = tests/mp3_test.c tests/utils.c tests/utils.h
|
||||
tests_mp3_test_LDADD = src/libsndfile.la
|
||||
|
||||
# Lite remove start
|
||||
tests_dwvw_test_SOURCES = tests/dwvw_test.c tests/utils.c tests/utils.h
|
||||
tests_dwvw_test_LDADD = src/libsndfile.la
|
||||
|
66
cmake/FindLame.cmake
Normal file
66
cmake/FindLame.cmake
Normal file
@ -0,0 +1,66 @@
|
||||
# - Find lame
|
||||
# Find the native lame includes and libraries
|
||||
#
|
||||
# LAME_INCLUDE_DIRS - where to find lame.h, etc.
|
||||
# LAME_LIBRARIES - List of libraries when using lame.
|
||||
# LAME_FOUND - True if Lame found.
|
||||
|
||||
if (LAME_INCLUDE_DIR)
|
||||
# Already in cache, be silent
|
||||
set(LAME_FIND_QUIETLY TRUE)
|
||||
endif ()
|
||||
|
||||
find_path (LAME_INCLUDE_DIR lame/lame.h
|
||||
HINTS
|
||||
${LAME_ROOT}
|
||||
)
|
||||
|
||||
# MSVC built lame may be named mp3lame_static.
|
||||
# The provided project files name the library with the lib prefix.
|
||||
|
||||
find_library (LAME_LIBRARY
|
||||
NAMES
|
||||
mp3lame
|
||||
mp3lame_static
|
||||
libmp3lame
|
||||
libmp3lame_static
|
||||
libmp3lame-static
|
||||
HINTS
|
||||
${LAME_ROOT}
|
||||
)
|
||||
|
||||
find_library (LAME_HIP_LIBRARY
|
||||
NAMES
|
||||
mpghip-static
|
||||
libmpghip-static
|
||||
HINTS
|
||||
${LAME_ROOT}
|
||||
)
|
||||
|
||||
# Handle the QUIETLY and REQUIRED arguments and set LAME_FOUND
|
||||
# to TRUE if all listed variables are TRUE.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args (Lame
|
||||
REQUIRED_VARS
|
||||
LAME_LIBRARY
|
||||
LAME_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if (LAME_FOUND)
|
||||
set (LAME_LIBRARIES ${LAME_LIBRARY} ${LAME_HIP_LIBRARY})
|
||||
set (LAME_INCLUDE_DIRS ${LAME_INCLUDE_DIR})
|
||||
|
||||
if (NOT TARGET Lame::Lame)
|
||||
add_library (Lame::Lame UNKNOWN IMPORTED)
|
||||
set_target_properties (Lame::Lame PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LAME_INCLUDE_DIRS}"
|
||||
IMPORTED_LOCATION "${LAME_LIBRARY}"
|
||||
)
|
||||
if (LAME_HIP_LIBRARY)
|
||||
set_property (TARGET Lame::Lame APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES "${LAME_HIP_LIBRARY}")
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(LAME_INCLUDE_DIR LAME_LIBRARY LAME_HIP_LIBRARY)
|
64
cmake/FindMpg123.cmake
Normal file
64
cmake/FindMpg123.cmake
Normal file
@ -0,0 +1,64 @@
|
||||
# - Find mpg123
|
||||
# Find the native mpg123 includes and libraries
|
||||
#
|
||||
# MPG123_INCLUDE_DIRS - where to find mpg123.h, etc.
|
||||
# MPG123_LIBRARIES - List of libraries when using mpg123.
|
||||
# MPG123_FOUND - True if Mpg123 found.
|
||||
|
||||
if (MPG123_INCLUDE_DIR)
|
||||
# Already in cache, be silent
|
||||
set(MPG123_FIND_QUIETLY TRUE)
|
||||
endif ()
|
||||
|
||||
find_package (PkgConfig QUIET)
|
||||
pkg_check_modules(PC_MPG123 QUIET libmpg123>=1.25.10)
|
||||
|
||||
set (MPG123_VERSION ${PC_MPG123_VERSION})
|
||||
|
||||
find_path (MPG123_INCLUDE_DIR mpg123.h
|
||||
HINTS
|
||||
${PC_MPG123_INCLUDEDIR}
|
||||
${PC_MPG123_INCLUDE_DIRS}
|
||||
${MPG123_ROOT}
|
||||
)
|
||||
|
||||
# MSVC built mpg123 may be named mpg123_static.
|
||||
# The provided project files name the library with the lib prefix.
|
||||
|
||||
find_library (MPG123_LIBRARY
|
||||
NAMES
|
||||
mpg123
|
||||
mpg123_static
|
||||
libmpg123
|
||||
libmpg123_static
|
||||
HINTS
|
||||
${PC_MPG123_LIBDIR}
|
||||
${PC_MPG123_LIBRARY_DIRS}
|
||||
${MPG123_ROOT}
|
||||
)
|
||||
|
||||
# Handle the QUIETLY and REQUIRED arguments and set MPG123_FOUND
|
||||
# to TRUE if all listed variables are TRUE.
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args (Mpg123
|
||||
REQUIRED_VARS
|
||||
MPG123_LIBRARY
|
||||
MPG123_INCLUDE_DIR
|
||||
VERSION_VAR
|
||||
MPG123_VERSION
|
||||
)
|
||||
|
||||
if (MPG123_FOUND)
|
||||
set (MPG123_LIBRARIES ${MPG123_LIBRARY})
|
||||
set (MPG123_INCLUDE_DIRS ${MPG123_INCLUDE_DIR})
|
||||
|
||||
if (NOT TARGET MPG123::libmpg123)
|
||||
add_library (MPG123::libmpg123 UNKNOWN IMPORTED)
|
||||
set_target_properties (MPG123::libmpg123 PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${MPG123_INCLUDE_DIRS}"
|
||||
IMPORTED_LOCATION "${MPG123_LIBRARIES}"
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(MPG123_INCLUDE_DIR MPG123_LIBRARY)
|
@ -56,6 +56,14 @@ else ()
|
||||
set (HAVE_EXTERNAL_XIPH_LIBS 0)
|
||||
endif ()
|
||||
|
||||
find_package (Lame)
|
||||
find_package (Mpg123 1.25.10)
|
||||
if (LAME_FOUND AND MPG123_FOUND)
|
||||
set (HAVE_MPEG_LIBS 1)
|
||||
else ()
|
||||
set (HAVE_MPEG_LIBS 0)
|
||||
endif()
|
||||
|
||||
find_package (Speex)
|
||||
find_package (SQLite3)
|
||||
|
||||
|
54
configure.ac
54
configure.ac
@ -148,6 +148,9 @@ AC_ARG_ENABLE([alsa],
|
||||
AC_ARG_ENABLE([external-libs],
|
||||
[AS_HELP_STRING([--disable-external-libs], [disable use of FLAC, Ogg and Vorbis [[default=no]]])])
|
||||
|
||||
AC_ARG_ENABLE([mpeg],
|
||||
[AS_HELP_STRING([--disable-mpeg], [disable use of LAME/MPG123 for MPEG (MP3) [[defaults=no]]])])
|
||||
|
||||
AC_ARG_ENABLE(octave,
|
||||
[AS_HELP_STRING([--enable-octave], [enable building of GNU Octave module])])
|
||||
|
||||
@ -388,6 +391,51 @@ 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, vorbis, and opus are available.])
|
||||
|
||||
dnl ====================================================================================
|
||||
dnl Check for MPEG libraris liblame
|
||||
|
||||
ac_cv_lame="no"
|
||||
ac_cv_mpg123="no"
|
||||
HAVE_MPEG=0
|
||||
MPEG_CFLAGS=""
|
||||
MPEG_LIBS=""
|
||||
|
||||
AS_IF([test -n "$PKG_CONFIG"], [
|
||||
AS_IF([test "x$enable_mpeg" = "xno"], [
|
||||
AC_MSG_WARN([[*** MPEG (Lame/MPG123) disabled. ***]])
|
||||
], [
|
||||
AC_CHECK_HEADER(lame/lame.h,
|
||||
lame_header_found="yes",
|
||||
lame_header_found="no")
|
||||
AC_SEARCH_LIBS(lame_set_VBR_q, [lame mp3lame], [lame_lib_found="yes"], [lame_lib_found="no"])
|
||||
AS_IF([test "x$lame_lib_found$lame_header_found" = "xyesyes"], [
|
||||
ac_cv_lame="yes"
|
||||
], [
|
||||
AC_MSG_WARN([["MPEG support selected but external Lame library cannot be found.]])
|
||||
])
|
||||
|
||||
PKG_CHECK_MOD_VERSION([MPG123], [libmpg123 >= 1.25.10], [
|
||||
ac_cv_mpg123="yes"
|
||||
], [
|
||||
AC_MSG_WARN([["MPEG support selected but external MPG123 library cannot be found.]])
|
||||
])
|
||||
|
||||
AS_IF([test "x$ac_cv_lame$ac_cv_mpg123" = "xyesyes"], [
|
||||
enable_mpeg="yes"
|
||||
HAVE_MPEG=1
|
||||
MPEG_CFLAGS="$MPG123_CFLAGS"
|
||||
MPEG_LIBS="$MPG123_LIBS"
|
||||
], [
|
||||
enable_mpeg="no"
|
||||
AS_ECHO([""])
|
||||
AC_MSG_WARN([[*** MPEG support disabled.]])
|
||||
AS_ECHO([""])
|
||||
])
|
||||
])
|
||||
])
|
||||
|
||||
AC_DEFINE_UNQUOTED([HAVE_MPEG], [$HAVE_MPEG], [Will be set to 1 if lame, mpg123 mpeg support is available.])
|
||||
|
||||
dnl ====================================================================================
|
||||
dnl Check for libsqlite3 (only used in regtest).
|
||||
|
||||
@ -666,6 +714,7 @@ AC_SUBST(GEN_TOOL)
|
||||
AC_SUBST(WIN_RC_VERSION)
|
||||
|
||||
AC_SUBST(HAVE_EXTERNAL_XIPH_LIBS)
|
||||
AC_SUBST(HAVE_MPEG)
|
||||
AC_SUBST(OS_SPECIFIC_CFLAGS)
|
||||
AC_SUBST(OS_SPECIFIC_LINKS)
|
||||
AC_SUBST(SNDIO_LIBS)
|
||||
@ -673,6 +722,10 @@ AC_SUBST(SNDIO_LIBS)
|
||||
AC_SUBST(EXTERNAL_XIPH_CFLAGS)
|
||||
AC_SUBST(EXTERNAL_XIPH_LIBS)
|
||||
AC_SUBST(EXTERNAL_XIPH_REQUIRE)
|
||||
AC_SUBST(MPG123_CFLAGS)
|
||||
AC_SUBST(MPG123_LIBS)
|
||||
AC_SUBST(MPEG_CFLAGS)
|
||||
AC_SUBST(MPEG_LIBS)
|
||||
AC_SUBST(SRC_BINDIR)
|
||||
AC_SUBST(TEST_BINDIR)
|
||||
|
||||
@ -718,6 +771,7 @@ AC_MSG_RESULT([
|
||||
Experimental code : ................... ${enable_experimental:-no}
|
||||
Using ALSA in example programs : ...... ${enable_alsa:-no}
|
||||
External FLAC/Ogg/Vorbis/Opus : ....... ${enable_external_libs:-no}
|
||||
External MPEG Lame/MPG123 : ........... ${enable_mpeg:-no}
|
||||
Building Octave interface : ........... ${OCTAVE_BUILD}
|
||||
|
||||
Tools :
|
||||
|
@ -71,6 +71,7 @@ enum
|
||||
SF_FORMAT_OGG = 0x200000, /* Xiph OGG container */
|
||||
SF_FORMAT_MPC2K = 0x210000, /* Akai MPC 2000 sampler */
|
||||
SF_FORMAT_RF64 = 0x220000, /* RF64 WAV file */
|
||||
SF_FORMAT_MP3 = 0x230000, /* So-called MP3 file. */
|
||||
|
||||
/* Subtypes from here on. */
|
||||
|
||||
@ -116,6 +117,10 @@ enum
|
||||
SF_FORMAT_ALAC_24 = 0x0072, /* Apple Lossless Audio Codec (24 bit). */
|
||||
SF_FORMAT_ALAC_32 = 0x0073, /* Apple Lossless Audio Codec (32 bit). */
|
||||
|
||||
SF_FORMAT_MPEG_LAYER_I = 0x0080, /* MPEG-1 Audio Layer I */
|
||||
SF_FORMAT_MPEG_LAYER_II = 0x0081, /* MPEG-1 Audio Layer II */
|
||||
SF_FORMAT_MPEG_LAYER_III = 0x0082, /* MPEG-2 Audio Layer III */
|
||||
|
||||
/* Endian-ness options. */
|
||||
|
||||
SF_ENDIAN_FILE = 0x00000000, /* Default file endian-ness. */
|
||||
@ -218,6 +223,9 @@ enum
|
||||
SFC_SET_OGG_PAGE_LATENCY_MS = 0x1302,
|
||||
SFC_SET_OGG_PAGE_LATENCY = 0x1303,
|
||||
|
||||
SFC_GET_BITRATE_MODE = 0x1302,
|
||||
SFC_SET_BITRATE_MODE = 0x1303,
|
||||
|
||||
/* Cart Chunk support */
|
||||
SFC_SET_CART_INFO = 0x1400,
|
||||
SFC_GET_CART_INFO = 0x1401,
|
||||
@ -334,6 +342,14 @@ enum
|
||||
SF_CHANNEL_MAP_MAX
|
||||
} ;
|
||||
|
||||
/* Bitrate mode values (for use with SFC_GET/SET_BITRATE_MODE)
|
||||
*/
|
||||
enum
|
||||
{ SF_BITRATE_MODE_CONSTANT = 0,
|
||||
SF_BITRATE_MODE_AVERAGE,
|
||||
SF_BITRATE_MODE_VARIABLE
|
||||
} ;
|
||||
|
||||
|
||||
/* A SNDFILE* pointer can be passed around much like stdio.h's FILE* pointer. */
|
||||
|
||||
|
21
src/common.c
21
src/common.c
@ -104,7 +104,7 @@ void
|
||||
psf_log_printf (SF_PRIVATE *psf, const char *format, ...)
|
||||
{ va_list ap ;
|
||||
uint32_t u ;
|
||||
int d, tens, shift, width, width_specifier, left_align, slen ;
|
||||
int d, tens, shift, width, width_specifier, left_align, slen, precision ;
|
||||
char c, *strptr, istr [5], lead_char, sign_char ;
|
||||
|
||||
va_start (ap, format) ;
|
||||
@ -153,6 +153,12 @@ psf_log_printf (SF_PRIVATE *psf, const char *format, ...)
|
||||
while ((c = *format++) && isdigit (c))
|
||||
width_specifier = width_specifier * 10 + (c - '0') ;
|
||||
|
||||
precision = 0 ;
|
||||
if (c == '.')
|
||||
{ while ((c = *format++) && isdigit (c))
|
||||
precision = precision * 10 + (c - '0') ;
|
||||
} ;
|
||||
|
||||
switch (c)
|
||||
{ case 0 : /* NULL character. */
|
||||
va_end (ap) ;
|
||||
@ -162,12 +168,15 @@ psf_log_printf (SF_PRIVATE *psf, const char *format, ...)
|
||||
strptr = va_arg (ap, char *) ;
|
||||
if (strptr == NULL)
|
||||
break ;
|
||||
slen = strlen (strptr) ;
|
||||
if (precision > 0)
|
||||
slen = strnlen (strptr, precision) ;
|
||||
else
|
||||
slen = strlen (strptr) ;
|
||||
width_specifier = width_specifier >= slen ? width_specifier - slen : 0 ;
|
||||
if (left_align == SF_FALSE)
|
||||
while (width_specifier -- > 0)
|
||||
log_putchar (psf, ' ') ;
|
||||
while (*strptr)
|
||||
while (slen--)
|
||||
log_putchar (psf, *strptr++) ;
|
||||
while (width_specifier -- > 0)
|
||||
log_putchar (psf, ' ') ;
|
||||
@ -877,7 +886,7 @@ header_seek (SF_PRIVATE *psf, sf_count_t position, int whence)
|
||||
psf_bump_header_allocation (psf, position) ;
|
||||
if (position > psf->header.len)
|
||||
{ /* Too much header to cache so just seek instead. */
|
||||
psf->header.indx = psf->header.end ;
|
||||
psf->header.indx = psf->header.end = 0 ;
|
||||
psf_fseek (psf, position, whence) ;
|
||||
return ;
|
||||
} ;
|
||||
@ -1563,6 +1572,7 @@ str_of_major_format (int format)
|
||||
CASE_NAME (SF_FORMAT_CAF) ;
|
||||
CASE_NAME (SF_FORMAT_WVE) ;
|
||||
CASE_NAME (SF_FORMAT_OGG) ;
|
||||
CASE_NAME (SF_FORMAT_MP3) ;
|
||||
default :
|
||||
break ;
|
||||
} ;
|
||||
@ -1599,6 +1609,9 @@ str_of_minor_format (int format)
|
||||
CASE_NAME (SF_FORMAT_DPCM_8) ;
|
||||
CASE_NAME (SF_FORMAT_DPCM_16) ;
|
||||
CASE_NAME (SF_FORMAT_VORBIS) ;
|
||||
CASE_NAME (SF_FORMAT_MPEG_LAYER_I) ;
|
||||
CASE_NAME (SF_FORMAT_MPEG_LAYER_II) ;
|
||||
CASE_NAME (SF_FORMAT_MPEG_LAYER_III) ;
|
||||
default :
|
||||
break ;
|
||||
} ;
|
||||
|
@ -739,6 +739,8 @@ enum
|
||||
|
||||
SFE_MPC_NO_MARKER,
|
||||
|
||||
SFE_MPEG_BAD_SAMPLERATE,
|
||||
|
||||
SFE_MAX_ERROR /* This must be last in list. */
|
||||
} ;
|
||||
|
||||
@ -894,10 +896,10 @@ int ogg_pcm_open (SF_PRIVATE *psf) ;
|
||||
int ogg_opus_open (SF_PRIVATE *psf) ;
|
||||
int ogg_open (SF_PRIVATE *psf) ;
|
||||
|
||||
int mp3_open (SF_PRIVATE *psf) ;
|
||||
|
||||
/* In progress. Do not currently work. */
|
||||
|
||||
int mpeg_open (SF_PRIVATE *psf) ;
|
||||
int rx2_open (SF_PRIVATE *psf) ;
|
||||
int txw_open (SF_PRIVATE *psf) ;
|
||||
int wve_open (SF_PRIVATE *psf) ;
|
||||
@ -1014,6 +1016,7 @@ typedef struct
|
||||
|
||||
int audio_detect (SF_PRIVATE * psf, AUDIO_DETECT *ad, const unsigned char * data, int datalen) ;
|
||||
int id3_skip (SF_PRIVATE * psf) ;
|
||||
const char *id3_lookup_v1_genre (int number) ;
|
||||
|
||||
void alac_get_desc_chunk_items (int subformat, uint32_t *fmt_flags, uint32_t *frames_per_packet) ;
|
||||
|
||||
|
@ -43,6 +43,9 @@
|
||||
/* Will be set to 1 if flac, ogg and vorbis are available. */
|
||||
#cmakedefine01 HAVE_EXTERNAL_XIPH_LIBS
|
||||
|
||||
/* Will be set to 1 if lame and mpg123 are available. */
|
||||
#cmakedefine01 HAVE_MPEG
|
||||
|
||||
/* Define to 1 if you have the `floor' function. */
|
||||
#cmakedefine01 HAVE_FLOOR
|
||||
|
||||
|
65
src/id3.c
65
src/id3.c
@ -26,6 +26,43 @@
|
||||
#include "sndfile.h"
|
||||
#include "sfendian.h"
|
||||
#include "common.h"
|
||||
#include "id3.h"
|
||||
|
||||
#if HAVE_MPEG
|
||||
#include <lame/lame.h>
|
||||
|
||||
struct id3v1_genre_handler_userdata
|
||||
{ int number ;
|
||||
const char *ret ;
|
||||
} ;
|
||||
|
||||
static void
|
||||
id3v1_genre_handler (int number, const char *description, void *userdata)
|
||||
{ struct id3v1_genre_handler_userdata *data = (struct id3v1_genre_handler_userdata *) userdata ;
|
||||
if (data->number == number)
|
||||
data->ret = description ;
|
||||
}
|
||||
|
||||
const char *
|
||||
id3_lookup_v1_genre (int number)
|
||||
{ struct id3v1_genre_handler_userdata data ;
|
||||
|
||||
data.number = number ;
|
||||
data.ret = NULL ;
|
||||
id3tag_genre_list (id3v1_genre_handler, &data) ;
|
||||
|
||||
return data.ret ;
|
||||
}
|
||||
|
||||
#else /* HAVE_MPEG */
|
||||
|
||||
const char *
|
||||
id3_lookup_v1_genre (int UNUSED (number))
|
||||
{ return NULL ;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
int
|
||||
id3_skip (SF_PRIVATE * psf)
|
||||
@ -57,3 +94,31 @@ id3_skip (SF_PRIVATE * psf)
|
||||
|
||||
return 0 ;
|
||||
} /* id3_skip */
|
||||
|
||||
const char *
|
||||
id3_process_v2_genre (const char *genre)
|
||||
{ int num = 0 ;
|
||||
char c ;
|
||||
const char *ptr ;
|
||||
|
||||
if (!genre)
|
||||
return NULL ;
|
||||
|
||||
/*
|
||||
** Genre may require more processing.
|
||||
**
|
||||
** It is allowed to have numeric references to the genre table from ID3v1.
|
||||
** We'll just convert the simple case here, strings of the format "(nnn)".
|
||||
*/
|
||||
ptr = genre ;
|
||||
if (ptr [0] == '(' && (c = *++ ptr) && isdigit (c))
|
||||
{ num = c - '0' ;
|
||||
while ((c == *++ ptr) && isdigit (c))
|
||||
num = num * 10 + (c - '0') ;
|
||||
if (c == ')' && (c = *++ ptr) == '\0' && num < 256)
|
||||
if ((ptr = id3_lookup_v1_genre (num)))
|
||||
return ptr ;
|
||||
} ;
|
||||
|
||||
return genre ;
|
||||
} /* id3_process_v2_genre */
|
||||
|
29
src/id3.h
Normal file
29
src/id3.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2019 Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
** Copyright (C) 2019 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_ID3_H
|
||||
#define SF_SRC_ID3_H
|
||||
|
||||
int id3_skip (SF_PRIVATE * psf) ;
|
||||
|
||||
const char *id3_lookup_v1_genre (int number) ;
|
||||
|
||||
const char *id3_process_v2_genre (const char *genre) ;
|
||||
|
||||
#endif /* SF_SRC_ID3_H */
|
174
src/mp3.c
Normal file
174
src/mp3.c
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
** Copyright (C) 2019 Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
** Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
** What is an MP3 file anyways?
|
||||
**
|
||||
** Believe it or not, MP3 files don't exist.
|
||||
**
|
||||
** The MPEG-1 standard defined a few audio codecs. The standard only defined a
|
||||
** streaming format of semi-independent frames of audio meant for broadcasting,
|
||||
** with no details or hints about stored on-disk formats. Each frame defines
|
||||
** it's own bitrate, channel count, sample rate. However, they aren't
|
||||
** completely independent.
|
||||
**
|
||||
** With its amazing-for-the-time compression ratio, the layer III audio codec
|
||||
** became quite popular with file sharers and the internet. A stream of layer
|
||||
** III audio would simply be written as a file, usually with the extension
|
||||
** .mp3. Over time enthusiast and proprietary encoders sprung up adding
|
||||
** different metadata headers and trailers, file seeking tables, and fiddling
|
||||
** with the codecs parameters. These changes are only really based on consensus.
|
||||
**
|
||||
** MPEG-1 I/II/III audio can be embedded in a few container formats (including
|
||||
** WAV), stored raw, or with additional metadata extension headers and trailers.
|
||||
**
|
||||
** This file is concerned only with the most common case of MPEG Layer III
|
||||
** audio without a container but with the additional metadata standards.
|
||||
**
|
||||
** For the purposes of libsndfile, the major format of SF_FORMAT_MP3 means the
|
||||
** following assumptions. A file of major format type SF_FORMAT_MP3:
|
||||
** - Contains only layer III audio frames (SF_FORMAT_MPEG_LAYER_III)
|
||||
** - All MPEG frames contained in the file have the same channel count
|
||||
** - All MPEG frames contained in the file have the same samplerate
|
||||
** - Has at least one of:
|
||||
** - an ID3v1 trailer
|
||||
** - an ID3v2 header or trailer
|
||||
** - A Lame/Xing/Info header
|
||||
**
|
||||
** Testing has revealed that, more than any other format, MP3 suffers from
|
||||
** corrupt files in the wild that most other software 'just works' with. This is
|
||||
** usually because the MP3 decoders are very lenient. They are aided by the
|
||||
** presence of a regular sync frame, but this makes it hard to classify them
|
||||
** in a library that consumes other better-specified file formats.
|
||||
*/
|
||||
|
||||
#include "sfconfig.h"
|
||||
|
||||
#include "sndfile.h"
|
||||
#include "common.h"
|
||||
|
||||
#if HAVE_MPEG
|
||||
|
||||
#include "mpeg.h"
|
||||
|
||||
static int mp3_write_header (SF_PRIVATE *psf, int calc_length) ;
|
||||
static int mp3_command (SF_PRIVATE *psf, int command, void *data, int datasize) ;
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Private functions
|
||||
*/
|
||||
|
||||
static int
|
||||
mp3_write_header (SF_PRIVATE *psf, int UNUSED (calc_length))
|
||||
{
|
||||
if (psf->have_written)
|
||||
return 0 ;
|
||||
|
||||
return mpeg_l3_encoder_write_id3tag (psf) ;
|
||||
} ;
|
||||
|
||||
static int
|
||||
mp3_command (SF_PRIVATE *psf, int command, void *data, int datasize)
|
||||
{ int bitrate_mode ;
|
||||
|
||||
switch (command)
|
||||
{ case SFC_SET_COMPRESSION_LEVEL :
|
||||
if (data == NULL || datasize != sizeof (double))
|
||||
{ psf->error = SFE_BAD_COMMAND_PARAM ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
if (psf->file.mode != SFM_WRITE)
|
||||
{ psf->error = SFE_NOT_WRITEMODE ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
return mpeg_l3_encoder_set_quality (psf, *(double *) data) ;
|
||||
|
||||
case SFC_SET_BITRATE_MODE :
|
||||
if (psf->file.mode != SFM_WRITE)
|
||||
{ psf->error = SFE_NOT_WRITEMODE ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
if (data == NULL || datasize != sizeof (int))
|
||||
{ psf->error = SFE_BAD_COMMAND_PARAM ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
bitrate_mode = *(int *) data ;
|
||||
return mpeg_l3_encoder_set_bitrate_mode (psf, bitrate_mode) ;
|
||||
|
||||
case SFC_GET_BITRATE_MODE :
|
||||
if (psf->file.mode == SFM_READ)
|
||||
return mpeg_decoder_get_bitrate_mode (psf) ;
|
||||
else
|
||||
return mpeg_l3_encoder_get_bitrate_mode (psf) ;
|
||||
|
||||
default :
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
|
||||
return SF_FALSE ;
|
||||
} /* mpeg_command */
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Public functions
|
||||
*/
|
||||
|
||||
int
|
||||
mp3_open (SF_PRIVATE *psf)
|
||||
{ int error ;
|
||||
|
||||
if (psf->file.mode == SFM_RDWR)
|
||||
return SFE_BAD_MODE_RW ;
|
||||
|
||||
if (psf->file.mode == SFM_WRITE)
|
||||
{ if (SF_CODEC (psf->sf.format) != SF_FORMAT_MPEG_LAYER_III)
|
||||
return SFE_BAD_OPEN_FORMAT ;
|
||||
|
||||
if ((error = mpeg_l3_encoder_init (psf, SF_TRUE)))
|
||||
return error ;
|
||||
|
||||
/* Choose variable bitrate mode by default for standalone (mp3) files.*/
|
||||
mpeg_l3_encoder_set_bitrate_mode (psf, SF_BITRATE_MODE_VARIABLE) ;
|
||||
|
||||
/* ID3 support */
|
||||
psf->strings.flags = SF_STR_ALLOW_START ;
|
||||
psf->write_header = mp3_write_header ;
|
||||
psf->datalength = 0 ;
|
||||
psf->dataoffset = 0 ;
|
||||
} ;
|
||||
|
||||
if (psf->file.mode == SFM_READ)
|
||||
{ if ((error = mpeg_decoder_init (psf)))
|
||||
return error ;
|
||||
} ;
|
||||
|
||||
psf->command = mp3_command ;
|
||||
|
||||
return 0 ;
|
||||
} /* mpeg_open */
|
||||
|
||||
#else /* HAVE_MPEG */
|
||||
|
||||
int
|
||||
mp3_open (SF_PRIVATE *psf)
|
||||
{
|
||||
psf_log_printf (psf, "This version of libsndfile was compiled without MP3 support.\n") ;
|
||||
return SFE_UNIMPLEMENTED ;
|
||||
} /* mpeg_open */
|
||||
|
||||
#endif
|
74
src/mpeg.h
Normal file
74
src/mpeg.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
** Copyright (C) 2019 Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
** Copyright (C) 2019 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 SNDFILE_MPEG_H
|
||||
#define SNDFILE_MPEG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
int mpeg_decoder_init (SF_PRIVATE *psf) ;
|
||||
|
||||
/*
|
||||
** Get the file bitrate mode, returning one of the SF_BITRATE_MODE_ enum
|
||||
** values. Purely informative, 'Frankenstein' files and VBR files without an
|
||||
** Xing/LAME/Info header may not be detected properly.
|
||||
*/
|
||||
int mpeg_decoder_get_bitrate_mode (SF_PRIVATE *psf) ;
|
||||
|
||||
|
||||
/*
|
||||
** Initialize an encoder instance for writing. If parameter info_tag is
|
||||
** SF_TRUE, a Xing/LAME/Info header is written at the beginning of the file,
|
||||
** (unless the file cannot seek.)
|
||||
*/
|
||||
int mpeg_l3_encoder_init (SF_PRIVATE *psf, int info_tag) ;
|
||||
|
||||
|
||||
/*
|
||||
** Write an ID3v2 header from the sndfile string metadata. Must be called
|
||||
** before any audio data is written. Writing an ID3v2 header will also cause
|
||||
** a ID3v1 trailer to be written on close automatically.
|
||||
*/
|
||||
int mpeg_l3_encoder_write_id3tag (SF_PRIVATE *psf) ;
|
||||
|
||||
/*
|
||||
** Set the encoder quality setting. Argument to compression should be identical
|
||||
** to that for SFC_SET_COMPRESSION_LEVEL; It should be in the range [0-1],
|
||||
** with 0 being highest quality, least compression, and 1 being the opposite.
|
||||
** Returns SF_TRUE on success, SF_FALSE otherwise.
|
||||
*/
|
||||
int mpeg_l3_encoder_set_quality (SF_PRIVATE *psf, double compression) ;
|
||||
|
||||
/*
|
||||
** Set the encoder bitrate mode. Can only be called before any data has been
|
||||
** written. Argument mode should be one of the SF_BITRATE_MODE_ enum values.
|
||||
** Returns SF_TRUE on success, SF_FALSE otherwise. The SF_BITRATE_MODE_FILE
|
||||
** enum value should not be passed here but rather intercepted at the container
|
||||
** level and translated according to the container.
|
||||
*/
|
||||
int mpeg_l3_encoder_set_bitrate_mode (SF_PRIVATE *psf, int mode) ;
|
||||
|
||||
/*
|
||||
** Get the encoder bitrate mode in use. Returns a SF_BITRATE_MODE_ enum value.
|
||||
** Will not return SF_BITRATE_MODE_FILE.
|
||||
*/
|
||||
int mpeg_l3_encoder_get_bitrate_mode (SF_PRIVATE *psf) ;
|
||||
|
||||
|
||||
#endif /* SNDFILE_MPEG_H */
|
659
src/mpeg_decode.c
Normal file
659
src/mpeg_decode.c
Normal file
@ -0,0 +1,659 @@
|
||||
/*
|
||||
** Copyright (C) 2019 Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
** Copyright (C) 2019 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 <math.h>
|
||||
|
||||
#include "sndfile.h"
|
||||
#include "common.h"
|
||||
#include "mpeg.h"
|
||||
|
||||
#if HAVE_MPEG
|
||||
|
||||
#include "sfendian.h"
|
||||
#include "id3.h"
|
||||
|
||||
#include <mpg123.h>
|
||||
|
||||
typedef struct
|
||||
{ mpg123_handle *pmh ;
|
||||
size_t header_remaining ;
|
||||
unsigned unique_id ;
|
||||
} MPEG_DEC_PRIVATE ;
|
||||
|
||||
static int mpeg_dec_close (SF_PRIVATE *psf) ;
|
||||
static sf_count_t mpeg_dec_seek (SF_PRIVATE *psf, int whence, sf_count_t count) ;
|
||||
|
||||
static ssize_t mpeg_dec_io_read (void *priv, void *buffer, size_t nbytes) ;
|
||||
static off_t mpeg_dec_io_lseek (void *priv, off_t offset, int whence) ;
|
||||
|
||||
static ssize_t
|
||||
mpeg_dec_io_read (void *priv, void *buffer, size_t nbytes)
|
||||
{ SF_PRIVATE *psf = (SF_PRIVATE *) priv ;
|
||||
MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
|
||||
if (pmp3d->header_remaining)
|
||||
{ if (pmp3d->header_remaining < nbytes)
|
||||
nbytes = pmp3d->header_remaining ;
|
||||
psf_binheader_readf (psf, "b", buffer, nbytes) ;
|
||||
pmp3d->header_remaining -= nbytes ;
|
||||
return nbytes ;
|
||||
} ;
|
||||
|
||||
return psf_fread (buffer, 1, nbytes, psf) ;
|
||||
} /* mpeg_dec_io_read */
|
||||
|
||||
static off_t
|
||||
mpeg_dec_io_lseek (void *priv, off_t offset, int whence)
|
||||
{ SF_PRIVATE *psf = (SF_PRIVATE *) priv ;
|
||||
|
||||
return psf_fseek (psf, offset, whence) ;
|
||||
} /* mpeg_dec_io_lseek */
|
||||
|
||||
static int
|
||||
mpeg_dec_close (SF_PRIVATE *psf)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
|
||||
if (pmp3d)
|
||||
{ if (pmp3d->pmh)
|
||||
{ mpg123_close (pmp3d->pmh) ;
|
||||
mpg123_delete (pmp3d->pmh) ;
|
||||
pmp3d->pmh = NULL ;
|
||||
}
|
||||
free (psf->codec_data) ;
|
||||
psf->codec_data = NULL ;
|
||||
} ;
|
||||
|
||||
return 0 ;
|
||||
} /* mpeg_dec_close */
|
||||
|
||||
static sf_count_t mpeg_dec_decode (SF_PRIVATE *psf, MPEG_DEC_PRIVATE *pmp3d, float *ptr, sf_count_t len)
|
||||
{ size_t done ;
|
||||
int error ;
|
||||
|
||||
error = mpg123_read (pmp3d->pmh, (unsigned char *) ptr, len * sizeof (float), &done) ;
|
||||
|
||||
if (error == MPG123_OK)
|
||||
return done / sizeof (float) ;
|
||||
|
||||
if (error == MPG123_DONE)
|
||||
return 0 ;
|
||||
|
||||
if (error == MPG123_NEW_FORMAT)
|
||||
{ psf->error = SFE_MALFORMED_FILE ;
|
||||
return -1 ;
|
||||
} ;
|
||||
|
||||
psf->error = SFE_INTERNAL ;
|
||||
return -1 ;
|
||||
} /* mpeg_dec_decode */
|
||||
|
||||
static inline void
|
||||
f2s_array (const float *src, int count, short *dest)
|
||||
{ while (--count >= 0)
|
||||
{ dest [count] = psf_lrintf (src [count] * (float) 0x7FFF) ;
|
||||
} ;
|
||||
} /* f2s_array */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_dec_read_s (SF_PRIVATE *psf, short *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
sf_count_t done ;
|
||||
const sf_count_t buflen = ARRAY_LEN (ubuf.fbuf) ;
|
||||
|
||||
while (len > 0)
|
||||
{ done = mpeg_dec_decode (psf, pmp3d, ubuf.fbuf, SF_MIN (buflen, len)) ;
|
||||
|
||||
if (done <= 0)
|
||||
break ;
|
||||
|
||||
f2s_array (ubuf.fbuf, done, ptr + total) ;
|
||||
|
||||
total += done ;
|
||||
len -= done ;
|
||||
}
|
||||
|
||||
return total ;
|
||||
} /*mpeg_dec_read_s */
|
||||
|
||||
static inline void
|
||||
f2i_array (const float *src, int count, int *dest)
|
||||
{ while (--count >= 0)
|
||||
{ dest [count] = psf_lrintf (src [count] * (float) 0x7FFFFFFF) ;
|
||||
} ;
|
||||
} /* f2i_array */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_dec_read_i (SF_PRIVATE *psf, int *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
sf_count_t done ;
|
||||
const sf_count_t buflen = ARRAY_LEN (ubuf.fbuf) ;
|
||||
|
||||
while (len > 0)
|
||||
{ done = mpeg_dec_decode (psf, pmp3d, ubuf.fbuf, SF_MIN (buflen, len)) ;
|
||||
|
||||
if (done <= 0)
|
||||
break ;
|
||||
|
||||
f2i_array (ubuf.fbuf, done, ptr + total) ;
|
||||
|
||||
total += done ;
|
||||
len -= done ;
|
||||
}
|
||||
|
||||
return total ;
|
||||
} /* mpeg_dec_read_i */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_dec_read_f (SF_PRIVATE *psf, float *ptr, sf_count_t len)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
sf_count_t done ;
|
||||
sf_count_t count ;
|
||||
|
||||
done = mpeg_dec_decode (psf, pmp3d, ptr, len) ;
|
||||
|
||||
if (done <= 0)
|
||||
return 0 ;
|
||||
|
||||
if (psf->norm_float == SF_FALSE)
|
||||
{ count = done ;
|
||||
while (--count >= 0)
|
||||
{ ptr [count] *= (double) 0x8000 ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
return done ;
|
||||
} /* mpeg_dec_read_f */
|
||||
|
||||
static inline void
|
||||
f2d_array (const float *src, int count, double *dest, double normfact)
|
||||
{ while (--count >= 0)
|
||||
{ dest [count] = src [count] * normfact ;
|
||||
}
|
||||
} /* f2d_array */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_dec_read_d (SF_PRIVATE *psf, double *ptr, sf_count_t len)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
sf_count_t done ;
|
||||
double normfact ;
|
||||
|
||||
normfact = (psf->norm_double == SF_TRUE) ? 1.0 : (double) 0x8000 ;
|
||||
|
||||
done = mpeg_dec_decode (psf, pmp3d, (float *) ptr, len) ;
|
||||
|
||||
if (done <= 0)
|
||||
return 0 ;
|
||||
|
||||
f2d_array ((float *) ptr, done, ptr, normfact) ;
|
||||
|
||||
return done ;
|
||||
} /* mpeg_dec_read_d */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_dec_seek (SF_PRIVATE *psf, int mode, sf_count_t count)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
off_t ret ;
|
||||
|
||||
if (mode != SFM_READ || psf->file.mode != SFM_READ)
|
||||
{ psf->error = SFE_BAD_SEEK ;
|
||||
return PSF_SEEK_ERROR ;
|
||||
} ;
|
||||
|
||||
ret = mpg123_seek (pmp3d->pmh, count, SEEK_SET) ;
|
||||
|
||||
if (ret < 0)
|
||||
return PSF_SEEK_ERROR ;
|
||||
|
||||
return (sf_count_t) ret ;
|
||||
} /* mpeg_dec_seek */
|
||||
|
||||
static int
|
||||
mpeg_dec_fill_sfinfo (mpg123_handle *mh, SF_INFO *info)
|
||||
{ int error ;
|
||||
int channels ;
|
||||
int encoding ;
|
||||
long rate ;
|
||||
off_t length ;
|
||||
|
||||
error = mpg123_getformat (mh, &rate, &channels, &encoding) ;
|
||||
if (error != MPG123_OK)
|
||||
return error ;
|
||||
|
||||
info->samplerate = rate ;
|
||||
info->channels = channels ;
|
||||
|
||||
length = mpg123_length (mh) ;
|
||||
if (length >= 0)
|
||||
{ info->frames = length ;
|
||||
info->seekable = SF_TRUE ;
|
||||
}
|
||||
else
|
||||
{ info->frames = SF_COUNT_MAX ;
|
||||
info->seekable = SF_FALSE ;
|
||||
}
|
||||
|
||||
/* Force 32-bit float samples. */
|
||||
if (encoding != MPG123_ENC_FLOAT_32)
|
||||
{ error = mpg123_format (mh, rate, channels, MPG123_ENC_FLOAT_32) ;
|
||||
} ;
|
||||
|
||||
return error ;
|
||||
} /* mpeg_dec_fill_sfinfo */
|
||||
|
||||
static void
|
||||
mpeg_dec_print_frameinfo (SF_PRIVATE *psf, const struct mpg123_frameinfo *fi)
|
||||
{ psf_log_printf (psf, "MPEG-1/2 Audio\n----------------------------------------\n") ;
|
||||
psf_log_printf (psf, " version: %s\n",
|
||||
fi->version == MPG123_1_0 ? "MPEG 1.0" :
|
||||
fi->version == MPG123_2_0 ? "MPEG 2.0" :
|
||||
fi->version == MPG123_2_5 ? "MPEG 2.5" : "?") ;
|
||||
psf_log_printf (psf, " layer: %d\n", fi->layer) ;
|
||||
psf_log_printf (psf, " rate: %d\n", fi->rate) ;
|
||||
psf_log_printf (psf, " mode: %s\n",
|
||||
fi->mode == MPG123_M_STEREO ? "stereo" :
|
||||
fi->mode == MPG123_M_JOINT ? "joint stereo" :
|
||||
fi->mode == MPG123_M_DUAL ? "dual channel" :
|
||||
fi->mode == MPG123_M_MONO ? "mono" : "?") ;
|
||||
psf_log_printf (psf, " mode ext: %d\n", fi->mode_ext) ;
|
||||
psf_log_printf (psf, " framesize: %d\n", fi->framesize) ;
|
||||
psf_log_printf (psf, " crc: %c\n", fi->flags & MPG123_CRC ? '1' : '0') ;
|
||||
psf_log_printf (psf, " copyright flag: %c\n", fi->flags & MPG123_COPYRIGHT ? '1' : '0') ;
|
||||
psf_log_printf (psf, " private flag: %c\n", fi->flags & MPG123_PRIVATE ? '1' : '0') ;
|
||||
psf_log_printf (psf, " original flag: %c\n", fi->flags & MPG123_ORIGINAL ? '1' : '0') ;
|
||||
psf_log_printf (psf, " emphasis: %d\n", fi->emphasis) ;
|
||||
psf_log_printf (psf, " bitrate mode: ") ;
|
||||
switch (fi->vbr)
|
||||
{ case MPG123_CBR :
|
||||
psf_log_printf (psf, "constant\n") ;
|
||||
break ;
|
||||
case MPG123_VBR :
|
||||
psf_log_printf (psf, "variable\n") ;
|
||||
break ;
|
||||
|
||||
case MPG123_ABR :
|
||||
psf_log_printf (psf, "average\n") ;
|
||||
psf_log_printf (psf, " ABR target: %d\n", fi->abr_rate) ;
|
||||
break ;
|
||||
} ;
|
||||
psf_log_printf (psf, " bitrate: %d kbps\n", fi->bitrate) ;
|
||||
} /* mpeg_dec_print_frameinfo */
|
||||
|
||||
/*
|
||||
* Like strlcpy, except the size argument is the maximum size of the input,
|
||||
* always null terminates the output string. Thus, up to size + 1 bytes may be
|
||||
* written.
|
||||
*
|
||||
* Returns the length of the copied string.
|
||||
*/
|
||||
static int
|
||||
strcpy_inbounded (char *dest, size_t size, const char *src)
|
||||
{ char *c = memccpy (dest, src, '\0', size) ;
|
||||
if (!c)
|
||||
c = dest + size ;
|
||||
*c = '\0' ;
|
||||
return c - dest ;
|
||||
} /* strcpy_inbounded */
|
||||
|
||||
static void
|
||||
mpeg_decoder_read_strings_id3v1 (SF_PRIVATE *psf, mpg123_id3v1 *tags)
|
||||
{ const char *genre ;
|
||||
char buf [31] ;
|
||||
|
||||
psf_log_printf (psf, "ID3v1 Tags\n") ;
|
||||
|
||||
if (strcpy_inbounded (buf, ARRAY_LEN (tags->title), tags->title))
|
||||
{ psf_log_printf (psf, " Title : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_TITLE, buf) ;
|
||||
} ;
|
||||
|
||||
if (strcpy_inbounded (buf, ARRAY_LEN (tags->artist), tags->artist))
|
||||
{ psf_log_printf (psf, " Artist : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_ARTIST, buf) ;
|
||||
} ;
|
||||
|
||||
if (strcpy_inbounded (buf, ARRAY_LEN (tags->album), tags->album))
|
||||
{ psf_log_printf (psf, " Album : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_ALBUM, buf) ;
|
||||
} ;
|
||||
|
||||
if (strcpy_inbounded (buf, ARRAY_LEN (tags->year), tags->year))
|
||||
{ psf_log_printf (psf, " Year : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_DATE, buf) ;
|
||||
} ;
|
||||
|
||||
if (strcpy_inbounded (buf, ARRAY_LEN (tags->comment), tags->comment))
|
||||
{ psf_log_printf (psf, " Comment : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_COMMENT, buf) ;
|
||||
} ;
|
||||
|
||||
/* ID3v1.1 Tracknumber */
|
||||
if (tags->comment [28] == '\0' && tags->comment [29] != '\0')
|
||||
{ snprintf (buf, ARRAY_LEN (buf), "%hhu", (unsigned char) tags->comment [29]) ;
|
||||
psf_log_printf (psf, " Tracknumber : %s\n", buf) ;
|
||||
psf_store_string (psf, SF_STR_TRACKNUMBER, buf) ;
|
||||
} ;
|
||||
|
||||
if ((genre = id3_lookup_v1_genre (tags->genre)) != NULL)
|
||||
{ psf_log_printf (psf, " Genre : %s\n", genre) ;
|
||||
psf_store_string (psf, SF_STR_GENRE, genre) ;
|
||||
} ;
|
||||
} /* mpeg_decoder_read_strings_id3v1 */
|
||||
|
||||
static void
|
||||
mpeg_decoder_read_strings_id3v2 (SF_PRIVATE *psf, mpg123_id3v2 *tags)
|
||||
{ mpg123_text *text_frame ;
|
||||
size_t i ;
|
||||
uint32_t marker ;
|
||||
const char *title = NULL ;
|
||||
const char *copyright = NULL ;
|
||||
const char *software = NULL ;
|
||||
const char *artist = NULL ;
|
||||
const char *comment = NULL ;
|
||||
const char *date = NULL ;
|
||||
const char *album = NULL ;
|
||||
const char *license = NULL ;
|
||||
const char *tracknumber = NULL ;
|
||||
const char *genre = NULL ;
|
||||
const char *tlen = NULL ;
|
||||
|
||||
psf_log_printf (psf, "ID3v2 Tags\n") ;
|
||||
|
||||
// Read the parsed text tags
|
||||
for (i = 0 ; i < tags->texts ; i++)
|
||||
{ text_frame = &tags->text [i] ;
|
||||
psf_log_printf (psf, " %.4s : %s\n", text_frame->id, text_frame->text.p) ;
|
||||
|
||||
// Thankfully mpg123 translates v2.2 3-byte frames to v2.3 4-byte for us.
|
||||
marker = MAKE_MARKER (text_frame->id [0], text_frame->id [1],
|
||||
text_frame->id [2], text_frame->id [3]) ;
|
||||
|
||||
/* Use our own map of frame types to metadata for text frames */
|
||||
switch (marker)
|
||||
{ case MAKE_MARKER ('T', 'I', 'T', '2') :
|
||||
title = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'C', 'O', 'P') :
|
||||
copyright = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'E', 'N', 'C') :
|
||||
case MAKE_MARKER ('T', 'S', 'S', 'E') :
|
||||
software = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'P', 'E', '1') :
|
||||
artist = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'A', 'L', 'B') :
|
||||
album = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'R', 'C', 'K') :
|
||||
tracknumber = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'Y', 'E', 'R') :
|
||||
case MAKE_MARKER ('T', 'D', 'R', 'C') :
|
||||
/* TODO (maybe)
|
||||
case MAKE_MARKER ('T', 'D', 'A', 'T') :
|
||||
case MAKE_MARKER ('T', 'I', 'M', 'E') :
|
||||
case MAKE_MARKER ('T', 'D', 'R', 'A') :
|
||||
*/
|
||||
date = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'O', 'W', 'N') :
|
||||
tracknumber = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'C', 'O', 'N') :
|
||||
genre = text_frame->text.p ;
|
||||
break ;
|
||||
|
||||
case MAKE_MARKER ('T', 'L', 'E', 'N') :
|
||||
tlen = text_frame->text.p ;
|
||||
break ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
/* Use mpg123's handling of comment headers, but print all the comment headers anyways. */
|
||||
if (tags->comment)
|
||||
comment = tags->comment->p ;
|
||||
for (i = 0 ; i < tags->comments ; i++)
|
||||
{ text_frame = &tags->comment_list [i] ;
|
||||
psf_log_printf (psf, " %.4s : (%s)[%s] %s\n", text_frame->id,
|
||||
text_frame->description. p, text_frame->lang, text_frame->text.p) ;
|
||||
} ;
|
||||
|
||||
/* Print extra headers */
|
||||
for (i = 0 ; i < tags->extras ; i++)
|
||||
{ text_frame = &tags->extra [i] ;
|
||||
psf_log_printf (psf, " %.4s : (%s) %s\n", text_frame->id,
|
||||
text_frame->description.p, text_frame->text.p) ;
|
||||
} ;
|
||||
|
||||
if (title)
|
||||
psf_store_string (psf, SF_STR_TITLE, title) ;
|
||||
if (copyright)
|
||||
psf_store_string (psf, SF_STR_COPYRIGHT, copyright) ;
|
||||
if (software)
|
||||
psf_store_string (psf, SF_STR_SOFTWARE, software) ;
|
||||
if (artist)
|
||||
psf_store_string (psf, SF_STR_ARTIST, artist) ;
|
||||
if (comment)
|
||||
psf_store_string (psf, SF_STR_COMMENT, comment) ;
|
||||
if (date)
|
||||
psf_store_string (psf, SF_STR_DATE, date) ;
|
||||
if (album)
|
||||
psf_store_string (psf, SF_STR_ALBUM, album) ;
|
||||
if (license)
|
||||
psf_store_string (psf, SF_STR_LICENSE, license) ;
|
||||
if (tracknumber)
|
||||
psf_store_string (psf, SF_STR_TRACKNUMBER, tracknumber) ;
|
||||
if (genre)
|
||||
psf_store_string (psf, SF_STR_GENRE, id3_process_v2_genre (genre)) ;
|
||||
if (tlen)
|
||||
{ /* If non-seekable, set framecount? Can we trust it? */
|
||||
} ;
|
||||
} /* mpeg_decoder_read_strings_id3v2 */
|
||||
|
||||
static void
|
||||
mpeg_decoder_read_strings (SF_PRIVATE *psf)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
mpg123_id3v1 *v1_tags ;
|
||||
mpg123_id3v2 *v2_tags ;
|
||||
|
||||
if (mpg123_id3 (pmp3d->pmh, &v1_tags, &v2_tags) != MPG123_OK)
|
||||
return ;
|
||||
|
||||
if (v1_tags != NULL)
|
||||
mpeg_decoder_read_strings_id3v1 (psf, v1_tags) ;
|
||||
|
||||
if (v2_tags != NULL)
|
||||
mpeg_decoder_read_strings_id3v2 (psf, v2_tags) ;
|
||||
} /* mpeg_decoder_read_strings */
|
||||
|
||||
static int
|
||||
mpeg_dec_byterate (SF_PRIVATE *psf)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
struct mpg123_frameinfo fi ;
|
||||
|
||||
if (mpg123_info (pmp3d->pmh, &fi) == MPG123_OK)
|
||||
return (fi.bitrate + 7) / 8 ;
|
||||
|
||||
return -1 ;
|
||||
|
||||
} /* mpeg_dec_byterate */
|
||||
|
||||
/*==============================================================================
|
||||
** exported functions
|
||||
*/
|
||||
|
||||
int
|
||||
mpeg_decoder_init (SF_PRIVATE *psf)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d ;
|
||||
struct mpg123_frameinfo fi ;
|
||||
int error ;
|
||||
|
||||
if (! (psf->file.mode & SFM_READ))
|
||||
return SFE_INTERNAL ;
|
||||
|
||||
/*
|
||||
** Check to see if we have already successfully opened the file when we
|
||||
** guessed it was an MP3 based on seeing an ID3 header.
|
||||
*/
|
||||
if (psf->codec_data != NULL &&
|
||||
(SF_CODEC (psf->sf.format) == SF_FORMAT_MPEG_LAYER_I
|
||||
|| SF_CODEC (psf->sf.format) == SF_FORMAT_MPEG_LAYER_II
|
||||
|| SF_CODEC (psf->sf.format) == SF_FORMAT_MPEG_LAYER_III)
|
||||
&& ((MPEG_DEC_PRIVATE *) psf->codec_data)->unique_id == psf->unique_id)
|
||||
return 0 ;
|
||||
|
||||
/*
|
||||
** *** FIXME - Threading issues ***
|
||||
**
|
||||
** mpg123_init() is a global call that should only be called once, and
|
||||
** should be paried with mpg123_exit() when done. libsndfile does not
|
||||
** provide for these requirements.
|
||||
**
|
||||
** Currently this is a moot issue as mpg123_init() non-conditionally writes
|
||||
** static areas with calculated data, and mpg123_exit() is a NOP, but this
|
||||
** could change in a future version of it!
|
||||
**
|
||||
** From mpg123.h:
|
||||
** > This should be called once in a non-parallel context. It is not explicitly
|
||||
** > thread-safe, but repeated/concurrent calls still _should_ be safe as static
|
||||
** > tables are filled with the same values anyway.
|
||||
**
|
||||
** Note that calling mpg123_init() after it has already completed is a NOP.
|
||||
*/
|
||||
if (mpg123_init () != MPG123_OK)
|
||||
return SFE_INTERNAL ;
|
||||
|
||||
psf->codec_data = pmp3d = calloc (1, sizeof (MPEG_DEC_PRIVATE)) ;
|
||||
if (!psf->codec_data)
|
||||
return SFE_MALLOC_FAILED ;
|
||||
|
||||
pmp3d->pmh = mpg123_new (NULL, &error) ;
|
||||
if (!pmp3d->pmh)
|
||||
{ psf_log_printf (psf, "Could not obtain a mpg123 handle: %s\n", mpg123_plain_strerror (error)) ;
|
||||
return SFE_INTERNAL ;
|
||||
} ;
|
||||
|
||||
psf->codec_close = mpeg_dec_close ;
|
||||
|
||||
mpg123_replace_reader_handle (pmp3d->pmh,
|
||||
mpeg_dec_io_read, mpeg_dec_io_lseek, NULL) ;
|
||||
|
||||
mpg123_param (pmp3d->pmh, MPG123_REMOVE_FLAGS, MPG123_AUTO_RESAMPLE, 1.0) ;
|
||||
mpg123_param (pmp3d->pmh, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT | MPG123_GAPLESS, 1.0) ;
|
||||
#if MPG123_API_VERSION >= 45
|
||||
mpg123_param (pmp3d->pmh, MPG123_ADD_FLAGS, MPG123_NO_FRANKENSTEIN, 1.0) ;
|
||||
#endif
|
||||
|
||||
psf->dataoffset = 0 ;
|
||||
|
||||
if (psf->is_pipe)
|
||||
{ // Need to read data in the binheader buffer. Avoid a file seek.
|
||||
psf_binheader_readf (psf, "p", 0) ;
|
||||
pmp3d->header_remaining = psf_binheader_readf (psf, NULL) ;
|
||||
mpg123_param (pmp3d->pmh, MPG123_ADD_FLAGS, MPG123_NO_PEEK_END, 1.0) ;
|
||||
}
|
||||
else
|
||||
psf_fseek (psf, 0, SEEK_SET) ;
|
||||
|
||||
error = mpg123_open_handle (pmp3d->pmh, psf) ;
|
||||
if (error != MPG123_OK)
|
||||
{ psf_log_printf (psf, "mpg123 could not open the file: %s\n", mpg123_plain_strerror (error)) ;
|
||||
return SFE_BAD_FILE ;
|
||||
} ;
|
||||
|
||||
if (mpeg_dec_fill_sfinfo (pmp3d->pmh, &psf->sf) != MPG123_OK)
|
||||
{ psf_log_printf (psf, "Cannot get MPEG decoder configuration: %s\n", mpg123_plain_strerror (error)) ;
|
||||
return SFE_BAD_FILE ;
|
||||
} ;
|
||||
|
||||
error = mpg123_info (pmp3d->pmh, &fi) ;
|
||||
if (error != MPG123_OK)
|
||||
{ psf_log_printf (psf, "Cannot get MPEG frame info: %s\n", mpg123_plain_strerror (error)) ;
|
||||
return SFE_INTERNAL ;
|
||||
}
|
||||
|
||||
switch (fi.layer)
|
||||
{ case 1 : psf->sf.format |= SF_FORMAT_MPEG_LAYER_I ; break ;
|
||||
case 2 : psf->sf.format |= SF_FORMAT_MPEG_LAYER_II ; break ;
|
||||
case 3 : psf->sf.format |= SF_FORMAT_MPEG_LAYER_III ; break ;
|
||||
default :
|
||||
return SFE_BAD_FILE ;
|
||||
} ;
|
||||
mpeg_dec_print_frameinfo (psf, &fi) ;
|
||||
|
||||
psf->read_short = mpeg_dec_read_s ;
|
||||
psf->read_int = mpeg_dec_read_i ;
|
||||
psf->read_float = mpeg_dec_read_f ;
|
||||
psf->read_double = mpeg_dec_read_d ;
|
||||
psf->seek = mpeg_dec_seek ;
|
||||
psf->byterate = mpeg_dec_byterate ;
|
||||
|
||||
mpeg_decoder_read_strings (psf) ;
|
||||
|
||||
if (psf->filelength != SF_COUNT_MAX)
|
||||
psf->datalength = psf->filelength - psf->dataoffset ;
|
||||
else
|
||||
psf->datalength = SF_COUNT_MAX ;
|
||||
|
||||
pmp3d->unique_id = psf->unique_id ;
|
||||
return 0 ;
|
||||
} /* mpeg_decoder_init */
|
||||
|
||||
int
|
||||
mpeg_decoder_get_bitrate_mode (SF_PRIVATE *psf)
|
||||
{ MPEG_DEC_PRIVATE *pmp3d = (MPEG_DEC_PRIVATE *) psf->codec_data ;
|
||||
struct mpg123_frameinfo fi ;
|
||||
|
||||
if (mpg123_info (pmp3d->pmh, &fi) == MPG123_OK)
|
||||
{
|
||||
switch (fi.vbr)
|
||||
{ case MPG123_CBR : return SF_BITRATE_MODE_CONSTANT ;
|
||||
case MPG123_ABR : return SF_BITRATE_MODE_AVERAGE ;
|
||||
case MPG123_VBR : return SF_BITRATE_MODE_VARIABLE ;
|
||||
default : break ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
psf_log_printf (psf, "Cannot determine MPEG bitrate mode.\n") ;
|
||||
return -1 ;
|
||||
} /* mpeg_decoder_get_bitrate_mode */
|
||||
|
||||
#else /* HAVE_MPEG */
|
||||
|
||||
int mpeg_decoder_init (SF_PRIVATE *psf)
|
||||
{ psf_log_printf (psf, "This version of libsndfile was compiled without MPEG decode support.\n") ;
|
||||
return SFE_UNIMPLEMENTED ;
|
||||
} /* mpeg_decoder_init */
|
||||
|
||||
#endif
|
784
src/mpeg_l3_encode.c
Normal file
784
src/mpeg_l3_encode.c
Normal file
@ -0,0 +1,784 @@
|
||||
/*
|
||||
** Copyright (C) 2020 Arthur Taylor <art@ified.ca>
|
||||
** Copyright (C) 2019 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 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 "sndfile.h"
|
||||
#include "common.h"
|
||||
#include "mpeg.h"
|
||||
|
||||
|
||||
#if HAVE_MPEG
|
||||
|
||||
#include <lame/lame.h>
|
||||
|
||||
/*
|
||||
* RANT RANT RANT
|
||||
*
|
||||
* Lame has 11 functions for inputing sample data of various types and
|
||||
* configurations, but due to bad definitions, or missing combinations, they
|
||||
* aren't really of much help to us.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{ lame_t lamef ;
|
||||
unsigned char *block ;
|
||||
size_t block_len ;
|
||||
int frame_samples ;
|
||||
double compression ;
|
||||
int initialized : 1 ;
|
||||
} MPEG_L3_ENC_PRIVATE ;
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------------
|
||||
** Private function prototypes.
|
||||
*/
|
||||
|
||||
static int mpeg_l3_encoder_close (SF_PRIVATE *psf) ;
|
||||
static int mpeg_l3_encoder_construct (SF_PRIVATE *psf) ;
|
||||
static int mpeg_l3_encoder_byterate (SF_PRIVATE *psf) ;
|
||||
|
||||
static sf_count_t mpeg_l3_encode_write_short_stereo (SF_PRIVATE *psf, const short *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_int_stereo (SF_PRIVATE *psf, const int *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_float_stereo (SF_PRIVATE *psf, const float *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_double_stereo (SF_PRIVATE *psf, const double *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_short_mono (SF_PRIVATE *psf, const short *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_int_mono (SF_PRIVATE *psf, const int *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_float_mono (SF_PRIVATE *psf, const float *ptr, sf_count_t len) ;
|
||||
static sf_count_t mpeg_l3_encode_write_double_mono (SF_PRIVATE *psf, const double *ptr, sf_count_t len) ;
|
||||
|
||||
/*-----------------------------------------------------------------------------------------------
|
||||
** Exported functions.
|
||||
*/
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_init (SF_PRIVATE *psf, int info_tag)
|
||||
{ MPEG_L3_ENC_PRIVATE* pmpeg = NULL ;
|
||||
|
||||
if (psf->file.mode == SFM_RDWR)
|
||||
return SFE_BAD_MODE_RW ;
|
||||
|
||||
if (psf->file.mode != SFM_WRITE)
|
||||
return SFE_INTERNAL ;
|
||||
|
||||
psf->codec_data = pmpeg = calloc (1, sizeof (MPEG_L3_ENC_PRIVATE)) ;
|
||||
if (!pmpeg)
|
||||
return SFE_MALLOC_FAILED ;
|
||||
|
||||
if (psf->sf.channels < 1 || psf->sf.channels > 2)
|
||||
return SFE_BAD_OPEN_FORMAT ;
|
||||
|
||||
if (! (pmpeg->lamef = lame_init ()))
|
||||
return SFE_MALLOC_FAILED ;
|
||||
|
||||
pmpeg->compression = -1.0 ; /* Unset */
|
||||
|
||||
lame_set_in_samplerate (pmpeg->lamef, psf->sf.samplerate) ;
|
||||
lame_set_num_channels (pmpeg->lamef, psf->sf.channels) ;
|
||||
if (lame_set_out_samplerate (pmpeg->lamef, psf->sf.samplerate) < 0)
|
||||
return SFE_MPEG_BAD_SAMPLERATE ;
|
||||
|
||||
lame_set_write_id3tag_automatic (pmpeg->lamef, 0) ;
|
||||
|
||||
if (!info_tag || psf->is_pipe)
|
||||
{ /* Can't seek back, so force disable Xing/Lame/Info header. */
|
||||
lame_set_bWriteVbrTag (pmpeg->lamef, 0) ;
|
||||
} ;
|
||||
|
||||
if (psf->sf.channels == 2)
|
||||
{ psf->write_short = mpeg_l3_encode_write_short_stereo ;
|
||||
psf->write_int = mpeg_l3_encode_write_int_stereo ;
|
||||
psf->write_float = mpeg_l3_encode_write_float_stereo ;
|
||||
psf->write_double = mpeg_l3_encode_write_double_stereo ;
|
||||
}
|
||||
else
|
||||
{ psf->write_short = mpeg_l3_encode_write_short_mono ;
|
||||
psf->write_int = mpeg_l3_encode_write_int_mono ;
|
||||
psf->write_float = mpeg_l3_encode_write_float_mono ;
|
||||
psf->write_double = mpeg_l3_encode_write_double_mono ;
|
||||
}
|
||||
|
||||
psf->sf.seekable = 0 ;
|
||||
psf->codec_close = mpeg_l3_encoder_close ;
|
||||
psf->byterate = mpeg_l3_encoder_byterate ;
|
||||
psf->dataoffset = 0 ;
|
||||
psf->datalength = 0 ;
|
||||
|
||||
return 0 ;
|
||||
} /* mpeg_l3_encoder_init */
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_write_id3tag (SF_PRIVATE *psf)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
unsigned char *id3v2_buffer ;
|
||||
int i, id3v2_size ;
|
||||
|
||||
if (psf->have_written)
|
||||
return 0 ;
|
||||
|
||||
if ((i = mpeg_l3_encoder_construct (psf)))
|
||||
return i ;
|
||||
|
||||
if (psf_fseek (psf, 0, SEEK_SET) != 0)
|
||||
return SFE_NOT_SEEKABLE ;
|
||||
|
||||
/* Safe to call multiple times. */
|
||||
id3tag_init (pmpeg->lamef) ;
|
||||
|
||||
for (i = 0 ; i < SF_MAX_STRINGS ; i++)
|
||||
{ switch (psf->strings.data [i].type)
|
||||
{ case SF_STR_TITLE :
|
||||
id3tag_set_title (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_ARTIST :
|
||||
id3tag_set_artist (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_ALBUM :
|
||||
id3tag_set_album (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_DATE :
|
||||
id3tag_set_year (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_COMMENT :
|
||||
id3tag_set_comment (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_GENRE :
|
||||
id3tag_set_genre (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
case SF_STR_TRACKNUMBER :
|
||||
id3tag_set_track (pmpeg->lamef, psf->strings.storage + psf->strings.data [i].offset) ;
|
||||
break ;
|
||||
|
||||
default:
|
||||
break ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
/* The header in this case is the ID3v2 tag header. */
|
||||
id3v2_size = lame_get_id3v2_tag (pmpeg->lamef, 0, 0) ;
|
||||
if (id3v2_size > 0)
|
||||
{ psf_log_printf (psf, "Writing ID3v2 header.\n") ;
|
||||
if (! (id3v2_buffer = malloc (id3v2_size)))
|
||||
return SFE_MALLOC_FAILED ;
|
||||
lame_get_id3v2_tag (pmpeg->lamef, id3v2_buffer, id3v2_size) ;
|
||||
psf_fwrite (id3v2_buffer, 1, id3v2_size, psf) ;
|
||||
psf->dataoffset = id3v2_size ;
|
||||
free (id3v2_buffer) ;
|
||||
} ;
|
||||
|
||||
return 0 ;
|
||||
}
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_set_quality (SF_PRIVATE *psf, double compression)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
int bitrate_mode ;
|
||||
int bitrate ;
|
||||
int ret ;
|
||||
|
||||
if (compression < 0.0 || compression > 1.0)
|
||||
return SF_FALSE ;
|
||||
|
||||
/*
|
||||
** Save the compression setting, as we may have to re-interpret it if
|
||||
** the bitrate mode changes.
|
||||
*/
|
||||
pmpeg->compression = compression ;
|
||||
|
||||
bitrate_mode = mpeg_l3_encoder_get_bitrate_mode (psf) ;
|
||||
if (bitrate_mode == SF_BITRATE_MODE_VARIABLE)
|
||||
{ ret = lame_set_VBR_quality (pmpeg->lamef, compression * 10.0) ;
|
||||
}
|
||||
else
|
||||
{ /* Choose a bitrate. */
|
||||
if (psf->sf.samplerate >= 32000)
|
||||
{ /* MPEG-1.0, bitrates are [32,320] kbps */
|
||||
bitrate = (320.0 - (compression * (320.0 - 32.0))) ;
|
||||
}
|
||||
else if (psf->sf.samplerate >= 16000)
|
||||
{ /* MPEG-2.0, bitrates are [8,160] */
|
||||
bitrate = (160.0 - (compression * (160.0 - 8.0))) ;
|
||||
}
|
||||
else
|
||||
{ /* MPEG-2.5, bitrates are [8,64] */
|
||||
bitrate = (64.0 - (compression * (64.0 - 8.0))) ;
|
||||
}
|
||||
|
||||
if (bitrate_mode == SF_BITRATE_MODE_AVERAGE)
|
||||
ret = lame_set_VBR_mean_bitrate_kbps (pmpeg->lamef, bitrate) ;
|
||||
else
|
||||
ret = lame_set_brate (pmpeg->lamef, bitrate) ;
|
||||
} ;
|
||||
|
||||
if (ret == LAME_OKAY)
|
||||
return SF_TRUE ;
|
||||
|
||||
psf_log_printf (psf, "Failed to set lame encoder quality.\n") ;
|
||||
return SF_FALSE ;
|
||||
} /* mpeg_l3_encoder_set_quality */
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_set_bitrate_mode (SF_PRIVATE *psf, int mode)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
enum vbr_mode_e vbr_mode ;
|
||||
|
||||
if (pmpeg->initialized)
|
||||
{ psf->error = SFE_CMD_HAS_DATA ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
|
||||
switch (mode)
|
||||
{ case SF_BITRATE_MODE_CONSTANT : vbr_mode = vbr_off ; break ;
|
||||
case SF_BITRATE_MODE_AVERAGE : vbr_mode = vbr_abr ; break ;
|
||||
case SF_BITRATE_MODE_VARIABLE : vbr_mode = vbr_default ; break ;
|
||||
default :
|
||||
psf->error = SFE_BAD_COMMAND_PARAM ;
|
||||
return SF_FALSE ;
|
||||
} ;
|
||||
|
||||
if (lame_set_VBR (pmpeg->lamef, vbr_mode) == LAME_OKAY)
|
||||
{ /* Re-evaluate the compression setting. */
|
||||
return mpeg_l3_encoder_set_quality (psf, pmpeg->compression) ;
|
||||
} ;
|
||||
|
||||
psf_log_printf (psf, "Failed to set LAME vbr mode to %d.\n", vbr_mode) ;
|
||||
return SF_FALSE ;
|
||||
} /* mpeg_l3_encoder_set_bitrate_mode */
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_get_bitrate_mode (SF_PRIVATE *psf)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
enum vbr_mode_e vbr_mode ;
|
||||
|
||||
vbr_mode = lame_get_VBR (pmpeg->lamef) ;
|
||||
|
||||
if (vbr_mode == vbr_off)
|
||||
return SF_BITRATE_MODE_CONSTANT ;
|
||||
if (vbr_mode == vbr_abr)
|
||||
return SF_BITRATE_MODE_AVERAGE ;
|
||||
if (vbr_mode == vbr_default || vbr_mode < vbr_max_indicator)
|
||||
return SF_BITRATE_MODE_VARIABLE ;
|
||||
|
||||
/* Something is wrong. */
|
||||
psf->error = SFE_INTERNAL ;
|
||||
return -1 ;
|
||||
} /* mpeg_l3_encoder_get_bitrate_mode */
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------------------------------------
|
||||
** Private functions.
|
||||
*/
|
||||
|
||||
static int
|
||||
mpeg_l3_encoder_close (SF_PRIVATE *psf)
|
||||
{ MPEG_L3_ENC_PRIVATE* pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
int ret, len ;
|
||||
sf_count_t pos ;
|
||||
unsigned char *buffer ;
|
||||
|
||||
/* Magic number 7200 comes from a comment in lame.h */
|
||||
len = 7200 ;
|
||||
if (! (buffer = malloc (len)))
|
||||
return SFE_MALLOC_FAILED ;
|
||||
ret = lame_encode_flush (pmpeg->lamef, buffer, len) ;
|
||||
if (ret > 0)
|
||||
psf_fwrite (buffer, 1, ret, psf) ;
|
||||
|
||||
/*
|
||||
** Write an IDv1 trailer. The whole tag structure is always 128 bytes, so is
|
||||
** guaranteed to fit in the buffer allocated above.
|
||||
*/
|
||||
ret = lame_get_id3v1_tag (pmpeg->lamef, buffer, len) ;
|
||||
if (ret > 0)
|
||||
{ psf_log_printf (psf, " Writing ID3v1 trailer.\n") ;
|
||||
psf_fwrite (buffer, 1, ret, psf) ;
|
||||
} ;
|
||||
|
||||
/*
|
||||
** If possible, seek back and write the LAME/XING/Info headers. This
|
||||
** contains information about the whole file and a seek table, and can
|
||||
** only be written after encoding.
|
||||
**
|
||||
** If enabled, Lame wrote an empty header at the beginning of the data
|
||||
** that we now fill in.
|
||||
*/
|
||||
ret = lame_get_lametag_frame (pmpeg->lamef, 0, 0) ;
|
||||
if (ret > 0)
|
||||
{ if (ret > len)
|
||||
{ len = ret ;
|
||||
free (buffer) ;
|
||||
if (! (buffer = malloc (len)))
|
||||
return SFE_MALLOC_FAILED ;
|
||||
} ;
|
||||
psf_log_printf (psf, " Writing LAME info header at offset %d, %d bytes.\n",
|
||||
psf->dataoffset, len) ;
|
||||
lame_get_lametag_frame (pmpeg->lamef, buffer, len) ;
|
||||
pos = psf_ftell (psf) ;
|
||||
if (psf_fseek (psf, psf->dataoffset, SEEK_SET) == psf->dataoffset)
|
||||
{ psf_fwrite (buffer, 1, ret, psf) ;
|
||||
psf_fseek (psf, pos, SEEK_SET) ;
|
||||
} ;
|
||||
} ;
|
||||
free (buffer) ;
|
||||
|
||||
free (pmpeg->block) ;
|
||||
pmpeg->block = NULL ;
|
||||
|
||||
if (pmpeg->lamef)
|
||||
{ lame_close (pmpeg->lamef) ;
|
||||
pmpeg->lamef = NULL ;
|
||||
} ;
|
||||
|
||||
return 0 ;
|
||||
} /* mpeg_l3_encoder_close */
|
||||
|
||||
static void
|
||||
mpeg_l3_encoder_log_config (SF_PRIVATE *psf, lame_t lamef)
|
||||
{ const char *version ;
|
||||
const char *chn_mode ;
|
||||
|
||||
switch (lame_get_version (lamef))
|
||||
{ case 0 : version = "2" ; break ;
|
||||
case 1 : version = "1" ; break ;
|
||||
case 2 : version = "2.5" ; break ;
|
||||
default : version = "unknown!?" ; break ;
|
||||
} ;
|
||||
switch (lame_get_mode (lamef))
|
||||
{ case STEREO : chn_mode = "stereo" ; break ;
|
||||
case JOINT_STEREO : chn_mode = "joint-stereo" ; break ;
|
||||
case MONO : chn_mode = "mono" ; break ;
|
||||
default : chn_mode = "unknown!?" ; break ;
|
||||
} ;
|
||||
psf_log_printf (psf, " MPEG Version : %s\n", version) ;
|
||||
psf_log_printf (psf, " Channel mode : %s\n", chn_mode) ;
|
||||
psf_log_printf (psf, " Samplerate : %d\n", lame_get_out_samplerate (lamef)) ;
|
||||
psf_log_printf (psf, " Encoder mode : ") ;
|
||||
switch (lame_get_VBR (lamef))
|
||||
{ case vbr_off :
|
||||
psf_log_printf (psf, "CBR\n") ;
|
||||
psf_log_printf (psf, " Bitrate : %d kbps\n", lame_get_brate (lamef)) ;
|
||||
break ;
|
||||
case vbr_abr :
|
||||
psf_log_printf (psf, "ABR\n") ;
|
||||
psf_log_printf (psf, " Mean Bitrate : %d kbps\n", lame_get_VBR_mean_bitrate_kbps (lamef)) ;
|
||||
break ;
|
||||
|
||||
case vbr_mt :
|
||||
case vbr_default :
|
||||
psf_log_printf (psf, "VBR\n") ;
|
||||
psf_log_printf (psf, " Quality : %d\n", lame_get_VBR_q (lamef)) ;
|
||||
break ;
|
||||
|
||||
default:
|
||||
psf_log_printf (psf, "Unknown!? (%d)\n", lame_get_VBR (lamef)) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
psf_log_printf (psf, " Encoder delay : %d\n", lame_get_encoder_delay (lamef)) ;
|
||||
psf_log_printf (psf, " Write INFO header : %d\n", lame_get_bWriteVbrTag (lamef)) ;
|
||||
} /* mpeg_l3_encoder_log_config */
|
||||
|
||||
static int
|
||||
mpeg_l3_encoder_construct (SF_PRIVATE *psf)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
int frame_samples_per_channel ;
|
||||
|
||||
if (pmpeg->initialized == SF_FALSE)
|
||||
{ if (lame_init_params (pmpeg->lamef) < 0)
|
||||
{ psf_log_printf (psf, "Failed to initialize lame encoder!\n") ;
|
||||
return SFE_INTERNAL ;
|
||||
} ;
|
||||
|
||||
psf_log_printf (psf, "Initialized LAME encoder.\n") ;
|
||||
mpeg_l3_encoder_log_config (psf, pmpeg->lamef) ;
|
||||
|
||||
frame_samples_per_channel = lame_get_framesize (pmpeg->lamef) ;
|
||||
|
||||
/*
|
||||
* Suggested output buffer size in bytes from lame.h comment is
|
||||
* 1.25 * samples + 7200
|
||||
*/
|
||||
pmpeg->block_len = (frame_samples_per_channel * 4) / 3 + 7200 ;
|
||||
pmpeg->frame_samples = frame_samples_per_channel * psf->sf.channels ;
|
||||
|
||||
pmpeg->block = malloc (pmpeg->block_len) ;
|
||||
if (!pmpeg->block)
|
||||
return SFE_MALLOC_FAILED ;
|
||||
|
||||
pmpeg->initialized = SF_TRUE ;
|
||||
} ;
|
||||
|
||||
return 0 ;
|
||||
} /* mpeg_l3_encoder_construct */
|
||||
|
||||
static int
|
||||
mpeg_l3_encoder_byterate (SF_PRIVATE *psf)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE *) psf->codec_data ;
|
||||
int bitrate_mode ;
|
||||
int byterate ;
|
||||
float calculated_byterate ;
|
||||
|
||||
bitrate_mode = mpeg_l3_encoder_get_bitrate_mode (psf) ;
|
||||
byterate = (lame_get_brate (pmpeg->lamef) + 7) / 8 ;
|
||||
|
||||
if (bitrate_mode == SF_BITRATE_MODE_VARIABLE)
|
||||
{ /*
|
||||
** For VBR, lame_get_brate returns the minimum bitrate, so calculate the
|
||||
** average byterate so far.
|
||||
*/
|
||||
calculated_byterate = psf_ftell (psf) - psf->dataoffset ;
|
||||
calculated_byterate /= (float) psf->write_current ;
|
||||
calculated_byterate *= (float) psf->sf.samplerate ;
|
||||
|
||||
return SF_MIN (byterate, (int) calculated_byterate) ;
|
||||
}
|
||||
|
||||
return byterate ;
|
||||
} /* mpeg_l3_encoder_byterate */
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_short_mono (SF_PRIVATE *psf, const short *ptr, sf_count_t len)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, (sf_count_t) pmpeg->frame_samples) ;
|
||||
|
||||
nbytes = lame_encode_buffer (pmpeg->lamef, ptr + total, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_short_stereo (SF_PRIVATE *psf, const short *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
const sf_count_t max_samples = SF_MIN (ARRAY_LEN (ubuf.sbuf), pmpeg->frame_samples) ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, max_samples) ;
|
||||
/*
|
||||
* An oversight, but lame_encode_buffer_interleaved() lacks a const.
|
||||
* As such, need another memcpy to not cause a warning.
|
||||
*/
|
||||
memcpy (ubuf.sbuf, ptr + total, writecount) ;
|
||||
nbytes = lame_encode_buffer_interleaved (pmpeg->lamef, ubuf.sbuf, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_int_mono (SF_PRIVATE *psf, const int *ptr, sf_count_t len)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, (sf_count_t) pmpeg->frame_samples) ;
|
||||
|
||||
nbytes = lame_encode_buffer_int (pmpeg->lamef, ptr + total, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_int_stereo (SF_PRIVATE *psf, const int *ptr, sf_count_t len)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, (sf_count_t) pmpeg->frame_samples) ;
|
||||
|
||||
nbytes = lame_encode_buffer_interleaved_int (pmpeg->lamef, ptr + total, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_float_mono (SF_PRIVATE *psf, const float *ptr, sf_count_t len)
|
||||
{ MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, (sf_count_t) pmpeg->frame_samples) ;
|
||||
|
||||
if (psf->norm_float)
|
||||
nbytes = lame_encode_buffer_ieee_float (pmpeg->lamef, ptr + total, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
else
|
||||
nbytes = lame_encode_buffer_float (pmpeg->lamef, ptr + total, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
normalize_float (float *dest, const float *src, sf_count_t count, float norm_fact)
|
||||
{ while (--count >= 0)
|
||||
{ dest [count] = src [count] * norm_fact ;
|
||||
} ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_float_stereo (SF_PRIVATE *psf, const float *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
const sf_count_t max_samples = SF_MIN (ARRAY_LEN (ubuf.fbuf), pmpeg->frame_samples) ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, max_samples) ;
|
||||
|
||||
if (psf->norm_float)
|
||||
nbytes = lame_encode_buffer_interleaved_ieee_float (pmpeg->lamef, ptr + total, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
else
|
||||
{ /* Lame lacks a non-normalized interleaved float write. Bummer. */
|
||||
normalize_float (ubuf.fbuf, ptr + total, writecount, 1.0 / (float) 0x8000) ;
|
||||
nbytes = lame_encode_buffer_interleaved_ieee_float (pmpeg->lamef, ubuf.fbuf, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
}
|
||||
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
normalize_double (double *dest, const double *src, sf_count_t count, double norm_fact)
|
||||
{ while (--count >= 0)
|
||||
{ dest [count] = src [count] * norm_fact ;
|
||||
} ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_double_mono (SF_PRIVATE *psf, const double *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
const sf_count_t max_samples = SF_MIN (ARRAY_LEN (ubuf.dbuf), pmpeg->frame_samples) ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, max_samples) ;
|
||||
|
||||
if (psf->norm_double)
|
||||
nbytes = lame_encode_buffer_ieee_double (pmpeg->lamef, ptr + total, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
else
|
||||
{ /* Lame lacks non-normalized double writing */
|
||||
normalize_double (ubuf.dbuf, ptr + total, writecount, 1.0 / (double) 0x8000) ;
|
||||
nbytes = lame_encode_buffer_ieee_double (pmpeg->lamef, ubuf.dbuf, NULL, writecount, pmpeg->block, pmpeg->block_len) ;
|
||||
}
|
||||
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
|
||||
static sf_count_t
|
||||
mpeg_l3_encode_write_double_stereo (SF_PRIVATE *psf, const double *ptr, sf_count_t len)
|
||||
{ BUF_UNION ubuf ;
|
||||
MPEG_L3_ENC_PRIVATE *pmpeg = (MPEG_L3_ENC_PRIVATE*) psf->codec_data ;
|
||||
sf_count_t total = 0 ;
|
||||
int nbytes, writecount, writen ;
|
||||
const sf_count_t max_samples = SF_MIN (ARRAY_LEN (ubuf.dbuf), pmpeg->frame_samples) ;
|
||||
|
||||
if ((psf->error = mpeg_l3_encoder_construct (psf)))
|
||||
return 0 ;
|
||||
|
||||
while (len)
|
||||
{ writecount = SF_MIN (len, max_samples) ;
|
||||
|
||||
if (psf->norm_double)
|
||||
nbytes = lame_encode_buffer_interleaved_ieee_double (pmpeg->lamef, ptr + total, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
else
|
||||
{ /* Lame lacks interleaved non-normalized double writing */
|
||||
normalize_double (ubuf.dbuf, ptr + total, writecount, 1.0 / (double) 0x8000) ;
|
||||
nbytes = lame_encode_buffer_interleaved_ieee_double (pmpeg->lamef, ubuf.dbuf, writecount / 2, pmpeg->block, pmpeg->block_len) ;
|
||||
}
|
||||
|
||||
if (nbytes < 0)
|
||||
{ psf_log_printf (psf, "lame_encode_buffer returned %d\n", nbytes) ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (nbytes)
|
||||
{ writen = psf_fwrite (pmpeg->block, 1, nbytes, psf) ;
|
||||
if (writen != nbytes)
|
||||
{ psf_log_printf (psf, "*** Warning : short write (%d != %d).\n", writen, nbytes) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
total += writecount ;
|
||||
len -= writecount ;
|
||||
} ;
|
||||
|
||||
return total ;
|
||||
}
|
||||
|
||||
#else /* HAVE_MPEG */
|
||||
|
||||
int
|
||||
mpeg_l3_encoder_init (SF_PRIVATE *psf, int UNUSED (vbr))
|
||||
{ psf_log_printf (psf, "This version of libsndfile was compiled without MPEG Layer 3 encoding support.\n") ;
|
||||
return SFE_UNIMPLEMENTED ;
|
||||
} /* mpeg_l3_encoder_init */
|
||||
|
||||
#endif
|
@ -282,7 +282,9 @@ 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_OPUS_BAD_SAMPLERATE , "Error : Opus only supports sample rates of 8000, 12000, 16000, 24000, and 48000." },
|
||||
|
||||
{ SFE_MPEG_BAD_SAMPLERATE , "Error : MPEG-1/2/2.5 only supports sample rates of 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, and 48000." },
|
||||
|
||||
{ SFE_CAF_NOT_CAF , "Error : Not a CAF file." },
|
||||
{ SFE_CAF_NO_DESC , "Error : No 'desc' marker in CAF file." },
|
||||
@ -959,6 +961,16 @@ sf_format_check (const SF_INFO *info)
|
||||
if (subformat == SF_FORMAT_FLOAT || subformat == SF_FORMAT_DOUBLE)
|
||||
return 1 ;
|
||||
break ;
|
||||
|
||||
case SF_FORMAT_MP3 :
|
||||
if (info->channels > 2)
|
||||
return 0 ;
|
||||
if (endian != SF_ENDIAN_FILE)
|
||||
return 0 ;
|
||||
/* TODO */
|
||||
if (subformat == SF_FORMAT_MPEG_LAYER_I || subformat == SF_FORMAT_MPEG_LAYER_II || subformat == SF_FORMAT_MPEG_LAYER_III)
|
||||
return 1 ;
|
||||
break ;
|
||||
default : break ;
|
||||
} ;
|
||||
|
||||
@ -2743,6 +2755,13 @@ format_from_extension (SF_PRIVATE *psf)
|
||||
psf->sf.samplerate = 8000 ;
|
||||
format = SF_FORMAT_RAW | SF_FORMAT_GSM610 ;
|
||||
}
|
||||
else if (strcmp (cptr, "mp3") == 0)
|
||||
{ /*
|
||||
* MPEG streams are quite tollerate of crap. If we couldn't identify a
|
||||
* MP3 stream, but it has a .mp3 extension, let libmpg123 have a try.
|
||||
*/
|
||||
format = SF_FORMAT_MP3 ;
|
||||
}
|
||||
|
||||
/* For RAW files, make sure the dataoffset if set correctly. */
|
||||
if ((SF_CONTAINER (format)) == SF_FORMAT_RAW)
|
||||
@ -2852,9 +2871,26 @@ retry:
|
||||
if (buffer [0] == MAKE_MARKER ('R', 'F', '6', '4') && buffer [2] == MAKE_MARKER ('W', 'A', 'V', 'E'))
|
||||
return SF_FORMAT_RF64 ;
|
||||
|
||||
if ((buffer [0] & MAKE_MARKER (0xFF, 0xE0, 0, 0)) == MAKE_MARKER (0xFF, 0xE0, 0, 0) && /* Frame sync */
|
||||
(buffer [0] & MAKE_MARKER (0, 0x18, 0, 0)) != MAKE_MARKER (0, 0x08, 0, 0) && /* Valid MPEG version */
|
||||
(buffer [0] & MAKE_MARKER (0, 0x06, 0, 0)) != MAKE_MARKER (0, 0, 0, 0) && /* Valid layer description */
|
||||
(buffer [0] & MAKE_MARKER (0, 0, 0xF0, 0)) != MAKE_MARKER (0, 0, 0xF0, 0) && /* Valid bitrate */
|
||||
(buffer [0] & MAKE_MARKER (0, 0, 0x0C, 0)) != MAKE_MARKER (0, 0, 0x0C, 0)) /* Valid samplerate */
|
||||
return SF_FORMAT_MP3 ;
|
||||
|
||||
if (buffer [0] == MAKE_MARKER ('I', 'D', '3', 2) || buffer [0] == MAKE_MARKER ('I', 'D', '3', 3)
|
||||
|| buffer [0] == MAKE_MARKER ('I', 'D', '3', 4))
|
||||
{ psf_log_printf (psf, "Found 'ID3' marker.\n") ;
|
||||
|
||||
/* Guess MP3, try and open it as such. Allows libmpg123 to parse the ID3v2 headers */
|
||||
if (psf->file.mode == SFM_READ)
|
||||
{ if (mp3_open (psf) == 0)
|
||||
return SF_FORMAT_MP3 | ((~SF_FORMAT_TYPEMASK) & psf->sf.format) ;
|
||||
else if (psf->codec_close)
|
||||
psf->codec_close (psf) ;
|
||||
} ;
|
||||
|
||||
/* Otherwise, seek to after the ID3 header */
|
||||
if (id3_skip (psf))
|
||||
goto retry ;
|
||||
return 0 ;
|
||||
@ -3233,6 +3269,10 @@ psf_open_file (SF_PRIVATE *psf, SF_INFO *sfinfo)
|
||||
error = mpc2k_open (psf) ;
|
||||
break ;
|
||||
|
||||
case SF_FORMAT_MP3 :
|
||||
error = mp3_open (psf) ;
|
||||
break ;
|
||||
|
||||
/* Lite remove end */
|
||||
|
||||
default :
|
||||
@ -3253,6 +3293,7 @@ psf_open_file (SF_PRIVATE *psf, SF_INFO *sfinfo)
|
||||
/* Actual embedded files. */
|
||||
break ;
|
||||
|
||||
case SF_FORMAT_MP3 :
|
||||
case SF_FORMAT_FLAC :
|
||||
/* Flac with an ID3v2 header? */
|
||||
break ;
|
||||
|
@ -113,6 +113,8 @@ test_log_printf (void)
|
||||
/* Test printing of strings. */
|
||||
CMP_4_ARGS (__LINE__, errors, "B %s, %3s, %8s, %-8s", "str") ;
|
||||
|
||||
CMP_4_ARGS (__LINE__, errors, "B %.2s, %.8s, %-8.8s, %-4.2s", "str") ;
|
||||
|
||||
if (errors)
|
||||
{ puts ("\nExiting due to errors.\n") ;
|
||||
exit (1) ;
|
||||
|
@ -184,33 +184,50 @@ main (int argc, char *argv [])
|
||||
" vorbis - test Ogg/Vorbis\n"
|
||||
" flac - test FLAC\n"
|
||||
" opus - test Opus\n"
|
||||
" mpeg - test mpeg\n"
|
||||
" all - perform all tests\n",
|
||||
argv [0]) ;
|
||||
exit (0) ;
|
||||
} ;
|
||||
|
||||
if (! HAVE_EXTERNAL_XIPH_LIBS)
|
||||
{ puts (" No Ogg/Vorbis tests because Ogg/Vorbis support was not compiled in.") ;
|
||||
return 0 ;
|
||||
} ;
|
||||
|
||||
if (strcmp (argv [1], "all") == 0)
|
||||
all_tests = 1 ;
|
||||
|
||||
if (all_tests || strcmp (argv [1], "vorbis") == 0)
|
||||
{ vorbis_test () ;
|
||||
compression_size_test (SF_FORMAT_OGG | SF_FORMAT_VORBIS, "vorbis.oga") ;
|
||||
tests ++ ;
|
||||
{ if (HAVE_EXTERNAL_XIPH_LIBS)
|
||||
{ vorbis_test () ;
|
||||
compression_size_test (SF_FORMAT_OGG | SF_FORMAT_VORBIS, "vorbis.oga") ;
|
||||
tests ++ ;
|
||||
}
|
||||
else
|
||||
puts (" No Ogg Vorbis tests because support was not compiled in.") ;
|
||||
} ;
|
||||
|
||||
if (all_tests || strcmp (argv [1], "flac") == 0)
|
||||
{ compression_size_test (SF_FORMAT_FLAC | SF_FORMAT_PCM_16, "pcm16.flac") ;
|
||||
tests ++ ;
|
||||
{ if (HAVE_EXTERNAL_XIPH_LIBS)
|
||||
{ compression_size_test (SF_FORMAT_FLAC | SF_FORMAT_PCM_16, "pcm16.flac") ;
|
||||
tests ++ ;
|
||||
}
|
||||
else
|
||||
puts (" No FLAC tests because support was not compiled in.") ;
|
||||
} ;
|
||||
|
||||
if (all_tests || strcmp (argv [1], "opus") == 0)
|
||||
{ compression_size_test (SF_FORMAT_OGG | SF_FORMAT_OPUS, "opus.opus") ;
|
||||
tests ++ ;
|
||||
{ if (HAVE_EXTERNAL_XIPH_LIBS)
|
||||
{ compression_size_test (SF_FORMAT_OGG | SF_FORMAT_OPUS, "opus.opus") ;
|
||||
tests ++ ;
|
||||
}
|
||||
else
|
||||
puts (" No Opus tests because support was not compiled in.") ;
|
||||
} ;
|
||||
|
||||
if (all_tests || strcmp (argv [1], "mpeg") == 0)
|
||||
{ if (HAVE_MPEG)
|
||||
{ compression_size_test (SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III, "mpeg.mp3") ;
|
||||
tests ++ ;
|
||||
}
|
||||
else
|
||||
puts (" No MPEG tests because support was not compiled in.") ;
|
||||
} ;
|
||||
|
||||
return 0 ;
|
||||
|
@ -127,6 +127,10 @@ main (int argc, char *argv [])
|
||||
float_scaled_test ("opus.opus", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_OPUS, -32.0) ;
|
||||
#endif
|
||||
|
||||
#if HAVE_MPEG
|
||||
float_scaled_test ("mpeg.mp3", allow_exit, SF_FALSE, SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III, -52.0) ;
|
||||
#endif
|
||||
|
||||
float_scaled_test ("replace_float.raw", allow_exit, SF_TRUE, SF_ENDIAN_LITTLE | SF_FORMAT_RAW | SF_FORMAT_FLOAT, -163.0) ;
|
||||
|
||||
/*==============================================================================
|
||||
@ -185,6 +189,10 @@ main (int argc, char *argv [])
|
||||
double_scaled_test ("opus.opus", allow_exit, SF_FALSE, SF_FORMAT_OGG | SF_FORMAT_OPUS, -32.0) ;
|
||||
#endif
|
||||
|
||||
#if HAVE_MPEG
|
||||
double_scaled_test ("mpeg.mp3", allow_exit, SF_FALSE, SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III, -52.0) ;
|
||||
#endif
|
||||
|
||||
double_scaled_test ("replace_double.raw", allow_exit, SF_TRUE, SF_FORMAT_RAW | SF_FORMAT_DOUBLE, -201.0) ;
|
||||
|
||||
putchar ('\n') ;
|
||||
|
348
tests/mp3_test.c
Normal file
348
tests/mp3_test.c
Normal file
@ -0,0 +1,348 @@
|
||||
/*
|
||||
** Copyright (C) 2007-2019 Erik de Castro Lopo <erikd@mega-nerd.com>
|
||||
** Copyright (C) 2019 John ffitch <jpff@codemist.co.uk>
|
||||
**
|
||||
** 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 <sndfile.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#define SAMPLE_RATE 44100
|
||||
#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
|
||||
mp3_short_test (void)
|
||||
{ const char * filename = "mpeg_short.mp3" ;
|
||||
|
||||
SNDFILE * file ;
|
||||
SF_INFO sfinfo ;
|
||||
short seek_data [10] ;
|
||||
unsigned k ;
|
||||
|
||||
print_test_name ("mpeg_short_test", filename) ;
|
||||
|
||||
/* Generate float data. */
|
||||
gen_windowed_sine_float (data_out.f, ARRAY_LEN (data_out.f), 1.0 * 0x7F00) ;
|
||||
|
||||
/* Convert to shorteger. */
|
||||
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_MP3 | SF_FORMAT_MPEG_LAYER_III ;
|
||||
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 ("mpeg_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) ;
|
||||
} /* mpeg_short_test */
|
||||
|
||||
static void
|
||||
mp3_int_test (void)
|
||||
{ const char * filename = "mpeg_int.mp3" ;
|
||||
|
||||
SNDFILE * file ;
|
||||
SF_INFO sfinfo ;
|
||||
int seek_data [10] ;
|
||||
unsigned k ;
|
||||
|
||||
print_test_name ("mpeg_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_MP3 | SF_FORMAT_MPEG_LAYER_III ;
|
||||
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 ("mpeg_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) ;
|
||||
} /* mpeg_int_test */
|
||||
|
||||
static void
|
||||
mp3_float_test (void)
|
||||
{ const char * filename = "mpeg_float.mp3" ;
|
||||
|
||||
SNDFILE * file ;
|
||||
SF_INFO sfinfo ;
|
||||
float seek_data [10] ;
|
||||
|
||||
print_test_name ("mpeg_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_MP3 | SF_FORMAT_MPEG_LAYER_III ;
|
||||
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 ("mpeg_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) ;
|
||||
} /* mpeg_float_test */
|
||||
|
||||
static void
|
||||
mp3_double_test (void)
|
||||
{ const char * filename = "mpeg_double.mp3" ;
|
||||
|
||||
SNDFILE * file ;
|
||||
SF_INFO sfinfo ;
|
||||
double seek_data [10] ;
|
||||
|
||||
print_test_name ("mpeg_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_MP3 | SF_FORMAT_MPEG_LAYER_III ;
|
||||
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 ("mpeg_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) ;
|
||||
} /* mpeg_double_test */
|
||||
|
||||
|
||||
static void
|
||||
mp3_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) ;
|
||||
} /* mpeg_stereo_seek_test */
|
||||
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
if (HAVE_MPEG)
|
||||
{ mp3_short_test () ;
|
||||
mp3_int_test () ;
|
||||
mp3_float_test () ;
|
||||
mp3_double_test () ;
|
||||
|
||||
mp3_stereo_seek_test ("mpeg_seek.mp3", SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III) ;
|
||||
}
|
||||
else
|
||||
puts (" No MPEG tests because mpg123/lame support was not compiled in.") ;
|
||||
return 0 ;
|
||||
} /* main */
|
||||
|
@ -115,6 +115,14 @@ main (int argc, char *argv [])
|
||||
test_count++ ;
|
||||
} ;
|
||||
|
||||
if (do_all || ! strcmp (argv [1], "mpeg"))
|
||||
{ if (HAVE_MPEG)
|
||||
string_start_test ("mpeg.mp3", SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III) ;
|
||||
else
|
||||
puts (" No MP3 tests because MPEG support was not compiled in.") ;
|
||||
test_count++ ;
|
||||
} ;
|
||||
|
||||
if (do_all || ! strcmp (argv [1], "ogg"))
|
||||
{ if (HAVE_EXTERNAL_XIPH_LIBS)
|
||||
string_start_test ("vorbis.oga", SF_FORMAT_OGG | SF_FORMAT_VORBIS) ;
|
||||
@ -197,7 +205,9 @@ static const char
|
||||
long_title [] = "This is a very long and very boring title for this file",
|
||||
long_artist [] = "The artist who kept on changing its name",
|
||||
genre [] = "The genre",
|
||||
trackno [] = "Track three" ;
|
||||
trackno [] = "Track three",
|
||||
id3v1_genre [] = "Rock",
|
||||
year [] = "2001" ;
|
||||
|
||||
|
||||
static short data_out [BUFFER_LEN] ;
|
||||
@ -374,6 +384,7 @@ string_start_test (const char *filename, int formattype)
|
||||
break ;
|
||||
|
||||
case SF_FORMAT_OGG | SF_FORMAT_VORBIS :
|
||||
case SF_FORMAT_MP3 | SF_FORMAT_MPEG_LAYER_III :
|
||||
break ;
|
||||
|
||||
default :
|
||||
@ -390,9 +401,15 @@ string_start_test (const char *filename, int formattype)
|
||||
sf_set_string (file, SF_STR_ARTIST, artist) ;
|
||||
sf_set_string (file, SF_STR_COPYRIGHT, copyright) ;
|
||||
sf_set_string (file, SF_STR_COMMENT, comment) ;
|
||||
sf_set_string (file, SF_STR_DATE, date) ;
|
||||
sf_set_string (file, SF_STR_ALBUM, album) ;
|
||||
sf_set_string (file, SF_STR_LICENSE, license) ;
|
||||
if (typemajor == SF_FORMAT_MP3)
|
||||
{ sf_set_string (file, SF_STR_GENRE, id3v1_genre) ;
|
||||
sf_set_string (file, SF_STR_DATE, year) ;
|
||||
}
|
||||
else
|
||||
{ sf_set_string (file, SF_STR_DATE, date) ;
|
||||
} ;
|
||||
|
||||
/* Write data to file. */
|
||||
test_write_short_or_die (file, 0, data_out, BUFFER_LEN, __LINE__) ;
|
||||
@ -415,24 +432,35 @@ string_start_test (const char *filename, int formattype)
|
||||
printf (" Bad filename : %s\n", cptr) ;
|
||||
} ;
|
||||
|
||||
cptr = sf_get_string (file, SF_STR_COPYRIGHT) ;
|
||||
if (cptr == NULL || strcmp (copyright, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad copyright : %s\n", cptr) ;
|
||||
if (typemajor != SF_FORMAT_MP3)
|
||||
{ cptr = sf_get_string (file, SF_STR_COPYRIGHT) ;
|
||||
if (cptr == NULL || strcmp (copyright, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad copyright : %s\n", cptr) ;
|
||||
} ;
|
||||
|
||||
cptr = sf_get_string (file, SF_STR_SOFTWARE) ;
|
||||
if (cptr == NULL || strstr (cptr, software) != cptr)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad software : %s\n", cptr) ;
|
||||
} ;
|
||||
|
||||
if (cptr && str_count (cptr, "libsndfile") != 1)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad software : %s\n", cptr) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
cptr = sf_get_string (file, SF_STR_SOFTWARE) ;
|
||||
if (cptr == NULL || strstr (cptr, software) != cptr)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad software : %s\n", cptr) ;
|
||||
} ;
|
||||
|
||||
if (cptr && str_count (cptr, "libsndfile") != 1)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad software : %s\n", cptr) ;
|
||||
if (typemajor == SF_FORMAT_MP3)
|
||||
{ cptr = sf_get_string (file, SF_STR_GENRE) ;
|
||||
if (cptr == NULL || strcmp (id3v1_genre, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad genre : %s\n", cptr) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
cptr = sf_get_string (file, SF_STR_ARTIST) ;
|
||||
@ -449,13 +477,29 @@ string_start_test (const char *filename, int formattype)
|
||||
printf (" Bad comment : %s\n", cptr) ;
|
||||
} ;
|
||||
|
||||
if (typemajor != SF_FORMAT_AIFF)
|
||||
{ cptr = sf_get_string (file, SF_STR_DATE) ;
|
||||
if (cptr == NULL || strcmp (date, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad date : %s\n", cptr) ;
|
||||
} ;
|
||||
switch (typemajor)
|
||||
{ case SF_FORMAT_AIFF :
|
||||
/* not supported */
|
||||
break ;
|
||||
|
||||
case SF_FORMAT_MP3 :
|
||||
/* id3 only supports years */
|
||||
cptr = sf_get_string (file, SF_STR_DATE) ;
|
||||
if (cptr == NULL || strcmp (year, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad date : %s\n", cptr) ;
|
||||
} ;
|
||||
break ;
|
||||
|
||||
default :
|
||||
cptr = sf_get_string (file, SF_STR_DATE) ;
|
||||
if (cptr == NULL || strcmp (date, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad date : %s\n", cptr) ;
|
||||
} ;
|
||||
break ;
|
||||
} ;
|
||||
|
||||
if (typemajor != SF_FORMAT_WAV && typemajor != SF_FORMAT_AIFF)
|
||||
@ -467,13 +511,21 @@ string_start_test (const char *filename, int formattype)
|
||||
} ;
|
||||
} ;
|
||||
|
||||
if (typemajor != SF_FORMAT_WAV && typemajor != SF_FORMAT_AIFF && typemajor != SF_FORMAT_RF64)
|
||||
{ cptr = sf_get_string (file, SF_STR_LICENSE) ;
|
||||
if (cptr == NULL || strcmp (license, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad license : %s\n", cptr) ;
|
||||
} ;
|
||||
switch (typemajor)
|
||||
{ case SF_FORMAT_WAV :
|
||||
case SF_FORMAT_AIFF :
|
||||
case SF_FORMAT_RF64 :
|
||||
case SF_FORMAT_MP3 :
|
||||
/* not supported */
|
||||
break ;
|
||||
|
||||
default:
|
||||
cptr = sf_get_string (file, SF_STR_LICENSE) ;
|
||||
if (cptr == NULL || strcmp (license, cptr) != 0)
|
||||
{ if (errors++ == 0)
|
||||
puts ("\n") ;
|
||||
printf (" Bad license : %s\n", cptr) ;
|
||||
} ;
|
||||
} ;
|
||||
|
||||
if (errors > 0)
|
||||
|
@ -352,6 +352,15 @@ echo "----------------------------------------------------------------------"
|
||||
echo " $sfversion passed tests on OPUS files."
|
||||
echo "----------------------------------------------------------------------"
|
||||
|
||||
# mpeg-tests
|
||||
./tests/mp3_test@EXEEXT@
|
||||
./tests/compression_size_test@EXEEXT@ mpeg
|
||||
./tests/string_test@EXEEXT@ mpeg
|
||||
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo " $sfversion passed tests on MPEG files."
|
||||
echo "----------------------------------------------------------------------"
|
||||
|
||||
# io-tests
|
||||
./tests/stdio_test@EXEEXT@
|
||||
./tests/pipe_test@EXEEXT@
|
||||
|
Loading…
Reference in New Issue
Block a user