diff --git a/Makefile.am b/Makefile.am index 95703eb1..f8cf2cd2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,98 +3,128 @@ ## copyright by the mpg123 project - free software under the terms of the LGPL 2.1 ## see COPYING and AUTHORS files in distribution or http://mpg123.org ## initially written by Nicholas J. Humfrey -dist_man_MANS = man1/mpg123.1 man1/out123.1 +## reworked for non-recursive make by Thomas Orgis -SUBDIRS = src doc -DIST_SUBDIRS = src doc +# Mention all global variables first before including Make modules. ACLOCAL_AMFLAGS = -I m4 +bin_PROGRAMS = +EXTRA_PROGRAMS = +LIBS = +EXTRA_DIST = +pkglib_LTLIBRARIES = +lib_LTLIBRARIES = +noinst_LIBRARIES = +noinst_LTLIBRARIES = +nodist_include_HEADERS = +dist_man_MANS = +CLEANFILES = + +AM_CPPFLAGS = -DPKGLIBDIR="\"$(pkglibdir)\"" +# That can be trimmed down later when adapting the sources to +# use relative paths for includes. +# Watch out for generated headers (that's why top_builddir is also present). +AM_CPPFLAGS += \ + $(LTDLINCL) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/compat \ + -I$(top_srcdir)/src/libmpg123 \ + -I$(top_srcdir)/src/libout123 \ + -I$(top_builddir)/src/libmpg123 -# pkg-config file for the mpg123 library pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libmpg123.pc +pkgconfig_DATA = + +# Include Make modules from subdirectories. +include src/Makemodule.am +include doc/Makemodule.am + +# Stuff from this directory. +pkgconfig_DATA += libmpg123.pc + +dist_man_MANS += man1/mpg123.1 man1/out123.1 # mpg123.spec is autogenerated but needs to be present in tarball! -EXTRA_DIST = \ - mpg123.spec \ - makedll.sh \ - windows-builds.sh \ - equalize.dat \ - NEWS.libmpg123 \ - ports/MSVC++/mpg123.h \ - ports/MSVC++/config.h \ - ports/MSVC++/msvc.c \ - ports/MSVC++/examples/scan.c \ - ports/MSVC++/examples/feedseek.c \ - ports/MSVC++/2005/libmpg123/libmpg123.vcproj \ - ports/MSVC++/2008/mpg123.sln \ - ports/MSVC++/2008/feedseek/feedseek.vcproj \ - ports/MSVC++/2008/mpglib/mpglib.vcproj \ - ports/MSVC++/2008/libmpg123/libmpg123.vcproj \ - ports/MSVC++/2008/scan/scan.vcproj \ - ports/MSVC++/2008/dump_seekindex/dump_seekindex.vcproj \ - ports/MSVC++/2008clr/2008clr.sln \ - ports/MSVC++/2008clr/mpg123clr/advanced.cpp \ - ports/MSVC++/2008clr/mpg123clr/advanced.h \ - ports/MSVC++/2008clr/mpg123clr/AssemblyInfo.cpp \ - ports/MSVC++/2008clr/mpg123clr/dllmain.cpp \ - ports/MSVC++/2008clr/mpg123clr/enum.h \ - ports/MSVC++/2008clr/mpg123clr/error.cpp \ - ports/MSVC++/2008clr/mpg123clr/error.h \ - ports/MSVC++/2008clr/mpg123clr/id3v1.cpp \ - ports/MSVC++/2008clr/mpg123clr/id3v1.h \ - ports/MSVC++/2008clr/mpg123clr/id3v2.cpp \ - ports/MSVC++/2008clr/mpg123clr/id3v2.h \ - ports/MSVC++/2008clr/mpg123clr/mpg123clr.cpp \ - ports/MSVC++/2008clr/mpg123clr/mpg123clr.h \ - ports/MSVC++/2008clr/mpg123clr/mpg123clr.rc \ - ports/MSVC++/2008clr/mpg123clr/mpg123clr.vcproj \ - ports/MSVC++/2008clr/mpg123clr/ReadMe.txt \ - ports/MSVC++/2008clr/mpg123clr/resource.h \ - ports/MSVC++/2008clr/mpg123clr/stdafx.cpp \ - ports/MSVC++/2008clr/mpg123clr/stdafx.h \ - ports/MSVC++/2008clr/mpg123clr/string.cpp \ - ports/MSVC++/2008clr/mpg123clr/string.h \ - ports/MSVC++/2008clr/mpg123clr/targetver.h \ - ports/MSVC++/2008clr/mpg123clr/text.cpp \ - ports/MSVC++/2008clr/mpg123clr/text.h \ - ports/MSVC++/2008clr/examples/feedseekclr/feedseekclr.csproj \ - ports/MSVC++/2008clr/examples/feedseekclr/Program.cs \ - ports/MSVC++/2008clr/examples/feedseekclr/Properties/AssemblyInfo.cs \ - ports/MSVC++/2008clr/examples/ReplaceReaderclr/ReplaceReaderclr.csproj \ - ports/MSVC++/2008clr/examples/ReplaceReaderclr/Program.cs \ - ports/MSVC++/2008clr/examples/ReplaceReaderclr/Properties/AssemblyInfo.cs \ - ports/MSVC++/2008clr/examples/scanclr/scanclr.csproj \ - ports/MSVC++/2008clr/examples/scanclr/Program.cs \ - ports/MSVC++/2008clr/examples/scanclr/Properties/AssemblyInfo.cs \ - ports/MSVC++/2010/mpg123.sln \ - ports/MSVC++/2010/dump_seekindex/dump_seekindex.vcxproj \ - ports/MSVC++/2010/dump_seekindex/dump_seekindex.vcxproj.filters \ - ports/MSVC++/2010/feedseek/feedseek.vcxproj \ - ports/MSVC++/2010/feedseek/feedseek.vcxproj.filters \ - ports/MSVC++/2010/libmpg123/libmpg123.vcxproj \ - ports/MSVC++/2010/scan/scan.vcxproj \ - ports/MSVC++/2010/scan/scan.vcxproj.filters \ - ports/MSVC++/CMP3Stream/libMPG123/libMPG123.vcproj \ - ports/MSVC++/CMP3Stream/libMPG123/PLACE_LIBMPG123_SOURCES_HERE \ - ports/MSVC++/CMP3Stream/README \ - ports/MSVC++/CMP3Stream/SOURCE/CORE_Log.CPP \ - ports/MSVC++/CMP3Stream/SOURCE/CORE_FileIn.CPP \ - ports/MSVC++/CMP3Stream/SOURCE/SourceFilter_MP3Stream.CPP \ - ports/MSVC++/CMP3Stream/SOURCE/CORE_Mutex.CPP \ - ports/MSVC++/CMP3Stream/INCLUDE/CORE/CORE_FileIn.H \ - ports/MSVC++/CMP3Stream/INCLUDE/CORE/SourceFilter_MP3.H \ - ports/MSVC++/CMP3Stream/INCLUDE/IIEP_FileIn.H \ - ports/MSVC++/CMP3Stream/INCLUDE/IIEP_Def.H \ - ports/README \ - ports/Sony_PSP/config.h \ - ports/Sony_PSP/README \ - ports/Sony_PSP/Makefile.psp \ - ports/Sony_PSP/readers.c.patch \ - ports/mpg123_.pas \ - ports/Xcode/config.h \ - ports/Xcode/mpg123.h \ - ports/Xcode/mpg123.xcodeproj/project.pbxproj \ - scripts/benchmark-cpu.pl \ - scripts/tag_lyrics.py \ - scripts/conplay \ - scripts/mpg123info +EXTRA_DIST += \ + mpg123.spec \ + makedll.sh \ + windows-builds.sh \ + equalize.dat \ + NEWS.libmpg123 \ + ports/MSVC++/mpg123.h \ + ports/MSVC++/config.h \ + ports/MSVC++/msvc.c \ + ports/MSVC++/examples/scan.c \ + ports/MSVC++/examples/feedseek.c \ + ports/MSVC++/2005/libmpg123/libmpg123.vcproj \ + ports/MSVC++/2008/mpg123.sln \ + ports/MSVC++/2008/feedseek/feedseek.vcproj \ + ports/MSVC++/2008/mpglib/mpglib.vcproj \ + ports/MSVC++/2008/libmpg123/libmpg123.vcproj \ + ports/MSVC++/2008/scan/scan.vcproj \ + ports/MSVC++/2008/dump_seekindex/dump_seekindex.vcproj \ + ports/MSVC++/2008clr/2008clr.sln \ + ports/MSVC++/2008clr/mpg123clr/advanced.cpp \ + ports/MSVC++/2008clr/mpg123clr/advanced.h \ + ports/MSVC++/2008clr/mpg123clr/AssemblyInfo.cpp \ + ports/MSVC++/2008clr/mpg123clr/dllmain.cpp \ + ports/MSVC++/2008clr/mpg123clr/enum.h \ + ports/MSVC++/2008clr/mpg123clr/error.cpp \ + ports/MSVC++/2008clr/mpg123clr/error.h \ + ports/MSVC++/2008clr/mpg123clr/id3v1.cpp \ + ports/MSVC++/2008clr/mpg123clr/id3v1.h \ + ports/MSVC++/2008clr/mpg123clr/id3v2.cpp \ + ports/MSVC++/2008clr/mpg123clr/id3v2.h \ + ports/MSVC++/2008clr/mpg123clr/mpg123clr.cpp \ + ports/MSVC++/2008clr/mpg123clr/mpg123clr.h \ + ports/MSVC++/2008clr/mpg123clr/mpg123clr.rc \ + ports/MSVC++/2008clr/mpg123clr/mpg123clr.vcproj \ + ports/MSVC++/2008clr/mpg123clr/ReadMe.txt \ + ports/MSVC++/2008clr/mpg123clr/resource.h \ + ports/MSVC++/2008clr/mpg123clr/stdafx.cpp \ + ports/MSVC++/2008clr/mpg123clr/stdafx.h \ + ports/MSVC++/2008clr/mpg123clr/string.cpp \ + ports/MSVC++/2008clr/mpg123clr/string.h \ + ports/MSVC++/2008clr/mpg123clr/targetver.h \ + ports/MSVC++/2008clr/mpg123clr/text.cpp \ + ports/MSVC++/2008clr/mpg123clr/text.h \ + ports/MSVC++/2008clr/examples/feedseekclr/feedseekclr.csproj \ + ports/MSVC++/2008clr/examples/feedseekclr/Program.cs \ + ports/MSVC++/2008clr/examples/feedseekclr/Properties/AssemblyInfo.cs \ + ports/MSVC++/2008clr/examples/ReplaceReaderclr/ReplaceReaderclr.csproj \ + ports/MSVC++/2008clr/examples/ReplaceReaderclr/Program.cs \ + ports/MSVC++/2008clr/examples/ReplaceReaderclr/Properties/AssemblyInfo.cs \ + ports/MSVC++/2008clr/examples/scanclr/scanclr.csproj \ + ports/MSVC++/2008clr/examples/scanclr/Program.cs \ + ports/MSVC++/2008clr/examples/scanclr/Properties/AssemblyInfo.cs \ + ports/MSVC++/2010/mpg123.sln \ + ports/MSVC++/2010/dump_seekindex/dump_seekindex.vcxproj \ + ports/MSVC++/2010/dump_seekindex/dump_seekindex.vcxproj.filters \ + ports/MSVC++/2010/feedseek/feedseek.vcxproj \ + ports/MSVC++/2010/feedseek/feedseek.vcxproj.filters \ + ports/MSVC++/2010/libmpg123/libmpg123.vcxproj \ + ports/MSVC++/2010/scan/scan.vcxproj \ + ports/MSVC++/2010/scan/scan.vcxproj.filters \ + ports/MSVC++/CMP3Stream/libMPG123/libMPG123.vcproj \ + ports/MSVC++/CMP3Stream/libMPG123/PLACE_LIBMPG123_SOURCES_HERE \ + ports/MSVC++/CMP3Stream/README \ + ports/MSVC++/CMP3Stream/SOURCE/CORE_Log.CPP \ + ports/MSVC++/CMP3Stream/SOURCE/CORE_FileIn.CPP \ + ports/MSVC++/CMP3Stream/SOURCE/SourceFilter_MP3Stream.CPP \ + ports/MSVC++/CMP3Stream/SOURCE/CORE_Mutex.CPP \ + ports/MSVC++/CMP3Stream/INCLUDE/CORE/CORE_FileIn.H \ + ports/MSVC++/CMP3Stream/INCLUDE/CORE/SourceFilter_MP3.H \ + ports/MSVC++/CMP3Stream/INCLUDE/IIEP_FileIn.H \ + ports/MSVC++/CMP3Stream/INCLUDE/IIEP_Def.H \ + ports/README \ + ports/Sony_PSP/config.h \ + ports/Sony_PSP/README \ + ports/Sony_PSP/Makefile.psp \ + ports/Sony_PSP/readers.c.patch \ + ports/mpg123_.pas \ + ports/Xcode/config.h \ + ports/Xcode/mpg123.h \ + ports/Xcode/mpg123.xcodeproj/project.pbxproj \ + scripts/benchmark-cpu.pl \ + scripts/tag_lyrics.py \ + scripts/conplay \ + scripts/mpg123info diff --git a/NEWS b/NEWS index 14e37d89..7f42c93e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,13 @@ +- Activate terminal control automatically if run in an interactive terminal. + Provide switch to _deactivate_ terminal control. + +- reverse order of arguments for out123_start() again, matching mpg123_getformat() + +- fancy: do progress bar on terminal by reversing text colors on status line + +- anticipate messages from libmpg123 with param.verbose>1 in term.c and print("\n") + on key presses that _will_ result in "Note: " printouts + 1.23.0 --- - Added mpg123 --no-infoframe. @@ -8,6 +18,13 @@ - Warning messages also start with a line break now to better fit in with verbose playback. - Reporting of clipped samples also includes a line break now. +- Default for --preload now is 0.2 instead of 1 (fill whole buffer before + playback). The maximum is 0.5 . This is mandated by corrected buffer + interaction in libout123. +- Improved interaction with buffer process in terminal control mode. + Seeking is more transparent now, taking dropped samples from buffer into + account to avoid unintended jumps. Direct seeks with number row do not + pause playback anymore. - Silently skip APE tags (thanks to Hans de Goede). - Some reduction in bitrot on AIX (typos in output module, build with --disable-largefile --with-audio=aix, real test welcome). diff --git a/configure.ac b/configure.ac index 3ed8e39f..f127a1a6 100644 --- a/configure.ac +++ b/configure.ac @@ -10,12 +10,23 @@ AC_PREREQ(2.57) dnl ############# Initialisation AC_INIT([mpg123], [1.23.0], [mpg123-devel@lists.sourceforge.net]) dnl Increment API_VERSION when the API gets changes (new functions). + +dnl libmpg123 API_VERSION=41 LIB_PATCHLEVEL=4 + +dnl libout123 +OUTAPI_VERSION=1 +OUTLIB_PATCHLEVEL=0 + dnl Since we want to be backwards compatible, both sides get set to API_VERSION. LIBMPG123_VERSION=$API_VERSION:$LIB_PATCHLEVEL:$API_VERSION +LIBOUT123_VERSION=$OUTAPI_VERSION:$OUTLIB_PATCHLEVEL:$OUTAPI_VERSION AC_SUBST(LIBMPG123_VERSION) AC_SUBST(API_VERSION) +AC_SUBST(LIBOUT123_VERSION) +AC_SUBST(OUTAPI_VERSION) + AC_CONFIG_SRCDIR(src/mpg123.c) AC_CONFIG_AUX_DIR(build) @@ -24,7 +35,9 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST dnl Version 1.7 of automake is recommended -AM_INIT_AUTOMAKE(1.7) +dnl Not sure what minimal version does not choke on sub directories. +dnl Testing with 1.14. +AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_HEADERS([src/config.h]) @@ -164,17 +177,8 @@ dnl Configure libtool AC_LIBTOOL_WIN32_DLL AM_PROG_LIBTOOL -if test x"$modules" = xdisabled -then - echo "Modules still disabled..." - MODULE_OBJ="legacy_module.\$(OBJEXT)" -else - MODULE_OBJ="module.\$(OBJEXT)" -fi - AM_CONDITIONAL( [HAVE_MODULES], [test "x$modules" = xenabled] ) -AC_SUBST(MODULE_OBJ) AC_SUBST(LT_LDFLAGS) AC_SUBST(EXEC_LT_LDFLAGS) @@ -1057,6 +1061,8 @@ else AC_MSG_RESULT([no]) fi +# Again, prepend path for non-recursive make. +LFS_LOBJ=`for i in $LFS_LOBJ; do echo src/libmpg123/$i; done | tr '\n' ' '` AC_SUBST(LFS_LOBJ) @@ -1472,6 +1478,12 @@ do word_in_list "$i.lo" $DECODER_LOBJ || DECODER_LOBJ="$DECODER_LOBJ $i.lo" done +# Another preprocessing step: Append prefix for non-recursive make. +# Just because $(addprefix ...) is a GNU extension. + +DECODER_OBJ=`for i in $DECODER_OBJ; do echo src/libmpg123/$i; done | tr '\n' ' '` +DECODER_LOBJ=`for i in $DECODER_LOBJ; do echo src/libmpg123/$i; done | tr '\n' ' '` + AC_SUBST(DECODER_OBJ) AC_SUBST(DECODER_LOBJ) @@ -1954,168 +1966,56 @@ fi # That's (beginning of) the list for mpg123's internal default. default_output_modules=$default_output_module -OUTPUT_OBJ= -OUTPUT_MOD= -OUTPUT_CFLAGS= -OUTPUT_LIBS= -OUTPUT_LDFLAGS= - # Setup the static build. -if test "x$modules" = xdisabled -then - echo "Preparing static output $default_output_module" - OUTPUT_MOD="$default_output_module" - OUTPUT_OBJ="output/$default_output_module.\$(OBJEXT)" - OUTPUT_CFLAGS= - OUTPUT_LIBS= - # That feels stupid... what about hashed arrays? - case $OUTPUT_MOD in - # Here's a script for that tedious list, perhaps to be outsourced together with the one in src/output/Makefile.am -#for i in tinyalsa alsa qsa coreaudio esd jack nas oss portaudio pulse sdl sndio sun win32 win32_wasapi aix alib arts hp os2 sgi mint openal +# The conditionals always need to be defined by configure, even if +# HAVE_MODULES is FALSE! +# Here's a script for that tedious list, perhaps to be outsourced together with the one in #src/output/Makefile.am +#for i in dummy tinyalsa alsa qsa coreaudio esd jack nas oss portaudio pulse sdl sndio sun win32 win32_wasapi aix alib arts hp os2 sgi mint openal #do echo $i; done | #perl -ne 'chomp; $big = uc($_); print <justwait) + xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_WAKEUP); + +- buffer_stop() + buffermem->justwait = TRUE; + buffer_sig(SIGINT, TRUE); + +- buffer_reset() + buffer_sig(SIGUSR1, TRUE); + +- buffer_resync() + if(buffermem->justwait) + { + buffermem->wakeme[XF_WRITER] = TRUE; + xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_RESYNC); + xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE); + } + else buffer_sig(SIGINT, TRUE); + +- plain_buffer_resync() + buffer_sig(SIGINT, FALSE); + +- buffer_ignore_lowmem() + if(buffermem->wakeme[XF_READER]) + xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_WAKEUP); + +- buffer_end() + xfermem_putcmd(buffermem->fd[XF_WRITER], rude ? XF_CMD_ABORT : XF_CMD_TERMINATE); + +Also, there is direct use of the xfermem API: + +- xfermem_write(buffermem, bytes, count) + to push data to buffer + +- xfermem_get_usedspace(buffermem) + to get bytes still in buffer (not written to audio output) + +- xfermem_block(XF_WRITER, buffermem) + for synchronization / messaging to writer (buffer client) + +- xfermem_putcmd(buffermem->fd[XF_WRITER], cmd) + to give comands to buffer + +- xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE (FALSE?)) + to get commands/response from buffer? + +I probably should clear that up first. + + +1.1 xfermem +----------- + +Quoting Oliver: + + This is a stand-alone module which implements a unidirectional, + fast pipe using mmap(). Its primary use is to transfer large + amounts of data from a parent process to its child process, + with a buffer in between which decouples blocking conditions + on both sides. Control information is transferred between the + processes through a socketpair. + +The actual shared memory is implemented using anonymous mmap(), +mmap() of /dev/zero, or via traditional System V memory. This reminds +me that there are some code paths that are not excercised often. +I should introduce (runtime?) switch to be able to test each variant. +On the other hand, the sysVshm API is not that rapidly changing. + +Anyhow, the point is that we have xfermem structure and buffer memory +shared between main and buffer process, by whatever means. Commands +are exchanged via the socket pair xfermem->fd[XF_WRITER] and +xfermem->fd[XF_READER]. + +- xfermem_init(xf, bufsize, msize, skipbuf) + to intialize the pipe, msize and skipbuf equal to zero for mpg123 use + +- xfermem_done(xf) + to free the shared memory, not bothering with cleaning up the sockets + Should I change that? The reader process exists before clearing the + data structure, but the writer process keeps the socket open ... + I think I should introduce xfermem_exit_writer() and xfermem_exit_reader(), + +- xfermem_init_writer() / xfermem_init_reader() + to close the respective other end of the socket pair + +- xfermem_get_freespace() + to return space available for writing + +- xfermem_get_usedspace() + to return space filled with data, waiting to be consumed + +- xfermem_getcmd(fd, block) + to ... well wait for a one-byte command code on the given file descriptor, + blocking or non-blocking. + +- xfermem_putcmd(fd, cmd) + to send a command to the other end with the fd on this side + +- xfermem_block(rw, xf) + to synchronize ... needs some thought + The value of rw is XF_READER or XF_WRITER, xf->wakeme[rw] is set, if the + other end has set xf->wakeme[1-rw], it gets a wakeup call + (xfermem_putcmd(xf->fd[rw], XF_CMD_WAKEUP)) and this end waits for a sign + (xfermem_getcmd(xf->fd[rw], TRUE)), clearing cf->wakeme[rw] after that. + Classic synchronization. + +- xfermem_sigblock(rw, xf, pid, sig) + to signal the other process by given pid and wait for a wakeup call as + response + I added that to fix bug 2796802 in the year 2009. Hm. Well, it is + necessary to interrupt the buffer process if it is not currently + waiting for a command. + +- xfermem_write(xf, buffer, bytes) + to wait until enough space is free, then copy over the bytes + A bonus is to wakeup the reader process if it has xf->wakeme set. + +That's it. It's a shared ringbuffer with some synchronization and +messaging. + + +1.2 buffer API explained +------------------------ + +Now it's time to decipher what the buffer API calls do. The buffer works +on an instance of xfermem which is called buffermem, but I'll refer to it +using xf as before. + +- buffer_start() + to send XF_CMD_WAKEUP to the buffer in case it is waiting (xf->justwait) + Why is there xf->justwait in addition to xf->wakeme? Apparently to set + it from the reader in buffer_stop(). It's the hack to be able to use + SIGINT for two messages. + +- buffer_stop() + to interrupt the buffer and send it into waiting mode + This sets xf->justwait and then signals SIGINT, waiting for the buffer + to acknowledge. The buffer process sets intflag and, on the next occasion + in the main loop, drains its command queue, sends wakeup to the writer + desired. It then waits for commands. + +- buffer_reset() + to interrupt the buffer, causing it to reopen the audio device, possibly + with new settings + This also discards all data currently in the buffer (presumably in + incompatible audio format/encoding) and sends a wakeup to the writer + if desired. Flushing of audio device happens first. + +- buffer_resync() + to send XF_CMD_RESYNC directly or indirectly (plain SIGINT) to the buffer + The buffer flushes the audio and discards all buffer data, wakes writer + afterwards. + +- plain_buffer_resync() + to signal a resync (SIGINT) to the buffer without waiting for the result + This disregards the case + +- buffer_ignore_lowmem() + to wake the buffer process + Yes, this just sends XF_CMD_WAKEUP to the buffer, if it is waiting for it. + Why the funny name for the function? + Well, it is designed for the situation where the buffer is waiting for + more data to start playback again. Normal writing of data gives + XF_CMD_WAKEUP_INFO, which only triggers playback if there is enough data + buffered. XF_CMD_WAKEUP triggers playback right away (if there is any data). + There is a catch: This routine only does a wakeup call if the buffer already + blocks waiting for a command, but there is a possible race condition where + the buffer is about to enter xfermem_block() but didn't yet. Then, there is + no effect of buffer_ignore_lowmem()! + +- buffer_end() + to either send XF_CMD_ABORT (end processing right away) or XF_CMD_TERMINATE + In either case, the buffer only gets this as normal command, not per signal, + though I have to think about what the signalling stuff really adds to the + picture. Damn! It's designed to abort flush_output(), or rather + ao->write(). I twarted that some time ago by making flush_output() resilient. + I need to revert that; the buffer code should not call flush_output. + Instead, it should just call ao->write(). Being interrupted does not matter + much since the buffer continues to write on the next iteration anyway. + But then, I also need to make the buffer resilient about ao->write() + being interrupted. It is already checking for SIGINT / SIGUSR1, but + I uttered something about SIGSTOP / SIGCONT. I need to revisit that + behaviour. Does SIGSTOP really cause audio output writes to return early? + + +1.3 Analysis +------------ + +Well, there we are now. Some questions popped, specifically about the use +of signals. + +- 1. Is the use of signals SIGINT / SIGUSR1 really appropriate? + +- 2. What do SIGSTOP / SIGCONT do to ao->flush()? + +The idea of the signals should be that the buffer reacts to them immediately +instead of finishing a write to the audio device. I don't see another point. +I may want to revisit which actions really demand that kind of reaction. +Currently, it's three of them: + +- stop operation (justwait) +- resync (discarding buffered data) +- reset (discard data, re-open audio device) + +That list looks sensible. + +Now, I damanged that logic in 2007 with commit 1278. I put flush_output() also +into the buffer, which broke the immediate reaction after a signal. Now the +question is: Do we really care about that immediate reaction? Only if the +hardware buffer happens to be large, or because it is blocking for some +reason. Hm, the latter could be annoying. + +In the current state, the signals could be replaced with normal messages and +nothing would change. But I will change that and repair the buffer reaction +again! + +The second question about SIGSTOP / SIGCONT doesn't really matter in this +context. The normal flush_output() indeed should loop to make things work, +as that is the semantics of the non-buffered output. It returns after it +is finised. It may be a question if I want to introduce that in the libout123 +API, too. I guess there should be a parameter or separate API call to +decide if the writing/flushing should return after being interrupted or +only after it wrote the given data. There are use cases for both. + +But still, I have to check the behaviour with SIGSTOP. How does ALSA handle +it? Heh, a search for that returns this: + + https://sourceforge.net/p/mpg123/bugs/37/ + + with alsa and mpg123-0.65, when I press ^Z and then + fg after some time, there is evil squeaky sound -- + the longer the pause, the longer the squeaky sound. + +I keep stumbling over my own tracks in the internet;-) Of course, this bug +wasn't always numbered 37, that's sf.net's reboot. Also, I don't find +an attachment, but a patch is referenced by Clemens. Did sf.net kill that? At +least I mentioned the revision: 637. Yes, that introduces some EINTR handling. + +Another interesting data point: + + http://compgroups.net/comp.linux.development.system/sigstop-and-interrupted-system/2865328 + + This is somewhat debatable. What the OP is talking about here is + behaviour that occurs *in the absence* of a signal handler. On almost + every other Unix, in this case, the signal is not visible to the + application; that is, the system call is automatically resumed upon + receipt of SIGCONT. + + The behaviour of Linux is idiosyncratic: on Linux, a stop signal + + SIGCONT causes certain system calls to fail with EINTR, even in the + absence of signal handlers. In my reading of SUSv3, this ishould not + happen. No other contemporary Unix implementation that I know of (and + I've tested many) does this. (I'm told that historically one oether + implementation -- I think it was AIX -- did this, but does not do it + nowadays, since it was deemed to be non-standard.) + + Cheers, + + Michael + +So it seems that, indeed, I need the outer loop over ao->flush to cope +with SIGSTOP / SIGCONT. I didn't add it just for fun. If I remove that +loop from the buffer, it needs to be aware that less written bytes that +given does not have to mean that an error occured. It could be +SIGSTOP+SIGCONT. And since the EINTR is handled inside the audio outputs, +the details are hidden and the buffer just has to assume that if the +number of written bytes is >= 0, that everything is OK so far. + + +1.4 Specific use of buffer API in terminal mode +----------------------------------------------- + +The client part that does care about buffer operation is the interactive +terminal control mode. I need to understand where it needs what buffer +calls and how they should be abstracted in a generic audio output API. + +First, there's buffer_ignore_lowmem() while paused to force playback in the +loop. The usage of the buffer with the pause/looping mode is not encouraged +anyway. + + +1.5 Synchronization issues +-------------------------- + +It occurs to me that, although the buffer logic at the core is rather old +and tried, there are race conditions. A common idiom is this: + + if (xf->wakeme[XF_X]) xfermem_putcmd(xf->fd[XF_Y], XF_CMD_WAKEUP); + +Now, this works if we are sure that xf->wakeme[XF_X] has been set by the other +end before the code above was triggered. Even without digging into shared +memory semantics and questions of atomicity (I hereby declare int-sized writes +as atomic, which generally works nowadays. Also, I could make the data type +smaller, as it's just about zero or one. How can the effective setting of +a single bit not be atomic?), this can only be considered safe if the +code is triggered in response to an action from the other side, after the +other side has set xf->wakeme[XF_X]. + +This works when the buffer is currently blocking, waiting for a command inside +xfermem_block(), and the writer does a xfermem_block or xfermem_sigblock on +its own. + +Let's focus on buffer_ignore_lowmem(). This works to unleash the buffer if +it is currently stuck in xfermem_block() because of not having enough data. +What if it just entered the branch for that but did not call xfermem_block() +yet? then, xf->wakeme[XF_READER] won't be set yet and buffer_ignore_lowmem() +will not trigger anything. The buffer will block although we wanted it to +continue! + +This is a very small time window, but it is possible. This is a definition of +unreliable code. + +In this specific case, we don't actually want to tell the buffer all the time +that it should ignore small buffer fill. We want it to ignore the low buffer +fill from now on until told otherwise. We want to set some permanent flag. + +Also, in this specific case, things work out since buffer_ignore_lowmem() is +called in a loop anyway. It will catch the buffer blocking some later time. +Is this now as intended? Is it buggy? + +The crux with buffer_ignore_lowmem() is that it assumes state on the side of +the buffer and gives a command the meaning of which depends on this state. +But, well it's really non-fatal in this case. I have to wonder how I can +make it robust for general use. I guess the sending of the commands needs +to be unconditional and the buffer needs to look for commands on each loop +cycle, without blocking. + +Uh, apart from the messed up semantics of output_pause(), I now come back +to the races with seekmode() of term.c . It uses buffer_stop, but it also +should do buffer_resync right away. The call to that follows later, after +we have put the buffer into justwait mode. And it actually works that way. +But it is still not the right way to do this! + +The interactive seeking really is an interesting place for audio buffering. +As long as I do not want to seek around inside the actual buffer, the +buffered data needs to be thrown away. This should happen in seekmode() +directly. Also, the playback needs to be paused with the buffer as long +as it does not really tie in with the seeking. They're mutually exclusive. + +If not using the buffer, playback can stay active to give audible feedback +during seeking. How do I abstract that? I guess there need to be +audio_buffer_disable/enable() calls. A bonus could even be to really just +disable the buffer and do playback directly while seeking, but I don't want +to close/reopen the audio device unnecessarily (think JACK again). + +There could be a pass-through mode for the buffer, giving lower latency. +But what am I doing here? I want to extract the audio code into a library. +I can do the funky improvements later. + + +2. What would I like to change? +=============================== + +I want to have operation with and without buffer more symmetric. I do not +want only a one-shot time window to query audio caps, I want to be able to +do that anytime (preferrably not while an audio device is open). I want +more communication with the buffer. I need to exchange strings (module +name, error response) and numbers (various parameters). + +There are established ways already. I can repoen the audio device with +differing settings. I can query caps. One just needs some generic +(or not so generic) fields in the xfermem structure. The only new thing +is that I can reserve some space for moderate error strings, too. Or: +If no audio data is pending, I'd have lots of space to point into for +message strings. But, well, it should be enough to communicate a specific +error code and the value of errno, if that may help. Verbose printing +can hapen to stderr. + +While at that, I'll make the communication safer by adhering to a stricter +protocol: Keep requests and responses together. Acknowledge actions on the +buffer side. The only fire-and-forget performance critical part is about +feeding audio data. Everhthing else should be rather synchronous, thank +you very much. diff --git a/scripts/intsym.pl b/scripts/intsym.pl index 37b3305d..83fbc6d8 100644 --- a/scripts/intsym.pl +++ b/scripts/intsym.pl @@ -2,64 +2,105 @@ use strict; -my $dir = 'src/libmpg123'; -my @headers = qw(compat decode dither frame getbits getcpuflags huffman icy2utf8 icy id3 index mpg123lib_intern optimize parse reader); -my $prefix = 'INT123_'; -my @leavealone = qw(strerror strdup); +print STDERR "Do not forget to handle renaming of symbols from a statically linked output module!\n"; -my %ident; +my @instances = +( +{ name => 'intsym.h' +, guard => 'MPG123_INTSYM_H' +, dir => 'src/libmpg123' +, headers => [qw(../compat/compat decode dither frame getbits getcpuflags huffman icy2utf8 icy id3 index mpg123lib_intern optimize parse reader)] +, prefix => 'INT123_' +, apiprefix => 'mpg123_' +, conditional => { strerror=>'HAVE_STRERROR', strdup=>'HAVE_STRDUP' } +, symbols => [qw(COS9 tfcos36 pnts)] # extra symbols +} +, +{ name => 'out123_intsym.h' +, guard => 'OUT123_INTSYM_H' +, dir => 'src/libout123' +, headers => [qw(../compat/compat module buffer xfermem wav out123_int)] +, prefix => 'IOT123_' +, apiprefix => 'out123_|audio_|output_' +, conditional => { strerror=>'HAVE_STRERROR', strdup=>'HAVE_STRDUP' } +, symbols => [qw(catchsignal)] # extra symbols +} +); -# Extra symbols. -my @symbols = qw(COS9 tfcos36 pnts); - -foreach my $header (@headers) +for my $i (@instances) { - print STDERR "==== working on header $header\n"; - open(DAT, '<', $dir.'/'.$header.'.h') or die "Cannot open $header.\n"; - while() + my $dir = $i->{dir}; + print STDERR "dir: $dir\n"; + my $outfile = "$dir/$i->{name}"; + print STDERR "generating $outfile\n"; + open(my $out, '>', $outfile) or die "Meh.\n"; + + my %ident; + my @symbols = @{$i->{symbols}}; + my $apiex = qr/^$i->{apiprefix}/; + + foreach my $header (@{$i->{headers}}) { - if(/^([^\s\(#][^\(]*)\s\*?([a-z][a-z_0-9]+)\s*\(/) - { - # Skip preprocessing/comment stuff and official API. - unless($1 =~ '^#' or $1 =~ '/\*' or $2 =~ /^mpg123_/) + print STDERR "==== working on header $header\n"; + open(DAT, '<', $dir.'/'.$header.'.h') or die "Cannot open $header.\n"; + while() + { # Only simple declarations parsed here, more configured manually. + if(/^([^\s\(#][^\(]*)\s\*?([a-z][a-z_0-9]+)\s*\(/) { - push(@symbols, $2) unless grep {$_ eq $2} @leavealone; + # Skip preprocessing/comment stuff and official API. + unless($1 =~ '^#' or $1 =~ '/\*' or $2 =~ $apiex) + { + push(@symbols, $2) unless grep {$_ eq $2} (keys %{$i->{conditional}}); + } } } + close(DAT); } - close(DAT); -} -print STDERR join("\n", glob("$dir/*.S"))."\n"; -foreach my $asm (glob("$dir/*.S")) -{ - print STDERR "==== working on asm file $asm\n"; - open(DAT, '<', $asm) or die "Cannot open $asm.\n"; - while() + print STDERR join("\n", glob("$dir/*.S"))."\n"; + foreach my $asm (glob("$dir/*.S")) { - if(/^\s*\.globl\s+ASM_NAME\((\S+)\)$/) + print STDERR "==== working on asm file $asm\n"; + open(DAT, '<', $asm) or die "Cannot open $asm.\n"; + while() { - print STDERR; - push(@symbols, $1) unless grep {$_ eq $1} @symbols; + if(/^\s*\.globl\s+ASM_NAME\((\S+)\)$/) + { + print STDERR; + push(@symbols, $1) unless grep {$_ eq $1} @symbols; + } } + close(DAT); } - close(DAT); -} -print "#ifndef MPG123_INTMAP_H\n"; -print "#define MPG123_INTMAP_H\n"; -print "/* Mapping of internal mpg123 symbols to something that is less likely to conflict in case of static linking. */\n"; + print $out "#ifndef $i->{guard}\n"; + print $out "#define $i->{guard}\n"; + print $out "/* Mapping of internal mpg123 symbols to something that is less likely to conflict in case of static linking. */\n"; -foreach my $sym (@symbols) -{ - my $name = $prefix.$sym; - my $signi = substr($name,0,31); - #print STDERR "$name / $signi\n"; - if(++$ident{$signi} > 1) + foreach my $sym (@symbols) { - die "That symbol is not unique in 31 chars: $name\n"; + my $name = $i->{prefix}.$sym; + my $signi = substr($name,0,31); + #print STDERR "$name / $signi\n"; + if(++$ident{$signi} > 1) + { + print STDERR "WARNING: That symbol is not unique in 31 chars: $name\n"; + } + print $out "#define $sym $name\n"; + } + foreach my $key (keys %{$i->{conditional}}) + { + my ($sym, $guard) = ($key, $i->{conditional}{$key}); + my $name = $i->{prefix}.$sym; + my $signi = substr($name,0,31); + if(++$ident{$signi} > 1) + { + print STDERR "WARNING: That symbol is not unique in 31 chars: $name\n"; + } + print $out "#ifndef $guard\n"; + print $out "#define $sym $name\n"; + print $out "#endif\n"; } - print "#define $sym $name\n"; -} -print "#endif\n"; + print $out "#endif\n"; +} diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 8b024f2e..00000000 --- a/src/Makefile.am +++ /dev/null @@ -1,238 +0,0 @@ -## Makefile.am: produce Makefile.in from this - -## copyright by the mpg123 project - free software under the terms of the LGPL 2.1 -## see COPYING and AUTHORS files in distribution or http://mpg123.org -## initially written by Nicholas J. Humfrey - -AM_CPPFLAGS = -DPKGLIBDIR="\"$(pkglibdir)\"" -mpg123_LDADD = $(LIBLTDL) libmpg123/libmpg123.la @MODULE_OBJ@ @OUTPUT_OBJ@ @OUTPUT_LIBS@ -mpg123_LDFLAGS = @EXEC_LT_LDFLAGS@ @OUTPUT_LDFLAGS@ - -# The dependency on libmpg123 could/should vanish. Or not. -# Does it matter much? -out123_LDADD = $(LIBLTDL) libmpg123/libmpg123.la @MODULE_OBJ@ @OUTPUT_OBJ@ @OUTPUT_LIBS@ -out123_LDFLAGS = @EXEC_LT_LDFLAGS@ @OUTPUT_LDFLAGS@ - -AM_CPPFLAGS += $(LTDLINCL) -I$(top_builddir)/src/libmpg123 -I$(top_srcdir)/src/libmpg123 -# libltdl is not mentioned here... it's not that trivial -mpg123_DEPENDENCIES = @OUTPUT_OBJ@ @MODULE_OBJ@ libmpg123/libmpg123.la -out123_DEPENDENCIES = @OUTPUT_OBJ@ @MODULE_OBJ@ libmpg123/libmpg123.la - -SUBDIRS = output libmpg123 -EXTRA_DIST = legacy_module.c module.c sfifo.c sfifo.h mpg123-with-modules out123-with-modules - - -CLEANFILES = *.a - -bin_PROGRAMS = mpg123 out123 mpg123-id3dump mpg123-strip - -mpg123_id3dump_DEPENDENCIES = libmpg123/libmpg123.la -mpg123_id3dump_LDADD = libmpg123/libmpg123.la - -mpg123_strip_DEPENDENCIES = libmpg123/libmpg123.la -mpg123_strip_LDADD = libmpg123/libmpg123.la - -EXTRA_PROGRAMS = tests/seek_whence tests/noise tests/text tests/plain_id3 - -mpg123_SOURCES = \ - audio.c \ - audio.h \ - buffer.c \ - buffer.h \ - common.c \ - common.h \ - sysutil.c \ - sysutil.h \ - libmpg123/compat.c \ - libmpg123/compat.h \ - control_generic.c \ - equalizer.c \ - getlopt.c \ - getlopt.h \ - httpget.c \ - httpget.h \ - resolver.c \ - resolver.h \ - genre.h \ - genre.c \ - module.h \ - mpg123.c \ - mpg123app.h \ - metaprint.c \ - metaprint.h \ - local.h \ - local.c \ - playlist.c \ - playlist.h \ - streamdump.h \ - streamdump.c \ - term.c \ - term.h \ - wav.c \ - win32_support.h \ - wavhead.h \ - xfermem.c \ - xfermem.h - -# Replace common.h by sysutil.h! -out123_SOURCES = \ - audio.c \ - audio.h \ - buffer.c \ - buffer.h \ - sysutil.c \ - sysutil.h \ - libmpg123/compat.c \ - libmpg123/compat.h \ - getlopt.c \ - getlopt.h \ - module.h \ - out123.c \ - mpg123app.h \ - wav.c \ - win32_support.h \ - wavhead.h \ - xfermem.c \ - xfermem.h - -mpg123_id3dump_SOURCES = mpg123-id3dump.c \ - getlopt.c \ - getlopt.h \ - libmpg123/compat.c \ - libmpg123/compat.h - -mpg123_strip_SOURCES = mpg123-strip.c \ - getlopt.c \ - getlopt.h \ - libmpg123/compat.c \ - libmpg123/compat.h - -if WIN32_CODES -mpg123_SOURCES += \ - win32_support.c \ - win32_net.c - -out123_SOURCES+= \ - win32_support.c - -mpg123_id3dump_SOURCES += \ - win32_support.c -endif - -# That is not nice... but it is how I manage to get the dependency on output/alsa.o without error about .deps/output/alsa.Tpo . -# Did I mention that recursive make sucks? -# Actually ... does that really make really here? Not been updated -# for some time before. I need to revisit our build system. -# `%'-style pattern rules are a GNU make extension -#find output/ -name '*.c' | sort | perl -ne 'chomp; $mod=$_; $mod=~s/\.c$/.\$(OBJEXT)/; -#print "$mod: $_ audio.h module.h\n"; -#print "\tcd output && \$(MAKE)\n\n";' - -output/aix.$(OBJEXT): output/aix.c audio.h module.h - cd output && $(MAKE) - -output/alib.$(OBJEXT): output/alib.c audio.h module.h - cd output && $(MAKE) - -output/alsa.$(OBJEXT): output/alsa.c audio.h module.h - cd output && $(MAKE) - -output/arts.$(OBJEXT): output/arts.c audio.h module.h - cd output && $(MAKE) - -output/coreaudio.$(OBJEXT): output/coreaudio.c audio.h module.h - cd output && $(MAKE) - -output/dummy.$(OBJEXT): output/dummy.c audio.h module.h - cd output && $(MAKE) - -output/esd.$(OBJEXT): output/esd.c audio.h module.h - cd output && $(MAKE) - -output/hp.$(OBJEXT): output/hp.c audio.h module.h - cd output && $(MAKE) - -output/jack.$(OBJEXT): output/jack.c audio.h module.h - cd output && $(MAKE) - -output/mint.$(OBJEXT): output/mint.c audio.h module.h - cd output && $(MAKE) - -output/nas.$(OBJEXT): output/nas.c audio.h module.h - cd output && $(MAKE) - -output/openal.$(OBJEXT): output/openal.c audio.h module.h - cd output && $(MAKE) - -output/os2.$(OBJEXT): output/os2.c audio.h module.h - cd output && $(MAKE) - -output/oss.$(OBJEXT): output/oss.c audio.h module.h - cd output && $(MAKE) - -output/portaudio.$(OBJEXT): output/portaudio.c audio.h module.h - cd output && $(MAKE) - -output/pulse.$(OBJEXT): output/pulse.c audio.h module.h - cd output && $(MAKE) - -output/qsa.$(OBJEXT): output/qsa.c audio.h module.h - cd output && $(MAKE) - -output/sdl.$(OBJEXT): output/sdl.c audio.h module.h - cd output && $(MAKE) - -output/sgi.$(OBJEXT): output/sgi.c audio.h module.h - cd output && $(MAKE) - -output/sndio.$(OBJEXT): output/sndio.c audio.h module.h - cd output && $(MAKE) - -output/sun.$(OBJEXT): output/sun.c audio.h module.h - cd output && $(MAKE) - -output/tinyalsa.$(OBJEXT): output/tinyalsa.c audio.h module.h - cd output && $(MAKE) - -output/win32.$(OBJEXT): output/win32.c audio.h module.h - cd output && $(MAKE) - -output/win32_wasapi.$(OBJEXT): output/win32_wasapi.c audio.h module.h - cd output && $(MAKE) - -# Would have to mention _all_ source files... Dammit, that's what the libmpg123/Makefile.am does! -# But again, the a make $something here needs that stupid rule... WHY??? -libmpg123/libmpg123.la: config.h libmpg123/mpg123.h - cd libmpg123 && $(MAKE) - -tests_seek_whence_SOURCES = \ -tests/seek_whence.c \ -libmpg123/compat.h \ -libmpg123/compat.c - -tests_seek_whence_DEPENDENCIES = libmpg123/libmpg123.la -tests_seek_whence_LDADD = libmpg123/libmpg123.la - -tests_noise_SOURCES = \ -tests/noise.c \ -libmpg123/compat.h \ -libmpg123/compat.c \ -libmpg123/dither.h \ -libmpg123/dither.c - -tests_text_SOURCES = \ -tests/text.c \ -tests/testtext.h \ -libmpg123/compat.h \ -libmpg123/compat.c - -tests_text_DEPENDENCIES = libmpg123/libmpg123.la -tests_text_LDADD = libmpg123/libmpg123.la - -tests_plain_id3_SOURCES = \ -tests/plain_id3.c \ -libmpg123/compat.h \ -libmpg123/compat.c - -tests_plain_id3_DEPENDENCIES = libmpg123/libmpg123.la -tests_plain_id3_LDADD = libmpg123/libmpg123.la diff --git a/src/Makemodule.am b/src/Makemodule.am new file mode 100644 index 00000000..bd70de9b --- /dev/null +++ b/src/Makemodule.am @@ -0,0 +1,161 @@ +# Module for non-recursive mpg123 build system. + +include src/libmpg123/Makemodule.am +include src/libout123/Makemodule.am + +bin_PROGRAMS += \ + src/mpg123 \ + src/out123 \ + src/mpg123-id3dump \ + src/mpg123-strip + +src_mpg123_LDADD = $(LIBLTDL) \ + src/libmpg123/libmpg123.la \ + src/libout123/libout123.la + +src_mpg123_LDFLAGS = @EXEC_LT_LDFLAGS@ $(output_ldflags) + +# The dependency on libmpg123 could/should vanish. Or not. +# Does it matter much? +src_out123_LDADD = $(LIBLTDL) \ + src/libmpg123/libmpg123.la \ + src/libout123/libout123.la + +src_out123_LDFLAGS = @EXEC_LT_LDFLAGS@ $(output_ldflags) + +# The sfifo situation needs to be cleared up. +# It's used from places. +EXTRA_DIST += \ + src/sfifo.c \ + src/sfifo.h \ + src/mpg123-with-modules \ + src/out123-with-modules + +CLEANFILES += src/*.a + +src_mpg123_id3dump_LDADD = src/libmpg123/libmpg123.la + +src_mpg123_strip_LDADD = src/libmpg123/libmpg123.la + +EXTRA_PROGRAMS += \ + src/tests/seek_whence \ + src/tests/noise \ + src/tests/text \ + src/tests/plain_id3 + +src_mpg123_SOURCES = \ + src/audio.c \ + src/audio.h \ + src/common.c \ + src/common.h \ + src/sysutil.c \ + src/sysutil.h \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h \ + src/control_generic.c \ + src/equalizer.c \ + src/getlopt.c \ + src/getlopt.h \ + src/httpget.c \ + src/httpget.h \ + src/resolver.c \ + src/resolver.h \ + src/genre.h \ + src/genre.c \ + src/mpg123.c \ + src/mpg123app.h \ + src/metaprint.c \ + src/metaprint.h \ + src/local.h \ + src/local.c \ + src/playlist.c \ + src/playlist.h \ + src/streamdump.h \ + src/streamdump.c \ + src/term.c \ + src/term.h \ + src/win32_support.h + +# Does that finally work to build/link the correct object file? +src_mpg123_SOURCES += + +# Replace common.h by sysutil.h! +src_out123_SOURCES = \ + src/audio.h \ + src/audio.c \ + src/sysutil.c \ + src/sysutil.h \ + src/common.h \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h \ + src/getlopt.c \ + src/getlopt.h \ + src/out123.c \ + src/mpg123app.h \ + src/win32_support.h + +src_mpg123_id3dump_SOURCES = \ + src/mpg123-id3dump.c \ + src/getlopt.c \ + src/getlopt.h \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h + +src_mpg123_strip_SOURCES = \ + src/mpg123-strip.c \ + src/getlopt.c \ + src/getlopt.h \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h + +if WIN32_CODES +src_mpg123_SOURCES += \ + src/win32_support.c \ + src/win32_net.c + +src_out123_SOURCES+= \ + src/win32_support.c + +src_mpg123_id3dump_SOURCES += \ + src/win32_support.c +endif + +src_tests_seek_whence_SOURCES = \ + src/tests/seek_whence.c \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h + +src_tests_seek_whence_DEPENDENCIES = src/libmpg123/libmpg123.la +src_tests_seek_whence_LDADD = src/libmpg123/libmpg123.la + +src_tests_noise_SOURCES = \ + src/tests/noise.c \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h \ + src/libmpg123/dither.h \ + src/libmpg123/dither_impl.h + +src_tests_text_SOURCES = \ + src/tests/text.c \ + src/tests/testtext.h \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h + +src_tests_text_DEPENDENCIES = src/libmpg123/libmpg123.la +src_tests_text_LDADD = src/libmpg123/libmpg123.la + +src_tests_plain_id3_SOURCES = \ + src/tests/plain_id3.c \ + src/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h + +src_tests_plain_id3_DEPENDENCIES = src/libmpg123/libmpg123.la +src_tests_plain_id3_LDADD = src/libmpg123/libmpg123.la diff --git a/src/audio.c b/src/audio.c index 5f43729a..8d34959c 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1,16 +1,16 @@ /* audio: audio output interface - copyright ?-2008 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp */ #include #include "mpg123app.h" +#include "out123.h" #include "common.h" #include "sysutil.h" -#include "buffer.h" #ifdef HAVE_SYS_WAIT_H #include @@ -18,237 +18,6 @@ #include "debug.h" -static int file_write(struct audio_output_struct* ao, unsigned char *bytes, int count) -{ - return (int)write(ao->fn, bytes, count); -} -static int wave_write(struct audio_output_struct* ao, unsigned char *bytes, int count) -{ - return wav_write(bytes, count); -} -static int builtin_get_formats(struct audio_output_struct *ao) -{ - if(param.outmode == DECODE_CDR) - { - if(ao->rate == 44100 && ao->channels == 2) - return MPG123_ENC_SIGNED_16; - else - return 0; - } - else if(param.outmode == DECODE_AU) return MPG123_ENC_SIGNED_16|MPG123_ENC_UNSIGNED_8|MPG123_ENC_ULAW_8; - else if(param.outmode == DECODE_WAV) return MPG123_ENC_SIGNED_16|MPG123_ENC_UNSIGNED_8|MPG123_ENC_FLOAT_32|MPG123_ENC_SIGNED_24|MPG123_ENC_SIGNED_32; - else return MPG123_ENC_ANY; -} -static int builtin_close(struct audio_output_struct *ao) -{ - switch(param.outmode) - { - case DECODE_WAV: - return wav_close(); - break; - case DECODE_AU: - return au_close(); - break; - case DECODE_CDR: - return cdr_close(); - break; - } - return -1; -} -static int builtin_nothingint(struct audio_output_struct *ao){ return 0; } -static void builtin_nothing(struct audio_output_struct *ao){} - -audio_output_t* open_fake_module(void) -{ - audio_output_t *ao = NULL; - ao = alloc_audio_output(); - if(ao == NULL) - { - error("Cannot allocate memory for audio output data."); - return NULL; - } - ao->module = NULL; - ao->open = builtin_nothingint; - ao->flush = builtin_nothing; - ao->get_formats = builtin_get_formats; - ao->write = wave_write; - ao->close = builtin_close; - ao->device = param.filename; - ao->is_open = FALSE; - switch(param.outmode) - { - case DECODE_FILE: - ao->fn = OutputDescriptor; - ao->write = file_write; - break; - case DECODE_WAV: - ao->open = wav_open; - break; - case DECODE_CDR: - ao->open = cdr_open; - break; - case DECODE_AU: - ao->open = au_open; - break; - case DECODE_TEST: - break; - } - - return ao; -} - -/* Open an audio output module, trying modules in list (comma-separated). */ -audio_output_t* open_output_module( const char* names ) -{ - mpg123_module_t *module = NULL; - audio_output_t *ao = NULL; - int result = 0; - char *curname, *modnames; - - if(param.usebuffer || names==NULL) return NULL; - - /* Use internal code. */ - if(param.outmode != DECODE_AUDIO) return open_fake_module(); - - modnames = strdup(names); - if(modnames == NULL) - { - error("Error allocating memory for module names."); - return NULL; - } - /* Now loop over the list of possible modules to find one that works. */ - curname = strtok(modnames, ","); - while(curname != NULL) - { - char* name = curname; - curname = strtok(NULL, ","); - if(param.verbose > 1) fprintf(stderr, "Trying output module %s.\n", name); - /* Open the module, initial check for availability+libraries. */ - module = open_module( "output", name ); - if(module == NULL) continue; - /* Check if module supports output */ - if(module->init_output == NULL) - { - error1("Module '%s' does not support audio output.", name); - close_module(module); - continue; /* Try next one. */ - } - /* Allocation+initialization of memory for audio output type. */ - ao = alloc_audio_output(); - if(ao==NULL) - { - error("Failed to allocate audio output structure."); - close_module(module); - break; /* This is fatal. */ - } - - /* Call the init function */ - ao->device = param.output_device; - ao->flags = param.output_flags; - /* Should I do funny stuff with stderr file descriptor instead? */ - if(curname == NULL) - { - if(param.verbose > 1) - fprintf(stderr, "Note: %s is the last output option... showing you any error messages now.\n", name); - } - else ao->auxflags |= MPG123_OUT_QUIET; /* Probing, so don't spill stderr with errors. */ - ao->is_open = FALSE; - ao->module = module; /* Need that to close module later. */ - result = module->init_output(ao); - if(result == 0) - { /* Try to open the device. I'm only interested in actually working modules. */ - result = open_output(ao); - close_output(ao); - } - else error2("Module '%s' init failed: %i", name, result); - - if(result!=0) - { /* Try next one... */ - close_module(module); - free(ao); - ao = NULL; - } - else - { /* All good, leave the loop. */ - if(param.verbose > 1) fprintf(stderr, "Output module '%s' chosen.\n", name); - - ao->auxflags &= ~MPG123_OUT_QUIET; - break; - } - } - - free(modnames); - if(ao==NULL) error1("Unable to find a working output module in this list: %s", names); - - return ao; -} - - - -/* Close the audio output and close the module */ -void close_output_module( audio_output_t* ao ) -{ - if (!ao) return; /* That covers buffer mode, too (ao == NULL there). */ - - debug("closing output module"); - /* Close the audio output */ - if(ao->is_open && ao->close != NULL) ao->close(ao); - - /* Deinitialise the audio output */ - if (ao->deinit) ao->deinit( ao ); - - /* Unload the module */ - if (ao->module) close_module( ao->module ); - - /* Free up memory */ - free( ao ); -} - - - -/* allocate and initialise memory */ -audio_output_t* alloc_audio_output() -{ - audio_output_t* ao = malloc( sizeof( audio_output_t ) ); - if (ao==NULL) error( "Failed to allocate memory for audio_output_t." ); - - /* Initialise variables */ - ao->fn = -1; - ao->rate = -1; - ao->gain = param.gain; - ao->userptr = NULL; - ao->device = NULL; - ao->channels = -1; - ao->format = -1; - ao->flags = 0; - ao->auxflags = 0; - - /*ao->module = NULL;*/ - - /* Set the callbacks to NULL */ - ao->open = NULL; - ao->get_formats = NULL; - ao->write = NULL; - ao->flush = NULL; - ao->close = NULL; - ao->deinit = NULL; - - return ao; -} - -/* -static void audio_output_dump(audio_output_t *ao) -{ - fprintf(stderr, "ao->fn=%d\n", ao->fn); - fprintf(stderr, "ao->userptr=%p\n", ao->userptr); - fprintf(stderr, "ao->rate=%ld\n", ao->rate); - fprintf(stderr, "ao->gain=%ld\n", ao->gain); - fprintf(stderr, "ao->device='%s'\n", ao->device); - fprintf(stderr, "ao->channels=%d\n", ao->channels); - fprintf(stderr, "ao->format=%d\n", ao->format); -} -*/ - struct enc_desc { int code; /* MPG123_ENC_SOMETHING */ @@ -320,6 +89,7 @@ const char* audio_encoding_name(const int encoding, const int longer) return name; } + static void capline(mpg123_handle *mh, long rate) { int enci; @@ -347,13 +117,9 @@ void print_capabilities(audio_output_t *ao, mpg123_handle *mh) size_t num_rates; const int *encs; size_t num_encs; - const char *name = ""; - const char *dev = ""; - if(!param.usebuffer) - { - name = ao->module ? ao->module->name : "file/raw/test"; - if(ao->device != NULL) dev = ao->device; - } + char *name; + char *dev; + out123_driver_info(ao, &name, &dev); mpg123_rates(&rates, &num_rates); mpg123_encodings(&encs, &num_encs); fprintf(stderr,"\nAudio driver: %s\nAudio device: %s\nAudio capabilities:\n(matrix of [S]tereo or [M]ono support for sample format and rate in Hz)\n |", name, dev); @@ -387,17 +153,10 @@ void audio_capabilities(audio_output_t *ao, mpg123_handle *mh) mpg123_format_none(mh); /* Start with nothing. */ if(param.force_encoding != NULL) { - int i; if(!param.quiet) fprintf(stderr, "Note: forcing output encoding %s\n", param.force_encoding); - for(i=0;i 2) fprintf(stderr, "Note: checking support for %liHz/%ich.\n", rate, channels); -#ifndef NOXFERMEM - if(param.usebuffer) - { /* Ask the buffer process. It is waiting for this. */ - buffermem->rate = rate; - buffermem->channels = channels; - buffermem->format = 0; /* Just have it initialized safely. */ - debug2("asking for formats for %liHz/%ich", rate, channels); - xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_AUDIOCAP); - xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE); - fmts = buffermem->format; - } - else -#endif - { /* Check myself. */ - ao->rate = rate; - ao->channels = channels; - fmts = ao->get_formats(ao); - } + + fmts = out123_encodings(ao, channels, rate); + if(param.verbose > 2) fprintf(stderr, "Note: result 0x%x\n", fmts); if(force_fmt) { /* Filter for forced encoding. */ @@ -442,257 +186,11 @@ void audio_capabilities(audio_output_t *ao, mpg123_handle *mh) else mpg123_format(mh, decode_rate, channels, fmts); } -#ifndef NOXFERMEM - /* Buffer loop shall start normal operation now. */ - if(param.usebuffer) - { - xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_WAKEUP); - xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE); - } -#endif - if(param.verbose > 1) print_capabilities(ao, mh); } -#if !defined(WIN32) && !defined(GENERIC) -#ifndef NOXFERMEM -static void catch_child(void) -{ - while (waitpid(-1, NULL, WNOHANG) > 0); -} -#endif -#endif - - -/* FIXME: Old output initialization code that needs updating */ - -int init_output(audio_output_t **ao) -{ - static int init_done = FALSE; - - if (init_done) return 1; - init_done = TRUE; - -#ifndef NOXFERMEM - if (param.usebuffer) - { - unsigned int bufferbytes; - sigset_t newsigset, oldsigset; - bufferbytes = (param.usebuffer * 1024); - if (bufferbytes < bufferblock) - { - bufferbytes = 2*bufferblock; - if(!param.quiet) fprintf(stderr, "Note: raising buffer to minimal size %liKiB\n", (unsigned long) bufferbytes>>10); - } - bufferbytes -= bufferbytes % bufferblock; - /* No +1024 for NtoM rounding problems anymore! */ - xfermem_init (&buffermem, bufferbytes ,0,0); - sigemptyset (&newsigset); - /* ThOr: I'm not quite sure why we need to block that signal here. */ - sigaddset (&newsigset, SIGUSR1); - sigprocmask (SIG_BLOCK, &newsigset, &oldsigset); -#if !defined(WIN32) && !defined(GENERIC) - catchsignal (SIGCHLD, catch_child); -#endif - switch ((buffer_pid = fork())) - { - case -1: /* error */ - error("cannot fork!"); - return -1; - case 0: /* child */ - { - /* Buffer process handles all audio stuff itself. */ - audio_output_t *bao = NULL; /* To be clear: That's the buffer's pointer. */ - param.usebuffer = 0; /* The buffer doesn't use the buffer. */ - /* Open audio output module */ - bao = open_output_module(param.output_module); - if(!bao) - { - error("Failed to open audio output module."); - exit(1); /* communicate failure? */ - } - if(open_output(bao) < 0) - { - error("Unable to open audio output."); - close_output_module(bao); - exit(2); - } - xfermem_init_reader (buffermem); - buffer_loop(bao, &oldsigset); /* Here the work happens. */ - xfermem_done_reader (buffermem); - xfermem_done (buffermem); - close_output(bao); - close_output_module(bao); - exit(0); - } - default: /* parent */ - xfermem_init_writer (buffermem); - } - /* ThOr: I want that USR1 signal back for control. */ - sigprocmask(SIG_UNBLOCK, &newsigset, NULL); - } -#else - if(param.usebuffer) - { - error("Buffer not available in this build!"); - return -1; - } -#endif - if(!param.usebuffer) - { /* Only if I handle audio device output: Get that module. */ - *ao = open_output_module(param.output_module); - if(!(*ao)) - { - error("Failed to open audio output module"); - return -1; - } - } - else *ao = NULL; /* That ensures we won't try to free it later... */ -#ifndef NOXFERMEM - if(param.usebuffer) - { /* Check if buffer is alive. */ - int res = xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE); - if(res < 0) - { - error("Buffer process didn't initialize!"); - return -1; - } - } -#endif - /* This has internal protection for buffer mode. */ - if(open_output(*ao) < 0) return -1; - - return 0; -} - -void exit_output(audio_output_t *ao, int rude) -{ - debug("exit output"); -#ifndef NOXFERMEM - if (param.usebuffer) - { - debug("ending buffer"); - buffer_stop(); /* Puts buffer into waiting-for-command mode. */ - buffer_end(rude); /* Gives command to end operation. */ - xfermem_done_writer(buffermem); - waitpid (buffer_pid, NULL, 0); - xfermem_done (buffermem); - } -#endif - /* Close the output... doesn't matter if buffer handled it, that's taken care of. */ - close_output(ao); - close_output_module(ao); -} - -void output_pause(audio_output_t *ao) -{ - if(param.usebuffer) buffer_stop(); - else ao->flush(ao); -} - -void output_unpause(audio_output_t *ao) -{ - if(param.usebuffer) buffer_start(); -} - -int flush_output(audio_output_t *ao, unsigned char *bytes, size_t count) -{ - if(count) - { - /* Error checks? */ -#ifndef NOXFERMEM - if(param.usebuffer){ if(xfermem_write(buffermem, bytes, count)) return -1; } - else -#endif - if(param.outmode != DECODE_TEST) - { - int sum = 0; - int written; - do - { /* Be in a loop for SIGSTOP/CONT */ - written = ao->write(ao, bytes, (int)count); - if(written >= 0){ sum+=written; count -= written; } - else error1("Error in writing audio (%s?)!", strerror(errno)); - } while(count>0 && written>=0); - return sum; - } - } - return (int)count; /* That is for DECODE_TEST */ -} - -int open_output(audio_output_t *ao) -{ - if(param.usebuffer) return 0; - - if(ao == NULL) - { - error("ao should not be NULL here!"); - exit(110); - } - - ao->framesize = ao->channels * mpg123_encsize(ao->format); - - switch(param.outmode) - { - case DECODE_AUDIO: - case DECODE_WAV: - case DECODE_AU: - case DECODE_CDR: - case DECODE_FILE: - debug("opening normal audio/file"); - ao->is_open = ao->open(ao) < 0 ? FALSE : TRUE; - if(!ao->is_open) - { - if(!AOQUIET) error("failed to open audio device"); - return -1; - } - else return 0; - break; - case DECODE_TEST: - debug("decoding to nowhere"); - return 0; - break; - } - debug("nothing"); - return -1; /* That's an error ... unknown outmode? */ -} - -/* is this used? */ -void close_output(audio_output_t *ao) -{ - if(param.usebuffer) return; - - debug("closing output"); - switch(param.outmode) - { - case DECODE_AUDIO: - case DECODE_WAV: - case DECODE_AU: - case DECODE_CDR: - /* Guard that close call; could be nasty. */ - if(ao->is_open) - { - ao->is_open = FALSE; - if(ao->close != NULL) ao->close(ao); - } - break; - } -} - -/* Also for WAV decoding? */ -int reset_output(audio_output_t *ao) -{ - if(!param.usebuffer) - { - close_output(ao); - return open_output(ao); - } - else return 0; -} - int set_pitch(mpg123_handle *fr, audio_output_t *ao, double new_pitch) { - int ret = 1; double old_pitch = param.pitch; long rate; int channels, format; @@ -706,19 +204,13 @@ int set_pitch(mpg123_handle *fr, audio_output_t *ao, double new_pitch) return 0; } - if(param.usebuffer) - { - error("No runtime pitch change with output buffer, sorry."); - return 0; - } - param.pitch = new_pitch; if(param.pitch < -0.99) param.pitch = -0.99; if(channels == 1) smode = MPG123_MONO; if(channels == 2) smode = MPG123_STEREO; - output_pause(ao); + out123_stop(ao); /* Remember: This takes param.pitch into account. */ audio_capabilities(ao, fr); if(!(mpg123_format_support(fr, rate, format) & smode)) @@ -729,13 +221,6 @@ int set_pitch(mpg123_handle *fr, audio_output_t *ao, double new_pitch) error("Reached a hardware limit there with pitch!"); param.pitch = old_pitch; audio_capabilities(ao, fr); - ret = 0; } - ao->format = format; - ao->channels = channels; - ao->rate = pitch_rate(rate); - reset_output(ao); - output_unpause(ao); - return ret; + return out123_start(ao, pitch_rate(rate), channels, format); } - diff --git a/src/audio.h b/src/audio.h index c2607070..8721e455 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,7 +1,9 @@ /* audio: audio output interface - copyright ?-2006 by the mpg123 project - free software under the terms of the LGPL 2.1 + This is what is left after separating out libout123. + + copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp */ @@ -16,86 +18,17 @@ #include "compat.h" #include "mpg123.h" -#include "module.h" - -#define AUDIO_OUT_HEADPHONES 0x01 -#define AUDIO_OUT_INTERNAL_SPEAKER 0x02 -#define AUDIO_OUT_LINE_OUT 0x04 - -enum { - DECODE_TEST, - DECODE_AUDIO, - DECODE_FILE, - DECODE_BUFFER, - DECODE_WAV, - DECODE_AU, - DECODE_CDR, - DECODE_AUDIOFILE -}; - -/* 3% rate tolerance */ -#define AUDIO_RATE_TOLERANCE 3 - -typedef struct audio_output_struct -{ - int fn; /* filenumber */ - void *userptr; /* driver specific pointer */ - - /* Callbacks */ - int (*open)(struct audio_output_struct *); - int (*get_formats)(struct audio_output_struct *); - int (*write)(struct audio_output_struct *, unsigned char *,int); - void (*flush)(struct audio_output_struct *); - int (*close)(struct audio_output_struct *); - int (*deinit)(struct audio_output_struct *); - - /* the module this belongs to */ - mpg123_module_t *module; - - char *device; /* device name */ - int flags; /* some bits; namely headphone/speaker/line */ - long rate; /* sample rate */ - long gain; /* output gain */ - int channels; /* number of channels */ - int format; /* format flags */ - int framesize; /* Output needs data in chunks of framesize bytes. */ - int is_open; /* something opened? */ -#define MPG123_OUT_QUIET 1 - int auxflags; /* For now just one: quiet mode (for probing). */ -} audio_output_t; - -/* Lazy. */ -#define AOQUIET (ao->auxflags & MPG123_OUT_QUIET) - -struct audio_format_name { - int val; - char *name; - char *sname; -}; +#include "out123.h" #define pitch_rate(rate) (param.pitch == 0 ? (rate) : (long) ((param.pitch+1.0)*(rate))) -/* ------ Declarations from "audio.c" ------ */ - -audio_output_t* open_output_module( const char* name ); -void close_output_module( audio_output_t* ao ); -audio_output_t* alloc_audio_output(); -void audio_capabilities(audio_output_t *ao, mpg123_handle *mh); -int audio_fit_capabilities(audio_output_t *ao,int c,int r); +int audio_enc_name2code(const char* name); const char* audio_encoding_name(const int encoding, const int longer); +void audio_enclist(char** list); + +void audio_capabilities(audio_output_t *ao, mpg123_handle *mh); void print_capabilities(audio_output_t *ao, mpg123_handle *mh); -int init_output(audio_output_t **ao); -void exit_output(audio_output_t *ao, int rude); -int flush_output(audio_output_t *ao, unsigned char *bytes, size_t count); -int open_output(audio_output_t *ao); -void close_output(audio_output_t *ao ); -int reset_output(audio_output_t *ao); -void output_pause(audio_output_t *ao); /* Prepare output for inactivity. */ -void output_unpause(audio_output_t *ao); /* Reactivate output (buffer process). */ - -void audio_enclist(char** list); /* Make a string of encoding names. */ - /* Twiddle audio output rate to yield speedup/down (pitch) effect. The actually achieved pitch value is stored in param.pitch. diff --git a/src/buffer.c b/src/buffer.c deleted file mode 100644 index c3b93455..00000000 --- a/src/buffer.c +++ /dev/null @@ -1,316 +0,0 @@ -/* - buffer.c: output buffer - - copyright 1997-2009 by the mpg123 project - free software under the terms of the LGPL 2.1 - see COPYING and AUTHORS files in distribution or http://mpg123.org - initially written by Oliver Fromme - - I (ThOr) am reviewing this file at about the same daytime as Oliver's timestamp here: - Mon Apr 14 03:53:18 MET DST 1997 - - dammed night coders;-) -*/ - -#include "mpg123app.h" - -#ifndef NOXFERMEM - -#include "common.h" -#include "sysutil.h" -#include -#include "debug.h" - -int outburst = 32768; - -static int intflag = FALSE; -static int usr1flag = FALSE; - -static void catch_interrupt (void) -{ - intflag = TRUE; -} - -static void catch_usr1 (void) -{ - usr1flag = TRUE; -} - -/* Interfaces to writer process */ - -extern void buffer_sig(int signal, int block); - -void real_buffer_ignore_lowmem(void) -{ - if (!buffermem) - return; - if(buffermem->wakeme[XF_READER]) - xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_WAKEUP); -} - -void real_buffer_end(int rude) -{ - if (!buffermem) - return; - xfermem_putcmd(buffermem->fd[XF_WRITER], rude ? XF_CMD_ABORT : XF_CMD_TERMINATE); -} - -void real_buffer_resync(void) -{ - if(buffermem->justwait) - { - buffermem->wakeme[XF_WRITER] = TRUE; - xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_RESYNC); - xfermem_getcmd(buffermem->fd[XF_WRITER], TRUE); - } - else buffer_sig(SIGINT, TRUE); -} - -void real_plain_buffer_resync(void) -{ - buffer_sig(SIGINT, FALSE); -} - -void real_buffer_reset(void) -{ - buffer_sig(SIGUSR1, TRUE); -} - -void real_buffer_start(void) -{ - if(buffermem->justwait) - { - debug("ending buffer's waiting"); - buffermem->justwait = FALSE; - xfermem_putcmd(buffermem->fd[XF_WRITER], XF_CMD_WAKEUP); - } -} - -void real_buffer_stop() -{ - buffermem->justwait = TRUE; - buffer_sig(SIGINT, TRUE); -} - -extern int buffer_pid; - -void buffer_sig(int signal, int block) -{ - if (!buffermem) return; - - if (!block) - { /* Just signal, do not wait for anything. */ - kill(buffer_pid, signal); - return; - } - - /* kill() and the waiting needs to be taken care of properly for parallel execution. - Nobody reported issues so far, but I want to be sure. */ - if(xfermem_sigblock(XF_WRITER, buffermem, buffer_pid, signal) != XF_CMD_WAKEUP) - perror("Could not resync/reset buffers"); - return; -} - -void buffer_loop(audio_output_t *ao, sigset_t *oldsigset) -{ - int bytes, outbytes; - int my_fd = buffermem->fd[XF_READER]; - txfermem *xf = buffermem; - int done = FALSE; - int preload; - - catchsignal (SIGINT, catch_interrupt); - catchsignal (SIGUSR1, catch_usr1); - sigprocmask (SIG_SETMASK, oldsigset, NULL); - - xfermem_putcmd(my_fd, XF_CMD_WAKEUP); - - debug("audio output: waiting for cap requests"); - /* wait for audio setup queries */ - while(1) - { - int cmd; - cmd = xfermem_block(XF_READER, xf); - if(cmd == XF_CMD_AUDIOCAP) - { - ao->rate = xf->rate; - ao->channels = xf->channels; - ao->format = ao->get_formats(ao); - debug3("formats for %liHz/%ich: 0x%x", ao->rate, ao->channels, ao->format); - xf->format = ao->format; - xfermem_putcmd(my_fd, XF_CMD_AUDIOCAP); - } - else if(cmd == XF_CMD_WAKEUP) - { - debug("got wakeup... leaving config mode"); - xfermem_putcmd(buffermem->fd[XF_READER], XF_CMD_WAKEUP); - break; - } - else - { - error1("unexpected command %i", cmd); - return; - } - } - - /* Fill complete buffer on first run before starting to play. - * Live mp3 streams constantly approach buffer underrun otherwise. [dk] - */ - preload = (int)(param.preload*xf->size); - if(preload > xf->size) preload = xf->size; - if(preload < 0) preload = 0; - - for (;;) { - if (intflag) { - debug("handle intflag... flushing"); - intflag = FALSE; - ao->flush(ao); - /* Either prepare for waiting or empty buffer now. */ - if(!xf->justwait) xf->readindex = xf->freeindex; - else - { - int cmd; - debug("Prepare for waiting; draining command queue. (There's a lot of wakeup commands pending, usually.)"); - do - { - cmd = xfermem_getcmd(my_fd, FALSE); - /* debug1("drain: %i", cmd); */ - } while(cmd > 0); - } - if(xf->wakeme[XF_WRITER]) xfermem_putcmd(my_fd, XF_CMD_WAKEUP); - } - if (usr1flag) { - debug("handling usr1flag"); - usr1flag = FALSE; - /* close and re-open in order to flush - * the device's internal buffer before - * changing the sample rate. [OF] - */ - /* writer must block when sending SIGUSR1 - * or we will lose all data processed - * in the meantime! [dk] - */ - xf->readindex = xf->freeindex; - /* We've nailed down the new starting location - - * writer is now safe to go on. [dk] - */ - if (xf->wakeme[XF_WRITER]) - xfermem_putcmd(my_fd, XF_CMD_WAKEUP); - ao->rate = xf->rate; - ao->channels = xf->channels; - ao->format = xf->format; - if (reset_output(ao) < 0) { - error1("failed to reset audio: %s", strerror(errno)); - exit(1); - } - } - if ( (bytes = xfermem_get_usedspace(xf)) < outburst ) { - /* if we got a buffer underrun we first - * fill 1/8 of the buffer before continue/start - * playing */ - if (preload < xf->size>>3) - preload = xf->size>>3; - if(preload < outburst) - preload = outburst; - } - debug1("bytes: %i", bytes); - if(xf->justwait || bytes < preload) { - int cmd; - if (done && !bytes) { - break; - } - - if(xf->justwait || !done) { - - /* Don't spill into errno check below. */ - errno = 0; - cmd = xfermem_block(XF_READER, xf); - debug1("got %i", cmd); - switch(cmd) { - - /* More input pending. */ - case XF_CMD_WAKEUP_INFO: - continue; - /* Yes, we know buffer is low but - * know we don't care. - */ - case XF_CMD_WAKEUP: - break; /* Proceed playing. */ - case XF_CMD_ABORT: /* Immediate end, discard buffer contents. */ - return; /* Cleanup happens outside of buffer_loop()*/ - case XF_CMD_TERMINATE: /* Graceful end, playing stuff in buffer and then return. */ - debug("going to terminate"); - done = TRUE; - break; - case XF_CMD_RESYNC: - debug("ordered resync"); - if (param.outmode == DECODE_AUDIO) ao->flush(ao); - - xf->readindex = xf->freeindex; - if (xf->wakeme[XF_WRITER]) xfermem_putcmd(my_fd, XF_CMD_WAKEUP); - continue; - break; - case -1: - if(intflag || usr1flag) /* Got signal, handle it at top of loop... */ - { - debug("buffer interrupted"); - continue; - } - if(errno) - error1("Yuck! Error in buffer handling... or somewhere unexpected: %s", strerror(errno)); - done = TRUE; - xf->readindex = xf->freeindex; - xfermem_putcmd(xf->fd[XF_READER], XF_CMD_TERMINATE); - break; - default: - fprintf(stderr, "\nEh!? Received unknown command 0x%x in buffer process.\n", cmd); - } - } - } - /* Hack! The writer issues XF_CMD_WAKEUP when first adjust - * audio settings. We do not want to lower the preload mark - * just yet! - */ - if (xf->justwait || !bytes) - continue; - preload = outburst; /* set preload to lower mark */ - if (bytes > xf->size - xf->readindex) - bytes = xf->size - xf->readindex; - if (bytes > outburst) - bytes = outburst; - - /* The output can only take multiples of framesize. */ - bytes -= bytes % ao->framesize; - - debug("write"); - outbytes = flush_output(ao, (unsigned char*) xf->data + xf->readindex, bytes); - - if(outbytes < bytes) - { - if(outbytes < 0) outbytes = 0; - if(!intflag && !usr1flag) { - error1("Ouch ... error while writing audio data: %s", strerror(errno)); - /* - * done==TRUE tells writer process to stop - * sending data. There might be some latency - * involved when resetting readindex to - * freeindex so we might need more than one - * cycle to terminate. (The number of cycles - * should be finite unless I managed to mess - * up something. ;-) [dk] - */ - done = TRUE; - xf->readindex = xf->freeindex; - xfermem_putcmd(xf->fd[XF_READER], XF_CMD_TERMINATE); - } - else debug("buffer interrupted"); - } - bytes = outbytes; - - xf->readindex = (xf->readindex + bytes) % xf->size; - if (xf->wakeme[XF_WRITER]) - xfermem_putcmd(my_fd, XF_CMD_WAKEUP); - } -} - -#endif - -/* EOF */ diff --git a/src/buffer.h b/src/buffer.h deleted file mode 100644 index f0d6ffe3..00000000 --- a/src/buffer.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - buffer.h: output buffer - - copyright 1999-2006 by the mpg123 project - free software under the terms of the LGPL 2.1 - see COPYING and AUTHORS files in distribution or http://mpg123.org - initially written by Daniel Kobras / Oliver Fromme -*/ - -/* - * Application specific interaction between main and buffer - * process. This is much less generic than the functions in - * xfermem so I chose to put it in buffer.[hc]. - * 01/28/99 [dk] - */ - -#ifndef _MPG123_BUFFER_H_ -#define _MPG123_BUFFER_H_ - -#ifndef NOXFERMEM -void real_buffer_ignore_lowmem(void); -void real_buffer_end(int rude); -void real_buffer_resync(void); -void real_plain_buffer_resync(void); -void real_buffer_reset(void); -void real_buffer_start(void); -void real_buffer_stop(void); -/* Hm, that's funny preprocessor weirdness. */ -#define buffer_start() (param.usebuffer ? real_buffer_start(),0 : 0) -#define buffer_stop() (param.usebuffer ? real_buffer_stop(),0 : 0) -#define buffer_reset() (param.usebuffer ? real_buffer_reset(),0 : 0) -#define buffer_resync() (param.usebuffer ? real_buffer_resync(),0 : 0) -#define plain_buffer_resync() (param.usebuffer ? real_plain_buffer_resync(),0 : 0) -#define buffer_end(a) (param.usebuffer ? real_buffer_end(a),0 : 0) -#define buffer_ignore_lowmem() (param.usebuffer ? real_buffer_ignore_lowmem(),0 : 0) -#else -#define buffer_start() -#define buffer_stop() -#define buffer_reset() -#define buffer_resync() -#define plain_buffer_resync() -#define buffer_end() -#define buffer_ignore_lowmem() -#endif - -#endif diff --git a/src/common.c b/src/common.c index fd7f8053..9c643e21 100644 --- a/src/common.c +++ b/src/common.c @@ -1,18 +1,19 @@ /* common: misc stuff... audio flush, status display... - copyright ?-2008 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp */ #include "mpg123app.h" +#include "out123.h" #include #include "common.h" #include "debug.h" -const char* rva_name[3] = { "off", "mix", "album" }; +const char* rva_name[3] = { "v", "m", "a" }; /* vanilla, mix, album */ static const char *modes[5] = {"Stereo", "Joint-Stereo", "Dual-Channel", "Single-Channel", "Invalid" }; static const char *smodes[5] = { "stereo", "joint-stereo", "dual-channel", "mono", "invalid" }; static const char *layers[4] = { "Unknown" , "I", "II", "III" }; @@ -25,7 +26,6 @@ static const int samples_per_frame[4][4] = { -1,-1,-1,-1 }, /* Unknown */ }; - /* concurring to print_rheader... here for control_generic */ const char* remote_header_help = "S "; void print_remote_header(mpg123_handle *mh) @@ -128,12 +128,43 @@ static void settle_time(double tim, unsigned long *times, char *sep) } } -void print_stat(mpg123_handle *fr, long offset, long buffsize) +/* Print output buffer fill. */ +void print_buf(const char* prefix, audio_output_t *ao) { - double tim[2]; + long rate; + int framesize; + double tim; + unsigned long times[3]; + char timesep; + size_t buffsize; + + buffsize = out123_buffered(ao); + if(out123_getformat(ao, &rate, NULL, NULL, &framesize)) + return; + tim = (double)(buffsize/framesize)/rate; + settle_time(tim, times, ×ep); + fprintf( stderr, "\r%s[%02lu:%02lu%c%02lu]" + , prefix, times[0], times[1], timesep, times[2] ); +} + +/* Note about position info with buffering: + Negative positions mean that the previous track is still playing from the + buffer. It's a countdown. The frame counter always relates to the last + decoded frame, what entered the buffer right now. */ +void print_stat(mpg123_handle *fr, long offset, audio_output_t *ao) +{ + double tim[3]; off_t rno, no; double basevol, realvol; char *icy; + size_t buffsize; + long rate; + int framesize; + + + buffsize = out123_buffered(ao); + if(out123_getformat(ao, &rate, NULL, NULL, &framesize)) + return; #ifndef WIN32 #ifndef GENERIC /* Only generate new stat line when stderr is ready... don't overfill... */ @@ -151,27 +182,30 @@ void print_stat(mpg123_handle *fr, long offset, long buffsize) } #endif #endif - if( MPG123_OK == mpg123_position(fr, offset, buffsize, &no, &rno, tim, tim+1) - && MPG123_OK == mpg123_getvolume(fr, &basevol, &realvol, NULL) ) + if( MPG123_OK == mpg123_position(fr, offset, buffsize, &no, &rno, tim, tim+1) + && MPG123_OK == mpg123_getvolume(fr, &basevol, &realvol, NULL) ) { int ti; /* Deal with overly long times. */ - unsigned long times[2][3]; - char timesep[2]; - char sign[2] = {' ', ' '}; - for(ti=0; ti<2; ++ti) + unsigned long times[3][3]; + char timesep[3]; + char sign[3] = {' ', ' ', ' '}; + tim[2] = (double)(buffsize/framesize)/rate; + for(ti=0; ti<3; ++ti) { if(tim[ti] < 0.){ sign[ti] = '-'; tim[ti] = -tim[ti]; } settle_time(tim[ti], times[ti], ×ep[ti]); } - fprintf(stderr, "\rFrame# %5"OFF_P" [%5"OFF_P"], Time:%c%02lu:%02lu%c%02lu%c[%02lu:%02lu%c%02lu], RVA:%6s, Vol: %3u(%3u)", + fprintf(stderr, "\rf# %5"OFF_P"[%5"OFF_P"] %c%02lu:%02lu%c%02lu[%c%02lu:%02lu%c%02lu] V(%s)=%3u(%3u)", (off_p)no, (off_p)rno, sign[0], times[0][0], times[0][1], timesep[0], times[0][2], sign[1], times[1][0], times[1][1], timesep[1], times[1][2], rva_name[param.rva], roundui(basevol*100), roundui(realvol*100) ); - if(param.usebuffer) fprintf(stderr,", [%8ld] ",(long)buffsize); + if(param.usebuffer) + fprintf( stderr," [%02lu:%02lu%c%02lu] " + , times[2][0], times[2][1], timesep[2], times[2][2] ); } /* Check for changed tags here too? */ if( mpg123_meta_check(fr) & MPG123_NEW_ICY && MPG123_OK == mpg123_icy(fr, &icy) ) diff --git a/src/common.h b/src/common.h index adc84826..f3445667 100644 --- a/src/common.h +++ b/src/common.h @@ -13,7 +13,7 @@ void print_header(mpg123_handle *); void print_header_compact(mpg123_handle *); -void print_stat(mpg123_handle *fr, long offset, long buffsize); +void print_stat(mpg123_handle *fr, long offset, audio_output_t *ao); void clear_stat(); /* for control_generic */ extern const char* remote_header_help; diff --git a/src/compat.c b/src/compat.c new file mode 100644 index 00000000..096c7b16 --- /dev/null +++ b/src/compat.c @@ -0,0 +1,13 @@ +/* + Just to ensure that libraries and programs get their separate + compatibility object. There should be a compatibility library, + I presume, but I don't want to create another glib, I just want + some internal functions to ease coding. + + I'll sort it out properly sometime. + + I smell symbol conflicts, anyway. Actually wondering why it + worked so far. +*/ + +#include "compat/compat_impl.h" diff --git a/src/libmpg123/compat.h b/src/compat/compat.h similarity index 88% rename from src/libmpg123/compat.h rename to src/compat/compat.h index 7843c48b..a11576d1 100644 --- a/src/libmpg123/compat.h +++ b/src/compat/compat.h @@ -15,7 +15,13 @@ #define MPG123_COMPAT_H #include "config.h" -#include "intsym.h" + +/* Needed for strdup(), in strict mode ... */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif + +#include #ifdef HAVE_STDLIB_H /* realloc, size_t */ @@ -63,6 +69,9 @@ #ifdef HAVE_STRING_H #include #endif +#ifdef HAVE_STRINGS_H +#include +#endif #ifdef OS2 #include @@ -133,6 +142,7 @@ typedef long ssize_p; * @return file descriptor (>=0) or error code. */ int compat_open(const char *filename, int flags); +FILE* compat_fopen(const char *filename, const char *mode); /** * Closing a file handle can be platform specific. @@ -141,6 +151,7 @@ int compat_open(const char *filename, int flags); * @return 0 if the file was successfully closed. A return value of -1 indicates an error. */ int compat_close(int infd); +int compat_fclose(FILE* stream); /* Those do make sense in a separate file, but I chose to include them in compat.c because that's the one source whose object is shared between mpg123 and libmpg123 -- and both need the functionality internally. */ @@ -173,6 +184,12 @@ int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen); int win32_utf8_wide(const char *const mbptr, wchar_t **wptr, size_t *buflen); #endif +/* Blocking write/read of data with signal resilience. + Both continue after being interrupted by signals and always return the + amount of processed data (shortage indicating actual problem or EOF). */ +size_t unintr_write(int fd, void const *buffer, size_t bytes); +size_t unintr_read (int fd, void *buffer, size_t bytes); + /* That one comes from Tellie on OS/2, needed in resolver. */ #ifdef __KLIBC__ typedef int socklen_t; @@ -186,4 +203,8 @@ typedef int socklen_t; #include "true.h" +#if (!defined(WIN32) || defined (__CYGWIN__)) && defined(HAVE_SIGNAL_H) +void (*catchsignal(int signum, void(*handler)()))(); +#endif + #endif diff --git a/src/compat/compat_impl.h b/src/compat/compat_impl.h new file mode 100644 index 00000000..38e1dfef --- /dev/null +++ b/src/compat/compat_impl.h @@ -0,0 +1,244 @@ +/* + compat: Some compatibility functions. + + The mpg123 code is determined to keep it's legacy. A legacy of old, old UNIX. + So anything possibly somewhat advanced should be considered to be put here, with proper #ifdef;-) + + copyright 2007-8 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Thomas Orgis, Windows Unicode stuff by JonY. +*/ + +#include "config.h" +#include "compat.h" + +#ifdef _MSC_VER +#include +#else +#include +#endif +#include + +#ifdef WANT_WIN32_UNICODE +#include +#include +#include +#endif + +#include "debug.h" + +/* A safe realloc also for very old systems where realloc(NULL, size) returns NULL. */ +void *safe_realloc(void *ptr, size_t size) +{ + if(ptr == NULL) return malloc(size); + else return realloc(ptr, size); +} + +#ifndef HAVE_STRERROR +const char *strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + return (errnum < sys_nerr) ? sys_errlist[errnum] : ""; +} +#endif + +#ifndef HAVE_STRDUP +char *strdup(const char *src) +{ + char *dest; + + if (!(dest = (char *) malloc(strlen(src)+1))) + return NULL; + else + return strcpy(dest, src); +} +#endif + +/* Always add a default permission mask in case of flags|O_CREAT. */ +int compat_open(const char *filename, int flags) +{ + int ret; +#if defined (WANT_WIN32_UNICODE) + wchar_t *frag = NULL; + + ret = win32_utf8_wide(filename, &frag, NULL); + /* Fallback to plain open when ucs-2 conversion fails */ + if((frag == NULL) || (ret == 0)) + goto open_fallback; + + /*Try _wopen */ + ret = _wopen(frag, flags|_O_BINARY, _S_IREAD | _S_IWRITE); + if(ret != -1 ) + goto open_ok; /* msdn says -1 means failure */ + +open_fallback: +#endif + +#if (defined(WIN32) && !defined (__CYGWIN__)) + /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */ + /* Try plain old _open(), if it fails, do nothing */ + ret = _open(filename, flags|_O_BINARY, _S_IREAD | _S_IWRITE); +#else + ret = open(filename, flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); +#endif + +#if defined (WANT_WIN32_UNICODE) +open_ok: + /* A cast to void*? Does Windows need that?! */ + free((void *)frag); +#endif + + return ret; +} + +/* Moved over from wav.c, logic with fallbacks added from the + example of compat_open(). */ +FILE* compat_fopen(const char *filename, const char *mode) +{ + FILE* stream = NULL; +#ifdef WANT_WIN32_UNICODE + int cnt = 0; + wchar_t *wname = NULL; + wchat_t *wmode = NULL; + + cnt = win32_utf8_wide(filename, &wname, NULL); + if( (filenamew == NULL) || (cnt == 0)) + goto fopen_fallback; + cnt = win32_utf8_wide(mode, &wmode, NULL); + if( (wmode == NULL) || (cnt == 0)) + goto fopen_fallback; + + stream = _wfopen(filenamew, wmode); + if(stream) goto fopen_ok; + +fopen_fallback: +#endif +#if (defined(WIN32) && !defined (__CYGWIN__)) + stream = _fopen(filename, mode); +#else + stream = fopen(filename, mode); +#endif +#ifdef WANT_WIN32_UNICODE + +fopen_ok: + free(wmode); + free(wname); +#endif + return stream; +} + +int compat_close(int infd) +{ +#if (defined(WIN32) && !defined (__CYGWIN__)) /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */ + return _close(infd); +#else + return close(infd); +#endif +} + +int compat_fclose(FILE *stream) +{ +#if (defined(WIN32) && !defined (__CYGWIN__)) + return _fclose(stream); +#else + return fclose(stream); +#endif +} + +/* Windows Unicode stuff */ + +#ifdef WANT_WIN32_UNICODE +int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen) +{ + size_t len; + char *buf; + int ret = 0; + + len = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, NULL, 0, NULL, NULL); /* Get utf-8 string length */ + buf = calloc(len + 1, sizeof (char)); /* Can we assume sizeof char always = 1? */ + + if(!buf) len = 0; + else { + if (len != 0) ret = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, buf, len, NULL, NULL); /*Do actual conversion*/ + buf[len] = '0'; /* Must terminate */ + } + *mbptr = buf; /* Set string pointer to allocated buffer */ + if(buflen != NULL) *buflen = (len) * sizeof (char); /* Give length of allocated memory if needed. */ + return ret; +} + +int win32_utf8_wide(const char *const mbptr, wchar_t **wptr, size_t *buflen) +{ + size_t len; + wchar_t *buf; + int ret = 0; + + len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, NULL, 0); /* Get converted size */ + buf = calloc(len + 1, sizeof (wchar_t)); /* Allocate memory accordingly */ + + if(!buf) len = 0; + else { + if (len != 0) ret = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, buf, len); /* Do conversion */ + buf[len] = L'0'; /* Must terminate */ + } + *wptr = buf; /* Set string pointer to allocated buffer */ + if (buflen != NULL) *buflen = len * sizeof (wchar_t); /* Give length of allocated memory if needed. */ + return ret; /* Number of characters written */ +} +#endif + + +/* This shall survive signals and any return value less than given byte count + is an error */ +size_t unintr_write(int fd, void const *buffer, size_t bytes) +{ + size_t written = 0; + while(bytes) + { + ssize_t part = write(fd, (char*)buffer+written, bytes); + if(part < 0 && errno != EINTR) + break; + bytes -= part; + written += part; + } + return written; +} + +/* Same for reading the data. */ +size_t unintr_read(int fd, void *buffer, size_t bytes) +{ + size_t got = 0; + while(bytes) + { + ssize_t part = read(fd, (char*)buffer+got, bytes); + if(part < 0 && errno != EINTR) + break; + bytes -= part; + got += part; + } + return got; +} + +#ifndef NO_CATCHSIGNAL +#if (!defined(WIN32) || defined (__CYGWIN__)) && defined(HAVE_SIGNAL_H) +void (*catchsignal(int signum, void(*handler)()))() +{ + struct sigaction new_sa; + struct sigaction old_sa; + +#ifdef DONT_CATCH_SIGNALS + fprintf (stderr, "Not catching any signals.\n"); + return ((void (*)()) -1); +#endif + + new_sa.sa_handler = handler; + sigemptyset(&new_sa.sa_mask); + new_sa.sa_flags = 0; + if(sigaction(signum, &new_sa, &old_sa) == -1) + return ((void (*)()) -1); + return (old_sa.sa_handler); +} +#endif +#endif diff --git a/src/control_generic.c b/src/control_generic.c index 956920a0..c0b2dfb9 100644 --- a/src/control_generic.c +++ b/src/control_generic.c @@ -8,6 +8,7 @@ */ #include "mpg123app.h" +#include "out123.h" #include #include #if !defined (WIN32) || defined (__CYGWIN__) @@ -18,14 +19,12 @@ #include #include "common.h" -#include "buffer.h" #include "genre.h" #include "playlist.h" #define MODE_STOPPED 0 #define MODE_PLAYING 1 #define MODE_PAUSED 2 -extern int buffer_pid; extern audio_output_t *ao; #ifdef FIFO @@ -100,7 +99,7 @@ void generic_sendstat (mpg123_handle *fr) { off_t current_frame, frames_left; double current_seconds, seconds_left; - if(!mpg123_position(fr, 0, xfermem_get_usedspace(buffermem), ¤t_frame, &frames_left, ¤t_seconds, &seconds_left)) + if(!mpg123_position(fr, 0, out123_buffered(ao), ¤t_frame, &frames_left, ¤t_seconds, &seconds_left)) generic_sendmsg("F %"OFF_P" %"OFF_P" %3.2f %3.2f", (off_p)current_frame, (off_p)frames_left, current_seconds, seconds_left); } @@ -213,11 +212,7 @@ void generic_sendinfo (char *filename) static void generic_load(mpg123_handle *fr, char *arg, int state) { - if(param.usebuffer) - { - buffer_resync(); - if(mode == MODE_PAUSED && state != MODE_PAUSED) buffer_start(); - } + out123_drop(ao); if(mode != MODE_STOPPED) { close_track(); @@ -359,7 +354,7 @@ int control_generic (mpg123_handle *fr) { mode = MODE_PAUSED; /* Hm, buffer should be stopped already, shouldn't it? */ - if(param.usebuffer) buffer_stop(); + if(param.usebuffer) out123_pause(ao); generic_sendmsg("P 1"); } else @@ -469,11 +464,11 @@ int control_generic (mpg123_handle *fr) { if (mode == MODE_PLAYING) { mode = MODE_PAUSED; - if(param.usebuffer) buffer_stop(); + out123_pause(ao); generic_sendmsg("P 1"); } else { mode = MODE_PLAYING; - if(param.usebuffer) buffer_start(); + out123_continue(ao); generic_sendmsg("P 2"); } } else generic_sendmsg("P 0"); @@ -483,11 +478,9 @@ int control_generic (mpg123_handle *fr) /* STOP */ if (!strcasecmp(comstr, "S") || !strcasecmp(comstr, "STOP")) { if (mode != MODE_STOPPED) { - if(param.usebuffer) - { - buffer_stop(); - buffer_resync(); - } + /* Do we want to drop here? */ + out123_drop(ao); + out123_stop(ao); close_track(); mode = MODE_STOPPED; generic_sendmsg("P 0"); @@ -694,7 +687,7 @@ int control_generic (mpg123_handle *fr) generic_sendmsg("E Error while seeking: %s", mpg123_strerror(fr)); mpg123_seek(fr, 0, SEEK_SET); } - if(param.usebuffer) buffer_resync(); + out123_drop(ao); newpos = mpg123_tell(fr); if(newpos <= oldpos) mpg123_meta_free(fr); @@ -728,7 +721,7 @@ int control_generic (mpg123_handle *fr) generic_sendmsg("E Error while seeking"); mpg123_seek_frame(fr, 0, SEEK_SET); } - if(param.usebuffer) buffer_resync(); + out123_drop(ao); if(framenum <= oldpos) mpg123_meta_free(fr); generic_sendmsg("J %d", framenum); diff --git a/src/libmpg123/Makefile.am b/src/libmpg123/Makefile.am deleted file mode 100644 index 198ad82f..00000000 --- a/src/libmpg123/Makefile.am +++ /dev/null @@ -1,197 +0,0 @@ -## Makefile.am: produce Makefile.in from this - -## copyright by the mpg123 project - free software under the terms of the LGPL 2.1 -## see COPYING and AUTHORS files in distribution or http://mpg123.org -## initially written by Nicholas J. Humfrey - -#AM_CFLAGS = @AUDIO_CFLAGS@ -#AM_LDFLAGS = -AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/libmpg123 - -EXTRA_DIST = mpg123.h.in - -LIBS = @LIBMPG123_LIBS@ - -EXTRA_PROGRAMS = testcpu -testcpu_dependencies = getcpuflags.$(OBJEXT) -testcpu_sources = testcpu.c -testcpu_LDADD = getcpuflags.$(OBJEXT) - - -CLEANFILES = *.a - - -# The library can have different names, depending on largefile setup. -# Libtool macros think they're smart. Because of that mpg123.la does not work, it must be libmpg123.la . -lib_LTLIBRARIES = libmpg123.la -nodist_include_HEADERS = mpg123.h - -libmpg123_la_LDFLAGS = -no-undefined -version-info @LIBMPG123_VERSION@ -export-symbols-regex '^mpg123_' -libmpg123_la_LIBADD = @DECODER_LOBJ@ @LFS_LOBJ@ -libmpg123_la_DEPENDENCIES = @DECODER_LOBJ@ @LFS_LOBJ@ - -libmpg123_la_SOURCES = \ - intsym.h \ - compat.c \ - compat.h \ - mpeghead.h \ - parse.c \ - parse.h \ - frame.c \ - format.c \ - frame.h \ - reader.h \ - debug.h \ - decode.h \ - sample.h \ - dct64.c \ - synth.h \ - synth_mono.h \ - synth_ntom.h \ - synth_8bit.h \ - synths.h \ - equalizer.c \ - huffman.h \ - newhuffman.h \ - icy.h \ - icy2utf8.h \ - id3.h \ - id3.c \ - true.h \ - getbits.h \ - optimize.h \ - optimize.c \ - readers.c \ - tabinit.c \ - libmpg123.c \ - gapless.h \ - mpg123lib_intern.h \ - abi_align.h \ - mangle.h \ - getcpuflags.h \ - index.h \ - index.c - -EXTRA_libmpg123_la_SOURCES = \ - lfs_alias.c \ - lfs_wrap.c \ - icy.c \ - icy2utf8.c \ - l2tables.h \ - layer1.c \ - layer2.c \ - layer3.c \ - dither.h \ - dither.c \ - feature.c \ - dct36_3dnowext.S \ - dct36_3dnow.S \ - dct36_sse.S \ - dct36_x86_64.S \ - dct36_avx.S \ - dct36_neon.S \ - dct36_neon64.S \ - dct64_3dnowext.S \ - dct64_3dnow.S \ - dct64_altivec.c \ - dct64_i386.c \ - dct64_i486.c \ - dct64_mmx.S \ - dct64_sse.S \ - dct64_sse_float.S \ - dct64_x86_64.S \ - dct64_x86_64_float.S \ - dct64_neon.S \ - dct64_neon_float.S \ - dct64_neon64.S \ - dct64_neon64_float.S \ - dct64_avx.S \ - dct64_avx_float.S \ - synth_3dnowext.S \ - synth_3dnow.S \ - synth_altivec.c \ - synth_i486.c \ - synth_i586_dither.S \ - synth_i586.S \ - synth_mmx.S \ - synth_sse3d.h \ - synth_sse.S \ - synth_sse_float.S \ - synth_sse_s32.S \ - synth_sse_accurate.S \ - synth_stereo_sse_float.S \ - synth_stereo_sse_s32.S \ - synth_stereo_sse_accurate.S \ - synth_x86_64.S \ - synth_x86_64_float.S \ - synth_x86_64_s32.S \ - synth_x86_64_accurate.S \ - synth_stereo_x86_64.S \ - synth_stereo_x86_64_float.S \ - synth_stereo_x86_64_s32.S \ - synth_stereo_x86_64_accurate.S \ - synth_arm.S \ - synth_arm_accurate.S \ - synth_neon.S \ - synth_neon_float.S \ - synth_neon_s32.S \ - synth_neon_accurate.S \ - synth_stereo_neon.S \ - synth_stereo_neon_float.S \ - synth_stereo_neon_s32.S \ - synth_stereo_neon_accurate.S \ - synth_neon64.S \ - synth_neon64_float.S \ - synth_neon64_s32.S \ - synth_neon64_accurate.S \ - synth_stereo_neon64.S \ - synth_stereo_neon64_float.S \ - synth_stereo_neon64_s32.S \ - synth_stereo_neon64_accurate.S \ - synth_stereo_avx.S \ - synth_stereo_avx_float.S \ - synth_stereo_avx_s32.S \ - synth_stereo_avx_accurate.S \ - ntom.c \ - synth.c \ - synth_8bit.c \ - synth_real.c \ - synth_s32.c \ - equalizer_3dnow.S \ - tabinit_mmx.S \ - stringbuf.c \ - getcpuflags.S \ - getcpuflags_x86_64.S \ - getcpuflags_arm.c \ - check_neon.S \ - l12_integer_tables.h \ - l3_integer_tables.h - -if USE_YASM_FOR_AVX -## Override rules for the sources that should be assembled with yasm - -AVX_SRCS = \ - dct36_avx.S \ - dct64_avx.S \ - dct64_avx_float.S \ - synth_stereo_avx.S \ - synth_stereo_avx_float.S \ - synth_stereo_avx_s32.S \ - synth_stereo_avx_accurate.S - -AVX_OBJS = $(AVX_SRCS:.S=.@OBJEXT@) - -ASM_DEPS = \ - mangle.h \ - $(top_builddir)/src/config.h \ - intsym.h - -$(AVX_OBJS): %.@OBJEXT@: %.S $(ASM_DEPS) - $(CPP) $(DEFAULT_INCLUDES) $(INCLUDES) -DASMALIGN_BALIGN $< | @YASM@ - @YASMFLAGS@ @YASM_FORMAT@ -o $@ - -$(AVX_OBJS:.@OBJEXT@=.lo): %.lo: %.@OBJEXT@ - @echo "# Generated by ltmain.sh (GNU libtool)" >$@ - @echo "pic_object='$<'" >>$@ - @echo "non_pic_object='$<'" >>$@ - -endif diff --git a/src/libmpg123/Makemodule.am b/src/libmpg123/Makemodule.am new file mode 100644 index 00000000..d7a677de --- /dev/null +++ b/src/libmpg123/Makemodule.am @@ -0,0 +1,192 @@ +# Module for non-recursive mpg123 build system. + +EXTRA_DIST += src/libmpg123/mpg123.h.in + +EXTRA_PROGRAMS += src/libmpg123/testcpu +src_libmpg123_testcpu_DEPENDENCIES = src/libmpg123/getcpuflags.$(OBJEXT) +src_libmpg123_testcpu_SOURCES = src/libmpg123/testcpu.c +src_libmpg123_testcpu_LDADD = src/libmpg123/getcpuflags.$(OBJEXT) + +# Necessary? +CLEANFILES += src/libmpg123/*.a + +# The library can have different names, depending on largefile setup. +# Libtool macros think they're smart. Because of that mpg123.la +# does not work, it must be libmpg123.la . +lib_LTLIBRARIES += src/libmpg123/libmpg123.la +nodist_include_HEADERS += src/libmpg123/mpg123.h + +src_libmpg123_libmpg123_la_LDFLAGS = \ + -no-undefined \ + -version-info @LIBMPG123_VERSION@ \ + -export-symbols-regex '^mpg123_' +src_libmpg123_libmpg123_la_LIBADD = @DECODER_LOBJ@ @LFS_LOBJ@ @LIBS@ +src_libmpg123_libmpg123_la_DEPENDENCIES = @DECODER_LOBJ@ @LFS_LOBJ@ + +src_libmpg123_libmpg123_la_SOURCES = \ + src/libmpg123/intsym.h \ + src/libmpg123/compat.c \ + src/compat/compat.h \ + src/compat/compat_impl.h \ + src/libmpg123/mpeghead.h \ + src/libmpg123/parse.c \ + src/libmpg123/parse.h \ + src/libmpg123/frame.c \ + src/libmpg123/format.c \ + src/libmpg123/frame.h \ + src/libmpg123/reader.h \ + src/libmpg123/debug.h \ + src/libmpg123/decode.h \ + src/libmpg123/sample.h \ + src/libmpg123/dct64.c \ + src/libmpg123/synth.h \ + src/libmpg123/synth_mono.h \ + src/libmpg123/synth_ntom.h \ + src/libmpg123/synth_8bit.h \ + src/libmpg123/synths.h \ + src/libmpg123/equalizer.c \ + src/libmpg123/huffman.h \ + src/libmpg123/newhuffman.h \ + src/libmpg123/icy.h \ + src/libmpg123/icy2utf8.h \ + src/libmpg123/id3.h \ + src/libmpg123/id3.c \ + src/libmpg123/true.h \ + src/libmpg123/getbits.h \ + src/libmpg123/optimize.h \ + src/libmpg123/optimize.c \ + src/libmpg123/readers.c \ + src/libmpg123/tabinit.c \ + src/libmpg123/libmpg123.c \ + src/libmpg123/gapless.h \ + src/libmpg123/mpg123lib_intern.h \ + src/libmpg123/abi_align.h \ + src/libmpg123/mangle.h \ + src/libmpg123/getcpuflags.h \ + src/libmpg123/index.h \ + src/libmpg123/index.c + +EXTRA_src_libmpg123_libmpg123_la_SOURCES = \ + src/libmpg123/lfs_alias.c \ + src/libmpg123/lfs_wrap.c \ + src/libmpg123/icy.c \ + src/libmpg123/icy2utf8.c \ + src/libmpg123/l2tables.h \ + src/libmpg123/layer1.c \ + src/libmpg123/layer2.c \ + src/libmpg123/layer3.c \ + src/libmpg123/dither.h \ + src/libmpg123/dither_impl.h \ + src/libmpg123/dither.c \ + src/libmpg123/feature.c \ + src/libmpg123/dct36_3dnowext.S \ + src/libmpg123/dct36_3dnow.S \ + src/libmpg123/dct36_sse.S \ + src/libmpg123/dct36_x86_64.S \ + src/libmpg123/dct36_avx.S \ + src/libmpg123/dct36_neon.S \ + src/libmpg123/dct36_neon64.S \ + src/libmpg123/dct64_3dnowext.S \ + src/libmpg123/dct64_3dnow.S \ + src/libmpg123/dct64_altivec.c \ + src/libmpg123/dct64_i386.c \ + src/libmpg123/dct64_i486.c \ + src/libmpg123/dct64_mmx.S \ + src/libmpg123/dct64_sse.S \ + src/libmpg123/dct64_sse_float.S \ + src/libmpg123/dct64_x86_64.S \ + src/libmpg123/dct64_x86_64_float.S \ + src/libmpg123/dct64_neon.S \ + src/libmpg123/dct64_neon_float.S \ + src/libmpg123/dct64_neon64.S \ + src/libmpg123/dct64_neon64_float.S \ + src/libmpg123/dct64_avx.S \ + src/libmpg123/dct64_avx_float.S \ + src/libmpg123/synth_3dnowext.S \ + src/libmpg123/synth_3dnow.S \ + src/libmpg123/synth_altivec.c \ + src/libmpg123/synth_i486.c \ + src/libmpg123/synth_i586_dither.S \ + src/libmpg123/synth_i586.S \ + src/libmpg123/synth_mmx.S \ + src/libmpg123/synth_sse3d.h \ + src/libmpg123/synth_sse.S \ + src/libmpg123/synth_sse_float.S \ + src/libmpg123/synth_sse_s32.S \ + src/libmpg123/synth_sse_accurate.S \ + src/libmpg123/synth_stereo_sse_float.S \ + src/libmpg123/synth_stereo_sse_s32.S \ + src/libmpg123/synth_stereo_sse_accurate.S \ + src/libmpg123/synth_x86_64.S \ + src/libmpg123/synth_x86_64_float.S \ + src/libmpg123/synth_x86_64_s32.S \ + src/libmpg123/synth_x86_64_accurate.S \ + src/libmpg123/synth_stereo_x86_64.S \ + src/libmpg123/synth_stereo_x86_64_float.S \ + src/libmpg123/synth_stereo_x86_64_s32.S \ + src/libmpg123/synth_stereo_x86_64_accurate.S \ + src/libmpg123/synth_arm.S \ + src/libmpg123/synth_arm_accurate.S \ + src/libmpg123/synth_neon.S \ + src/libmpg123/synth_neon_float.S \ + src/libmpg123/synth_neon_s32.S \ + src/libmpg123/synth_neon_accurate.S \ + src/libmpg123/synth_stereo_neon.S \ + src/libmpg123/synth_stereo_neon_float.S \ + src/libmpg123/synth_stereo_neon_s32.S \ + src/libmpg123/synth_stereo_neon_accurate.S \ + src/libmpg123/synth_neon64.S \ + src/libmpg123/synth_neon64_float.S \ + src/libmpg123/synth_neon64_s32.S \ + src/libmpg123/synth_neon64_accurate.S \ + src/libmpg123/synth_stereo_neon64.S \ + src/libmpg123/synth_stereo_neon64_float.S \ + src/libmpg123/synth_stereo_neon64_s32.S \ + src/libmpg123/synth_stereo_neon64_accurate.S \ + src/libmpg123/synth_stereo_avx.S \ + src/libmpg123/synth_stereo_avx_float.S \ + src/libmpg123/synth_stereo_avx_s32.S \ + src/libmpg123/synth_stereo_avx_accurate.S \ + src/libmpg123/ntom.c \ + src/libmpg123/synth.c \ + src/libmpg123/synth_8bit.c \ + src/libmpg123/synth_real.c \ + src/libmpg123/synth_s32.c \ + src/libmpg123/equalizer_3dnow.S \ + src/libmpg123/tabinit_mmx.S \ + src/libmpg123/stringbuf.c \ + src/libmpg123/getcpuflags.S \ + src/libmpg123/getcpuflags_x86_64.S \ + src/libmpg123/getcpuflags_arm.c \ + src/libmpg123/check_neon.S \ + src/libmpg123/l12_integer_tables.h \ + src/libmpg123/l3_integer_tables.h + +if USE_YASM_FOR_AVX +## Override rules for the sources that should be assembled with yasm + +AVX_SRCS = \ + src/libmpg123/dct36_avx.S \ + src/libmpg123/dct64_avx.S \ + src/libmpg123/dct64_avx_float.S \ + src/libmpg123/synth_stereo_avx.S \ + src/libmpg123/synth_stereo_avx_float.S \ + src/libmpg123/synth_stereo_avx_s32.S \ + src/libmpg123/synth_stereo_avx_accurate.S + +AVX_OBJS = $(AVX_SRCS:.S=.@OBJEXT@) + +ASM_DEPS = \ + src/libmpg123/mangle.h \ + $(top_builddir)/src/config.h \ + src/libmpg123/intsym.h + +$(AVX_OBJS): %.@OBJEXT@: %.S $(ASM_DEPS) + $(CPP) $(DEFAULT_INCLUDES) $(INCLUDES) -DASMALIGN_BALIGN $< | @YASM@ - @YASMFLAGS@ @YASM_FORMAT@ -o $@ + +$(AVX_OBJS:.@OBJEXT@=.lo): %.lo: %.@OBJEXT@ + @echo "# Generated by ltmain.sh (GNU libtool)" >$@ + @echo "pic_object='$<'" >>$@ + @echo "non_pic_object='$<'" >>$@ + +endif diff --git a/src/libmpg123/compat.c b/src/libmpg123/compat.c index 18ea76ef..f5ea72d4 100644 --- a/src/libmpg123/compat.c +++ b/src/libmpg123/compat.c @@ -1,138 +1,16 @@ /* - compat: Some compatibility functions. + Just to ensure that libraries and programs get their separate + compatibility object. There should be a compatibility library, + I presume, but I don't want to create another glib, I just want + some internal functions to ease coding. - The mpg123 code is determined to keep it's legacy. A legacy of old, old UNIX. - So anything possibly somewhat advanced should be considered to be put here, with proper #ifdef;-) + I'll sort it out properly sometime. - copyright 2007-8 by the mpg123 project - free software under the terms of the LGPL 2.1 - see COPYING and AUTHORS files in distribution or http://mpg123.org - initially written by Thomas Orgis, Windows Unicode stuff by JonY. + I smell symbol conflicts, anyway. Actually wondering why it + worked so far. */ #include "config.h" -#include "compat.h" - -#ifdef _MSC_VER -#include -#else -#include -#endif -#include - -#ifdef WANT_WIN32_UNICODE -#include -#include -#include -#endif - -#include "debug.h" - -/* A safe realloc also for very old systems where realloc(NULL, size) returns NULL. */ -void *safe_realloc(void *ptr, size_t size) -{ - if(ptr == NULL) return malloc(size); - else return realloc(ptr, size); -} - -#ifndef HAVE_STRERROR -const char *strerror(int errnum) -{ - extern int sys_nerr; - extern char *sys_errlist[]; - - return (errnum < sys_nerr) ? sys_errlist[errnum] : ""; -} -#endif - -#ifndef HAVE_STRDUP -char *strdup(const char *src) -{ - char *dest; - - if (!(dest = (char *) malloc(strlen(src)+1))) - return NULL; - else - return strcpy(dest, src); -} -#endif - -int compat_open(const char *filename, int flags) -{ - int ret; -#if defined (WANT_WIN32_UNICODE) - wchar_t *frag = NULL; - - ret = win32_utf8_wide(filename, &frag, NULL); - if ((frag == NULL) || (ret == 0)) goto fallback; /* Fallback to plain open when ucs-2 conversion fails */ - - ret = _wopen(frag, flags); /*Try _wopen */ - if (ret != -1 ) goto open_ok; /* msdn says -1 means failure */ - -fallback: -#endif - -#if (defined(WIN32) && !defined (__CYGWIN__)) /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */ - ret = _open(filename, flags); /* Try plain old _open(), if it fails, do nothing */ -#else - /* On UNIX, we always add a default permission mask in case flags|O_CREAT. */ - ret = open(filename, flags, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); -#endif - -#if defined (WANT_WIN32_UNICODE) -open_ok: - free ((void *)frag); /* Freeing a NULL should be OK */ -#endif - - return ret; -} - -int compat_close(int infd) -{ -#if (defined(WIN32) && !defined (__CYGWIN__)) /* MSDN says POSIX function is deprecated beginning in Visual C++ 2005 */ - return _close(infd); -#else - return close(infd); -#endif -} - -/* Windows Unicode stuff */ - -#ifdef WANT_WIN32_UNICODE -int win32_wide_utf8(const wchar_t * const wptr, char **mbptr, size_t * buflen) -{ - size_t len; - char *buf; - int ret = 0; - - len = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, NULL, 0, NULL, NULL); /* Get utf-8 string length */ - buf = calloc(len + 1, sizeof (char)); /* Can we assume sizeof char always = 1? */ - - if(!buf) len = 0; - else { - if (len != 0) ret = WideCharToMultiByte(CP_UTF8, 0, wptr, -1, buf, len, NULL, NULL); /*Do actual conversion*/ - buf[len] = '0'; /* Must terminate */ - } - *mbptr = buf; /* Set string pointer to allocated buffer */ - if(buflen != NULL) *buflen = (len) * sizeof (char); /* Give length of allocated memory if needed. */ - return ret; -} - -int win32_utf8_wide(const char *const mbptr, wchar_t **wptr, size_t *buflen) -{ - size_t len; - wchar_t *buf; - int ret = 0; - - len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, NULL, 0); /* Get converted size */ - buf = calloc(len + 1, sizeof (wchar_t)); /* Allocate memory accordingly */ - - if(!buf) len = 0; - else { - if (len != 0) ret = MultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS, mbptr, -1, buf, len); /* Do conversion */ - buf[len] = L'0'; /* Must terminate */ - } - *wptr = buf; /* Set string pointer to allocated buffer */ - if (buflen != NULL) *buflen = len * sizeof (wchar_t); /* Give length of allocated memory if needed. */ - return ret; /* Number of characters written */ -} -#endif +#include "intsym.h" +#define NO_CATCHSIGNAL +#include "compat/compat_impl.h" diff --git a/src/libmpg123/dither.c b/src/libmpg123/dither.c index 30dbdd13..329a4de7 100644 --- a/src/libmpg123/dither.c +++ b/src/libmpg123/dither.c @@ -1,119 +1,3 @@ -/* - dither: Generate shaped noise for dithering - - copyright 2009 by the mpg123 project - free software under the terms of the LGPL 2.1 - see COPYING and AUTHORS files in distribution or http://mpg123.org - initially written by Taihei Monma -*/ - -#include "config.h" -#include "compat.h" -#include "dither.h" - -static const uint32_t init_seed = 2463534242UL; - -#define LAP 100 - -/* - xorshift random number generator, with output scaling to [-0.5, 0.5] - This is the white noise... - See http://www.jstatsoft.org/v08/i14/paper on XOR shift random number generators. -*/ -static float rand_xorshift32(uint32_t *seed) -{ - union - { - uint32_t i; - float f; - } fi; - - fi.i = *seed; - fi.i ^= (fi.i<<13); - fi.i ^= (fi.i>>17); - fi.i ^= (fi.i<<5); - *seed = fi.i; - - /* scale the number to [-0.5, 0.5] */ -#ifdef IEEE_FLOAT - fi.i = (fi.i>>9)|0x3f800000; - fi.f -= 1.5f; -#else - fi.f = (double)fi.i / 4294967295.0; - fi.f -= 0.5f; -#endif - return fi.f; -} - -static void white_noise(float *table, size_t count) -{ - size_t i; - uint32_t seed = init_seed; - - for(i=0; i 2*LAP ? LAP : count/2; - - float input_noise; - float xv[9], yv[9]; - - for(i=0;i<9;i++) - { - xv[i] = yv[i] = 0.0f; - } - - for(i=0;i=lap) table[i-lap] = yv[8] * 3.0f; - } -} - -void mpg123_noise(float* table, size_t count, enum mpg123_noise_type noisetype) -{ - switch(noisetype) - { - case mpg123_white_noise: white_noise(table, count); break; - case mpg123_tpdf_noise: tpdf_noise(table, count); break; - case mpg123_highpass_tpdf_noise: - highpass_tpdf_noise(table, count); - break; - } -} - -/* Generate white noise and shape it with a high pass filter. */ -void dither_table_init(float *dithertable) -{ - highpass_tpdf_noise(dithertable, DITHERSIZE); -} +/* Hack to allow building the same code with and without libtool. */ +#include "intsym.h" +#include "dither_impl.h" diff --git a/src/libmpg123/dither_impl.h b/src/libmpg123/dither_impl.h new file mode 100644 index 00000000..30dbdd13 --- /dev/null +++ b/src/libmpg123/dither_impl.h @@ -0,0 +1,119 @@ +/* + dither: Generate shaped noise for dithering + + copyright 2009 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Taihei Monma +*/ + +#include "config.h" +#include "compat.h" +#include "dither.h" + +static const uint32_t init_seed = 2463534242UL; + +#define LAP 100 + +/* + xorshift random number generator, with output scaling to [-0.5, 0.5] + This is the white noise... + See http://www.jstatsoft.org/v08/i14/paper on XOR shift random number generators. +*/ +static float rand_xorshift32(uint32_t *seed) +{ + union + { + uint32_t i; + float f; + } fi; + + fi.i = *seed; + fi.i ^= (fi.i<<13); + fi.i ^= (fi.i>>17); + fi.i ^= (fi.i<<5); + *seed = fi.i; + + /* scale the number to [-0.5, 0.5] */ +#ifdef IEEE_FLOAT + fi.i = (fi.i>>9)|0x3f800000; + fi.f -= 1.5f; +#else + fi.f = (double)fi.i / 4294967295.0; + fi.f -= 0.5f; +#endif + return fi.f; +} + +static void white_noise(float *table, size_t count) +{ + size_t i; + uint32_t seed = init_seed; + + for(i=0; i 2*LAP ? LAP : count/2; + + float input_noise; + float xv[9], yv[9]; + + for(i=0;i<9;i++) + { + xv[i] = yv[i] = 0.0f; + } + + for(i=0;i=lap) table[i-lap] = yv[8] * 3.0f; + } +} + +void mpg123_noise(float* table, size_t count, enum mpg123_noise_type noisetype) +{ + switch(noisetype) + { + case mpg123_white_noise: white_noise(table, count); break; + case mpg123_tpdf_noise: tpdf_noise(table, count); break; + case mpg123_highpass_tpdf_noise: + highpass_tpdf_noise(table, count); + break; + } +} + +/* Generate white noise and shape it with a high pass filter. */ +void dither_table_init(float *dithertable) +{ + highpass_tpdf_noise(dithertable, DITHERSIZE); +} diff --git a/src/libmpg123/icy.c b/src/libmpg123/icy.c index dca2c5dd..1c287e78 100644 --- a/src/libmpg123/icy.c +++ b/src/libmpg123/icy.c @@ -1,11 +1,13 @@ /* icy: Puny code to pretend for a serious ICY data structure. - copyright 2007 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright 2007-2015 by the mpg123 project + -= free software under the terms of the LGPL 2.1 =- see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Thomas Orgis */ +#include "intsym.h" #include "icy.h" void init_icy(struct icy_meta *icy) diff --git a/src/libmpg123/icy2utf8.c b/src/libmpg123/icy2utf8.c index 4e72162b..3b989f83 100644 --- a/src/libmpg123/icy2utf8.c +++ b/src/libmpg123/icy2utf8.c @@ -26,6 +26,8 @@ * Convert from ICY encoding (windows-1252 codepage) to UTF-8 */ +#include "config.h" +#include "intsym.h" /* Includes string and stdlib headers... */ #include "compat.h" diff --git a/src/libmpg123/index.c b/src/libmpg123/index.c index bb5af327..162237ad 100644 --- a/src/libmpg123/index.c +++ b/src/libmpg123/index.c @@ -1,11 +1,13 @@ /* index: frame index data structure and functions - copyright 2007-8 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright 2007-2015 by the mpg123 project + -= free software under the terms of the LGPL 2.1 =- see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Thomas Orgis */ +#include "intsym.h" #include "index.h" #include "debug.h" diff --git a/src/libmpg123/intsym.h b/src/libmpg123/intsym.h index 14057ff2..9b949b00 100644 --- a/src/libmpg123/intsym.h +++ b/src/libmpg123/intsym.h @@ -1,14 +1,18 @@ -#ifndef MPG123_INTMAP_H -#define MPG123_INTMAP_H +#ifndef MPG123_INTSYM_H +#define MPG123_INTSYM_H /* Mapping of internal mpg123 symbols to something that is less likely to conflict in case of static linking. */ #define COS9 INT123_COS9 #define tfcos36 INT123_tfcos36 #define pnts INT123_pnts #define safe_realloc INT123_safe_realloc #define compat_open INT123_compat_open +#define compat_fopen INT123_compat_fopen #define compat_close INT123_compat_close +#define compat_fclose INT123_compat_fclose #define win32_wide_utf8 INT123_win32_wide_utf8 #define win32_utf8_wide INT123_win32_utf8_wide +#define unintr_write INT123_unintr_write +#define unintr_read INT123_unintr_read #define ntom_set_ntom INT123_ntom_set_ntom #define synth_1to1 INT123_synth_1to1 #define synth_1to1_dither INT123_synth_1to1_dither @@ -29,6 +33,8 @@ #define synth_1to1_arm INT123_synth_1to1_arm #define synth_1to1_neon INT123_synth_1to1_neon #define synth_1to1_stereo_neon INT123_synth_1to1_stereo_neon +#define synth_1to1_neon64 INT123_synth_1to1_neon64 +#define synth_1to1_stereo_neon64 INT123_synth_1to1_stereo_neon64 #define absynth_1to1_i486 INT123_absynth_1to1_i486 #define synth_1to1_mono INT123_synth_1to1_mono #define synth_1to1_m2s INT123_synth_1to1_m2s @@ -75,6 +81,8 @@ #define synth_1to1_real_stereo_altivec INT123_synth_1to1_real_stereo_altivec #define synth_1to1_real_neon INT123_synth_1to1_real_neon #define synth_1to1_real_stereo_neon INT123_synth_1to1_real_stereo_neon +#define synth_1to1_real_neon64 INT123_synth_1to1_real_neon64 +#define synth_1to1_real_stereo_neon64 INT123_synth_1to1_real_stereo_neon64 #define synth_1to1_real_mono INT123_synth_1to1_real_mono #define synth_1to1_real_m2s INT123_synth_1to1_real_m2s #define synth_2to1_real INT123_synth_2to1_real @@ -100,6 +108,8 @@ #define synth_1to1_s32_stereo_altivec INT123_synth_1to1_s32_stereo_altivec #define synth_1to1_s32_neon INT123_synth_1to1_s32_neon #define synth_1to1_s32_stereo_neon INT123_synth_1to1_s32_stereo_neon +#define synth_1to1_s32_neon64 INT123_synth_1to1_s32_neon64 +#define synth_1to1_s32_stereo_neon64 INT123_synth_1to1_s32_stereo_neon64 #define synth_1to1_s32_mono INT123_synth_1to1_s32_mono #define synth_1to1_s32_m2s INT123_synth_1to1_s32_m2s #define synth_2to1_s32 INT123_synth_2to1_s32 @@ -120,8 +130,8 @@ #define dct36 INT123_dct36 #define dct36_3dnow INT123_dct36_3dnow #define dct36_3dnowext INT123_dct36_3dnowext -#define dct36_sse INT123_dct36_sse #define dct36_x86_64 INT123_dct36_x86_64 +#define dct36_sse INT123_dct36_sse #define dct36_avx INT123_dct36_avx #define dct36_neon INT123_dct36_neon #define dct36_neon64 INT123_dct36_neon64 @@ -198,8 +208,11 @@ #define double_to_long_rounded INT123_double_to_long_rounded #define scale_rounded INT123_scale_rounded #define decode_update INT123_decode_update +#define decoder_synth_bytes INT123_decoder_synth_bytes #define samples_to_bytes INT123_samples_to_bytes #define bytes_to_samples INT123_bytes_to_samples +#define outblock_bytes INT123_outblock_bytes +#define postprocess_buffer INT123_postprocess_buffer #define frame_cpu_opt INT123_frame_cpu_opt #define set_synth_functions INT123_set_synth_functions #define dectype INT123_dectype @@ -216,6 +229,10 @@ #define compute_bpf INT123_compute_bpf #define time_to_frame INT123_time_to_frame #define get_songlen INT123_get_songlen +#define bc_prepare INT123_bc_prepare +#define bc_cleanup INT123_bc_cleanup +#define bc_poolsize INT123_bc_poolsize +#define bc_fill INT123_bc_fill #define open_stream INT123_open_stream #define open_stream_handle INT123_open_stream_handle #define open_feed INT123_open_feed @@ -223,20 +240,21 @@ #define feed_forget INT123_feed_forget #define feed_set_pos INT123_feed_set_pos #define open_bad INT123_open_bad +#define check_neon INT123_check_neon #define dct64_3dnow INT123_dct64_3dnow #define dct64_3dnowext INT123_dct64_3dnowext +#define dct64_avx INT123_dct64_avx +#define dct64_real_avx INT123_dct64_real_avx #define dct64_mmx INT123_dct64_mmx #define dct64_MMX INT123_dct64_MMX +#define dct64_neon INT123_dct64_neon +#define dct64_neon64 INT123_dct64_neon64 +#define dct64_real_neon64 INT123_dct64_real_neon64 +#define dct64_real_neon INT123_dct64_real_neon #define dct64_sse INT123_dct64_sse #define dct64_real_sse INT123_dct64_real_sse #define dct64_x86_64 INT123_dct64_x86_64 #define dct64_real_x86_64 INT123_dct64_real_x86_64 -#define dct64_avx INT123_dct64_avx -#define dct64_real_avx INT123_dct64_real_avx -#define dct64_neon INT123_dct64_neon -#define dct64_real_neon INT123_dct64_real_neon -#define dct64_neon64 INT123_dct64_neon64 -#define dct64_real_neon64 INT123_dct64_real_neon64 #define do_equalizer_3dnow INT123_do_equalizer_3dnow #define synth_1to1_3dnow_asm INT123_synth_1to1_3dnow_asm #define synth_1to1_arm_asm INT123_synth_1to1_arm_asm @@ -244,9 +262,29 @@ #define synth_1to1_i586_asm INT123_synth_1to1_i586_asm #define synth_1to1_i586_asm_dither INT123_synth_1to1_i586_asm_dither #define synth_1to1_MMX INT123_synth_1to1_MMX +#define synth_1to1_neon_asm INT123_synth_1to1_neon_asm +#define synth_1to1_neon64_asm INT123_synth_1to1_neon64_asm +#define synth_1to1_neon64_accurate_asm INT123_synth_1to1_neon64_accurate_asm +#define synth_1to1_real_neon64_asm INT123_synth_1to1_real_neon64_asm +#define synth_1to1_s32_neon64_asm INT123_synth_1to1_s32_neon64_asm +#define synth_1to1_neon_accurate_asm INT123_synth_1to1_neon_accurate_asm +#define synth_1to1_real_neon_asm INT123_synth_1to1_real_neon_asm +#define synth_1to1_s32_neon_asm INT123_synth_1to1_s32_neon_asm #define synth_1to1_sse_accurate_asm INT123_synth_1to1_sse_accurate_asm #define synth_1to1_real_sse_asm INT123_synth_1to1_real_sse_asm #define synth_1to1_s32_sse_asm INT123_synth_1to1_s32_sse_asm +#define synth_1to1_s_avx_asm INT123_synth_1to1_s_avx_asm +#define synth_1to1_s_avx_accurate_asm INT123_synth_1to1_s_avx_accurate_asm +#define synth_1to1_real_s_avx_asm INT123_synth_1to1_real_s_avx_asm +#define synth_1to1_s32_s_avx_asm INT123_synth_1to1_s32_s_avx_asm +#define synth_1to1_s_neon_asm INT123_synth_1to1_s_neon_asm +#define synth_1to1_s_neon64_asm INT123_synth_1to1_s_neon64_asm +#define synth_1to1_s_neon64_accurate_asm INT123_synth_1to1_s_neon64_accurate_asm +#define synth_1to1_real_s_neon64_asm INT123_synth_1to1_real_s_neon64_asm +#define synth_1to1_s32_s_neon64_asm INT123_synth_1to1_s32_s_neon64_asm +#define synth_1to1_s_neon_accurate_asm INT123_synth_1to1_s_neon_accurate_asm +#define synth_1to1_real_s_neon_asm INT123_synth_1to1_real_s_neon_asm +#define synth_1to1_s32_s_neon_asm INT123_synth_1to1_s32_s_neon_asm #define synth_1to1_s_sse_accurate_asm INT123_synth_1to1_s_sse_accurate_asm #define synth_1to1_real_s_sse_asm INT123_synth_1to1_real_s_sse_asm #define synth_1to1_s32_s_sse_asm INT123_synth_1to1_s32_s_sse_asm @@ -258,27 +296,12 @@ #define synth_1to1_x86_64_accurate_asm INT123_synth_1to1_x86_64_accurate_asm #define synth_1to1_real_x86_64_asm INT123_synth_1to1_real_x86_64_asm #define synth_1to1_s32_x86_64_asm INT123_synth_1to1_s32_x86_64_asm -#define synth_1to1_s_avx_asm INT123_synth_1to1_s_avx_asm -#define synth_1to1_s_avx_accurate_asm INT123_synth_1to1_s_avx_accurate_asm -#define synth_1to1_real_s_avx_asm INT123_synth_1to1_real_s_avx_asm -#define synth_1to1_s32_s_avx_asm INT123_synth_1to1_s32_s_avx_asm -#define synth_1to1_neon_asm INT123_synth_1to1_neon_asm -#define synth_1to1_neon_accurate_asm INT123_synth_1to1_neon_accurate_asm -#define synth_1to1_real_neon_asm INT123_synth_1to1_real_neon_asm -#define synth_1to1_s32_neon_asm INT123_synth_1to1_s32_neon_asm -#define synth_1to1_s_neon_asm INT123_synth_1to1_s_neon_asm -#define synth_1to1_s_neon_accurate_asm INT123_synth_1to1_s_neon_accurate_asm -#define synth_1to1_real_s_neon_asm INT123_synth_1to1_real_s_neon_asm -#define synth_1to1_s32_s_neon_asm INT123_synth_1to1_s32_s_neon_asm -#define synth_1to1_neon64_asm INT123_synth_1to1_neon64_asm -#define synth_1to1_neon64_accurate_asm INT123_synth_1to1_neon64_accurate_asm -#define synth_1to1_real_neon64_asm INT123_synth_1to1_real_neon64_asm -#define synth_1to1_s32_neon64_asm INT123_synth_1to1_s32_neon64_asm -#define synth_1to1_s_neon64_asm INT123_synth_1to1_s_neon64_asm -#define synth_1to1_s_neon64_accurate_asm INT123_synth_1to1_s_neon64_accurate_asm -#define synth_1to1_real_s_neon64_asm INT123_synth_1to1_real_s_neon64_asm -#define synth_1to1_s32_s_neon64_asm INT123_synth_1to1_s32_s_neon64_asm #define costab_mmxsse INT123_costab_mmxsse #define make_decode_tables_mmx_asm INT123_make_decode_tables_mmx_asm -#define check_neon INT123_check_neon +#ifndef HAVE_STRDUP +#define strdup INT123_strdup +#endif +#ifndef HAVE_STRERROR +#define strerror INT123_strerror +#endif #endif diff --git a/src/legacy_module.c b/src/libmpg123/legacy_module.c similarity index 100% rename from src/legacy_module.c rename to src/libmpg123/legacy_module.c diff --git a/src/libout123/Makemodule.am b/src/libout123/Makemodule.am new file mode 100644 index 00000000..93a7a0b2 --- /dev/null +++ b/src/libout123/Makemodule.am @@ -0,0 +1,54 @@ +# Module for non-recursive mpg123 build system. + +include src/libout123/modules/Makemodule.am + +# Precursor to the proper libout123. +# For now only wrapping the module loader or the legacy module. +noinst_LTLIBRARIES += src/libout123/libmodule.la + +lib_LTLIBRARIES += src/libout123/libout123.la +src_libout123_libout123_la_SOURCES = \ + src/libout123/libout123.c \ + src/libout123/out123.h \ + src/libout123/out123_int.h \ + src/libout123/out123_intsym.h \ + src/libout123/compat.c \ + src/libout123/wav.c \ + src/libout123/wav.h \ + src/libout123/wavhead.h \ + src/libout123/buffer.c \ + src/libout123/buffer.h \ + src/libout123/xfermem.c \ + src/libout123/xfermem.h + +src_libout123_libout123_la_LDFLAGS = \ + -no-undefined -version-info @LIBOUT123_VERSION@ -export-symbols-regex '^out123_' + +src_libout123_libout123_la_LIBADD = \ + src/libout123/libmodule.la \ + src/libout123/modules/libdefaultmodule.la + +src_libout123_libmodule_la_SOURCES = src/libout123/module.h + +if HAVE_MODULES + +src_libout123_libmodule_la_SOURCES += src/libout123/module.c +src_libout123_libmodule_la_LIBADD = -lltdl + +else + +src_libout123_libmodule_la_SOURCES += src/libout123/legacy_module.c + +endif + +# Too lazy to put it into deps for every module. +EXTRA_DIST += src/libout123/outmod_def.h + +# The sfifo code is directly used in some modules. +EXTRA_DIST += \ + src/libout123/out123.h.in \ + src/libout123/sfifo.c \ + src/libout123/sfifo.h + +nodist_include_HEADERS += \ + src/libout123/out123.h diff --git a/src/libout123/buffer.c b/src/libout123/buffer.c new file mode 100644 index 00000000..92a49631 --- /dev/null +++ b/src/libout123/buffer.c @@ -0,0 +1,814 @@ +/* + buffer.c: output buffer + + copyright 1997-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Oliver Fromme + + I (ThOr) am reviewing this file at about the same daytime as Oliver's timestamp here: + Mon Apr 14 03:53:18 MET DST 1997 + - dammed night coders;-) + + This has been heavily reworked to be barely recognizable for the creation of + libout123. There is more structure in the communication, as is necessary if + the libout123 functionality is offered via some API to unknown client + programs instead of being used from mpg123 alone. The basic idea is the same, + the xfermem part only sligthly modified for more synchronization, as I sensed + potential deadlocks. --ThOr +*/ + +/* + Communication to the buffer is normally via xfermem_putcmd() and blocking + on a response, relying on the buffer process periodically checking for + pending commands. + + For more immediate concerns, you can send SIGINT. The only result is that this + interrupts a current device writing operation and causes the buffer to wait + for a following command. +*/ + +#include "buffer.h" +#include "out123_int.h" +#include "xfermem.h" +#include +#include "debug.h" +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#define BUF_CMD_OPEN XF_CMD_CUSTOM1 +#define BUF_CMD_CLOSE XF_CMD_CUSTOM2 +#define BUF_CMD_START XF_CMD_CUSTOM3 +#define BUF_CMD_STOP XF_CMD_CUSTOM4 +#define BUF_CMD_AUDIOCAP XF_CMD_CUSTOM5 +#define BUF_CMD_PARAM XF_CMD_CUSTOM6 +#define BUF_CMD_NDRAIN XF_CMD_CUSTOM7 + +/* TODO: Dynamically allocate that to allow multiple instances. */ +int outburst = 32768; + +/* This is static and global for the forked buffer process. + Another forked buffer process will have its on value. */ +static int intflag = FALSE; + +static void catch_interrupt (void) +{ + intflag = TRUE; +} + +static int write_string(audio_output_t *ao, int who, const char *buf); +static int read_string(audio_output_t *ao +, int who, char **buf, byte *prebuf, int *preoff, int presize); +static int buffer_loop(audio_output_t *ao); + +static void catch_child(void) +{ + /* Disabled for now. We do not really need that. + Rather get return status in a controlled way in buffer_exit(). */ + /* while (waitpid(-1, NULL, WNOHANG) > 0); */ +} + +/* + Functions called from the controlling process. +*/ + +/* Start a buffer process. */ +int buffer_init(audio_output_t *ao, size_t bytes) +{ + buffer_exit(ao); + if(bytes < outburst) bytes = 2*outburst; + +#ifdef DONT_CATCH_SIGNALS +#error I really need to catch signals here! +#endif + xfermem_init(&ao->buffermem, bytes, 0, 0); + /* Is catch_child() really useful? buffer_exit() does waitpid(). + And if buffer_exit() is not called, the main process might be + killed off and not be able to run a signal handler anyway. */ + catchsignal(SIGCHLD, catch_child); + switch((ao->buffer_pid = fork())) + { + case -1: /* error */ + error("cannot fork!"); + goto buffer_init_bad; + case 0: /* child */ + { + int ret; + /* + Ensure the normal default value for buffer_pid to be able + to call normal out123 routines from the buffer proess. + One could keep it at zero and even use this for detecting the + buffer process and do special stuff for that. But the point + is that there shouldn't be special stuff. + */ + ao->buffer_pid = -1; + /* Not preparing audio output anymore, that comes later. */ + xfermem_init_reader(ao->buffermem); + ret = buffer_loop(ao); /* Here the work happens. */ + xfermem_done_reader(ao->buffermem); + xfermem_done(ao->buffermem); + /* Proper cleanup of output handle, including out123_close(). */ + out123_del(ao); + exit(ret); + } + default: /* parent */ + { + int cmd; + xfermem_init_writer(ao->buffermem); + debug("waiting for inital pong from buffer process"); + if( (cmd=xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE)) + != XF_CMD_PONG ) + { + if(!AOQUIET) + error2("Got %i instead of expected initial response %i. Killing rogue buffer process." + , cmd, XF_CMD_PONG); + kill(ao->buffer_pid, SIGKILL); + buffer_exit(ao); + return -1; + } + } + } + + return 0; +buffer_init_bad: + if(ao->buffermem) + { + xfermem_done(ao->buffermem); + ao->buffermem = NULL; + } + return -1; +} + +/* End a buffer process. */ +void buffer_exit(audio_output_t *ao) +{ + int status = 0; + if(ao->buffer_pid == -1) return; + + debug("ending buffer"); + buffer_stop(ao); /* Puts buffer into waiting-for-command mode. */ + buffer_end(ao); /* Gives command to end operation. */ + xfermem_done_writer(ao->buffermem); + waitpid(ao->buffer_pid, &status, 0); + xfermem_done(ao->buffermem); + ao->buffermem = NULL; + ao->buffer_pid = -1; + if(WIFEXITED(status)) + { + int ret = WEXITSTATUS(status); + if(ret && !AOQUIET) + error1("Buffer process isses arose, non-zero return value %i.", ret); + } + else if(!AOQUIET) + error("Buffer process did not exit normally."); +} + +/* + Communication from writer to reader (buffer process). + Remember: The ao struct here is the writer's instance. +*/ + +static int buffer_cmd_finish(audio_output_t *ao) +{ + /* Only if buffer returns XF_CMD_OK we got lucky. Otherwise, we expect + the buffer to deliver a reason right after XF_CMD_ERROR. */ + switch(xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE)) + { + case XF_CMD_OK: return 0; + case XF_CMD_ERROR: + if(!GOOD_READVAL(ao->buffermem->fd[XF_WRITER], ao->errcode)) + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + break; + default: + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } +} + +int buffer_sync_param(audio_output_t *ao) +{ + int writerfd = ao->buffermem->fd[XF_WRITER]; + if(xfermem_putcmd(writerfd, BUF_CMD_PARAM) != 1) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + /* Calling an external serialization routine to avoid forgetting + any fresh parameters here. */ + if(write_parameters(ao, writerfd)) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + return buffer_cmd_finish(ao); +} + +int buffer_open(audio_output_t *ao, const char* driver, const char* device) +{ + int writerfd = ao->buffermem->fd[XF_WRITER]; + + if(xfermem_putcmd(writerfd, BUF_CMD_OPEN) != 1) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + /* Passing over driver and device name. */ + if( write_string(ao, XF_WRITER, driver) + || write_string(ao, XF_WRITER, device) ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + + if(buffer_cmd_finish(ao) == 0) + { + /* Retrieve driver and device name. */ + if( read_string(ao, XF_WRITER, &ao->driver, NULL, NULL, 0) + || read_string(ao, XF_WRITER, &ao->device, NULL, NULL, 0) ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + } + return 0; +} + +int buffer_encodings(audio_output_t *ao) +{ + int writerfd = ao->buffermem->fd[XF_WRITER]; + + if(xfermem_putcmd(writerfd, BUF_CMD_AUDIOCAP) != 1) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + /* Now shoving over the parameters for opening the device. */ + if( + !GOOD_WRITEVAL(writerfd, ao->channels) + || !GOOD_WRITEVAL(writerfd, ao->rate) + ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + + if(buffer_cmd_finish(ao) == 0) + { + int encodings; + /* If all good, the answer can be read how. */ + if(!GOOD_READVAL(writerfd, encodings)) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + else return encodings; + } + else return -1; +} + +int buffer_start(audio_output_t *ao) +{ + int writerfd = ao->buffermem->fd[XF_WRITER]; + if(xfermem_putcmd(writerfd, BUF_CMD_START) != 1) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + /* Now shoving over the parameters for opening the device. */ + if( + !GOOD_WRITEVAL(writerfd, ao->format) + || !GOOD_WRITEVAL(writerfd, ao->channels) + || !GOOD_WRITEVAL(writerfd, ao->rate) + ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return -1; + } + + return buffer_cmd_finish(ao); +} + +#define BUFFER_SIMPLE_CONTROL(name, cmd) \ +void name(audio_output_t *ao) \ +{ \ + xfermem_putcmd(ao->buffermem->fd[XF_WRITER], cmd); \ + xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE); \ +} + +BUFFER_SIMPLE_CONTROL(buffer_stop, BUF_CMD_STOP) +BUFFER_SIMPLE_CONTROL(buffer_continue, XF_CMD_CONTINUE) +BUFFER_SIMPLE_CONTROL(buffer_ignore_lowmem, XF_CMD_IGNLOW) +BUFFER_SIMPLE_CONTROL(buffer_drain, XF_CMD_DRAIN) +BUFFER_SIMPLE_CONTROL(buffer_end, XF_CMD_TERMINATE) +BUFFER_SIMPLE_CONTROL(buffer_close, BUF_CMD_CLOSE) + +#define BUFFER_SIGNAL_CONTROL(name, cmd) \ +void name(audio_output_t *ao) \ +{ \ + kill(ao->buffer_pid, SIGINT); \ + xfermem_putcmd(ao->buffermem->fd[XF_WRITER], cmd); \ + xfermem_getcmd(ao->buffermem->fd[XF_WRITER], TRUE); \ +} + +BUFFER_SIGNAL_CONTROL(buffer_pause, XF_CMD_PAUSE) +BUFFER_SIGNAL_CONTROL(buffer_drop, XF_CMD_DROP) + +size_t buffer_fill(audio_output_t *ao) +{ + return xfermem_get_usedspace(ao->buffermem); +} + +void buffer_ndrain(audio_output_t *ao, size_t bytes) +{ + size_t oldfill; + int writerfd = ao->buffermem->fd[XF_WRITER]; + + oldfill = buffer_fill(ao); + if(xfermem_putcmd(writerfd, BUF_CMD_NDRAIN) != 1) + { + ao->errcode = OUT123_BUFFER_ERROR; + return; + } + /* Now shoving over the parameters for opening the device. */ + if( !GOOD_WRITEVAL(writerfd, bytes) + || !GOOD_WRITEVAL(writerfd, oldfill) ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return; + } + + buffer_cmd_finish(ao); +} + +/* The workhorse: Send data to the buffer with some synchronization and even + error checking. */ +size_t buffer_write(audio_output_t *ao, void *buffer, size_t bytes) +{ + /* + Writing the whole buffer in one piece is no good as that means + waiting for the buffer being empty. That is called a buffer underrun. + We want to refill the buffer before that happens. So, what is sane? + */ + size_t written = 0; + size_t max_piece = ao->buffermem->size / 2; + while(bytes) + { + size_t count_piece = bytes > max_piece + ? max_piece + : bytes; + int ret = xfermem_write(ao->buffermem + , (char*)buffer+written, count_piece); + if(ret) + { + if(!AOQUIET) + error1("writing to buffer memory failed (%i)", ret); + if(ret == XF_CMD_ERROR) + { + /* Buffer tells me that it has an error waiting. */ + if(!GOOD_READVAL(ao->buffermem->fd[XF_WRITER], ao->errcode)) + ao->errcode = OUT123_BUFFER_ERROR; + } + return 0; + } + bytes -= count_piece; + written += count_piece; + } + return written; +} + + +/* + Code for the buffer process itself. +*/ + +/* + +buffer loop: + +{ + 1. normal operation: get data, feed to audio device + (if device open and alive, if data there, if no other command pending) + 2. command response: pause/unpause, open module/device, query caps + + One command at a time, synchronized ... writer process blocks, waiting for + response. +} + +*/ + +/* + Fill buffer to that value when starting playback from stopped state or after + experiencing a serious underrun. + One might also define intermediate preload to recover from underruns. Earlier + code used 1/8 of the buffer. +*/ +static size_t preload_size(audio_output_t *ao) +{ + size_t preload = 0; + txfermem *xf = ao->buffermem; + /* Fill configured part of buffer on first run before starting to play. + * Live mp3 streams constantly approach buffer underrun otherwise. [dk] + */ + if(ao->preload > 0.) preload = (size_t)(ao->preload*xf->size); + if(preload > xf->size/2) preload = xf->size/2; + + return preload; +} + +/* Play one piece of audio from the buffer after settling preload etc. + On error, the device is closed and this naturally stops playback + as that depends on ao->state == play_live. + This plays _at_ _most_ the given amount of bytes, usually less. */ +static void buffer_play(audio_output_t *ao, size_t bytes) +{ + int written; + txfermem *xf = ao->buffermem; + + /* Settle amount of bytes accessible in one block. */ + if (bytes > xf->size - xf->readindex) + bytes = xf->size - xf->readindex; + /* Not more than configured output block. */ + if (bytes > outburst) + bytes = outburst; + /* The output can only take multiples of framesize. */ + bytes -= bytes % ao->framesize; + /* Now do a normal ao->write(), with interruptions by signals + being expected. */ + written = ao->write(ao, (unsigned char*)xf->data+xf->readindex, (int)bytes); + debug2("buffer wrote %i B / %i B to device", written, (int)bytes); + if(written >= 0) + /* Advance read pointer by the amount of written bytes. */ + xf->readindex = (xf->readindex + written) % xf->size; + else + { + ao->errcode = OUT123_DEV_PLAY; + if(!(ao->flags & OUT123_QUIET)) + error1("Error in writing audio (%s?)!", strerror(errno)); + out123_close(ao); + } +} + +/* Now I'm getting really paranoid: Helper to skip bytes from command + channel if we cannot allocate enough memory to hold the data. */ +static void skip_bytes(int fd, size_t count) +{ + while(count) + { + char buf[1024]; + if(!unintr_read(fd, buf, (count < sizeof(buf) ? count : sizeof(buf)))) + return; + } +} + +/* Write a string to command channel. + Return 0 on success, set ao->errcode on issues. */ +static int write_string(audio_output_t *ao, int who, const char *buf) +{ + txfermem *xf = ao->buffermem; + int my_fd = xf->fd[who]; + size_t len; + + /* A NULL string is passed als zero bytes. */ + len = buf ? (strlen(buf)+1) : 0; + if( !GOOD_WRITEVAL(my_fd, len) + || !GOOD_WRITEBUF(my_fd, buf, len) ) + { + ao->errcode = OUT123_BUFFER_ERROR; + return 2; + } + return 0; +} + +/* Read a value from command channel with prebuffer. + This assumes responsible use and avoids needless checking of input. + And, yes, it modifies the preoff argument! + Returns 0 on success, modifies prebuffer fill. */ +int read_buf(int fd, void *addr, size_t size, byte *prebuf, int *preoff, int presize) +{ + size_t need = size; + + if(prebuf) + { + int have = presize - *preoff; + if(have > need) + have = need; + memcpy(addr, prebuf+*preoff, have); + *preoff += have; + addr = (char*)addr+have; + need -= have; + } + if(need) + return !GOOD_READBUF(fd, addr, need); + else + return 0; +} + +/* Read a string from command channel. + Return 0 on success, set ao->errcode on issues. */ +static int read_string(audio_output_t *ao +, int who, char **buf, byte *prebuf, int *preoff, int presize) +{ + txfermem *xf = ao->buffermem; + int my_fd = xf->fd[who]; + size_t len; + + if(*buf) + free(*buf); + *buf = NULL; + + if(read_buf(my_fd, &len, sizeof(len), prebuf, preoff, presize)) + { + ao->errcode = OUT123_BUFFER_ERROR; + return 2; + } + /* If there is an insane length of given, that shall be handled. */ + if(len && !(*buf = malloc(len))) + { + ao->errcode = OUT123_DOOM; + skip_bytes(my_fd, len); + return -1; + } + + if(read_buf(my_fd, *buf, len, prebuf, preoff, presize)) + { + ao->errcode = OUT123_BUFFER_ERROR; + return 2; + } + return 0; +} + + +/* The main loop, returns 0 when no issue occured. */ +int buffer_loop(audio_output_t *ao) +{ + txfermem *xf = ao->buffermem; + int my_fd = xf->fd[XF_READER]; + int preloading = FALSE; + + /* Be prepared to use SIGINT for communication. */ + catchsignal (SIGINT, catch_interrupt); + /* sigprocmask (SIG_SETMASK, oldsigset, NULL); */ + /* Say hello to the writer. */ + xfermem_putcmd(my_fd, XF_CMD_PONG); + + debug1("buffer with preload %g", ao->preload); + while(1) + { + /* If a device is opened and playing, it is our first duty to keep it playing. */ + if(ao->state == play_live) + { + size_t bytes = xfermem_get_usedspace(xf); + debug2( "Play or preload? Got %"SIZE_P" B / %"SIZE_P" B." + , (size_p)bytes, (size_p)preload_size(ao) ); + if(preloading) + preloading = (bytes < preload_size(ao)); + if(!preloading) + { + if(bytes < outburst) + preloading = TRUE; + else + buffer_play(ao, bytes); + } + } + /* Now always check for a pending command, in a blocking way if there is + no playback. */ + debug1("Buffer cmd? (Interruped: %i)", intflag); + /* + The writer only ever signals before sending a command and also waiting + for a response. So, the right place to reset the flag is any time + before giving the response. But let's ensure two things: + 1. The flag really is only cleared when a command response is given. + 2. Command parsing does not stop until a command demanding a response + was handled. + */ + do + { + /* Getting a whole block of commands to efficiently process those + XF_CMD_DATA messages. */ + byte cmd[100]; + int cmdcount; + int i; + + cmdcount = xfermem_getcmds( my_fd + , (preloading || intflag || (ao->state != play_live)) + , cmd + , sizeof(cmd) ); + if(cmdcount < 0) + { + error1("Reading a command set returned %i, my link is broken.", cmdcount); + return 1; + } +#ifdef DEBUG + for(i=0; istate == play_live) + xfermem_putcmd(my_fd, XF_CMD_PONG); + else + { + xfermem_putcmd(my_fd, XF_CMD_ERROR); + if(ao->errcode == OUT123_OK) + ao->errcode = OUT123_NOT_LIVE; + if(!GOOD_WRITEVAL(my_fd, ao->errcode)) + return 2; + } + break; + case BUF_CMD_PARAM: + intflag = FALSE; + /* If that does not work, communication is broken anyway and + writer will notice soon enough. */ + read_parameters(ao, my_fd, cmd, &i, cmdcount); + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case BUF_CMD_OPEN: + { + char *driver = NULL; + char *device = NULL; + int success; + + intflag = FALSE; + success = ( + !read_string(ao, XF_READER, &driver, cmd, &i, cmdcount) + && !read_string(ao, XF_READER, &device, cmd, &i, cmdcount) + && !out123_open(ao, driver, device) + ); + free(device); + free(driver); + if(success) + { + xfermem_putcmd(my_fd, XF_CMD_OK); + if( write_string(ao, XF_READER, ao->driver) + || write_string(ao, XF_READER, ao->device) ) + return 2; + } + else + { + xfermem_putcmd(my_fd, XF_CMD_ERROR); + /* Again, no sense to bitch around about communication errors, + just quit. */ + if(!GOOD_WRITEVAL(my_fd, ao->errcode)) + return 2; + } + } + break; + case BUF_CMD_CLOSE: + intflag = FALSE; + out123_close(ao); + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case BUF_CMD_AUDIOCAP: + { + int encodings; + + intflag = FALSE; + if( + !GOOD_READVAL_BUF(my_fd, ao->channels) + || !GOOD_READVAL_BUF(my_fd, ao->rate) + ) + return 2; + encodings = out123_encodings(ao, ao->channels, ao->rate); + if(encodings >= 0) + { + xfermem_putcmd(my_fd, XF_CMD_OK); + if(!GOOD_WRITEVAL(my_fd, encodings)) + return 2; + } + else + { + xfermem_putcmd(my_fd, XF_CMD_ERROR); + if(!GOOD_WRITEVAL(my_fd, ao->errcode)) + return 2; + } + } + break; + case BUF_CMD_START: + intflag = FALSE; + if( + !GOOD_READVAL_BUF(my_fd, ao->format) + || !GOOD_READVAL_BUF(my_fd, ao->channels) + || !GOOD_READVAL_BUF(my_fd, ao->rate) + ) + return 2; + if(!out123_start(ao, ao->format, ao->channels, ao->rate)) + { + preloading = TRUE; + xfermem_putcmd(my_fd, XF_CMD_OK); + } + else + { + xfermem_putcmd(my_fd, XF_CMD_ERROR); + if(!GOOD_WRITEVAL(my_fd, ao->errcode)) + return 2; + } + break; + case BUF_CMD_STOP: + intflag = FALSE; + if(ao->state == play_live) + { /* Drain is implied! */ + size_t bytes; + while((bytes = xfermem_get_usedspace(xf))) + buffer_play(ao, bytes); + } + out123_stop(ao); + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case XF_CMD_CONTINUE: + intflag = FALSE; + out123_continue(ao); + preloading = TRUE; + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case XF_CMD_IGNLOW: + intflag = FALSE; + preloading = FALSE; + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case XF_CMD_DRAIN: + intflag = FALSE; + if(ao->state == play_live) + { + size_t bytes; + while( + (bytes = xfermem_get_usedspace(xf)) + && bytes > ao->framesize + ) + buffer_play(ao, bytes); + out123_drain(ao); + } + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case BUF_CMD_NDRAIN: + { + size_t limit; + size_t oldfill; + + intflag = FALSE; + if( + !GOOD_READVAL_BUF(my_fd, limit) + || !GOOD_READVAL_BUF(my_fd, oldfill) + ) + return 2; + if(ao->state == play_live) + { + size_t bytes; + while( + (bytes = xfermem_get_usedspace(xf)) + && bytes > ao->framesize + && oldfill >= bytes /* paranoia, overflow would handle it anyway */ + && (oldfill-bytes) < limit + ) + buffer_play(ao, bytes > limit ? limit : bytes); + out123_drain(ao); + debug2( "buffer drained %"SIZE_P" / %"SIZE_P + , oldfill-bytes, limit ); + } + xfermem_putcmd(my_fd, XF_CMD_OK); + } + break; + case XF_CMD_TERMINATE: + intflag = FALSE; + /* Will that response always reach the writer? Well, at worst, + it's an ignored error on xfermem_getcmd(). */ + xfermem_putcmd(my_fd, XF_CMD_OK); + return 0; + case XF_CMD_PAUSE: + intflag = FALSE; + out123_pause(ao); + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + case XF_CMD_DROP: + intflag = FALSE; + xf->readindex = xf->freeindex; + out123_drop(ao); + xfermem_putcmd(my_fd, XF_CMD_OK); + break; + default: + if(!AOQUIET) + error1("Unknown command %u encountered. Confused Suicide!", cmd[i]); + return 1; +#undef GOOD_READVAL_BUF + } + } /* Ensure that an interrupt-giving command has been received. */ + while(intflag); + if(intflag && !AOQUIET) + error("buffer: The intflag should not be set anymore."); + intflag = FALSE; /* Any possible harm by _not_ ensuring that the flag is cleared here? */ + } +} diff --git a/src/libout123/buffer.h b/src/libout123/buffer.h new file mode 100644 index 00000000..499c5177 --- /dev/null +++ b/src/libout123/buffer.h @@ -0,0 +1,59 @@ +/* + buffer.h: output buffer + + copyright 1999-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Daniel Kobras / Oliver Fromme +*/ + +/* + * Application specific interaction between main and buffer + * process. This is much less generic than the functions in + * xfermem so I chose to put it in buffer.[hc]. + * 01/28/99 [dk] + */ + +#ifndef _MPG123_BUFFER_H_ +#define _MPG123_BUFFER_H_ + +#include "out123_int.h" +#include "compat.h" + +int buffer_init(audio_output_t *ao, size_t bytes); +void buffer_exit(audio_output_t *ao); + +/* Messages with payload. */ + +int buffer_sync_param(audio_output_t *ao); +int buffer_open(audio_output_t *ao, const char* driver, const char* device); +int buffer_encodings(audio_output_t *ao); +int buffer_start(audio_output_t *ao); +void buffer_ndrain(audio_output_t *ao, size_t bytes); + +/* Simple messages to be deal with after playback. */ + +void buffer_stop(audio_output_t *ao); +void buffer_close(audio_output_t *ao); +void buffer_continue(audio_output_t *ao); +/* Still undecided if that one is to be used anywhere. */ +void buffer_ignore_lowmem(audio_output_t *ao); +void buffer_drain(audio_output_t *ao); +void buffer_end(audio_output_t *ao); + +/* Simple messages with interruption of playback. */ + +void buffer_pause(audio_output_t *ao); +void buffer_drop(audio_output_t *ao); + +/* The actual work: Hand over audio data. */ +size_t buffer_write(audio_output_t *ao, void *buffer, size_t bytes); + +/* Thin wrapper over xfermem giving the current buffer fill. */ +size_t buffer_fill(audio_output_t *ao); + +/* Special handler to safely read values from command channel with + an additional buffer handed in. Exported for read_parameters(). */ +int read_buf(int fd, void *addr, size_t size +, byte *prebuf, int *preoff, int presize); + +#endif diff --git a/src/libout123/compat.c b/src/libout123/compat.c new file mode 100644 index 00000000..7f1f8dd5 --- /dev/null +++ b/src/libout123/compat.c @@ -0,0 +1,15 @@ +/* + Just to ensure that libraries and programs get their separate + compatibility object. There should be a compatibility library, + I presume, but I don't want to create another glib, I just want + some internal functions to ease coding. + + I'll sort it out properly sometime. + + I smell symbol conflicts, anyway. Actually wondering why it + worked so far. +*/ + +#include "config.h" +#include "out123_intsym.h" +#include "compat/compat_impl.h" diff --git a/src/libout123/legacy_module.c b/src/libout123/legacy_module.c new file mode 100644 index 00000000..3a9d2172 --- /dev/null +++ b/src/libout123/legacy_module.c @@ -0,0 +1,95 @@ +/* + legacy_module.c: dummy interface to modular code loader for legacy build system + + copyright 2008 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Nicholas J Humfrey +*/ + +#include "out123_int.h" +#include "debug.h" + +/* A single module is staticly compiled in for each type */ +extern mpg123_module_t mpg123_output_module_info; +/* extern mpg123_module_t mpg123_input_module_info; */ + + +/* Open a module */ +mpg123_module_t* +open_module(const char* type, const char* name, int verbose) +{ + mpg123_module_t *mod = NULL; + + /* Select the module info structure, based on the desired type */ + if (strcmp(type, "output")==0) { + mod = &mpg123_output_module_info; +/* + } else if (strcmp(type, "input")==0) { + mod = &mpg123_input_module_info; +*/ + } else { + if(verbose >= 0) + error1("Unable to open module type '%s'.", type); + return NULL; + } + + /* Check the module compiled in is the module requested */ + if (strcmp(name, mod->name)!=0) { + if(verbose >= 0) + { + error1("Unable to open requested module '%s'.", name); + error1("The only available statically compiled module is '%s'." + , mod->name); + } + return NULL; + } + + /* Debugging info */ + debug1("Details of static module type '%s':", type); + debug1(" api_version=%d", mod->api_version); + debug1(" name=%s", mod->name); + debug1(" description=%s", mod->description); + debug1(" revision=%s", mod->revision); + debug1(" handle=%p", (void*)mod->handle); + + return mod; +} + + +void close_module(mpg123_module_t* module, int verbose) +{ + debug("close_module()"); + + /* Module was never really 'loaded', so nothing to do here. */ +} + + +int list_modules(const char *type, char ***names, char ***descr, int verbose) +{ + debug("list_modules()" ); + + *names = NULL; + *descr = NULL; + + if( + (*names=malloc(sizeof(char*))) + && !((*names)[0]=NULL) /* for safe cleanup */ + && ((*names)[0]=strdup(mpg123_output_module_info.name)) + && (*descr=malloc(sizeof(char*))) + && !((*descr)[0]=NULL) /* for safe cleanup */ + && ((*descr)[0]=strdup(mpg123_output_module_info.description)) + ) + return 1; + else + { + if(*names) + free((*names)[0]); + free(*names); + if(*descr) + free((*descr)[0]); + free(*descr); + return -1; + } +} + + diff --git a/src/libout123/libout123.c b/src/libout123/libout123.c new file mode 100644 index 00000000..14b7ba60 --- /dev/null +++ b/src/libout123/libout123.c @@ -0,0 +1,885 @@ +/* + audio: audio output interface + + copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 + see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Michael Hipp +*/ + +#include +#include "out123_int.h" +#include "wav.h" +#include "buffer.h" + +#include "debug.h" + +static int have_buffer(audio_output_t *ao) +{ + return (ao->buffer_pid != -1); +} + +static int modverbose(audio_output_t *ao) +{ + return AOQUIET + ? -1 + : ao->verbose; +} + +static void check_output_module( audio_output_t *ao +, const char *name, const char *device, int final ); + +static void out123_clear_module(audio_output_t *ao) +{ + ao->open = NULL; + ao->get_formats = NULL; + ao->write = NULL; + ao->flush = NULL; + ao->drain = NULL; + ao->close = NULL; + ao->deinit = NULL; + + ao->module = NULL; + ao->userptr = NULL; + ao->fn = -1; +} + +audio_output_t* out123_new(void) +{ + audio_output_t* ao = malloc( sizeof( audio_output_t ) ); + if(!ao) + return NULL; + ao->errcode = 0; +#ifndef NOXFERMEM + ao->buffer_pid = -1; + ao->buffer_fd[0] = -1; + ao->buffer_fd[1] = -1; + ao->buffermem = NULL; +#endif + + out123_clear_module(ao); + ao->driver = NULL; + ao->device = NULL; + + ao->flags = 0; + ao->rate = -1; + ao->gain = -1; + ao->channels = -1; + ao->format = -1; + ao->framesize = 0; + ao->state = play_dead; + ao->auxflags = 0; + ao->preload = 0.; + ao->verbose = 0; + ao->device_buffer = 0.; + return ao; +} + +void out123_del(audio_output_t *ao) +{ + debug1("out123_del(%p)", (void*)ao); + if(!ao) return; + + out123_close(ao); /* TODO: That talks to the buffer if present. */ + out123_set_buffer(ao, 0); +#ifndef NOXFERMEM + if(have_buffer(ao)) buffer_exit(ao); +#endif + free(ao); +} + +/* Error reporting */ + +/* Carefully keep that in sync with the error enum! */ +static const char *const errstring[OUT123_ERRCOUNT] = +{ + "no problem" +, "out of memory" +, "bad driver name" +, "failure loading driver module" +, "no driver loaded" +, "no active audio device" +, "some device playback error" +, "failed to open device" +, "buffer (communication) error" +, "basic module system error" +}; + +const char* out123_strerror(audio_output_t *ao) +{ + return out123_plain_strerror(out123_errcode(ao)); +} + +int out123_errcode(audio_output_t *ao) +{ + if(!ao) return OUT123_ERR; + else return ao->errcode; +} + +const char* out123_plain_strerror(int errcode) +{ + if(errcode >= OUT123_ERRCOUNT || errcode < 0) + return "invalid error code"; + + /* Let's be paranoid, one _may_ forget to extend errstrings when + adding a new entry to the enum. */ + if(errcode >= sizeof(errstring)/sizeof(char*)) + return "outdated error list (library bug)"; + + return errstring[errcode]; +} + +static int out123_seterr(audio_output_t *ao, enum out123_error errcode) +{ + if(!ao) + return OUT123_ERR; + ao->errcode = errcode; + return errcode == OUT123_OK ? OUT123_OK : OUT123_ERR; +} + +/* pre-playback setup */ + +int out123_set_buffer(audio_output_t *ao, size_t buffer_bytes) +{ + debug2("out123_set_buffer(%p, %"SIZE_P")", (void*)ao, (size_p)buffer_bytes); + if(!ao) + return OUT123_ERR; + ao->errcode = 0; + /* Close any audio output module if present, also kill of buffer if present, + then start new buffer process with newly allocated storage if given + size is non-zero. */ + out123_close(ao); +#ifndef NOXFERMEM + if(have_buffer(ao)) + buffer_exit(ao); + if(buffer_bytes) + return buffer_init(ao, buffer_bytes); +#endif + return 0; +} + +int out123_param( audio_output_t *ao, enum out123_parms code +, long value, double fvalue ) +{ + int ret = 0; + + debug4("out123_param(%p, %i, %li, %g)", (void*)ao, (int)code, value, fvalue); + if(!ao) + return OUT123_ERR; + ao->errcode = 0; + + switch(code) + { + case OUT123_FLAGS: + ao->flags = (int)value; + break; + case OUT123_PRELOAD: + ao->preload = fvalue; + break; + case OUT123_GAIN: + ao->gain = value; + break; + case OUT123_VERBOSE: + ao->verbose = (int)value; + break; + case OUT123_DEVICEBUFFER: + ao->device_buffer = fvalue; + break; + default: + error1("bad parameter code %i", (int)code); + ret = -1; + } +#ifndef NOXFERMEM + /* If there is a buffer, it needs to update its copy of parameters. */ + if(have_buffer(ao)) + /* No error check; if that fails, buffer is dead and we will notice + soon enough. */ + buffer_sync_param(ao); +#endif + return ret; +} + +int out123_getparam( audio_output_t *ao, enum out123_parms code +, long *ret_value, double *ret_fvalue ) +{ + int ret = 0; + long value = 0; + double fvalue = 0.; + + debug4( "out123_getparam(%p, %i, %p, %p)" + , (void*)ao, (int)code, (void*)ret_value, (void*)ret_fvalue ); + if(!ao) + return OUT123_ERR; + ao->errcode = 0; + + switch(code) + { + case OUT123_FLAGS: + value = ao->flags; + break; + case OUT123_PRELOAD: + fvalue = ao->preload; + break; + case OUT123_GAIN: + value = ao->gain; + break; + case OUT123_VERBOSE: + value = ao->verbose; + break; + case OUT123_DEVICEBUFFER: + fvalue = ao->device_buffer; + break; + default: + error1("bad parameter code %i", (int)code); + ret = OUT123_ERR; + } + if(!ret) + { + if(ret_value) *ret_value = value; + if(ret_fvalue) *ret_fvalue = fvalue; + } + return ret; +} + +int out123_param_from(audio_output_t *ao, audio_output_t* from_ao) +{ + debug2("out123_param_from(%p, %p)", (void*)ao, (void*)from_ao); + if(!ao || !from_ao) return -1; + + ao->flags = from_ao->flags; + ao->preload = from_ao->preload; + ao->gain = from_ao->gain; + ao->device_buffer = from_ao->device_buffer; + ao->verbose = from_ao->verbose; + + return 0; +} + +/* Serialization of tunable parameters to communicate them between + main process and buffer. Make sure these two stay in sync ... */ + +int write_parameters(audio_output_t *ao, int fd) +{ + if( + GOOD_WRITEVAL(fd, ao->flags) + && GOOD_WRITEVAL(fd, ao->preload) + && GOOD_WRITEVAL(fd, ao->gain) + && GOOD_WRITEVAL(fd, ao->device_buffer) + && GOOD_WRITEVAL(fd, ao->verbose) + ) + return 0; + else + return -1; +} + +int read_parameters(audio_output_t *ao +, int fd, byte *prebuf, int *preoff, int presize) +{ +#define GOOD_READVAL_BUF(fd, val) \ + !read_buf(fd, &val, sizeof(val), prebuf, preoff, presize) + if( + GOOD_READVAL_BUF(fd, ao->flags) + && GOOD_READVAL_BUF(fd, ao->preload) + && GOOD_READVAL_BUF(fd, ao->gain) + && GOOD_READVAL_BUF(fd, ao->device_buffer) + && GOOD_READVAL_BUF(fd, ao->verbose) + ) + return 0; + else + return -1; +#undef GOOD_READVAL_BUF +} + +int out123_open(audio_output_t *ao, const char* driver, const char* device) +{ + debug3( "out123_open(%p, %s, %s)", (void*)ao + , driver ? driver : "", device ? device : "" ); + if(!ao) + return OUT123_ERR; + ao->errcode = 0; + + out123_close(ao); + debug("out123_open() continuing"); + + /* Ensure that audio format is freshly set for "no format yet" mode. + In out123_start*/ + ao->rate = -1; + ao->channels = -1; + ao->format = -1; + +#ifndef NOXFERMEM + if(have_buffer(ao)) + { + if(buffer_open(ao, driver, device)) + return OUT123_ERR; + } + else +#endif + { + /* We just quickly check if the device can be accessed at all, + same as out123_encodings! */ + char *nextname, *modnames; + const char *names = driver ? driver : DEFAULT_OUTPUT_MODULE; + + if(!names) return out123_seterr(ao, OUT123_BAD_DRIVER_NAME); + + /* It is ridiculous how these error messages are larger than the pieces + of memory they are about! */ + if(device && !(ao->device = strdup(device))) + { + if(!AOQUIET) error("OOM device name copy"); + return out123_seterr(ao, OUT123_DOOM); + } + + if(!(modnames = strdup(names))) + { + out123_close(ao); /* Frees ao->device, too. */ + if(!AOQUIET) error("OOM driver names"); + return out123_seterr(ao, OUT123_DOOM); + } + + /* Now loop over the list of possible modules to find one that works. */ + nextname = strtok(modnames, ","); + while(!ao->open && nextname) + { + char *curname = nextname; + nextname = strtok(NULL, ","); + check_output_module(ao, curname, device, !nextname); + if(ao->open) + { + if(AOVERBOSE(2)) + fprintf(stderr, "Chosen output module: %s\n", curname); + /* A bit redundant, but useful when it's a fake module. */ + if(!(ao->driver = strdup(curname))) + { + out123_close(ao); + if(!AOQUIET) error("OOM driver name"); + return out123_seterr(ao, OUT123_DOOM); + } + } + } + + free(modnames); + + if(!ao->open) /* At least an open() routine must be present. */ + { + if(!AOQUIET) + error2("Found no driver out of [%s] working with device %s." + , names, device ? device : ""); + /* Proper more detailed error code could be set already. */ + if(ao->errcode == OUT123_OK) + ao->errcode = OUT123_BAD_DRIVER; + return OUT123_ERR; + } + } + /* Got something. */ + ao->state = play_stopped; + return OUT123_OK; +} + +/* Be resilient, always do cleanup work regardless of state. */ +void out123_close(audio_output_t *ao) +{ + debug1("out123_close(%p)", (void*)ao); + if(!ao) + return; + ao->errcode = 0; + + out123_drain(ao); + +#ifndef NOXFERMEM + if(have_buffer(ao)) + buffer_close(ao); + else +#endif + { + if(ao->close) + { + int ret; + if((ret=ao->close(ao))) + { + if(!AOQUIET) + error1("ao->close() returned %i", ret); + } + } + if(ao->deinit) + ao->deinit(ao); + if(ao->module) + close_module(ao->module, modverbose(ao)); + /* Null module methods and pointer. */ + out123_clear_module(ao); + } + + /* These copies exist in addition to the ones for the buffer. */ + if(ao->driver) + free(ao->driver); + ao->driver = NULL; + if(ao->device) + free(ao->device); + ao->device = NULL; + + ao->state = play_dead; +} + +int out123_start( audio_output_t *ao +, int encoding, int channels, long rate ) +{ + debug4( "out123_start(%p, %i, %i, %li)" + , (void*)ao, (int)encoding, channels, rate ); + if(!ao) + return OUT123_ERR; + ao->errcode = 0; + + out123_stop(ao); + debug("out123_start() continuing"); + if(ao->state != play_stopped) + return out123_seterr(ao, OUT123_NO_DRIVER); + + /* Stored right away as parameters for ao->open() and also for reference. + framesize needed for out123_play(). */ + ao->rate = rate; + ao->channels = channels; + ao->format = encoding; + ao->framesize = out123_samplesize(encoding)*channels; + +#ifndef NOXFERMEM + if(have_buffer(ao)) + { + if(!buffer_start(ao)) + { + ao->state = play_live; + return OUT123_OK; + } + else + return OUT123_ERR; + } + else +#endif + { + if(ao->open(ao) < 0) + return out123_seterr(ao, OUT123_DEV_OPEN); + ao->state = play_live; + return OUT123_OK; + } +} + +void out123_pause(audio_output_t *ao) +{ + debug1("out123_pause(%p)", (void*)ao); + if(ao && ao->state == play_live) + { +#ifndef NOXFERMEM + if(have_buffer(ao)) buffer_pause(ao); +#endif + /* TODO: Be nice and prepare output device for silence. */ + ao->state = play_paused; + } +} + +void out123_continue(audio_output_t *ao) +{ + debug1("out123_continue(%p)", (void*)ao); + if(ao && ao->state == play_paused) + { +#ifndef NOXFERMEM + if(have_buffer(ao)) buffer_continue(ao); +#endif + /* TODO: Revitalize device for resuming playback. */ + ao->state = play_live; + } +} + +void out123_stop(audio_output_t *ao) +{ + debug1("out123_stop(%p)", (void*)ao); + if(!ao) + return; + ao->errcode = 0; + if(!(ao->state == play_paused || ao->state == play_live)) + return; +#ifndef NOXFERMEM + if(have_buffer(ao)) + buffer_stop(ao); + else +#endif + if(ao->close && ao->close(ao) && !AOQUIET) + error("trouble closing device"); + ao->state = play_stopped; +} + +size_t out123_play(audio_output_t *ao, void *bytes, size_t count) +{ + size_t sum = 0; + int written; + + debug3("out123_play(%p, %p, %"SIZE_P")", (void*)ao, bytes, (size_p)count); + if(!ao) + return 0; + ao->errcode = 0; + if(ao->state != play_live) + { + ao->errcode = OUT123_NOT_LIVE; + return 0; + } + + /* Ensure that we are writing whole PCM frames. */ + count -= count % ao->framesize; + if(!count) return 0; + +#ifndef NOXFERMEM + if(have_buffer(ao)) + return buffer_write(ao, bytes, count); + else +#endif + do /* Playback in a loop to be able to continue after interruptions. */ + { + written = ao->write(ao, (unsigned char*)bytes, (int)count); + if(written >= 0){ sum+=written; count -= written; } + else + { + ao->errcode = OUT123_DEV_PLAY; + if(!AOQUIET) + error1("Error in writing audio (%s?)!", strerror(errno)); + /* If written < 0, this is a serious issue ending this playback round. */ + break; + } + } while(count && ao->flags & OUT123_KEEP_PLAYING); + return sum; +} + +/* Drop means to flush it down. Quickly. */ +void out123_drop(audio_output_t *ao) +{ + debug1("out123_drop(%p)", (void*)ao); + if(!ao) + return; + ao->errcode = 0; +#ifndef NO_XFERMEM + if(have_buffer(ao)) + buffer_drop(ao); + else +#endif + if(ao->state == play_live) + { + if(ao->flush) ao->flush(ao); + } +} + +void out123_drain(audio_output_t *ao) +{ + debug1("out123_drain(%p)", (void*)ao); + if(!ao) + return; + ao->errcode = 0; + if(ao->state != play_live) + return; +#ifndef NO_XFERMEM + if(have_buffer(ao)) + buffer_drain(ao); + else +#endif + if(ao->drain) + ao->drain(ao); +} + +void out123_ndrain(audio_output_t *ao, size_t bytes) +{ + debug2("out123_ndrain(%p, %"SIZE_P")", (void*)ao, (size_p)bytes); + if(!ao) + return; + ao->errcode = 0; + if(ao->state != play_live) + return; +#ifndef NO_XFERMEM + if(have_buffer(ao)) + buffer_ndrain(ao, bytes); + else +#endif + if(ao->drain) + ao->drain(ao); +} + + +/* A function that does nothing and returns nothing. */ +static void builtin_nothing(audio_output_t *ao){} +static int test_open(audio_output_t *ao) +{ + debug("test_open"); + return OUT123_OK; +} +static int test_get_formats(audio_output_t *ao) +{ + debug("test_get_formats"); + return MPG123_ENC_ANY; +} +static int test_write(audio_output_t *ao, unsigned char *buf, int len) +{ + debug2("test_write: %i B from %p", len, (void*)buf); + return len; +} +static void test_flush(audio_output_t *ao) +{ + debug("test_flush"); +} +static void test_drain(audio_output_t *ao) +{ + debug("test_drain"); +} +static int test_close(audio_output_t *ao) +{ + debug("test_drain"); + return 0; +} + +/* Open one of our builtin driver modules. */ +static int open_fake_module(audio_output_t *ao, const char *driver) +{ + if(!strcmp("test", driver)) + { + ao->open = test_open; + ao->get_formats = test_get_formats; + ao->write = test_write; + ao->flush = test_flush; + ao->drain = test_drain; + ao->close = test_close; + } + else + if(!strcmp("raw", driver)) + { + ao->open = raw_open; + ao->get_formats = raw_formats; + ao->write = wav_write; + ao->flush = builtin_nothing; + ao->drain = wav_drain; + ao->close = raw_close; + } + else + if(!strcmp("wav", driver)) + { + ao->open = wav_open; + ao->get_formats = wav_formats; + ao->write = wav_write; + ao->flush = builtin_nothing; + ao->drain = wav_drain; + ao->close = wav_close; + } + else + if(!strcmp("cdr", driver)) + { + ao->open = cdr_open; + ao->get_formats = cdr_formats; + ao->write = wav_write; + ao->flush = builtin_nothing; + ao->drain = wav_drain; + ao->close = raw_close; + } + else + if(!strcmp("au", driver)) + { + ao->open = au_open; + ao->get_formats = au_formats; + ao->write = wav_write; + ao->flush = builtin_nothing; + ao->drain = wav_drain; + ao->close = au_close; + } + else return OUT123_ERR; + + return OUT123_OK; +} + +/* Check if given output module is loadable and has a working device. + final flag triggers printing and storing of errors. */ +static void check_output_module( audio_output_t *ao +, const char *name, const char *device, int final ) +{ + int result; + + if(AOVERBOSE(1)) + fprintf(stderr, "Trying output module: %s\n", name); + + /* Use internal code. */ + if(open_fake_module(ao, name) == OUT123_OK) + return; + + /* Open the module, initial check for availability+libraries. */ + ao->module = open_module( "output", name, modverbose(ao)); + if(!ao->module) + return; + /* Check if module supports output */ + if(!ao->module->init_output) + { + if(final) + error1("Module '%s' does not support audio output.", name); + goto check_output_module_cleanup; + } + + /* Should I do funny stuff with stderr file descriptor instead? */ + if(final) + { + if(AOVERBOSE(2)) + fprintf(stderr + , "Note: %s is the last output option... showing you any error messages now.\n" + , name); + } + else ao->auxflags |= OUT123_QUIET; /* Probing, so don't spill stderr with errors. */ + result = ao->module->init_output(ao); + if(result == 0) + { /* Try to open the device. I'm only interested in actually working modules. */ + ao->format = -1; + result = ao->open(ao); + debug1("ao->open() = %i", result); + ao->close(ao); + } + else if(!AOQUIET) + error2("Module '%s' init failed: %i", name, result); + + ao->auxflags &= ~OUT123_QUIET; + + if(result >= 0) + return; + +check_output_module_cleanup: + /* Only if module did not check out we get to clean up here. */ + close_module(ao->module, modverbose(ao)); + out123_clear_module(ao); + return; +} + +/* +static void audio_output_dump(audio_output_t *ao) +{ + fprintf(stderr, "ao->fn=%d\n", ao->fn); + fprintf(stderr, "ao->userptr=%p\n", ao->userptr); + fprintf(stderr, "ao->rate=%ld\n", ao->rate); + fprintf(stderr, "ao->gain=%ld\n", ao->gain); + fprintf(stderr, "ao->device='%s'\n", ao->device); + fprintf(stderr, "ao->channels=%d\n", ao->channels); + fprintf(stderr, "ao->format=%d\n", ao->format); +} +*/ + +int out123_drivers(audio_output_t *ao, char ***names, char ***descr) +{ + char **tmpnames; + char **tmpdescr; + int count; + int i; + + if(!ao) + return -1; + + debug3("out123_drivers(%p, %p, %p)", (void*)ao, (void*)names, (void*)descr); + /* Wrap the call to isolate the lower levels from the user not being + interested in both lists. it's a bit wasteful, but the code looks + ugly enough already down there. */ + count = list_modules("output", &tmpnames, &tmpdescr, modverbose(ao)); + + if(count < 0) + return count; + + /* Return or free gathered lists of names or descriptions. */ + if(names) + *names = tmpnames; + else + { + for(i=0; idriver and ao->device set, also with buffer. + The latter can be positively NULL, though. */ +int out123_driver_info(audio_output_t *ao, char **driver, char **device) +{ + debug3( "out123_driver_info(%p, %p, %p)" + , (void*)ao, (void*)driver, (void*)device ); + if(!ao) + return OUT123_ERR; + if(!ao->driver) + return out123_seterr(ao, OUT123_NO_DRIVER); + + if(driver) + *driver = ao->driver; + if(device) + *device = ao->device; + return OUT123_OK; +} + +int out123_encodings(audio_output_t *ao, int channels, long rate) +{ + debug3("out123_encodings(%p, %i, %li)", (void*)ao, channels, rate); + if(!ao) + return OUT123_ERR; + ao->errcode = OUT123_OK; + + out123_stop(ao); /* That brings the buffer into waiting state, too. */ + + if(ao->state != play_stopped) + return out123_seterr(ao, OUT123_NO_DRIVER); + + ao->channels = channels; + ao->rate = rate; +#ifndef NOXFERMEM + if(have_buffer(ao)) + return buffer_encodings(ao); + else +#endif + { + int enc = 0; + ao->format = -1; + if(ao->open(ao) >= 0) + { + enc = ao->get_formats(ao); + ao->close(ao); + return enc; + } + else + return out123_seterr(ao, (ao->errcode != OUT123_OK + ? ao->errcode + : OUT123_DEV_OPEN)); + } +} + +size_t out123_buffered(audio_output_t *ao) +{ + debug1("out123_buffered(%p)", (void*)ao); + if(!ao) + return 0; + ao->errcode = 0; +#ifndef NOXFERMEM + if(have_buffer(ao)) + return buffer_fill(ao); + else +#endif + return 0; +} + +int out123_getformat( audio_output_t *ao +, long *rate, int *channels, int *encoding, int *framesize ) +{ + if(!ao) + return OUT123_ERR; + + if(!(ao->state == play_paused || ao->state == play_live)) + return out123_seterr(ao, OUT123_NOT_LIVE); + + if(rate) + *rate = ao->rate; + if(channels) + *channels = ao->channels; + if(encoding) + *encoding = ao->format; + if(framesize) + *framesize = ao->framesize; + return OUT123_OK; +} diff --git a/src/module.c b/src/libout123/module.c similarity index 50% rename from src/module.c rename to src/libout123/module.c index 3e7174dc..121d2c10 100644 --- a/src/module.c +++ b/src/libout123/module.c @@ -5,15 +5,16 @@ see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Nicholas J Humfrey */ - +#include "config.h" +#include "out123_intsym.h" +#include "compat.h" #include #include -#include #include #include #include -#include "mpg123app.h" +#include "module.h" #include "debug.h" #ifndef HAVE_LTDL @@ -32,8 +33,8 @@ static const char* modulesearch[] = ,"plugins" }; -static char *get_the_cwd(); /* further down... */ -static char *get_module_dir() +static char *get_the_cwd(int verbose); /* further down... */ +static char *get_module_dir(int verbose) { /* Either PKGLIBDIR is accessible right away or we search for some possible plugin dirs relative to binary path. */ DIR* dir = NULL; @@ -42,14 +43,17 @@ static char *get_module_dir() /* Compiled-in default module dir or environment variable MPG123_MODDIR. */ defaultdir = getenv("MPG123_MODDIR"); if(defaultdir == NULL) - defaultdir=PKGLIBDIR; + defaultdir=PKGLIBDIR; + else if(verbose > 1) + fprintf(stderr, "Trying module directory from environment: %s\n", defaultdir); dir = opendir(defaultdir); if(dir != NULL) { size_t l = strlen(defaultdir); - if(param.verbose > 1) fprintf(stderr, "Using default module dir: %s\n", defaultdir); + if(verbose > 1) + fprintf(stderr, "Using default module dir: %s\n", defaultdir); moddir = malloc(l+1); if(moddir != NULL) { @@ -65,19 +69,21 @@ static char *get_module_dir() { const char *testpath = modulesearch[i]; size_t l; - if(binpath != NULL) l = strlen(binpath) + strlen(testpath) + 1; - else l = strlen(testpath); + fprintf(stderr, "TODO: module search relative to binary path\n"); +/* if(binpath != NULL) l = strlen(binpath) + strlen(testpath) + 1; + else */ l = strlen(testpath); moddir = malloc(l+1); if(moddir != NULL) { - if(binpath==NULL) /* a copy of testpath, when there is no prefix */ + /*if(binpath==NULL)*/ /* a copy of testpath, when there is no prefix */ snprintf(moddir, l+1, "%s", testpath); - else - snprintf(moddir, l+1, "%s/%s", binpath, testpath); + /* else + snprintf(moddir, l+1, "%s/%s", binpath, testpath); */ moddir[l] = 0; - if(param.verbose > 1) fprintf(stderr, "Looking for module dir: %s\n", moddir); + if(verbose > 1) + fprintf(stderr, "Looking for module dir: %s\n", moddir); dir = opendir(moddir); if(dir != NULL) @@ -89,12 +95,13 @@ static char *get_module_dir() } } } - if(param.verbose > 1) fprintf(stderr, "Module dir: %s\n", moddir != NULL ? moddir : ""); + if(verbose > 1) + fprintf(stderr, "Module dir: %s\n", moddir != NULL ? moddir : ""); return moddir; } /* Open a module in current directory. */ -mpg123_module_t* open_module_here(const char* type, const char* name) +mpg123_module_t* open_module_here(const char* type, const char* name, int verbose) { lt_dlhandle handle = NULL; mpg123_module_t *module = NULL; @@ -106,7 +113,8 @@ mpg123_module_t* open_module_here(const char* type, const char* name) /* Initialize libltdl */ if(lt_dlinit()) { - error("Failed to initialise libltdl"); + if(verbose > -1) + error("Failed to initialise libltdl"); return NULL; } @@ -115,20 +123,22 @@ mpg123_module_t* open_module_here(const char* type, const char* name) module_path_len = 2 + strlen(type) + 1 + strlen(name) + strlen(MODULE_FILE_SUFFIX) + 1; module_path = malloc( module_path_len ); if (module_path == NULL) { - error1( "Failed to allocate memory for module name: %s", strerror(errno) ); + if(verbose > -1) + error1( "Failed to allocate memory for module name: %s", strerror(errno) ); return NULL; } snprintf( module_path, module_path_len, "./%s_%s%s", type, name, MODULE_FILE_SUFFIX ); /* Display the path of the module created */ - if(param.verbose > 1) fprintf(stderr, "Module path: %s\n", module_path ); + if(verbose > 1) + fprintf(stderr, "Module path: %s\n", module_path ); /* Open the module */ handle = lt_dlopen( module_path ); free( module_path ); if (handle==NULL) { error2( "Failed to open module %s: %s", name, lt_dlerror() ); - if(param.verbose > 1) - fprintf(stderr, "Note: This could be because of braindead path in the .la file...\n"); + if(verbose > 1) + fprintf(stderr, "Note: This could be because of braindead path in the .la file...\n"); return NULL; } @@ -139,7 +149,8 @@ mpg123_module_t* open_module_here(const char* type, const char* name) strlen( MODULE_SYMBOL_SUFFIX ) + 1; module_symbol = malloc(module_symbol_len); if (module_symbol == NULL) { - error1( "Failed to allocate memory for module symbol: %s", strerror(errno) ); + if(verbose > -1) + error1( "Failed to allocate memory for module symbol: %s", strerror(errno) ); return NULL; } snprintf( module_symbol, module_symbol_len, "%s%s%s", MODULE_SYMBOL_PREFIX, type, MODULE_SYMBOL_SUFFIX ); @@ -168,26 +179,31 @@ mpg123_module_t* open_module_here(const char* type, const char* name) /* Open a module, including directory search. */ -mpg123_module_t* open_module(const char* type, const char* name) +mpg123_module_t* open_module(const char* type, const char* name, int verbose) { mpg123_module_t *module = NULL; char *workdir = NULL; char *moddir = NULL; - workdir = get_the_cwd(); - moddir = get_module_dir(); + workdir = get_the_cwd(verbose); + moddir = get_module_dir(verbose); if(workdir == NULL || moddir == NULL) { - error("Failure getting workdir or moddir! (Perhaps set MPG123_MODDIR?)"); - if(workdir == NULL) fprintf(stderr, "Hint: I need to know the current working directory to be able to come back after hunting modules. I will not leave because I do not know where I am.\n"); - + if(verbose > -1) + { + error("Failure getting workdir or moddir! (Perhaps set MPG123_MODDIR?)"); + if(workdir == NULL) + fprintf(stderr, "Hint: I need to know the current working directory to be able to come back after hunting modules. I will not leave because I do not know where I am.\n"); + } if(workdir != NULL) free(workdir); if(moddir != NULL) free(moddir); return NULL; } - if(chdir(moddir) == 0) module = open_module_here(type, name); - else error2("Failed to enter module directory %s: %s", moddir, strerror(errno)); + if(chdir(moddir) == 0) + module = open_module_here(type, name, verbose); + else if(verbose > -1) + error2("Failed to enter module directory %s: %s", moddir, strerror(errno)); chdir(workdir); free(moddir); @@ -195,17 +211,18 @@ mpg123_module_t* open_module(const char* type, const char* name) return module; } -void close_module( mpg123_module_t* module ) +void close_module( mpg123_module_t* module, int verbose ) { lt_dlhandle handle = module->handle; int err = lt_dlclose( handle ); - if (err) error1("Failed to close module: %s", lt_dlerror() ); + if(err && verbose > -1) + error1("Failed to close module: %s", lt_dlerror()); } #define PATH_STEP 50 -static char *get_the_cwd() +static char *get_the_cwd(int verbose) { size_t bs = PATH_STEP; char *buf = malloc(bs); @@ -215,7 +232,8 @@ static char *get_the_cwd() char *buf2; if(errno != ERANGE) { - error1("getcwd returned unexpected error: %s", strerror(errno)); + if(verbose > -1) + error1("getcwd returned unexpected error: %s", strerror(errno)); free(buf); return NULL; } @@ -228,85 +246,125 @@ static char *get_the_cwd() return buf; } -void list_modules() +int list_modules(const char *type, char ***names, char ***descr, int verbose) { DIR* dir = NULL; struct dirent *dp = NULL; char *moddir = NULL; + int count = 0; - moddir = get_module_dir(); + *names = NULL; + *descr = NULL; + + moddir = get_module_dir(verbose); if(moddir == NULL) { - error("Failure getting module directory! (Perhaps set MPG123_MODDIR?)"); - exit(-1); /* TODO: change this to return a value instead of exit()! */ + if(verbose > -1) + error("Failure getting module directory! (Perhaps set MPG123_MODDIR?)"); + return -1; } /* Open the module directory */ - dir = opendir(moddir); + dir = moddir != NULL ? opendir(moddir) : NULL; if (dir==NULL) { - error2("Failed to open the module directory (%s): %s\n", PKGLIBDIR, strerror(errno)); + if(verbose > -1) + error2("Failed to open the module directory (%s): %s\n" + , PKGLIBDIR, strerror(errno)); free(moddir); - exit(-1); + return -1; } - + if(chdir(moddir) != 0) { - error2("Failed to enter module directory (%s): %s\n", PKGLIBDIR, strerror(errno)); - closedir( dir ); + if(verbose > -1) + error2("Failed to enter module directory (%s): %s\n" + , PKGLIBDIR, strerror(errno)); + closedir(dir); free(moddir); - exit(-1); + return -1; } - /* Display the program title */ - /* print_title(stderr); */ - - /* List the output modules */ - printf("\n"); - printf("Available modules\n"); - printf("-----------------\n"); - - while( (dp = readdir(dir)) != NULL ) { - struct stat fst; - if(stat(dp->d_name, &fst) != 0) continue; - if(S_ISREG(fst.st_mode)) /* Allow links? */ - { - char* ext = dp->d_name + strlen( dp->d_name ) - strlen( MODULE_FILE_SUFFIX ); - if (strcmp(ext, MODULE_FILE_SUFFIX) == 0) - { - char *module_name = NULL; - char *module_type = NULL; - char *uscore_pos = NULL; - mpg123_module_t *module = NULL; - - /* Extract the module type */ - module_type = strdup( dp->d_name ); - uscore_pos = strchr( module_type, '_' ); - if (uscore_pos==NULL || (uscore_pos>=module_type+strlen(module_type)+1) ) - { - free(module_type); - continue; - } - - *uscore_pos = '\0'; - - /* Extract the short name of the module */ - module_name = strdup( dp->d_name + strlen( module_type ) + 1 ); - module_name[ strlen( module_name ) - strlen( MODULE_FILE_SUFFIX ) ] = '\0'; - /* Open the module */ - module = open_module_here(module_type, module_name); - if (module) { - printf("%-15s%s %s\n", module->name, module_type, module->description ); - - /* Close the module again */ - close_module( module ); - } - free( module_name ); - free( module_type ); - } - } - } - - closedir( dir ); free(moddir); - exit(0); + + while( count >= 0 && (dp = readdir(dir)) != NULL ) + { + char *module_name = NULL; + char *module_type = NULL; + char *uscore_pos = NULL; + mpg123_module_t *module = NULL; + char* ext; + struct stat fst; + + /* Various checks as loop shortcuts, avoiding too much nesting. */ + + if(stat(dp->d_name, &fst) != 0) + continue; + if(!S_ISREG(fst.st_mode)) /* Allow links? */ + continue; + + ext = dp->d_name + + strlen(dp->d_name) + - strlen(MODULE_FILE_SUFFIX); + if(strcmp(ext, MODULE_FILE_SUFFIX)) + continue; + + /* Extract the module type */ + if(!(module_type=strdup(dp->d_name))) + continue; + /* Only list modules of desired type. */ + if(strcmp(type, module_type)) + continue; + uscore_pos = strchr( module_type, '_' ); + if( uscore_pos==NULL + || (uscore_pos>=module_type+strlen(module_type)+1) ) + { + free(module_type); + continue; + } + *uscore_pos = '\0'; + + /* Extract the short name of the module */ + module_name = strdup(dp->d_name + strlen(module_type) + 1); + if(!module_name) + { + free(module_type); + continue; + } + module_name[strlen(module_name)-strlen(MODULE_FILE_SUFFIX)] = '\0'; + + /* Open the module */ + if((module=open_module_here(module_type, module_name, verbose))) + { + char **more_names = NULL; + char **more_descr = NULL; + char *name = NULL; + char *description = NULL; + if( + (name=strdup(module->name)) + && (description=strdup(module->description)) + && (more_names=safe_realloc(*names, sizeof(char*)*(count+1))) + && (more_descr=safe_realloc(*descr, sizeof(char*)*(count+1))) + ) + { + *names = more_names; + *descr = more_descr; + (*names)[count] = name; + (*descr)[count] = description; + ++count; + } + else + { + free(more_descr); + free(more_names); + free(description); + free(name); + } + /* Close the module again */ + close_module(module, verbose); + } + free(module_name); + free(module_type); + } + closedir(dir); + return count; } diff --git a/src/module.h b/src/libout123/module.h similarity index 69% rename from src/module.h rename to src/libout123/module.h index b2e5ffba..b8e429d9 100644 --- a/src/module.h +++ b/src/libout123/module.h @@ -1,20 +1,20 @@ /* module: module loading and listing interface - copyright ?-2007 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright ?-2015 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org + initially written by Nicholas J. Humphfrey */ #ifndef _MPG123_MODULE_H_ #define _MPG123_MODULE_H_ -/* Pulled in by mpg123app.h! */ - #ifdef HAVE_LTDL #include #endif -#define MPG123_MODULE_API_VERSION (1) +/* TODO: put that into out123_int.h instead? */ +#define MPG123_MODULE_API_VERSION (2) /* The full structure is delared in audio.h */ struct audio_output_struct; @@ -41,8 +41,8 @@ typedef struct mpg123_module_struct { /* ------ Declarations from "module.c" ------ */ -mpg123_module_t* open_module( const char* type, const char* name ); -void close_module( mpg123_module_t* module ); -void list_modules(); +mpg123_module_t* open_module(const char* type, const char* name, int verbose); +void close_module(mpg123_module_t* module, int verbose); +int list_modules(const char *type, char ***names, char ***descr, int verbose); #endif diff --git a/src/libout123/modules/Makemodule.am b/src/libout123/modules/Makemodule.am new file mode 100644 index 00000000..34770caf --- /dev/null +++ b/src/libout123/modules/Makemodule.am @@ -0,0 +1,732 @@ +# Module for non-recursive mpg123 build system. +# Gah! Not even re-defining that variable is allowed in automake! +# I WANT TO USE PROPER MAKE! +# makedir := src/libout123/modules +# Experiment: Does automake pick that up in a Make variable? +# Damn, no! It complains wildly. +# I just want to use GNU Make and be done with it! +# Perhaps the next build system rewrite ... +#makenam=src_libout123_modules + +# Optionally containing the one static module to use. +noinst_LTLIBRARIES += src/libout123/modules/libdefaultmodule.la + +# Do not include uneeded headers from mpg123app.h . +libout123_mod_cppflags = -DBUILDING_OUTPUT_MODULES=1 + +# These are not tested and _very_ likely need work: aix alib hp os2 sgi mint + +# Use that sh/perl script to generate the module entries: +# Confused as to when to use _LIBADD and when _LDADD. +# _LDADD gives errors from autotools. +#echo \ +#dummy tinyalsa alsa qsa coreaudio esd jack nas oss portaudio \ +#pulse sdl sndio sun win32 win32_wasapi aix alib arts hp os2 \ +#sgi mint openal \ +#| tr ' ' '\n' | +#perl -ne 'chomp; $big = uc($_); print < #include -#include "mpg123app.h" +#include "out123_int.h" #include "debug.h" /* use AUDIO_BSIZE to set the msec for audio buffering in Ultimedia library diff --git a/src/output/alib.c b/src/libout123/modules/alib.c similarity index 99% rename from src/output/alib.c rename to src/libout123/modules/alib.c index 635382d0..328e85a7 100644 --- a/src/output/alib.c +++ b/src/libout123/modules/alib.c @@ -30,7 +30,7 @@ /**************************************************************************/ -#include "mpg123app.h" +#include "out123_int.h" #include diff --git a/src/output/alsa.c b/src/libout123/modules/alsa.c similarity index 98% rename from src/output/alsa.c rename to src/libout123/modules/alsa.c index 9b76b7bb..16eee8a7 100644 --- a/src/output/alsa.c +++ b/src/libout123/modules/alsa.c @@ -7,7 +7,7 @@ initially written by Clemens Ladisch */ -#include "mpg123app.h" +#include "out123_int.h" #include "audio.h" #include "module.h" #include @@ -16,6 +16,7 @@ #define ALSA_PCM_NEW_HW_PARAMS_API #define ALSA_PCM_NEW_SW_PARAMS_API +#include /* GCC complains about missing declaration of alloca. */ #include #include "debug.h" diff --git a/src/output/arts.c b/src/libout123/modules/arts.c similarity index 99% rename from src/output/arts.c rename to src/libout123/modules/arts.c index 04ee02f9..740b9104 100644 --- a/src/output/arts.c +++ b/src/libout123/modules/arts.c @@ -8,7 +8,7 @@ */ -#include "mpg123app.h" +#include "out123_int.h" #include #include "debug.h" diff --git a/src/output/coreaudio.c b/src/libout123/modules/coreaudio.c similarity index 99% rename from src/output/coreaudio.c rename to src/libout123/modules/coreaudio.c index 125ad8c3..f79ee44f 100644 --- a/src/output/coreaudio.c +++ b/src/libout123/modules/coreaudio.c @@ -9,7 +9,7 @@ */ -#include "mpg123app.h" +#include "out123_int.h" #include #include diff --git a/src/output/dummy.c b/src/libout123/modules/dummy.c similarity index 98% rename from src/output/dummy.c rename to src/libout123/modules/dummy.c index 9f782927..d31e5ad7 100644 --- a/src/output/dummy.c +++ b/src/libout123/modules/dummy.c @@ -5,7 +5,7 @@ see COPYING and AUTHORS files in distribution or http://mpg123.org */ -#include "mpg123app.h" +#include "out123_int.h" #include "debug.h" static int open_dummy(audio_output_t *ao) diff --git a/src/output/esd.c b/src/libout123/modules/esd.c similarity index 99% rename from src/output/esd.c rename to src/libout123/modules/esd.c index 8a670f18..bc2b6ba0 100644 --- a/src/output/esd.c +++ b/src/libout123/modules/esd.c @@ -8,7 +8,7 @@ /* First the common header, including config.h ...this is important for stuff like _FILE_OFFSET_BITS */ -#include "mpg123app.h" +#include "out123_int.h" #include #include diff --git a/src/output/hp.c b/src/libout123/modules/hp.c similarity index 99% rename from src/output/hp.c rename to src/libout123/modules/hp.c index 475ed036..8be12a87 100644 --- a/src/output/hp.c +++ b/src/libout123/modules/hp.c @@ -6,7 +6,7 @@ initially written by Michael Hipp */ -#include "mpg123app.h" +#include "out123_int.h" #include #include #include "debug.h" diff --git a/src/output/jack.c b/src/libout123/modules/jack.c similarity index 99% rename from src/output/jack.c rename to src/libout123/modules/jack.c index 95871c4e..1439de0e 100644 --- a/src/output/jack.c +++ b/src/libout123/modules/jack.c @@ -12,7 +12,7 @@ #include #include -#include "mpg123app.h" +#include "out123_int.h" #include "debug.h" #define MAX_CHANNELS (2) diff --git a/src/output/mint.c b/src/libout123/modules/mint.c similarity index 99% rename from src/output/mint.c rename to src/libout123/modules/mint.c index 72102937..28137662 100644 --- a/src/output/mint.c +++ b/src/libout123/modules/mint.c @@ -8,7 +8,7 @@ /* derived from LINUX, VOXWARE and SUN for MiNT Audio Device by Petr Stehlik */ #include -#include "mpg123app.h" +#include "out123_int.h" #include #include #include "debug.h" diff --git a/src/output/nas.c b/src/libout123/modules/nas.c similarity index 99% rename from src/output/nas.c rename to src/libout123/modules/nas.c index ad69b19e..1eb56b40 100644 --- a/src/output/nas.c +++ b/src/libout123/modules/nas.c @@ -6,7 +6,7 @@ initially written by Martin Denn */ -#include "mpg123app.h" +#include "out123_int.h" #include #include