pax_global_header00006660000000000000000000000064125307464210014516gustar00rootroot0000000000000052 comment=0326cdc7376016be906a4e5b9a7b6de1c33c06ba libgroove-4.3.0/000077500000000000000000000000001253074642100135125ustar00rootroot00000000000000libgroove-4.3.0/.gitignore000066400000000000000000000000071253074642100154770ustar00rootroot00000000000000/build libgroove-4.3.0/CHANGELOG.md000066400000000000000000000224371253074642100153330ustar00rootroot00000000000000### Version 4.3.0 (2015-05-25) * player: Add support for "exact mode" - re-opens the audio device when a buffer is encountered that does not match the open audio device's sound parameters. * build: Simplify cmake find modules. * Add const qualifier to some fields and functions. * metadata example: print the number of channels. * Add documentation. * Add more channel layouts. * build: Compile in Debug mode by default. ### Version 4.2.1 (2014-10-07) * updating metadata: set time_base on stream not the codec * libav lockmgr: set mutex pointer to NULL on destroy * fix build on GNU/hurd ### Version 4.2.0 (2014-09-25) * build: remove bundled dependencies * build: simpler cmake find modules, both code and license * player: add shim to fix build failure on OSX ### Version 4.1.1 (2014-06-20) * playlist: fix race condition which can cause decoder to hang * dummy player: fix timing issues ### Version 4.1.0 (2014-06-13) * playlist: added `groove_playlist_set_fill_mode`. Allows you to choose between buffering until all sinks are full and buffering until any sinks are full. ### Version 4.0.4 (2014-06-03) * Fixed a race condition where seeking to a different playlist item and then back would have a window of time where it could report the wrong position. * Properly play and pause network streams. ### Version 4.0.3 (2014-05-31) * build: update bundled libav to latest stable 10 release * build: link player with -lrt for clock_gettime. closes #67 * playlist: fix case where filter graph was not being rebuilt. closes #65 * playlist: fix race condition segfault when attaching a sink * encoder: properly reset encoding when flush or playlist end is encountered. closes #66 ### Version 4.0.2 (2014-05-20) * player: thread cleanup only if thread was initialized - fixes potential crash on player detach * build: look for includes in the current source tree. Fixes an issue when a previous version of the library is installed. * build: on unix link with -lm ### Version 4.0.1 (2014-05-13) * groove_playlist_get_position: always set seconds even when item is NULL * playlist: correct generation of the sink map - fixes potential error when adding multiple sinks - optimizes some cases where sinks can share filter graph chain - dummy player now uses disable_resample optimization * dummy player: avoid floating point error accumulation ### Version 4.0.0 (2014-05-12) * GrooveBuffer struct contains the presentation time stamp * move include statements to outside of extern C * ability to set true peak on playlist items. closes #50 * support per-sink gain adjustment. closes #41 * GroovePlaylist: `volume` renamed to `gain` - `groove_playlist_set_gain` renamed to `groove_playlist_set_item_gain` - `groove_playlist_set_volume` renamed to `groove_playlist_set_gain` * player: specify device by index rather than name. closes #44 * player: ability to attach a dummy player device. closes #60 * fingerprinter: encode/decode return 0 on success, < 0 on error * fingerprinter: info struct contains raw fingerprint instead of compressed string. closes #61 ### Version 3.1.1 (2014-04-21) * fingerprinter example: add --raw option * fingerprinter sink: add error checking to chromaprint function calls * fingerprinter sink: fix documentation. raw fingerprints are signed 32-bit integers, not unsigned 32-bit integers. * fingerprinter sink: change `void *` to `int32_t *` for encode/decode functions ### Version 3.1.0 (2014-04-19) * add acoustid fingerprinter sink. Closes #19 * build: revert GNUInstallDirs * update to libav 10.1 ### Version 3.0.8 (2014-04-01) * loudness scanning: fix memory corruption when scanning large album * update to libav10 * playlist: fix segfault on out of memory * playlist: fix segfault race condition when adding a sink ### Version 3.0.7 (2014-03-16) * build: fix cmake warnings * use ebur128 true peak instead of sample peak * fix bug where accessing "album" metadata would instead return the "album_artist" * update to libav 10 beta2 * use the compand filter to allow setting the gain to > 1.0. Closes #45 * log error when cannot set up filter graph * loudness scanning: fix crash for songs with 0 frames. Closes #48 * playlist example: fix race condition ### Version 3.0.6 (2014-02-20) * build: avoid useless dependencies ### Version 3.0.5 (2014-02-20) * update to libav dff1c19140e70. Closes #16 ASF seeking bug * build: use GNUInstallDirs ### Version 3.0.4 (2014-02-09) * delete SDL2 config-generated files from repo (they were causing an issue with debian packaging) ### Version 3.0.3 (2014-02-09) * update libav to 246d3bf0ec ### Version 3.0.2 (2013-11-25) * build: add static targets to all libraries ### Version 3.0.1 (2013-11-25) * build: depend on system libav if possible ### Version 3.0.0 (2013-11-24) * queue: depend on pthread instead of SDL2 * file: depend on pthreads instead of SDL2 * isolate SDL dependency to player * encoder: depend on pthread instead of SDL2 * player: use pthread for mutexes instead of SDL2 * loudness detector: depend on pthread instead of SDL2 * playlist: use pthread for mutexes instead of SDL2 * separate independent components into their own librares. closes #39 * build: use the same version info for all libs ### Version 2.0.4 (2013-11-23) * update libav to d4df02131b5522 * playlist: set sent_end_of_q at playlist create * better timestamp setting ### Version 2.0.3 (2013-11-22) * fix build when libspeexdsp is installed * cmake: support 2.8.7 * groove.h conforms to C90 * buffer implemented with pthreads instead of SDL * playlist: fix GrooveBuffer ref/unref race condition. closes #28 ### Version 2.0.2 (2013-11-19) * out-of-tree build for bundled libav * update libav to 16e7b189c54 ### Version 2.0.1 (2013-11-18) * compile with -O3 * update libav to 1c01b0253eb * build system: bundle SDL2 but try to use system SDL2 if possible * when doing bundled SDL2, use the cmake build * enable SDL_File because osx needs it * try to build against system libebur128 and fall back on bundled. closes #38 ### Version 2.0.0 (2013-11-16) * decode: remove last_decoded_file * SDL2 bundled dependency cleanup * Makefile: libgroove.so links against -ldl * fix 100% CPU by not disabling sdl timer. closes #20 * playlist example: check for failure to create player * decode thread: end of playlist sentinel and full checking for every sink * update init_filter_graph to route based on sinks * sink: separate create from attach / destroy from detach * depend on system SDL2 * expose GrooveSink buffer_size to public API * rename GroovePlayer to GroovePlaylist * rename player.c to playlist.c * rename GrooveDeviceSink to GroovePlayer * rename device_sink.c to player.c * rename sample_count to frame_count * print error string when cannot create aformat filter * playlist: protect sink_map with a mutex * player: flush queue on detach; reset queue on attach * rename LICENSE to COPYING as per ubuntu suggestion * encoder sink implementation * transcoding example * workaround for av_guess_codec bug * fix some memory leaks found with valgrind * fix segfault when logging is on for file open errors * fix segfault when playlist item deleted * logging: no audio stream found is INFO not ERROR * encoder: stop when encoding buffer full. closes #32 * file: loggeng unrecognized format is INFO not ERROR * encoder: default bit_rate to 256kbps * support setting buffer_frame_count. closes #33 * transcode example: ability to join multiple tracks into one * encoder: add flushes to correctly obtain format header * encoder: set pts and dts to stream->nb_samples. closes #34 * playlist: decode_head waits on a mutex condition instead of sleeping * playlist: when sinks are full wait on mutex condition * encoder: draining uses a mutex condition instead of delay. closes #24 * transcode example: copy metadata to new file. closes #31 * encoder: fix deadlock race condition * add missing condition signals * consistent pointer conventions in groove.h * avoid prefixing header defines with underscores * better public/private pattern for GrooveFile. see #37 * queue: better public/private pattern and no typedefs * encoder: better public/private pattern * buffer: better public/private pattern and get rid of typedef * player: better public/private pattern and remove typedefs * playlist: better public/private pattern and remove typedefs * remove typedef on GrooveTag. see #36 * scan: better public/private pattern and remove typedefs * add license information to each file. closes #29 * use proper prototypes when no arguments * do not use atexit; provide groove_finish * encoder: fix cleanup deadlock race condition * playlist: fix small memory leak on cleanup * player: delete unused field * loudness detector rewrite. closes #27 and #25 * LoudnessDetector also reports duration. closes #23 * loudness detector: more error checking * readme update * loudness detector: ability to get position (progress) * add _peek() methods to all sinks * remove groove_loudness_to_replaygain from API * loudness detector: faster and more accurate album info * add groove_encoder_position API * update libav to 72ca830f511fcdc * build with cmake * fix build errors with clang * cmake: only do -Wl,-Bsymbolic on unix environments * fix build on OSX ### Version 1.0.0 (2013-08-08) * initial public release libgroove-4.3.0/CMakeLists.txt000066400000000000000000000254211253074642100162560ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) endif() project(groove C) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) set(LIBGROOVE_STATUS "yes") set(LIBGROOVE_VERSION_MAJOR 4) set(LIBGROOVE_VERSION_MINOR 3) set(LIBGROOVE_VERSION_PATCH 0) set(LIBGROOVE_VERSION "${LIBGROOVE_VERSION_MAJOR}.${LIBGROOVE_VERSION_MINOR}.${LIBGROOVE_VERSION_PATCH}") message("Configuring libgroove version ${LIBGROOVE_VERSION}") file(GLOB_RECURSE LIBGROOVE_SOURCES ${CMAKE_SOURCE_DIR}/groove/*.c) file(GLOB_RECURSE LIBGROOVE_HEADERS ${CMAKE_SOURCE_DIR}/groove/*.h) if(DISABLE_PLAYER) set(LIBGROOVE_PLAYER_STATUS "no") else(DISABLE_PLAYER) set(LIBGROOVE_PLAYER_STATUS "yes") message("Configuring libgrooveplayer version ${LIBGROOVE_VERSION}") set(LIBGROOVE_PLAYER_SOURCES ${CMAKE_SOURCE_DIR}/grooveplayer/player.c) file(GLOB_RECURSE LIBGROOVE_PLAYER_HEADERS ${CMAKE_SOURCE_DIR}/grooveplayer/*.h) if(APPLE) set(LIBGROOVE_PLAYER_SOURCES ${LIBGROOVE_PLAYER_SOURCES} ${CMAKE_SOURCE_DIR}/grooveplayer/osx_time_shim.c) endif(APPLE) endif(DISABLE_PLAYER) if(DISABLE_LOUDNESS) set(LIBGROOVE_LOUDNESS_STATUS "no") else(DISABLE_LOUDNESS) set(LIBGROOVE_LOUDNESS_STATUS "yes") message("Configuring libgrooveloudness version ${LIBGROOVE_VERSION}") file(GLOB_RECURSE LIBGROOVE_LOUDNESS_SOURCES ${CMAKE_SOURCE_DIR}/grooveloudness/*.c) file(GLOB_RECURSE LIBGROOVE_LOUDNESS_HEADERS ${CMAKE_SOURCE_DIR}/grooveloudness/*.h) endif(DISABLE_LOUDNESS) if(DISABLE_FINGERPRINTER) set(LIBGROOVE_FINGERPRINTER_STATUS "no") else(DISABLE_FINGERPRINTER) set(LIBGROOVE_FINGERPRINTER_STATUS "yes") message("Configuring libgroovefingerprinter version ${LIBGROOVE_VERSION}") file(GLOB_RECURSE LIBGROOVE_FINGERPRINTER_SOURCES ${CMAKE_SOURCE_DIR}/groovefingerprinter/*.c) file(GLOB_RECURSE LIBGROOVE_FINGERPRINTER_HEADERS ${CMAKE_SOURCE_DIR}/groovefingerprinter/*.h) endif(DISABLE_FINGERPRINTER) # check for C99 find_package(C99) if(C99_FLAG_DETECTED) set(HAVE_C99 TRUE) set(STATUS_C99 "OK") else(C99_FLAG_DETECTED) set(STATUS_C99 "not found") set(LIBGROOVE_STATUS "missing dependencies") set(LIBGROOVE_PLAYER_STATUS "missing dependencies") set(LIBGROOVE_LOUDNESS_STATUS "missing dependencies") endif(C99_FLAG_DETECTED) # check for ebur128 if(DISABLE_LOUDNESS) set(STATUS_EBUR128 "not needed") else(DISABLE_LOUDNESS) find_package(ebur128) if(EBUR128_FOUND) set(STATUS_EBUR128 "OK") else(EBUR128_FOUND) set(STATUS_EBUR128 "not found") endif(EBUR128_FOUND) endif(DISABLE_LOUDNESS) # check for chromaprint if(DISABLE_FINGERPRINTER) set(STATUS_CHROMAPRINT "not needed") else(DISABLE_FINGERPRINTER) find_package(Chromaprint) if(CHROMAPRINT_FOUND) set(STATUS_CHROMAPRINT "OK") else(CHROMAPRINT_FOUND) set(STATUS_CHROMAPRINT "not found") endif(CHROMAPRINT_FOUND) endif(DISABLE_FINGERPRINTER) # check for SDL2 if(DISABLE_PLAYER) set(STATUS_SDL2 "not needed") else(DISABLE_PLAYER) find_package(SDL2) if(SDL2_FOUND) set(STATUS_SDL2 "OK") else(SDL2_FOUND) set(STATUS_SDL2 "not found") endif(SDL2_FOUND) endif(DISABLE_PLAYER) # check for libav find_package(LibAV) if(AVCODEC_FOUND) set(STATUS_LIBAVCODEC "OK") else() set(STATUS_LIBAVCODEC "not found") endif() if(AVFILTER_FOUND) set(STATUS_LIBAVFILTER "OK") else() set(STATUS_LIBAVFILTER "not found") endif() if(AVFORMAT_FOUND) set(STATUS_LIBAVFORMAT "OK") else() set(STATUS_LIBAVFORMAT "not found") endif() if(AVUTIL_FOUND) set(STATUS_LIBAVUTIL "OK") else() set(STATUS_LIBAVUTIL "not found") endif() # check for pthreads find_package(Threads) if(Threads_FOUND) set(STATUS_THREADS "OK") else(Threads_FOUND) set(STATUS_THREADS "not found") set(LIBGROOVE_STATUS "missing dependencies") set(LIBGROOVE_PLAYER_STATUS "missing dependencies") set(LIBGROOVE_LOUDNESS_STATUS "missing dependencies") endif(Threads_FOUND) configure_file ( "${PROJECT_SOURCE_DIR}/groove/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) set(LIB_CFLAGS "${C99_C_FLAGS} -pedantic -Werror -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes -D_REENTRANT -D_POSIX_C_SOURCE=200809L") set(EXAMPLE_CFLAGS "${C99_C_FLAGS} -pedantic -Werror -Wall -g") set(EXAMPLE_INCLUDES "${PROJECT_SOURCE_DIR}") add_library(groove SHARED ${LIBGROOVE_SOURCES} ${LIBGROOVE_HEADERS}) set_target_properties(groove PROPERTIES SOVERSION ${LIBGROOVE_VERSION_MAJOR} VERSION ${LIBGROOVE_VERSION} COMPILE_FLAGS ${LIB_CFLAGS} ) if(LIBGROOVE_LDFLAGS) set_target_properties(groove PROPERTIES LINK_FLAGS ${LIBGROOVE_LDFLAGS}) endif() include_directories(${PROJECT_SOURCE_DIR}) include_directories(${PROJECT_BINARY_DIR}) target_link_libraries(groove LINK_PRIVATE ${AVCODEC_LIBRARIES} ${AVFILTER_LIBRARIES} ${AVFORMAT_LIBRARIES}) target_link_libraries(groove LINK_PUBLIC ${AVUTIL_LIBRARIES}) if(UNIX) target_link_libraries(groove LINK_PRIVATE m) endif(UNIX) include_directories(${LIBAV_INCLUDE_DIRS}) target_link_libraries(groove LINK_PUBLIC ${CMAKE_THREAD_LIBS_INIT}) add_library(groove_static STATIC ${LIBGROOVE_SOURCES} ${LIBGROOVE_HEADERS}) set_target_properties(groove_static PROPERTIES OUTPUT_NAME groove COMPILE_FLAGS "${LIB_CFLAGS} -fPIC") install(TARGETS groove_static DESTINATION lib) install(FILES "groove/groove.h" "groove/queue.h" "groove/encoder.h" DESTINATION "include/groove") install(TARGETS groove DESTINATION lib) add_executable(metadata example/metadata.c) set_target_properties(metadata PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(metadata groove) add_dependencies(metadata groove) add_executable(transcode example/transcode.c) set_target_properties(transcode PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(transcode groove) add_dependencies(transcode groove) if(DISABLE_PLAYER) else() add_library(grooveplayer SHARED ${LIBGROOVE_PLAYER_SOURCES} ${LIBGROOVE_PLAYER_HEADERS}) set_target_properties(grooveplayer PROPERTIES SOVERSION ${LIBGROOVE_VERSION_MAJOR} VERSION ${LIBGROOVE_VERSION} COMPILE_FLAGS ${LIB_CFLAGS} ) target_link_libraries(grooveplayer LINK_PUBLIC groove) if(UNIX AND NOT APPLE) target_link_libraries(grooveplayer LINK_PRIVATE rt) endif() add_dependencies(grooveplayer groove) target_link_libraries(grooveplayer LINK_PRIVATE ${SDL2_LIBRARY}) include_directories(${SDL2_INCLUDE_DIR}) install(FILES "grooveplayer/player.h" DESTINATION "include/grooveplayer") install(TARGETS grooveplayer DESTINATION lib) add_library(grooveplayer_static STATIC ${LIBGROOVE_PLAYER_SOURCES} ${LIBGROOVE_PLAYER_HEADERS}) set_target_properties(grooveplayer_static PROPERTIES OUTPUT_NAME grooveplayer COMPILE_FLAGS "${LIB_CFLAGS} -fPIC") install(TARGETS grooveplayer_static DESTINATION lib) add_executable(playlist example/playlist.c ${PROJECT_SOURCE_DIR}/grooveplayer/player.h) set_target_properties(playlist PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(playlist groove grooveplayer) add_dependencies(playlist groove grooveplayer) endif() if(DISABLE_LOUDNESS) else() add_library(grooveloudness SHARED ${LIBGROOVE_LOUDNESS_SOURCES} ${LIBGROOVE_LOUDNESS_HEADERS}) set_target_properties(grooveloudness PROPERTIES SOVERSION ${LIBGROOVE_VERSION_MAJOR} VERSION ${LIBGROOVE_VERSION} COMPILE_FLAGS ${LIB_CFLAGS} ) target_link_libraries(grooveloudness LINK_PUBLIC groove ${CMAKE_THREAD_LIBS_INIT}) add_dependencies(grooveloudness groove) target_link_libraries(grooveloudness LINK_PRIVATE ${EBUR128_LIBRARY}) include_directories(${EBUR128_INCLUDE_DIR}) install(FILES "grooveloudness/loudness.h" DESTINATION "include/grooveloudness") install(TARGETS grooveloudness DESTINATION lib) add_library(grooveloudness_static STATIC ${LIBGROOVE_LOUDNESS_SOURCES} ${LIBGROOVE_LOUDNESS_HEADERS}) set_target_properties(grooveloudness_static PROPERTIES OUTPUT_NAME grooveloudness COMPILE_FLAGS "${LIB_CFLAGS} -fPIC") install(TARGETS grooveloudness_static DESTINATION lib) add_executable(replaygain example/replaygain.c) set_target_properties(replaygain PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(replaygain groove grooveloudness) add_dependencies(replaygain groove grooveloudness) endif() if(DISABLE_FINGERPRINTER) else() add_library(groovefingerprinter SHARED ${LIBGROOVE_FINGERPRINTER_SOURCES} ${LIBGROOVE_FINGERPRINTER_HEADERS}) set_target_properties(groovefingerprinter PROPERTIES SOVERSION ${LIBGROOVE_VERSION_MAJOR} VERSION ${LIBGROOVE_VERSION} COMPILE_FLAGS ${LIB_CFLAGS} ) target_link_libraries(groovefingerprinter LINK_PUBLIC groove ${CMAKE_THREAD_LIBS_INIT}) add_dependencies(groovefingerprinter groove) target_link_libraries(groovefingerprinter LINK_PRIVATE ${CHROMAPRINT_LIBRARY}) include_directories(${CHROMAPRINT_INCLUDE_DIR}) install(FILES "groovefingerprinter/fingerprinter.h" DESTINATION "include/groovefingerprinter") install(TARGETS groovefingerprinter DESTINATION lib) add_library(groovefingerprinter_static STATIC ${LIBGROOVE_FINGERPRINTER_SOURCES} ${LIBGROOVE_FINGERPRINTER_HEADERS}) set_target_properties(groovefingerprinter_static PROPERTIES OUTPUT_NAME groovefingerprinter COMPILE_FLAGS "${LIB_CFLAGS} -fPIC") install(TARGETS groovefingerprinter_static DESTINATION lib) add_executable(fingerprint example/fingerprint.c) set_target_properties(fingerprint PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(fingerprint groove groovefingerprinter) add_dependencies(fingerprint groove groovefingerprinter) endif() message("\n" "Installation Summary\n" "--------------------\n" "* Install Directory : ${CMAKE_INSTALL_PREFIX}\n" "* Build libgroove : ${LIBGROOVE_STATUS}\n" "* Build libgrooveplayer : ${LIBGROOVE_PLAYER_STATUS}\n" "* Build libgrooveloudness : ${LIBGROOVE_LOUDNESS_STATUS}\n" "* Build libgroovefingerprinter : ${LIBGROOVE_FINGERPRINTER_STATUS}\n" ) message( "System Dependencies\n" "-------------------\n" "* C99 Compiler : ${STATUS_C99}\n" "* threads : ${STATUS_THREADS}\n" "* SDL2 : ${STATUS_SDL2}\n" "* ebur128 : ${STATUS_EBUR128}\n" "* chromaprint : ${STATUS_CHROMAPRINT}\n" "* libavformat : ${STATUS_LIBAVFORMAT}\n" "* libavcodec : ${STATUS_LIBAVCODEC}\n" "* libavfilter : ${STATUS_LIBAVFILTER}\n" "* libavutil : ${STATUS_LIBAVUTIL}" ) message("\n" "If everything is looks good, proceed with\n" "make\n" ) libgroove-4.3.0/LICENSE000066400000000000000000000020721253074642100145200ustar00rootroot00000000000000The MIT License (Expat) Copyright (c) 2014 Andrew Kelley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libgroove-4.3.0/README.md000066400000000000000000000140441253074642100147740ustar00rootroot00000000000000libgroove ========= This library provides decoding and encoding of audio on a playlist. It is intended to be used as a backend for music player applications. That said, it is also generic enough to be used as a backend for any streaming audio processing utility. Features -------- * Uses [libav](http://www.libav.org/) for robust decoding and encoding. A list of supported file formats and codecs [is available](http://www.libav.org/general.html#Supported-File-Formats-and-Codecs). * Add and remove entries on a playlist for gapless playback. * Supports idempotent pause, play, and seek. * Per-playlist-item gain adjustment so you can implement loudness compensation without audio glitches. * Read and write metadata tags. * Choose between smooth mode and exact mode during playback. * **smooth mode** - open the audio device once and resample everything to fit that sample rate and format. * **exact mode** - open and close the audio device as necessary in effort to open the audio device with parameters matching the incoming audio data. * Extensible sink-based interface. A sink provides resampling and keeps its buffer full. Types of sinks: * **raw sink** - Provides reference-counted raw audio buffers you can do whatever you like with. For example a real-time audio visualization. All other sink types are built on top of this one. * **player sink** - Sends frames to a sound device. * **encoder sink** - Provides encoded audio buffers. For example, you could use this to create an HTTP audio stream. * **loudness scanner sink** - Uses the [EBU R 128](http://tech.ebu.ch/loudness) standard to detect loudness. The values it produces are compatible with the [ReplayGain](http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification) specification. * **fingerprint sink** - Uses [chromaprint](http://acoustid.org/chromaprint) to generate unique song IDs that can be used with the acoustid service. * Thread-safe. * Example programs included: * `playlist` - Play a series of songs with gapless playback. * `metadata` - Read or update song metadata. * `replaygain` - Report the suggested replaygain for a set of files. * `transcode` - Transcode one or more files into one output file. * `fingerprint` - Generate acoustid fingerprints for one or more files. Dependencies ------------ You will need these to compile libgroove. * [cmake](http://www.cmake.org/) * [libav](http://libav.org) * suggested flags: `--enable-shared --disable-static --enable-libmp3lame --enable-libvorbis --enable-gpl` * [libebur128](https://github.com/jiixyj/libebur128) * make sure it is compiled with the speex dependency so that true peak functions are available. * [libsdl2-dev](http://www.libsdl.org/) * [libchromaprint-dev](http://acoustid.org/chromaprint) Installation ------------ Installing from a package is recommended, but instructions for installing from source are also provided at the end of this list. ### [Ubuntu PPA](https://launchpad.net/~andrewrk/+archive/libgroove) Note: as of Ubuntu 14.10 Utopic Unicorn, libgroove is included in the default repository index so you don't need a PPA. ```sh sudo apt-add-repository ppa:andrewrk/libgroove sudo apt-get update sudo apt-get install libgroove-dev libgrooveplayer-dev libgrooveloudness-dev \ libgroovefingerprinter-dev ``` ### [FreeBSD Port](http://www.freshports.org/audio/libgroove/) ```sh pkg install audio/libgroove ``` ### [Debian](http://packages.qa.debian.org/libg/libgroove.html) libgroove ships with Debian Jessie. ```sh sudo apt-get install libgroove-dev libgrooveplayer-dev \ libgrooveloudness-dev libgroovefingerprinter-dev ``` ### [Arch Linux](https://aur.archlinux.org/packages/libgroove/) libgroove is available through the [AUR](https://aur.archlinux.org/). ```sh wget https://aur.archlinux.org/packages/li/libgroove/libgroove.tar.gz tar xzf libgroove.tar.gz cd libgroove makepkg sudo pacman -U libgroove-* ``` Some notes: * libgroove depends upon several other packages. Dependencies available through the official repositories can be installed with pacman (e.g. `pacman -S --asdeps sdl2`), and dependencies available through the AUR can be installed via the procedure shown above. * An [AUR helper](https://wiki.archlinux.org/index.php/AUR_helper) can ease the process of installing packages from the AUR. * The [AUR User Guidelines](https://wiki.archlinux.org/index.php/AUR_User_Guidelines) page on the Arch Wiki contains gobs of useful information. Please see that page if you have any further questions about using the AUR. ### [Mac OS X Homebrew](http://brew.sh/) ```sh brew install libgroove ``` ### From Source ```sh mkdir build && cd build && cmake ../ # Verify that the configure output is to your liking. make sudo make install ``` Documentation ------------- Check out the example programs in the example folder. Read header files for the relevant APIs: * groove/groove.h * globals * GrooveFile * GroovePlaylist * GrooveBuffer * GrooveSink * groove/encoder.h * GrooveEncoder * grooveplayer/player.h * GroovePlayer * grooveloudness/loudness.h * GrooveLoudnessDetector * groovefingerprinter/fingerprinter.h * GrooveFingerprinter Join #libgroove on irc.freenode.org and ask questions. Projects Using libgroove ------------------------ Feel free to make a pull request adding yours to this list. * [waveform](https://github.com/andrewrk/waveform) generates PNG waveform visualizations. * [node-groove](https://github.com/andrewrk/node-groove) provides [Node.js](http://nodejs.org/) bindings to libgroove. * [Groove Basin](https://github.com/andrewrk/groovebasin) is a music player with lazy multi-core replaygain scanning, a web interface inspired by Amarok 1.4, http streaming, upload, download and a dynamic playlist mode. * [groove-rs](https://github.com/andrewrk/groove-rs) provides [rust](http://rust-lang.org) bindings to libgroove. * [ruby-groove](https://github.com/johnmuhl/ruby-groove) provides Ruby FFI bindings to libgroove. * [TrenchBowl](https://github.com/andrewrk/TrenchBowl) is a simple Qt GUI on top of libgroove. libgroove-4.3.0/cmake/000077500000000000000000000000001253074642100145725ustar00rootroot00000000000000libgroove-4.3.0/cmake/FindC99.cmake000066400000000000000000000113561253074642100167470ustar00rootroot00000000000000# - Finds C99 standard support # This internally calls the check_c_source_compiles macro to determine the # appropriate flags for a C99 standard compilation. #============================================================================= # Copyright 2013 Ian Liu Rodrigues # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . #============================================================================= include(CheckCSourceCompiles) set(C99_C_TEST_SOURCE "#include #include #include #include #include // Check varargs macros. These examples are taken from C99 6.10.3.5. #define debug(...) fprintf (stderr, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug (\"Flag\"); debug (\"X = %d\\\\n\", x); showlist (The first, second, and third items.); report (x>y, \"x is %d but y is %d\", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK your preprocessor is broken; #endif #if BIG_OK #else your preprocessor is broken; #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\\\\0'; ++i) continue; return 0; } // Check varargs and va_copy. static void test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = NULL; int number = 0; float fnumber = 0.0; while (*format) { switch (*format++) { case 's': // string str = va_arg (args_copy, const char *); break; case 'd': // int number = va_arg (args_copy, int); break; case 'f': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); number = (number != 0) && (str != NULL) && (fnumber != 0.0); } int main () { // Check bool. _Bool success = false; // Check restrict. if (test_restrict (\"String literal\") == 0) success = true; char *restrict newvar = \"Another string\"; // Check varargs. test_varargs (\"s, d' f .\", \"string\", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Check named initializers. struct named_init ni = { .number = 34, .name = L\"Test wide string\", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings return (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == 'x' || dynamic_array[ni.number - 1] != 543); ; return 0; }") set(C99_C_FLAG_CANDIDATES " " "-std=c99" "-std=gnu99" "-c99" "-AC99" "-xc99=all" "-qlanglvl=extc99" ) if(DEFINED C99_C_FLAGS) set(C99_C_FLAG_CANDIDATES) endif(DEFINED C99_C_FLAGS) set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") foreach(FLAG ${C99_C_FLAG_CANDIDATES}) set(CMAKE_REQUIRED_FLAGS "${FLAG}") unset(C99_FLAG_DETECTED CACHE) message(STATUS "Try C99 C flag = [${FLAG}]") check_c_source_compiles("${C99_C_TEST_SOURCE}" C99_FLAG_DETECTED) if(C99_FLAG_DETECTED) set(C99_C_FLAGS_INTERNAL "${FLAG}") break() endif(C99_FLAG_DETECTED) endforeach(FLAG ${C99_C_FLAG_CANDIDATES}) set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") set(C99_C_FLAGS "${C99_C_FLAGS_INTERNAL}" CACHE STRING "C compiler flags for C99 standard") mark_as_advanced(C99_C_FLAGS)libgroove-4.3.0/cmake/FindChromaprint.cmake000066400000000000000000000007511253074642100206660ustar00rootroot00000000000000# Copyright (c) 2014 Andrew Kelley # This file is MIT licensed. # See http://opensource.org/licenses/MIT # CHROMAPRINT_FOUND # CHROMAPRINT_INCLUDE_DIR # CHROMAPRINT_LIBRARY find_path(CHROMAPRINT_INCLUDE_DIR NAMES chromaprint.h) find_library(CHROMAPRINT_LIBRARY NAMES chromaprint) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CHROMAPRINT DEFAULT_MSG CHROMAPRINT_LIBRARY CHROMAPRINT_INCLUDE_DIR) mark_as_advanced(CHROMAPRINT_INCLUDE_DIR CHROMAPRINT_LIBRARY) libgroove-4.3.0/cmake/FindLibAV.cmake000066400000000000000000000041351253074642100173350ustar00rootroot00000000000000# Copyright (c) 2014 Andrew Kelley # This file is MIT licensed. # See http://opensource.org/licenses/MIT # LIBAV_FOUND # LIBAV_INCLUDE_DIRS # LIBAV_LIBRARIES # AVFILTER_FOUND # AVFILTER_INCLUDE_DIRS # AVFILTER_LIBRARIES # AVFORMAT_FOUND # AVFORMAT_INCLUDE_DIRS # AVFORMAT_LIBRARIES # AVCODEC_FOUND # AVCODEC_INCLUDE_DIRS # AVCODEC_LIBRARIES # AVUTIL_FOUND # AVUTIL_INCLUDE_DIRS # AVUTIL_LIBRARIES find_path(AVFILTER_INCLUDE_DIRS NAMES libavfilter/avfilter.h) find_library(AVFILTER_LIBRARIES NAMES avfilter) if(AVFILTER_LIBRARIES AND AVFILTER_INCLUDE_DIRS) set(AVFILTER_FOUND TRUE) else() set(AVFILTER_FOUND FALSE) endif() find_path(AVFORMAT_INCLUDE_DIRS NAMES libavformat/avformat.h) find_library(AVFORMAT_LIBRARIES NAMES avformat) if(AVFORMAT_LIBRARIES AND AVFORMAT_INCLUDE_DIRS) set(AVFORMAT_FOUND TRUE) else() set(AVFORMAT_FOUND FALSE) endif() find_path(AVCODEC_INCLUDE_DIRS NAMES libavcodec/avcodec.h) find_library(AVCODEC_LIBRARIES NAMES avcodec) if(AVCODEC_LIBRARIES AND AVCODEC_INCLUDE_DIRS) set(AVCODEC_FOUND TRUE) else() set(AVCODEC_FOUND FALSE) endif() find_path(AVUTIL_INCLUDE_DIRS NAMES libavutil/avutil.h) find_library(AVUTIL_LIBRARIES NAMES avutil) if(AVUTIL_LIBRARIES AND AVUTIL_INCLUDE_DIRS) set(AVUTIL_FOUND TRUE) else() set(AVUTIL_FOUND FALSE) endif() if(AVFILTER_FOUND AND AVFORMAT_FOUND AND AVCODEC_FOUND AND AVUTIL_FOUND) set(LIBAV_FOUND TRUE) set(LIBAV_INCLUDE_DIRS ${AVFILTER_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) set(LIBAV_LIBRARIES ${AVFILTER_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES}) else() set(LIBAV_FOUND FALSE) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LIBAV DEFAULT_MSG AVFILTER_LIBRARIES AVFILTER_INCLUDE_DIRS AVFORMAT_LIBRARIES AVFORMAT_INCLUDE_DIRS AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIRS AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIRS) mark_as_advanced( AVFILTER_INCLUDE_DIRS AVFILTER_LIBRARIES AVFORMAT_INCLUDE_DIRS AVFORMAT_LIBRARIES AVCODEC_INCLUDE_DIRS AVCODEC_LIBRARIES AVUTIL_INCLUDE_DIRS AVUTIL_LIBRARIES) libgroove-4.3.0/cmake/FindSDL2.cmake000066400000000000000000000006311253074642100171010ustar00rootroot00000000000000# Copyright (c) 2014 Andrew Kelley # This file is MIT licensed. # See http://opensource.org/licenses/MIT # SDL2_FOUND # SDL2_INCLUDE_DIR # SDL2_LIBRARY find_path(SDL2_INCLUDE_DIR NAMES SDL2/SDL.h) find_library(SDL2_LIBRARY NAMES SDL2) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SDL2 DEFAULT_MSG SDL2_LIBRARY SDL2_INCLUDE_DIR) mark_as_advanced(SDL2_INCLUDE_DIR SDL2_LIBRARY) libgroove-4.3.0/cmake/Findebur128.cmake000066400000000000000000000006711253074642100175710ustar00rootroot00000000000000# Copyright (c) 2014 Andrew Kelley # This file is MIT licensed. # See http://opensource.org/licenses/MIT # EBUR128_FOUND # EBUR128_INCLUDE_DIR # EBUR128_LIBRARY find_path(EBUR128_INCLUDE_DIR NAMES ebur128.h) find_library(EBUR128_LIBRARY NAMES ebur128) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(EBUR128 DEFAULT_MSG EBUR128_LIBRARY EBUR128_INCLUDE_DIR) mark_as_advanced(EBUR128_INCLUDE_DIR EBUR128_LIBRARY) libgroove-4.3.0/example/000077500000000000000000000000001253074642100151455ustar00rootroot00000000000000libgroove-4.3.0/example/fingerprint.c000066400000000000000000000051311253074642100176400ustar00rootroot00000000000000/* compute the acoustid of a list of songs */ #include #include #include #include #include static int usage(char *arg0) { fprintf(stderr, "Usage: %s [--raw] file1 file2 ...\n", arg0); return 1; } int main(int argc, char * argv[]) { if (argc < 2) return usage(argv[0]); groove_init(); atexit(groove_finish); groove_set_logging(GROOVE_LOG_INFO); struct GroovePlaylist *playlist = groove_playlist_create(); int raw = 0; for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == '-') { arg += 2; if (strcmp(arg, "raw") == 0) { raw = 1; } else { return usage(argv[0]); } } else { struct GrooveFile * file = groove_file_open(arg); if (!file) { fprintf(stderr, "Unable to open %s\n", arg); continue; } groove_playlist_insert(playlist, file, 1.0, 1.0, NULL); } } struct GrooveFingerprinter *printer = groove_fingerprinter_create(); groove_fingerprinter_attach(printer, playlist); struct GrooveFingerprinterInfo info; while (groove_fingerprinter_info_get(printer, &info, 1) == 1) { if (info.item) { printf("\nduration: %f: %s\n", info.duration, info.item->file->filename); if (raw) { for (int i = 0; i < info.fingerprint_size; i += 1) { printf("%"PRId32"\n", info.fingerprint[i]); } } else { char *encoded_fp; if (groove_fingerprinter_encode(info.fingerprint, info.fingerprint_size, &encoded_fp) < 0) { fprintf(stderr, "Unable to encode fingerprint\n"); } else { printf("%s\n", encoded_fp); groove_fingerprinter_dealloc(encoded_fp); } } groove_fingerprinter_free_info(&info); } else { break; } } struct GroovePlaylistItem *item = playlist->head; while (item) { struct GrooveFile *file = item->file; struct GroovePlaylistItem *next = item->next; groove_playlist_remove(playlist, item); groove_file_close(file); item = next; } groove_fingerprinter_detach(printer); groove_fingerprinter_destroy(printer); groove_playlist_destroy(playlist); return 0; } libgroove-4.3.0/example/libgroove.pc000066400000000000000000000004631253074642100174640ustar00rootroot00000000000000# For more information: http://www.freedesktop.org/wiki/Software/pkg-config/ libdir= includedir= Name: libgroove Description: libgroove provides decoding and encoding of audio on a playlist. Version: Libs: -L${libdir} -lgroove -lgrooveplayer -lgrooveloudness -lgroovefingerprinter Cflags: -I${includedir} libgroove-4.3.0/example/metadata.c000066400000000000000000000043431253074642100170750ustar00rootroot00000000000000/* read or update metadata in a media file */ #include #include #include #include static int usage(char *exe) { fprintf(stderr, "Usage: %s [--update key value] [--delete key]\n" "Repeat --update and --delete as many times as you need to.\n", exe); return 1; } int main(int argc, char * argv[]) { /* parse arguments */ char *exe = argv[0]; char *filename; struct GrooveFile *file; int i; char *arg; char *key; char *value; struct GrooveTag *tag; if (argc < 2) return usage(exe); printf("Using libgroove v%s\n", groove_version()); filename = argv[1]; groove_init(); atexit(groove_finish); groove_set_logging(GROOVE_LOG_INFO); file = groove_file_open(filename); if (!file) { fprintf(stderr, "error opening file\n"); return 1; } for (i = 2; i < argc; i += 1) { arg = argv[i]; if (strcmp("--update", arg) == 0) { if (i + 2 >= argc) { groove_file_close(file); fprintf(stderr, "--update requires 2 arguments"); return usage(exe); } key = argv[++i]; value = argv[++i]; groove_file_metadata_set(file, key, value, 0); } else if (strcmp("--delete", arg) == 0) { if (i + 1 >= argc) { groove_file_close(file); fprintf(stderr, "--delete requires 1 argument"); return usage(exe); } key = argv[++i]; groove_file_metadata_set(file, key, NULL, 0); } else { groove_file_close(file); return usage(exe); } } struct GrooveAudioFormat audio_format; groove_file_audio_format(file, &audio_format); printf("channels=%d\n", groove_channel_layout_count(audio_format.channel_layout)); tag = NULL; printf("duration=%f\n", groove_file_duration(file)); while ((tag = groove_file_metadata_get(file, "", tag, 0))) printf("%s=%s\n", groove_tag_key(tag), groove_tag_value(tag)); if (file->dirty && groove_file_save(file) < 0) fprintf(stderr, "error saving file\n"); groove_file_close(file); return 0; } libgroove-4.3.0/example/playlist.c000066400000000000000000000063701253074642100171600ustar00rootroot00000000000000/* play several files in a row and then exit */ #include #include #include #include static int usage(const char *exe) { fprintf(stderr, "Usage: %s [--volume 1.0] [--exact] [--dummy] file1 file2 ...\n", exe); return 1; } int main(int argc, char * argv[]) { // parse arguments const char *exe = argv[0]; if (argc < 2) return usage(exe); groove_init(); atexit(groove_finish); groove_set_logging(GROOVE_LOG_INFO); struct GroovePlaylist *playlist = groove_playlist_create(); if (!playlist) { fprintf(stderr, "Error creating playlist.\n"); return 1; } struct GroovePlayer *player = groove_player_create(); for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == '-') { arg += 2; if (strcmp(arg, "dummy") == 0) { player->device_index = GROOVE_PLAYER_DUMMY_DEVICE; } else if (strcmp(arg, "exact") == 0) { player->use_exact_audio_format = 1; } else if (i + 1 >= argc) { return usage(exe); } else if (strcmp(arg, "volume") == 0) { double volume = atof(argv[++i]); groove_playlist_set_gain(playlist, volume); } else { return usage(exe); } } else { struct GrooveFile * file = groove_file_open(arg); if (!file) { fprintf(stderr, "Not queuing %s\n", arg); continue; } groove_playlist_insert(playlist, file, 1.0, 1.0, NULL); } } groove_player_attach(player, playlist); union GroovePlayerEvent event; struct GroovePlaylistItem *item; while (groove_player_event_get(player, &event, 1) >= 0) { switch (event.type) { case GROOVE_EVENT_BUFFERUNDERRUN: fprintf(stderr, "buffer underrun\n"); break; case GROOVE_EVENT_DEVICEREOPENED: fprintf(stderr, "device re-opened\n"); break; case GROOVE_EVENT_NOWPLAYING: groove_player_position(player, &item, NULL); if (!item) { printf("done\n"); item = playlist->head; while (item) { struct GrooveFile *file = item->file; struct GroovePlaylistItem *next = item->next; groove_playlist_remove(playlist, item); groove_file_close(file); item = next; } groove_player_detach(player); groove_player_destroy(player); groove_playlist_destroy(playlist); return 0; } struct GrooveTag *artist_tag = groove_file_metadata_get(item->file, "artist", NULL, 0); struct GrooveTag *title_tag = groove_file_metadata_get(item->file, "title", NULL, 0); if (artist_tag && title_tag) { printf("Now playing: %s - %s\n", groove_tag_value(artist_tag), groove_tag_value(title_tag)); } else { printf("Now playing: %s\n", item->file->filename); } break; } } return 1; } libgroove-4.3.0/example/replaygain.c000066400000000000000000000043451253074642100174520ustar00rootroot00000000000000/* replaygain scanner */ #include #include #include static double clamp_rg(double x) { if (x < -51.0) return -51.0; else if (x > 51.0) return 51.0; else return x; } double loudness_to_replaygain(double loudness) { return clamp_rg(-18.0 - loudness); } int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s file1 file2 ...\n", argv[0]); return 1; } groove_init(); atexit(groove_finish); groove_set_logging(GROOVE_LOG_INFO); struct GroovePlaylist *playlist = groove_playlist_create(); for (int i = 1; i < argc; i += 1) { char * filename = argv[i]; struct GrooveFile * file = groove_file_open(filename); if (!file) { fprintf(stderr, "Unable to open %s\n", filename); continue; } groove_playlist_insert(playlist, file, 1.0, 1.0, NULL); } struct GrooveLoudnessDetector *detector = groove_loudness_detector_create(); groove_loudness_detector_attach(detector, playlist); struct GrooveLoudnessDetectorInfo info; while (groove_loudness_detector_info_get(detector, &info, 1) == 1) { if (info.item) { fprintf(stderr, "\nfile complete: %s\n", info.item->file->filename); fprintf(stderr, "suggested gain: %.2f dB, sample peak: %f, duration: %fs\n", loudness_to_replaygain(info.loudness), info.peak, info.duration); } else { fprintf(stderr, "\nAll files complete.\n"); fprintf(stderr, "suggested gain: %.2f dB, sample peak: %f, duration: %fs\n", loudness_to_replaygain(info.loudness), info.peak, info.duration); break; } } struct GroovePlaylistItem *item = playlist->head; while (item) { struct GrooveFile *file = item->file; struct GroovePlaylistItem *next = item->next; groove_playlist_remove(playlist, item); groove_file_close(file); item = next; } groove_loudness_detector_detach(detector); groove_loudness_detector_destroy(detector); groove_playlist_destroy(playlist); return 0; } libgroove-4.3.0/example/transcode.c000066400000000000000000000065051253074642100173010ustar00rootroot00000000000000/* transcode one or more files into one output file */ #include #include #include #include #include static int usage(char *arg0) { fprintf(stderr, "Usage: %s file1 [file2 ...] --output outputfile [--bitrate 320] [--format name] [--codec name] [--mime mimetype]\n", arg0); return 1; } int main(int argc, char * argv[]) { // arg parsing int bit_rate_k = 320; char *format = NULL; char *codec = NULL; char *mime = NULL; char *output_file_name = NULL; groove_init(); atexit(groove_finish); groove_set_logging(GROOVE_LOG_INFO); struct GroovePlaylist *playlist = groove_playlist_create(); for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == '-') { arg += 2; if (i + 1 >= argc) { return usage(argv[0]); } else if (strcmp(arg, "bitrate") == 0) { bit_rate_k = atoi(argv[++i]); } else if (strcmp(arg, "format") == 0) { format = argv[++i]; } else if (strcmp(arg, "codec") == 0) { codec = argv[++i]; } else if (strcmp(arg, "mime") == 0) { mime = argv[++i]; } else if (strcmp(arg, "output") == 0) { output_file_name = argv[++i]; } else { return usage(argv[0]); } } else { struct GrooveFile * file = groove_file_open(arg); if (!file) { fprintf(stderr, "Error opening input file %s\n", arg); return 1; } groove_playlist_insert(playlist, file, 1.0, 1.0, NULL); } } if (!output_file_name) return usage(argv[0]); struct GrooveEncoder *encoder = groove_encoder_create(); encoder->bit_rate = bit_rate_k * 1000; encoder->format_short_name = format; encoder->codec_short_name = codec; encoder->filename = output_file_name; encoder->mime_type = mime; if (groove_playlist_count(playlist) == 1) { groove_file_audio_format(playlist->head->file, &encoder->target_audio_format); // copy metadata struct GrooveTag *tag = NULL; while((tag = groove_file_metadata_get(playlist->head->file, "", tag, 0))) { groove_encoder_metadata_set(encoder, groove_tag_key(tag), groove_tag_value(tag), 0); } } if (groove_encoder_attach(encoder, playlist) < 0) { fprintf(stderr, "error attaching encoder\n"); return 1; } FILE *f = fopen(output_file_name, "wb"); if (!f) { fprintf(stderr, "Error opening output file %s\n", output_file_name); return 1; } struct GrooveBuffer *buffer; while (groove_encoder_buffer_get(encoder, &buffer, 1) == GROOVE_BUFFER_YES) { fwrite(buffer->data[0], 1, buffer->size, f); groove_buffer_unref(buffer); } fclose(f); groove_encoder_detach(encoder); groove_encoder_destroy(encoder); struct GroovePlaylistItem *item = playlist->head; while (item) { struct GrooveFile *file = item->file; struct GroovePlaylistItem *next = item->next; groove_playlist_remove(playlist, item); groove_file_close(file); item = next; } groove_playlist_destroy(playlist); return 0; } libgroove-4.3.0/groove/000077500000000000000000000000001253074642100150135ustar00rootroot00000000000000libgroove-4.3.0/groove/buffer.c000066400000000000000000000016721253074642100164360ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "buffer.h" #include void groove_buffer_ref(struct GrooveBuffer *buffer) { struct GrooveBufferPrivate *b = (struct GrooveBufferPrivate *) buffer; pthread_mutex_lock(&b->mutex); b->ref_count += 1; pthread_mutex_unlock(&b->mutex); } void groove_buffer_unref(struct GrooveBuffer *buffer) { if (!buffer) return; struct GrooveBufferPrivate *b = (struct GrooveBufferPrivate *) buffer; pthread_mutex_lock(&b->mutex); b->ref_count -= 1; int free = b->ref_count == 0; pthread_mutex_unlock(&b->mutex); if (free) { pthread_mutex_destroy(&b->mutex); if (b->is_packet && b->data) { av_free(b->data); } else if (b->frame) { av_frame_free(&b->frame); } av_free(b); } } libgroove-4.3.0/groove/buffer.h000066400000000000000000000011501253074642100164320ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_BUFFER_H_INCLUDED #define GROOVE_BUFFER_H_INCLUDED #include "groove.h" #include #include #include struct GrooveBufferPrivate { struct GrooveBuffer externals; AVFrame *frame; int is_packet; int ref_count; pthread_mutex_t mutex; // used for when is_packet is true // GrooveBuffer::data[0] will point to this uint8_t *data; }; #endif /* GROOVE_BUFFER_H_INCLUDED */ libgroove-4.3.0/groove/config.h.in000066400000000000000000000003311253074642100170330ustar00rootroot00000000000000#define GROOVE_VERSION_MAJOR @LIBGROOVE_VERSION_MAJOR@ #define GROOVE_VERSION_MINOR @LIBGROOVE_VERSION_MINOR@ #define GROOVE_VERSION_PATCH @LIBGROOVE_VERSION_PATCH@ #define GROOVE_VERSION_STRING "@LIBGROOVE_VERSION@" libgroove-4.3.0/groove/encoder.c000066400000000000000000000531311253074642100166010ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "encoder.h" #include "queue.h" #include "buffer.h" #include #include #include #include #include #include #include #include #include struct GrooveEncoderPrivate { struct GrooveEncoder externals; struct GrooveQueue *audioq; struct GrooveSink *sink; AVFormatContext *fmt_ctx; AVOutputFormat *oformat; AVCodec *codec; AVStream *stream; AVPacket pkt; int audioq_size; // in bytes int abort_request; // set temporarily struct GroovePlaylistItem *purge_item; // encode_head_mutex applies to variables inside this block. pthread_mutex_t encode_head_mutex; char encode_head_mutex_inited; // encode_thread waits on this when the encoded audio buffer queue // is full. pthread_cond_t drain_cond; char drain_cond_inited; struct GroovePlaylistItem *encode_head; double encode_pos; uint64_t encode_pts; struct GrooveAudioFormat encode_format; pthread_t thread_id; AVIOContext *avio; unsigned char *avio_buf; int sent_header; char strbuf[512]; AVDictionary *metadata; uint64_t next_pts; }; static struct GrooveBuffer *end_of_q_sentinel = NULL; static int encode_buffer(struct GrooveEncoder *encoder, struct GrooveBuffer *buffer) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; av_init_packet(&e->pkt); AVFrame *frame = NULL; if (buffer) { e->encode_head = buffer->item; e->encode_pos = buffer->pos; e->encode_format = buffer->format; struct GrooveBufferPrivate *b = (struct GrooveBufferPrivate *) buffer; frame = b->frame; frame->pts = e->next_pts; e->encode_pts = e->next_pts; e->next_pts += buffer->frame_count + 1; } int got_packet = 0; int errcode = avcodec_encode_audio2(e->stream->codec, &e->pkt, frame, &got_packet); if (errcode < 0) { av_strerror(errcode, e->strbuf, sizeof(e->strbuf)); av_log(NULL, AV_LOG_ERROR, "error encoding audio frame: %s\n", e->strbuf); return -1; } if (!got_packet) return -1; av_write_frame(e->fmt_ctx, &e->pkt); av_free_packet(&e->pkt); return 0; } static void cleanup_avcontext(struct GrooveEncoderPrivate *e) { if (e->stream) { avcodec_close(e->stream->codec); // stream is freed by freeing the AVFormatContext e->stream = NULL; } if (e->fmt_ctx) { avformat_free_context(e->fmt_ctx); e->fmt_ctx = NULL; } e->sent_header = 0; e->encode_head = NULL; e->encode_pos = -1.0; e->encode_pts = 0; e->next_pts = 0; } static int init_avcontext(struct GrooveEncoder *encoder) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; e->fmt_ctx = avformat_alloc_context(); if (!e->fmt_ctx) { av_log(NULL, AV_LOG_ERROR, "unable to allocate format context\n"); return -1; } e->fmt_ctx->pb = e->avio; e->fmt_ctx->oformat = e->oformat; e->stream = avformat_new_stream(e->fmt_ctx, e->codec); if (!e->stream) { av_log(NULL, AV_LOG_ERROR, "unable to create output stream\n"); return -1; } AVCodecContext *codec_ctx = e->stream->codec; codec_ctx->bit_rate = encoder->bit_rate; codec_ctx->sample_fmt = (enum AVSampleFormat)encoder->actual_audio_format.sample_fmt; codec_ctx->sample_rate = encoder->actual_audio_format.sample_rate; codec_ctx->channel_layout = encoder->actual_audio_format.channel_layout; codec_ctx->channels = av_get_channel_layout_nb_channels(encoder->actual_audio_format.channel_layout); codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; int err = avcodec_open2(codec_ctx, e->codec, NULL); if (err < 0) { av_strerror(err, e->strbuf, sizeof(e->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to open codec: %s\n", e->strbuf); return -1; } e->stream->codec = codec_ctx; return 0; } static void *encode_thread(void *arg) { struct GrooveEncoder *encoder = arg; struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; struct GrooveBuffer *buffer; while (!e->abort_request) { pthread_mutex_lock(&e->encode_head_mutex); if (e->audioq_size >= encoder->encoded_buffer_size) { pthread_cond_wait(&e->drain_cond, &e->encode_head_mutex); pthread_mutex_unlock(&e->encode_head_mutex); continue; } // we definitely want to unlock the mutex while we wait for the // next buffer. Otherwise there will be a deadlock when sink_flush or // sink_purge is called. pthread_mutex_unlock(&e->encode_head_mutex); int result = groove_sink_buffer_get(e->sink, &buffer, 1); pthread_mutex_lock(&e->encode_head_mutex); if (result == GROOVE_BUFFER_END) { // flush encoder with empty packets while (encode_buffer(encoder, NULL) >= 0) {} // then flush format context with empty packets while (av_write_frame(e->fmt_ctx, NULL) == 0) {} // send trailer avio_flush(e->avio); av_log(NULL, AV_LOG_INFO, "encoder: writing trailer\n"); if (av_write_trailer(e->fmt_ctx) < 0) { av_log(NULL, AV_LOG_ERROR, "could not write trailer\n"); } avio_flush(e->avio); groove_queue_put(e->audioq, end_of_q_sentinel); cleanup_avcontext(e); init_avcontext(encoder); pthread_mutex_unlock(&e->encode_head_mutex); continue; } if (result != GROOVE_BUFFER_YES) { pthread_mutex_unlock(&e->encode_head_mutex); break; } if (!e->sent_header) { avio_flush(e->avio); // copy metadata to format context av_dict_free(&e->fmt_ctx->metadata); AVDictionaryEntry *tag = NULL; while((tag = av_dict_get(e->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { av_dict_set(&e->fmt_ctx->metadata, tag->key, tag->value, AV_DICT_IGNORE_SUFFIX); } av_log(NULL, AV_LOG_INFO, "encoder: writing header\n"); if (avformat_write_header(e->fmt_ctx, NULL) < 0) { av_log(NULL, AV_LOG_ERROR, "could not write header\n"); } avio_flush(e->avio); e->sent_header = 1; } encode_buffer(encoder, buffer); pthread_mutex_unlock(&e->encode_head_mutex); groove_buffer_unref(buffer); } return NULL; } static void sink_purge(struct GrooveSink *sink, struct GroovePlaylistItem *item) { struct GrooveEncoder *encoder = sink->userdata; struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; pthread_mutex_lock(&e->encode_head_mutex); e->purge_item = item; groove_queue_purge(e->audioq); e->purge_item = NULL; if (e->encode_head == item) { e->encode_head = NULL; e->encode_pos = -1.0; } pthread_cond_signal(&e->drain_cond); pthread_mutex_unlock(&e->encode_head_mutex); } static void sink_flush(struct GrooveSink *sink) { struct GrooveEncoder *encoder = sink->userdata; struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; pthread_mutex_lock(&e->encode_head_mutex); groove_queue_flush(e->audioq); cleanup_avcontext(e); init_avcontext(encoder); groove_queue_put(e->audioq, end_of_q_sentinel); pthread_cond_signal(&e->drain_cond); pthread_mutex_unlock(&e->encode_head_mutex); } static int audioq_purge(struct GrooveQueue* queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return 0; struct GrooveEncoderPrivate *e = queue->context; return buffer->item == e->purge_item; } static void audioq_cleanup(struct GrooveQueue* queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveEncoderPrivate *e = queue->context; e->audioq_size -= buffer->size; groove_buffer_unref(buffer); } static void audioq_put(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveEncoderPrivate *e = queue->context; e->audioq_size += buffer->size; } static void audioq_get(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveEncoderPrivate *e = queue->context; struct GrooveEncoder *encoder = &e->externals; e->audioq_size -= buffer->size; if (e->audioq_size < encoder->encoded_buffer_size) pthread_cond_signal(&e->drain_cond); } static int encoder_write_packet(void *opaque, uint8_t *buf, int buf_size) { struct GrooveEncoderPrivate *e = opaque; struct GrooveBufferPrivate *b = av_mallocz(sizeof(struct GrooveBufferPrivate)); if (!b) { av_log(NULL, AV_LOG_ERROR, "unable to allocate buffer\n"); return -1; } struct GrooveBuffer *buffer = &b->externals; if (pthread_mutex_init(&b->mutex, NULL) != 0) { av_free(b); av_log(NULL, AV_LOG_ERROR, "unable to create mutex\n"); return -1; } buffer->item = e->encode_head; buffer->pos = e->encode_pos; buffer->pts = e->encode_pts; buffer->format = e->encode_format; b->is_packet = 1; b->data = av_malloc(buf_size); if (!b->data) { av_free(buffer); av_free(b); pthread_mutex_destroy(&b->mutex); av_log(NULL, AV_LOG_ERROR, "unable to create data buffer\n"); return -1; } memcpy(b->data, buf, buf_size); buffer->data = &b->data; buffer->size = buf_size; b->ref_count = 1; groove_queue_put(e->audioq, buffer); return 0; } struct GrooveEncoder *groove_encoder_create(void) { struct GrooveEncoderPrivate *e = av_mallocz(sizeof(struct GrooveEncoderPrivate)); if (!e) { av_log(NULL, AV_LOG_ERROR, "unable to allocate encoder\n"); return NULL; } struct GrooveEncoder *encoder = &e->externals; const int buffer_size = 4 * 1024; e->avio_buf = av_malloc(buffer_size); if (!e->avio_buf) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to allocate avio buffer\n"); return NULL; } e->avio = avio_alloc_context(e->avio_buf, buffer_size, 1, encoder, NULL, encoder_write_packet, NULL); if (!e->avio) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to allocate avio context\n"); return NULL; } if (pthread_mutex_init(&e->encode_head_mutex, NULL) != 0) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to create mutex\n"); return NULL; } e->encode_head_mutex_inited = 1; if (pthread_cond_init(&e->drain_cond, NULL) != 0) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to create mutex condition\n"); return NULL; } e->drain_cond_inited = 1; e->audioq = groove_queue_create(); if (!e->audioq) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to allocate queue\n"); return NULL; } e->audioq->context = encoder; e->audioq->cleanup = audioq_cleanup; e->audioq->put = audioq_put; e->audioq->get = audioq_get; e->audioq->purge = audioq_purge; e->sink = groove_sink_create(); if (!e->sink) { groove_encoder_destroy(encoder); av_log(NULL, AV_LOG_ERROR, "unable to allocate sink\n"); return NULL; } e->sink->userdata = encoder; e->sink->purge = sink_purge; e->sink->flush = sink_flush; // set some defaults encoder->bit_rate = 256 * 1000; encoder->target_audio_format.sample_rate = 44100; encoder->target_audio_format.sample_fmt = GROOVE_SAMPLE_FMT_S16; encoder->target_audio_format.channel_layout = GROOVE_CH_LAYOUT_STEREO; encoder->sink_buffer_size = e->sink->buffer_size; encoder->encoded_buffer_size = 16 * 1024; encoder->gain = e->sink->gain; return encoder; } void groove_encoder_destroy(struct GrooveEncoder *encoder) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; if (e->sink) groove_sink_destroy(e->sink); if (e->audioq) groove_queue_destroy(e->audioq); if (e->encode_head_mutex_inited) pthread_mutex_destroy(&e->encode_head_mutex); if (e->drain_cond_inited) pthread_cond_destroy(&e->drain_cond); if (e->avio) av_free(e->avio); if (e->avio_buf) av_free(e->avio_buf); if (e->metadata) av_dict_free(&e->metadata); av_free(e); } static int abs_diff(int a, int b) { int n = a - b; return n >= 0 ? n : -n; } static int codec_supports_fmt(AVCodec *codec, enum GrooveSampleFormat fmt) { const enum GrooveSampleFormat *p = (enum GrooveSampleFormat*) codec->sample_fmts; while (*p != GROOVE_SAMPLE_FMT_NONE) { if (*p == fmt) return 1; p += 1; } return 0; } static enum GrooveSampleFormat closest_supported_sample_fmt(AVCodec *codec, enum GrooveSampleFormat target) { // if we can, return exact match. otherwise, return the format with the // next highest sample byte count if (!codec->sample_fmts) return target; int target_size = av_get_bytes_per_sample((enum AVSampleFormat)target); const enum GrooveSampleFormat *p = (enum GrooveSampleFormat*) codec->sample_fmts; enum GrooveSampleFormat best = *p; int best_size = av_get_bytes_per_sample((enum AVSampleFormat)best); while (*p != GROOVE_SAMPLE_FMT_NONE) { if (*p == target) return target; int size = av_get_bytes_per_sample((enum AVSampleFormat)*p); if ((best_size < target_size && size > best_size) || (size >= target_size && abs_diff(target_size, size) < abs_diff(target_size, best_size))) { best_size = size; best = *p; } p += 1; } // prefer interleaved format enum GrooveSampleFormat packed_best = (enum GrooveSampleFormat)av_get_packed_sample_fmt((enum AVSampleFormat)best); return codec_supports_fmt(codec, packed_best) ? packed_best : best; } static int closest_supported_sample_rate(AVCodec *codec, int target) { // if we can, return exact match. otherwise, return the minimum sample // rate >= target if (!codec->supported_samplerates) return target; const int *p = codec->supported_samplerates; int best = *p; while (*p) { if (*p == target) return target; if ((best < target && *p > best) || (*p >= target && abs_diff(target, *p) < abs_diff(target, best))) { best = *p; } p += 1; } return best; } static uint64_t closest_supported_channel_layout(AVCodec *codec, uint64_t target) { // if we can, return exact match. otherwise, return the layout with the // minimum number of channels >= target if (!codec->channel_layouts) return target; int target_count = av_get_channel_layout_nb_channels(target); const uint64_t *p = codec->channel_layouts; uint64_t best = *p; int best_count = av_get_channel_layout_nb_channels(best); while (*p) { if (*p == target) return target; int count = av_get_channel_layout_nb_channels(*p); if ((best_count < target_count && count > best_count) || (count >= target_count && abs_diff(target_count, count) < abs_diff(target_count, best_count))) { best_count = count; best = *p; } p += 1; } return best; } static void log_audio_fmt(const struct GrooveAudioFormat *fmt) { char buf[128]; av_get_channel_layout_string(buf, sizeof(buf), 0, fmt->channel_layout); av_log(NULL, AV_LOG_INFO, "encoder: using audio format: %s, %d Hz, %s\n", av_get_sample_fmt_name((enum AVSampleFormat)fmt->sample_fmt), fmt->sample_rate, buf); } int groove_encoder_attach(struct GrooveEncoder *encoder, struct GroovePlaylist *playlist) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; encoder->playlist = playlist; groove_queue_reset(e->audioq); e->oformat = av_guess_format(encoder->format_short_name, encoder->filename, encoder->mime_type); if (!e->oformat) { groove_encoder_detach(encoder); av_log(NULL, AV_LOG_ERROR, "unable to determine format\n"); return -1; } // av_guess_codec ignores mime_type, filename, and codec_short_name. see // https://bugzilla.libav.org/show_bug.cgi?id=580 // because of this we do a workaround to return the correct codec based on // the codec_short_name. AVCodec *codec = NULL; if (encoder->codec_short_name) { codec = avcodec_find_encoder_by_name(encoder->codec_short_name); if (!codec) { const AVCodecDescriptor *desc = avcodec_descriptor_get_by_name(encoder->codec_short_name); if (desc) { codec = avcodec_find_encoder(desc->id); } } } if (!codec) { enum AVCodecID codec_id = av_guess_codec(e->oformat, encoder->codec_short_name, encoder->filename, encoder->mime_type, AVMEDIA_TYPE_AUDIO); codec = avcodec_find_encoder(codec_id); if (!codec) { groove_encoder_detach(encoder); av_log(NULL, AV_LOG_ERROR, "unable to find encoder\n"); return -1; } } e->codec = codec; av_log(NULL, AV_LOG_INFO, "encoder: using codec: %s\n", codec->long_name); encoder->actual_audio_format.sample_fmt = closest_supported_sample_fmt( codec, encoder->target_audio_format.sample_fmt); encoder->actual_audio_format.sample_rate = closest_supported_sample_rate( codec, encoder->target_audio_format.sample_rate); encoder->actual_audio_format.channel_layout = closest_supported_channel_layout( codec, encoder->target_audio_format.channel_layout); log_audio_fmt(&encoder->actual_audio_format); int err = init_avcontext(encoder); if (err < 0) { groove_encoder_detach(encoder); return err; } e->sink->audio_format = encoder->actual_audio_format; e->sink->buffer_size = encoder->sink_buffer_size; e->sink->buffer_sample_count = (codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE) ? 0 : e->stream->codec->frame_size; e->sink->gain = encoder->gain; if (groove_sink_attach(e->sink, playlist) < 0) { groove_encoder_detach(encoder); av_log(NULL, AV_LOG_ERROR, "unable to attach sink\n"); return -1; } if (pthread_create(&e->thread_id, NULL, encode_thread, encoder) != 0) { groove_encoder_detach(encoder); av_log(NULL, AV_LOG_ERROR, "unable to create encoder thread\n"); return -1; } return 0; } int groove_encoder_detach(struct GrooveEncoder *encoder) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; e->abort_request = 1; groove_sink_detach(e->sink); groove_queue_flush(e->audioq); groove_queue_abort(e->audioq); pthread_cond_signal(&e->drain_cond); pthread_join(e->thread_id, NULL); e->abort_request = 0; cleanup_avcontext(e); e->oformat = NULL; e->codec = NULL; encoder->playlist = NULL; return 0; } int groove_encoder_buffer_get(struct GrooveEncoder *encoder, struct GrooveBuffer **buffer, int block) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; if (groove_queue_get(e->audioq, (void**)buffer, block) == 1) { if (*buffer == end_of_q_sentinel) { *buffer = NULL; return GROOVE_BUFFER_END; } else { return GROOVE_BUFFER_YES; } } else { *buffer = NULL; return GROOVE_BUFFER_NO; } } struct GrooveTag *groove_encoder_metadata_get(struct GrooveEncoder *encoder, const char *key, const struct GrooveTag *prev, int flags) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; const AVDictionaryEntry *entry = (const AVDictionaryEntry *) prev; return (struct GrooveTag *) av_dict_get(e->metadata, key, entry, flags|AV_DICT_IGNORE_SUFFIX); } int groove_encoder_metadata_set(struct GrooveEncoder *encoder, const char *key, const char *value, int flags) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; return av_dict_set(&e->metadata, key, value, flags|AV_DICT_IGNORE_SUFFIX); } int groove_encoder_buffer_peek(struct GrooveEncoder *encoder, int block) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; return groove_queue_peek(e->audioq, block); } void groove_encoder_position(struct GrooveEncoder *encoder, struct GroovePlaylistItem **item, double *seconds) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; pthread_mutex_lock(&e->encode_head_mutex); if (item) *item = e->encode_head; if (seconds) *seconds = e->encode_pos; pthread_mutex_unlock(&e->encode_head_mutex); } int groove_encoder_set_gain(struct GrooveEncoder *encoder, double gain) { struct GrooveEncoderPrivate *e = (struct GrooveEncoderPrivate *) encoder; encoder->gain = gain; return groove_sink_set_gain(e->sink, gain); } libgroove-4.3.0/groove/encoder.h000066400000000000000000000115021253074642100166020ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_ENCODER_H_INCLUDED #define GROOVE_ENCODER_H_INCLUDED #include "groove.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* attach a GrooveEncoder to a playlist to keep a buffer of encoded audio full. * for example you could use it to implement an http audio stream */ struct GrooveEncoder { /* The desired audio format to encode. * groove_encoder_create defaults these to 44100 Hz, * signed 16-bit int, stereo. * These are preferences; if a setting cannot be used, a substitute will be * used instead. actual_audio_format is set to the actual values. */ struct GrooveAudioFormat target_audio_format; /* Select encoding quality by choosing a target bit rate in bits per * second. Note that typically you see this expressed in "kbps", such * as 320kbps or 128kbps. Surprisingly, in this circumstance 1 kbps is * 1000 bps, *not* 1024 bps as you would expect. * groove_encoder_create defaults this to 256000 */ int bit_rate; /* optional - choose a short name for the format * to help libgroove guess which format to use * use `avconv -formats` to get a list of possibilities */ const char *format_short_name; /* optional - choose a short name for the codec * to help libgroove guess which codec to use * use `avconv -codecs` to get a list of possibilities */ const char *codec_short_name; /* optional - provide an example filename * to help libgroove guess which format/codec to use */ const char *filename; /* optional - provide a mime type string * to help libgroove guess which format/codec to use */ const char *mime_type; /* how big the sink buffer should be, in sample frames. * groove_encoder_create defaults this to 8192 */ int sink_buffer_size; /* how big the encoded audio buffer should be, in bytes * groove_encoder_create defaults this to 16384 */ int encoded_buffer_size; /* This volume adjustment to make to this player. * It is recommended that you leave this at 1.0 and instead adjust the * gain of the underlying playlist. * If you want to change this value after you have already attached the * sink to the playlist, you must use groove_encoder_set_gain. * float format. Defaults to 1.0 */ double gain; /* read-only. set when attached and cleared when detached */ struct GroovePlaylist *playlist; /* read-only. set to the actual format you get when you attach to a * playlist. ideally will be the same as target_audio_format but might * not be. */ struct GrooveAudioFormat actual_audio_format; }; struct GrooveEncoder *groove_encoder_create(void); /* detach before destroying */ void groove_encoder_destroy(struct GrooveEncoder *encoder); /* once you attach, you must detach before destroying the playlist * at playlist begin, format headers are generated. when end of playlist is * reached, format trailers are generated. */ int groove_encoder_attach(struct GrooveEncoder *encoder, struct GroovePlaylist *playlist); int groove_encoder_detach(struct GrooveEncoder *encoder); /* returns < 0 on error, GROOVE_BUFFER_NO on aborted (block=1) or no buffer * ready (block=0), GROOVE_BUFFER_YES on buffer returned, and GROOVE_BUFFER_END * on end of playlist. * buffer is always set to either a valid GrooveBuffer or NULL. */ int groove_encoder_buffer_get(struct GrooveEncoder *encoder, struct GrooveBuffer **buffer, int block); /* returns < 0 on error, 0 on no buffer ready, 1 on buffer ready * if block is 1, block until buffer is ready */ int groove_encoder_buffer_peek(struct GrooveEncoder *encoder, int block); /* see docs for groove_file_metadata_get */ struct GrooveTag *groove_encoder_metadata_get(struct GrooveEncoder *encoder, const char *key, const struct GrooveTag *prev, int flags); /* see docs for groove_file_metadata_set */ int groove_encoder_metadata_set(struct GrooveEncoder *encoder, const char *key, const char *value, int flags); /* get the position of the encode head * both the current playlist item and the position in seconds in the playlist * item are given. item will be set to NULL if the playlist is empty * you may pass NULL for item or seconds */ void groove_encoder_position(struct GrooveEncoder *encoder, struct GroovePlaylistItem **item, double *seconds); /* See the gain property of GrooveSink. It is recommended that you leave this * at 1.0 and instead adjust the gain of the playlist. * returns 0 on success, < 0 on error */ int groove_encoder_set_gain(struct GrooveEncoder *encoder, double gain); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* GROOVE_ENCODER_H_INCLUDED */ libgroove-4.3.0/groove/file.c000066400000000000000000000305501253074642100161010ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "file.h" #include #include static int decode_interrupt_cb(void *ctx) { struct GrooveFilePrivate *f = ctx; return f ? f->abort_request : 0; } struct GrooveFile *groove_file_open(const char *filename) { struct GrooveFilePrivate *f = av_mallocz(sizeof(struct GrooveFilePrivate)); if (!f) { av_log(NULL, AV_LOG_ERROR, "unable to allocate file context\n"); return NULL; } struct GrooveFile *file = &f->externals; f->audio_stream_index = -1; f->seek_pos = -1; if (pthread_mutex_init(&f->seek_mutex, NULL) != 0) { av_free(f); av_log(NULL, AV_LOG_ERROR, "unable to create seek mutex\n"); return NULL; } f->ic = avformat_alloc_context(); if (!f->ic) { groove_file_close(file); av_log(NULL, AV_LOG_ERROR, "unable to allocate format context\n"); return NULL; } file->filename = f->ic->filename; f->ic->interrupt_callback.callback = decode_interrupt_cb; f->ic->interrupt_callback.opaque = file; int err = avformat_open_input(&f->ic, filename, NULL, NULL); if (err < 0) { groove_file_close(file); av_log(NULL, AV_LOG_INFO, "%s: unrecognized format\n", filename); return NULL; } err = avformat_find_stream_info(f->ic, NULL); if (err < 0) { groove_file_close(file); av_log(NULL, AV_LOG_ERROR, "%s: could not find codec parameters\n", filename); return NULL; } // set all streams to discard. in a few lines here we will find the audio // stream and cancel discarding it for (int i = 0; i < f->ic->nb_streams; i++) f->ic->streams[i]->discard = AVDISCARD_ALL; f->audio_stream_index = av_find_best_stream(f->ic, AVMEDIA_TYPE_AUDIO, -1, -1, &f->decoder, 0); if (f->audio_stream_index < 0) { groove_file_close(file); av_log(NULL, AV_LOG_INFO, "%s: no audio stream found\n", filename); return NULL; } if (!f->decoder) { groove_file_close(file); av_log(NULL, AV_LOG_ERROR, "%s: no decoder found\n", filename); return NULL; } f->audio_st = f->ic->streams[f->audio_stream_index]; f->audio_st->discard = AVDISCARD_DEFAULT; AVCodecContext *avctx = f->audio_st->codec; if (avcodec_open2(avctx, f->decoder, NULL) < 0) { groove_file_close(file); av_log(NULL, AV_LOG_ERROR, "unable to open decoder\n"); return NULL; } if (!avctx->channel_layout) avctx->channel_layout = av_get_default_channel_layout(avctx->channels); if (!avctx->channel_layout) { groove_file_close(file); av_log(NULL, AV_LOG_ERROR, "unable to guess channel layout\n"); return NULL; } // copy the audio stream metadata to the context metadata av_dict_copy(&f->ic->metadata, f->audio_st->metadata, 0); return file; } // should be safe to call no matter what state the file is in void groove_file_close(struct GrooveFile *file) { if (!file) return; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *)file; f->abort_request = 1; if (f->audio_stream_index >= 0) { AVCodecContext *avctx = f->ic->streams[f->audio_stream_index]->codec; av_free_packet(&f->audio_pkt); f->ic->streams[f->audio_stream_index]->discard = AVDISCARD_ALL; avcodec_close(avctx); f->audio_st = NULL; f->audio_stream_index = -1; } // disable interrupting f->abort_request = 0; if (f->ic) avformat_close_input(&f->ic); pthread_mutex_destroy(&f->seek_mutex); av_free(f); } const char *groove_file_short_names(struct GrooveFile *file) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; return f->ic->iformat->name; } double groove_file_duration(struct GrooveFile *file) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; double time_base = av_q2d(f->audio_st->time_base); return time_base * f->audio_st->duration; } void groove_file_audio_format(struct GrooveFile *file, struct GrooveAudioFormat *audio_format) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; AVCodecContext *codec_ctx = f->audio_st->codec; audio_format->sample_rate = codec_ctx->sample_rate; audio_format->channel_layout = codec_ctx->channel_layout; audio_format->sample_fmt = (enum GrooveSampleFormat)codec_ctx->sample_fmt; } struct GrooveTag *groove_file_metadata_get(struct GrooveFile *file, const char *key, const struct GrooveTag *prev, int flags) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; const AVDictionaryEntry *e = (const AVDictionaryEntry *) prev; if (key && key[0] == 0) flags |= AV_DICT_IGNORE_SUFFIX; return (struct GrooveTag *) av_dict_get(f->ic->metadata, key, e, flags); } int groove_file_metadata_set(struct GrooveFile *file, const char *key, const char *value, int flags) { file->dirty = 1; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; return av_dict_set(&f->ic->metadata, key, value, flags); } const char *groove_tag_key(struct GrooveTag *tag) { AVDictionaryEntry *e = (AVDictionaryEntry *) tag; return e->key; } const char *groove_tag_value(struct GrooveTag *tag) { AVDictionaryEntry *e = (AVDictionaryEntry *) tag; return e->value; } static int tempfileify(char * str, size_t max_len) { size_t len = strlen(str); if (len + 10 > max_len) return -1; char prepend[11]; int n = rand() % 99999; snprintf(prepend, 11, ".tmp%05d-", n); // find the last slash and insert after it // if no slash, insert at beginning char * slash = strrchr(str, '/'); char * pos = slash ? slash + 1 : str; size_t orig_len = len - (pos - str); memmove(pos + 10, pos, orig_len); strncpy(pos, prepend, 10); return 0; } static void cleanup_save(struct GrooveFile *file) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; av_free_packet(&f->audio_pkt); avio_closep(&f->oc->pb); if (f->tempfile_exists) { if (remove(f->oc->filename) != 0) av_log(NULL, AV_LOG_WARNING, "Error deleting temp file during cleanup\n"); f->tempfile_exists = 0; } if (f->oc) { avformat_free_context(f->oc); f->oc = NULL; } } int groove_file_save(struct GrooveFile *file) { if (!file->dirty) return 0; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; // detect output format AVOutputFormat *ofmt = av_guess_format(f->ic->iformat->name, f->ic->filename, NULL); if (!ofmt) { av_log(NULL, AV_LOG_ERROR, "Could not deduce output format to use.\n"); return -1; } // allocate output media context f->oc = avformat_alloc_context(); if (!f->oc) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "Could not create output context: out of memory\n"); return -1; } f->oc->oformat = ofmt; snprintf(f->oc->filename, sizeof(f->oc->filename), "%s", f->ic->filename); if (tempfileify(f->oc->filename, sizeof(f->oc->filename)) < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "could not create temp file - filename too long\n"); return -1; } // open output file if needed if (!(ofmt->flags & AVFMT_NOFILE)) { if (avio_open(&f->oc->pb, f->oc->filename, AVIO_FLAG_WRITE) < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "could not open '%s'\n", f->oc->filename); return -1; } f->tempfile_exists = 1; } // add all the streams for (int i = 0; i < f->ic->nb_streams; i++) { AVStream *in_stream = f->ic->streams[i]; AVStream *out_stream = avformat_new_stream(f->oc, NULL); if (!out_stream) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "error allocating output stream\n"); return -1; } out_stream->id = in_stream->id; out_stream->disposition = in_stream->disposition; out_stream->time_base = in_stream->time_base; AVCodecContext *icodec = in_stream->codec; AVCodecContext *ocodec = out_stream->codec; ocodec->bits_per_raw_sample = icodec->bits_per_raw_sample; ocodec->chroma_sample_location = icodec->chroma_sample_location; ocodec->codec_id = icodec->codec_id; ocodec->codec_type = icodec->codec_type; if (!ocodec->codec_tag) { if (!f->oc->oformat->codec_tag || av_codec_get_id (f->oc->oformat->codec_tag, icodec->codec_tag) == ocodec->codec_id || av_codec_get_tag(f->oc->oformat->codec_tag, icodec->codec_id) <= 0) ocodec->codec_tag = icodec->codec_tag; } ocodec->bit_rate = icodec->bit_rate; ocodec->rc_max_rate = icodec->rc_max_rate; ocodec->rc_buffer_size = icodec->rc_buffer_size; ocodec->field_order = icodec->field_order; uint64_t extra_size = (uint64_t)icodec->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE; if (extra_size > INT_MAX) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "codec extra size too big\n"); return AVERROR(EINVAL); } ocodec->extradata = av_mallocz(extra_size); if (!ocodec->extradata) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "could not allocate codec extradata: out of memory\n"); return AVERROR(ENOMEM); } memcpy(ocodec->extradata, icodec->extradata, icodec->extradata_size); ocodec->extradata_size = icodec->extradata_size; switch (ocodec->codec_type) { case AVMEDIA_TYPE_AUDIO: ocodec->channel_layout = icodec->channel_layout; ocodec->sample_rate = icodec->sample_rate; ocodec->channels = icodec->channels; ocodec->frame_size = icodec->frame_size; ocodec->audio_service_type = icodec->audio_service_type; ocodec->block_align = icodec->block_align; break; case AVMEDIA_TYPE_VIDEO: ocodec->pix_fmt = icodec->pix_fmt; ocodec->width = icodec->width; ocodec->height = icodec->height; ocodec->has_b_frames = icodec->has_b_frames; if (!ocodec->sample_aspect_ratio.num) { ocodec->sample_aspect_ratio = out_stream->sample_aspect_ratio = in_stream->sample_aspect_ratio.num ? in_stream->sample_aspect_ratio : icodec->sample_aspect_ratio.num ? icodec->sample_aspect_ratio : (AVRational){0, 1}; } break; case AVMEDIA_TYPE_SUBTITLE: ocodec->width = icodec->width; ocodec->height = icodec->height; break; case AVMEDIA_TYPE_DATA: case AVMEDIA_TYPE_ATTACHMENT: break; default: cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "unrecognized stream type\n"); return -1; } } // set metadata av_dict_copy(&f->oc->metadata, f->ic->metadata, 0); if (avformat_write_header(f->oc, NULL) < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "could not write header\n"); return -1; } AVPacket *pkt = &f->audio_pkt; for (;;) { int err = av_read_frame(f->ic, pkt); if (err == AVERROR_EOF) { break; } else if (err < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "error reading frame\n"); return -1; } if (av_write_frame(f->oc, pkt) < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "error writing frame\n"); return -1; } av_free_packet(pkt); } if (av_write_trailer(f->oc) < 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "could not write trailer\n"); return -1; } if (rename(f->oc->filename, f->ic->filename) != 0) { cleanup_save(file); av_log(NULL, AV_LOG_ERROR, "error renaming tmp file to original file\n"); return -1; } f->tempfile_exists = 0; cleanup_save(file); file->dirty = 0; return 0; } libgroove-4.3.0/groove/file.h000066400000000000000000000016551253074642100161120ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_FILE_H_INCLUDED #define GROOVE_FILE_H_INCLUDED #include "groove.h" #include #include struct GrooveFilePrivate { struct GrooveFile externals; int audio_stream_index; int abort_request; // true when we're closing the file AVFormatContext *ic; AVCodec *decoder; AVStream *audio_st; // this mutex protects the fields in this block pthread_mutex_t seek_mutex; int64_t seek_pos; // -1 if no seek request int seek_flush; // whether the seek request wants us to flush the buffer int eof; double audio_clock; // position of the decode head AVPacket audio_pkt; // state while saving AVFormatContext *oc; int tempfile_exists; int paused; }; #endif /* GROOVE_FILE_H_INCLUDED */ libgroove-4.3.0/groove/global.c000066400000000000000000000047671253074642100164350ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "groove.h" #include "config.h" #include #include #include #include static int should_deinit_network = 0; static int my_lockmgr_cb(void **mutex, enum AVLockOp op) { if (mutex == NULL) return -1; pthread_mutex_t *pmutex; switch (op) { case AV_LOCK_CREATE: pmutex = av_mallocz(sizeof(pthread_mutex_t)); *mutex = pmutex; return pthread_mutex_init(pmutex, NULL); case AV_LOCK_OBTAIN: pmutex = *mutex; return pthread_mutex_lock(pmutex); case AV_LOCK_RELEASE: pmutex = *mutex; return pthread_mutex_unlock(pmutex); case AV_LOCK_DESTROY: pmutex = *mutex; int err = pthread_mutex_destroy(pmutex); av_free(pmutex); *mutex = NULL; return err; } return 0; } int groove_init(void) { av_lockmgr_register(&my_lockmgr_cb); srand(time(NULL)); // register all codecs, demux and protocols avcodec_register_all(); av_register_all(); avformat_network_init(); avfilter_register_all(); should_deinit_network = 1; av_log_set_level(AV_LOG_QUIET); return 0; } void groove_finish(void) { if (should_deinit_network) { avformat_network_deinit(); should_deinit_network = 0; } } void groove_set_logging(int level) { av_log_set_level(level); } int groove_channel_layout_count(uint64_t channel_layout) { return av_get_channel_layout_nb_channels(channel_layout); } uint64_t groove_channel_layout_default(int count) { return av_get_default_channel_layout(count); } int groove_sample_format_bytes_per_sample(enum GrooveSampleFormat format) { return av_get_bytes_per_sample((enum AVSampleFormat)format); } int groove_audio_formats_equal(const struct GrooveAudioFormat *a, const struct GrooveAudioFormat *b) { return (a->sample_rate == b->sample_rate && a->channel_layout == b->channel_layout && a->sample_fmt == b->sample_fmt); } const char *groove_version(void) { return GROOVE_VERSION_STRING; } int groove_version_major(void) { return GROOVE_VERSION_MAJOR; } int groove_version_minor(void) { return GROOVE_VERSION_MINOR; } int groove_version_patch(void) { return GROOVE_VERSION_PATCH; } libgroove-4.3.0/groove/groove.h000066400000000000000000000415521253074642100164740ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_H_INCLUDED #define GROOVE_H_INCLUDED #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /************* global *************/ /* call once at the beginning of your program from the main thread * returns 0 on success, < 0 on error */ int groove_init(void); /* call at the end of your program to clean up. after calling this * you may no longer use this API. */ void groove_finish(void); /* enable/disable logging of errors */ #define GROOVE_LOG_QUIET -8 #define GROOVE_LOG_ERROR 16 #define GROOVE_LOG_WARNING 24 #define GROOVE_LOG_INFO 32 void groove_set_logging(int level); /* channel layouts */ #define GROOVE_CH_FRONT_LEFT 0x00000001 #define GROOVE_CH_FRONT_RIGHT 0x00000002 #define GROOVE_CH_FRONT_CENTER 0x00000004 #define GROOVE_CH_LOW_FREQUENCY 0x00000008 #define GROOVE_CH_BACK_LEFT 0x00000010 #define GROOVE_CH_BACK_RIGHT 0x00000020 #define GROOVE_CH_FRONT_LEFT_OF_CENTER 0x00000040 #define GROOVE_CH_FRONT_RIGHT_OF_CENTER 0x00000080 #define GROOVE_CH_BACK_CENTER 0x00000100 #define GROOVE_CH_SIDE_LEFT 0x00000200 #define GROOVE_CH_SIDE_RIGHT 0x00000400 #define GROOVE_CH_TOP_CENTER 0x00000800 #define GROOVE_CH_TOP_FRONT_LEFT 0x00001000 #define GROOVE_CH_TOP_FRONT_CENTER 0x00002000 #define GROOVE_CH_TOP_FRONT_RIGHT 0x00004000 #define GROOVE_CH_TOP_BACK_LEFT 0x00008000 #define GROOVE_CH_TOP_BACK_CENTER 0x00010000 #define GROOVE_CH_TOP_BACK_RIGHT 0x00020000 #define GROOVE_CH_STEREO_LEFT 0x20000000 #define GROOVE_CH_STEREO_RIGHT 0x40000000 #define GROOVE_CH_WIDE_LEFT 0x0000000080000000ULL #define GROOVE_CH_WIDE_RIGHT 0x0000000100000000ULL #define GROOVE_CH_LAYOUT_MONO (GROOVE_CH_FRONT_CENTER) #define GROOVE_CH_LAYOUT_STEREO (GROOVE_CH_FRONT_LEFT|GROOVE_CH_FRONT_RIGHT) #define GROOVE_CH_LAYOUT_2POINT1 (GROOVE_CH_LAYOUT_STEREO|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_2_1 (GROOVE_CH_LAYOUT_STEREO|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_SURROUND (GROOVE_CH_LAYOUT_STEREO|GROOVE_CH_FRONT_CENTER) #define GROOVE_CH_LAYOUT_3POINT1 (GROOVE_CH_LAYOUT_SURROUND|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_4POINT0 (GROOVE_CH_LAYOUT_SURROUND|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_4POINT1 (GROOVE_CH_LAYOUT_4POINT0|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_2_2 (GROOVE_CH_LAYOUT_STEREO|GROOVE_CH_SIDE_LEFT|GROOVE_CH_SIDE_RIGHT) #define GROOVE_CH_LAYOUT_QUAD (GROOVE_CH_LAYOUT_STEREO|GROOVE_CH_BACK_LEFT|GROOVE_CH_BACK_RIGHT) #define GROOVE_CH_LAYOUT_5POINT0 (GROOVE_CH_LAYOUT_SURROUND|GROOVE_CH_SIDE_LEFT|GROOVE_CH_SIDE_RIGHT) #define GROOVE_CH_LAYOUT_5POINT1 (GROOVE_CH_LAYOUT_5POINT0|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_5POINT0_BACK (GROOVE_CH_LAYOUT_SURROUND|GROOVE_CH_BACK_LEFT|GROOVE_CH_BACK_RIGHT) #define GROOVE_CH_LAYOUT_5POINT1_BACK (GROOVE_CH_LAYOUT_5POINT0_BACK|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_6POINT0 (GROOVE_CH_LAYOUT_5POINT0|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_6POINT0_FRONT (GROOVE_CH_LAYOUT_2_2|GROOVE_CH_FRONT_LEFT_OF_CENTER|GROOVE_CH_FRONT_RIGHT_OF_CENTER) #define GROOVE_CH_LAYOUT_HEXAGONAL (GROOVE_CH_LAYOUT_5POINT0_BACK|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_6POINT1 (GROOVE_CH_LAYOUT_5POINT1|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_6POINT1_BACK (GROOVE_CH_LAYOUT_5POINT1_BACK|GROOVE_CH_BACK_CENTER) #define GROOVE_CH_LAYOUT_6POINT1_FRONT (GROOVE_CH_LAYOUT_6POINT0_FRONT|GROOVE_CH_LOW_FREQUENCY) #define GROOVE_CH_LAYOUT_7POINT0 (GROOVE_CH_LAYOUT_5POINT0|GROOVE_CH_BACK_LEFT|GROOVE_CH_BACK_RIGHT) #define GROOVE_CH_LAYOUT_7POINT0_FRONT (GROOVE_CH_LAYOUT_5POINT0|GROOVE_CH_FRONT_LEFT_OF_CENTER|GROOVE_CH_FRONT_RIGHT_OF_CENTER) #define GROOVE_CH_LAYOUT_7POINT1 (GROOVE_CH_LAYOUT_5POINT1|GROOVE_CH_BACK_LEFT|GROOVE_CH_BACK_RIGHT) #define GROOVE_CH_LAYOUT_7POINT1_WIDE (GROOVE_CH_LAYOUT_5POINT1|GROOVE_CH_FRONT_LEFT_OF_CENTER|GROOVE_CH_FRONT_RIGHT_OF_CENTER) #define GROOVE_CH_LAYOUT_7POINT1_WIDE_BACK (GROOVE_CH_LAYOUT_5POINT1_BACK|GROOVE_CH_FRONT_LEFT_OF_CENTER|GROOVE_CH_FRONT_RIGHT_OF_CENTER) #define GROOVE_CH_LAYOUT_OCTAGONAL (GROOVE_CH_LAYOUT_5POINT0|GROOVE_CH_BACK_LEFT|GROOVE_CH_BACK_CENTER|GROOVE_CH_BACK_RIGHT) #define GROOVE_CH_LAYOUT_STEREO_DOWNMIX (GROOVE_CH_STEREO_LEFT|GROOVE_CH_STEREO_RIGHT) /* get the channel count for the channel layout */ int groove_channel_layout_count(uint64_t channel_layout); /* get the default channel layout based on the channel count */ uint64_t groove_channel_layout_default(int count); enum GrooveSampleFormat { GROOVE_SAMPLE_FMT_NONE = -1, GROOVE_SAMPLE_FMT_U8, /* unsigned 8 bits */ GROOVE_SAMPLE_FMT_S16, /* signed 16 bits */ GROOVE_SAMPLE_FMT_S32, /* signed 32 bits */ GROOVE_SAMPLE_FMT_FLT, /* float (32 bits) */ GROOVE_SAMPLE_FMT_DBL, /* double (64 bits) */ GROOVE_SAMPLE_FMT_U8P, /* unsigned 8 bits, planar */ GROOVE_SAMPLE_FMT_S16P, /* signed 16 bits, planar */ GROOVE_SAMPLE_FMT_S32P, /* signed 32 bits, planar */ GROOVE_SAMPLE_FMT_FLTP, /* float (32 bits), planar */ GROOVE_SAMPLE_FMT_DBLP /* double (64 bits), planar */ }; struct GrooveAudioFormat { int sample_rate; uint64_t channel_layout; enum GrooveSampleFormat sample_fmt; }; int groove_sample_format_bytes_per_sample(enum GrooveSampleFormat format); /* returns 1 if the audio formats have the same sample rate, channel layout, * and sample format. returns 0 otherwise. */ int groove_audio_formats_equal(const struct GrooveAudioFormat *a, const struct GrooveAudioFormat *b); int groove_version_major(void); int groove_version_minor(void); int groove_version_patch(void); const char *groove_version(void); /************* GrooveFile *************/ struct GrooveFile { int dirty; /* read-only */ const char *filename; /* read-only */ }; /* flags to groove_file_metadata_* */ #define GROOVE_TAG_MATCH_CASE 1 #define GROOVE_TAG_DONT_OVERWRITE 16 /* If the entry already exists, append to it. Note that no * delimiter is added, the strings are simply concatenated. */ #define GROOVE_TAG_APPEND 32 struct GrooveTag; const char *groove_tag_key(struct GrooveTag *tag); const char *groove_tag_value(struct GrooveTag *tag); /* you are always responsible for calling groove_file_close on the * returned GrooveFile. */ struct GrooveFile *groove_file_open(const char *filename); void groove_file_close(struct GrooveFile *file); struct GrooveTag *groove_file_metadata_get(struct GrooveFile *file, const char *key, const struct GrooveTag *prev, int flags); /* key entry to add to metadata. will be strdup'd * value entry to add to metadata. will be strdup'd * passing NULL causes existing entry to be deleted. * return >= 0 on success otherwise an error code < 0 * note that this will not save the file; you must call groove_file_save * to do that. */ int groove_file_metadata_set(struct GrooveFile *file, const char *key, const char *value, int flags); /* a comma separated list of short names for the format */ const char *groove_file_short_names(struct GrooveFile *file); /* write changes made to metadata to disk. * return < 0 on error */ int groove_file_save(struct GrooveFile *file); /* main audio stream duration in seconds. note that this relies on a * combination of format headers and heuristics. It can be inaccurate. * The most accurate way to learn the duration of a file is to use * GrooveLoudnessDetector */ double groove_file_duration(struct GrooveFile *file); /* get the audio format of the main audio stream of a file */ void groove_file_audio_format(struct GrooveFile *file, struct GrooveAudioFormat *audio_format); /************* GroovePlaylist *************/ struct GroovePlaylistItem { /* all fields are read-only. modify with methods below. */ struct GrooveFile *file; /* A volume adjustment in float format to apply to the file when it plays. * This is typically used for loudness compensation, for example ReplayGain. * To convert from dB to float, use exp(log(10) * 0.05 * dB_value) */ double gain; /* The sample peak of this playlist item is assumed to be 1.0 in float * format. If you know for certain that the peak is less than 1.0, you * may set this value which may allow the volume adjustment to use * a pure amplifier rather than a compressor. This results in slightly * better audio quality. */ double peak; /* A GroovePlaylist is a doubly linked list. Use these fields to * traverse the list. */ struct GroovePlaylistItem *prev; struct GroovePlaylistItem *next; }; struct GroovePlaylist { /* all fields are read-only. modify using methods below. * doubly linked list which is the playlist */ struct GroovePlaylistItem *head; struct GroovePlaylistItem *tail; /* volume adjustment in float format which applies to all playlist items * and all sinks. defaults to 1.0. */ double gain; }; /* a playlist keeps its sinks full. */ struct GroovePlaylist *groove_playlist_create(void); /* this will not call groove_file_close on any files * it will remove all playlist items and sinks from the playlist */ void groove_playlist_destroy(struct GroovePlaylist *playlist); void groove_playlist_play(struct GroovePlaylist *playlist); void groove_playlist_pause(struct GroovePlaylist *playlist); void groove_playlist_seek(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double seconds); /* once you add a file to the playlist, you must not destroy it until you first * remove it from the playlist. * next: the item to insert before. if NULL, you will append to the playlist. * gain: see GroovePlaylistItem structure. use 1.0 for no adjustment. * peak: see GroovePlaylistItem structure. use 1.0 for no adjustment. * returns the newly created playlist item, or NULL if out of memory. */ struct GroovePlaylistItem *groove_playlist_insert( struct GroovePlaylist *playlist, struct GrooveFile *file, double gain, double peak, struct GroovePlaylistItem *next); /* this will not call groove_file_close on item->file ! * item is destroyed and the address it points to is no longer valid */ void groove_playlist_remove(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item); /* get the position of the decode head * both the current playlist item and the position in seconds in the playlist * item are given. item will be set to NULL if the playlist is empty * seconds will be set to -1.0 if item is NULL. * you may pass NULL for item or seconds * Note that typically you are more interested in the position of the play * head, not the decode head. For example, if you have a GroovePlayer attached, * groove_player_position will give you the position of the play head. */ void groove_playlist_position(struct GroovePlaylist *playlist, struct GroovePlaylistItem **item, double *seconds); /* return 1 if the playlist is playing; 0 if it is not. */ int groove_playlist_playing(struct GroovePlaylist *playlist); /* remove all playlist items */ void groove_playlist_clear(struct GroovePlaylist *playlist); /* return the count of playlist items */ int groove_playlist_count(struct GroovePlaylist *playlist); void groove_playlist_set_gain(struct GroovePlaylist *playlist, double gain); void groove_playlist_set_item_gain(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double gain); void groove_playlist_set_item_peak(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double peak); /* This is the default behavior. The playlist will decode audio if any sinks * are not full. If any sinks do not drain fast enough the data will buffer up * in the playlist. */ #define GROOVE_EVERY_SINK_FULL 0 /* With this behavior, the playlist will stop decoding audio when any attached * sink is full, and then resume decoding audio every sink is not full. */ #define GROOVE_ANY_SINK_FULL 1 /* Use this to set the fill mode using the constants above */ void groove_playlist_set_fill_mode(struct GroovePlaylist *playlist, int mode); /************ GrooveBuffer ****************/ #define GROOVE_BUFFER_NO 0 #define GROOVE_BUFFER_YES 1 #define GROOVE_BUFFER_END 2 struct GrooveBuffer { /* all fields read-only * for interleaved audio, data[0] is the buffer. * for planar audio, each channel has a separate data pointer. * for encoded audio, data[0] is the encoded buffer. */ uint8_t **data; struct GrooveAudioFormat format; /* number of audio frames described by this buffer * for encoded audio, this is unknown and set to 0. */ int frame_count; /* when encoding, if item is NULL, this is a format header or trailer. * otherwise, this is encoded audio for the item specified. * when decoding, item is never NULL. */ struct GroovePlaylistItem *item; double pos; /* total number of bytes contained in this buffer */ int size; /* presentation time stamp of the buffer */ uint64_t pts; }; void groove_buffer_ref(struct GrooveBuffer *buffer); void groove_buffer_unref(struct GrooveBuffer *buffer); /************** GrooveSink ****************/ /* use this to get access to a realtime raw audio buffer * for example you could use it to draw a waveform or other visualization * GroovePlayer uses this internally to get the audio buffer for playback */ struct GrooveSink { /* set this to the audio format you want the sink to output */ struct GrooveAudioFormat audio_format; /* Set this flag to ignore audio_format. If you set this flag, the * buffers you pull from this sink could have any audio format. */ int disable_resample; /* If you leave this to its default of 0, frames pulled from the sink * will have sample count determined by efficiency. * If you set this to a positive number, frames pulled from the sink * will always have this number of samples. */ int buffer_sample_count; /* how big the buffer queue should be, in sample frames. * groove_sink_create defaults this to 8192 */ int buffer_size; /* This volume adjustment only applies to this sink. * It is recommended that you leave this at 1.0 and instead adjust the * gain of the playlist. * If you want to change this value after you have already attached the * sink to the playlist, you must use groove_sink_set_gain. * float format. Defaults to 1.0 */ double gain; /* set to whatever you want */ void *userdata; /* called when the audio queue is flushed. For example, if you seek to a * different location in the song. */ void (*flush)(struct GrooveSink *); /* called when a playlist item is deleted. Take this opportunity to remove * all your references to the GroovePlaylistItem. */ void (*purge)(struct GrooveSink *, struct GroovePlaylistItem *); /* called when the playlist is paused */ void (*pause)(struct GrooveSink *); /* called when the playlist is played */ void (*play)(struct GrooveSink *); /* read-only. set when you call groove_sink_attach. cleared when you call * groove_sink_detach */ struct GroovePlaylist *playlist; /* read-only. automatically computed from audio_format when you call * groove_sink_attach */ int bytes_per_sec; }; struct GrooveSink *groove_sink_create(void); void groove_sink_destroy(struct GrooveSink *sink); /* before calling this, set audio_format * returns 0 on success, < 0 on error */ int groove_sink_attach(struct GrooveSink *sink, struct GroovePlaylist *playlist); /* returns 0 on success, < 0 on error */ int groove_sink_detach(struct GrooveSink *sink); /* returns < 0 on error, GROOVE_BUFFER_NO on aborted (block=1) or no buffer * ready (block=0), GROOVE_BUFFER_YES on buffer returned, and GROOVE_BUFFER_END * on end of playlist. * buffer is always set to either a valid GrooveBuffer or NULL */ int groove_sink_buffer_get(struct GrooveSink *sink, struct GrooveBuffer **buffer, int block); /* returns < 0 on error, 0 on no buffer ready, 1 on buffer ready * if block is 1, block until buffer is ready */ int groove_sink_buffer_peek(struct GrooveSink *sink, int block); /* See the gain property of GrooveSink. It is recommended that you leave this * at 1.0 and instead adjust the gain of the playlist. * returns 0 on success, < 0 on error */ int groove_sink_set_gain(struct GrooveSink *sink, double gain); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* GROOVE_H_INCLUDED */ libgroove-4.3.0/groove/playlist.c000066400000000000000000001320701253074642100170230ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "file.h" #include "queue.h" #include "buffer.h" #include #include #include #include #include #include #include struct GrooveSinkPrivate { struct GrooveSink externals; struct GrooveQueue *audioq; int audioq_size; // in bytes int min_audioq_size; // in bytes }; struct SinkStack { struct GrooveSink *sink; struct SinkStack *next; }; struct SinkMap { struct SinkStack *stack_head; AVFilterContext *abuffersink_ctx; struct SinkMap *next; }; struct GroovePlaylistPrivate { struct GroovePlaylist externals; pthread_t thread_id; int abort_request; AVPacket audio_pkt_temp; AVFrame *in_frame; int paused; int in_sample_rate; uint64_t in_channel_layout; enum AVSampleFormat in_sample_fmt; AVRational in_time_base; char strbuf[512]; AVFilterGraph *filter_graph; AVFilterContext *abuffer_ctx; AVFilter *volume_filter; AVFilter *compand_filter; AVFilter *abuffer_filter; AVFilter *asplit_filter; AVFilter *aformat_filter; AVFilter *abuffersink_filter; pthread_mutex_t drain_cond_mutex; int drain_cond_mutex_inited; // this mutex applies to the variables in this block pthread_mutex_t decode_head_mutex; int decode_head_mutex_inited; // decode_thread waits on this cond when the decode_head is NULL pthread_cond_t decode_head_cond; int decode_head_cond_inited; // decode_thread waits on this cond when every sink is full // should also signal when the first sink is attached. pthread_cond_t sink_drain_cond; int sink_drain_cond_inited; // pointer to current playlist item being decoded struct GroovePlaylistItem *decode_head; // desired volume for the volume filter double volume; // known true peak value double peak; // set to 1 to trigger a rebuild int rebuild_filter_graph_flag; // map audio format to list of sinks // for each map entry, use the first sink in the stack as the example // of the audio format in that stack struct SinkMap *sink_map; int sink_map_count; // the value that was used to construct the filter graph double filter_volume; double filter_peak; // only touched by decode_thread, tells whether we have sent the end_of_q_sentinel int sent_end_of_q; struct GroovePlaylistItem *purge_item; // set temporarily int (*detect_full_sinks)(struct GroovePlaylist*); }; // this is used to tell the difference between a buffer underrun // and the end of the playlist. static struct GrooveBuffer *end_of_q_sentinel = NULL; static int frame_size(const AVFrame *frame) { return av_get_channel_layout_nb_channels(frame->channel_layout) * av_get_bytes_per_sample(frame->format) * frame->nb_samples; } static struct GrooveBuffer * frame_to_groove_buffer(struct GroovePlaylist *playlist, struct GrooveSink *sink, AVFrame *frame) { struct GrooveBufferPrivate *b = av_mallocz(sizeof(struct GrooveBufferPrivate)); if (!b) { av_log(NULL, AV_LOG_ERROR, "unable to allocate buffer\n"); return NULL; } struct GrooveBuffer *buffer = &b->externals; if (pthread_mutex_init(&b->mutex, NULL) != 0) { av_free(b); av_log(NULL, AV_LOG_ERROR, "unable to create mutex\n"); return NULL; } struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GrooveFile *file = p->decode_head->file; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; buffer->item = p->decode_head; buffer->pos = f->audio_clock; buffer->data = frame->extended_data; buffer->frame_count = frame->nb_samples; buffer->format.channel_layout = frame->channel_layout; buffer->format.sample_fmt = frame->format; buffer->format.sample_rate = frame->sample_rate; buffer->size = frame_size(frame); buffer->pts = frame->pts; b->frame = frame; return buffer; } // decode one audio packet and return its uncompressed size static int audio_decode_frame(struct GroovePlaylist *playlist, struct GrooveFile *file) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; AVPacket *pkt = &f->audio_pkt; AVCodecContext *dec = f->audio_st->codec; AVPacket *pkt_temp = &p->audio_pkt_temp; *pkt_temp = *pkt; // update the audio clock with the pts if we can if (pkt->pts != AV_NOPTS_VALUE) f->audio_clock = av_q2d(f->audio_st->time_base) * pkt->pts; int max_data_size = 0; int len1, got_frame; int new_packet = 1; AVFrame *in_frame = p->in_frame; // NOTE: the audio packet can contain several frames while (pkt_temp->size > 0 || (!pkt_temp->data && new_packet)) { new_packet = 0; len1 = avcodec_decode_audio4(dec, in_frame, &got_frame, pkt_temp); if (len1 < 0) { // if error, we skip the frame pkt_temp->size = 0; return -1; } pkt_temp->data += len1; pkt_temp->size -= len1; if (!got_frame) { // stop sending empty packets if the decoder is finished if (!pkt_temp->data && dec->codec->capabilities & CODEC_CAP_DELAY) return 0; continue; } // push the audio data from decoded frame into the filtergraph int err = av_buffersrc_write_frame(p->abuffer_ctx, in_frame); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "error writing frame to buffersrc: %s\n", p->strbuf); return -1; } // for each data format in the sink map, pull filtered audio from its // buffersink, turn it into a GrooveBuffer and then increment the ref // count for each sink in that stack. struct SinkMap *map_item = p->sink_map; double clock_adjustment = 0; while (map_item) { struct GrooveSink *example_sink = map_item->stack_head->sink; int data_size = 0; for (;;) { AVFrame *oframe = av_frame_alloc(); int err = example_sink->buffer_sample_count == 0 ? av_buffersink_get_frame(map_item->abuffersink_ctx, oframe) : av_buffersink_get_samples(map_item->abuffersink_ctx, oframe, example_sink->buffer_sample_count); if (err == AVERROR_EOF || err == AVERROR(EAGAIN)) { av_frame_free(&oframe); break; } if (err < 0) { av_frame_free(&oframe); av_log(NULL, AV_LOG_ERROR, "error reading buffer from buffersink\n"); return -1; } struct GrooveBuffer *buffer = frame_to_groove_buffer(playlist, example_sink, oframe); if (!buffer) { av_frame_free(&oframe); return -1; } data_size += buffer->size; struct SinkStack *stack_item = map_item->stack_head; // we hold this reference to avoid cleanups until at least this loop // is done and we call unref after it. groove_buffer_ref(buffer); while (stack_item) { struct GrooveSink *sink = stack_item->sink; struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; // as soon as we call groove_queue_put, this buffer could be unref'd. // so we ref before putting it in the queue, and unref if it failed. groove_buffer_ref(buffer); if (groove_queue_put(s->audioq, buffer) < 0) { av_log(NULL, AV_LOG_ERROR, "unable to put buffer in queue\n"); groove_buffer_unref(buffer); } stack_item = stack_item->next; } groove_buffer_unref(buffer); } if (data_size > max_data_size) { max_data_size = data_size; clock_adjustment = data_size / (double)example_sink->bytes_per_sec; } map_item = map_item->next; } // if no pts, then estimate it if (pkt->pts == AV_NOPTS_VALUE) f->audio_clock += clock_adjustment; return max_data_size; } return max_data_size; } static const double dB_scale = 0.1151292546497023; // log(10) * 0.05 static double gain_to_dB(double gain) { return log(gain) / dB_scale; } static int create_volume_filter(struct GroovePlaylistPrivate *p, AVFilterContext **audio_src_ctx, double vol, double amp_vol) { int err; if (vol < 0.0) vol = 0.0; if (amp_vol < 1.0) { snprintf(p->strbuf, sizeof(p->strbuf), "volume=%f", vol); av_log(NULL, AV_LOG_INFO, "volume: %s\n", p->strbuf); AVFilterContext *volume_ctx; err = avfilter_graph_create_filter(&volume_ctx, p->volume_filter, NULL, p->strbuf, NULL, p->filter_graph); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "error initializing volume filter\n"); return err; } err = avfilter_link(*audio_src_ctx, 0, volume_ctx, 0); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to link volume filter: %s\n", p->strbuf); return err; } *audio_src_ctx = volume_ctx; } else if (amp_vol > 1.0) { double attack = 0.1; double decay = 0.2; const char *points = "-2/-2"; double soft_knee = 0.02; double gain = gain_to_dB(vol); double volume_param = 0.0; double delay = 0.2; snprintf(p->strbuf, sizeof(p->strbuf), "%f:%f:%s:%f:%f:%f:%f", attack, decay, points, soft_knee, gain, volume_param, delay); av_log(NULL, AV_LOG_INFO, "compand: %s\n", p->strbuf); AVFilterContext *compand_ctx; err = avfilter_graph_create_filter(&compand_ctx, p->compand_filter, NULL, p->strbuf, NULL, p->filter_graph); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "error initializing compand filter\n"); return err; } err = avfilter_link(*audio_src_ctx, 0, compand_ctx, 0); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to link compand filter: %s\n", p->strbuf); return err; } *audio_src_ctx = compand_ctx; } return 0; } // abuffer -> volume -> asplit for each audio format // -> volume -> aformat -> abuffersink // if the volume gain is > 1.0, we use a compand filter instead // for soft limiting. static int init_filter_graph(struct GroovePlaylist *playlist, struct GrooveFile *file) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; // destruct old graph avfilter_graph_free(&p->filter_graph); // create new graph p->filter_graph = avfilter_graph_alloc(); if (!p->filter_graph) { av_log(NULL, AV_LOG_ERROR, "unable to create filter graph: out of memory\n"); return -1; } int err; // create abuffer filter AVCodecContext *avctx = f->audio_st->codec; AVRational time_base = f->audio_st->time_base; snprintf(p->strbuf, sizeof(p->strbuf), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, time_base.num, time_base.den, avctx->sample_rate, av_get_sample_fmt_name(avctx->sample_fmt), avctx->channel_layout); av_log(NULL, AV_LOG_INFO, "abuffer: %s\n", p->strbuf); // save these values so we can compare later and check // whether we have to reconstruct the graph p->in_sample_rate = avctx->sample_rate; p->in_channel_layout = avctx->channel_layout; p->in_sample_fmt = avctx->sample_fmt; p->in_time_base = time_base; err = avfilter_graph_create_filter(&p->abuffer_ctx, p->abuffer_filter, NULL, p->strbuf, NULL, p->filter_graph); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "error initializing abuffer filter\n"); return err; } // as we create filters, this points the next source to link to AVFilterContext *audio_src_ctx = p->abuffer_ctx; // save the volume value so we can compare later and check // whether we have to reconstruct the graph p->filter_volume = p->volume; p->filter_peak = p->peak; // if volume is < 1.0, create volume filter // == 1.0, do not create a filter // > 1.0, create a compand filter (for soft limiting) double vol = p->volume; // adjust for the known true peak of the playlist item. In other words, if // we know that the song peaks at 0.8, and we want to amplify by 1.2, that // comes out to 0.96 so we know that we can safely amplify by 1.2 even // though it's greater than 1.0. double amp_vol = vol * (p->peak > 1.0 ? 1.0 : p->peak); err = create_volume_filter(p, &audio_src_ctx, vol, amp_vol); if (err < 0) return err; // if only one sink, no need for asplit if (p->sink_map_count >= 2) { AVFilterContext *asplit_ctx; snprintf(p->strbuf, sizeof(p->strbuf), "%d", p->sink_map_count); av_log(NULL, AV_LOG_INFO, "asplit: %s\n", p->strbuf); err = avfilter_graph_create_filter(&asplit_ctx, p->asplit_filter, NULL, p->strbuf, NULL, p->filter_graph); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "unable to create asplit filter\n"); return err; } err = avfilter_link(audio_src_ctx, 0, asplit_ctx, 0); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "unable to link to asplit\n"); return err; } audio_src_ctx = asplit_ctx; } // for each audio format, create aformat and abuffersink filters struct SinkMap *map_item = p->sink_map; int pad_index = 0; while (map_item) { struct GrooveSink *example_sink = map_item->stack_head->sink; struct GrooveAudioFormat *audio_format = &example_sink->audio_format; AVFilterContext *inner_audio_src_ctx = audio_src_ctx; // create volume filter err = create_volume_filter(p, &inner_audio_src_ctx, example_sink->gain, example_sink->gain); if (err < 0) return err; if (!example_sink->disable_resample) { AVFilterContext *aformat_ctx; // create aformat filter snprintf(p->strbuf, sizeof(p->strbuf), "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%"PRIx64, av_get_sample_fmt_name((enum AVSampleFormat)audio_format->sample_fmt), audio_format->sample_rate, audio_format->channel_layout); av_log(NULL, AV_LOG_INFO, "aformat: %s\n", p->strbuf); err = avfilter_graph_create_filter(&aformat_ctx, p->aformat_filter, NULL, p->strbuf, NULL, p->filter_graph); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to create aformat filter: %s\n", p->strbuf); return err; } err = avfilter_link(inner_audio_src_ctx, pad_index, aformat_ctx, 0); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to link aformat filter: %s\n", p->strbuf); return err; } inner_audio_src_ctx = aformat_ctx; } // create abuffersink filter err = avfilter_graph_create_filter(&map_item->abuffersink_ctx, p->abuffersink_filter, NULL, NULL, NULL, p->filter_graph); if (err < 0) { av_log(NULL, AV_LOG_ERROR, "unable to create abuffersink filter\n"); return err; } err = avfilter_link(inner_audio_src_ctx, 0, map_item->abuffersink_ctx, 0); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "unable to link abuffersink filter: %s\n", p->strbuf); return err; } pad_index += 1; map_item = map_item->next; } err = avfilter_graph_config(p->filter_graph, NULL); if (err < 0) { av_strerror(err, p->strbuf, sizeof(p->strbuf)); av_log(NULL, AV_LOG_ERROR, "error configuring the filter graph: %s\n", p->strbuf); return err; } p->rebuild_filter_graph_flag = 0; return 0; } static int maybe_init_filter_graph(struct GroovePlaylist *playlist, struct GrooveFile *file) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; AVCodecContext *avctx = f->audio_st->codec; AVRational time_base = f->audio_st->time_base; // if the input format stuff has changed, then we need to re-build the graph if (!p->filter_graph || p->rebuild_filter_graph_flag || p->in_sample_rate != avctx->sample_rate || p->in_channel_layout != avctx->channel_layout || p->in_sample_fmt != avctx->sample_fmt || p->in_time_base.num != time_base.num || p->in_time_base.den != time_base.den || p->volume != p->filter_volume || p->peak != p->filter_peak) { return init_filter_graph(playlist, file); } return 0; } static int every_sink(struct GroovePlaylist *playlist, int (*func)(struct GrooveSink *), int default_value) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct SinkMap *map_item = p->sink_map; while (map_item) { struct SinkStack *stack_item = map_item->stack_head; while (stack_item) { struct GrooveSink *sink = stack_item->sink; int value = func(sink); if (value != default_value) return value; stack_item = stack_item->next; } map_item = map_item->next; } return default_value; } static int sink_is_full(struct GrooveSink *sink) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; return s->audioq_size >= s->min_audioq_size; } static int every_sink_full(struct GroovePlaylist *playlist) { return every_sink(playlist, sink_is_full, 1); } static int any_sink_full(struct GroovePlaylist *playlist) { return every_sink(playlist, sink_is_full, 0); } static int sink_signal_end(struct GrooveSink *sink) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; groove_queue_put(s->audioq, end_of_q_sentinel); return 0; } static void every_sink_signal_end(struct GroovePlaylist *playlist) { every_sink(playlist, sink_signal_end, 0); } static int sink_flush(struct GrooveSink *sink) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; groove_queue_flush(s->audioq); if (sink->flush) sink->flush(sink); return 0; } static void every_sink_flush(struct GroovePlaylist *playlist) { every_sink(playlist, sink_flush, 0); } static int decode_one_frame(struct GroovePlaylist *playlist, struct GrooveFile *file) { struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; AVPacket *pkt = &f->audio_pkt; // abort_request is set if we are destroying the file if (f->abort_request) return -1; // might need to rebuild the filter graph if certain things changed if (maybe_init_filter_graph(playlist, file) < 0) return -1; // handle seek requests pthread_mutex_lock(&f->seek_mutex); if (f->seek_pos >= 0) { if (av_seek_frame(f->ic, f->audio_stream_index, f->seek_pos, 0) < 0) { av_log(NULL, AV_LOG_ERROR, "%s: error while seeking\n", f->ic->filename); } else if (f->seek_flush) { every_sink_flush(playlist); } avcodec_flush_buffers(f->audio_st->codec); f->seek_pos = -1; f->eof = 0; } pthread_mutex_unlock(&f->seek_mutex); if (f->eof) { if (f->audio_st->codec->codec->capabilities & CODEC_CAP_DELAY) { av_init_packet(pkt); pkt->data = NULL; pkt->size = 0; pkt->stream_index = f->audio_stream_index; if (audio_decode_frame(playlist, file) > 0) { // keep flushing return 0; } } // this file is complete. move on return -1; } int err = av_read_frame(f->ic, pkt); if (err < 0) { // treat all errors as EOF, but log non-EOF errors. if (err != AVERROR_EOF) { av_log(NULL, AV_LOG_WARNING, "error reading frames\n"); } f->eof = 1; return 0; } if (pkt->stream_index != f->audio_stream_index) { // we're only interested in the One True Audio Stream av_free_packet(pkt); return 0; } audio_decode_frame(playlist, file); av_free_packet(pkt); return 0; } static void audioq_put(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveSinkPrivate *s = queue->context; s->audioq_size += buffer->size; } static void audioq_get(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveSink *sink = queue->context; struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; s->audioq_size -= buffer->size; struct GroovePlaylist *playlist = sink->playlist; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; if (s->audioq_size < s->min_audioq_size) { pthread_mutex_lock(&p->drain_cond_mutex); pthread_cond_signal(&p->sink_drain_cond); pthread_mutex_unlock(&p->drain_cond_mutex); } } static void audioq_cleanup(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return; struct GrooveSink *sink = queue->context; struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; s->audioq_size -= buffer->size; groove_buffer_unref(buffer); } static int audioq_purge(struct GrooveQueue *queue, void *obj) { struct GrooveBuffer *buffer = obj; if (buffer == end_of_q_sentinel) return 0; struct GrooveSink *sink = queue->context; struct GroovePlaylist *playlist = sink->playlist; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GroovePlaylistItem *item = p->purge_item; return buffer->item == item; } static void update_playlist_volume(struct GroovePlaylist *playlist) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GroovePlaylistItem *item = p->decode_head; p->volume = playlist->gain * item->gain; p->peak = item->peak; } // this thread is responsible for decoding and inserting buffers of decoded // audio into each sink static void *decode_thread(void *arg) { struct GroovePlaylistPrivate *p = arg; struct GroovePlaylist *playlist = &p->externals; while (!p->abort_request) { pthread_mutex_lock(&p->decode_head_mutex); // if we don't have anything to decode, wait until we do if (!p->decode_head) { if (!p->sent_end_of_q) { every_sink_signal_end(playlist); p->sent_end_of_q = 1; } pthread_cond_wait(&p->decode_head_cond, &p->decode_head_mutex); pthread_mutex_unlock(&p->decode_head_mutex); continue; } p->sent_end_of_q = 0; // if all sinks are filled up, no need to read more struct GrooveFile *file = p->decode_head->file; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; pthread_mutex_lock(&p->drain_cond_mutex); if (p->detect_full_sinks(playlist) && (f->seek_pos < 0 || !f->seek_flush)) { if (!f->paused) { av_read_pause(f->ic); f->paused = 1; } pthread_mutex_unlock(&p->decode_head_mutex); pthread_cond_wait(&p->sink_drain_cond, &p->drain_cond_mutex); pthread_mutex_unlock(&p->drain_cond_mutex); continue; } pthread_mutex_unlock(&p->drain_cond_mutex); if (f->paused) { av_read_play(f->ic); f->paused = 0; } update_playlist_volume(playlist); if (decode_one_frame(playlist, file) < 0) { p->decode_head = p->decode_head->next; // seek to beginning of next song if (p->decode_head) { struct GrooveFile *next_file = p->decode_head->file; struct GrooveFilePrivate *next_f = (struct GrooveFilePrivate *) next_file; pthread_mutex_lock(&next_f->seek_mutex); next_f->seek_pos = 0; next_f->seek_flush = 0; pthread_mutex_unlock(&next_f->seek_mutex); } } pthread_mutex_unlock(&p->decode_head_mutex); } return NULL; } static int sink_formats_compatible(const struct GrooveSink *example_sink, const struct GrooveSink *test_sink) { // buffer_sample_count 0 means we don't care if (test_sink->buffer_sample_count != 0 && example_sink->buffer_sample_count != test_sink->buffer_sample_count) { return 0; } if (example_sink->gain != test_sink->gain) return 0; if (!test_sink->disable_resample && (example_sink->audio_format.sample_rate != test_sink->audio_format.sample_rate || example_sink->audio_format.channel_layout != test_sink->audio_format.channel_layout || example_sink->audio_format.sample_fmt != test_sink->audio_format.sample_fmt)) { return 0; } return 1; } static int remove_sink_from_map(struct GrooveSink *sink) { struct GroovePlaylist *playlist = sink->playlist; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct SinkMap *map_item = p->sink_map; struct SinkMap *prev_map_item = NULL; while (map_item) { struct SinkMap *next_map_item = map_item->next; struct SinkStack *stack_item = map_item->stack_head; struct SinkStack *prev_stack_item = NULL; while (stack_item) { struct SinkStack *next_stack_item = stack_item->next; struct GrooveSink *item_sink = stack_item->sink; if (item_sink == sink) { av_free(stack_item); if (prev_stack_item) { prev_stack_item->next = next_stack_item; } else if (next_stack_item) { map_item->stack_head = next_stack_item; } else { // the stack is empty; delete the map item av_free(map_item); p->sink_map_count -= 1; if (prev_map_item) { prev_map_item->next = next_map_item; } else { p->sink_map = next_map_item; } } return 0; } prev_stack_item = stack_item; stack_item = next_stack_item; } prev_map_item = map_item; map_item = next_map_item; } return -1; } static int add_sink_to_map(struct GroovePlaylist *playlist, struct GrooveSink *sink) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct SinkStack *stack_entry = av_mallocz(sizeof(struct SinkStack)); if (!stack_entry) return -1; stack_entry->sink = sink; struct SinkMap *map_item = p->sink_map; while (map_item) { // if our sink matches the example sink from this map entry, // push our sink onto the stack and we're done struct GrooveSink *example_sink = map_item->stack_head->sink; if (sink_formats_compatible(example_sink, sink)) { stack_entry->next = map_item->stack_head->next; map_item->stack_head->next = stack_entry; return 0; } // maybe we need to swap the example sink with the new sink to make // it work. In this case we need to rebuild the filter graph. if (sink_formats_compatible(sink, example_sink)) { stack_entry->next = map_item->stack_head; map_item->stack_head = stack_entry; p->rebuild_filter_graph_flag = 1; return 0; } map_item = map_item->next; } // we did not find somewhere to put it, so push it onto the stack. struct SinkMap *map_entry = av_mallocz(sizeof(struct SinkMap)); map_entry->stack_head = stack_entry; if (!map_entry) { av_free(stack_entry); return -1; } if (p->sink_map) { map_entry->next = p->sink_map; p->sink_map = map_entry; } else { p->sink_map = map_entry; } p->rebuild_filter_graph_flag = 1; p->sink_map_count += 1; return 0; } static int groove_sink_play(struct GrooveSink *sink) { if (sink->play) sink->play(sink); return 0; } static int groove_sink_pause(struct GrooveSink *sink) { if (sink->pause) sink->pause(sink); return 0; } int groove_sink_detach(struct GrooveSink *sink) { struct GroovePlaylist *playlist = sink->playlist; if (!playlist) return -1; struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; if (s->audioq) { groove_queue_abort(s->audioq); groove_queue_flush(s->audioq); } struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); int err = remove_sink_from_map(sink); pthread_mutex_unlock(&p->decode_head_mutex); sink->playlist = NULL; return err; } int groove_sink_attach(struct GrooveSink *sink, struct GroovePlaylist *playlist) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; // cache computed audio format stuff int channel_count = av_get_channel_layout_nb_channels(sink->audio_format.channel_layout); int bytes_per_frame = channel_count * av_get_bytes_per_sample((enum AVSampleFormat)sink->audio_format.sample_fmt); sink->bytes_per_sec = bytes_per_frame * sink->audio_format.sample_rate; s->min_audioq_size = sink->buffer_size * bytes_per_frame; av_log(NULL, AV_LOG_INFO, "audio queue size: %d\n", s->min_audioq_size); // add the sink to the entry that matches its audio format struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; // must do this above add_sink_to_map to avid race condition sink->playlist = playlist; pthread_mutex_lock(&p->decode_head_mutex); int err = add_sink_to_map(playlist, sink); pthread_mutex_lock(&p->drain_cond_mutex); pthread_cond_signal(&p->sink_drain_cond); pthread_mutex_unlock(&p->drain_cond_mutex); pthread_mutex_unlock(&p->decode_head_mutex); if (err < 0) { sink->playlist = NULL; av_log(NULL, AV_LOG_ERROR, "unable to attach device: out of memory\n"); return err; } // in case we've called abort on the queue, reset groove_queue_reset(s->audioq); return 0; } int groove_sink_buffer_get(struct GrooveSink *sink, struct GrooveBuffer **buffer, int block) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; if (groove_queue_get(s->audioq, (void**)buffer, block) == 1) { if (*buffer == end_of_q_sentinel) { *buffer = NULL; return GROOVE_BUFFER_END; } else { return GROOVE_BUFFER_YES; } } else { *buffer = NULL; return GROOVE_BUFFER_NO; } } int groove_sink_buffer_peek(struct GrooveSink *sink, int block) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; return groove_queue_peek(s->audioq, block); } struct GroovePlaylist * groove_playlist_create(void) { struct GroovePlaylistPrivate *p = av_mallocz(sizeof(struct GroovePlaylistPrivate)); if (!p) { av_log(NULL, AV_LOG_ERROR, "unable to allocate playlist\n"); return NULL; } struct GroovePlaylist *playlist = &p->externals; // the one that the playlist can read playlist->gain = 1.0; // the other volume multiplied by the playlist item's gain p->volume = 1.0; // set this flag to true so that a race condition does not send the end of // queue sentinel early. p->sent_end_of_q = 1; p->detect_full_sinks = every_sink_full; if (pthread_mutex_init(&p->decode_head_mutex, NULL) != 0) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to allocate decode head mutex\n"); return NULL; } p->decode_head_mutex_inited = 1; if (pthread_mutex_init(&p->drain_cond_mutex, NULL) != 0) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to allocate drain cond mutex\n"); return NULL; } p->drain_cond_mutex_inited = 1; if (pthread_cond_init(&p->decode_head_cond, NULL) != 0) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to allocate decode head mutex condition\n"); return NULL; } p->decode_head_cond_inited = 1; if (pthread_cond_init(&p->sink_drain_cond, NULL) != 0) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to allocate sink drain mutex condition\n"); return NULL; } p->sink_drain_cond_inited = 1; p->in_frame = av_frame_alloc(); if (!p->in_frame) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to allocate frame\n"); return NULL; } if (pthread_create(&p->thread_id, NULL, decode_thread, playlist) != 0) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to create playlist thread\n"); return NULL; } p->volume_filter = avfilter_get_by_name("volume"); if (!p->volume_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get volume filter\n"); return NULL; } p->compand_filter = avfilter_get_by_name("compand"); if (!p->compand_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get compand filter\n"); return NULL; } p->abuffer_filter = avfilter_get_by_name("abuffer"); if (!p->abuffer_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get abuffer filter\n"); return NULL; } p->asplit_filter = avfilter_get_by_name("asplit"); if (!p->asplit_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get asplit filter\n"); return NULL; } p->aformat_filter = avfilter_get_by_name("aformat"); if (!p->aformat_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get aformat filter\n"); return NULL; } p->abuffersink_filter = avfilter_get_by_name("abuffersink"); if (!p->abuffersink_filter) { groove_playlist_destroy(playlist); av_log(NULL, AV_LOG_ERROR, "unable to get abuffersink filter\n"); return NULL; } return playlist; } void groove_playlist_destroy(struct GroovePlaylist *playlist) { groove_playlist_clear(playlist); struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; // wait for decode thread to finish p->abort_request = 1; pthread_cond_signal(&p->decode_head_cond); pthread_cond_signal(&p->sink_drain_cond); pthread_join(p->thread_id, NULL); every_sink(playlist, groove_sink_detach, 0); avfilter_graph_free(&p->filter_graph); av_frame_free(&p->in_frame); if (p->decode_head_mutex_inited) pthread_mutex_destroy(&p->decode_head_mutex); if (p->drain_cond_mutex_inited) pthread_mutex_destroy(&p->drain_cond_mutex); if (p->decode_head_cond_inited) pthread_cond_destroy(&p->decode_head_cond); if (p->sink_drain_cond_inited) pthread_cond_destroy(&p->sink_drain_cond); av_free(p); } void groove_playlist_play(struct GroovePlaylist *playlist) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; // no mutex needed for this boolean flag if (p->paused == 0) return; p->paused = 0; every_sink(playlist, groove_sink_play, 0); } void groove_playlist_pause(struct GroovePlaylist *playlist) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; // no mutex needed for this boolean flag if (p->paused == 1) return; p->paused = 1; every_sink(playlist, groove_sink_pause, 0); } void groove_playlist_seek(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double seconds) { struct GrooveFile * file = item->file; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; int64_t ts = seconds * f->audio_st->time_base.den / f->audio_st->time_base.num; if (f->ic->start_time != AV_NOPTS_VALUE) ts += f->ic->start_time; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); pthread_mutex_lock(&f->seek_mutex); f->seek_pos = ts; f->seek_flush = 1; pthread_mutex_unlock(&f->seek_mutex); p->decode_head = item; pthread_cond_signal(&p->decode_head_cond); pthread_mutex_unlock(&p->decode_head_mutex); } struct GroovePlaylistItem *groove_playlist_insert(struct GroovePlaylist *playlist, struct GrooveFile *file, double gain, double peak, struct GroovePlaylistItem *next) { struct GroovePlaylistItem * item = av_mallocz(sizeof(struct GroovePlaylistItem)); if (!item) return NULL; item->file = file; item->next = next; item->gain = gain; item->peak = peak; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; // lock decode_head_mutex so that decode_head cannot point to a new item // while we're screwing around with the queue pthread_mutex_lock(&p->decode_head_mutex); if (next) { if (next->prev) { item->prev = next->prev; item->prev->next = item; next->prev = item; } else { playlist->head = item; } } else if (!playlist->head) { playlist->head = item; playlist->tail = item; pthread_mutex_lock(&f->seek_mutex); f->seek_pos = 0; f->seek_flush = 0; pthread_mutex_unlock(&f->seek_mutex); p->decode_head = playlist->head; pthread_cond_signal(&p->decode_head_cond); } else { item->prev = playlist->tail; playlist->tail->next = item; playlist->tail = item; } pthread_mutex_unlock(&p->decode_head_mutex); return item; } static int purge_sink(struct GrooveSink *sink) { struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; groove_queue_purge(s->audioq); struct GroovePlaylist *playlist = sink->playlist; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; struct GroovePlaylistItem *item = p->purge_item; if (sink->purge) sink->purge(sink, item); return 0; } void groove_playlist_remove(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); // if it's currently being played, seek to the next item if (item == p->decode_head) { p->decode_head = item->next; } if (item->prev) { item->prev->next = item->next; } else { playlist->head = item->next; } if (item->next) { item->next->prev = item->prev; } else { playlist->tail = item->prev; } // in each sink, // we must be absolutely sure to purge the audio buffer queue // of references to item before freeing it at the bottom of this method p->purge_item = item; every_sink(playlist, purge_sink, 0); p->purge_item = NULL; pthread_mutex_lock(&p->drain_cond_mutex); pthread_cond_signal(&p->sink_drain_cond); pthread_mutex_unlock(&p->drain_cond_mutex); pthread_mutex_unlock(&p->decode_head_mutex); av_free(item); } void groove_playlist_clear(struct GroovePlaylist *playlist) { struct GroovePlaylistItem * node = playlist->head; if (!node) return; while (node) { struct GroovePlaylistItem *next = node->next; groove_playlist_remove(playlist, node); node = next; } } int groove_playlist_count(struct GroovePlaylist *playlist) { struct GroovePlaylistItem * node = playlist->head; int count = 0; while (node) { count += 1; node = node->next; } return count; } void groove_playlist_set_item_gain(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double gain) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); item->gain = gain; if (item == p->decode_head) { update_playlist_volume(playlist); } pthread_mutex_unlock(&p->decode_head_mutex); } void groove_playlist_set_item_peak(struct GroovePlaylist *playlist, struct GroovePlaylistItem *item, double peak) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); item->peak = peak; if (item == p->decode_head) { update_playlist_volume(playlist); } pthread_mutex_unlock(&p->decode_head_mutex); } void groove_playlist_position(struct GroovePlaylist *playlist, struct GroovePlaylistItem **item, double *seconds) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); if (item) *item = p->decode_head; if (seconds) { if (p->decode_head) { struct GrooveFile *file = p->decode_head->file; struct GrooveFilePrivate *f = (struct GrooveFilePrivate *) file; *seconds = f->audio_clock; } else { *seconds = -1.0; } } pthread_mutex_unlock(&p->decode_head_mutex); } void groove_playlist_set_gain(struct GroovePlaylist *playlist, double gain) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); playlist->gain = gain; if (p->decode_head) update_playlist_volume(playlist); pthread_mutex_unlock(&p->decode_head_mutex); } int groove_playlist_playing(struct GroovePlaylist *playlist) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; return !p->paused; } struct GrooveSink * groove_sink_create(void) { struct GrooveSinkPrivate *s = av_mallocz(sizeof(struct GrooveSinkPrivate)); if (!s) { av_log(NULL, AV_LOG_ERROR, "could not create sink: out of memory\n"); return NULL; } struct GrooveSink *sink = &s->externals; sink->buffer_size = 8192; sink->gain = 1.0; s->audioq = groove_queue_create(); if (!s->audioq) { groove_sink_destroy(sink); av_log(NULL, AV_LOG_ERROR, "could not create audio buffer: out of memory\n"); return NULL; } s->audioq->context = sink; s->audioq->cleanup = audioq_cleanup; s->audioq->put = audioq_put; s->audioq->get = audioq_get; s->audioq->purge = audioq_purge; return sink; } void groove_sink_destroy(struct GrooveSink *sink) { if (!sink) return; struct GrooveSinkPrivate *s = (struct GrooveSinkPrivate *) sink; if (s->audioq) groove_queue_destroy(s->audioq); av_free(s); } int groove_sink_set_gain(struct GrooveSink *sink, double gain) { // we must re-create the sink mapping and the filter graph // if the gain changes struct GroovePlaylist *playlist = sink->playlist; struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); sink->gain = gain; int err = remove_sink_from_map(sink); if (err) { pthread_mutex_unlock(&p->decode_head_mutex); return err; } err = add_sink_to_map(playlist, sink); if (err) { pthread_mutex_unlock(&p->decode_head_mutex); return err; } p->rebuild_filter_graph_flag = 1; pthread_mutex_unlock(&p->decode_head_mutex); return 0; } void groove_playlist_set_fill_mode(struct GroovePlaylist *playlist, int mode) { struct GroovePlaylistPrivate *p = (struct GroovePlaylistPrivate *) playlist; pthread_mutex_lock(&p->decode_head_mutex); if (mode == GROOVE_EVERY_SINK_FULL) { p->detect_full_sinks = every_sink_full; } else { p->detect_full_sinks = any_sink_full; } pthread_mutex_unlock(&p->decode_head_mutex); } libgroove-4.3.0/groove/queue.c000066400000000000000000000122411253074642100163030ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "queue.h" #include #include struct ItemList { void *obj; struct ItemList *next; }; struct GrooveQueuePrivate { struct GrooveQueue externals; struct ItemList *first; struct ItemList *last; pthread_mutex_t mutex; pthread_cond_t cond; int abort_request; }; struct GrooveQueue *groove_queue_create(void) { struct GrooveQueuePrivate *q = av_mallocz(sizeof(struct GrooveQueuePrivate)); if (!q) return NULL; if (pthread_mutex_init(&q->mutex, NULL) != 0) { av_free(q); return NULL; } if (pthread_cond_init(&q->cond, NULL) != 0) { av_free(q); pthread_mutex_destroy(&q->mutex); return NULL; } struct GrooveQueue *queue = &q->externals; queue->cleanup = groove_queue_cleanup_default; return queue; } void groove_queue_flush(struct GrooveQueue *queue) { struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); struct ItemList *el; struct ItemList *el1; for (el = q->first; el != NULL; el = el1) { el1 = el->next; if (queue->cleanup) queue->cleanup(queue, el->obj); av_free(el); } q->first = NULL; q->last = NULL; pthread_mutex_unlock(&q->mutex); } void groove_queue_destroy(struct GrooveQueue *queue) { groove_queue_flush(queue); struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_destroy(&q->mutex); pthread_cond_destroy(&q->cond); av_free(q); } void groove_queue_abort(struct GrooveQueue *queue) { struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); q->abort_request = 1; pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); } void groove_queue_reset(struct GrooveQueue *queue) { struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); q->abort_request = 0; pthread_mutex_unlock(&q->mutex); } int groove_queue_put(struct GrooveQueue *queue, void *obj) { struct ItemList * el1 = av_mallocz(sizeof(struct ItemList)); if (!el1) return -1; el1->obj = obj; struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); if (!q->last) q->first = el1; else q->last->next = el1; q->last = el1; if (queue->put) queue->put(queue, obj); pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); return 0; } int groove_queue_peek(struct GrooveQueue *queue, int block) { int ret; struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); for (;;) { if (q->abort_request) { ret = -1; break; } if (q->first) { ret = 1; break; } else if (!block) { ret = 0; break; } else { pthread_cond_wait(&q->cond, &q->mutex); } } pthread_mutex_unlock(&q->mutex); return ret; } int groove_queue_get(struct GrooveQueue *queue, void **obj_ptr, int block) { struct ItemList *ev1; int ret; struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); for (;;) { if (q->abort_request) { ret = -1; break; } ev1 = q->first; if (ev1) { q->first = ev1->next; if (!q->first) q->last = NULL; if (queue->get) queue->get(queue, ev1->obj); *obj_ptr = ev1->obj; av_free(ev1); ret = 1; break; } else if(!block) { ret = 0; break; } else { pthread_cond_wait(&q->cond, &q->mutex); } } pthread_mutex_unlock(&q->mutex); return ret; } void groove_queue_purge(struct GrooveQueue *queue) { struct GrooveQueuePrivate *q = (struct GrooveQueuePrivate *) queue; pthread_mutex_lock(&q->mutex); struct ItemList *node = q->first; struct ItemList *prev = NULL; while (node) { if (queue->purge(queue, node->obj)) { if (prev) { prev->next = node->next; if (queue->cleanup) queue->cleanup(queue, node->obj); av_free(node); node = prev->next; if (!node) q->last = prev; } else { struct ItemList *next = node->next; if (queue->cleanup) queue->cleanup(queue, node->obj); av_free(node); q->first = next; node = next; if (!node) q->last = NULL; } } else { prev = node; node = node->next; } } pthread_mutex_unlock(&q->mutex); } void groove_queue_cleanup_default(struct GrooveQueue *queue, void *obj) { av_free(obj); } libgroove-4.3.0/groove/queue.h000066400000000000000000000022501253074642100163070ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_QUEUE_H_INCLUDED #define GROOVE_QUEUE_H_INCLUDED struct GrooveQueue { void *context; // defaults to groove_queue_cleanup_default void (*cleanup)(struct GrooveQueue*, void *obj); void (*put)(struct GrooveQueue*, void *obj); void (*get)(struct GrooveQueue*, void *obj); int (*purge)(struct GrooveQueue*, void *obj); }; struct GrooveQueue *groove_queue_create(void); void groove_queue_flush(struct GrooveQueue *queue); void groove_queue_destroy(struct GrooveQueue *queue); void groove_queue_abort(struct GrooveQueue *queue); void groove_queue_reset(struct GrooveQueue *queue); int groove_queue_put(struct GrooveQueue *queue, void *obj); // returns -1 if aborted, 1 if got event, 0 if no event ready int groove_queue_get(struct GrooveQueue *queue, void **obj_ptr, int block); int groove_queue_peek(struct GrooveQueue *queue, int block); void groove_queue_purge(struct GrooveQueue *queue); void groove_queue_cleanup_default(struct GrooveQueue *queue, void *obj); #endif /* GROOVE_QUEUE_H_INCLUDED */ libgroove-4.3.0/groovefingerprinter/000077500000000000000000000000001253074642100176125ustar00rootroot00000000000000libgroove-4.3.0/groovefingerprinter/fingerprinter.c000066400000000000000000000276031253074642100226440ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "fingerprinter.h" #include #include #include #include #include #include #include #include struct GrooveFingerprinterPrivate { struct GrooveFingerprinter externals; int state_history_count; // index into all_track_states struct GrooveSink *sink; struct GrooveQueue *info_queue; pthread_t thread_id; // info_head_mutex applies to variables inside this block. pthread_mutex_t info_head_mutex; char info_head_mutex_inited; // current playlist item pointer struct GroovePlaylistItem *info_head; double info_pos; // analyze_thread waits on this when the info queue is full pthread_cond_t drain_cond; char drain_cond_inited; // how many items are in the queue int info_queue_count; double track_duration; double album_duration; ChromaprintContext *chroma_ctx; // set temporarily struct GroovePlaylistItem *purge_item; int abort_request; }; static int emit_track_info(struct GrooveFingerprinterPrivate *p) { struct GrooveFingerprinterInfo *info = av_mallocz(sizeof(struct GrooveFingerprinterInfo)); if (!info) { av_log(NULL, AV_LOG_ERROR, "unable to allocate fingerprinter info\n"); return -1; } info->item = p->info_head; info->duration = p->track_duration; if (!chromaprint_finish(p->chroma_ctx)) { av_log(NULL, AV_LOG_ERROR, "unable to finish chromaprint\n"); return -1; } if (!chromaprint_get_raw_fingerprint(p->chroma_ctx, (void**)&info->fingerprint, &info->fingerprint_size)) { av_log(NULL, AV_LOG_ERROR, "unable to get fingerprint\n"); return -1; } groove_queue_put(p->info_queue, info); return 0; } static void *print_thread(void *arg) { struct GrooveFingerprinterPrivate *p = arg; struct GrooveFingerprinter *printer = &p->externals; struct GrooveBuffer *buffer; while (!p->abort_request) { pthread_mutex_lock(&p->info_head_mutex); if (p->info_queue_count >= printer->info_queue_size) { pthread_cond_wait(&p->drain_cond, &p->info_head_mutex); pthread_mutex_unlock(&p->info_head_mutex); continue; } // we definitely want to unlock the mutex while we wait for the // next buffer. Otherwise there will be a deadlock when sink_flush or // sink_purge is called. pthread_mutex_unlock(&p->info_head_mutex); int result = groove_sink_buffer_get(p->sink, &buffer, 1); pthread_mutex_lock(&p->info_head_mutex); if (result == GROOVE_BUFFER_END) { // last file info emit_track_info(p); // send album info struct GrooveFingerprinterInfo *info = av_mallocz( sizeof(struct GrooveFingerprinterInfo)); if (info) { info->duration = p->album_duration; groove_queue_put(p->info_queue, info); } else { av_log(NULL, AV_LOG_ERROR, "unable to allocate album fingerprint info\n"); } p->album_duration = 0.0; p->info_head = NULL; p->info_pos = -1.0; pthread_mutex_unlock(&p->info_head_mutex); continue; } if (result != GROOVE_BUFFER_YES) { pthread_mutex_unlock(&p->info_head_mutex); break; } if (buffer->item != p->info_head) { if (p->info_head) { emit_track_info(p); } if (!chromaprint_start(p->chroma_ctx, 44100, 2)) { av_log(NULL, AV_LOG_ERROR, "unable to start fingerprint\n"); } p->track_duration = 0.0; p->info_head = buffer->item; p->info_pos = buffer->pos; } double buffer_duration = buffer->frame_count / (double)buffer->format.sample_rate; p->track_duration += buffer_duration; p->album_duration += buffer_duration; if (!chromaprint_feed(p->chroma_ctx, buffer->data[0], buffer->frame_count * 2)) { av_log(NULL, AV_LOG_ERROR, "unable to feed fingerprint\n"); } pthread_mutex_unlock(&p->info_head_mutex); groove_buffer_unref(buffer); } return NULL; } static void info_queue_cleanup(struct GrooveQueue* queue, void *obj) { struct GrooveFingerprinterInfo *info = obj; struct GrooveFingerprinterPrivate *p = queue->context; p->info_queue_count -= 1; av_free(info); } static void info_queue_put(struct GrooveQueue *queue, void *obj) { struct GrooveFingerprinterPrivate *p = queue->context; p->info_queue_count += 1; } static void info_queue_get(struct GrooveQueue *queue, void *obj) { struct GrooveFingerprinterPrivate *p = queue->context; struct GrooveFingerprinter *printer = &p->externals; p->info_queue_count -= 1; if (p->info_queue_count < printer->info_queue_size) pthread_cond_signal(&p->drain_cond); } static int info_queue_purge(struct GrooveQueue* queue, void *obj) { struct GrooveFingerprinterInfo *info = obj; struct GrooveFingerprinterPrivate *p = queue->context; return info->item == p->purge_item; } static void sink_purge(struct GrooveSink *sink, struct GroovePlaylistItem *item) { struct GrooveFingerprinterPrivate *p = sink->userdata; pthread_mutex_lock(&p->info_head_mutex); p->purge_item = item; groove_queue_purge(p->info_queue); p->purge_item = NULL; if (p->info_head == item) { p->info_head = NULL; p->info_pos = -1.0; } pthread_cond_signal(&p->drain_cond); pthread_mutex_unlock(&p->info_head_mutex); } static void sink_flush(struct GrooveSink *sink) { struct GrooveFingerprinterPrivate *p = sink->userdata; pthread_mutex_lock(&p->info_head_mutex); groove_queue_flush(p->info_queue); p->track_duration = 0.0; p->info_head = NULL; p->info_pos = -1.0; pthread_cond_signal(&p->drain_cond); pthread_mutex_unlock(&p->info_head_mutex); } struct GrooveFingerprinter *groove_fingerprinter_create(void) { struct GrooveFingerprinterPrivate *p = av_mallocz(sizeof(struct GrooveFingerprinterPrivate)); if (!p) { av_log(NULL, AV_LOG_ERROR, "unable to allocate fingerprinter\n"); return NULL; } struct GrooveFingerprinter *printer = &p->externals; if (pthread_mutex_init(&p->info_head_mutex, NULL) != 0) { groove_fingerprinter_destroy(printer); av_log(NULL, AV_LOG_ERROR, "unable to create mutex\n"); return NULL; } p->info_head_mutex_inited = 1; if (pthread_cond_init(&p->drain_cond, NULL) != 0) { groove_fingerprinter_destroy(printer); av_log(NULL, AV_LOG_ERROR, "unable to create mutex condition\n"); return NULL; } p->drain_cond_inited = 1; p->info_queue = groove_queue_create(); if (!p->info_queue) { groove_fingerprinter_destroy(printer); av_log(NULL, AV_LOG_ERROR, "unable to allocate queue\n"); return NULL; } p->info_queue->context = printer; p->info_queue->cleanup = info_queue_cleanup; p->info_queue->put = info_queue_put; p->info_queue->get = info_queue_get; p->info_queue->purge = info_queue_purge; p->sink = groove_sink_create(); if (!p->sink) { groove_fingerprinter_destroy(printer); av_log(NULL, AV_LOG_ERROR, "unable to allocate sink\n"); return NULL; } p->sink->audio_format.sample_rate = 44100; p->sink->audio_format.channel_layout = GROOVE_CH_LAYOUT_STEREO; p->sink->audio_format.sample_fmt = GROOVE_SAMPLE_FMT_S16; p->sink->userdata = printer; p->sink->purge = sink_purge; p->sink->flush = sink_flush; // set some defaults printer->info_queue_size = INT_MAX; printer->sink_buffer_size = p->sink->buffer_size; return printer; } void groove_fingerprinter_destroy(struct GrooveFingerprinter *printer) { if (!printer) return; struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; if (p->sink) groove_sink_destroy(p->sink); if (p->info_queue) groove_queue_destroy(p->info_queue); if (p->info_head_mutex_inited) pthread_mutex_destroy(&p->info_head_mutex); if (p->drain_cond_inited) pthread_cond_destroy(&p->drain_cond); av_free(p); } int groove_fingerprinter_attach(struct GrooveFingerprinter *printer, struct GroovePlaylist *playlist) { struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; printer->playlist = playlist; groove_queue_reset(p->info_queue); p->chroma_ctx = chromaprint_new(CHROMAPRINT_ALGORITHM_DEFAULT); if (!p->chroma_ctx) { groove_fingerprinter_detach(printer); av_log(NULL, AV_LOG_ERROR, "unable to allocate chromaprint\n"); return -1; } if (groove_sink_attach(p->sink, playlist) < 0) { groove_fingerprinter_detach(printer); av_log(NULL, AV_LOG_ERROR, "unable to attach sink\n"); return -1; } if (pthread_create(&p->thread_id, NULL, print_thread, printer) != 0) { groove_fingerprinter_detach(printer); av_log(NULL, AV_LOG_ERROR, "unable to create printer thread\n"); return -1; } return 0; } int groove_fingerprinter_detach(struct GrooveFingerprinter *printer) { struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; p->abort_request = 1; groove_sink_detach(p->sink); groove_queue_flush(p->info_queue); groove_queue_abort(p->info_queue); pthread_cond_signal(&p->drain_cond); pthread_join(p->thread_id, NULL); printer->playlist = NULL; if (p->chroma_ctx) { chromaprint_free(p->chroma_ctx); p->chroma_ctx = NULL; } p->abort_request = 0; p->info_head = NULL; p->info_pos = 0; p->track_duration = 0.0; return 0; } int groove_fingerprinter_info_get(struct GrooveFingerprinter *printer, struct GrooveFingerprinterInfo *info, int block) { struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; struct GrooveFingerprinterInfo *info_ptr; if (groove_queue_get(p->info_queue, (void**)&info_ptr, block) == 1) { *info = *info_ptr; av_free(info_ptr); return 1; } return 0; } int groove_fingerprinter_info_peek(struct GrooveFingerprinter *printer, int block) { struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; return groove_queue_peek(p->info_queue, block); } void groove_fingerprinter_position(struct GrooveFingerprinter *printer, struct GroovePlaylistItem **item, double *seconds) { struct GrooveFingerprinterPrivate *p = (struct GrooveFingerprinterPrivate *) printer; pthread_mutex_lock(&p->info_head_mutex); if (item) *item = p->info_head; if (seconds) *seconds = p->info_pos; pthread_mutex_unlock(&p->info_head_mutex); } void groove_fingerprinter_free_info(struct GrooveFingerprinterInfo *info) { if (!info->fingerprint) return; chromaprint_dealloc((void*)info->fingerprint); info->fingerprint = NULL; } int groove_fingerprinter_encode(int32_t *fp, int size, char **encoded_fp) { int encoded_size; int err = chromaprint_encode_fingerprint(fp, size, CHROMAPRINT_ALGORITHM_DEFAULT, (void*)encoded_fp, &encoded_size, 1); return err == 1 ? 0 : -1; } int groove_fingerprinter_decode(char *encoded_fp, int32_t **fp, int *size) { int algorithm; int encoded_size = strlen(encoded_fp); int err = chromaprint_decode_fingerprint(encoded_fp, encoded_size, (void**)fp, size, &algorithm, 1); return err == 1 ? 0 : -1; } void groove_fingerprinter_dealloc(void *ptr) { if (!ptr) return; chromaprint_dealloc(ptr); } libgroove-4.3.0/groovefingerprinter/fingerprinter.h000066400000000000000000000101621253074642100226410ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_FINGERPRINTER_H_INCLUDED #define GROOVE_FINGERPRINTER_H_INCLUDED #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* use this to find out the unique id of an audio track */ struct GrooveFingerprinterInfo { /* raw fingerprint. A fingerprint is an array of signed 32-bit integers. */ int32_t *fingerprint; /* the number of 32-bit integers in the fingerprint array */ int fingerprint_size; /* how many seconds long this song is */ double duration; /* the playlist item that this info applies to. * When this is NULL this is the end-of-playlist sentinel and * other properties are undefined. */ struct GroovePlaylistItem *item; }; struct GrooveFingerprinter { /* maximum number of GrooveFingerprinterInfo items to store in this * fingerprinter's queue. this defaults to MAX_INT, meaning that * the fingerprinter will cause the decoder to decode the entire * playlist. if you want to instead, for example, obtain fingerprints * at the same time as playback, you might set this value to 1. */ int info_queue_size; /* how big the sink buffer should be, in sample frames. * groove_fingerprinter_create defaults this to 8192 */ int sink_buffer_size; /* read-only. set when attached and cleared when detached */ struct GroovePlaylist *playlist; }; struct GrooveFingerprinter *groove_fingerprinter_create(void); void groove_fingerprinter_destroy(struct GrooveFingerprinter *printer); /* once you attach, you must detach before destroying the playlist */ int groove_fingerprinter_attach(struct GrooveFingerprinter *printer, struct GroovePlaylist *playlist); int groove_fingerprinter_detach(struct GrooveFingerprinter *printer); /* returns < 0 on error, 0 on aborted (block=1) or no info ready (block=0), * 1 on info returned. * When you get info you must free it with groove_fingerprinter_free_info. */ int groove_fingerprinter_info_get(struct GrooveFingerprinter *printer, struct GrooveFingerprinterInfo *info, int block); void groove_fingerprinter_free_info(struct GrooveFingerprinterInfo *info); /* returns < 0 on error, 0 on no info ready, 1 on info ready * if block is 1, block until info is ready */ int groove_fingerprinter_info_peek(struct GrooveFingerprinter *printer, int block); /* get the position of the printer head * both the current playlist item and the position in seconds in the playlist * item are given. item will be set to NULL if the playlist is empty * you may pass NULL for item or seconds */ void groove_fingerprinter_position(struct GrooveFingerprinter *printer, struct GroovePlaylistItem **item, double *seconds); /** * Compress and base64-encode a raw fingerprint * * The caller is responsible for freeing the returned pointer using * groove_fingerprinter_dealloc(). * * Parameters: * - fp: pointer to an array of signed 32-bit integers representing the raw * fingerprint to be encoded * - size: number of items in the raw fingerprint * - encoded_fp: pointer to a pointer, where the encoded fingerprint will be * stored * * Returns: * - 0 on success, < 0 on error */ int groove_fingerprinter_encode(int32_t *fp, int size, char **encoded_fp); /** * Uncompress and base64-decode an encoded fingerprint * * The caller is responsible for freeing the returned pointer using * groove_fingerprinter_dealloc(). * * Parameters: * - encoded_fp: Pointer to an encoded fingerprint * - encoded_size: Size of the encoded fingerprint in bytes * - fp: Pointer to a pointer, where the decoded raw fingerprint (array * of signed 32-bit integers) will be stored * - size: Number of items in the returned raw fingerprint * * Returns: * - 0 on success, < 0 on error */ int groove_fingerprinter_decode(char *encoded_fp, int32_t **fp, int *size); void groove_fingerprinter_dealloc(void *ptr); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* GROOVE_FINGERPRINTER_H_INCLUDED */ libgroove-4.3.0/grooveloudness/000077500000000000000000000000001253074642100165705ustar00rootroot00000000000000libgroove-4.3.0/grooveloudness/loudness.c000066400000000000000000000341051253074642100205730ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "loudness.h" #include #include #include #include #include #include #include #include struct GrooveLoudnessDetectorPrivate { struct GrooveLoudnessDetector externals; int state_history_count; // index into all_track_states int cur_track_index; ebur128_state **all_track_states; struct GrooveSink *sink; struct GrooveQueue *info_queue; pthread_t thread_id; // info_head_mutex applies to variables inside this block. pthread_mutex_t info_head_mutex; char info_head_mutex_inited; // current playlist item pointer struct GroovePlaylistItem *info_head; double info_pos; // analyze_thread waits on this when the info queue is full pthread_cond_t drain_cond; char drain_cond_inited; // how many items are in the queue int info_queue_count; double album_peak; double track_duration; double album_duration; // set temporarily struct GroovePlaylistItem *purge_item; int abort_request; }; static int emit_track_info(struct GrooveLoudnessDetectorPrivate *d) { struct GrooveLoudnessDetectorInfo *info = av_mallocz(sizeof(struct GrooveLoudnessDetectorInfo)); if (!info) { av_log(NULL, AV_LOG_ERROR, "unable to allocate loudness detector info\n"); return -1; } info->item = d->info_head; info->duration = d->track_duration; ebur128_state *cur_track_state = d->all_track_states[d->cur_track_index]; if (!cur_track_state) { // we received the end before we expected it. This happens for example // when a DRM-protected song is played. In this situation, set duration to 0 // to indicate no song data. info->loudness = 0; info->peak = 0; } else { ebur128_loudness_global(cur_track_state, &info->loudness); ebur128_true_peak(cur_track_state, 0, &info->peak); double out; ebur128_true_peak(cur_track_state, 1, &out); if (out > info->peak) info->peak = out; if (info->peak > d->album_peak) d->album_peak = info->peak; } groove_queue_put(d->info_queue, info); return 0; } static int resize_state_history(struct GrooveLoudnessDetectorPrivate *d) { int new_size = d->state_history_count * 2; d->all_track_states = realloc(d->all_track_states, new_size * sizeof(ebur128_state *)); if (!d->all_track_states) { av_log(NULL, AV_LOG_ERROR, "unable to reallocate state pointer array\n"); return -1; } int zero_count = new_size - d->state_history_count; memset(d->all_track_states + d->state_history_count, 0, zero_count * sizeof(ebur128_state *)); d->state_history_count = new_size; return 0; } static void *detect_thread(void *arg) { struct GrooveLoudnessDetectorPrivate *d = arg; struct GrooveLoudnessDetector *detector = &d->externals; struct GrooveBuffer *buffer; while (!d->abort_request) { pthread_mutex_lock(&d->info_head_mutex); if (d->info_queue_count >= detector->info_queue_size) { pthread_cond_wait(&d->drain_cond, &d->info_head_mutex); pthread_mutex_unlock(&d->info_head_mutex); continue; } // we definitely want to unlock the mutex while we wait for the // next buffer. Otherwise there will be a deadlock when sink_flush or // sink_purge is called. pthread_mutex_unlock(&d->info_head_mutex); int result = groove_sink_buffer_get(d->sink, &buffer, 1); pthread_mutex_lock(&d->info_head_mutex); if (result == GROOVE_BUFFER_END) { // last file info emit_track_info(d); // send album info struct GrooveLoudnessDetectorInfo *info = av_mallocz( sizeof(struct GrooveLoudnessDetectorInfo)); if (info) { info->duration = d->album_duration; if (!detector->disable_album) { ebur128_loudness_global_multiple(d->all_track_states, d->cur_track_index + 1, &info->loudness); } info->peak = d->album_peak; groove_queue_put(d->info_queue, info); } else { av_log(NULL, AV_LOG_ERROR, "unable to allocate album loudness info\n"); } if (!detector->disable_album) { for (int i = 0; i <= d->cur_track_index; i += 1) { if (d->all_track_states[i]) ebur128_destroy(&d->all_track_states[i]); } d->cur_track_index = 0; } d->album_peak = 0.0; d->album_duration = 0.0; d->info_head = NULL; d->info_pos = -1.0; pthread_mutex_unlock(&d->info_head_mutex); continue; } if (result != GROOVE_BUFFER_YES) { pthread_mutex_unlock(&d->info_head_mutex); break; } if (buffer->item != d->info_head) { if (d->all_track_states[d->cur_track_index]) { emit_track_info(d); if (detector->disable_album) { ebur128_destroy(&d->all_track_states[d->cur_track_index]); } else { d->cur_track_index += 1; if (d->cur_track_index >= d->state_history_count) { av_log(NULL, AV_LOG_WARNING, "loudness scanner: resizing state history." " Unless you're loudness-scanning very large albums you might" " consider setting disable_album to 1.\n"); resize_state_history(d); } } } d->all_track_states[d->cur_track_index] = ebur128_init(2, 44100, EBUR128_MODE_TRUE_PEAK|EBUR128_MODE_I); if (!d->all_track_states[d->cur_track_index]) { av_log(NULL, AV_LOG_ERROR, "unable to allocate EBU R128 track context\n"); } d->track_duration = 0.0; d->info_head = buffer->item; d->info_pos = buffer->pos; } double buffer_duration = buffer->frame_count / (double)buffer->format.sample_rate; d->track_duration += buffer_duration; d->album_duration += buffer_duration; ebur128_add_frames_double(d->all_track_states[d->cur_track_index], (double*)buffer->data[0], buffer->frame_count); pthread_mutex_unlock(&d->info_head_mutex); groove_buffer_unref(buffer); } return NULL; } static void info_queue_cleanup(struct GrooveQueue* queue, void *obj) { struct GrooveLoudnessDetectorInfo *info = obj; struct GrooveLoudnessDetectorPrivate *d = queue->context; d->info_queue_count -= 1; av_free(info); } static void info_queue_put(struct GrooveQueue *queue, void *obj) { struct GrooveLoudnessDetectorPrivate *d = queue->context; d->info_queue_count += 1; } static void info_queue_get(struct GrooveQueue *queue, void *obj) { struct GrooveLoudnessDetectorPrivate *d = queue->context; struct GrooveLoudnessDetector *detector = &d->externals; d->info_queue_count -= 1; if (d->info_queue_count < detector->info_queue_size) pthread_cond_signal(&d->drain_cond); } static int info_queue_purge(struct GrooveQueue* queue, void *obj) { struct GrooveLoudnessDetectorInfo *info = obj; struct GrooveLoudnessDetectorPrivate *d = queue->context; return info->item == d->purge_item; } static void sink_purge(struct GrooveSink *sink, struct GroovePlaylistItem *item) { struct GrooveLoudnessDetectorPrivate *d = sink->userdata; pthread_mutex_lock(&d->info_head_mutex); d->purge_item = item; groove_queue_purge(d->info_queue); d->purge_item = NULL; if (d->info_head == item) { d->info_head = NULL; d->info_pos = -1.0; } pthread_cond_signal(&d->drain_cond); pthread_mutex_unlock(&d->info_head_mutex); } static void sink_flush(struct GrooveSink *sink) { struct GrooveLoudnessDetectorPrivate *d = sink->userdata; pthread_mutex_lock(&d->info_head_mutex); groove_queue_flush(d->info_queue); for (int i = 0; i <= d->cur_track_index; i += 1) { if (d->all_track_states[i]) ebur128_destroy(&d->all_track_states[i]); } d->cur_track_index = 0; d->track_duration = 0.0; d->info_head = NULL; d->info_pos = -1.0; pthread_cond_signal(&d->drain_cond); pthread_mutex_unlock(&d->info_head_mutex); } struct GrooveLoudnessDetector *groove_loudness_detector_create(void) { struct GrooveLoudnessDetectorPrivate *d = av_mallocz(sizeof(struct GrooveLoudnessDetectorPrivate)); if (!d) { av_log(NULL, AV_LOG_ERROR, "unable to allocate loudness detector\n"); return NULL; } struct GrooveLoudnessDetector *detector = &d->externals; if (pthread_mutex_init(&d->info_head_mutex, NULL) != 0) { groove_loudness_detector_destroy(detector); av_log(NULL, AV_LOG_ERROR, "unable to create mutex\n"); return NULL; } d->info_head_mutex_inited = 1; if (pthread_cond_init(&d->drain_cond, NULL) != 0) { groove_loudness_detector_destroy(detector); av_log(NULL, AV_LOG_ERROR, "unable to create mutex condition\n"); return NULL; } d->drain_cond_inited = 1; d->info_queue = groove_queue_create(); if (!d->info_queue) { groove_loudness_detector_destroy(detector); av_log(NULL, AV_LOG_ERROR, "unable to allocate queue\n"); return NULL; } d->info_queue->context = detector; d->info_queue->cleanup = info_queue_cleanup; d->info_queue->put = info_queue_put; d->info_queue->get = info_queue_get; d->info_queue->purge = info_queue_purge; d->sink = groove_sink_create(); if (!d->sink) { groove_loudness_detector_destroy(detector); av_log(NULL, AV_LOG_ERROR, "unable to allocate sink\n"); return NULL; } d->sink->audio_format.sample_rate = 44100; d->sink->audio_format.channel_layout = GROOVE_CH_LAYOUT_STEREO; d->sink->audio_format.sample_fmt = GROOVE_SAMPLE_FMT_DBL; d->sink->userdata = detector; d->sink->purge = sink_purge; d->sink->flush = sink_flush; // set some defaults detector->info_queue_size = INT_MAX; detector->sink_buffer_size = d->sink->buffer_size; return detector; } void groove_loudness_detector_destroy(struct GrooveLoudnessDetector *detector) { if (!detector) return; struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; if (d->sink) groove_sink_destroy(d->sink); if (d->info_queue) groove_queue_destroy(d->info_queue); if (d->info_head_mutex_inited) pthread_mutex_destroy(&d->info_head_mutex); if (d->drain_cond_inited) pthread_cond_destroy(&d->drain_cond); av_free(d); } int groove_loudness_detector_attach(struct GrooveLoudnessDetector *detector, struct GroovePlaylist *playlist) { struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; detector->playlist = playlist; groove_queue_reset(d->info_queue); // set the initial state history size. if we run out we will realloc later. d->state_history_count = detector->disable_album ? 1 : 128; d->all_track_states = calloc(d->state_history_count, sizeof(ebur128_state*)); d->cur_track_index = 0; if (!d->all_track_states) { groove_loudness_detector_detach(detector); av_log(NULL, AV_LOG_ERROR, "unable to allocate ebur128 track state pointers\n"); return -1; } if (groove_sink_attach(d->sink, playlist) < 0) { groove_loudness_detector_detach(detector); av_log(NULL, AV_LOG_ERROR, "unable to attach sink\n"); return -1; } if (pthread_create(&d->thread_id, NULL, detect_thread, detector) != 0) { groove_loudness_detector_detach(detector); av_log(NULL, AV_LOG_ERROR, "unable to create detector thread\n"); return -1; } return 0; } int groove_loudness_detector_detach(struct GrooveLoudnessDetector *detector) { struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; d->abort_request = 1; groove_sink_detach(d->sink); groove_queue_flush(d->info_queue); groove_queue_abort(d->info_queue); pthread_cond_signal(&d->drain_cond); pthread_join(d->thread_id, NULL); detector->playlist = NULL; if (d->all_track_states) { for (int i = 0; i <= d->cur_track_index; i += 1) { if (d->all_track_states[i]) ebur128_destroy(&d->all_track_states[i]); } free(d->all_track_states); d->all_track_states = NULL; } d->cur_track_index = 0; d->abort_request = 0; d->info_head = NULL; d->info_pos = 0; d->track_duration = 0.0; return 0; } int groove_loudness_detector_info_get(struct GrooveLoudnessDetector *detector, struct GrooveLoudnessDetectorInfo *info, int block) { struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; struct GrooveLoudnessDetectorInfo *info_ptr; if (groove_queue_get(d->info_queue, (void**)&info_ptr, block) == 1) { *info = *info_ptr; av_free(info_ptr); return 1; } return 0; } int groove_loudness_detector_info_peek(struct GrooveLoudnessDetector *detector, int block) { struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; return groove_queue_peek(d->info_queue, block); } void groove_loudness_detector_position(struct GrooveLoudnessDetector *detector, struct GroovePlaylistItem **item, double *seconds) { struct GrooveLoudnessDetectorPrivate *d = (struct GrooveLoudnessDetectorPrivate *) detector; pthread_mutex_lock(&d->info_head_mutex); if (item) *item = d->info_head; if (seconds) *seconds = d->info_pos; pthread_mutex_unlock(&d->info_head_mutex); } libgroove-4.3.0/grooveloudness/loudness.h000066400000000000000000000065131253074642100206020ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_LOUDNESS_H_INCLUDED #define GROOVE_LOUDNESS_H_INCLUDED #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ struct GrooveLoudnessDetectorInfo { /* loudness is in LUFS. 1 LUFS == 1 dB * EBU R128 specifies that playback should target -23 LUFS. replaygain on * the other hand is a suggestion of how many dB to adjust the gain so * that it equals -18 dB. * so, for playback you might adjust the gain so that it is equal to -18 dB * (this would be the replaygain standard) or so that it is equal to -23 dB * (this would be the EBU R128 standard). */ double loudness; /* peak amplitude in float format */ double peak; /* how many seconds long this song is */ double duration; /* if item is NULL, this info applies to all songs analyzed until * this point. otherwise it is the playlist item that this info * applies to. * when disable_album is set, this sentinel is still sent, but loudness * will be set to 0 */ struct GroovePlaylistItem *item; }; struct GrooveLoudnessDetector { /* maximum number of GrooveLoudnessDetectorInfo items to store in this * loudness detector's queue. this defaults to MAX_INT, meaning that * the loudness detector will cause the decoder to decode the entire * playlist. if you want to instead, for example, obtain loudness info * at the same time as playback, you might set this value to 1. */ int info_queue_size; /* how big the sink buffer should be, in sample frames. * groove_loudness_detector_create defaults this to 8192 */ int sink_buffer_size; /* set to 1 to only compute track loudness. This is faster and requires * less memory than computing both. */ int disable_album; /* read-only. set when attached and cleared when detached */ struct GroovePlaylist *playlist; }; struct GrooveLoudnessDetector *groove_loudness_detector_create(void); void groove_loudness_detector_destroy(struct GrooveLoudnessDetector *detector); /* once you attach, you must detach before destroying the playlist */ int groove_loudness_detector_attach(struct GrooveLoudnessDetector *detector, struct GroovePlaylist *playlist); int groove_loudness_detector_detach(struct GrooveLoudnessDetector *detector); /* returns < 0 on error, 0 on aborted (block=1) or no info ready (block=0), * 1 on info returned */ int groove_loudness_detector_info_get(struct GrooveLoudnessDetector *detector, struct GrooveLoudnessDetectorInfo *info, int block); /* returns < 0 on error, 0 on no info ready, 1 on info ready * if block is 1, block until info is ready */ int groove_loudness_detector_info_peek(struct GrooveLoudnessDetector *detector, int block); /* get the position of the detect head * both the current playlist item and the position in seconds in the playlist * item are given. item will be set to NULL if the playlist is empty * you may pass NULL for item or seconds */ void groove_loudness_detector_position(struct GrooveLoudnessDetector *detector, struct GroovePlaylistItem **item, double *seconds); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* GROOVE_LOUDNESS_H_INCLUDED */ libgroove-4.3.0/grooveplayer/000077500000000000000000000000001253074642100162305ustar00rootroot00000000000000libgroove-4.3.0/grooveplayer/osx_time_shim.c000066400000000000000000000012661253074642100212500ustar00rootroot00000000000000/* * Copyright (c) 2014 K. Ernest "iFire" Lee * Copyright (c) 2014 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "osx_time_shim.h" #include int clock_gettime(clockid_t clk_id, struct timespec *tp) { clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), clk_id, &cclock); kern_return_t retval = clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); tp->tv_sec = mts.tv_sec; tp->tv_nsec = mts.tv_nsec; return retval; } int pthread_condattr_setclock(pthread_condattr_t *attr, int foo) { return 0; } libgroove-4.3.0/grooveplayer/osx_time_shim.h000066400000000000000000000013431253074642100212510ustar00rootroot00000000000000/* * Copyright (c) 2014 K. Ernest "iFire" Lee * Copyright (c) 2014 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_MACH_TIME_H_INCLUDED #define GROOVE_MACH_TIME_H_INCLUDED #ifdef __APPLE__ #include #include #include #include #include #define CLOCK_MONOTONIC SYSTEM_CLOCK typedef int clockid_t; /* the mach kernel uses struct mach_timespec, so struct timespec is loaded from for compatability */ int clock_gettime(clockid_t clk_id, struct timespec *tp); int pthread_condattr_setclock(pthread_condattr_t *attr, int foo); #endif #endif libgroove-4.3.0/grooveplayer/player.c000066400000000000000000000560041253074642100176750ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include "player.h" #include #include #include #include #include #include #include #include #include "osx_time_shim.h" struct GroovePlayerPrivate { struct GroovePlayer externals; struct GrooveBuffer *audio_buf; size_t audio_buf_size; // in frames size_t audio_buf_index; // in frames int channel_count; int bytes_per_sample; int bytes_per_frame; // this mutex applies to the variables in this block pthread_mutex_t play_head_mutex; bool play_head_mutex_inited; // pointer to current item where the buffered audio is reaching the device struct GroovePlaylistItem *play_head; // number of seconds into the play_head song where the buffered audio // is reaching the device double play_pos; SDL_AudioDeviceID device_id; struct GrooveSink *sink; struct GrooveQueue *eventq; // for dummy player pthread_t dummy_thread_id; bool dummy_thread_inited; bool abort_request; uint64_t start_nanos; uint64_t frames_consumed; pthread_cond_t pause_cond; pthread_condattr_t cond_attr; bool pause_cond_inited; int paused; // watchdog thread for opening and closing audio device pthread_t device_thread_id; int device_thread_inited; pthread_cond_t device_thread_cond; bool device_thread_cond_inited; int silence_bytes_left; bool request_device_reopen; struct GrooveAudioFormat device_format; int device_buffer_size; }; static int open_audio_device(struct GroovePlayer *player, const struct GrooveAudioFormat *target_format, struct GrooveAudioFormat *actual_format, int use_exact_audio_format); static Uint16 groove_fmt_to_sdl_fmt(enum GrooveSampleFormat fmt) { switch (fmt) { case GROOVE_SAMPLE_FMT_U8: case GROOVE_SAMPLE_FMT_U8P: return AUDIO_U8; case GROOVE_SAMPLE_FMT_S16: case GROOVE_SAMPLE_FMT_S16P: return AUDIO_S16SYS; case GROOVE_SAMPLE_FMT_S32: case GROOVE_SAMPLE_FMT_S32P: return AUDIO_S32SYS; case GROOVE_SAMPLE_FMT_FLT: case GROOVE_SAMPLE_FMT_FLTP: return AUDIO_F32SYS; default: av_log(NULL, AV_LOG_ERROR, "unable to use selected format. using GROOVE_SAMPLE_FMT_S16 instead.\n"); return AUDIO_S16SYS; } } static enum GrooveSampleFormat sdl_fmt_to_groove_fmt(Uint16 sdl_format) { switch (sdl_format) { case AUDIO_U8: return GROOVE_SAMPLE_FMT_U8; case AUDIO_S16SYS: return GROOVE_SAMPLE_FMT_S16; case AUDIO_S32SYS: return GROOVE_SAMPLE_FMT_S32; case AUDIO_F32SYS: return GROOVE_SAMPLE_FMT_FLT; default: return GROOVE_SAMPLE_FMT_NONE; } } static void emit_event(struct GrooveQueue *queue, enum GroovePlayerEventType type) { union GroovePlayerEvent *evt = av_malloc(sizeof(union GroovePlayerEvent)); if (!evt) { av_log(NULL, AV_LOG_ERROR, "unable to create event: out of memory\n"); return; } evt->type = type; if (groove_queue_put(queue, evt) < 0) av_log(NULL, AV_LOG_ERROR, "unable to put event on queue: out of memory\n"); } static uint64_t now_nanos(void) { struct timespec tms; clock_gettime(CLOCK_MONOTONIC, &tms); uint64_t tv_sec = tms.tv_sec; uint64_t sec_mult = 1000000000; uint64_t tv_nsec = tms.tv_nsec; return tv_sec * sec_mult + tv_nsec; } static void close_audio_device(struct GroovePlayerPrivate *p) { if (p->device_id > 0) { SDL_CloseAudioDevice(p->device_id); p->device_id = 0; } } static void *device_thread_run(void *arg) { struct GroovePlayer *player = arg; struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; for (;;) { if (p->abort_request) break; pthread_mutex_lock(&p->play_head_mutex); if (!p->request_device_reopen) { pthread_cond_wait(&p->device_thread_cond, &p->play_head_mutex); pthread_mutex_unlock(&p->play_head_mutex); continue; } p->request_device_reopen = false; close_audio_device(p); p->device_format = p->audio_buf->format; open_audio_device(player, &p->audio_buf->format, NULL, player->use_exact_audio_format); SDL_PauseAudioDevice(p->device_id, 0); emit_event(p->eventq, GROOVE_EVENT_DEVICEREOPENED); pthread_mutex_unlock(&p->play_head_mutex); } return NULL; } // this thread is started if the user selects a dummy device instead of a // real device. static void *dummy_thread(void *arg) { struct GroovePlayer *player = arg; struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; while (!p->abort_request) { pthread_mutex_lock(&p->play_head_mutex); if (p->paused) { pthread_cond_wait(&p->pause_cond, &p->play_head_mutex); pthread_mutex_unlock(&p->play_head_mutex); continue; } uint64_t now = now_nanos(); int more = 1; while (more) { more = 0; if (!p->audio_buf || p->audio_buf_index >= p->audio_buf->frame_count) { groove_buffer_unref(p->audio_buf); p->audio_buf_index = 0; p->audio_buf_size = 0; int ret = groove_sink_buffer_get(p->sink, &p->audio_buf, 0); if (ret == GROOVE_BUFFER_END) { emit_event(p->eventq, GROOVE_EVENT_NOWPLAYING); p->play_head = NULL; p->play_pos = -1.0; } else if (ret == GROOVE_BUFFER_YES) { if (p->play_head != p->audio_buf->item) emit_event(p->eventq, GROOVE_EVENT_NOWPLAYING); p->play_head = p->audio_buf->item; p->play_pos = p->audio_buf->pos; p->audio_buf_size = p->audio_buf->size; } else { // since this is a dummy player whose only job is to keep // track of time, we're going to pretend that we did *not* // just get a buffer underrun. Instead we'll wait patiently // for the next buffer to appear and handle it appropriately. pthread_mutex_unlock(&p->play_head_mutex); break; } } if (p->audio_buf) { uint64_t nanos_per_frame = 1000000000 / p->audio_buf->format.sample_rate; uint64_t total_nanos = now - p->start_nanos; uint64_t total_frames = total_nanos / nanos_per_frame; int frames_to_kill = total_frames - p->frames_consumed; int new_index = p->audio_buf_index + frames_to_kill; if (new_index > p->audio_buf->frame_count) { more = 1; new_index = p->audio_buf->frame_count; frames_to_kill = new_index - p->audio_buf_index; } p->frames_consumed += frames_to_kill; p->audio_buf_index = new_index; p->play_pos += frames_to_kill / (double) p->audio_buf->format.sample_rate; } } // sleep for a little while struct timespec tms; clock_gettime(CLOCK_MONOTONIC, &tms); tms.tv_nsec += 10000000; pthread_cond_timedwait(&p->pause_cond, &p->play_head_mutex, &tms); pthread_mutex_unlock(&p->play_head_mutex); } return NULL; } static bool is_planar(enum GrooveSampleFormat fmt) { switch (fmt) { default: return false; case GROOVE_SAMPLE_FMT_U8P: case GROOVE_SAMPLE_FMT_S16P: case GROOVE_SAMPLE_FMT_S32P: case GROOVE_SAMPLE_FMT_FLTP: case GROOVE_SAMPLE_FMT_DBLP: return true; } } static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) { struct GroovePlayerPrivate *p = opaque; struct GrooveSink *sink = p->sink; struct GroovePlaylist *playlist = sink->playlist; double bytes_per_sec = sink->bytes_per_sec; int paused = !groove_playlist_playing(playlist); pthread_mutex_lock(&p->play_head_mutex); while (len > 0) { bool waiting_for_silence = (p->silence_bytes_left > 0); if (!p->request_device_reopen && !waiting_for_silence && !paused && p->audio_buf_index >= p->audio_buf_size) { groove_buffer_unref(p->audio_buf); p->audio_buf_index = 0; p->audio_buf_size = 0; int ret = groove_sink_buffer_get(p->sink, &p->audio_buf, 0); if (ret == GROOVE_BUFFER_END) { emit_event(p->eventq, GROOVE_EVENT_NOWPLAYING); p->play_head = NULL; p->play_pos = -1.0; } else if (ret == GROOVE_BUFFER_YES) { if (p->play_head != p->audio_buf->item) emit_event(p->eventq, GROOVE_EVENT_NOWPLAYING); p->play_head = p->audio_buf->item; p->play_pos = p->audio_buf->pos; p->audio_buf_size = p->audio_buf->frame_count; p->channel_count = groove_channel_layout_count(p->audio_buf->format.channel_layout); p->bytes_per_sample = groove_sample_format_bytes_per_sample(p->audio_buf->format.sample_fmt); p->bytes_per_frame = p->bytes_per_sample * p->channel_count; if (p->device_thread_inited && !groove_audio_formats_equal(&p->audio_buf->format, &p->device_format)) { p->silence_bytes_left = p->device_buffer_size; waiting_for_silence = true; } } else { // errors are treated the same as no buffer ready emit_event(p->eventq, GROOVE_EVENT_BUFFERUNDERRUN); } } if (p->request_device_reopen || waiting_for_silence || paused || !p->audio_buf) { // fill with silence memset(stream, 0, len); if (waiting_for_silence) { p->silence_bytes_left -= len; if (p->silence_bytes_left <= 0) { p->request_device_reopen = true; pthread_cond_signal(&p->device_thread_cond); } } break; } size_t read_frame_count = p->audio_buf_size - p->audio_buf_index; int write_frame_count = len / p->bytes_per_frame; int frame_count = (read_frame_count < write_frame_count) ? read_frame_count : write_frame_count; int bytes_consumed = frame_count * p->bytes_per_frame; if (is_planar(p->audio_buf->format.sample_fmt)) { int end_frame = p->audio_buf_index + frame_count; for (; p->audio_buf_index < end_frame; p->audio_buf_index += 1) { for (int ch = 0; ch < p->channel_count; ch += 1) { memcpy(stream, &p->audio_buf->data[ch][p->audio_buf_index * p->bytes_per_sample], p->bytes_per_sample); stream += p->bytes_per_sample; } } } else { memcpy(stream, p->audio_buf->data[0] + p->audio_buf_index * p->bytes_per_frame, bytes_consumed); stream += bytes_consumed; p->audio_buf_index += frame_count; } len -= bytes_consumed; p->play_pos += bytes_consumed / bytes_per_sec; } pthread_mutex_unlock(&p->play_head_mutex); } static void sink_purge(struct GrooveSink *sink, struct GroovePlaylistItem *item) { struct GroovePlayerPrivate *p = sink->userdata; pthread_mutex_lock(&p->play_head_mutex); if (p->play_head == item) { p->play_head = NULL; p->play_pos = -1.0; groove_buffer_unref(p->audio_buf); p->audio_buf = NULL; p->audio_buf_index = 0; p->audio_buf_size = 0; p->start_nanos = now_nanos(); p->frames_consumed = 0; emit_event(p->eventq, GROOVE_EVENT_NOWPLAYING); } pthread_mutex_unlock(&p->play_head_mutex); } static void sink_pause(struct GrooveSink *sink) { struct GroovePlayer *player = sink->userdata; // only the dummy device needs to handle pausing if (player->device_index != GROOVE_PLAYER_DUMMY_DEVICE) return; struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; // mark the position in time that we paused at. // no mutex needed for simple boolean flag p->paused = 1; } static void sink_play(struct GrooveSink *sink) { struct GroovePlayer *player = sink->userdata; // only the dummy device needs to handle playing if (player->device_index != GROOVE_PLAYER_DUMMY_DEVICE) return; struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; // mark the position in time that we started playing at. pthread_mutex_lock(&p->play_head_mutex); p->start_nanos = now_nanos(); p->frames_consumed = 0; p->paused = 0; pthread_cond_signal(&p->pause_cond); pthread_mutex_unlock(&p->play_head_mutex); } static void sink_flush(struct GrooveSink *sink) { struct GroovePlayerPrivate *p = sink->userdata; pthread_mutex_lock(&p->play_head_mutex); groove_buffer_unref(p->audio_buf); p->audio_buf = NULL; p->audio_buf_index = 0; p->audio_buf_size = 0; p->start_nanos = now_nanos(); p->frames_consumed = 0; p->play_pos = -1.0; p->play_head = NULL; pthread_mutex_unlock(&p->play_head_mutex); } struct GroovePlayer *groove_player_create(void) { struct GroovePlayerPrivate *p = av_mallocz(sizeof(struct GroovePlayerPrivate)); if (!p) { av_log(NULL, AV_LOG_ERROR, "unable to create player: out of memory\n"); return NULL; } if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { av_free(p); av_log(NULL, AV_LOG_ERROR, "unable to init SDL audio subsystem: %s\n", SDL_GetError()); return NULL; } struct GroovePlayer *player = &p->externals; p->sink = groove_sink_create(); if (!p->sink) { groove_player_destroy(player); av_log(NULL, AV_LOG_ERROR,"unable to create sink: out of memory\n"); return NULL; } p->sink->userdata = player; p->sink->purge = sink_purge; p->sink->flush = sink_flush; p->sink->pause = sink_pause; p->sink->play = sink_play; if (pthread_mutex_init(&p->play_head_mutex, NULL) != 0) { groove_player_destroy(player); av_log(NULL, AV_LOG_ERROR,"unable to create play head mutex: out of memory\n"); return NULL; } p->play_head_mutex_inited = true; pthread_condattr_init(&p->cond_attr); pthread_condattr_setclock(&p->cond_attr, CLOCK_MONOTONIC); if (pthread_cond_init(&p->pause_cond, &p->cond_attr) != 0) { groove_player_destroy(player); av_log(NULL, AV_LOG_ERROR, "unable to create mutex condition\n"); return NULL; } p->pause_cond_inited = true; p->eventq = groove_queue_create(); if (!p->eventq) { groove_player_destroy(player); av_log(NULL, AV_LOG_ERROR,"unable to create event queue: out of memory\n"); return NULL; } if (pthread_cond_init(&p->device_thread_cond, NULL) != 0) { groove_player_destroy(player); av_log(NULL, AV_LOG_ERROR, "unable to create mutex condition\n"); return NULL; } p->device_thread_cond_inited = true; // set some nice defaults player->target_audio_format.sample_rate = 44100; player->target_audio_format.channel_layout = GROOVE_CH_LAYOUT_STEREO; player->target_audio_format.sample_fmt = GROOVE_SAMPLE_FMT_S16; // small because there is no way to clear the buffer. player->device_buffer_size = 1024; player->sink_buffer_size = 8192; player->gain = p->sink->gain; player->device_index = -1; // default device return player; } void groove_player_destroy(struct GroovePlayer *player) { if (!player) return; SDL_QuitSubSystem(SDL_INIT_AUDIO); struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; if (p->device_thread_cond_inited) pthread_cond_destroy(&p->device_thread_cond); if (p->play_head_mutex_inited) pthread_mutex_destroy(&p->play_head_mutex); if (p->pause_cond_inited) pthread_cond_destroy(&p->pause_cond); if (p->eventq) groove_queue_destroy(p->eventq); groove_sink_destroy(p->sink); av_free(p); } static int open_audio_device(struct GroovePlayer *player, const struct GrooveAudioFormat *target_format, struct GrooveAudioFormat *actual_format, int use_exact_audio_format) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; SDL_AudioSpec wanted_spec, spec; wanted_spec.format = groove_fmt_to_sdl_fmt(target_format->sample_fmt); wanted_spec.freq = target_format->sample_rate; wanted_spec.channels = groove_channel_layout_count(target_format->channel_layout); wanted_spec.samples = p->device_buffer_size; wanted_spec.callback = sdl_audio_callback; wanted_spec.userdata = player; const char* device_name = NULL; if (player->device_index >= 0) device_name = SDL_GetAudioDeviceName(player->device_index, 0); int allowed_changes = use_exact_audio_format ? 0 : SDL_AUDIO_ALLOW_ANY_CHANGE; p->device_id = SDL_OpenAudioDevice(device_name, 0, &wanted_spec, &spec, allowed_changes); if (p->device_id == 0) { av_log(NULL, AV_LOG_ERROR, "unable to open audio device: %s\n", SDL_GetError()); return -1; } if (actual_format) { actual_format->sample_rate = spec.freq; actual_format->channel_layout = groove_channel_layout_default(spec.channels); actual_format->sample_fmt = sdl_fmt_to_groove_fmt(spec.format); } return 0; } int groove_player_attach(struct GroovePlayer *player, struct GroovePlaylist *playlist) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; p->device_buffer_size = player->device_buffer_size; p->sink->gain = player->gain; p->sink->buffer_size = player->sink_buffer_size; if (player->device_index == GROOVE_PLAYER_DUMMY_DEVICE) { // dummy device player->actual_audio_format = player->target_audio_format; p->sink->audio_format = player->actual_audio_format; p->sink->disable_resample = 1; } else { if (open_audio_device(player, &player->target_audio_format, &player->actual_audio_format, player->use_exact_audio_format)) { return -1; } // based on spec that we got, attach a sink with those properties p->sink->audio_format = player->actual_audio_format; if (p->sink->audio_format.sample_fmt == GROOVE_SAMPLE_FMT_NONE) { groove_player_detach(player); av_log(NULL, AV_LOG_ERROR, "unsupported audio device sample format\n"); return -1; } if (player->use_exact_audio_format) { p->sink->disable_resample = 1; if (pthread_create(&p->device_thread_id, NULL, device_thread_run, player) != 0) { groove_player_detach(player); av_log(NULL, AV_LOG_ERROR, "unable to create device thread\n"); return -1; } p->device_thread_inited = true; } } int err = groove_sink_attach(p->sink, playlist); if (err < 0) { groove_player_detach(player); av_log(NULL, AV_LOG_ERROR, "unable to attach sink\n"); return err; } p->play_pos = -1.0; groove_queue_reset(p->eventq); if (player->device_index == GROOVE_PLAYER_DUMMY_DEVICE) { if (groove_playlist_playing(playlist)) sink_play(p->sink); else sink_pause(p->sink); // set up thread to keep track of time if (pthread_create(&p->dummy_thread_id, NULL, dummy_thread, player) != 0) { groove_player_detach(player); av_log(NULL, AV_LOG_ERROR, "unable to create dummy player thread\n"); return -1; } p->dummy_thread_inited = true; } else { SDL_PauseAudioDevice(p->device_id, 0); } return 0; } int groove_player_detach(struct GroovePlayer *player) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; p->abort_request = true; if (p->device_thread_inited) { pthread_mutex_lock(&p->play_head_mutex); pthread_cond_signal(&p->device_thread_cond); pthread_mutex_unlock(&p->play_head_mutex); pthread_join(p->device_thread_id, NULL); p->device_thread_inited = false; } if (p->eventq) { groove_queue_flush(p->eventq); groove_queue_abort(p->eventq); } if (p->sink->playlist) { groove_sink_detach(p->sink); } close_audio_device(p); if (p->dummy_thread_inited) { pthread_mutex_lock(&p->play_head_mutex); pthread_cond_signal(&p->pause_cond); pthread_mutex_unlock(&p->play_head_mutex); pthread_join(p->dummy_thread_id, NULL); p->dummy_thread_inited = false; } player->playlist = NULL; groove_buffer_unref(p->audio_buf); p->audio_buf = NULL; p->request_device_reopen = false; p->silence_bytes_left = 0; p->abort_request = false; return 0; } int groove_device_count(void) { return SDL_GetNumAudioDevices(0); } const char * groove_device_name(int index) { return SDL_GetAudioDeviceName(index, 0); } void groove_player_position(struct GroovePlayer *player, struct GroovePlaylistItem **item, double *seconds) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; pthread_mutex_lock(&p->play_head_mutex); if (item) *item = p->play_head; if (seconds) *seconds = p->play_pos; pthread_mutex_unlock(&p->play_head_mutex); } int groove_player_event_get(struct GroovePlayer *player, union GroovePlayerEvent *event, int block) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; union GroovePlayerEvent *tmp; int err = groove_queue_get(p->eventq, (void **)&tmp, block); if (err > 0) { *event = *tmp; av_free(tmp); } return err; } int groove_player_event_peek(struct GroovePlayer *player, int block) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; return groove_queue_peek(p->eventq, block); } int groove_player_set_gain(struct GroovePlayer *player, double gain) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; player->gain = gain; return groove_sink_set_gain(p->sink, gain); } struct GrooveAudioFormat groove_player_get_device_audio_format(struct GroovePlayer *player) { struct GroovePlayerPrivate *p = (struct GroovePlayerPrivate *) player; struct GrooveAudioFormat result; pthread_mutex_lock(&p->play_head_mutex); result = p->device_format; pthread_mutex_unlock(&p->play_head_mutex); return result; } libgroove-4.3.0/grooveplayer/player.h000066400000000000000000000130061253074642100176750ustar00rootroot00000000000000/* * Copyright (c) 2013 Andrew Kelley * * This file is part of libgroove, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #ifndef GROOVE_PLAYER_H_INCLUDED #define GROOVE_PLAYER_H_INCLUDED #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* use this to make a playlist utilize your speakers */ enum GroovePlayerEventType { /* when the currently playing track changes. */ GROOVE_EVENT_NOWPLAYING, /* when something tries to read from an empty buffer */ GROOVE_EVENT_BUFFERUNDERRUN, /* when the audio device is re-opened due to audio format changing*/ GROOVE_EVENT_DEVICEREOPENED }; union GroovePlayerEvent { enum GroovePlayerEventType type; }; #define GROOVE_PLAYER_DEFAULT_DEVICE (-1) #define GROOVE_PLAYER_DUMMY_DEVICE (-2) struct GroovePlayer { /* set this to the device you want to open * could also be GROOVE_PLAYER_DEFAULT_DEVICE or GROOVE_PLAYER_DUMMY_DEVICE */ int device_index; /* The desired audio format settings with which to open the device. * groove_player_create defaults these to 44100 Hz, * signed 16-bit int, stereo. * These are preferences; if a setting cannot be used, a substitute will be * used instead. actual_audio_format is set to the actual values. */ struct GrooveAudioFormat target_audio_format; /* how big the device buffer should be, in sample frames. * must be a power of 2. * groove_player_create defaults this to 1024 */ int device_buffer_size; /* how big the sink buffer should be, in sample frames. * groove_player_create defaults this to 8192 */ int sink_buffer_size; /* This volume adjustment to make to this player. * It is recommended that you leave this at 1.0 and instead adjust the * gain of the underlying playlist. * If you want to change this value after you have already attached the * sink to the playlist, you must use groove_player_set_gain. * float format. Defaults to 1.0 */ double gain; /* read-only. set when you call groove_player_attach and cleared when * you call groove_player_detach */ struct GroovePlaylist *playlist; /* read-only. set to the actual format you get when you open the device. * ideally will be the same as target_audio_format but might not be. */ struct GrooveAudioFormat actual_audio_format; /* If you set this to 1, target_audio_format and actual_audio_format are * ignored and no resampling, channel layout remapping, or sample format * conversion will occur. The audio device will be reopened with exact * parameters whenever necessary. */ int use_exact_audio_format; }; /* Returns the number of available devices exposed by the current driver or -1 * if an explicit list of devices can't be determined. A return value of -1 * does not necessarily mean an error condition. * In many common cases, when this function returns a value <= 0, it can still * successfully open the default device (NULL for the device name) * This function may trigger a complete redetect of available hardware. It * should not be called for each iteration of a loop, but rather once at the * start of a loop. */ int groove_device_count(void); /* Returns the name of the audio device at the requested index, or NULL on error * The string returned by this function is UTF-8 encoded, read-only, and * managed internally. You are not to free it. If you need to keep the string * for any length of time, you should make your own copy of it. */ const char *groove_device_name(int index); struct GroovePlayer *groove_player_create(void); void groove_player_destroy(struct GroovePlayer *player); /* Attaches the player to the playlist instance and opens the device to * begin playback. * Internally this creates a GrooveSink and sends the samples to the device. * you must detach a player before destroying it or the playlist it is * attached to * returns 0 on success, < 0 on error */ int groove_player_attach(struct GroovePlayer *player, struct GroovePlaylist *playlist); /* returns 0 on success, < 0 on error */ int groove_player_detach(struct GroovePlayer *player); /* get the position of the play head * both the current playlist item and the position in seconds in the playlist * item are given. item will be set to NULL if the playlist is empty * you may pass NULL for item or seconds */ void groove_player_position(struct GroovePlayer *player, struct GroovePlaylistItem **item, double *seconds); /* returns < 0 on error, 0 on no event ready, 1 on got event */ int groove_player_event_get(struct GroovePlayer *player, union GroovePlayerEvent *event, int block); /* returns < 0 on error, 0 on no event ready, 1 on event ready * if block is 1, block until event is ready */ int groove_player_event_peek(struct GroovePlayer *player, int block); /* See the gain property of GrooveSink. It is recommended that you leave this * at 1.0 and instead adjust the gain of the playlist. * returns 0 on success, < 0 on error */ int groove_player_set_gain(struct GroovePlayer *player, double gain); /* When you set the use_exact_audio_format field to 1, the audio device is * closed and re-opened as necessary. When this happens, a * GROOVE_EVENT_DEVICEREOPENED event is emitted, and you can use this function * to discover the audio format of the device. */ struct GrooveAudioFormat groove_player_get_device_audio_format(struct GroovePlayer *player); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* GROOVE_PLAYER_H_INCLUDED */