diff options
author | Lars-Dominik Braun <lars@6xq.net> | 2014-03-31 17:35:28 +0200 |
---|---|---|
committer | Lars-Dominik Braun <lars@6xq.net> | 2014-03-31 20:20:22 +0200 |
commit | c872f00508ce4afe3b8ec863b23595c31fd8b4be (patch) | |
tree | c441d537a5ead0d659bb05e83e8b4bd7d4066f87 | |
parent | 8e685c992516834e35bcccea1f61c39a9d847e2f (diff) | |
download | pianobar-windows-c872f00508ce4afe3b8ec863b23595c31fd8b4be.tar.gz pianobar-windows-c872f00508ce4afe3b8ec863b23595c31fd8b4be.tar.bz2 pianobar-windows-c872f00508ce4afe3b8ec863b23595c31fd8b4be.zip |
Use libav/ffmpeg for audio decoding
libav 9.12 and ffmpeg 2.2 have been tested.
Here’s why: My mp4 “parser” *cough* never was a mp4 parser in the sense
that it actually understood the file format. Instead it grepped the
input stream for “magic” strings (section identifiers). That alone
should be sufficient to throw away the code and rewrite it. Additionally
libfaad2 has not been updated for ages. I guess it was abandoned in
favor of libav/ffmpeg.
With libav/ffmpeg, which we support both as long as the API’s don’t
diverge too much, pianobar gains fast and reliable AAC and MP3 decoding
without bothering too much about the details. Most users will have it
installed already. On my own machine libav consumes about 2/3 CPU time
compared to the previous solution when playing AAC. Unfortunately memory
usage doubled and my attempts to disable unused protocols/formats/codec
failed due to libav’s API limitations.
While cleaning up a small detail regarding the eventcmd API has changed
too: Song duration and position are measured in seconds instead of
milliseconds now. Since libav/ffmpeg keeps track of accurate timing the
precision pianobar keeps track of can be reduced, while still being
sufficient for most users.
-rw-r--r-- | INSTALL | 36 | ||||
-rw-r--r-- | Makefile | 30 | ||||
-rw-r--r-- | src/main.c | 75 | ||||
-rw-r--r-- | src/player.c | 690 | ||||
-rw-r--r-- | src/player.h | 84 | ||||
-rw-r--r-- | src/ui.c | 4 | ||||
-rw-r--r-- | src/ui_act.c | 12 |
7 files changed, 304 insertions, 627 deletions
@@ -10,8 +10,7 @@ Dependencies - gnutls - gcrypt (with blowfish cipher enabled) - json-c -- libfaad2 (compiled with --without-drm) -- libmad (optional, Pandora One users only) +- libav/ffmpeg - UTF-8 console/locale Building @@ -29,36 +28,3 @@ Or install it by issuing gmake install -Selecting features -++++++++++++++++++ - -It is possible to disable certain features when building pianobar by setting -one of the variables listed below. In fact it is required if you don’t have the -corresponding library installed. So if you don’t want AAC playback or don’t -have libfaad installed for example, run - - gmake DISABLE_FAAD=1 - -instead of a plain `gmake`. - -DISABLE_FAAD=1 - Disables AAC playback. -DISABLE_MAD=1 - Disables MP3 playback. - -Ubuntu 12.04 -++++++++++++ - -To install on Ubuntu >= 12.04 - - sudo apt-get install \ - libao-dev \ - libmad0-dev \ - libfaad-dev \ - libgnutls-dev \ - libjson0-dev \ - libgcrypt11-dev - make - sudo make install - -You can then fire it up with `pianobar` @@ -73,22 +73,8 @@ LIBWAITRESS_INCLUDE:=${LIBWAITRESS_DIR} LIBWAITRESS_TEST_SRC=${LIBWAITRESS_DIR}/waitress-test.c LIBWAITRESS_TEST_OBJ:=${LIBWAITRESS_TEST_SRC:.c=.o} -ifeq (${DISABLE_FAAD}, 1) - LIBFAAD_CFLAGS:= - LIBFAAD_LDFLAGS:= -else - LIBFAAD_CFLAGS:=-DENABLE_FAAD - LIBFAAD_LDFLAGS:=-lfaad -endif - -ifeq (${DISABLE_MAD}, 1) - LIBMAD_CFLAGS:= - LIBMAD_LDFLAGS:= -else - LIBMAD_CFLAGS:=-DENABLE_MAD - LIBMAD_CFLAGS+=$(shell pkg-config --cflags mad) - LIBMAD_LDFLAGS:=$(shell pkg-config --libs mad) -endif +LIBAV_CFLAGS=$(shell pkg-config --cflags libavcodec libavformat libavutil libavfilter) +LIBAV_LDFLAGS=$(shell pkg-config --libs libavcodec libavformat libavutil libavfilter) LIBGNUTLS_CFLAGS:=$(shell pkg-config --cflags gnutls) LIBGNUTLS_LDFLAGS:=$(shell pkg-config --libs gnutls) @@ -104,15 +90,14 @@ ifeq (${DYNLINK},1) pianobar: ${PIANOBAR_OBJ} ${PIANOBAR_HDR} libpiano.so.0 @echo " LINK $@" @${CC} -o $@ ${PIANOBAR_OBJ} ${LDFLAGS} -lao -lpthread -lm -L. -lpiano \ - ${LIBFAAD_LDFLAGS} ${LIBMAD_LDFLAGS} ${LIBGNUTLS_LDFLAGS} \ - ${LIBGCRYPT_LDFLAGS} + ${LIBAV_LDFLAGS} ${LIBGNUTLS_LDFLAGS} ${LIBGCRYPT_LDFLAGS} else pianobar: ${PIANOBAR_OBJ} ${PIANOBAR_HDR} ${LIBPIANO_OBJ} ${LIBWAITRESS_OBJ} \ ${LIBWAITRESS_HDR} @echo " LINK $@" @${CC} ${CFLAGS} ${LDFLAGS} ${PIANOBAR_OBJ} ${LIBPIANO_OBJ} \ ${LIBWAITRESS_OBJ} -lao -lpthread -lm \ - ${LIBFAAD_LDFLAGS} ${LIBMAD_LDFLAGS} ${LIBGNUTLS_LDFLAGS} \ + ${LIBAV_LDFLAGS} ${LIBGNUTLS_LDFLAGS} \ ${LIBGCRYPT_LDFLAGS} ${LIBJSONC_LDFLAGS} -o $@ endif @@ -134,7 +119,7 @@ libpiano.so.0: ${LIBPIANO_RELOBJ} ${LIBPIANO_HDR} ${LIBWAITRESS_RELOBJ} \ %.d: %.c @set -e; rm -f $@; \ $(CC) -M ${CFLAGS} -I ${LIBPIANO_INCLUDE} -I ${LIBWAITRESS_INCLUDE} \ - ${LIBFAAD_CFLAGS} ${LIBMAD_CFLAGS} ${LIBGNUTLS_CFLAGS} \ + ${LIBAV_CFLAGS} ${LIBGNUTLS_CFLAGS} \ ${LIBGCRYPT_CFLAGS} ${LIBJSONC_CFLAGS} $< > $@.$$$$; \ sed '1 s,^.*\.o[ :]*,$*.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ @@ -147,7 +132,7 @@ libpiano.so.0: ${LIBPIANO_RELOBJ} ${LIBPIANO_HDR} ${LIBWAITRESS_RELOBJ} \ %.o: %.c @echo " CC $<" @${CC} ${CFLAGS} -I ${LIBPIANO_INCLUDE} -I ${LIBWAITRESS_INCLUDE} \ - ${LIBFAAD_CFLAGS} ${LIBMAD_CFLAGS} ${LIBGNUTLS_CFLAGS} \ + ${LIBAV_CFLAGS} ${LIBGNUTLS_CFLAGS} \ ${LIBGCRYPT_CFLAGS} ${LIBJSONC_CFLAGS} -c -o $@ $< # create position independent code (for shared libraries) @@ -168,10 +153,9 @@ all: pianobar debug: pianobar debug: CC=clang -debug: CFLAGS=-Wall -Wextra \ +debug: CFLAGS=-g -Wall -Wextra \ -pedantic \ -Wno-unused-parameter \ - -fsanitize=address \ -fsanitize=integer \ -fsanitize=undefined \ -fsanitize=alignment \ @@ -51,7 +51,6 @@ THE SOFTWARE. #include <sys/types.h> #include <sys/wait.h> - /* pandora.com library */ #include <piano.h> @@ -246,38 +245,38 @@ static void BarMainGetPlaylist (BarApp_t *app) { /* start new player thread */ static void BarMainStartPlayback (BarApp_t *app, pthread_t *playerThread) { - BarUiPrintSong (&app->settings, app->playlist, app->curStation->isQuickMix ? + assert (app != NULL); + assert (playerThread != NULL); + + const PianoSong_t * const curSong = app->playlist; + assert (curSong != NULL); + + BarUiPrintSong (&app->settings, curSong, app->curStation->isQuickMix ? PianoFindStationById (app->ph.stations, - app->playlist->stationId) : NULL); + curSong->stationId) : NULL); - if (app->playlist->audioUrl == NULL) { + static const char httpPrefix[] = "http://"; + /* avoid playing local files */ + if (curSong->audioUrl == NULL || + strncmp (curSong->audioUrl, httpPrefix, strlen (httpPrefix)) != 0) { BarUiMsg (&app->settings, MSG_ERR, "Invalid song url.\n"); } else { /* setup player */ memset (&app->player, 0, sizeof (app->player)); - WaitressInit (&app->player.waith); - WaitressSetUrl (&app->player.waith, app->playlist->audioUrl); - - /* set up global proxy, player is NULLed on songfinish */ - if (app->settings.proxy != NULL) { - WaitressSetProxy (&app->player.waith, app->settings.proxy); - } - - app->player.gain = app->playlist->fileGain; - app->player.scale = BarPlayerCalcScale (app->player.gain + app->settings.volume); - app->player.audioFormat = app->playlist->audioFormat; + app->player.url = curSong->audioUrl; + app->player.gain = curSong->fileGain; app->player.settings = &app->settings; - app->player.songDuration = app->playlist->length * 1000; + app->player.songDuration = curSong->length; pthread_mutex_init (&app->player.pauseMutex, NULL); pthread_cond_init (&app->player.pauseCond, NULL); /* throw event */ BarUiStartEventCmd (&app->settings, "songstart", - app->curStation, app->playlist, &app->player, app->ph.stations, + app->curStation, curSong, &app->player, app->ph.stations, PIANO_RET_OK, WAITRESS_RET_OK); - /* prevent race condition, mode must _not_ be FREED if + /* prevent race condition, mode must _not_ be DEAD if * thread has been started */ app->player.mode = PLAYER_STARTING; /* start player */ @@ -319,21 +318,21 @@ static void BarMainPlayerCleanup (BarApp_t *app, pthread_t *playerThread) { /* print song duration */ static void BarMainPrintTime (BarApp_t *app) { - /* Ugly: songDuration is unsigned _long_ int! Lets hope this won't - * overflow */ - int songRemaining = (signed long int) (app->player.songDuration - - app->player.songPlayed) / BAR_PLAYER_MS_TO_S_FACTOR; - enum {POSITIVE, NEGATIVE} sign = NEGATIVE; - if (songRemaining < 0) { - /* song is longer than expected */ - sign = POSITIVE; - songRemaining = -songRemaining; + unsigned int songRemaining; + char sign; + + if (app->player.songPlayed <= app->player.songDuration) { + songRemaining = app->player.songDuration - app->player.songPlayed; + sign = '-'; + } else { + /* longer than expected */ + songRemaining = app->player.songPlayed - app->player.songDuration; + sign = '+'; } - BarUiMsg (&app->settings, MSG_TIME, "%c%02i:%02i/%02li:%02li\r", - (sign == POSITIVE ? '+' : '-'), - songRemaining / 60, songRemaining % 60, - app->player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR / 60, - app->player.songDuration / BAR_PLAYER_MS_TO_S_FACTOR % 60); + BarUiMsg (&app->settings, MSG_TIME, "%c%02u:%02u/%02u:%02u\r", + sign, songRemaining / 60, songRemaining % 60, + app->player.songDuration / 60, + app->player.songDuration % 60); } /* main loop @@ -363,13 +362,13 @@ static void BarMainLoop (BarApp_t *app) { while (!app->doQuit) { /* song finished playing, clean up things/scrobble song */ - if (app->player.mode == PLAYER_FINISHED_PLAYBACK) { + if (app->player.mode == PLAYER_FINISHED) { BarMainPlayerCleanup (app, &playerThread); } /* check whether player finished playing and start playing new * song */ - if (app->player.mode == PLAYER_FREED && app->curStation != NULL) { + if (app->player.mode == PLAYER_DEAD && app->curStation != NULL) { /* what's next? */ if (app->playlist != NULL) { PianoSong_t *histsong = app->playlist; @@ -389,12 +388,12 @@ static void BarMainLoop (BarApp_t *app) { BarMainHandleUserInput (app); /* show time */ - if (app->player.mode < PLAYER_FINISHED_PLAYBACK) { + if (app->player.mode < PLAYER_FINISHED) { BarMainPrintTime (app); } } - if (app->player.mode != PLAYER_FREED) { + if (app->player.mode != PLAYER_DEAD) { pthread_join (playerThread, NULL); } } @@ -415,11 +414,11 @@ int main (int argc, char **argv) { signal (SIGPIPE, SIG_IGN); /* init some things */ - ao_initialize (); gcry_check_version (NULL); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); gnutls_global_init (); + BarPlayerInit (); BarSettingsInit (&app.settings); BarSettingsRead (&app.settings); @@ -488,7 +487,7 @@ int main (int argc, char **argv) { PianoDestroyPlaylist (app.songHistory); PianoDestroyPlaylist (app.playlist); WaitressFree (&app.waith); - ao_shutdown(); + BarPlayerDestroy (); gnutls_global_deinit (); BarSettingsDestroy (&app.settings); diff --git a/src/player.c b/src/player.c index 55b9e90..e16f690 100644 --- a/src/player.c +++ b/src/player.c @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2013 +Copyright (c) 2008-2014 Lars-Dominik Braun <lars@6xq.net> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -31,519 +31,293 @@ THE SOFTWARE. #include <assert.h> #include <arpa/inet.h> +#include <ao/ao.h> +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavutil/avutil.h> +#include <libavfilter/avfilter.h> +#include <libavfilter/avfiltergraph.h> +#include <libavfilter/buffersink.h> +#include <libavfilter/buffersrc.h> +#include <libavutil/channel_layout.h> +#include <libavutil/opt.h> + #include "player.h" #include "config.h" #include "ui.h" #include "ui_types.h" -#define bigToHostEndian32(x) ntohl(x) - -/* pandora uses float values with 2 digits precision. Scale them by 100 to get - * a "nice" integer */ -#define RG_SCALE_FACTOR 100 - -/* wait until the pause flag is cleared - * @param player structure - * @return true if the player should quit - */ -static bool BarPlayerCheckPauseQuit (struct audioPlayer *player) { - bool quit = false; - - pthread_mutex_lock (&player->pauseMutex); - while (true) { - if (player->doQuit) { - quit = true; - break; - } - if (!player->doPause) { - break; - } - pthread_cond_wait(&player->pauseCond, - &player->pauseMutex); - } - pthread_mutex_unlock (&player->pauseMutex); - - return quit; +static void printError (const BarSettings_t * const settings, + const char * const msg, int ret) { + char avmsg[128]; + av_strerror (ret, avmsg, sizeof (avmsg)); + BarUiMsg (settings, MSG_ERR, "%s (%s)\n", msg, avmsg); } -/* 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 +/* global initialization + * + * XXX: in theory we can select the filters/formats we want to support, but + * this does not work in practise. */ -unsigned int BarPlayerCalcScale (const float applyGain) { - return pow (10.0, applyGain / 20.0) * RG_SCALE_FACTOR; +void BarPlayerInit () { + ao_initialize (); + av_register_all (); + avfilter_register_all (); + avformat_network_init (); } -/* apply replaygain to signed short value - * @param value - * @param replaygain scale (calculated by computeReplayGainScale) - * @return scaled value - */ -static inline signed short int applyReplayGain (const signed short int value, - const unsigned int scale) { - int tmpReplayBuf = value * (signed int) scale; - /* clipping */ - if (tmpReplayBuf > SHRT_MAX*RG_SCALE_FACTOR) { - return SHRT_MAX; - } else if (tmpReplayBuf < SHRT_MIN*RG_SCALE_FACTOR) { - return SHRT_MIN; - } else { - return tmpReplayBuf / RG_SCALE_FACTOR; - } +void BarPlayerDestroy () { + avformat_network_deinit (); + avfilter_uninit (); + ao_shutdown (); } -/* Refill player's buffer with dataSize of data - * @param player structure - * @param new data - * @param data size - * @return 1 on success, 0 when buffer overflow occured +/* Update volume filter + * + * XXX: I’m not sure whether this is thread-safe or not */ -static inline int BarPlayerBufferFill (struct audioPlayer *player, - const char *data, const size_t dataSize) { - /* fill buffer */ - if (player->bufferFilled + dataSize > BAR_PLAYER_BUFSIZE) { - BarUiMsg (player->settings, MSG_ERR, "Buffer overflow!\n"); - return 0; +void BarPlayerSetVolume (struct audioPlayer * const player) { + assert (player != NULL); + assert (player->fvolume != NULL); + + int ret; + /* convert from decibel */ + const double volume = pow (10, (player->settings->volume + player->gain) / 20); + /* XXX: can we avoid accessing ->priv here? */ + if ((ret = av_opt_set_double (player->fvolume->priv, "volume", volume, + 0)) != 0) { + printError (player->settings, "Cannot set volume", ret); } - memcpy (player->buffer+player->bufferFilled, data, dataSize); - player->bufferFilled += dataSize; - player->bufferRead = 0; - player->bytesReceived += dataSize; - return 1; } -/* move data beginning from read pointer to buffer beginning and - * overwrite data already read from buffer - * @param player structure - * @return nothing at all +#define softfail(msg) \ + printError (player->settings, msg, ret); \ + pret = PLAYER_RET_SOFTFAIL; \ + goto finish; + +/* player thread; for every song a new thread is started + * @param audioPlayer structure + * @return PLAYER_RET_* */ -static inline void BarPlayerBufferMove (struct audioPlayer *player) { - /* move remaining bytes to buffer beginning */ - memmove (player->buffer, player->buffer + player->bufferRead, - (player->bufferFilled - player->bufferRead)); - player->bufferFilled -= player->bufferRead; -} +void *BarPlayerThread (void *data) { + assert (data != NULL); -#ifdef ENABLE_FAAD + struct audioPlayer * const player = data; + int ret; + intptr_t pret = PLAYER_RET_OK; + const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16; -/* play aac stream - * @param streamed data - * @param received bytes - * @param extra data (player data) - * @return received bytes or less on error - */ -static WaitressCbReturn_t BarPlayerAACCb (void *ptr, size_t size, - void *stream) { - const char *data = ptr; - struct audioPlayer *player = stream; - - if (BarPlayerCheckPauseQuit (player) || - !BarPlayerBufferFill (player, data, size)) { - return WAITRESS_CB_RET_ERR; + ao_device *aoDev = NULL; + ao_sample_format aoFmt; + + AVFilterGraph *fgraph = NULL; + AVFrame *frame = NULL, *filteredFrame = NULL; + AVFormatContext *fctx = NULL; + AVCodecContext *cctx = NULL; + + /* stream setup */ + if ((ret = avformat_open_input (&fctx, player->url, NULL, NULL)) < 0) { + softfail ("Unable to open audio file"); } - if (player->mode == PLAYER_RECV_DATA) { - short int *aacDecoded; - NeAACDecFrameInfo frameInfo; - size_t i; - - while (player->sampleSizeCurr < player->sampleSizeN && - (player->bufferFilled - player->bufferRead) >= - player->sampleSize[player->sampleSizeCurr]) { - /* going through this loop can take up to a few seconds => - * allow earlier thread abort */ - if (BarPlayerCheckPauseQuit (player)) { - return WAITRESS_CB_RET_ERR; - } + if ((ret = avformat_find_stream_info (fctx, NULL)) < 0) { + softfail ("find_stream_info"); + } - /* decode frame */ - aacDecoded = NeAACDecDecode(player->aacHandle, &frameInfo, - &player->buffer[player->bufferRead], - player->sampleSize[player->sampleSizeCurr]); - player->bufferRead += player->sampleSize[player->sampleSizeCurr]; - ++player->sampleSizeCurr; - - if (frameInfo.error != 0) { - /* skip this frame, songPlayed will be slightly off if this - * happens */ - BarUiMsg (player->settings, MSG_ERR, "Decoding error: %s\n", - NeAACDecGetErrorMessage (frameInfo.error)); - continue; - } - /* assuming data in stsz atom is correct */ - assert (frameInfo.bytesconsumed == - player->sampleSize[player->sampleSizeCurr-1]); + const int streamIdx = av_find_best_stream (fctx, AVMEDIA_TYPE_AUDIO, -1, + -1, NULL, 0); + if (streamIdx < 0) { + softfail ("find_best_stream"); + } - for (i = 0; i < frameInfo.samples; i++) { - 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 * 2); - /* add played frame length to played time, explained below */ - player->songPlayed += (unsigned long long int) frameInfo.samples * - (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / - (unsigned long long int) player->samplerate / - (unsigned long long int) player->channels; - } - if (player->sampleSizeCurr >= player->sampleSizeN) { - /* no more frames, drop data */ - player->bufferRead = player->bufferFilled; - } - } else { - if (player->mode == PLAYER_INITIALIZED) { - while (player->bufferRead+4 < player->bufferFilled) { - if (memcmp (player->buffer + player->bufferRead, "esds", - 4) == 0) { - player->mode = PLAYER_FOUND_ESDS; - player->bufferRead += 4; - break; - } - player->bufferRead++; - } - } - if (player->mode == PLAYER_FOUND_ESDS) { - /* FIXME: is this the correct way? */ - /* we're gonna read 10 bytes */ - while (player->bufferRead+1+4+5 < player->bufferFilled) { - if (memcmp (player->buffer + player->bufferRead, - "\x05\x80\x80\x80", 4) == 0) { - ao_sample_format format; - int audioOutDriver; - - /* +1+4 needs to be replaced by <something>! */ - player->bufferRead += 1+4; - char err = NeAACDecInit2 (player->aacHandle, player->buffer + - player->bufferRead, 5, &player->samplerate, - &player->channels); - player->bufferRead += 5; - if (err != 0) { - BarUiMsg (player->settings, MSG_ERR, - "Error while initializing audio decoder " - "(%i)\n", err); - return WAITRESS_CB_RET_ERR; - } - audioOutDriver = ao_default_driver_id(); - memset (&format, 0, sizeof (format)); - format.bits = 16; - format.channels = player->channels; - format.rate = player->samplerate; - format.byte_format = AO_FMT_NATIVE; - if ((player->audioOutDevice = ao_open_live (audioOutDriver, - &format, NULL)) == NULL) { - /* we're not interested in the errno */ - player->aoError = 1; - BarUiMsg (player->settings, MSG_ERR, - "Cannot open audio device\n"); - return WAITRESS_CB_RET_ERR; - } - player->mode = PLAYER_AUDIO_INITIALIZED; - break; - } - player->bufferRead++; - } - } - if (player->mode == PLAYER_AUDIO_INITIALIZED) { - while (player->bufferRead+4+8 < player->bufferFilled) { - if (memcmp (player->buffer + player->bufferRead, "stsz", - 4) == 0) { - player->mode = PLAYER_FOUND_STSZ; - player->bufferRead += 4; - /* skip version and unknown */ - player->bufferRead += 8; - break; - } - player->bufferRead++; - } - } - /* get frame sizes */ - if (player->mode == PLAYER_FOUND_STSZ) { - while (player->bufferRead+4 < player->bufferFilled) { - /* how many frames do we have? */ - if (player->sampleSizeN == 0) { - /* mp4 uses big endian, convert */ - memcpy (&player->sampleSizeN, player->buffer + - player->bufferRead, sizeof (uint32_t)); - player->sampleSizeN = - bigToHostEndian32 (player->sampleSizeN); - - player->sampleSize = malloc (player->sampleSizeN * - sizeof (*player->sampleSize)); - assert (player->sampleSize != NULL); - player->bufferRead += sizeof (uint32_t); - player->sampleSizeCurr = 0; - /* set up song duration (assuming one frame always contains - * the same number of samples) - * calculation: channels * number of frames * samples per - * frame / samplerate */ - /* FIXME: Hard-coded number of samples per frame */ - player->songDuration = (unsigned long long int) player->sampleSizeN * - 4096LL * (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / - (unsigned long long int) player->samplerate / - (unsigned long long int) player->channels; - break; - } else { - memcpy (&player->sampleSize[player->sampleSizeCurr], - player->buffer + player->bufferRead, - sizeof (uint32_t)); - player->sampleSize[player->sampleSizeCurr] = - bigToHostEndian32 ( - player->sampleSize[player->sampleSizeCurr]); - - player->sampleSizeCurr++; - player->bufferRead += sizeof (uint32_t); - } - /* all sizes read, nearly ready for data mode */ - if (player->sampleSizeCurr >= player->sampleSizeN) { - player->mode = PLAYER_SAMPLESIZE_INITIALIZED; - break; - } - } - } - /* search for data atom and let the show begin... */ - if (player->mode == PLAYER_SAMPLESIZE_INITIALIZED) { - while (player->bufferRead+4 < player->bufferFilled) { - if (memcmp (player->buffer + player->bufferRead, "mdat", - 4) == 0) { - player->mode = PLAYER_RECV_DATA; - player->sampleSizeCurr = 0; - player->bufferRead += 4; - break; - } - player->bufferRead++; - } - } + AVStream * const st = fctx->streams[streamIdx]; + cctx = st->codec; + + /* decoder setup */ + AVCodec * const decoder = avcodec_find_decoder (cctx->codec_id); + if (decoder == NULL) { + softfail ("find_decoder"); } - BarPlayerBufferMove (player); + if ((ret = avcodec_open2 (cctx, decoder, NULL)) < 0) { + softfail ("codec_open2"); + } - return WAITRESS_CB_RET_OK; -} + frame = avcodec_alloc_frame (); + assert (frame != NULL); + filteredFrame = avcodec_alloc_frame (); + assert (filteredFrame != NULL); -#endif /* ENABLE_FAAD */ + AVPacket pkt; + av_init_packet (&pkt); + pkt.data = NULL; + pkt.size = 0; -#ifdef ENABLE_MAD + /* filter setup */ + char strbuf[256]; -/* convert mad's internal fixed point format to short int - * @param mad fixed - * @return short int - */ -static inline signed short int BarPlayerMadToShort (const mad_fixed_t fixed) { - /* Clipping */ - if (fixed >= MAD_F_ONE) { - return SHRT_MAX; - } else if (fixed <= -MAD_F_ONE) { - return -SHRT_MAX; + if ((fgraph = avfilter_graph_alloc ()) == NULL) { + softfail ("graph_alloc"); } - /* Conversion */ - return (signed short int) (fixed >> (MAD_F_FRACBITS - 15)); -} + /* abuffer */ + AVFilterContext *fabuf = NULL; + AVRational time_base = st->time_base; + snprintf (strbuf, sizeof (strbuf), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, + time_base.num, time_base.den, cctx->sample_rate, + av_get_sample_fmt_name (cctx->sample_fmt), + cctx->channel_layout); + if ((ret = avfilter_graph_create_filter (&fabuf, + avfilter_get_by_name ("abuffer"), NULL, strbuf, NULL, fgraph)) < 0) { + softfail ("create_filter abuffer"); + } -/* mp3 playback callback - */ -static WaitressCbReturn_t BarPlayerMp3Cb (void *ptr, size_t size, - void *stream) { - const char *data = ptr; - struct audioPlayer *player = stream; - size_t i; - - if (BarPlayerCheckPauseQuit (player) || - !BarPlayerBufferFill (player, data, size)) { - return WAITRESS_CB_RET_ERR; + /* volume */ + if ((ret = avfilter_graph_create_filter (&player->fvolume, + avfilter_get_by_name ("volume"), NULL, NULL, NULL, fgraph)) < 0) { + softfail ("create_filter volume"); + } + BarPlayerSetVolume (player); + + /* aformat: convert float samples into something more usable */ + AVFilterContext *fafmt = NULL; + snprintf (strbuf, sizeof (strbuf), "sample_fmts=%s", + av_get_sample_fmt_name (avformat)); + if ((ret = avfilter_graph_create_filter (&fafmt, + avfilter_get_by_name ("aformat"), NULL, strbuf, NULL, + fgraph)) < 0) { + softfail ("create_filter aformat"); } - /* some "prebuffering" */ - if (player->mode < PLAYER_RECV_DATA && - player->bufferFilled < BAR_PLAYER_BUFSIZE / 2) { - return WAITRESS_CB_RET_OK; + /* abuffersink */ + AVFilterContext *fbufsink = NULL; + if ((ret = avfilter_graph_create_filter (&fbufsink, + avfilter_get_by_name ("abuffersink"), NULL, NULL, NULL, fgraph)) < 0) { + softfail ("create_filter abuffersink"); } - 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) { - BarUiMsg (player->settings, MSG_ERR, - "mp3 decoding error: %s\n", - mad_stream_errorstr (&player->mp3Stream)); - return WAITRESS_CB_RET_ERR; - } else { - /* rebuffering required => exit loop */ + /* connect filter: abuffer -> volume -> aformat -> abuffersink */ + if (avfilter_link (fabuf, 0, player->fvolume, 0) != 0 || + avfilter_link (player->fvolume, 0, fafmt, 0) != 0 || + avfilter_link (fafmt, 0, fbufsink, 0) != 0) { + softfail ("filter_link"); + } + + if ((ret = avfilter_graph_config (fgraph, NULL)) < 0) { + softfail ("graph_config"); + } + + /* setup libao */ + memset (&aoFmt, 0, sizeof (aoFmt)); + aoFmt.bits = av_get_bytes_per_sample (avformat) * 8; + assert (aoFmt.bits > 0); + aoFmt.channels = cctx->channels; + aoFmt.rate = cctx->sample_rate; + aoFmt.byte_format = AO_FMT_NATIVE; + + int driver = ao_default_driver_id (); + if ((aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) { + BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n"); + pret = PLAYER_RET_HARDFAIL; + goto finish; + } + + player->songPlayed = 0; + player->songDuration = av_q2d (st->time_base) * (double) st->duration; + + while (av_read_frame (fctx, &pkt) >= 0) { + AVPacket pkt_orig = pkt; + + /* pausing */ + pthread_mutex_lock (&player->pauseMutex); + while (true) { + if (!player->doPause) { + av_read_play (fctx); break; + } else { + av_read_pause (fctx); } + pthread_cond_wait (&player->pauseCond, &player->pauseMutex); } - 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(); - memset (&format, 0, sizeof (format)); - format.bits = 16; - format.channels = player->channels; - format.rate = player->samplerate; - format.byte_format = AO_FMT_NATIVE; - if ((player->audioOutDevice = ao_open_live (audioOutDriver, - &format, NULL)) == NULL) { - player->aoError = 1; - BarUiMsg (player->settings, MSG_ERR, - "Cannot open audio device\n"); - return WAITRESS_CB_RET_ERR; - } - - /* calc song length using the framerate of the first decoded frame */ - player->songDuration = (unsigned long long int) player->waith.request.contentLength / - ((unsigned long long int) player->mp3Frame.header.bitrate / - (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / 8LL); + pthread_mutex_unlock (&player->pauseMutex); - /* must be > PLAYER_SAMPLESIZE_INITIALIZED, otherwise time won't - * be visible to user (ugly, but mp3 decoding != aac decoding) */ - player->mode = PLAYER_RECV_DATA; - } - /* samples * length * channels */ - ao_play (player->audioOutDevice, (char *) madDecoded, - player->mp3Synth.pcm.length * 2 * 2); - - /* avoid division by 0 */ - if (player->mode == PLAYER_RECV_DATA) { - /* same calculation as in aac player; don't need to divide by - * channels, length is number of samples for _one_ channel */ - player->songPlayed += (unsigned long long int) player->mp3Synth.pcm.length * - (unsigned long long int) BAR_PLAYER_MS_TO_S_FACTOR / - (unsigned long long int) player->samplerate; + if (player->doQuit) { + av_free_packet (&pkt_orig); + break; } - if (BarPlayerCheckPauseQuit (player)) { - return WAITRESS_CB_RET_ERR; + if (pkt.stream_index != streamIdx) { + av_free_packet (&pkt_orig); + continue; } - } while (player->mp3Stream.error != MAD_ERROR_BUFLEN); - player->bufferRead += player->mp3Stream.next_frame - player->buffer; + do { + int got_frame = 0; - BarPlayerBufferMove (player); + const int decoded = avcodec_decode_audio4 (cctx, frame, &got_frame, + &pkt); + if (decoded < 0) { + softfail ("decode_audio4"); + } - return WAITRESS_CB_RET_OK; -} -#endif /* ENABLE_MAD */ + if (got_frame != 0) { + /* XXX: suppresses warning from resample filter */ + if (frame->pts == (int64_t) AV_NOPTS_VALUE) { + frame->pts = 0; + } + ret = av_buffersrc_write_frame (fabuf, frame); + assert (ret >= 0); + + while (true) { + AVFilterBufferRef *audioref = NULL; + if (av_buffersink_read (fbufsink, &audioref) < 0) { + /* try again next frame */ + break; + } -/* player thread; for every song a new thread is started - * @param audioPlayer structure - * @return PLAYER_RET_* - */ -void *BarPlayerThread (void *data) { - struct audioPlayer *player = data; - char extraHeaders[32]; - void *ret = PLAYER_RET_OK; - #ifdef ENABLE_FAAD - NeAACDecConfigurationPtr conf; - #endif - WaitressReturn_t wRet = WAITRESS_RET_ERR; - - /* init handles */ - player->waith.data = (void *) player; - /* extraHeaders will be initialized later */ - player->waith.extraHeaders = extraHeaders; - player->buffer = malloc (BAR_PLAYER_BUFSIZE); - - 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); - - player->waith.callback = BarPlayerAACCb; - break; - #endif /* ENABLE_FAAD */ + ret = avfilter_copy_buf_props (filteredFrame, audioref); + assert (ret >= 0); - #ifdef ENABLE_MAD - case PIANO_AF_MP3: - mad_stream_init (&player->mp3Stream); - mad_frame_init (&player->mp3Frame); - mad_synth_init (&player->mp3Synth); + const int numChannels = av_get_channel_layout_nb_channels ( + filteredFrame->channel_layout); + const int bps = av_get_bytes_per_sample(filteredFrame->format); + ao_play (aoDev, (char *) filteredFrame->data[0], + filteredFrame->nb_samples * numChannels * bps); - player->waith.callback = BarPlayerMp3Cb; - break; - #endif /* ENABLE_MAD */ + avfilter_unref_bufferp (&audioref); + } + } - default: - BarUiMsg (player->settings, MSG_ERR, "Unsupported audio format!\n"); - ret = (void *) PLAYER_RET_HARDFAIL; - goto cleanup; - break; - } - - player->mode = PLAYER_INITIALIZED; - - /* This loop should work around song abortions by requesting the - * missing part of the song */ - do { - snprintf (extraHeaders, sizeof (extraHeaders), "Range: bytes=%zu-\r\n", - player->bytesReceived); - wRet = WaitressFetchCall (&player->waith); - } while (wRet == WAITRESS_RET_PARTIAL_FILE || wRet == WAITRESS_RET_TIMEOUT - || wRet == WAITRESS_RET_READ_ERR); - - switch (player->audioFormat) { - #ifdef ENABLE_FAAD - case PIANO_AF_AACPLUS: - NeAACDecClose(player->aacHandle); - free (player->sampleSize); - break; - #endif /* ENABLE_FAAD */ + pkt.data += decoded; + pkt.size -= decoded; + } while (pkt.size > 0); - #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 */ + av_free_packet (&pkt_orig); - default: - /* this should never happen */ - assert (0); - break; + player->songPlayed = av_q2d (st->time_base) * (double) pkt.pts; } - if (player->aoError) { - ret = (void *) PLAYER_RET_HARDFAIL; +finish: + ao_close (aoDev); + if (fgraph != NULL) { + avfilter_graph_free (&fgraph); } - - /* Pandora sends broken audio url’s sometimes (“bad request”). ignore them. */ - if (wRet != WAITRESS_RET_OK && wRet != WAITRESS_RET_CB_ABORT) { - BarUiMsg (player->settings, MSG_ERR, "Cannot access audio file: %s\n", - WaitressErrorToStr (wRet)); - ret = (void *) PLAYER_RET_SOFTFAIL; + if (cctx != NULL) { + avcodec_close (cctx); + } + if (fctx != NULL) { + avformat_close_input (&fctx); + } + if (frame != NULL) { + avcodec_free_frame (&frame); + } + if (filteredFrame != NULL) { + avcodec_free_frame (&filteredFrame); } -cleanup: - ao_close (player->audioOutDevice); - WaitressFree (&player->waith); - free (player->buffer); - - player->mode = PLAYER_FINISHED_PLAYBACK; + player->mode = PLAYER_FINISHED; - return ret; + return (void *) pret; } + diff --git a/src/player.h b/src/player.h index d107e41..562c12d 100644 --- a/src/player.h +++ b/src/player.h @@ -26,90 +26,48 @@ THE SOFTWARE. #include "config.h" -#ifdef ENABLE_FAAD -#include <neaacdec.h> -#endif - -#ifdef ENABLE_MAD -#include <mad.h> -#endif - -#include <ao/ao.h> /* required for freebsd */ #include <sys/types.h> #include <pthread.h> #include <stdint.h> +#include <libavfilter/avfilter.h> #include <piano.h> #include <waitress.h> #include "settings.h" -#define BAR_PLAYER_MS_TO_S_FACTOR 1000 -#define BAR_PLAYER_BUFSIZE (WAITRESS_BUFFER_SIZE*2) - struct audioPlayer { - bool doQuit; /* protected by pauseMutex */ - bool doPause; /* protected by pauseMutex */ - unsigned char channels; - unsigned char aoError; + /* protected by pauseMutex */ + volatile bool doQuit; + volatile bool doPause; + pthread_mutex_t pauseMutex; + pthread_cond_t pauseCond; enum { - PLAYER_FREED = 0, /* thread is not running */ + PLAYER_DEAD = 0, /* thread is not running */ PLAYER_STARTING, /* thread is starting */ - PLAYER_INITIALIZED, /* decoder/waitress initialized */ - PLAYER_FOUND_ESDS, - PLAYER_AUDIO_INITIALIZED, /* audio device opened */ - PLAYER_FOUND_STSZ, - PLAYER_SAMPLESIZE_INITIALIZED, - PLAYER_RECV_DATA, /* playing track */ - PLAYER_FINISHED_PLAYBACK + PLAYER_PLAYING, + PLAYER_FINISHED, } mode; - PianoAudioFormat_t audioFormat; - - unsigned int scale; - float gain; - - /* duration and already played time; measured in milliseconds */ - unsigned long int songDuration; - unsigned long int songPlayed; - - unsigned long samplerate; - - size_t bufferFilled; - size_t bufferRead; - size_t bytesReceived; - - /* aac */ - #ifdef ENABLE_FAAD - /* stsz atom: sample sizes */ - size_t sampleSizeN; - size_t sampleSizeCurr; - uint32_t *sampleSize; - NeAACDecHandle aacHandle; - #endif - - /* mp3 */ - #ifdef ENABLE_MAD - struct mad_stream mp3Stream; - struct mad_frame mp3Frame; - struct mad_synth mp3Synth; - #endif - - /* audio out */ - ao_device *audioOutDevice; - const BarSettings_t *settings; - unsigned char *buffer; + AVFilterContext *fvolume; - pthread_mutex_t pauseMutex; - pthread_cond_t pauseCond; - WaitressHandle_t waith; + volatile double volume; + double gain; + char *url; + const BarSettings_t *settings; + + /* measured in seconds */ + volatile unsigned int songDuration; + volatile unsigned int songPlayed; }; enum {PLAYER_RET_OK = 0, PLAYER_RET_HARDFAIL = 1, PLAYER_RET_SOFTFAIL = 2}; void *BarPlayerThread (void *data); -unsigned int BarPlayerCalcScale (float); +void BarPlayerSetVolume (struct audioPlayer * const player); +void BarPlayerInit (); +void BarPlayerDestroy (); #endif /* _PLAYER_H */ @@ -735,8 +735,8 @@ void BarUiStartEventCmd (const BarSettings_t *settings, const char *type, "pRetStr=%s\n" "wRet=%i\n" "wRetStr=%s\n" - "songDuration=%lu\n" - "songPlayed=%lu\n" + "songDuration=%u\n" + "songPlayed=%u\n" "rating=%i\n" "detailUrl=%s\n", curSong == NULL ? "" : curSong->artist, diff --git a/src/ui_act.c b/src/ui_act.c index d5c6b2b..91a4cf7 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -51,6 +51,7 @@ static inline void BarUiDoSkipSong (struct audioPlayer *player) { pthread_mutex_lock (&player->pauseMutex); player->doQuit = true; + player->doPause = false; pthread_cond_broadcast (&player->pauseCond); pthread_mutex_unlock (&player->pauseMutex); } @@ -636,30 +637,25 @@ BarUiActCallback(BarUiActBookmark) { } } -static void BarUiActUpdateScale (BarApp_t *app) { - /* FIXME: assuming unsigned integer store is atomic operation */ - app->player.scale = BarPlayerCalcScale (app->player.gain + app->settings.volume); -} - /* decrease volume */ BarUiActCallback(BarUiActVolDown) { --app->settings.volume; - BarUiActUpdateScale (app); + BarPlayerSetVolume (&app->player); } /* increase volume */ BarUiActCallback(BarUiActVolUp) { ++app->settings.volume; - BarUiActUpdateScale (app); + BarPlayerSetVolume (&app->player); } /* reset volume */ BarUiActCallback(BarUiActVolReset) { app->settings.volume = 0; - BarUiActUpdateScale (app); + BarPlayerSetVolume (&app->player); } /* manage station (remove seeds or feedback) |