aboutsummaryrefslogblamecommitdiffstats
path: root/src/outputBuffer.c
blob: fd6b115f64b9f334a52b295a5393e5052cffa1f7 (plain) (tree)
1
2
                                
                                                                   

















                                                                            
                  
                      






                          
                   









                                                                

                               











                                                                 



                                                                         














                                                                

                                                                  










                                                                   

                                           
 



                                                         

 
                                             
 










































                                                                             

 
                                                  
 





                                                                       

 

                                                                              
 
                            
               
                           















                                                                            

 
                                           
 






                                                                             

                            


                                  
 

                                     
                            








                                                         
         
                                           
                              

 
                                                
 



                                  

 
                                                




                                                         
                            

                                   
                                        
                                          


                                    
                                          
 
                                                         


                                      
                              

 





                                                                            
 











                                                                    

 
                                                 
 


















                                                                            
                                                  

                                    

 
                                              
 




























                                                                             
                                                    
                            

                                    



                                         

 





                                                                       
                                                                
 






                                                                           


                    
 

                                         
                                                              

                                
 














                                                                             

 









                                                         

 

                                                                   
   
                   
 















                                                                 
                                  



                                               
 


















                                                                            

                                 
                            






                                                         




                                                                      

                       
 




                                                  





                                                                      
         
 



                              
 







                                                 
                                
         
 







                                                    

                        
                                                                       





                                                               



                                                             

                                                                  

 
                                           
 















                                                         
                                                                       




                                                     
                 
         






                                          
 
                                     
 





                                                         
 



                                                         
 
 




                                                              
 
                            













                                                                    
                                            







                                                                   
 

                                 
/* the Music Player Daemon (MPD)
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
 * This project's homepage is: http://www.musicpd.org
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "outputBuffer.h"

#include "utils.h"
#include "normalize.h"
#include "ringbuf.h"
#include "condition.h"
#include "song.h"
#include "main_notify.h"
#include "player_error.h"
#include "log.h"
#include "action_status.h"
#include "decode.h"

/* typically have 2048-4096 of these structs, so pack tightly */
struct ob_chunk {
	mpd_uint16 len; /* 0: skip this chunk */
	mpd_uint16 bit_rate;
	float time;
	mpd_uint8 seq; /* see seq_ok() for explanation */
	char data[CHUNK_SIZE];
};

static struct ob_chunk silence;

enum ob_xfade_state {
	XFADE_DISABLED = 0,
	XFADE_ENABLED
};

static struct condition ob_action_cond = STATIC_COND_INITIALIZER;
static struct condition ob_halt_cond = STATIC_COND_INITIALIZER;
static struct condition ob_seq_cond = STATIC_COND_INITIALIZER;

struct output_buffer {
	struct ringbuf *index; /* index for chunks */
	struct ob_chunk *chunks;

	size_t bpp_max; /* buffer_before_play, user setting, in chunks */
	size_t bpp_cur; /* current prebuffer size (in chunks) */

	enum ob_state state; /* protected by ob_action_cond */
	enum ob_action action; /* protected by ob_action_cond */
	enum ob_xfade_state xfade_state; /* thread-internal */
	int sw_vol;
	int bit_rate;
	float total_time;
	float elapsed_time;
	AudioFormat audio_format;
	size_t xfade_cur;
	size_t xfade_max;
	float xfade_time;
	void *conv_buf;
	size_t conv_buf_len;
	pthread_t thread;
	ConvState conv_state;
	mpd_uint8 seq_drop;
	mpd_uint8 seq_player; /* only gets changed by ob.thread */
	mpd_uint8 seq_decoder; /* only gets changed by dc.thread */
	struct ringbuf preseek_index;
	enum ob_state preseek_state;
	mpd_uint16 *preseek_len;
};

static struct output_buffer ob;

#include "outputBuffer_xfade.h"
#include "outputBuffer_accessors.h"

static enum action_status ob_do_stop(void);
static void stop_playback(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	cond_enter(&ob_action_cond);
	ob_do_stop();
	cond_leave(&ob_action_cond);
}

