From 54deede40a49d2947029608a6dd942302569ee1c Mon Sep 17 00:00:00 2001 From: Alexey Yakovenko Date: Mon, 14 Sep 2009 21:45:43 +0200 Subject: gapless playback WIP --- callbacks.c | 26 ++++----- main.c | 12 ++--- playlist.c | 41 --------------- playlist.h | 8 +-- plugins.c | 10 ++-- streamer.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ streamer.h | 5 ++ 7 files changed, 187 insertions(+), 86 deletions(-) diff --git a/callbacks.c b/callbacks.c index 26264582..2b300e19 100644 --- a/callbacks.c +++ b/callbacks.c @@ -642,8 +642,8 @@ on_voice1_clicked (GtkButton *button, gpointer user_data) { codec_lock (); - if (playlist_current.decoder && playlist_current.decoder->mutevoice) { - playlist_current.decoder->mutevoice (0, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); + if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { + str_playing_song.decoder->mutevoice (0, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); } codec_unlock (); } @@ -654,8 +654,8 @@ on_voice2_clicked (GtkButton *button, gpointer user_data) { codec_lock (); - if (playlist_current.decoder && playlist_current.decoder->mutevoice) { - playlist_current.decoder->mutevoice (1, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); + if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { + str_playing_song.decoder->mutevoice (1, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); } codec_unlock (); } @@ -666,8 +666,8 @@ on_voice3_clicked (GtkButton *button, gpointer user_data) { codec_lock (); - if (playlist_current.decoder && playlist_current.decoder->mutevoice) { - playlist_current.decoder->mutevoice (2, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); + if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { + str_playing_song.decoder->mutevoice (2, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); } codec_unlock (); } @@ -678,8 +678,8 @@ on_voice4_clicked (GtkButton *button, gpointer user_data) { codec_lock (); - if (playlist_current.decoder && playlist_current.decoder->mutevoice) { - playlist_current.decoder->mutevoice (3, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); + if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { + str_playing_song.decoder->mutevoice (3, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); } codec_unlock (); } @@ -690,8 +690,8 @@ on_voice5_clicked (GtkButton *button, gpointer user_data) { codec_lock (); - if (playlist_current.decoder && playlist_current.decoder->mutevoice) { - playlist_current.decoder->mutevoice (4, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); + if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { + str_playing_song.decoder->mutevoice (4, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); } codec_unlock (); } @@ -936,8 +936,8 @@ seekbar_draw (GtkWidget *widget) { pos = x; } else { - if (playlist_current.decoder && playlist_current.duration > 0) { - pos = streamer_get_playpos () / playlist_current.duration; + if (str_playing_song.decoder && str_playing_song.duration > 0) { + pos = streamer_get_playpos () / str_playing_song.duration; pos *= widget->allocation.width; } } @@ -1024,7 +1024,7 @@ on_seekbar_button_release_event (GtkWidget *widget, seekbar_moving = 0; seekbar_draw (widget); seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); - float time = event->x * playlist_current.duration / (widget->allocation.width); + float time = event->x * str_playing_song.duration / (widget->allocation.width); if (time < 0) { time = 0; } diff --git a/main.c b/main.c index b569bbba..7c817fa4 100644 --- a/main.c +++ b/main.c @@ -95,21 +95,21 @@ update_songinfo (void) { else if (p_isstopped ()) { strcpy (sbtext_new, "Stopped"); } - else if (playlist_current.decoder) { + else if (str_playing_song.decoder) { codec_lock (); - DB_decoder_t *c = playlist_current.decoder; + DB_decoder_t *c = str_playing_song.decoder; float playpos = streamer_get_playpos (); int minpos = playpos / 60; int secpos = playpos - minpos * 60; - int mindur = playlist_current.duration / 60; - int secdur = playlist_current.duration - mindur * 60; + int mindur = str_playing_song.duration / 60; + int secdur = str_playing_song.duration - mindur * 60; const char *mode = c->info.channels == 1 ? "Mono" : "Stereo"; int samplerate = c->info.samplerate; int bitspersample = c->info.bps; songpos = playpos; codec_unlock (); - snprintf (sbtext_new, 512, "[%s] %dHz | %d bit | %s | %d:%02d / %d:%02d | %d songs total", playlist_current.filetype ? playlist_current.filetype:"-", samplerate, bitspersample, mode, minpos, secpos, mindur, secdur, pl_getcount ()); + snprintf (sbtext_new, 512, "[%s] %dHz | %d bit | %s | %d:%02d / %d:%02d | %d songs total", str_playing_song.filetype ? str_playing_song.filetype:"-", samplerate, bitspersample, mode, minpos, secpos, mindur, secdur, pl_getcount ()); } if (strcmp (sbtext_new, sb_text)) { @@ -135,7 +135,7 @@ update_songinfo (void) { if (mainwin) { GtkWidget *widget = lookup_widget (mainwin, "seekbar"); // translate volume to seekbar pixels - songpos /= playlist_current.duration; + songpos /= str_playing_song.duration; songpos *= widget->allocation.width; if ((int)(songpos*2) != (int)(last_songpos*2)) { GDK_THREADS_ENTER(); diff --git a/playlist.c b/playlist.c index 151f8409..14ccd2cd 100644 --- a/playlist.c +++ b/playlist.c @@ -46,7 +46,6 @@ playItem_t *playlist_head[PL_MAX_ITERATORS]; playItem_t *playlist_tail[PL_MAX_ITERATORS]; -playItem_t playlist_current; playItem_t *playlist_current_ptr; int pl_count = 0; static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random @@ -644,46 +643,6 @@ pl_item_free (playItem_t *it) { } } -int -pl_set_current (playItem_t *it) { - int ret = 0; - int from = pl_get_idx_of (playlist_current_ptr); - int to = it ? pl_get_idx_of (it) : -1; - if (playlist_current.decoder) { - plug_trigger_event (DB_EV_SONGFINISHED); - } - codec_lock (); - if (playlist_current.decoder) { - playlist_current.decoder->free (); - } - pl_item_free (&playlist_current); - playlist_current_ptr = it; - if (it && it->decoder) { - // don't do anything on fail, streamer will take care - ret = it->decoder->init (DB_PLAYITEM (it)); - if (ret < 0) { -// pl_item_free (&playlist_current); -// playlist_current_ptr = NULL; -// return ret; -//// it->decoder->info.samplesPerSecond = -1; - } - } - if (playlist_current_ptr) { - streamer_reset (0); - } - if (it) { - it->played = 1; - it->started_timestamp = time (NULL); - pl_item_copy (&playlist_current, it); - } - codec_unlock (); - if (it) { - plug_trigger_event (DB_EV_SONGSTARTED); - } - messagepump_push (M_SONGCHANGED, 0, from, to); - return ret; -} - int pl_prevsong (void) { if (!playlist_head[PL_MAIN]) { diff --git a/playlist.h b/playlist.h index 51bf7124..40fdc7db 100644 --- a/playlist.h +++ b/playlist.h @@ -53,8 +53,8 @@ typedef struct playItem_s { extern playItem_t *playlist_head[PL_MAX_ITERATORS]; // head of linked list extern playItem_t *playlist_tail[PL_MAX_ITERATORS]; // tail of linked list -extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item -extern playItem_t playlist_current; // copy of playlist item being played (stays in memory even if removed from playlist) +extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item (or NULL) + extern int pl_count; int @@ -108,8 +108,8 @@ pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t * playItem_t * pl_insert_cue (playItem_t *after, const char *cuename, struct DB_decoder_s *decoder, const char *ftype, float duration); -int -pl_set_current (playItem_t *it); +//int +//pl_set_current (playItem_t *it); // returns -1 if theres no next song, or playlist finished // reason 0 means "song finished", 1 means "user clicked next" diff --git a/plugins.c b/plugins.c index f6ac266b..4a91641b 100644 --- a/plugins.c +++ b/plugins.c @@ -215,18 +215,18 @@ plug_playback_random (void) { float plug_playback_get_pos (void) { - if (playlist_current.duration <= 0) { + if (str_playing_song.duration <= 0) { return 0; } - return streamer_get_playpos () * 100 / playlist_current.duration; + return streamer_get_playpos () * 100 / str_playing_song.duration; } void plug_playback_set_pos (float pos) { - if (playlist_current.duration <= 0) { + if (str_playing_song.duration <= 0) { return; } - float t = pos * playlist_current.duration / 100.f; + float t = pos * str_playing_song.duration / 100.f; streamer_set_seek (t); } @@ -246,7 +246,7 @@ plug_trigger_event (int ev) { case DB_EV_SONGFINISHED: { DB_event_song_t *pev = malloc (sizeof (DB_event_song_t)); - pev->song = DB_PLAYITEM (&playlist_current); + pev->song = DB_PLAYITEM (&str_playing_song); event = DB_EVENT (pev); } break; diff --git a/streamer.c b/streamer.c index e351e41d..c6d85b00 100644 --- a/streamer.c +++ b/streamer.c @@ -44,7 +44,7 @@ static float g_fbuffer[200000]; // hack! static float g_srcbuffer[200000]; // hack! static int streaming_terminate; -#define STREAM_BUFFER_SIZE 200000 +#define STREAM_BUFFER_SIZE (200000*5) static int streambuffer_fill; static int bytes_until_next_song = 0; static char streambuffer[STREAM_BUFFER_SIZE]; @@ -57,6 +57,100 @@ static float seekpos = -1; static float playpos = 0; // play position of current song +playItem_t str_playing_song; +playItem_t str_streaming_song; +// remember pointers to original instances of playitems +static playItem_t *orig_playing_song; +static playItem_t *orig_streaming_song; + +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) + +// playlist must call that whenever item was removed +void +streamer_song_removed_notify (playItem_t *it) { + if (it == orig_playing_song) { + orig_playing_song = NULL; + } + if (it == orig_streaming_song) { + orig_streaming_song = NULL; + // FIXME: must queue new next song for streaming + } +} + +// that must be called after last sample from str_playing_song was done reading +static int +streamer_set_current (playItem_t *it) { + trace ("streamer_set_current %p, buns=%d\n", it); + if (str_streaming_song.decoder) { + str_streaming_song.decoder->free (); + } + pl_item_free (&str_streaming_song); + orig_streaming_song = it; + if (!it) { + return 0; + } + if (it->decoder) { + pl_item_copy (&str_streaming_song, it); + int ret = it->decoder->init (DB_PLAYITEM (it)); + if (ret < 0) { + trace ("decoder->init returned %d\n", ret); + return ret; + } + else { + trace ("bps=%d, channels=%d, samplerate=%d\n", it->decoder->info.bps, it->decoder->info.channels, it->decoder->info.samplerate); + } + streamer_reset (0); // reset SRC + } + else { + trace ("no decoder in playitem!\n"); + orig_streaming_song = NULL; + return -1; + } + if (bytes_until_next_song == -1) { + bytes_until_next_song = 0; + } + return 0; +} + +#if 0 +static int +str_set_current (playItem_t *it) { + int ret = 0; + int from = pl_get_idx_of (playlist_current_ptr); + int to = it ? pl_get_idx_of (it) : -1; + if (str_playing_song.decoder) { + plug_trigger_event (DB_EV_SONGFINISHED); + } + codec_lock (); + if (str_playing_song.decoder) { + str_playing_song.decoder->free (); + } + pl_item_free (&str_playing_song); + playlist_current_ptr = it; + if (it && it->decoder) { + // don't do anything on fail, streamer will take care + ret = it->decoder->init (DB_PLAYITEM (it)); +// if (ret < 0) { +// } + } + if (playlist_current_ptr) { + streamer_reset (0); + } + if (it) { + it->played = 1; + it->started_timestamp = time (NULL); + pl_item_copy (&str_playing_song, it); + } + codec_unlock (); + if (it) { + plug_trigger_event (DB_EV_SONGSTARTED); + } + messagepump_push (M_SONGCHANGED, 0, from, to); + return ret; +} +#endif + float streamer_get_playpos (void) { return playpos; @@ -64,10 +158,12 @@ streamer_get_playpos (void) { void streamer_set_nextsong (int song, int pstate) { + trace ("streamer_set_nextsong %d %d\n", song, pstate); nextsong = song; nextsong_pstate = pstate; if (p_isstopped ()) { // no sense to wait until end of previous song, reset buffer + trace ("fuck!\n"); bytes_until_next_song = 0; } } @@ -88,7 +184,8 @@ streamer_thread (uintptr_t ctx) { while (!streaming_terminate) { struct timeval tm1; gettimeofday (&tm1, NULL); - if (nextsong >= 0 && bytes_until_next_song == 0) { + if (nextsong >= 0) { // start streaming next song + trace ("nextsong=%d\n", nextsong); int sng = nextsong; int pstate = nextsong_pstate; nextsong = -1; @@ -96,14 +193,14 @@ streamer_thread (uintptr_t ctx) { codecleft = 0; codec_unlock (); if (badsong == sng) { - //printf ("looped to bad file. stopping...\n"); + trace ("looped to bad file. stopping...\n"); streamer_set_nextsong (-2, 1); badsong = -1; continue; } - int ret = pl_set_current (pl_get_for_idx (sng)); + int ret = streamer_set_current (pl_get_for_idx (sng)); if (ret < 0) { - //printf ("bad file in playlist, skipping...\n"); + trace ("bad file in playlist, skipping...\n"); // remember bad song number in case of looping if (badsong == -1) { badsong = sng; @@ -114,7 +211,6 @@ streamer_thread (uintptr_t ctx) { continue; } badsong = -1; - playpos = 0; if (pstate == 0) { p_stop (); } @@ -126,10 +222,11 @@ streamer_thread (uintptr_t ctx) { } } else if (nextsong == -2) { + trace ("nextsong=-2\n"); nextsong = -1; p_stop (); messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1); - pl_set_current (NULL); + streamer_set_current (NULL); continue; } else if (p_isstopped ()) { @@ -137,12 +234,46 @@ streamer_thread (uintptr_t ctx) { continue; } + if (bytes_until_next_song == 0) { + trace ("bytes_until_next_song=0, starting playback of new song\n"); + bytes_until_next_song = -1; + // plugin will get pointer to str_playing_song + trace ("sending songfinished to plugins\n"); + plug_trigger_event (DB_EV_SONGFINISHED); + // copy streaming into playing + pl_item_copy (&str_playing_song, &str_streaming_song); + int from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1; + int to = orig_streaming_song ? pl_get_idx_of (orig_streaming_song) : -1; + trace ("from=%d, to=%d\n", from, to); + orig_playing_song = orig_streaming_song; + str_playing_song.played = 1; + str_playing_song.started_timestamp = time (NULL); + playlist_current_ptr = orig_playing_song; + // that is needed for playlist drawing + trace ("sending songchanged\n"); + messagepump_push (M_SONGCHANGED, 0, from, to); + // plugin will get pointer to new str_playing_song + trace ("sending songstarted to plugins\n"); + plug_trigger_event (DB_EV_SONGSTARTED); + playpos = 0; + } + if (seekpos >= 0) { + trace ("seeking to %f\n", seekpos); float pos = seekpos; seekpos = -1; - if (playlist_current.decoder && playlist_current.decoder->seek (pos) >= 0) { + + if (orig_playing_song != orig_streaming_song) { + // restart playing from new position + pl_item_free (&str_streaming_song); + orig_streaming_song = orig_playing_song; + pl_item_copy (&str_streaming_song, orig_streaming_song); + bytes_until_next_song = -1; + } + + if (str_playing_song.decoder && str_playing_song.decoder->seek (pos) >= 0) { streamer_lock (); - playpos = playlist_current.decoder->info.readpos; + playpos = str_playing_song.decoder->info.readpos; streambuffer_fill = 0; streamer_unlock (); codec_lock (); @@ -156,7 +287,7 @@ streamer_thread (uintptr_t ctx) { int alloc_time = 1000 / (96000 * 4 / 4096); streamer_lock (); - if (streambuffer_fill < (STREAM_BUFFER_SIZE-4096) && bytes_until_next_song == 0) { + if (streambuffer_fill < (STREAM_BUFFER_SIZE-4096)/* && bytes_until_next_song == 0*/) { int sz = STREAM_BUFFER_SIZE - streambuffer_fill; int minsize = 4096; if (streambuffer_fill < 16384) { @@ -178,6 +309,7 @@ streamer_thread (uintptr_t ctx) { if (alloc_time > 0) { usleep (alloc_time * 1000); } +// trace ("fill: %d/%d\n", streambuffer_fill, STREAM_BUFFER_SIZE); } if (src) { @@ -222,8 +354,9 @@ streamer_read_async (char *bytes, int size) { for (;;) { int bytesread = 0; codec_lock (); - DB_decoder_t *decoder = playlist_current.decoder; + DB_decoder_t *decoder = str_streaming_song.decoder; if (!decoder) { + trace ("no decoder!\n"); codec_unlock (); break; } @@ -356,7 +489,8 @@ streamer_read_async (char *bytes, int size) { } else { // that means EOF - if (bytes_until_next_song == 0) { + if (bytes_until_next_song < 0) { + trace ("finished streaming song, queueing next\n"); bytes_until_next_song = streambuffer_fill; pl_nextsong (0); } @@ -388,13 +522,16 @@ streamer_read (char *bytes, int size) { } streambuffer_fill -= sz; playpos += (float)sz/p_get_rate ()/4.f; - playlist_current.playtime += (float)sz/p_get_rate ()/4.f; + str_streaming_song.playtime += (float)sz/p_get_rate ()/4.f; if (playlist_current_ptr) { - playlist_current_ptr->playtime = playlist_current.playtime; + playlist_current_ptr->playtime = str_streaming_song.playtime; } - bytes_until_next_song -= sz; - if (bytes_until_next_song < 0) { - bytes_until_next_song = 0; + if (bytes_until_next_song > 0) { + bytes_until_next_song -= sz; + if (bytes_until_next_song < 0) { + bytes_until_next_song = 0; + } + trace ("buns: %d\n", bytes_until_next_song); } } streamer_unlock (); diff --git a/streamer.h b/streamer.h index 7c0a987a..80668f7d 100644 --- a/streamer.h +++ b/streamer.h @@ -18,6 +18,11 @@ #ifndef __STREAMER_H #define __STREAMER_H +#include "playlist.h" + +extern playItem_t str_playing_song; +extern playItem_t str_streaming_song; + int streamer_init (void); -- cgit v1.2.3