summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars-Dominik Braun <lars@6xq.net>2014-07-21 17:17:03 +0200
committerLars-Dominik Braun <lars@6xq.net>2014-07-21 17:17:03 +0200
commitd16d6eb53869e8380ccfbe7dbc2c09d98c40ab6c (patch)
treef7bb0a9b5b1ff9a99082e69aa5ecf9d171acc274
parent8172003638eb2e5bb38e9a98be0f86a0c6e1cf1f (diff)
downloadpianobar-windows-d16d6eb53869e8380ccfbe7dbc2c09d98c40ab6c.tar.gz
pianobar-windows-d16d6eb53869e8380ccfbe7dbc2c09d98c40ab6c.tar.bz2
pianobar-windows-d16d6eb53869e8380ccfbe7dbc2c09d98c40ab6c.zip
Refactor audio player, add retry timeout
Fixes #442.
-rw-r--r--src/config.h25
-rw-r--r--src/main.h2
-rw-r--r--src/player.c276
-rw-r--r--src/player.h23
-rw-r--r--src/ui.c2
-rw-r--r--src/ui.h2
-rw-r--r--src/ui_act.c2
7 files changed, 221 insertions, 111 deletions
diff --git a/src/config.h b/src/config.h
index 6d7147d..fb8a041 100644
--- a/src/config.h
+++ b/src/config.h
@@ -6,4 +6,29 @@
#define VERSION "2014.06.08-dev"
+/* ffmpeg/libav quirks detection
+ * ffmpeg’s micro versions always start at 100, that’s how we can distinguish
+ * ffmpeg and libav */
+#include <libavfilter/version.h>
+
+/* is "timeout" option present (all versions of ffmpeg, not libav) */
+#if LIBAVFILTER_VERSION_MICRO >= 100
+#define HAVE_AV_TIMEOUT
+#endif
+
+/* does graph_send_command exist (ffmpeg >=2.2) */
+#if LIBAVFILTER_VERSION_MAJOR == 4 && \
+ LIBAVFILTER_VERSION_MICRO >= 100
+#define HAVE_AVFILTER_GRAPH_SEND_COMMAND
+#endif
+
+/* need avcodec.h (ffmpeg 1.2) */
+#if LIBAVFILTER_VERSION_MAJOR == 3 && \
+ LIBAVFILTER_VERSION_MINOR <= 42 && \
+ LIBAVFILTER_VERSION_MINOR > 32 && \
+ LIBAVFILTER_VERSION_MICRO >= 100
+#define HAVE_AV_BUFFERSINK_GET_BUFFER_REF
+#define HAVE_LIBAVFILTER_AVCODEC_H
+#endif
+
#endif /* SRC_CONFIG_H_S6A1C09K */
diff --git a/src/main.h b/src/main.h
index 9567e06..12887ea 100644
--- a/src/main.h
+++ b/src/main.h
@@ -34,7 +34,7 @@ THE SOFTWARE.
typedef struct {
PianoHandle_t ph;
WaitressHandle_t waith;
- struct audioPlayer player;
+ player_t player;
BarSettings_t settings;
/* first item is current song */
PianoSong_t *playlist;
diff --git a/src/player.c b/src/player.c
index 3a2c9fa..5421310 100644
--- a/src/player.c
+++ b/src/player.c
@@ -31,28 +31,9 @@ THE SOFTWARE.
#include <assert.h>
#include <arpa/inet.h>
-/* ffmpeg/libav quirks
- * ffmpeg’s micro versions always start at 100, that’s how we can distinguish
- * ffmpeg and libav */
-#include <libavfilter/version.h>
-/* ffmpeg >=2.2 */
-#if LIBAVFILTER_VERSION_MAJOR == 4 && \
- LIBAVFILTER_VERSION_MICRO >= 100
-#define HAVE_AVFILTER_GRAPH_SEND_COMMAND
-#endif
-
-/* ffmpeg 1.2 */
-#if LIBAVFILTER_VERSION_MAJOR == 3 && \
- LIBAVFILTER_VERSION_MINOR <= 42 && \
- LIBAVFILTER_VERSION_MINOR > 32 && \
- LIBAVFILTER_VERSION_MICRO >= 100
-#define HAVE_AV_BUFFERSINK_GET_BUFFER_REF
-#define HAVE_LIBAVFILTER_AVCODEC_H
-#endif
+#include "config.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>
@@ -64,12 +45,17 @@ THE SOFTWARE.
#endif
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
+#ifndef HAVE_AV_TIMEOUT
+#include <libavutil/time.h>
+#endif
#include "player.h"
-#include "config.h"
#include "ui.h"
#include "ui_types.h"
+/* default sample format */
+const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16;
+
static void printError (const BarSettings_t * const settings,
const char * const msg, int ret) {
char avmsg[128];
@@ -97,7 +83,7 @@ void BarPlayerDestroy () {
/* Update volume filter
*/
-void BarPlayerSetVolume (struct audioPlayer * const player) {
+void BarPlayerSetVolume (player_t * const player) {
assert (player != NULL);
assert (player->fvolume != NULL);
@@ -124,51 +110,74 @@ void BarPlayerSetVolume (struct audioPlayer * const player) {
#define softfail(msg) \
printError (player->settings, msg, ret); \
- pret = PLAYER_RET_SOFTFAIL; \
- goto finish;
+ return false;
-/* player thread; for every song a new thread is started
- * @param audioPlayer structure
- * @return PLAYER_RET_*
+#ifndef HAVE_AV_TIMEOUT
+/* interrupt callback for libav, which lacks a timeout option
+ *
+ * obviously calling ping() a lot of times and then calling av_gettime here
+ * again is rather inefficient.
*/
-void *BarPlayerThread (void *data) {
- assert (data != NULL);
+static int intCb (void * const data) {
+ player_t * const player = data;
+ assert (player != NULL);
+ /* 10 seconds timeout (usec) */
+ return (av_gettime () - player->ping) > 10*1000000;
+}
- struct audioPlayer * const player = data;
- int ret;
- intptr_t pret = PLAYER_RET_OK;
- const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16;
+#define ping() player->ping = av_gettime ()
+#else
+#define ping()
+#endif
- ao_device *aoDev = NULL;
- ao_sample_format aoFmt;
+static bool openStream (player_t * const player) {
+ assert (player != NULL);
+ /* no leak? */
+ assert (player->fctx == NULL);
- AVFrame *frame = NULL, *filteredFrame = NULL;
- AVFormatContext *fctx = NULL;
- AVCodecContext *cctx = NULL;
+ int ret;
/* stream setup */
- if ((ret = avformat_open_input (&fctx, player->url, NULL, NULL)) < 0) {
+ AVDictionary *options = NULL;
+#ifdef HAVE_AV_TIMEOUT
+ /* 10 seconds timeout on TCP r/w */
+ av_dict_set (&options, "timeout", "10000000", 0);
+#else
+ /* libav does not support the timeout option above. the workaround stores
+ * the current time with ping() now and then, registers an interrupt
+ * callback (below) and compares saved/current time in this callback. it’s
+ * not bullet-proof, but seems to work fine for av_read_frame. */
+ player->fctx = avformat_alloc_context ();
+ player->fctx->interrupt_callback.callback = intCb;
+ player->fctx->interrupt_callback.opaque = player;
+#endif
+
+ assert (player->url != NULL);
+ ping ();
+ if ((ret = avformat_open_input (&player->fctx, player->url, NULL, &options)) < 0) {
softfail ("Unable to open audio file");
}
- if ((ret = avformat_find_stream_info (fctx, NULL)) < 0) {
+ ping ();
+ if ((ret = avformat_find_stream_info (player->fctx, NULL)) < 0) {
softfail ("find_stream_info");
}
/* ignore all streams, undone for audio stream below */
- for (size_t i = 0; i < fctx->nb_streams; i++) {
- fctx->streams[i]->discard = AVDISCARD_ALL;
+ for (size_t i = 0; i < player->fctx->nb_streams; i++) {
+ player->fctx->streams[i]->discard = AVDISCARD_ALL;
}
- const int streamIdx = av_find_best_stream (fctx, AVMEDIA_TYPE_AUDIO, -1,
- -1, NULL, 0);
- if (streamIdx < 0) {
+ ping ();
+ player->streamIdx = av_find_best_stream (player->fctx, AVMEDIA_TYPE_AUDIO,
+ -1, -1, NULL, 0);
+ if (player->streamIdx < 0) {
softfail ("find_best_stream");
}
- AVStream * const st = fctx->streams[streamIdx];
- cctx = st->codec;
- st->discard = AVDISCARD_DEFAULT;
+ player->st = player->fctx->streams[player->streamIdx];
+ AVCodecContext * const cctx = player->st->codec;
+ player->st->discard = AVDISCARD_DEFAULT;
/* decoder setup */
AVCodec * const decoder = avcodec_find_decoder (cctx->codec_id);
@@ -180,32 +189,39 @@ void *BarPlayerThread (void *data) {
softfail ("codec_open2");
}
- frame = avcodec_alloc_frame ();
- assert (frame != NULL);
- filteredFrame = avcodec_alloc_frame ();
- assert (filteredFrame != NULL);
+ if (player->lastTimestamp > 0) {
+ ping ();
+ av_seek_frame (player->fctx, player->streamIdx, player->lastTimestamp, 0);
+ }
- AVPacket pkt;
- av_init_packet (&pkt);
- pkt.data = NULL;
- pkt.size = 0;
+ player->songPlayed = 0;
+ player->songDuration = av_q2d (player->st->time_base) *
+ (double) player->st->duration;
+ player->mode = PLAYER_PLAYING;
+ return true;
+}
+
+/* setup filter chain
+ */
+static bool openFilter (player_t * const player) {
/* filter setup */
char strbuf[256];
+ int ret = 0;
+ AVCodecContext * const cctx = player->st->codec;
if ((player->fgraph = avfilter_graph_alloc ()) == NULL) {
softfail ("graph_alloc");
}
/* abuffer */
- AVFilterContext *fabuf = NULL;
- AVRational time_base = st->time_base;
+ AVRational time_base = player->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,
+ if ((ret = avfilter_graph_create_filter (&player->fabuf,
avfilter_get_by_name ("abuffer"), NULL, strbuf, NULL,
player->fgraph)) < 0) {
softfail ("create_filter abuffer");
@@ -230,17 +246,16 @@ void *BarPlayerThread (void *data) {
}
/* abuffersink */
- AVFilterContext *fbufsink = NULL;
- if ((ret = avfilter_graph_create_filter (&fbufsink,
+ if ((ret = avfilter_graph_create_filter (&player->fbufsink,
avfilter_get_by_name ("abuffersink"), NULL, NULL, NULL,
player->fgraph)) < 0) {
softfail ("create_filter abuffersink");
}
/* connect filter: abuffer -> volume -> aformat -> abuffersink */
- if (avfilter_link (fabuf, 0, player->fvolume, 0) != 0 ||
+ if (avfilter_link (player->fabuf, 0, player->fvolume, 0) != 0 ||
avfilter_link (player->fvolume, 0, fafmt, 0) != 0 ||
- avfilter_link (fafmt, 0, fbufsink, 0) != 0) {
+ avfilter_link (fafmt, 0, player->fbufsink, 0) != 0) {
softfail ("filter_link");
}
@@ -248,7 +263,15 @@ void *BarPlayerThread (void *data) {
softfail ("graph_config");
}
- /* setup libao */
+ return true;
+}
+
+/* setup libao
+ */
+static bool openDevice (player_t * const player) {
+ AVCodecContext * const cctx = player->st->codec;
+
+ ao_sample_format aoFmt;
memset (&aoFmt, 0, sizeof (aoFmt));
aoFmt.bits = av_get_bytes_per_sample (avformat) * 8;
assert (aoFmt.bits > 0);
@@ -257,49 +280,64 @@ void *BarPlayerThread (void *data) {
aoFmt.byte_format = AO_FMT_NATIVE;
int driver = ao_default_driver_id ();
- if ((aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) {
+ if ((player->aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) {
BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n");
- pret = PLAYER_RET_HARDFAIL;
- goto finish;
+ return false;
}
- player->songPlayed = 0;
- player->songDuration = av_q2d (st->time_base) * (double) st->duration;
- player->mode = PLAYER_PLAYING;
+ return true;
+}
+
+/* decode and play stream. returns 0 or av error code.
+ */
+static int play (player_t * const player) {
+ assert (player != NULL);
+
+ AVPacket pkt;
+ av_init_packet (&pkt);
+ pkt.data = NULL;
+ pkt.size = 0;
+
+ AVFrame *frame = NULL, *filteredFrame = NULL;
+ frame = avcodec_alloc_frame ();
+ assert (frame != NULL);
+ filteredFrame = avcodec_alloc_frame ();
+ assert (filteredFrame != NULL);
+
+ while (!player->doQuit) {
+ ping ();
+ int ret = av_read_frame (player->fctx, &pkt);
+ if (ret < 0) {
+ av_free_packet (&pkt);
+ return ret;
+ } else if (pkt.stream_index != player->streamIdx) {
+ av_free_packet (&pkt);
+ continue;
+ }
- 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);
+ av_read_play (player->fctx);
break;
} else {
- av_read_pause (fctx);
+ av_read_pause (player->fctx);
}
pthread_cond_wait (&player->pauseCond, &player->pauseMutex);
}
pthread_mutex_unlock (&player->pauseMutex);
- if (player->doQuit) {
- av_free_packet (&pkt_orig);
- break;
- }
-
- if (pkt.stream_index != streamIdx) {
- av_free_packet (&pkt_orig);
- continue;
- }
-
do {
int got_frame = 0;
- const int decoded = avcodec_decode_audio4 (cctx, frame, &got_frame,
- &pkt);
+ const int decoded = avcodec_decode_audio4 (player->st->codec,
+ frame, &got_frame, &pkt);
if (decoded < 0) {
- softfail ("decode_audio4");
+ /* skip this one */
+ break;
}
if (got_frame != 0) {
@@ -307,16 +345,17 @@ void *BarPlayerThread (void *data) {
if (frame->pts == (int64_t) AV_NOPTS_VALUE) {
frame->pts = 0;
}
- ret = av_buffersrc_write_frame (fabuf, frame);
+ ret = av_buffersrc_write_frame (player->fabuf, frame);
assert (ret >= 0);
while (true) {
AVFilterBufferRef *audioref = NULL;
#ifdef HAVE_AV_BUFFERSINK_GET_BUFFER_REF
/* ffmpeg’s compatibility layer is broken in some releases */
- if (av_buffersink_get_buffer_ref (fbufsink, &audioref, 0) < 0) {
+ if (av_buffersink_get_buffer_ref (player->fbufsink,
+ &audioref, 0) < 0) {
#else
- if (av_buffersink_read (fbufsink, &audioref) < 0) {
+ if (av_buffersink_read (player->fbufsink, &audioref) < 0) {
#endif
/* try again next frame */
break;
@@ -328,7 +367,7 @@ void *BarPlayerThread (void *data) {
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],
+ ao_play (player->aoDev, (char *) filteredFrame->data[0],
filteredFrame->nb_samples * numChannels * bps);
avfilter_unref_bufferp (&audioref);
@@ -341,26 +380,57 @@ void *BarPlayerThread (void *data) {
av_free_packet (&pkt_orig);
- player->songPlayed = av_q2d (st->time_base) * (double) pkt.pts;
+ player->songPlayed = av_q2d (player->st->time_base) * (double) pkt.pts;
+ player->lastTimestamp = pkt.pts;
}
-finish:
- ao_close (aoDev);
+ avcodec_free_frame (&filteredFrame);
+ avcodec_free_frame (&frame);
+
+ return 0;
+}
+
+static void finish (player_t * const player) {
+ ao_close (player->aoDev);
+ player->aoDev = NULL;
if (player->fgraph != NULL) {
avfilter_graph_free (&player->fgraph);
+ player->fgraph = NULL;
}
- if (cctx != NULL) {
- avcodec_close (cctx);
+ if (player->st != NULL && player->st->codec != NULL) {
+ avcodec_close (player->st->codec);
+ player->st = NULL;
}
- if (fctx != NULL) {
- avformat_close_input (&fctx);
- }
- if (frame != NULL) {
- avcodec_free_frame (&frame);
- }
- if (filteredFrame != NULL) {
- avcodec_free_frame (&filteredFrame);
+ if (player->fctx != NULL) {
+ avformat_close_input (&player->fctx);
}
+}
+
+/* player thread; for every song a new thread is started
+ * @param audioPlayer structure
+ * @return PLAYER_RET_*
+ */
+void *BarPlayerThread (void *data) {
+ assert (data != NULL);
+
+ player_t * const player = data;
+ intptr_t pret = PLAYER_RET_OK;
+
+ bool retry = false;
+ do {
+ if (openStream (player)) {
+ if (openFilter (player) && openDevice (player)) {
+ retry = play (player) == AVERROR_INVALIDDATA;
+ } else {
+ /* filter missing or audio device busy */
+ pret = PLAYER_RET_HARDFAIL;
+ }
+ } else {
+ /* stream not found */
+ pret = PLAYER_RET_SOFTFAIL;
+ }
+ finish (player);
+ } while (retry);
player->mode = PLAYER_FINISHED;
diff --git a/src/player.h b/src/player.h
index 2c89c93..eb06d64 100644
--- a/src/player.h
+++ b/src/player.h
@@ -31,14 +31,17 @@ THE SOFTWARE.
#include <pthread.h>
#include <stdint.h>
+#include <ao/ao.h>
+#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/avfiltergraph.h>
#include <piano.h>
#include <waitress.h>
#include "settings.h"
+#include "config.h"
-struct audioPlayer {
+typedef struct {
/* protected by pauseMutex */
volatile bool doQuit;
volatile bool doPause;
@@ -52,9 +55,21 @@ struct audioPlayer {
PLAYER_FINISHED,
} mode;
+ /* libav */
AVFilterContext *fvolume;
AVFilterGraph *fgraph;
-
+ AVFormatContext *fctx;
+ AVStream *st;
+ AVFilterContext *fbufsink, *fabuf;
+ int streamIdx;
+ int64_t lastTimestamp;
+#ifndef HAVE_AV_TIMEOUT
+ int64_t ping;
+#endif
+
+ ao_device *aoDev;
+
+ /* settings */
volatile double volume;
double gain;
char *url;
@@ -63,12 +78,12 @@ struct audioPlayer {
/* measured in seconds */
volatile unsigned int songDuration;
volatile unsigned int songPlayed;
-};
+} player_t;
enum {PLAYER_RET_OK = 0, PLAYER_RET_HARDFAIL = 1, PLAYER_RET_SOFTFAIL = 2};
void *BarPlayerThread (void *data);
-void BarPlayerSetVolume (struct audioPlayer * const player);
+void BarPlayerSetVolume (player_t * const player);
void BarPlayerInit ();
void BarPlayerDestroy ();
diff --git a/src/ui.c b/src/ui.c
index 5d5b816..6259ba1 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -684,7 +684,7 @@ size_t BarUiListSongs (const BarSettings_t *settings,
*/
void BarUiStartEventCmd (const BarSettings_t *settings, const char *type,
const PianoStation_t *curStation, const PianoSong_t *curSong,
- const struct audioPlayer *player, PianoStation_t *stations,
+ const player_t * const player, PianoStation_t *stations,
PianoReturn_t pRet, WaitressReturn_t wRet) {
pid_t chld;
int pipeFd[2];
diff --git a/src/ui.h b/src/ui.h
index 96ad32f..60262f8 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -49,7 +49,7 @@ void BarUiPrintSong (const BarSettings_t *, const PianoSong_t *,
const PianoStation_t *);
size_t BarUiListSongs (const BarSettings_t *, const PianoSong_t *, const char *);
void BarUiStartEventCmd (const BarSettings_t *, const char *,
- const PianoStation_t *, const PianoSong_t *, const struct audioPlayer *,
+ const PianoStation_t *, const PianoSong_t *, const player_t *,
PianoStation_t *, PianoReturn_t, WaitressReturn_t);
int BarUiPianoCall (BarApp_t * const, PianoRequestType_t,
void *, PianoReturn_t *, WaitressReturn_t *);
diff --git a/src/ui_act.c b/src/ui_act.c
index 91a4cf7..7515396 100644
--- a/src/ui_act.c
+++ b/src/ui_act.c
@@ -46,7 +46,7 @@ THE SOFTWARE.
/* helper to _really_ skip a song (unlock mutex, quit player)
* @param player handle
*/
-static inline void BarUiDoSkipSong (struct audioPlayer *player) {
+static inline void BarUiDoSkipSong (player_t * const player) {
assert (player != NULL);
pthread_mutex_lock (&player->pauseMutex);