/*
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 "threading.h"
#include "codec.h"
#include "playlist.h"
#include "common.h"
#include "streamer.h"
#include "playback.h"
#include "messagepump.h"
#include "messages.h"
#include "conf.h"
static SRC_STATE *src;
static SRC_DATA srcdata;
static int codecleft;
static char g_readbuffer[200000]; // hack!
static float g_fbuffer[200000]; // hack!
static float g_srcbuffer[200000]; // hack!
static int streaming_terminate;
#define STREAM_BUFFER_SIZE 200000
static int streambuffer_fill;
static int bytes_until_next_song = 0;
static char streambuffer[STREAM_BUFFER_SIZE];
static uintptr_t mutex;
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
float
streamer_get_playpos (void) {
return playpos;
}
void
streamer_set_nextsong (int song, int 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;
}
}
void
streamer_set_seek (float pos) {
seekpos = pos;
}
static int
streamer_read_async (char *bytes, int size);
void
streamer_thread (uintptr_t ctx) {
codecleft = 0;
while (!streaming_terminate) {
if (nextsong >= 0 && bytes_until_next_song == 0) {
int sng = nextsong;
int pstate = nextsong_pstate;
nextsong = -1;
codec_lock ();
codecleft = 0;
codec_unlock ();
if (badsong == sng) {
//printf ("looped to bad file. stopping...\n");
streamer_set_nextsong (-2, 1);
badsong = -1;
continue;
}
int ret = pl_set_current (pl_get_for_idx (sng));
if (ret < 0) {
//printf ("bad file in playlist, skipping...\n");
// remember bad song number in case of looping
if (badsong == -1) {
badsong = sng;
}
// try jump to next song
pl_nextsong (0);
usleep (3000);
continue;
}
badsong = -1;
playpos = 0;
if (pstate == 0) {
p_stop ();
}
else if (pstate == 1) {
p_play ();
}
else if (pstate == 2) {
p_pause ();
}
}
else if (nextsong == -2) {
nextsong = -1;
p_stop ();
messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1);
continue;
}
else if (p_isstopped ()) {
usleep (3000);
continue;
}
if (seekpos >= 0) {
float pos = seekpos;
seekpos = -1;
if (playlist_current.codec && playlist_current.codec->seek (pos) >= 0) {
streamer_lock ();
playpos = playlist_current.codec->info.readposition;
streambuffer_fill = 0;
streamer_unlock ();
codec_lock ();
codecleft = 0;
codec_unlock ();
}
}
streamer_lock ();
if (streambuffer_fill < STREAM_BUFFER_SIZE && bytes_until_next_song == 0) {
int sz = STREAM_BUFFER_SIZE - streambuffer_fill;
int minsize = 4096;
if (streambuffer_fill < 16384) {
minsize = 16384;
}
sz = min (minsize, sz);
assert ((sz&3) == 0);
int bytesread = streamer_read_async (&streambuffer[streambuffer_fill], sz);
//printf ("req=%d, got=%d\n", sz, bytesread);
streambuffer_fill += bytesread;
}
streamer_unlock ();
usleep (6000);
//printf ("fill: %d \r", streambuffer_fill);
}
if (src) {
src_delete (src);
src = NULL;
}
}
int
streamer_init (void) {
mutex = mutex_create ();
// src = src_new (SRC_SINC_BEST_QUALITY, 2, NULL);
// src = src_new (SRC_LINEAR, 2, NULL);
src = src_new (conf_src_quality, 2, NULL);
if (!src) {
return -1;
}
thread_start (streamer_thread, 0);
return 0;
}
void
streamer_free (void) {
streaming_terminate = 1;
mutex_free (mutex);
}
void
streamer_reset (int full) { // must be called when current song changes by external reasons
codecleft = 0;
if (full) {
streambuffer_fill = 0;
}
src_reset (src);
}
// returns number of bytes been read
static int
streamer_read_async (char *bytes, int size) {
int initsize = size;
for (;;) {
int bytesread = 0;
codec_lock ();
codec_t *codec = playlist_current.codec;
if (!codec) {
codec_unlock ();
break;
}
if (codec->info.samplesPerSecond != -1) {
int nchannels = codec->info.channels;
int samplerate = codec->info.samplesPerSecond;
// read and do SRC
if (codec->info.samplesPerSecond == p_get_rate ()) {
int i;
if (codec->info.channels == 2) {
bytesread = codec->read (bytes, size);
codec_unlock ();
}
else {
bytesread = codec->read (g_readbuffer, size/2);
codec_unlock ();
for (i = 0; i < size/4; i++) {
int16_t sample = (int16_t)(((int32_t)(((int16_t*)g_readbuffer)[i])));
((int16_t*)bytes)[i*2+0] = sample;
((int16_t*)bytes)[i*2+1] = sample;
}
bytesread *= 2;
}
}
else {
int nsamples = size/4;
// convert to codec samplerate
nsamples = nsamples * samplerate / p_get_rate () * 2;
if (!src_is_valid_ratio ((double)p_get_rate ()/samplerate)) {
printf ("invalid ratio! %d / %d = %f", p_get_rate (), samplerate, (float)p_get_rate ()/samplerate);
}
assert (src_is_valid_ratio ((double)p_get_rate ()/samplerate));
// read data at source samplerate (with some room for SRC)
int nbytes = (nsamples - codecleft) * 2 * nchannels;
if (nbytes < 0) {
nbytes = 0;
}
else {
// if (nbytes & 3) {
// printf ("FATAL: nbytes=%d, nsamples=%d, codecleft=%d, nchannels=%d, ratio=%f\n", nbytes, nsamples, codecleft, nchannels, (float)p_get_rate ()/samplerate);
// assert ((nbytes & 3) == 0);
// }
bytesread = codec->read (g_readbuffer, nbytes);
}
codec_unlock ();
// recalculate nsamples according to how many bytes we've got
nsamples = bytesread / (2 * nchannels) + codecleft;
// convert to float
int i;
float *fbuffer = g_fbuffer + codecleft*2;
if (nchannels == 2) {
for (i = 0; i < (nsamples - codecleft) * 2; i++) {
fbuffer[i] = ((int16_t *)g_readbuffer)[i]/32767.f;
}
}
else if (nchannels == 1) { // convert mono to stereo
for (i = 0; i < (nsamples - codecleft); i++) {
fbuffer[i*2+0] = ((int16_t *)g_readbuffer)[i]/32767.f;
fbuffer[i*2+1] = fbuffer[i*2+0];
}
}
//codec_lock ();
// convert samplerate
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);
for (i = 0; i < bytesread/2; i++) {
float sample = g_srcbuffer[i];
if (sample > 1) {
sample = 1;
}
if (sample < -1) {
sample = -1;
}
((int16_t*)bytes)[i] = (int16_t)(sample*32767.f);
}
// calculate how many unused input samples left
codecleft = nsamples - srcdata.input_frames_used;
// copy spare samples for next update
memmove (g_fbuffer, &g_fbuffer[srcdata.input_frames_used*2], codecleft * 8);
}
}
else {
codec_unlock ();
}
bytes += bytesread;
size -= bytesread;
if (size == 0) {
return initsize;
}
else {
// that means EOF
if (bytes_until_next_song == 0) {
bytes_until_next_song = streambuffer_fill;
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) {
streamer_lock ();
int sz = min (size, streambuffer_fill);
if (sz) {
memcpy (bytes, streambuffer, sz);
if (sz < streambuffer_fill) {
// shift buffer
memmove (streambuffer, &streambuffer[sz], streambuffer_fill-sz);
}
streambuffer_fill -= sz;
playpos += (float)sz/p_get_rate ()/4.f;
bytes_until_next_song -= sz;
if (bytes_until_next_song < 0) {
bytes_until_next_song = 0;
}
}
streamer_unlock ();
return sz;
}
int
streamer_get_fill (void) {
return streambuffer_fill;
}
int
streamer_ok_to_read (int len) {
if (bytes_until_next_song > 0) {
return 1;
}
return streambuffer_fill >= (len*2);
}
int
streamer_is_buffering (void) {
if (streambuffer_fill < 16384) {
return 1;
}
else {
return 0;
}
}