void ob_trigger_action(enum ob_action action)
{
	/*
	 * This can be called by both dc.thread and main_thread, but only one
	 * action can be in progress at once.  So we use this private mutex
	 * to protect against simultaneous invocations stepping over
	 * each other
	 */
	static pthread_mutex_t trigger_lock = PTHREAD_MUTEX_INITIALIZER;
	pthread_mutex_lock(&trigger_lock);
	DEBUG(__FILE__": %d action: %d\n", __LINE__, action);
	assert(!pthread_equal(pthread_self(), ob.thread));
	DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
	cond_enter(&ob_action_cond);
	DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
	assert(ob.action == OB_ACTION_NONE);

	if (pthread_equal(pthread_self(), dc.thread))
		assert(action == OB_ACTION_PLAY ||
		       action == OB_ACTION_SEEK_START ||
		       action == OB_ACTION_SEEK_FINISH);
	else
		assert(action != OB_ACTION_PLAY &&
		       action != OB_ACTION_SEEK_START &&
		       action != OB_ACTION_SEEK_FINISH);
	ob.action = action;
	do {
		switch (ob.state) {
		case OB_STATE_PAUSE:
		case OB_STATE_STOP:
		case OB_STATE_SEEK:
			DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
			cond_signal_sync(&ob_halt_cond);
			DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
			break;
		default: break;
		}
		DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
		cond_wait(&ob_action_cond);
	} while (ob.action != OB_ACTION_NONE);
	DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
	assert(ob.action == OB_ACTION_NONE);
	cond_leave(&ob_action_cond);
	DEBUG(__FILE__":%s %d\n", __func__, __LINE__);
	pthread_mutex_unlock(&trigger_lock);
}

static enum action_status ob_finalize_action(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	ob.action = OB_ACTION_NONE;
	/* DEBUG(__FILE__ ":%s signaling %d\n", __func__, __LINE__); */
	cond_signal(&ob_action_cond);
	cond_leave(&ob_action_cond);
	return AS_COMPLETE;
}

/* marks all buffered chunks with sequence number matching `seq' as invalid */
static enum action_status ob_do_drop(void)
{
	struct rbvec vec[2];
	long i;
	mpd_uint8 seq_drop;

	cond_enter(&ob_seq_cond);
	seq_drop = ob.seq_drop;
	/* drop the audio that we've already pushed to the device, too */
	if (seq_drop == ob.seq_player)
		dropBufferedAudio();
	cond_leave(&ob_seq_cond);

	assert(pthread_equal(pthread_self(), ob.thread));
	for (i = (long)ringbuf_get_read_vector(ob.index, vec); --i >= 0; ) {
		struct ob_chunk *c = get_chunk(vec, i);
		assert(c);
		if (c->seq == seq_drop)
			c->len = 0;
	}
	return ob_finalize_action();
}

static enum action_status ob_do_pause(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	ob.xfade_state = XFADE_DISABLED;
	/*
	 * This will eventually set certain outputs (like shout) into 'pause'
	 * state where it'll just play silence instead of disconnecting
	 * listeners
	 */
	dropBufferedAudio();
	closeAudioDevice();
	ob.state = OB_STATE_PAUSE;
	return AS_INPROGRESS;
}

static void reader_reset_buffer(void)
{
	struct rbvec vec[2];
	size_t nr;
	long i;

	assert(pthread_equal(pthread_self(), ob.thread));
	nr = ringbuf_get_read_vector(ob.index, vec);
	for (i = nr; --i >= 0; ) {
		struct ob_chunk *c = get_chunk(vec, i);
		assert(c);
		c->len = 0;
	}
	ringbuf_read_advance(ob.index, nr);
	metadata_pipe_clear();
}

static void ob_seq_player_set(mpd_uint8 seq_num)
{
	cond_enter(&ob_seq_cond);
	ob.seq_player = seq_num;
	cond_signal(&ob_seq_cond);
	cond_leave(&ob_seq_cond);
}

