summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL36
-rw-r--r--Makefile30
-rw-r--r--src/main.c75
-rw-r--r--src/player.c690
-rw-r--r--src/player.h84
-rw-r--r--src/ui.c4
-rw-r--r--src/ui_act.c12
7 files changed, 304 insertions, 627 deletions
diff --git a/INSTALL b/INSTALL
index 3283027..f3db418 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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`
diff --git a/Makefile b/Makefile
index 2867da1..2267419 100644
--- a/Makefile
+++ b/Makefile
@@ -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 \
diff --git a/src/main.c b/src/main.c
index 9fa94eb..eeb4279 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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 */
diff --git a/src/ui.c b/src/ui.c
index 4bd70fd..36f5c25 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -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)