summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoredward-p <micro.fedora@gmail.com>2018-09-15 13:57:39 +0200
committerLars-Dominik Braun <lars@6xq.net>2018-09-15 14:01:05 +0200
commitbbbdd99fdaacfe0a3c9a1237fa0d086907c1286f (patch)
tree0445df2e4d243e0f4f51a9a6e3658bf0335040df
parentcf82c5eba208ddc327f7620d1961c9c719c4e13c (diff)
downloadpianobar-bbbdd99fdaacfe0a3c9a1237fa0d086907c1286f.tar.gz
pianobar-bbbdd99fdaacfe0a3c9a1237fa0d086907c1286f.tar.bz2
pianobar-bbbdd99fdaacfe0a3c9a1237fa0d086907c1286f.zip
Implement audio buffering
Prevent stuttering on low-power devices like Android phones by moving playback to its own thread and decoupling it from decoding through a reasonably sized buffer. Fixes #665.
-rw-r--r--src/player.c140
-rw-r--r--src/player.h6
-rw-r--r--src/ui_act.c4
3 files changed, 108 insertions, 42 deletions
diff --git a/src/player.c b/src/player.c
index ac39e38..ff1f1c9 100644
--- a/src/player.c
+++ b/src/player.c
@@ -21,7 +21,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
-/* receive/play audio stream */
+/* receive/play audio stream.
+ *
+ * There are two threads involved here:
+ * BarPlayerThread
+ * Sets up the stream and fetches the data into a ffmpeg buffersrc
+ * BarAoPlayThread
+ * Reads data from the filter chain’s sink and hands it over to libao for
+ * playback.
+ *
+ */
#include "config.h"
@@ -74,6 +83,8 @@ void BarPlayerInit (player_t * const p, const BarSettings_t * const settings) {
pthread_mutex_init (&p->lock, NULL);
pthread_cond_init (&p->cond, NULL);
+ pthread_mutex_init (&p->aoplayLock, NULL);
+ pthread_cond_init (&p->aoplayCond, NULL);
BarPlayerReset (p);
p->settings = settings;
}
@@ -81,6 +92,8 @@ void BarPlayerInit (player_t * const p, const BarSettings_t * const settings) {
void BarPlayerDestroy (player_t * const p) {
pthread_cond_destroy (&p->cond);
pthread_mutex_destroy (&p->lock);
+ pthread_cond_destroy (&p->aoplayCond);
+ pthread_mutex_destroy (&p->aoplayLock);
avformat_network_deinit ();
ao_shutdown ();
@@ -356,12 +369,11 @@ static int play (player_t * const player) {
pkt.data = NULL;
pkt.size = 0;
- AVFrame *frame = NULL, *filteredFrame = NULL;
+ AVFrame *frame = NULL;
frame = av_frame_alloc ();
assert (frame != NULL);
- filteredFrame = av_frame_alloc ();
- assert (filteredFrame != NULL);
-
+ pthread_t aoplaythread;
+ pthread_create (&aoplaythread, NULL, BarAoPlayThread, player);
enum { FILL, DRAIN, DONE } drainMode = FILL;
int ret = 0;
while (!shouldQuit (player) && drainMode != DONE) {
@@ -377,6 +389,12 @@ static int play (player_t * const player) {
continue;
} else if (ret < 0) {
/* error, abort */
+ /* mark the EOF, so that BarAoPlayThread can quit*/
+ pthread_mutex_lock (&player->aoplayLock);
+ const int rt = av_buffersrc_add_frame (player->fabuf, NULL);
+ assert (rt == 0);
+ pthread_cond_broadcast (&player->aoplayCond);
+ pthread_mutex_unlock (&player->aoplayLock);
break;
} else {
/* fill buffer */
@@ -384,22 +402,17 @@ static int play (player_t * const player) {
}
}
- /* pausing */
- pthread_mutex_lock (&player->lock);
- if (player->doPause) {
- av_read_pause (player->fctx);
- do {
- pthread_cond_wait (&player->cond, &player->lock);
- } while (player->doPause);
- av_read_play (player->fctx);
- }
- pthread_mutex_unlock (&player->lock);
-
while (!shouldQuit (player)) {
ret = avcodec_receive_frame (cctx, frame);
if (ret == AVERROR_EOF) {
/* done draining */
drainMode = DONE;
+ /* mark the EOF*/
+ pthread_mutex_lock (&player->aoplayLock);
+ const int rt = av_buffersrc_add_frame (player->fabuf, NULL);
+ assert (rt == 0);
+ pthread_cond_broadcast (&player->aoplayCond);
+ pthread_mutex_unlock (&player->aoplayLock);
break;
} else if (ret != 0) {
/* no more output */
@@ -410,36 +423,31 @@ static int play (player_t * const player) {
if (frame->pts == (int64_t) AV_NOPTS_VALUE) {
frame->pts = 0;
}
+ pthread_mutex_lock (&player->aoplayLock);
ret = av_buffersrc_write_frame (player->fabuf, frame);
assert (ret >= 0);
-
- while (true) {
- if (av_buffersink_get_frame (player->fbufsink, filteredFrame) < 0) {
- /* try again next frame */
- break;
+ pthread_mutex_unlock (&player->aoplayLock);
+
+ int64_t bufferHealth = 0;
+ const int64_t minBufferHealth = 4; /* in seconds */
+ do {
+ pthread_mutex_lock (&player->aoplayLock);
+ bufferHealth = av_q2d (player->st->time_base) *
+ (double) (frame->pts - player->lastTimestamp);
+ if (bufferHealth > minBufferHealth) {
+ /* Buffer get healthy, resume */
+ pthread_cond_broadcast (&player->aoplayCond);
+ /* Buffer is healthy enough, wait */
+ pthread_cond_wait (&player->aoplayCond, &player->aoplayLock);
}
-
- const int numChannels = av_get_channel_layout_nb_channels (
- filteredFrame->channel_layout);
- const int bps = av_get_bytes_per_sample(filteredFrame->format);
- ao_play (player->aoDev, (char *) filteredFrame->data[0],
- filteredFrame->nb_samples * numChannels * bps);
-
- av_frame_unref (filteredFrame);
- }
+ pthread_mutex_unlock (&player->aoplayLock);
+ } while (bufferHealth > minBufferHealth);
}
- const unsigned int songPlayed = av_q2d (player->st->time_base) * (double) pkt.pts;
- pthread_mutex_lock (&player->lock);
- player->songPlayed = songPlayed;
- pthread_mutex_unlock (&player->lock);
- player->lastTimestamp = pkt.pts;
-
av_packet_unref (&pkt);
}
-
- av_frame_free (&filteredFrame);
av_frame_free (&frame);
+ pthread_join (aoplaythread, NULL);
return ret;
}
@@ -496,3 +504,59 @@ void *BarPlayerThread (void *data) {
return (void *) pret;
}
+void *BarAoPlayThread (void *data) {
+ assert (data != NULL);
+
+ player_t * const player = data;
+
+ AVFrame *filteredFrame = NULL;
+ filteredFrame = av_frame_alloc ();
+ assert (filteredFrame != NULL);
+
+ int ret;
+ while (!shouldQuit(player)) {
+ pthread_mutex_lock (&player->aoplayLock);
+ ret = av_buffersink_get_frame (player->fbufsink, filteredFrame);
+ if (ret == AVERROR_EOF || shouldQuit (player)) {
+ /* we are done here */
+ pthread_mutex_unlock (&player->aoplayLock);
+ break;
+ } else if (ret < 0) {
+ /* wait for more frames */
+ pthread_cond_wait (&player->aoplayCond, &player->aoplayLock);
+ pthread_mutex_unlock (&player->aoplayLock);
+ continue;
+ }
+ pthread_mutex_unlock (&player->aoplayLock);
+
+ const int numChannels = av_get_channel_layout_nb_channels (
+ filteredFrame->channel_layout);
+ const int bps = av_get_bytes_per_sample (filteredFrame->format);
+ ao_play (player->aoDev, (char *) filteredFrame->data[0],
+ filteredFrame->nb_samples * numChannels * bps);
+
+ const unsigned int songPlayed = av_q2d (player->st->time_base) *
+ (double) filteredFrame->pts;
+ pthread_mutex_lock (&player->lock);
+ player->songPlayed = songPlayed;
+
+ /* pausing */
+ if (player->doPause) {
+ do {
+ pthread_cond_wait (&player->cond, &player->lock);
+ } while (player->doPause);
+ }
+ pthread_mutex_unlock (&player->lock);
+
+ /* notify download thread, we might need more data */
+ pthread_mutex_lock (&player->aoplayLock);
+ player->lastTimestamp = filteredFrame->pts;
+ pthread_cond_broadcast (&player->aoplayCond);
+ pthread_mutex_unlock (&player->aoplayLock);
+
+ av_frame_unref (filteredFrame);
+ }
+ av_frame_free (&filteredFrame);
+
+ return (void *) 0;
+}
diff --git a/src/player.h b/src/player.h
index e9482c4..3179785 100644
--- a/src/player.h
+++ b/src/player.h
@@ -51,9 +51,8 @@ typedef enum {
typedef struct {
/* public attributes protected by mutex */
- pthread_mutex_t lock;
- pthread_cond_t cond; /* broadcast changes to doPause */
-
+ pthread_mutex_t lock, aoplayLock;
+ pthread_cond_t cond, aoplayCond; /* broadcast changes to doPause */
bool doQuit, doPause;
/* measured in seconds */
@@ -86,6 +85,7 @@ typedef struct {
enum {PLAYER_RET_OK = 0, PLAYER_RET_HARDFAIL = 1, PLAYER_RET_SOFTFAIL = 2};
void *BarPlayerThread (void *data);
+void *BarAoPlayThread (void *data);
void BarPlayerSetVolume (player_t * const player);
void BarPlayerInit (player_t * const p, const BarSettings_t * const settings);
void BarPlayerReset (player_t * const p);
diff --git a/src/ui_act.c b/src/ui_act.c
index eeb6fa6..ba340c1 100644
--- a/src/ui_act.c
+++ b/src/ui_act.c
@@ -57,6 +57,9 @@ static inline void BarUiDoSkipSong (player_t * const player) {
player->doPause = false;
pthread_cond_broadcast (&player->cond);
pthread_mutex_unlock (&player->lock);
+ pthread_mutex_lock (&player->aoplayLock);
+ pthread_cond_broadcast (&player->aoplayCond);
+ pthread_mutex_unlock (&player->aoplayLock);
}
/* transform station if necessary to allow changes like rename, rate, ...
@@ -881,4 +884,3 @@ BarUiActCallback(BarUiActManageStation) {
PianoDestroyStationInfo (&reqData.info);
}
-