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:
Arthur Taylor 2019-02-09 22:45:55 -08:00
parent be637260f7
commit 663a59aa6e
24 changed files with 2593 additions and 63 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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
View 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
View 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)

View File

@ -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)

View File

@ -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 :

View File

@ -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. */

View File

@ -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 ;
} ;

View File

@ -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) ;

View File

@ -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

View File

@ -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
View 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
View 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
View 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
View 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
View 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

View File

@ -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 ;

View File

@ -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) ;

View File

@ -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 ;

View File

@ -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
View 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 */

View File

@ -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)

View File

@ -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@