/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2006 Timo Hirvonen
*
* 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 "player.h"
#include "buffer.h"
#include "input.h"
#include "output.h"
#include "sf.h"
#include "op.h"
#include "utils.h"
#include "xmalloc.h"
#include "debug.h"
#include "compiler.h"
#include "options.h"
#include "mpris.h"
#include "cmus.h"
#include
#include
#include
#include
#include
#include
#include
#include
const char * const player_status_names[] = {
"stopped", "playing", "paused", NULL
};
enum producer_status {
PS_UNLOADED,
PS_STOPPED,
PS_PLAYING,
PS_PAUSED
};
enum consumer_status {
CS_STOPPED,
CS_PLAYING,
CS_PAUSED
};
/* protects player_info_priv and player_metadata */
static pthread_mutex_t player_info_mutex = CMUS_MUTEX_INITIALIZER;
struct player_info player_info;
char player_metadata[255 * 16 + 1];
static struct player_info player_info_priv = {
.ti = NULL,
.status = PLAYER_STATUS_STOPPED,
.pos = 0,
.current_bitrate = -1,
.buffer_fill = 0,
.buffer_size = 0,
.error_msg = NULL,
.file_changed = 0,
.metadata_changed = 0,
.status_changed = 0,
.position_changed = 0,
.buffer_fill_changed = 0,
};
/* continue playing after track is finished? */
int player_cont = 1;
/* continue playing after album is finished? */
int player_cont_album = 1;
/* repeat current track forever? */
int player_repeat_current;
enum replaygain replaygain;
int replaygain_limit = 1;
double replaygain_preamp = 0.0;
int soft_vol;
int soft_vol_l;
int soft_vol_r;
static sample_format_t buffer_sf;
static CHANNEL_MAP(buffer_channel_map);
static pthread_t producer_thread;
static pthread_mutex_t producer_mutex = CMUS_MUTEX_INITIALIZER;
static pthread_cond_t producer_playing = CMUS_COND_INITIALIZER;
static int producer_running = 1;
static enum producer_status producer_status = PS_UNLOADED;
static struct input_plugin *ip = NULL;
static pthread_t consumer_thread;
static pthread_mutex_t consumer_mutex = CMUS_MUTEX_INITIALIZER;
static pthread_cond_t consumer_playing = CMUS_COND_INITIALIZER;
static int consumer_running = 1;
static enum consumer_status consumer_status = CS_STOPPED;
static unsigned long consumer_pos = 0;
/* for replay gain and soft vol
* usually same as consumer_pos, sometimes more than consumer_pos
*/
static unsigned long scale_pos;
static double replaygain_scale = 1.0;
/* locking {{{ */
#define player_info_priv_lock() cmus_mutex_lock(&player_info_mutex)
#define player_info_priv_unlock() cmus_mutex_unlock(&player_info_mutex)
#define producer_lock() cmus_mutex_lock(&producer_mutex)
#define producer_unlock() cmus_mutex_unlock(&producer_mutex)
#define consumer_lock() cmus_mutex_lock(&consumer_mutex)
#define consumer_unlock() cmus_mutex_unlock(&consumer_mutex)
#define player_lock() \
do { \
consumer_lock(); \
producer_lock(); \
} while (0)
#define player_unlock() \
do { \
producer_unlock(); \
consumer_unlock(); \
} while (0)
/* locking }}} */
static void reset_buffer(void)
{
buffer_reset();
consumer_pos = 0;
scale_pos = 0;
pthread_cond_broadcast(&producer_playing);
}
static void set_buffer_sf(void)
{
buffer_sf = ip_get_sf(ip);
ip_get_channel_map(ip, buffer_channel_map);
/* ip_read converts samples to this format */
if (sf_get_channels(buffer_sf) <= 2 && sf_get_bits(buffer_sf) <= 16) {
buffer_sf &= SF_RATE_MASK;
buffer_sf |= sf_channels(2) | sf_bits(16) | sf_signed(1);
buffer_sf |= sf_host_endian();
channel_map_init_stereo(buffer_channel_map);
}
}
#define SOFT_VOL_SCALE 65536
/* coefficients for volumes 0..99, for 100 65536 is used
* data copied from alsa-lib src/pcm/pcm_softvol.c
*/
static const unsigned short soft_vol_db[100] = {
0x0000, 0x0110, 0x011c, 0x012f, 0x013d, 0x0152, 0x0161, 0x0179,
0x018a, 0x01a5, 0x01c1, 0x01d5, 0x01f5, 0x020b, 0x022e, 0x0247,
0x026e, 0x028a, 0x02b6, 0x02d5, 0x0306, 0x033a, 0x035f, 0x0399,
0x03c2, 0x0403, 0x0431, 0x0479, 0x04ac, 0x04fd, 0x0553, 0x058f,
0x05ef, 0x0633, 0x069e, 0x06ea, 0x0761, 0x07b5, 0x083a, 0x0898,
0x092c, 0x09cb, 0x0a3a, 0x0aeb, 0x0b67, 0x0c2c, 0x0cb6, 0x0d92,
0x0e2d, 0x0f21, 0x1027, 0x10de, 0x1202, 0x12cf, 0x1414, 0x14f8,
0x1662, 0x1761, 0x18f5, 0x1a11, 0x1bd3, 0x1db4, 0x1f06, 0x211d,
0x2297, 0x24ec, 0x2690, 0x292a, 0x2aff, 0x2de5, 0x30fe, 0x332b,
0x369f, 0x390d, 0x3ce6, 0x3f9b, 0x43e6, 0x46eb, 0x4bb3, 0x4f11,
0x5466, 0x5a18, 0x5e19, 0x6472, 0x68ea, 0x6ffd, 0x74f8, 0x7cdc,
0x826a, 0x8b35, 0x9499, 0x9b35, 0xa5ad, 0xad0b, 0xb8b7, 0xc0ee,
0xcdf1, 0xd71a, 0xe59c, 0xefd3
};
static inline void scale_sample_int16_t(int16_t *buf, int i, int vol, int swap)
{
int32_t sample = swap ? (int16_t)swap_uint16(buf[i]) : buf[i];
if (sample < 0) {
sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample < INT16_MIN)
sample = INT16_MIN;
} else {
sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample > INT16_MAX)
sample = INT16_MAX;
}
buf[i] = swap ? swap_uint16(sample) : sample;
}
static inline int32_t scale_sample_s24le(int32_t s, int vol)
{
int64_t sample = s;
if (sample < 0) {
sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample < -0x800000)
sample = -0x800000;
} else {
sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample > 0x7fffff)
sample = 0x7fffff;
}
return sample;
}
static inline void scale_sample_int32_t(int32_t *buf, int i, int vol, int swap)
{
int64_t sample = swap ? (int32_t)swap_uint32(buf[i]) : buf[i];
if (sample < 0) {
sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample < INT32_MIN)
sample = INT32_MIN;
} else {
sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
if (sample > INT32_MAX)
sample = INT32_MAX;
}
buf[i] = swap ? swap_uint32(sample) : sample;
}
static inline int sf_need_swap(sample_format_t sf)
{
#ifdef WORDS_BIGENDIAN
return !sf_get_bigendian(sf);
#else
return sf_get_bigendian(sf);
#endif
}
#define SCALE_SAMPLES(TYPE, buffer, count, l, r, swap) \
{ \
const int frames = count / sizeof(TYPE) / 2; \
TYPE *buf = (void *) buffer; \
int i; \
/* avoid underflowing -32768 to 32767 when scale is 65536 */ \
if (l != SOFT_VOL_SCALE && r != SOFT_VOL_SCALE) { \
for (i = 0; i < frames; i++) { \
scale_sample_##TYPE(buf, i * 2, l, swap); \
scale_sample_##TYPE(buf, i * 2 + 1, r, swap); \
} \
} else if (l != SOFT_VOL_SCALE) { \
for (i = 0; i < frames; i++) \
scale_sample_##TYPE(buf, i * 2, l, swap); \
} else if (r != SOFT_VOL_SCALE) { \
for (i = 0; i < frames; i++) \
scale_sample_##TYPE(buf, i * 2 + 1, r, swap); \
} \
}
static inline int32_t read_s24le(const char *buf)
{
const unsigned char *b = (const unsigned char *) buf;
return b[0] | (b[1] << 8) | (((const signed char *) buf)[2] << 16);
}
static inline void write_s24le(char *buf, int32_t x)
{
unsigned char *b = (unsigned char *) buf;
b[0] = x;
b[1] = x >> 8;
b[2] = x >> 16;
}
static void scale_samples_s24le(char *buf, unsigned int count, int l, int r)
{
int frames = count / 3 / 2;
if (l != SOFT_VOL_SCALE && r != SOFT_VOL_SCALE) {
while (frames--) {
write_s24le(buf, scale_sample_s24le(read_s24le(buf), l));
buf += 3;
write_s24le(buf, scale_sample_s24le(read_s24le(buf), r));
buf += 3;
}
} else if (l != SOFT_VOL_SCALE) {
while (frames--) {
write_s24le(buf, scale_sample_s24le(read_s24le(buf), l));
buf += 3 * 2;
}
} else if (r != SOFT_VOL_SCALE) {
buf += 3;
while (frames--) {
write_s24le(buf, scale_sample_s24le(read_s24le(buf), r));
buf += 3 * 2;
}
}
}
static void scale_samples(char *buffer, unsigned int *countp)
{
unsigned int count = *countp;
int ch, bits, l, r;
BUG_ON(scale_pos < consumer_pos);
if (consumer_pos != scale_pos) {
unsigned int offs = scale_pos - consumer_pos;
if (offs >= count)
return;
buffer += offs;
count -= offs;
}
scale_pos += count;
if (replaygain_scale == 1.0 && soft_vol_l == 100 && soft_vol_r == 100)
return;
ch = sf_get_channels(buffer_sf);
bits = sf_get_bits(buffer_sf);
if (ch != 2 || (bits != 16 && bits != 24 && bits != 32))
return;
l = SOFT_VOL_SCALE;
r = SOFT_VOL_SCALE;
if (soft_vol && soft_vol_l != 100)
l = soft_vol_db[soft_vol_l];
if (soft_vol && soft_vol_r != 100)
r = soft_vol_db[soft_vol_r];
l *= replaygain_scale;
r *= replaygain_scale;
switch (bits) {
case 16:
SCALE_SAMPLES(int16_t, buffer, count, l, r, sf_need_swap(buffer_sf));
break;
case 24:
if (likely(!sf_get_bigendian(buffer_sf)))
scale_samples_s24le(buffer, count, l, r);
break;
case 32:
SCALE_SAMPLES(int32_t, buffer, count, l, r, sf_need_swap(buffer_sf));
break;
}
}
static void update_rg_scale(void)
{
double gain, peak, db, scale, limit;
replaygain_scale = 1.0;
if (!player_info_priv.ti || !replaygain)
return;
if (replaygain == RG_TRACK || replaygain == RG_TRACK_PREFERRED) {
gain = player_info_priv.ti->rg_track_gain;
peak = player_info_priv.ti->rg_track_peak;
} else {
gain = player_info_priv.ti->rg_album_gain;
peak = player_info_priv.ti->rg_album_peak;
}
if (isnan(gain)) {
if (replaygain == RG_TRACK_PREFERRED) {
gain = player_info_priv.ti->rg_album_gain;
peak = player_info_priv.ti->rg_album_peak;
} else if (replaygain == RG_ALBUM_PREFERRED) {
gain = player_info_priv.ti->rg_track_gain;
peak = player_info_priv.ti->rg_track_peak;
}
}
if (isnan(gain)) {
d_print("gain not available\n");
return;
}
if (isnan(peak)) {
d_print("peak not available, defaulting to 1\n");
peak = 1;
}
if (peak < 0.05) {
d_print("peak (%g) is too small\n", peak);
return;
}
db = replaygain_preamp + gain;
scale = pow(10.0, db / 20.0);
replaygain_scale = scale;
limit = 1.0 / peak;
if (replaygain_limit && !isnan(peak)) {
if (replaygain_scale > limit)
replaygain_scale = limit;
}
d_print("gain = %f, peak = %f, db = %f, scale = %f, limit = %f, replaygain_scale = %f\n",
gain, peak, db, scale, limit, replaygain_scale);
}
static inline unsigned int buffer_second_size(void)
{
return sf_get_second_size(buffer_sf);
}
/* updating player status {{{ */
static inline void _file_changed(struct track_info *ti)
{
player_info_priv_lock();
if (player_info_priv.ti)
track_info_unref(player_info_priv.ti);
player_info_priv.ti = ti;
update_rg_scale();
player_metadata[0] = 0;
player_info_priv.file_changed = 1;
player_info_priv_unlock();
}
static inline void file_changed(struct track_info *ti)
{
if (ti) {
d_print("file: %s\n", ti->filename);
} else {
d_print("unloaded\n");
}
_file_changed(ti);
}
static inline void metadata_changed(void)
{
struct keyval *comments;
int rc;
player_info_priv_lock();
if (ip_get_metadata(ip)) {
d_print("metadata changed: %s\n", ip_get_metadata(ip));
memcpy(player_metadata, ip_get_metadata(ip), 255 * 16 + 1);
}
rc = ip_read_comments(ip, &comments);
if (!rc) {
if (player_info_priv.ti->comments)
keyvals_free(player_info_priv.ti->comments);
track_info_set_comments(player_info_priv.ti, comments);
}
player_info_priv.metadata_changed = 1;
player_info_priv_unlock();
}
static void player_error(const char *msg)
{
player_info_priv_lock();
player_info_priv.status = (enum player_status)consumer_status;
player_info_priv.pos = 0;
player_info_priv.current_bitrate = -1;
player_info_priv.buffer_fill = buffer_get_filled_chunks();
player_info_priv.buffer_size = buffer_nr_chunks;
player_info_priv.status_changed = 1;
free(player_info_priv.error_msg);
player_info_priv.error_msg = xstrdup(msg);
player_info_priv_unlock();
d_print("ERROR: '%s'\n", msg);
}
static void CMUS_FORMAT(2, 3) player_ip_error(int rc, const char *format, ...)
{
char buffer[1024];
va_list ap;
char *msg;
int save = errno;
va_start(ap, format);
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
errno = save;
msg = ip_get_error_msg(ip, rc, buffer);
player_error(msg);
free(msg);
}
static void CMUS_FORMAT(2, 3) player_op_error(int rc, const char *format, ...)
{
char buffer[1024];
va_list ap;
char *msg;
int save = errno;
va_start(ap, format);
vsnprintf(buffer, sizeof(buffer), format, ap);
va_end(ap);
errno = save;
msg = op_get_error_msg(rc, buffer);
player_error(msg);
free(msg);
}
/*
* buffer-fill changed
*/
static void _producer_buffer_fill_update(void)
{
int fill;
player_info_priv_lock();
fill = buffer_get_filled_chunks();
if (fill != player_info_priv.buffer_fill) {
/* d_print("\n"); */
player_info_priv.buffer_fill = fill;
player_info_priv.buffer_fill_changed = 1;
}
player_info_priv_unlock();
}
/*
* playing position changed
*/
static void _consumer_position_update(void)
{
static unsigned int old_pos = -1;
unsigned int pos = 0;
long bitrate;
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
pos = consumer_pos / buffer_second_size();
if (pos != old_pos) {
old_pos = pos;
player_info_priv_lock();
player_info_priv.pos = pos;
if (show_current_bitrate) {
bitrate = ip_current_bitrate(ip);
if (bitrate != -1)
player_info_priv.current_bitrate = bitrate;
}
player_info_priv.position_changed = 1;
player_info_priv_unlock();
}
}
/*
* something big happened (stopped/paused/unpaused...)
*/
static void _player_status_changed(void)
{
unsigned int pos = 0;
/* d_print("\n"); */
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
pos = consumer_pos / buffer_second_size();
player_info_priv_lock();
player_info_priv.status = (enum player_status)consumer_status;
player_info_priv.pos = pos;
player_info_priv.current_bitrate = -1;
player_info_priv.buffer_fill = buffer_get_filled_chunks();
player_info_priv.buffer_size = buffer_nr_chunks;
player_info_priv.status_changed = 1;
player_info_priv_unlock();
}
/* updating player status }}} */
static void _prebuffer(void)
{
int limit_chunks;
BUG_ON(producer_status != PS_PLAYING);
if (ip_is_remote(ip)) {
limit_chunks = buffer_nr_chunks;
} else {
int limit_ms, limit_size;
limit_ms = 250;
limit_size = limit_ms * buffer_second_size() / 1000;
limit_chunks = limit_size / CHUNK_SIZE;
if (limit_chunks < 1)
limit_chunks = 1;
}
while (1) {
int nr_read, size, filled;
char *wpos;
filled = buffer_get_filled_chunks();
/* d_print("PREBUF: %2d / %2d\n", filled, limit_chunks); */
/* not fatal */
//BUG_ON(filled > limit_chunks);
if (filled >= limit_chunks)
break;
size = buffer_get_wpos(&wpos);
nr_read = ip_read(ip, wpos, size);
if (nr_read < 0) {
if (nr_read == -1 && errno == EAGAIN)
continue;
player_ip_error(nr_read, "reading file %s", ip_get_filename(ip));
/* ip_read sets eof */
nr_read = 0;
}
if (ip_metadata_changed(ip))
metadata_changed();
/* buffer_fill with 0 count marks current chunk filled */
buffer_fill(nr_read);
_producer_buffer_fill_update();
if (nr_read == 0) {
/* EOF */
break;
}
}
}
/* setting producer status {{{ */
static void _producer_status_update(enum producer_status status)
{
producer_status = status;
pthread_cond_broadcast(&producer_playing);
}
static void _producer_play(void)
{
if (producer_status == PS_UNLOADED) {
struct track_info *ti;
if ((ti = cmus_get_next_track())) {
int rc;
ip = ip_new(ti->filename);
rc = ip_open(ip);
if (rc) {
player_ip_error(rc, "opening file `%s'", ti->filename);
ip_delete(ip);
track_info_unref(ti);
file_changed(NULL);
} else {
ip_setup(ip);
_producer_status_update(PS_PLAYING);
file_changed(ti);
}
}
} else if (producer_status == PS_PLAYING) {
if (ip_seek(ip, 0.0) == 0) {
reset_buffer();
}
} else if (producer_status == PS_STOPPED) {
int rc;
rc = ip_open(ip);
if (rc) {
player_ip_error(rc, "opening file `%s'", ip_get_filename(ip));
ip_delete(ip);
_producer_status_update(PS_UNLOADED);
} else {
ip_setup(ip);
_producer_status_update(PS_PLAYING);
}
} else if (producer_status == PS_PAUSED) {
_producer_status_update(PS_PLAYING);
}
}
static void _producer_stop(void)
{
if (producer_status == PS_PLAYING || producer_status == PS_PAUSED) {
ip_close(ip);
_producer_status_update(PS_STOPPED);
reset_buffer();
}
}
static void _producer_unload(void)
{
_producer_stop();
if (producer_status == PS_STOPPED) {
ip_delete(ip);
_producer_status_update(PS_UNLOADED);
}
}
static void _producer_pause(void)
{
if (producer_status == PS_PLAYING) {
_producer_status_update(PS_PAUSED);
} else if (producer_status == PS_PAUSED) {
_producer_status_update(PS_PLAYING);
}
}
static void _producer_set_file(struct track_info *ti)
{
_producer_unload();
ip = ip_new(ti->filename);
_producer_status_update(PS_STOPPED);
file_changed(ti);
}
/* setting producer status }}} */
/* setting consumer status {{{ */
static void _consumer_status_update(enum consumer_status status)
{
consumer_status = status;
pthread_cond_broadcast(&consumer_playing);
}
static void _consumer_play(void)
{
if (consumer_status == CS_PLAYING) {
op_drop();
} else if (consumer_status == CS_STOPPED) {
int rc;
set_buffer_sf();
rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
player_op_error(rc, "opening audio device");
} else {
_consumer_status_update(CS_PLAYING);
}
} else if (consumer_status == CS_PAUSED) {
op_unpause();
_consumer_status_update(CS_PLAYING);
}
}
static void _consumer_drain_and_stop(void)
{
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
op_close();
_consumer_status_update(CS_STOPPED);
}
}
static void _consumer_stop(void)
{
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
op_drop();
op_close();
_consumer_status_update(CS_STOPPED);
}
}
static void _consumer_pause(void)
{
if (consumer_status == CS_PLAYING) {
op_pause();
_consumer_status_update(CS_PAUSED);
} else if (consumer_status == CS_PAUSED) {
op_unpause();
_consumer_status_update(CS_PLAYING);
}
}
/* setting consumer status }}} */
static int change_sf(int drop)
{
int old_sf = buffer_sf;
CHANNEL_MAP(old_channel_map);
channel_map_copy(old_channel_map, buffer_channel_map);
set_buffer_sf();
if (buffer_sf != old_sf || !channel_map_equal(buffer_channel_map, old_channel_map, sf_get_channels(buffer_sf))) {
/* reopen */
int rc;
if (drop)
op_drop();
op_close();
rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
player_op_error(rc, "opening audio device");
_consumer_status_update(CS_STOPPED);
_producer_stop();
return rc;
}
} else if (consumer_status == CS_PAUSED) {
op_drop();
op_unpause();
}
_consumer_status_update(CS_PLAYING);
return 0;
}
static void _consumer_handle_eof(void)
{
struct track_info *ti;
if (ip_is_remote(ip)) {
_producer_stop();
_consumer_drain_and_stop();
player_error("lost connection");
return;
}
if (player_info_priv.ti)
player_info_priv.ti->play_count++;
if (player_repeat_current) {
if (player_cont) {
ip_seek(ip, 0);
reset_buffer();
} else {
_producer_stop();
_consumer_drain_and_stop();
}
_player_status_changed();
return;
}
if ((ti = cmus_get_next_track())) {
_producer_unload();
ip = ip_new(ti->filename);
_producer_status_update(PS_STOPPED);
/* PS_STOPPED, CS_PLAYING */
if (player_cont && (player_cont_album == 1 || strcmp(player_info_priv.ti->album,ti->album) == 0)) {
_producer_play();
if (producer_status == PS_UNLOADED) {
_consumer_stop();
track_info_unref(ti);
file_changed(NULL);
} else {
/* PS_PLAYING */
file_changed(ti);
if (!change_sf(0))
_prebuffer();
}
} else {
_consumer_drain_and_stop();
file_changed(ti);
}
} else {
_producer_unload();
_consumer_drain_and_stop();
file_changed(NULL);
}
_player_status_changed();
}
static void *consumer_loop(void *arg)
{
while (1) {
int rc, space;
int size;
char *rpos;
consumer_lock();
if (!consumer_running)
break;
if (consumer_status == CS_PAUSED || consumer_status == CS_STOPPED) {
pthread_cond_wait(&consumer_playing, &consumer_mutex);
consumer_unlock();
continue;
}
space = op_buffer_space();
if (space < 0) {
d_print("op_buffer_space returned %d %s\n", space,
space == -1 ? strerror(errno) : "");
/* try to reopen */
op_close();
_consumer_status_update(CS_STOPPED);
_consumer_play();
consumer_unlock();
continue;
}
/* d_print("BS: %6d %3d\n", space, space * 1000 / (44100 * 2 * 2)); */
while (1) {
if (space == 0) {
_consumer_position_update();
consumer_unlock();
ms_sleep(25);
break;
}
size = buffer_get_rpos(&rpos);
if (size == 0) {
producer_lock();
if (producer_status != PS_PLAYING) {
producer_unlock();
consumer_unlock();
break;
}
/* must recheck rpos */
size = buffer_get_rpos(&rpos);
if (size == 0) {
/* OK. now it's safe to check if we are at EOF */
if (ip_eof(ip)) {
/* EOF */
_consumer_handle_eof();
producer_unlock();
consumer_unlock();
break;
} else {
/* possible underrun */
producer_unlock();
_consumer_position_update();
consumer_unlock();
/* d_print("possible underrun\n"); */
ms_sleep(10);
break;
}
}
/* player_buffer and ip.eof were inconsistent */
producer_unlock();
}
if (size > space)
size = space;
if (soft_vol || replaygain)
scale_samples(rpos, (unsigned int *)&size);
rc = op_write(rpos, size);
if (rc < 0) {
d_print("op_write returned %d %s\n", rc,
rc == -1 ? strerror(errno) : "");
/* try to reopen */
op_close();
_consumer_status_update(CS_STOPPED);
_consumer_play();
consumer_unlock();
break;
}
buffer_consume(rc);
consumer_pos += rc;
space -= rc;
}
}
_consumer_stop();
consumer_unlock();
return NULL;
}
static void *producer_loop(void *arg)
{
while (1) {
/* number of chunks to fill
* too big => seeking is slow
* too small => underruns?
*/
const int chunks = 1;
int size, nr_read, i;
char *wpos;
producer_lock();
if (!producer_running)
break;
if (producer_status == PS_UNLOADED ||
producer_status == PS_PAUSED ||
producer_status == PS_STOPPED || ip_eof(ip)) {
pthread_cond_wait(&producer_playing, &producer_mutex);
producer_unlock();
continue;
}
for (i = 0; ; i++) {
size = buffer_get_wpos(&wpos);
if (size == 0) {
/* buffer is full */
producer_unlock();
ms_sleep(50);
break;
}
nr_read = ip_read(ip, wpos, size);
if (nr_read < 0) {
if (nr_read != -1 || errno != EAGAIN) {
player_ip_error(nr_read, "reading file %s",
ip_get_filename(ip));
/* ip_read sets eof */
nr_read = 0;
} else {
producer_unlock();
ms_sleep(50);
break;
}
}
if (ip_metadata_changed(ip))
metadata_changed();
/* buffer_fill with 0 count marks current chunk filled */
buffer_fill(nr_read);
if (nr_read == 0) {
/* consumer handles EOF */
producer_unlock();
ms_sleep(50);
break;
}
if (i == chunks) {
producer_unlock();
/* don't sleep! */
break;
}
}
_producer_buffer_fill_update();
}
_producer_unload();
producer_unlock();
return NULL;
}
void player_init(void)
{
int rc;
#ifdef REALTIME_SCHEDULING
pthread_attr_t attr;
#endif
pthread_attr_t *attrp = NULL;
/* 1 s is 176400 B (0.168 MB)
* 10 s is 1.68 MB
*/
buffer_nr_chunks = 10 * 44100 * 16 / 8 * 2 / CHUNK_SIZE;
buffer_init();
#ifdef REALTIME_SCHEDULING
rc = pthread_attr_init(&attr);
BUG_ON(rc);
rc = pthread_attr_setschedpolicy(&attr, SCHED_RR);
if (rc) {
d_print("could not set real-time scheduling priority: %s\n", strerror(rc));
} else {
struct sched_param param;
d_print("using real-time scheduling\n");
param.sched_priority = sched_get_priority_max(SCHED_RR);
d_print("setting priority to %d\n", param.sched_priority);
rc = pthread_attr_setschedparam(&attr, ¶m);
BUG_ON(rc);
attrp = &attr;
}
#endif
rc = pthread_create(&producer_thread, NULL, producer_loop, NULL);
BUG_ON(rc);
rc = pthread_create(&consumer_thread, attrp, consumer_loop, NULL);
if (rc && attrp) {
d_print("could not create thread using real-time scheduling: %s\n", strerror(rc));
rc = pthread_create(&consumer_thread, NULL, consumer_loop, NULL);
}
BUG_ON(rc);
/* update player_info_priv.cont etc. */
player_lock();
_player_status_changed();
player_unlock();
}
void player_exit(void)
{
int rc;
player_lock();
consumer_running = 0;
pthread_cond_broadcast(&consumer_playing);
producer_running = 0;
pthread_cond_broadcast(&producer_playing);
player_unlock();
rc = pthread_join(consumer_thread, NULL);
BUG_ON(rc);
rc = pthread_join(producer_thread, NULL);
BUG_ON(rc);
buffer_free();
}
void player_stop(void)
{
player_lock();
_consumer_stop();
_producer_stop();
_player_status_changed();
player_unlock();
}
void player_play(void)
{
int prebuffer;
player_lock();
if (producer_status == PS_PLAYING && ip_is_remote(ip)) {
/* seeking not allowed */
player_unlock();
return;
}
prebuffer = consumer_status == CS_STOPPED;
_producer_play();
if (producer_status == PS_PLAYING) {
_consumer_play();
if (consumer_status != CS_PLAYING)
_producer_stop();
} else {
_consumer_stop();
}
_player_status_changed();
if (consumer_status == CS_PLAYING && prebuffer)
_prebuffer();
player_unlock();
}
void player_pause(void)
{
if (ip && ip_is_remote(ip) && consumer_status == CS_PLAYING) {
/* pausing not allowed */
player_stop();
return;
}
player_lock();
if (consumer_status == CS_STOPPED) {
_producer_play();
if (producer_status == PS_PLAYING) {
_consumer_play();
if (consumer_status != CS_PLAYING)
_producer_stop();
}
_player_status_changed();
if (consumer_status == CS_PLAYING)
_prebuffer();
player_unlock();
return;
}
_producer_pause();
_consumer_pause();
_player_status_changed();
player_unlock();
}
void player_pause_playback(void)
{
if (consumer_status == CS_PLAYING)
player_pause();
}
void player_set_file(struct track_info *ti)
{
player_lock();
_producer_set_file(ti);
if (producer_status == PS_UNLOADED) {
_consumer_stop();
goto out;
}
/* PS_STOPPED */
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
op_drop();
_producer_play();
if (producer_status == PS_UNLOADED) {
_consumer_stop();
goto out;
}
change_sf(1);
}
out:
_player_status_changed();
if (producer_status == PS_PLAYING)
_prebuffer();
player_unlock();
}
void player_play_file(struct track_info *ti)
{
player_lock();
_producer_set_file(ti);
if (producer_status == PS_UNLOADED) {
_consumer_stop();
goto out;
}
/* PS_STOPPED */
_producer_play();
/* PS_UNLOADED,PS_PLAYING */
if (producer_status == PS_UNLOADED) {
_consumer_stop();
goto out;
}
/* PS_PLAYING */
if (consumer_status == CS_STOPPED) {
_consumer_play();
if (consumer_status == CS_STOPPED)
_producer_stop();
} else {
op_drop();
change_sf(1);
}
out:
_player_status_changed();
if (producer_status == PS_PLAYING)
_prebuffer();
player_unlock();
}
void player_file_changed(struct track_info *ti)
{
_file_changed(ti);
}
void player_seek(double offset, int relative, int start_playing)
{
int stopped = 0;
player_lock();
if (consumer_status == CS_STOPPED) {
stopped = 1;
_producer_play();
if (producer_status == PS_PLAYING) {
_consumer_play();
if (consumer_status != CS_PLAYING) {
_producer_stop();
player_unlock();
return;
} else
_player_status_changed();
}
}
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
double pos, duration, new_pos;
int rc;
pos = (double)consumer_pos / (double)buffer_second_size();
duration = ip_duration(ip);
if (duration < 0) {
/* can't seek */
d_print("can't seek\n");
player_unlock();
return;
}
if (relative) {
new_pos = pos + offset;
if (new_pos < 0.0)
new_pos = 0.0;
if (offset > 0.0) {
/* seeking forward */
if (new_pos > duration) {
player_unlock();
cmus_next();
return;
}
if (new_pos < 0.0)
new_pos = 0.0;
if (new_pos < pos - 0.5) {
/* must seek at least 0.5s */
d_print("must seek at least 0.5s\n");
player_unlock();
return;
}
}
} else {
new_pos = offset;
if (new_pos < 0.0) {
d_print("seek offset negative\n");
player_unlock();
return;
}
if (new_pos > duration - 5.0) {
new_pos = duration - 5.0;
if (new_pos < 0.0)
new_pos = 0.0;
}
}
/* d_print("seeking %g/%g (%g from eof)\n", new_pos, duration, duration - new_pos); */
rc = ip_seek(ip, new_pos);
if (rc == 0) {
d_print("doing op_drop after seek\n");
op_drop();
reset_buffer();
consumer_pos = new_pos * buffer_second_size();
scale_pos = consumer_pos;
_consumer_position_update();
if (stopped && !start_playing) {
_producer_pause();
_consumer_pause();
_player_status_changed();
}
} else {
player_ip_error(rc, "seeking in file %s", ip_get_filename(ip));
d_print("error: ip_seek returned %d\n", rc);
}
}
mpris_seeked();
player_unlock();
}
/*
* change output plugin without stopping playback
*/
void player_set_op(const char *name)
{
int rc;
player_lock();
/* drop needed because close drains the buffer */
if (consumer_status == CS_PAUSED)
op_drop();
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
op_close();
if (name) {
d_print("setting op to '%s'\n", name);
rc = op_select(name);
} else {
/* first initialized plugin */
d_print("selecting first initialized op\n");
rc = op_select_any();
}
if (rc) {
_consumer_status_update(CS_STOPPED);
_producer_stop();
if (name)
player_op_error(rc, "selecting output plugin '%s'", name);
else
player_op_error(rc, "selecting any output plugin");
player_unlock();
return;
}
if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
set_buffer_sf();
rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
_consumer_status_update(CS_STOPPED);
_producer_stop();
player_op_error(rc, "opening audio device");
player_unlock();
return;
}
if (consumer_status == CS_PAUSED)
op_pause();
}
player_unlock();
}
void player_set_buffer_chunks(unsigned int nr_chunks)
{
player_lock();
_producer_stop();
_consumer_stop();
buffer_nr_chunks = nr_chunks;
buffer_init();
_player_status_changed();
player_unlock();
}
int player_get_buffer_chunks(void)
{
return buffer_nr_chunks;
}
void player_set_soft_volume(int l, int r)
{
consumer_lock();
soft_vol_l = l;
soft_vol_r = r;
consumer_unlock();
}
void player_set_soft_vol(int soft)
{
consumer_lock();
/* don't mess with scale_pos if soft_vol or replaygain is already enabled */
if (!soft_vol && !replaygain)
scale_pos = consumer_pos;
soft_vol = soft;
consumer_unlock();
}
static int calc_vol(int val, int old, int max_vol, unsigned int flags)
{
if (flags & VF_RELATIVE) {
if (flags & VF_PERCENTAGE)
val = scale_from_percentage(val, max_vol);
val += old;
} else if (flags & VF_PERCENTAGE) {
val = scale_from_percentage(val, max_vol);
}
return clamp(val, 0, max_vol);
}
int player_set_vol(int l, int lf, int r, int rf)
{
int rc = OP_ERROR_SUCCESS;
if (soft_vol) {
l = calc_vol(l, soft_vol_l, 100, lf);
r = calc_vol(r, soft_vol_r, 100, rf);
player_set_soft_volume(l, r);
} else {
mixer_read_volume();
l = calc_vol(l, volume_l, volume_max, lf);
r = calc_vol(r, volume_r, volume_max, rf);
rc = mixer_set_volume(l, r);
mixer_read_volume();
}
return rc;
}
void player_set_rg(enum replaygain rg)
{
player_lock();
/* don't mess with scale_pos if soft_vol or replaygain is already enabled */
if (!soft_vol && !replaygain)
scale_pos = consumer_pos;
replaygain = rg;
player_info_priv_lock();
update_rg_scale();
player_info_priv_unlock();
player_unlock();
}
void player_set_rg_limit(int limit)
{
player_lock();
replaygain_limit = limit;
player_info_priv_lock();
update_rg_scale();
player_info_priv_unlock();
player_unlock();
}
void player_set_rg_preamp(double db)
{
player_lock();
replaygain_preamp = db;
player_info_priv_lock();
update_rg_scale();
player_info_priv_unlock();
player_unlock();
}
void player_info_snapshot(void)
{
player_info_priv_lock();
free(player_info.error_msg);
if (player_info.ti)
track_info_unref(player_info.ti);
memcpy(&player_info, &player_info_priv, sizeof(player_info));
if (player_info.ti)
track_info_ref(player_info.ti);
player_info_priv.file_changed = 0;
player_info_priv.metadata_changed = 0;
player_info_priv.status_changed = 0;
player_info_priv.position_changed = 0;
player_info_priv.buffer_fill_changed = 0;
player_info_priv.error_msg = NULL;
player_info_priv_unlock();
}
void player_metadata_lock(void)
{
cmus_mutex_lock(&player_info_mutex);
}
void player_metadata_unlock(void)
{
cmus_mutex_unlock(&player_info_mutex);
}