From c9e23f4e29f08fc53b2306311fa70b1cf4b78b0c Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Sun, 1 Feb 2009 13:35:50 +0100 Subject: mp3 playback support Now libfaad and/or libmad are used for playback. There's currently no remaining time displayed for mp3 playback. --- src/CMakeLists.txt | 28 +++++-- src/FindMad.cmake | 35 +++++++++ src/config.h.in | 10 +++ src/main.c | 5 +- src/player.c | 224 +++++++++++++++++++++++++++++++++++++++++++++-------- src/player.h | 45 +++++++++-- src/settings.c | 15 ++++ src/settings.h | 3 +- src/ui_act.c | 14 ++-- 9 files changed, 324 insertions(+), 55 deletions(-) create mode 100644 src/FindMad.cmake (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0274af0..c7f6998 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,20 +1,38 @@ set (CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +set (CMAKE_C_FLAGS -Wall) + find_package (LibXml2 REQUIRED) find_package (CURL REQUIRED) -find_package (Faad REQUIRED) find_package (Readline REQUIRED) find_package (LibAo REQUIRED) -find_package (Threads REQUIRED) -find_library (LIBM m) +# find threading implementation +find_package (Threads REQUIRED) if (NOT CMAKE_USE_PTHREADS_INIT) message (FATAL_ERROR "pthread is currently required") endif (NOT CMAKE_USE_PTHREADS_INIT) +# check for libm +find_library (LIBM m) if (NOT LIBM) message (FATAL_ERROR "libm is required") endif (NOT LIBM) +# check for audio decoding library +find_package (Faad) +find_package (Mad) +if (NOT FAAD_FOUND AND NOT MAD_FOUND) + message (FATAL_ERROR "libmad and/or libfaad are required.") +endif (NOT FAAD_FOUND AND NOT MAD_FOUND) +if (FAAD_FOUND) + message (STATUS "Found libfaad, enabling aac decoding") + set (ENABLE_FAAD 1) +endif (FAAD_FOUND) +if (MAD_FOUND) + message (STATUS "Found libmad, enabling mp3 decoding") + set (ENABLE_MAD 1) +endif (MAD_FOUND) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) @@ -23,12 +41,12 @@ include_directories ( ${pianobar_SOURCE_DIR}/libwardrobe/src ${LIBXML2_INCLUDE_DIR} ${CURL_INCLUDE_DIRS} ${READLINE_INCLUDE_DIR} ${FAAD_INCLUDE_DIRS} - ${LIBAO_INCLUDE_DIRS}) + ${LIBAO_INCLUDE_DIRS} ${MAD_INCLUDE_DIRS}) add_executable (pianobar main.c terminal.c settings.c player.c ui.c ui_act.c) target_link_libraries (pianobar piano wardrobe ${CURL_LIBRARIES} ${LIBXML2_LIBRARIES} ${READLINE_LIBRARY} ${FAAD_LIBRARY} ${LIBAO_LIBRARY} - ${CMAKE_THREAD_LIBS_INIT} ${LIBM}) + ${CMAKE_THREAD_LIBS_INIT} ${MAD_LIBRARIES} ${LIBM}) install (TARGETS pianobar RUNTIME DESTINATION bin) install (FILES pianobar.1 DESTINATION share/man/man1) diff --git a/src/FindMad.cmake b/src/FindMad.cmake new file mode 100644 index 0000000..f05f469 --- /dev/null +++ b/src/FindMad.cmake @@ -0,0 +1,35 @@ +# MAD_INCLUDE_DIRS - where to find curl/curl.h, etc. +# MAD_LIBRARIES - List of libraries when using curl. +# MAD_FOUND - True if curl found. + +# Look for the header file. +FIND_PATH(MAD_INCLUDE_DIR NAMES mad.h) +MARK_AS_ADVANCED(MAD_INCLUDE_DIR) + +# Look for the library. +FIND_LIBRARY(MAD_LIBRARY NAMES mad) +MARK_AS_ADVANCED(MAD_LIBRARY) + +# Copy the results to the output variables. +IF(MAD_INCLUDE_DIR AND MAD_LIBRARY) + SET(MAD_FOUND 1) + SET(MAD_LIBRARIES ${MAD_LIBRARY}) + SET(MAD_INCLUDE_DIRS ${MAD_INCLUDE_DIR}) +ELSE(MAD_INCLUDE_DIR AND MAD_LIBRARY) + SET(MAD_FOUND 0) + SET(MAD_LIBRARIES) + SET(MAD_INCLUDE_DIRS) +ENDIF(MAD_INCLUDE_DIR AND MAD_LIBRARY) + +# Report the results. +IF(NOT MAD_FOUND) + SET(MAD_DIR_MESSAGE + "libmad was not found. Make sure MAD_LIBRARY and MAD_INCLUDE_DIR are set.") + IF(NOT MAD_FIND_QUIETLY) + MESSAGE(STATUS "${MAD_DIR_MESSAGE}") + ELSE(NOT MAD_FIND_QUIETLY) + IF(MAD_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "${MAD_DIR_MESSAGE}") + ENDIF(MAD_FIND_REQUIRED) + ENDIF(NOT MAD_FIND_QUIETLY) +ENDIF(NOT MAD_FOUND) diff --git a/src/config.h.in b/src/config.h.in index 60600a7..a75a43f 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1 +1,11 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +/* package name */ #define PACKAGE "${PACKAGE}" + +/* used libraries */ +#cmakedefine ENABLE_FAAD +#cmakedefine ENABLE_MAD + +#endif /* _CONFIG_H */ diff --git a/src/main.c b/src/main.c index b44330a..33ece10 100644 --- a/src/main.c +++ b/src/main.c @@ -52,7 +52,7 @@ inline float BarSamplesToSeconds (float samplerate, float channels, int main (int argc, char **argv) { PianoHandle_t ph; - struct aacPlayer player; + static struct audioPlayer player; BarSettings_t settings; PianoSong_t *curSong = NULL; PianoStation_t *curStation = NULL; @@ -165,7 +165,7 @@ int main (int argc, char **argv) { BarUiMsg ("Receiving new playlist... "); PianoDestroyPlaylist (&ph); if (BarUiPrintPianoStatus (PianoGetPlaylist (&ph, - curStation->id, PIANO_AF_AACPLUS)) != + curStation->id, settings.audioFormat)) != PIANO_RET_OK) { curStation = NULL; } else { @@ -198,6 +198,7 @@ int main (int argc, char **argv) { memset (&player, 0, sizeof (player)); player.url = strdup (curSong->audioUrl); player.gain = curSong->fileGain; + player.audioFormat = curSong->audioFormat; /* start player */ pthread_create (&playerThread, NULL, BarPlayerThread, diff --git a/src/player.c b/src/player.c index 94c7969..da4f60a 100644 --- a/src/player.c +++ b/src/player.c @@ -43,32 +43,56 @@ THE SOFTWARE. return 0; \ } +/* pandora uses float values with 2 digits precision. Scale them by 100 to get + * a "nice" integer */ +#define RG_SCALE_FACTOR 100 + /* compute replaygain scale factor * algo taken from here: http://www.dsprelated.com/showmessage/29246/1.php * mpd does the same * @param apply this gain * @return this * yourvalue = newgain value */ -inline float computeReplayGainScale (float applyGain) { - return pow (10.0, applyGain / 20.0); +inline unsigned int computeReplayGainScale (float applyGain) { + return pow (10.0, applyGain / 20.0) * RG_SCALE_FACTOR; +} + +/* apply replaygain to signed short value + * @param value + * @param replaygain scale (calculated by computeReplayGainScale) + * @return scaled value + */ +inline signed short int applyReplayGain (signed short int value, + unsigned int scale) { + int tmpReplayBuf = value * scale; + /* avoid clipping */ + if (tmpReplayBuf > INT16_MAX*RG_SCALE_FACTOR) { + return INT16_MAX; + } else if (tmpReplayBuf < INT16_MIN*RG_SCALE_FACTOR) { + return INT16_MIN; + } else { + return tmpReplayBuf / RG_SCALE_FACTOR; + } } -/* our heart: plays streamed music +#ifdef ENABLE_FAAD + +/* play aac stream * @param streamed data * @param block size * @param received blocks * @param extra data (player data) * @return received bytes or less on error */ -size_t BarPlayerCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { +size_t BarPlayerAACCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { char *data = ptr; - struct aacPlayer *player = stream; + struct audioPlayer *player = stream; QUIT_PAUSE_CHECK; /* fill buffer */ if (player->bufferFilled + size*nmemb > sizeof (player->buffer)) { - printf ("Buffer overflow!\n"); + printf (PACKAGE ": Buffer overflow!\n"); return 0; } memcpy (player->buffer+player->bufferFilled, data, size*nmemb); @@ -79,14 +103,13 @@ size_t BarPlayerCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { if (player->mode == PLAYER_RECV_DATA) { short int *aacDecoded; NeAACDecFrameInfo frameInfo; - int tmpReplayBuf; size_t i; while ((player->bufferFilled - player->bufferRead) > player->sampleSize[player->sampleSizeCurr]) { /* decode frame */ aacDecoded = NeAACDecDecode(player->aacHandle, &frameInfo, - (unsigned char *) player->buffer + player->bufferRead, + player->buffer + player->bufferRead, player->sampleSize[player->sampleSizeCurr]); if (frameInfo.error != 0) { printf (PACKAGE ": Decoding error: %s\n\n", @@ -94,19 +117,11 @@ size_t BarPlayerCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { break; } for (i = 0; i < frameInfo.samples; i++) { - tmpReplayBuf = (float) aacDecoded[i] * player->scale; - /* avoid clipping */ - if (tmpReplayBuf > INT16_MAX) { - aacDecoded[i] = INT16_MAX; - } else if (tmpReplayBuf < INT16_MIN) { - aacDecoded[i] = INT16_MIN; - } else { - aacDecoded[i] = tmpReplayBuf; - } + aacDecoded[i] = applyReplayGain (aacDecoded[i], player->scale); } /* ao_play needs bytes: 1 sample = 16 bits = 2 bytes */ ao_play (player->audioOutDevice, (char *) aacDecoded, - frameInfo.samples * 16 / 8); + frameInfo.samples * 2); player->bufferRead += frameInfo.bytesconsumed; player->sampleSizeCurr++; /* going through this loop can take up to a few seconds => @@ -136,15 +151,14 @@ size_t BarPlayerCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { /* +1+4 needs to be replaced by ! */ player->bufferRead += 1+4; - char err = NeAACDecInit2 (player->aacHandle, - (unsigned char *) player->buffer + + char err = NeAACDecInit2 (player->aacHandle, player->buffer + player->bufferRead, 5, &player->samplerate, &player->channels); player->bufferRead += 5; if (err != 0) { - printf ("Error while initializing audio decoder" + printf (PACKAGE ": Error while initializing audio decoder" "(%i)\n", err); - return 1; + return 0; } audioOutDriver = ao_default_driver_id(); format.bits = 16; @@ -218,38 +232,162 @@ size_t BarPlayerCurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { /* move remaining bytes to buffer beginning */ memmove (player->buffer, player->buffer + player->bufferRead, (player->bufferFilled - player->bufferRead)); - player->bufferFilled = (player->bufferFilled - player->bufferRead); + player->bufferFilled -= player->bufferRead; return size*nmemb; } +#endif /* ENABLE_FAAD */ + +#ifdef ENABLE_MAD + +/* convert mad's internal fixed point format to short int + * @param mad fixed + * @return short int + */ +inline signed short int BarPlayerMadToShort (mad_fixed_t fixed) { + /* Clipping */ + if (fixed >= MAD_F_ONE) { + return SHRT_MAX; + } else if (fixed <= -MAD_F_ONE) { + return -SHRT_MAX; + } + + /* Conversion */ + return (signed short int) (fixed >> (MAD_F_FRACBITS - 15)); +} + +size_t BarPlayerMp3CurlCb (void *ptr, size_t size, size_t nmemb, void *stream) { + char *data = ptr; + struct audioPlayer *player = stream; + size_t i; + + QUIT_PAUSE_CHECK; + + /* fill buffer */ + if (player->bufferFilled + size*nmemb > sizeof (player->buffer)) { + printf (PACKAGE ": Buffer overflow!\n"); + return 0; + } + memcpy (player->buffer+player->bufferFilled, data, size*nmemb); + player->bufferFilled += size*nmemb; + player->bufferRead = 0; + player->bytesReceived += size*nmemb; + + mad_stream_buffer (&player->mp3Stream, player->buffer, + player->bufferFilled); + player->mp3Stream.error = 0; + do { + /* channels * max samples found in mad.h */ + signed short int madDecoded[2*1152], *madPtr = madDecoded; + + if (mad_frame_decode (&player->mp3Frame, &player->mp3Stream) != 0) { + if (player->mp3Stream.error != MAD_ERROR_BUFLEN) { + printf (PACKAGE ": mp3 decoding error: %s.\n", + mad_stream_errorstr (&player->mp3Stream)); + return 0; + } else { + /* rebuffering required => exit loop */ + break; + } + } + mad_timer_add (&player->mp3Timer, player->mp3Frame.header.duration); + mad_synth_frame (&player->mp3Synth, &player->mp3Frame); + for (i = 0; i < player->mp3Synth.pcm.length; i++) { + /* left channel */ + *(madPtr++) = applyReplayGain (BarPlayerMadToShort ( + player->mp3Synth.pcm.samples[0][i]), player->scale); + + /* right channel */ + *(madPtr++) = applyReplayGain (BarPlayerMadToShort ( + player->mp3Synth.pcm.samples[1][i]), player->scale); + } + if (player->mode < PLAYER_AUDIO_INITIALIZED) { + ao_sample_format format; + int audioOutDriver; + + player->channels = player->mp3Synth.pcm.channels; + player->samplerate = player->mp3Synth.pcm.samplerate; + audioOutDriver = ao_default_driver_id(); + format.bits = 16; + format.channels = player->channels; + format.rate = player->samplerate; + format.byte_format = AO_FMT_LITTLE; + player->audioOutDevice = ao_open_live (audioOutDriver, + &format, NULL); + player->mode = PLAYER_AUDIO_INITIALIZED; + } + /* samples * length * channels */ + ao_play (player->audioOutDevice, (char *) madDecoded, + player->mp3Synth.pcm.length * 2 * 2); + + QUIT_PAUSE_CHECK; + } while (player->mp3Stream.error != MAD_ERROR_BUFLEN); + + player->bufferRead += player->mp3Stream.next_frame - player->buffer; + + /* move remaining bytes to buffer beginning */ + memmove (player->buffer, player->buffer + player->bufferRead, + (player->bufferFilled - player->bufferRead)); + player->bufferFilled -= player->bufferRead; + + return size*nmemb; +} +#endif /* ENABLE_MAD */ /* player thread; for every song a new thread is started * @param aacPlayer structure * @return NULL NULL NULL ... */ void *BarPlayerThread (void *data) { - struct aacPlayer *player = data; + struct audioPlayer *player = data; + #ifdef ENABLE_FAAD NeAACDecConfigurationPtr conf; + #endif CURLcode curlRet = 0; /* init handles */ pthread_mutex_init (&player->pauseMutex, NULL); player->audioFd = curl_easy_init (); - player->aacHandle = NeAACDecOpen(); + + switch (player->audioFormat) { + #ifdef ENABLE_FAAD + case PIANO_AF_AACPLUS: + player->aacHandle = NeAACDecOpen(); + /* set aac conf */ + conf = NeAACDecGetCurrentConfiguration(player->aacHandle); + conf->outputFormat = FAAD_FMT_16BIT; + conf->downMatrix = 1; + NeAACDecSetConfiguration(player->aacHandle, conf); + + curl_easy_setopt (player->audioFd, CURLOPT_WRITEFUNCTION, + BarPlayerAACCurlCb); + break; + #endif /* ENABLE_FAAD */ + + #ifdef ENABLE_MAD + case PIANO_AF_MP3: + mad_stream_init (&player->mp3Stream); + mad_frame_init (&player->mp3Frame); + mad_synth_init (&player->mp3Synth); + mad_timer_reset (&player->mp3Timer); + + curl_easy_setopt (player->audioFd, CURLOPT_WRITEFUNCTION, + BarPlayerMp3CurlCb); + break; + #endif /* ENABLE_MAD */ + + default: + printf (PACKAGE ": Unsupported audio format!\n"); + return NULL; + break; + } /* init replaygain */ player->scale = computeReplayGainScale (player->gain); - /* set aac conf */ - conf = NeAACDecGetCurrentConfiguration(player->aacHandle); - conf->outputFormat = FAAD_FMT_16BIT; - conf->downMatrix = 1; - NeAACDecSetConfiguration(player->aacHandle, conf); - /* init curl */ curl_easy_setopt (player->audioFd, CURLOPT_URL, player->url); - curl_easy_setopt (player->audioFd, CURLOPT_WRITEFUNCTION, BarPlayerCurlCb); curl_easy_setopt (player->audioFd, CURLOPT_WRITEDATA, (void *) player); curl_easy_setopt (player->audioFd, CURLOPT_USERAGENT, PACKAGE); curl_easy_setopt (player->audioFd, CURLOPT_CONNECTTIMEOUT, 60); @@ -269,12 +407,32 @@ void *BarPlayerThread (void *data) { curlRet = curl_easy_perform (player->audioFd); } while (curlRet == CURLE_PARTIAL_FILE); - NeAACDecClose(player->aacHandle); + switch (player->audioFormat) { + #ifdef ENABLE_FAAD + case PIANO_AF_AACPLUS: + NeAACDecClose(player->aacHandle); + break; + #endif /* ENABLE_FAAD */ + + #ifdef ENABLE_MAD + case PIANO_AF_MP3: + mad_synth_finish (&player->mp3Synth); + mad_frame_finish (&player->mp3Frame); + mad_stream_finish (&player->mp3Stream); + break; + #endif /* ENABLE_MAD */ + + default: + /* this should never happen: thread is aborted above */ + break; + } ao_close(player->audioOutDevice); curl_easy_cleanup (player->audioFd); + #ifdef ENABLE_FAAD if (player->sampleSize != NULL) { free (player->sampleSize); } + #endif /* ENABLE_FAAD */ pthread_mutex_destroy (&player->pauseMutex); player->mode = PLAYER_FINISHED_PLAYBACK; diff --git a/src/player.h b/src/player.h index 7756d0a..eab03a9 100644 --- a/src/player.h +++ b/src/player.h @@ -24,37 +24,68 @@ THE SOFTWARE. #ifndef _PLAYER_H #define _PLAYER_H +#include "config.h" + #include + +#ifdef ENABLE_FAAD #include +#endif + +#ifdef ENABLE_MAD +#include +#endif + #include #include +#include -struct aacPlayer { +struct audioPlayer { /* buffer; should be large enough */ - char buffer[CURL_MAX_WRITE_SIZE*2]; + unsigned char buffer[CURL_MAX_WRITE_SIZE*2]; size_t bufferFilled; size_t bufferRead; + size_t bytesReceived; + enum {PLAYER_FREED = 0, PLAYER_INITIALIZED, PLAYER_FOUND_ESDS, PLAYER_AUDIO_INITIALIZED, PLAYER_FOUND_STSZ, PLAYER_SAMPLESIZE_INITIALIZED, PLAYER_RECV_DATA, PLAYER_FINISHED_PLAYBACK} mode; - size_t bytesReceived; - /* stsz atom: sample sizes */ - unsigned int *sampleSize; + + PianoAudioFormat_t audioFormat; + size_t sampleSizeN; size_t sampleSizeCurr; + /* aac */ + #ifdef ENABLE_FAAD NeAACDecHandle aacHandle; + /* stsz atom: sample sizes */ + unsigned int *sampleSize; + #endif + + /* mp3 */ + #ifdef ENABLE_MAD + struct mad_stream mp3Stream; + struct mad_frame mp3Frame; + struct mad_synth mp3Synth; + mad_timer_t mp3Timer; + #endif + unsigned long samplerate; unsigned char channels; + float gain; - float scale; + unsigned int scale; + /* audio out */ ao_device *audioOutDevice; + + CURL *audioFd; char *url; + char doQuit; pthread_mutex_t pauseMutex; - CURL *audioFd; }; void *BarPlayerThread (void *data); diff --git a/src/settings.c b/src/settings.c index 2e5dab6..e3e811a 100644 --- a/src/settings.c +++ b/src/settings.c @@ -154,6 +154,15 @@ void BarSettingsRead (BarSettings_t *settings) { "act_stationselectquickmix", NULL}, }; + /* apply defaults */ + #ifdef ENABLE_FAAD + settings->audioFormat = PIANO_AF_AACPLUS; + #else + #ifdef ENABLE_MAD + settings->audioFormat = PIANO_AF_MP3; + #endif + #endif + BarGetXdgConfigDir (PACKAGE "/config", configfile, sizeof (configfile)); if ((configfd = fopen (configfile, "r")) == NULL) { /* use default keyboard shortcuts */ @@ -212,6 +221,12 @@ void BarSettingsRead (BarSettings_t *settings) { break; } } + } else if (strcmp ("audio_format", key) == 0) { + if (strcmp (val, "aacplus") == 0) { + settings->audioFormat = PIANO_AF_AACPLUS; + } else if (strcmp (val, "mp3") == 0) { + settings->audioFormat = PIANO_AF_MP3; + } } } diff --git a/src/settings.h b/src/settings.h index ed71530..a1afea1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -29,7 +29,7 @@ THE SOFTWARE. #include "player.h" -#define BAR_KS_ARGS PianoHandle_t *ph, struct aacPlayer *player, \ +#define BAR_KS_ARGS PianoHandle_t *ph, struct audioPlayer *player, \ struct BarSettings *settings, PianoSong_t **curSong, \ PianoStation_t **curStation, char *doQuit @@ -50,6 +50,7 @@ struct BarSettings { char *configKey; struct BarKeyShortcut *next; } *keys; + PianoAudioFormat_t audioFormat; }; typedef struct BarSettings BarSettings_t; diff --git a/src/ui_act.c b/src/ui_act.c index 5c28672..2ecc9c2 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -44,7 +44,7 @@ THE SOFTWARE. /* helper to _really_ skip a song (unlock mutex, quit player) * @param player handle */ -inline void BarUiDoSkipSong (struct aacPlayer *player) { +inline void BarUiDoSkipSong (struct audioPlayer *player) { player->doQuit = 1; pthread_mutex_unlock (&player->pauseMutex); } @@ -178,6 +178,7 @@ void BarUiActSongInfo (BAR_KS_ARGS) { printf ("Song infos:\n" "album:\t%s\n" "artist:\t%s\n" + "audioFormat:\t%i\n" "audioUrl:\t%s\n" "fileGain:\t%f\n" "focusTraitId:\t%s\n" @@ -188,12 +189,11 @@ void BarUiActSongInfo (BAR_KS_ARGS) { "stationId:\t%s\n" "title:\t%s\n" "userSeed:\t%s\n", - (*curSong)->album, (*curSong)->artist, (*curSong)->audioUrl, - (*curSong)->fileGain, (*curSong)->focusTraitId, - (*curSong)->identity, (*curSong)->matchingSeed, - (*curSong)->musicId, (*curSong)->rating, - (*curSong)->stationId, (*curSong)->title, - (*curSong)->userSeed); + (*curSong)->album, (*curSong)->artist, (*curSong)->audioFormat, + (*curSong)->audioUrl, (*curSong)->fileGain, + (*curSong)->focusTraitId, (*curSong)->identity, + (*curSong)->matchingSeed, (*curSong)->musicId, (*curSong)->rating, + (*curSong)->stationId, (*curSong)->title, (*curSong)->userSeed); } /* rate current song -- cgit v1.2.3