diff options
Diffstat (limited to 'src')
-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 |
5 files changed, 296 insertions, 569 deletions
@@ -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) |