static enum action_status ob_do_reset(int close)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	ob.elapsed_time = 0;
	ob.total_time = 0;
	reader_reset_buffer();
	dropBufferedAudio();
	if (close)
		closeAudioDevice();
	ob.xfade_state = XFADE_DISABLED;
	ob_seq_player_set(ob.seq_decoder);
	return ob_finalize_action();
}

static enum action_status ob_do_stop(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	if (ob.state == OB_STATE_STOP)
		return AS_INPROGRESS;
	ob.state = OB_STATE_STOP;
	return ob_do_reset(1);
}

/*
 * we need to reset the buffer *before* we seek because the decoder
 * _may_ try to flush out the last remnants of the previously decoded audio,
 * so we need to ensure there is space available for that
 */
static enum action_status ob_do_seek_start(void)
{
	int i;

	assert(pthread_equal(pthread_self(), ob.thread));

	/* preserve pre-seek ringbuf and state information */
	memcpy(&ob.preseek_index, ob.index, sizeof(struct ringbuf));
	for (i = ob.preseek_index.size; --i >= 0; )
		ob.preseek_len[i] = ob.chunks[i].len;
	ob.preseek_state = ob.state;
	ob.state = OB_STATE_SEEK;
	reader_reset_buffer();
	return AS_INPROGRESS;
}

static enum action_status ob_do_seek_finish(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	assert(ob.state == OB_STATE_SEEK);
	ob.state = ob.preseek_state;
	if (dc.seek_where < 0) {
		int i;
		assert(dc.seek_where == DC_SEEK_MISMATCH ||
		       dc.seek_where == DC_SEEK_ERROR);

		/* restore the old ringbuf index if we failed to seek */
		memcpy(ob.index, &ob.preseek_index, sizeof(struct ringbuf));
		for (i = ob.preseek_index.size; --i >= 0; )
			ob.chunks[i].len = ob.preseek_len[i];
	} else {
		assert(dc.seek_where >= 0);
		ob.xfade_state = XFADE_DISABLED;
		ob.elapsed_time = dc.seek_where;
		ob.total_time = dc.total_time;
		reader_reset_buffer();
		dropBufferedAudio();
		ob_seq_player_set(ob.seq_decoder);
	}
	return ob_finalize_action();
}

static enum action_status ob_take_action(void)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	if (mpd_likely(ob.action == OB_ACTION_NONE))
		return AS_COMPLETE;
	DEBUG(__FILE__": %s %d\n", __func__, __LINE__);
	cond_enter(&ob_action_cond);
	DEBUG(__FILE__": %s %d action: %d\n", __func__, __LINE__, ob.action);
	switch (ob.action) {
	case OB_ACTION_NONE: return ob_finalize_action();
	case OB_ACTION_PLAY: ob.state = OB_STATE_PLAY; break;
	case OB_ACTION_DROP: return ob_do_drop();
	case OB_ACTION_SEEK_START: return ob_do_seek_start();
	case OB_ACTION_SEEK_FINISH: return ob_do_seek_finish();
	case OB_ACTION_PAUSE_SET:
		if (ob.state == OB_STATE_PLAY)
			return ob_do_pause();
		ob.state = OB_STATE_PAUSE;
		break;
	case OB_ACTION_PAUSE_UNSET:
		if (ob.state == OB_STATE_PAUSE)
			ob.state = OB_STATE_PLAY;
		break;
	case OB_ACTION_PAUSE_FLIP:
		switch (ob.state) {
		case OB_STATE_PLAY: return ob_do_pause();
		case OB_STATE_PAUSE: ob.state = OB_STATE_PLAY; break;
		default: break;
		}
		break;
	case OB_ACTION_STOP: return ob_do_stop();
	case OB_ACTION_RESET: return ob_do_reset(0);
	case OB_ACTION_QUIT:
		dropBufferedAudio();
		closeAudioDevice();
		ob.state = OB_STATE_QUIT;
		return AS_INPROGRESS;
	}
	return ob_finalize_action();
}

/*
 * looks up the chunk given by index `i', returns NULL if `i' is beyond
 * the end of the buffer.  This allows us to treat our chunks array
 * like an infinite, rotating buffer.  The first available chunk
 * is always indexed as `0', the second one as `1', and so on...
 */
