diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.h | 25 | ||||
| -rw-r--r-- | src/main.h | 2 | ||||
| -rw-r--r-- | src/player.c | 276 | ||||
| -rw-r--r-- | src/player.h | 23 | ||||
| -rw-r--r-- | src/ui.c | 2 | ||||
| -rw-r--r-- | src/ui.h | 2 | ||||
| -rw-r--r-- | src/ui_act.c | 2 | 
7 files changed, 221 insertions, 111 deletions
| diff --git a/src/config.h b/src/config.h index 6d7147d..fb8a041 100644 --- a/src/config.h +++ b/src/config.h @@ -6,4 +6,29 @@  #define VERSION "2014.06.08-dev" +/* ffmpeg/libav quirks detection + * ffmpeg’s micro versions always start at 100, that’s how we can distinguish + * ffmpeg and libav */ +#include <libavfilter/version.h> + +/* is "timeout" option present (all versions of ffmpeg, not libav) */ +#if LIBAVFILTER_VERSION_MICRO >= 100 +#define HAVE_AV_TIMEOUT +#endif + +/* does graph_send_command exist (ffmpeg >=2.2) */ +#if LIBAVFILTER_VERSION_MAJOR == 4 && \ +		LIBAVFILTER_VERSION_MICRO >= 100 +#define HAVE_AVFILTER_GRAPH_SEND_COMMAND +#endif + +/* need avcodec.h (ffmpeg 1.2) */ +#if LIBAVFILTER_VERSION_MAJOR == 3 && \ +		LIBAVFILTER_VERSION_MINOR <= 42 && \ +		LIBAVFILTER_VERSION_MINOR > 32 && \ +		LIBAVFILTER_VERSION_MICRO >= 100 +#define HAVE_AV_BUFFERSINK_GET_BUFFER_REF +#define HAVE_LIBAVFILTER_AVCODEC_H +#endif +  #endif /* SRC_CONFIG_H_S6A1C09K */ @@ -34,7 +34,7 @@ THE SOFTWARE.  typedef struct {  	PianoHandle_t ph;  	WaitressHandle_t waith; -	struct audioPlayer player; +	player_t player;  	BarSettings_t settings;  	/* first item is current song */  	PianoSong_t *playlist; diff --git a/src/player.c b/src/player.c index 3a2c9fa..5421310 100644 --- a/src/player.c +++ b/src/player.c @@ -31,28 +31,9 @@ THE SOFTWARE.  #include <assert.h>  #include <arpa/inet.h> -/* ffmpeg/libav quirks - * ffmpeg’s micro versions always start at 100, that’s how we can distinguish - * ffmpeg and libav */ -#include <libavfilter/version.h> -/* ffmpeg >=2.2 */ -#if LIBAVFILTER_VERSION_MAJOR == 4 && \ -		LIBAVFILTER_VERSION_MICRO >= 100 -#define HAVE_AVFILTER_GRAPH_SEND_COMMAND -#endif - -/* ffmpeg 1.2 */ -#if LIBAVFILTER_VERSION_MAJOR == 3 && \ -		LIBAVFILTER_VERSION_MINOR <= 42 && \ -		LIBAVFILTER_VERSION_MINOR > 32 && \ -		LIBAVFILTER_VERSION_MICRO >= 100 -#define HAVE_AV_BUFFERSINK_GET_BUFFER_REF -#define HAVE_LIBAVFILTER_AVCODEC_H -#endif +#include "config.h" -#include <ao/ao.h>  #include <libavcodec/avcodec.h> -#include <libavformat/avformat.h>  #include <libavutil/avutil.h>  #include <libavfilter/avfilter.h>  #include <libavfilter/avfiltergraph.h> @@ -64,12 +45,17 @@ THE SOFTWARE.  #endif  #include <libavutil/channel_layout.h>  #include <libavutil/opt.h> +#ifndef HAVE_AV_TIMEOUT +#include <libavutil/time.h> +#endif  #include "player.h" -#include "config.h"  #include "ui.h"  #include "ui_types.h" +/* default sample format */ +const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16; +  static void printError (const BarSettings_t * const settings,  		const char * const msg, int ret) {  	char avmsg[128]; @@ -97,7 +83,7 @@ void BarPlayerDestroy () {  /*	Update volume filter   */ -void BarPlayerSetVolume (struct audioPlayer * const player) { +void BarPlayerSetVolume (player_t * const player) {  	assert (player != NULL);  	assert (player->fvolume != NULL); @@ -124,51 +110,74 @@ void BarPlayerSetVolume (struct audioPlayer * const player) {  #define softfail(msg) \  	printError (player->settings, msg, ret); \ -	pret = PLAYER_RET_SOFTFAIL; \ -	goto finish; +	return false; -/*	player thread; for every song a new thread is started - *	@param audioPlayer structure - *	@return PLAYER_RET_* +#ifndef HAVE_AV_TIMEOUT +/*	interrupt callback for libav, which lacks a timeout option + * + *	obviously calling ping() a lot of times and then calling av_gettime here + *	again is rather inefficient.   */ -void *BarPlayerThread (void *data) { -	assert (data != NULL); +static int intCb (void * const data) { +	player_t * const player = data; +	assert (player != NULL); +	/* 10 seconds timeout (usec) */ +	return (av_gettime () - player->ping) > 10*1000000; +} -	struct audioPlayer * const player = data; -	int ret; -	intptr_t pret = PLAYER_RET_OK; -	const enum AVSampleFormat avformat = AV_SAMPLE_FMT_S16; +#define ping() player->ping = av_gettime () +#else +#define ping() +#endif -	ao_device *aoDev = NULL; -	ao_sample_format aoFmt; +static bool openStream (player_t * const player) { +	assert (player != NULL); +	/* no leak? */ +	assert (player->fctx == NULL); -	AVFrame *frame = NULL, *filteredFrame = NULL; -	AVFormatContext *fctx = NULL; -	AVCodecContext *cctx = NULL; +	int ret;  	/* stream setup */ -	if ((ret = avformat_open_input (&fctx, player->url, NULL, NULL)) < 0) { +	AVDictionary *options = NULL; +#ifdef HAVE_AV_TIMEOUT +	/* 10 seconds timeout on TCP r/w */ +	av_dict_set (&options, "timeout", "10000000", 0); +#else +	/* libav does not support the timeout option above. the workaround stores +	 * the current time with ping() now and then, registers an interrupt +	 * callback (below) and compares saved/current time in this callback. it’s +	 * not bullet-proof, but seems to work fine for av_read_frame. */ +	player->fctx = avformat_alloc_context (); +	player->fctx->interrupt_callback.callback = intCb; +	player->fctx->interrupt_callback.opaque = player; +#endif + +	assert (player->url != NULL); +	ping (); +	if ((ret = avformat_open_input (&player->fctx, player->url, NULL, &options)) < 0) {  		softfail ("Unable to open audio file");  	} -	if ((ret = avformat_find_stream_info (fctx, NULL)) < 0) { +	ping (); +	if ((ret = avformat_find_stream_info (player->fctx, NULL)) < 0) {  		softfail ("find_stream_info");  	}  	/* ignore all streams, undone for audio stream below */ -	for (size_t i = 0; i < fctx->nb_streams; i++) { -		fctx->streams[i]->discard = AVDISCARD_ALL; +	for (size_t i = 0; i < player->fctx->nb_streams; i++) { +		player->fctx->streams[i]->discard = AVDISCARD_ALL;  	} -	const int streamIdx = av_find_best_stream (fctx, AVMEDIA_TYPE_AUDIO, -1, -			-1, NULL, 0); -	if (streamIdx < 0) { +	ping (); +	player->streamIdx = av_find_best_stream (player->fctx, AVMEDIA_TYPE_AUDIO, +			-1, -1, NULL, 0); +	if (player->streamIdx < 0) {  		softfail ("find_best_stream");  	} -	AVStream * const st = fctx->streams[streamIdx]; -	cctx = st->codec; -	st->discard = AVDISCARD_DEFAULT; +	player->st = player->fctx->streams[player->streamIdx]; +	AVCodecContext * const cctx = player->st->codec; +	player->st->discard = AVDISCARD_DEFAULT;  	/* decoder setup */  	AVCodec * const decoder = avcodec_find_decoder (cctx->codec_id); @@ -180,32 +189,39 @@ void *BarPlayerThread (void *data) {  		softfail ("codec_open2");  	} -	frame = avcodec_alloc_frame (); -	assert (frame != NULL); -	filteredFrame = avcodec_alloc_frame (); -	assert (filteredFrame != NULL); +	if (player->lastTimestamp > 0) { +		ping (); +		av_seek_frame (player->fctx, player->streamIdx, player->lastTimestamp, 0); +	} -	AVPacket pkt; -	av_init_packet (&pkt); -	pkt.data = NULL; -	pkt.size = 0; +	player->songPlayed = 0; +	player->songDuration = av_q2d (player->st->time_base) * +			(double) player->st->duration; +	player->mode = PLAYER_PLAYING; +	return true; +} + +/*	setup filter chain + */ +static bool openFilter (player_t * const player) {  	/* filter setup */  	char strbuf[256]; +	int ret = 0; +	AVCodecContext * const cctx = player->st->codec;  	if ((player->fgraph = avfilter_graph_alloc ()) == NULL) {  		softfail ("graph_alloc");  	}  	/* abuffer */ -	AVFilterContext *fabuf = NULL; -	AVRational time_base = st->time_base; +	AVRational time_base = player->st->time_base;  	snprintf (strbuf, sizeof (strbuf),  			"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64,   			time_base.num, time_base.den, cctx->sample_rate,  			av_get_sample_fmt_name (cctx->sample_fmt),  			cctx->channel_layout); -	if ((ret = avfilter_graph_create_filter (&fabuf, +	if ((ret = avfilter_graph_create_filter (&player->fabuf,  			avfilter_get_by_name ("abuffer"), NULL, strbuf, NULL,  			player->fgraph)) < 0) {  		softfail ("create_filter abuffer"); @@ -230,17 +246,16 @@ void *BarPlayerThread (void *data) {  	}  	/* abuffersink */ -	AVFilterContext *fbufsink = NULL; -	if ((ret = avfilter_graph_create_filter (&fbufsink, +	if ((ret = avfilter_graph_create_filter (&player->fbufsink,  			avfilter_get_by_name ("abuffersink"), NULL, NULL, NULL,  			player->fgraph)) < 0) {  		softfail ("create_filter abuffersink");  	}  	/* connect filter: abuffer -> volume -> aformat -> abuffersink */ -	if (avfilter_link (fabuf, 0, player->fvolume, 0) != 0 || +	if (avfilter_link (player->fabuf, 0, player->fvolume, 0) != 0 ||  			avfilter_link (player->fvolume, 0, fafmt, 0) != 0 || -			avfilter_link (fafmt, 0, fbufsink, 0) != 0) { +			avfilter_link (fafmt, 0, player->fbufsink, 0) != 0) {  		softfail ("filter_link");  	} @@ -248,7 +263,15 @@ void *BarPlayerThread (void *data) {  		softfail ("graph_config");  	} -	/* setup libao */ +	return true; +} + +/*	setup libao + */ +static bool openDevice (player_t * const player) { +	AVCodecContext * const cctx = player->st->codec; + +	ao_sample_format aoFmt;  	memset (&aoFmt, 0, sizeof (aoFmt));  	aoFmt.bits = av_get_bytes_per_sample (avformat) * 8;  	assert (aoFmt.bits > 0); @@ -257,49 +280,64 @@ void *BarPlayerThread (void *data) {  	aoFmt.byte_format = AO_FMT_NATIVE;  	int driver = ao_default_driver_id (); -	if ((aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) { +	if ((player->aoDev = ao_open_live (driver, &aoFmt, NULL)) == NULL) {  		BarUiMsg (player->settings, MSG_ERR, "Cannot open audio device.\n"); -		pret = PLAYER_RET_HARDFAIL; -		goto finish; +		return false;  	} -	player->songPlayed = 0; -	player->songDuration = av_q2d (st->time_base) * (double) st->duration; -	player->mode = PLAYER_PLAYING; +	return true; +} + +/*	decode and play stream. returns 0 or av error code. + */ +static int play (player_t * const player) { +	assert (player != NULL); + +	AVPacket pkt; +	av_init_packet (&pkt); +	pkt.data = NULL; +	pkt.size = 0; + +	AVFrame *frame = NULL, *filteredFrame = NULL; +	frame = avcodec_alloc_frame (); +	assert (frame != NULL); +	filteredFrame = avcodec_alloc_frame (); +	assert (filteredFrame != NULL); + +	while (!player->doQuit) { +		ping (); +		int ret = av_read_frame (player->fctx, &pkt); +		if (ret < 0) { +			av_free_packet (&pkt); +			return ret; +		} else if (pkt.stream_index != player->streamIdx) { +			av_free_packet (&pkt); +			continue; +		} -	while (av_read_frame (fctx, &pkt) >= 0) {  		AVPacket pkt_orig = pkt;  		/* pausing */  		pthread_mutex_lock (&player->pauseMutex);  		while (true) {  			if (!player->doPause) { -				av_read_play (fctx); +				av_read_play (player->fctx);  				break;  			} else { -				av_read_pause (fctx); +				av_read_pause (player->fctx);  			}  			pthread_cond_wait (&player->pauseCond, &player->pauseMutex);  		}  		pthread_mutex_unlock (&player->pauseMutex); -		if (player->doQuit) { -			av_free_packet (&pkt_orig); -			break; -		} - -		if (pkt.stream_index != streamIdx) { -			av_free_packet (&pkt_orig); -			continue; -		} -  		do {  			int got_frame = 0; -			const int decoded = avcodec_decode_audio4 (cctx, frame, &got_frame, -					&pkt); +			const int decoded = avcodec_decode_audio4 (player->st->codec, +					frame, &got_frame, &pkt);  			if (decoded < 0) { -				softfail ("decode_audio4"); +				/* skip this one */ +				break;  			}  			if (got_frame != 0) { @@ -307,16 +345,17 @@ void *BarPlayerThread (void *data) {  				if (frame->pts == (int64_t) AV_NOPTS_VALUE) {  					frame->pts = 0;  				} -				ret = av_buffersrc_write_frame (fabuf, frame); +				ret = av_buffersrc_write_frame (player->fabuf, frame);  				assert (ret >= 0);  				while (true) {  					AVFilterBufferRef *audioref = NULL;  #ifdef HAVE_AV_BUFFERSINK_GET_BUFFER_REF  					/* ffmpeg’s compatibility layer is broken in some releases */ -					if (av_buffersink_get_buffer_ref (fbufsink, &audioref, 0) < 0) { +					if (av_buffersink_get_buffer_ref (player->fbufsink, +							&audioref, 0) < 0) {  #else -					if (av_buffersink_read (fbufsink, &audioref) < 0) { +					if (av_buffersink_read (player->fbufsink, &audioref) < 0) {  #endif  						/* try again next frame */  						break; @@ -328,7 +367,7 @@ void *BarPlayerThread (void *data) {  					const int numChannels = av_get_channel_layout_nb_channels (  							filteredFrame->channel_layout);  					const int bps = av_get_bytes_per_sample(filteredFrame->format); -					ao_play (aoDev, (char *) filteredFrame->data[0], +					ao_play (player->aoDev, (char *) filteredFrame->data[0],  							filteredFrame->nb_samples * numChannels * bps);  					avfilter_unref_bufferp (&audioref); @@ -341,26 +380,57 @@ void *BarPlayerThread (void *data) {  		av_free_packet (&pkt_orig); -		player->songPlayed = av_q2d (st->time_base) * (double) pkt.pts; +		player->songPlayed = av_q2d (player->st->time_base) * (double) pkt.pts; +		player->lastTimestamp = pkt.pts;  	} -finish: -	ao_close (aoDev); +	avcodec_free_frame (&filteredFrame); +	avcodec_free_frame (&frame); + +	return 0; +} + +static void finish (player_t * const player) { +	ao_close (player->aoDev); +	player->aoDev = NULL;  	if (player->fgraph != NULL) {  		avfilter_graph_free (&player->fgraph); +		player->fgraph = NULL;  	} -	if (cctx != NULL) { -		avcodec_close (cctx); +	if (player->st != NULL && player->st->codec != NULL) { +		avcodec_close (player->st->codec); +		player->st = NULL;  	} -	if (fctx != NULL) { -		avformat_close_input (&fctx); -	} -	if (frame != NULL) { -		avcodec_free_frame (&frame); -	} -	if (filteredFrame != NULL) { -		avcodec_free_frame (&filteredFrame); +	if (player->fctx != NULL) { +		avformat_close_input (&player->fctx);  	} +} + +/*	player thread; for every song a new thread is started + *	@param audioPlayer structure + *	@return PLAYER_RET_* + */ +void *BarPlayerThread (void *data) { +	assert (data != NULL); + +	player_t * const player = data; +	intptr_t pret = PLAYER_RET_OK; + +	bool retry = false; +	do { +		if (openStream (player)) { +			if (openFilter (player) && openDevice (player)) { +				retry = play (player) == AVERROR_INVALIDDATA; +			} else { +				/* filter missing or audio device busy */ +				pret = PLAYER_RET_HARDFAIL; +			} +		} else { +			/* stream not found */ +			pret = PLAYER_RET_SOFTFAIL; +		} +		finish (player); +	} while (retry);  	player->mode = PLAYER_FINISHED; diff --git a/src/player.h b/src/player.h index 2c89c93..eb06d64 100644 --- a/src/player.h +++ b/src/player.h @@ -31,14 +31,17 @@ THE SOFTWARE.  #include <pthread.h>  #include <stdint.h> +#include <ao/ao.h> +#include <libavformat/avformat.h>  #include <libavfilter/avfilter.h>  #include <libavfilter/avfiltergraph.h>  #include <piano.h>  #include <waitress.h>  #include "settings.h" +#include "config.h" -struct audioPlayer { +typedef struct {  	/* protected by pauseMutex */  	volatile bool doQuit;  	volatile bool doPause; @@ -52,9 +55,21 @@ struct audioPlayer {  		PLAYER_FINISHED,  	} mode; +	/* libav */  	AVFilterContext *fvolume;  	AVFilterGraph *fgraph; - +	AVFormatContext *fctx; +	AVStream *st; +	AVFilterContext *fbufsink, *fabuf; +	int streamIdx; +	int64_t lastTimestamp; +#ifndef HAVE_AV_TIMEOUT +	int64_t ping; +#endif + +	ao_device *aoDev; + +	/* settings */  	volatile double volume;  	double gain;  	char *url; @@ -63,12 +78,12 @@ struct audioPlayer {  	/* measured in seconds */  	volatile unsigned int songDuration;  	volatile unsigned int songPlayed; -}; +} player_t;  enum {PLAYER_RET_OK = 0, PLAYER_RET_HARDFAIL = 1, PLAYER_RET_SOFTFAIL = 2};  void *BarPlayerThread (void *data); -void BarPlayerSetVolume (struct audioPlayer * const player); +void BarPlayerSetVolume (player_t * const player);  void BarPlayerInit ();  void BarPlayerDestroy (); @@ -684,7 +684,7 @@ size_t BarUiListSongs (const BarSettings_t *settings,   */  void BarUiStartEventCmd (const BarSettings_t *settings, const char *type,  		const PianoStation_t *curStation, const PianoSong_t *curSong, -		const struct audioPlayer *player, PianoStation_t *stations, +		const player_t * const player, PianoStation_t *stations,                  PianoReturn_t pRet, WaitressReturn_t wRet) {  	pid_t chld;  	int pipeFd[2]; @@ -49,7 +49,7 @@ void BarUiPrintSong (const BarSettings_t *, const PianoSong_t *,  		const PianoStation_t *);  size_t BarUiListSongs (const BarSettings_t *, const PianoSong_t *, const char *);  void BarUiStartEventCmd (const BarSettings_t *, const char *, -		const PianoStation_t *, const PianoSong_t *, const struct audioPlayer *, +		const PianoStation_t *, const PianoSong_t *, const player_t *,  		PianoStation_t *, PianoReturn_t, WaitressReturn_t);  int BarUiPianoCall (BarApp_t * const, PianoRequestType_t,  		void *, PianoReturn_t *, WaitressReturn_t *); diff --git a/src/ui_act.c b/src/ui_act.c index 91a4cf7..7515396 100644 --- a/src/ui_act.c +++ b/src/ui_act.c @@ -46,7 +46,7 @@ THE SOFTWARE.  /*	helper to _really_ skip a song (unlock mutex, quit player)   *	@param player handle   */ -static inline void BarUiDoSkipSong (struct audioPlayer *player) { +static inline void BarUiDoSkipSong (player_t * const player) {  	assert (player != NULL);  	pthread_mutex_lock (&player->pauseMutex); | 
