/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009 Alexey Yakovenko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "threading.h" #include "codec.h" #include "playlist.h" #include "common.h" #include "streamer.h" #include "playback.h" #include "messagepump.h" #include "conf.h" #include "plugins.h" #include "optmath.h" #include "volume.h" #include "vfs.h" //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) static intptr_t streamer_tid; static int src_quality; static SRC_STATE *src; static SRC_DATA srcdata; static int codecleft; static int conf_replaygain_mode = 0; static int conf_replaygain_scale = 1; // that's buffer for resampling. // our worst case is 192KHz downsampling to 22050Hz with 2048 sample output buffer #define INPUT_BUFFER_SIZE (2048*192000/22050*8) static char g_readbuffer[INPUT_BUFFER_SIZE]; // fbuffer contains readbuffer converted to floating point, to pass to SRC static float g_fbuffer[INPUT_BUFFER_SIZE]; // output SRC buffer - can't really exceed INPUT_BUFFER_SIZE #define SRC_BUFFER_SIZE (INPUT_BUFFER_SIZE*2) static float g_srcbuffer[SRC_BUFFER_SIZE]; static int streaming_terminate; static playItem_t *streamer_initializing_item; // buffer up to 3 seconds at 44100Hz stereo #define STREAM_BUFFER_SIZE 0x80000 // slightly more than 3 seconds of 44100 stereo #define STREAM_BUFFER_MASK 0x7ffff //#define STREAM_BUFFER_SIZE 0x10000 // slightly more than 3 seconds of 44100 stereo //#define STREAM_BUFFER_MASK 0xffff static int streambuffer_fill; static int streambuffer_pos; static int bytes_until_next_song = 0; static char streambuffer[STREAM_BUFFER_SIZE]; static uintptr_t mutex; static uintptr_t decodemutex; static int nextsong = -1; static int nextsong_pstate = -1; static int badsong = -1; static float seekpos = -1; static float playpos = 0; // play position of current song static int avg_bitrate = -1; // avg bitrate of current song static int last_bitrate = -1; // last bitrate of current song static int prevtrack_samplerate = -1; 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; static int streamer_buffering; // to allow interruption of stall file requests static DB_FILE *streamer_file; playItem_t * streamer_get_streaming_track (void) { return orig_streaming_song; } playItem_t * streamer_get_playing_track (void) { return &str_playing_song; } // playlist must call that whenever item was removed void streamer_song_removed_notify (playItem_t *it) { if (!mutex) { return; // streamer is not running } plug_trigger_event (DB_EV_TRACKDELETED, (uintptr_t)it); if (it == orig_playing_song) { orig_playing_song = NULL; } if (it == orig_streaming_song) { orig_streaming_song = NULL; // queue new next song for streaming if (bytes_until_next_song > 0) { streambuffer_fill = bytes_until_next_song; pl_nextsong (0); } } } // that must be called after last sample from str_playing_song was done reading static int streamer_set_current (playItem_t *it) { int from, to; streamer_buffering = 1; from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1; to = it ? pl_get_idx_of (it) : -1; if (!orig_playing_song || p_isstopped ()) { playlist_current_ptr = it; } trace ("from=%d, to=%d\n", from, to); trace ("sending songchanged\n"); // printf ("songchanged[2] %d->%d\n", from, to); // messagepump_push (M_SONGCHANGED, 0, from, to); 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) { goto success; } if (!it->decoder && it->filetype && !strcmp (it->filetype, "content")) { // try to get content-type DB_FILE *fp = streamer_file = vfs_fopen (it->fname); const char *ext = NULL; if (fp) { const char *ct = vfs_get_content_type (fp); if (ct) { fprintf (stderr, "got content-type: %s\n", ct); if (!strcmp (ct, "audio/mpeg")) { ext = "mp3"; } else if (!strcmp (ct, "application/ogg")) { ext = "ogg"; } } streamer_file = NULL; vfs_fclose (fp); } if (ext) { DB_decoder_t **decoders = plug_get_decoder_list (); // match by decoder for (int i = 0; decoders[i]; i++) { if (decoders[i]->exts) { const char **exts = decoders[i]->exts; for (int e = 0; exts[e]; e++) { if (!strcasecmp (exts[e], ext)) { it->decoder = decoders[i]; it->filetype = decoders[i]->filetypes[0]; } } } } } } if (it->decoder) { streamer_lock (); streamer_initializing_item = it; streamer_unlock (); int ret = it->decoder->init (DB_PLAYITEM (it)); streamer_lock (); streamer_initializing_item = NULL; streamer_unlock (); pl_item_copy (&str_streaming_song, it); if (ret < 0) { trace ("decoder->init returned %d\n", ret); trace ("orig_playing_song = %p\n", orig_playing_song); if (playlist_current_ptr == it) { playlist_current_ptr = NULL; messagepump_push (M_TRACKCHANGED, 0, to, 0); } 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; } success: messagepump_push (M_TRACKCHANGED, 0, to, 0); return 0; } float streamer_get_playpos (void) { return playpos; } void streamer_set_bitrate (int bitrate) { if (bytes_until_next_song <= 0) { // prevent next track from resetting current playback bitrate last_bitrate = bitrate; } } int streamer_get_apx_bitrate (void) { return avg_bitrate; } 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 bytes_until_next_song = 0; playpos = 0; // try to interrupt file operation streamer_lock (); if (streamer_file && streamer_file->vfs->streaming) { trace ("interrupting streamer file access...\n"); vfs_fstop (streamer_file); } else if (streamer_initializing_item) { playItem_t *it = streamer_initializing_item; if (it->decoder->info.file && it->decoder->info.file->vfs->streaming) { trace ("interrupting plugin stream %p...\n", it->decoder->info.file); vfs_fstop (it->decoder->info.file); } } streamer_unlock (); } } void streamer_set_seek (float pos) { seekpos = pos; } static int streamer_read_async (char *bytes, int size); void streamer_thread (void *ctx) { prctl (PR_SET_NAME, "deadbeef-stream", 0, 0, 0, 0); codecleft = 0; while (!streaming_terminate) { struct timeval tm1; gettimeofday (&tm1, NULL); if (nextsong >= 0) { // start streaming next song trace ("nextsong=%d\n", nextsong); int sng = nextsong; int pstate = nextsong_pstate; nextsong = -1; codec_lock (); codecleft = 0; codec_unlock (); if (badsong == sng) { trace ("looped to bad file. stopping...\n"); streamer_set_nextsong (-2, 1); badsong = -1; continue; } playItem_t *try = pl_get_for_idx (sng); int ret = streamer_set_current (try); if (ret < 0) { trace ("failed to play track %s, skipping...\n", try->fname); // remember bad song number in case of looping if (badsong == -1) { badsong = sng; } // try jump to next song pl_nextsong (0); trace ("pl_nextsong switched to track %d\n", nextsong); usleep (50000); continue; } badsong = -1; if (pstate == 0) { p_stop (); } else if (pstate == 1) { last_bitrate = -1; avg_bitrate = -1; p_play (); } else if (pstate == 2) { p_pause (); } } else if (nextsong == -2 && (nextsong_pstate==0 || bytes_until_next_song == 0)) { int from = orig_playing_song ? pl_get_idx_of (orig_playing_song) : -1; bytes_until_next_song = -1; trace ("nextsong=-2\n"); nextsong = -1; p_stop (); if (str_playing_song.decoder) { trace ("sending songfinished to plugins [1]\n"); plug_trigger_event (DB_EV_SONGFINISHED, 0); } streamer_set_current (NULL); pl_item_free (&str_playing_song); orig_playing_song = NULL; messagepump_push (M_SONGCHANGED, 0, from, -1); continue; } else if (p_isstopped ()) { usleep (50000); continue; } if (bytes_until_next_song == 0) { if (!str_streaming_song.fname) { // means last song was deleted during final drain nextsong = -1; p_stop (); streamer_set_current (NULL); continue; } trace ("bytes_until_next_song=0, starting playback of new song\n"); 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); trace ("sending songchanged\n"); messagepump_push (M_SONGCHANGED, 0, from, to); bytes_until_next_song = -1; // plugin will get pointer to str_playing_song if (str_playing_song.decoder) { trace ("sending songfinished to plugins [2]\n"); plug_trigger_event (DB_EV_SONGFINISHED, 0); } // free old copy of playing pl_item_free (&str_playing_song); // copy streaming into playing pl_item_copy (&str_playing_song, &str_streaming_song); last_bitrate = -1; avg_bitrate = -1; orig_playing_song = orig_streaming_song; if (orig_playing_song) { orig_playing_song->played = 1; orig_playing_song->started_timestamp = time (NULL); str_playing_song.started_timestamp = time (NULL); } playlist_current_ptr = orig_playing_song; // that is needed for playlist drawing // plugin will get pointer to new str_playing_song trace ("sending songstarted to plugins\n"); plug_trigger_event (DB_EV_SONGSTARTED, 0); playpos = 0; // change samplerate if (prevtrack_samplerate != str_playing_song.decoder->info.samplerate) { plug_get_output ()->change_rate (str_playing_song.decoder->info.samplerate); prevtrack_samplerate = str_playing_song.decoder->info.samplerate; } } if (seekpos >= 0) { trace ("seeking to %f\n", seekpos); float pos = seekpos; seekpos = -1; if (orig_playing_song != orig_streaming_song) { trace ("streamer already switched to next track\n"); // restart playing from new position if(str_streaming_song.decoder) { str_streaming_song.decoder->free (); 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; int ret = str_streaming_song.decoder->init (DB_PLAYITEM (orig_streaming_song)); if (ret < 0) { trace ("failed to restart prev track on seek, trying to jump to next track\n"); pl_nextsong (0); trace ("pl_nextsong switched to track %d\n", nextsong); usleep (50000); continue; } } streamer_buffering = 1; int trk = pl_get_idx_of (orig_streaming_song); if (trk != -1) { messagepump_push (M_TRACKCHANGED, 0, trk, 0); } if (str_playing_song.decoder && str_playing_song._duration > 0) { streamer_lock (); streambuffer_fill = 0; streambuffer_pos = 0; codec_lock (); codecleft = 0; codec_unlock (); if (str_playing_song.decoder->seek (pos) >= 0) { playpos = str_playing_song.decoder->info.readpos; } last_bitrate = -1; avg_bitrate = -1; streamer_unlock(); } } // read ahead at 384K per second // that means 10ms per 4k block, or 40ms per 16k block int alloc_time = 1000 / (96000 * 4 / 4096); streamer_lock (); 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) { minsize = 16384; alloc_time *= 4; } sz = min (minsize, sz); assert ((sz&3) == 0); char buf[sz]; streamer_unlock (); int bytesread = streamer_read_async (buf,sz); streamer_lock (); memcpy (streambuffer+streambuffer_fill, buf, sz); streambuffer_fill += bytesread; } streamer_unlock (); struct timeval tm2; gettimeofday (&tm2, NULL); int ms = (tm2.tv_sec*1000+tm2.tv_usec/1000) - (tm1.tv_sec*1000+tm1.tv_usec/1000); alloc_time -= ms; if (alloc_time > 0) { usleep (alloc_time * 1000); // usleep (1000); } // trace ("fill: %d/%d\n", streambuffer_fill, STREAM_BUFFER_SIZE); } // stop streaming song if(str_streaming_song.decoder) { str_streaming_song.decoder->free (); } pl_item_free (&str_streaming_song); pl_item_free (&str_playing_song); if (src) { src_delete (src); src = NULL; } } int streamer_init (void) { mutex = mutex_create (); decodemutex = mutex_create (); src_quality = conf_get_int ("src_quality", 2); src = src_new (src_quality, 2, NULL); conf_replaygain_mode = conf_get_int ("replaygain_mode", 0); conf_replaygain_scale = conf_get_int ("replaygain_scale", 1); if (!src) { return -1; } streamer_tid = thread_start (streamer_thread, NULL); return 0; } void streamer_free (void) { streaming_terminate = 1; thread_join (streamer_tid); mutex_free (decodemutex); decodemutex = 0; mutex_free (mutex); mutex = 0; } void streamer_reset (int full) { // must be called when current song changes by external reasons codecleft = 0; if (full) { streambuffer_pos = 0; streambuffer_fill = 0; } src_reset (src); } int replaygain = 1; int replaygain_scale = 1; static void apply_replay_gain_int16 (playItem_t *it, char *bytes, int size) { if (!replaygain || !conf_replaygain_mode) { return; } int vol = 1000; if (conf_replaygain_mode == 1) { if (it->replaygain_track_gain == 0) { return; } if (conf_replaygain_scale && replaygain_scale) { vol = db_to_amp (str_streaming_song.replaygain_track_gain)/str_streaming_song.replaygain_track_peak * 1000; } else { vol = db_to_amp (str_streaming_song.replaygain_track_gain) * 1000; } } else if (conf_replaygain_mode == 2) { if (it->replaygain_album_gain == 0) { return; } if (conf_replaygain_scale && replaygain_scale) { vol = db_to_amp (str_streaming_song.replaygain_album_gain)/str_streaming_song.replaygain_album_peak * 1000; } else { vol = db_to_amp (str_streaming_song.replaygain_album_gain) * 1000; } } int16_t *s = (int16_t*)bytes; for (int j = 0; j < size/2; j++) { int32_t sample = ((int32_t)(*s)) * vol / 1000; if (sample > 0x7fff) { sample = 0x7fff; } else if (sample < -0x8000) { sample = -0x8000; } *s = (int16_t)sample; s++; } } static void apply_replay_gain_float32 (playItem_t *it, char *bytes, int size) { if (!replaygain || !conf_replaygain_mode) { return; } float vol = 1.f; if (conf_replaygain_mode == 1) { if (it->replaygain_track_gain == 0) { return; } if (conf_replaygain_scale && replaygain_scale) { vol = db_to_amp (it->replaygain_track_gain)/it->replaygain_track_peak; } else { vol = db_to_amp (it->replaygain_track_gain); } } else if (conf_replaygain_mode == 2) { if (it->replaygain_album_gain == 0) { return; } if (conf_replaygain_scale && replaygain_scale) { vol = db_to_amp (it->replaygain_album_gain)/it->replaygain_album_peak; } else { vol = db_to_amp (it->replaygain_album_gain); } } float *s = (float*)bytes; for (int j = 0; j < size/4; j++) { float sample = ((float)*s) * vol; if (sample > 1.f) { sample = 1.f; } else if (sample < -1.f) { sample = -1.f; } *s = sample; s++; } } static void mono_int16_to_stereo_int16 (int16_t *in, int16_t *out, int nsamples) { while (nsamples > 0) { int16_t sample = *in++; *out++ = sample; *out++ = sample; nsamples--; } } static void int16_to_float32 (int16_t *in, float *out, int nsamples) { while (nsamples > 0) { *out++ = (*in++)/(float)0x7fff; nsamples--; } } static void mono_int16_to_stereo_float32 (int16_t *in, float *out, int nsamples) { while (nsamples > 0) { float sample = (*in++)/(float)0x7fff; *out++ = sample; *out++ = sample; nsamples--; } } static void mono_float32_to_stereo_float32 (float *in, float *out, int nsamples) { while (nsamples > 0) { float sample = *in++; *out++ = sample; *out++ = sample; nsamples--; } } static void float32_to_int16 (float *in, int16_t *out, int nsamples) { fpu_control ctl; fpu_setround (&ctl); while (nsamples > 0) { float sample = *in++; if (sample > 1) { sample = 1; } else if (sample < -1) { sample = -1; } *out++ = (int16_t)ftoi (sample*0x7fff); nsamples--; } fpu_restore (ctl); } // returns number of bytes been read static int streamer_read_async (char *bytes, int size) { int initsize = size; for (;;) { int bytesread = 0; codec_lock (); DB_decoder_t *decoder = str_streaming_song.decoder; if (!decoder) { // means there's nothing left to stream, so just do nothing codec_unlock (); break; } if (decoder->info.samplerate != -1) { int nchannels = decoder->info.channels; int samplerate = decoder->info.samplerate; if (decoder->info.samplerate == p_get_rate ()) { // samplerate match int i; if (decoder->info.channels == 2) { bytesread = decoder->read_int16 (bytes, size); apply_replay_gain_int16 (&str_streaming_song, bytes, size); codec_unlock (); } else { bytesread = decoder->read_int16 (g_readbuffer, size>>1); apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, size>>1); mono_int16_to_stereo_int16 ((int16_t*)g_readbuffer, (int16_t*)bytes, size>>2); bytesread *= 2; codec_unlock (); } } else if (src_is_valid_ratio ((double)p_get_rate ()/samplerate)) { // read and do SRC int nsamples = size/4; nsamples = nsamples * samplerate / p_get_rate () * 2; // read data at source samplerate (with some room for SRC) int nbytes = (nsamples - codecleft) * 2 * nchannels; int samplesize = 2; if (nbytes <= 0) { nbytes = 0; } else { if (!decoder->read_float32) { if (nbytes > INPUT_BUFFER_SIZE) { trace ("input buffer overflow\n"); nbytes = INPUT_BUFFER_SIZE; } bytesread = decoder->read_int16 (g_readbuffer, nbytes); apply_replay_gain_int16 (&str_streaming_song, g_readbuffer, nbytes); } else { samplesize = 4; } } codec_unlock (); // recalculate nsamples according to how many bytes we've got if (nbytes != 0) { int i; if (!decoder->read_float32) { nsamples = bytesread / (samplesize * nchannels) + codecleft; // convert to float float *fbuffer = g_fbuffer + codecleft*2; int n = nsamples - codecleft; if (nchannels == 2) { n <<= 1; int16_to_float32 ((int16_t*)g_readbuffer, fbuffer, n); } else if (nchannels == 1) { // convert mono to stereo mono_int16_to_stereo_float32 ((int16_t*)g_readbuffer, fbuffer, n); } } else { float *fbuffer = g_fbuffer + codecleft*2; if (nchannels == 1) { codec_lock (); bytesread = decoder->read_float32 (g_readbuffer, nbytes*2); codec_unlock (); apply_replay_gain_float32 (&str_streaming_song, g_readbuffer, nbytes*2); nsamples = bytesread / (samplesize * nchannels) + codecleft; mono_float32_to_stereo_float32 ((float *)g_readbuffer, fbuffer, nsamples-codecleft); } else { codec_lock (); bytesread = decoder->read_float32 ((char *)fbuffer, nbytes*2); codec_unlock (); apply_replay_gain_float32 (&str_streaming_song, (char *)fbuffer, nbytes*2); nsamples = bytesread / (samplesize * nchannels) + codecleft; } } } //codec_lock (); // convert samplerate mutex_lock (decodemutex); srcdata.data_in = g_fbuffer; srcdata.data_out = g_srcbuffer; srcdata.input_frames = nsamples; srcdata.output_frames = size/4; srcdata.src_ratio = (double)p_get_rate ()/samplerate; srcdata.end_of_input = 0; // src_set_ratio (src, srcdata.src_ratio); src_process (src, &srcdata); //codec_unlock (); // convert back to s16 format nbytes = size; int genbytes = srcdata.output_frames_gen * 4; bytesread = min(size, genbytes); float32_to_int16 ((float*)g_srcbuffer, (int16_t*)bytes, bytesread>>1); // calculate how many unused input samples left codecleft = nsamples - srcdata.input_frames_used; mutex_unlock (decodemutex); // copy spare samples for next update memmove (g_fbuffer, &g_fbuffer[srcdata.input_frames_used*2], codecleft * 8); } else { fprintf (stderr, "invalid ratio! %d / %d = %f", p_get_rate (), samplerate, (float)p_get_rate ()/samplerate); } } else { codec_unlock (); } bytes += bytesread; size -= bytesread; if (size == 0) { return initsize; } else { // that means EOF if (bytes_until_next_song < 0) { trace ("finished streaming song, queueing next\n"); bytes_until_next_song = streambuffer_fill; if (conf_get_int ("playlist.stop_after_current", 0)) { streamer_set_nextsong (-2, 1); } else { pl_nextsong (0); } } break; } } return initsize - size; } void streamer_lock (void) { mutex_lock (mutex); } void streamer_unlock (void) { mutex_unlock (mutex); } int streamer_read (char *bytes, int size) { #if 0 struct timeval tm1; gettimeofday (&tm1, NULL); #endif streamer_lock (); int sz = min (size, streambuffer_fill); if (sz) { memcpy (bytes, streambuffer, sz); memmove (streambuffer, streambuffer+sz, streambuffer_fill-sz); streambuffer_fill -= sz; playpos += (float)sz/p_get_rate ()/4.f; str_playing_song.playtime += (float)sz/p_get_rate ()/4.f; if (playlist_current_ptr) { str_playing_song.filetype = playlist_current_ptr->filetype; } if (playlist_current_ptr) { playlist_current_ptr->playtime = str_playing_song.playtime; } if (bytes_until_next_song > 0) { bytes_until_next_song -= sz; if (bytes_until_next_song < 0) { bytes_until_next_song = 0; } } } streamer_unlock (); // approximate bitrate if (last_bitrate != -1) { if (avg_bitrate == -1) { avg_bitrate = last_bitrate; } else { if (avg_bitrate < last_bitrate) { avg_bitrate += 5; if (avg_bitrate > last_bitrate) { avg_bitrate = last_bitrate; } } else if (avg_bitrate > last_bitrate) { avg_bitrate -= 5; if (avg_bitrate < last_bitrate) { avg_bitrate = last_bitrate; } } } // printf ("apx bitrate: %d (last %d)\n", avg_bitrate, last_bitrate); } else { avg_bitrate = -1; } #if 0 struct timeval tm2; gettimeofday (&tm2, NULL); int ms = (tm2.tv_sec*1000+tm2.tv_usec/1000) - (tm1.tv_sec*1000+tm1.tv_usec/1000); printf ("streamer_read took %d ms\n", ms); #endif return sz; } int streamer_get_fill (void) { return streambuffer_fill; } int streamer_ok_to_read (int len) { if (len >= 0 && (bytes_until_next_song > 0 || streambuffer_fill >= (len*2))) { if (streamer_buffering) { streamer_buffering = 0; if (orig_streaming_song) { int trk = pl_get_idx_of (orig_streaming_song); if (trk != -1) { messagepump_push (M_TRACKCHANGED, 0, trk, 0); } } } return 1; } else { return 1-streamer_buffering; } return 0; } int streamer_is_buffering (void) { if (streambuffer_fill < 16384) { return 1; } else { return 0; } } void streamer_configchanged (void) { conf_replaygain_mode = conf_get_int ("replaygain_mode", 0); conf_replaygain_scale = conf_get_int ("replaygain_scale", 1); int q = conf_get_int ("src_quality", 2); if (q != src_quality && q >= SRC_SINC_BEST_QUALITY && q <= SRC_LINEAR) { mutex_lock (decodemutex); fprintf (stderr, "changing src_quality from %d to %d\n", src_quality, q); src_quality = q; if (src) { src_delete (src); src = NULL; } memset (&srcdata, 0, sizeof (srcdata)); src = src_new (src_quality, 2, NULL); mutex_unlock (decodemutex); } } void streamer_play_current_track (void) { if (p_ispaused ()) { // unpause currently paused track p_unpause (); plug_trigger_event_paused (0); } else if (playlist_current_row[PL_MAIN] != -1) { // play currently selected track p_stop (); streamer_set_nextsong (playlist_current_row[PL_MAIN], 1); } else { // restart currently playing track p_stop (); streamer_set_nextsong (0, 1); } }