static struct ob_chunk *get_chunk(struct rbvec vec[2], size_t i)
{
	if (vec[0].len > i)
		return &ob.chunks[vec[0].base + i - ob.index->buf];
	if (i && vec[1].base) {
		assert(vec[0].len > 0);
		i -= vec[0].len;
		if (vec[1].len > i)
			return &ob.chunks[vec[1].base + i - ob.index->buf];
	}
	return NULL;
}

static void prevent_buffer_underrun(void)
{
	if (playAudio(silence.data, sizeof(silence.data)) < 0)
		stop_playback();
}

/* causes ob_do_drop() to be called (and waits for completion) */
void ob_drop_audio(enum ob_drop_type type)
{
	assert(!pthread_equal(pthread_self(), ob.thread));
	assert(!pthread_equal(pthread_self(), dc.thread));
	assert(dc.state == DC_STATE_STOP); /* not needed, just a good idea */
	cond_enter(&ob_seq_cond);
	switch (type) {
	case OB_DROP_DECODED: ob.seq_drop = ob.seq_decoder; break;
	case OB_DROP_PLAYING: ob.seq_drop = ob.seq_player; break;
	}
	cond_leave(&ob_seq_cond);
	/* DEBUG("dropping %u\n", ob.seq_drop); */
	ob_trigger_action(OB_ACTION_DROP);
	/* DEBUG("done dropping %u\n", ob.seq_drop); */
}

/* call this exactly once before decoding each song */
void ob_advance_sequence(void)
{
	assert(pthread_equal(pthread_self(), dc.thread));
	DEBUG(__FILE__": %s %d\n", __func__, __LINE__);
	cond_enter(&ob_seq_cond);
	++ob.seq_decoder;
	cond_leave(&ob_seq_cond);
	DEBUG(__FILE__": %s %d\n", __func__, __LINE__);
	DEBUG("ob.seq_decoder: %d\n", ob.seq_decoder);
}

/*
 * Returns true if output buffer is playing the song we're decoding
 */
int ob_synced(void)
{
	int ret;
	assert(!pthread_equal(pthread_self(), dc.thread));
	assert(!pthread_equal(pthread_self(), ob.thread));
	/* assert(pthread_equal(pthread_self(), main_thread)); */
	cond_enter(&ob_seq_cond);
	ret = (ob.seq_decoder == ob.seq_player);
	cond_leave(&ob_seq_cond);
	return ret;
}

static void new_song_chunk(struct ob_chunk *a)
{
	assert(pthread_equal(pthread_self(), ob.thread));
	ob.xfade_state = XFADE_DISABLED;
	ob.total_time = dc.total_time;
	/* DEBUG("ob.total_time: %f\n", ob.total_time); */
	ob_seq_player_set(a->seq);
	wakeup_main_task(); /* sync playlist */
}

#include "outputBuffer_audio.h"

static void send_next_tag(void)
{
	static MpdTag *last_tag;
	MpdTag *tag;

	if ((tag = metadata_pipe_recv())) { /* streaming tag */
		DEBUG("Caught new metadata! %p\n", tag);
		sendMetadataToAudioDevice(tag);
		freeMpdTag(tag);
		wakeup_main_task(); /* call sync_metadata() in playlist.c */
	} else if ((tag = playlist_current_tag())) { /* static file tag */
		/* shouldn't need mpdTagsAreEqual here for static tags */
		if (last_tag != tag) {
			sendMetadataToAudioDevice(tag);
			last_tag = tag;
		}
	}
}

static void play_next_chunk(void)
{
	struct rbvec vec[2];
	struct ob_chunk *a;
	size_t nr;
	static float last_time;

	assert(pthread_equal(pthread_self(), ob.thread));

	nr = ringbuf_get_read_vector(ob.index, vec);
	if (mpd_unlikely(!nr)) {
		if (dc.state == DC_STATE_STOP && ! playlist_playing())
			stop_playback();
		else
			prevent_buffer_underrun();
		return;
	}

	if (ob.xfade_time <= 0) {
		if (nr < ob.bpp_cur) {
			prevent_buffer_underrun();
			return;
		}
	} else if (nr < xfade_chunks_needed(vec)) {
		if (dc.state != DC_STATE_STOP && playlist_playing()) {
			prevent_buffer_underrun();
			return;
		}
		/* nearing end of last track, xfade to silence.. */
	}

	a = get_chunk(vec, 0);
	assert(a);
	if (! a->len)
		goto out;

	if (ob.xfade_state == XFADE_ENABLED) {
		struct ob_chunk *b;
		b = get_chunk(vec, ob.xfade_max);
		if (!b) { /* xfade to silence */
			b = &silence;
			b->len = a->len;
			b->seq = a->seq + 1;
		}
		xfade_mix(a, b);
	}

	last_time = ob.elapsed_time = a->time;
	ob.bit_rate = a->bit_rate;

	if (mpd_unlikely(ob.seq_player != a->seq)) {
		if (open_audio_devices(1) < 0)
			return;
		new_song_chunk(a);
	}
	send_next_tag();

	pcm_volumeChange(a->data, a->len, &ob.audio_format, ob.sw_vol);
	if (playAudio(a->data, a->len) < 0)
		stop_playback();
	a->len = 0; /* mark the chunk as empty for ob_send() */
out:
	ringbuf_read_advance(ob.index, 1);

	/* we've played our first chunk, stop prebuffering */
	if (mpd_unlikely(ob.bpp_cur))
		ob.bpp_cur = 0;

	/* unblock ob_send() if it was waiting on a full buffer */
	dc_try_unhalt();
}

static void * ob_task(mpd_unused void *arg)
{
	enum action_status as;

	assert(pthread_equal(pthread_self(), ob.thread));
	cond_enter(&ob_halt_cond);
	while (1) {
		as = ob_take_action();
		switch (ob.state) {
		case OB_STATE_PLAY:
			assert(as == AS_COMPLETE);
			if (open_audio_devices(0) >= 0)
				play_next_chunk();
			break;
		case OB_STATE_STOP:
		case OB_STATE_PAUSE:
		case OB_STATE_SEEK:
			assert(as != AS_DEFERRED);
			ob.bpp_cur = ob.bpp_max; /* enable prebuffer */
			if (as == AS_INPROGRESS)
				ob_finalize_action();
			cond_wait(&ob_halt_cond);
			break;
		case OB_STATE_QUIT: goto out;
		}
	}
out:
	cond_leave(&ob_halt_cond);
	assert(ob.state == OB_STATE_QUIT);
	assert(as == AS_INPROGRESS);
	ob_finalize_action();
	return NULL;
}

#include "outputBuffer_config_init.h"

void ob_seek_start(void)
{
	assert(pthread_equal(pthread_self(), dc.thread));
	assert(dc.seek_where >= 0);
	ob_trigger_action(OB_ACTION_SEEK_START);
}

void ob_seek_finish(void)
{
	assert(pthread_equal(pthread_self(), dc.thread));
	ob_trigger_action(OB_ACTION_SEEK_FINISH);
}

/*
 * if there are any partially written chunk, flush them out to
 * the output process _before_ decoding the next track
 */
void ob_flush(void)
{
	struct rbvec vec[2];

	assert(pthread_equal(pthread_self(), dc.thread));
	/* DEBUG(__FILE__":%s %d\n", __func__, __LINE__); */

	if (ringbuf_get_write_vector(ob.index, vec)) {
		/* DEBUG(__FILE__":%s %d\n", __func__, __LINE__); */
		struct ob_chunk *c = get_chunk(vec, 0);
		assert(c);
		if (c->len) {
			assert(ob.seq_decoder == c->seq);
			switch (ob.state) {
			case OB_STATE_SEEK:
				assert(0);
			case OB_STATE_PLAY:
			case OB_STATE_PAUSE:
				ringbuf_write_advance(ob.index, 1);
				break;
			case OB_STATE_STOP:
			case OB_STATE_QUIT:
				c->len = 0;
			}
		}
	}
}

#include "outputBuffer_ob_send.h"