implemented alsa audioOutput plugin, now it needs testing
git-svn-id: https://svn.musicpd.org/mpd/trunk@3008 09075e82-0dd4-0310-85a5-a0d7c8717e4f
This commit is contained in:
		
							
								
								
									
										4
									
								
								TODO
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								TODO
									
									
									
									
									
								
							| @@ -22,6 +22,10 @@ | ||||
| *) add a method for clearling the audio device's buffer.  This isn't possible  | ||||
| 	with libao api. | ||||
|  | ||||
| *) add support so that audioOutput plugins can modify the output audio format. | ||||
| 	(This way, alsa's _near functions can be used to adjust for output | ||||
| 	 devices on the fly: for channels, bits, and rate) | ||||
|  | ||||
| *) add support for playing aac streams (gee, thanks icecast) | ||||
|  | ||||
| *) implement apev2 and id3v1 tag reader from xmms-musepack plugin | ||||
|   | ||||
| @@ -2,6 +2,7 @@ bin_PROGRAMS = mpd | ||||
| SUBDIRS = $(ID3_SUBDIR) $(MAD_SUBDIR) $(MP4FF_SUBDIR) | ||||
|  | ||||
| mpd_audioOutputs = \ | ||||
| 	audioOutputs/audioOutput_alsa.c \ | ||||
| 	audioOutputs/audioOutput_ao.c \ | ||||
| 	audioOutputs/audioOutput_oss.c \ | ||||
| 	audioOutputs/audioOutput_shout.c | ||||
|   | ||||
| @@ -56,9 +56,10 @@ int cmpAudioFormat(AudioFormat * f1, AudioFormat * f2) { | ||||
| 	return memcmp(f1, f2, sizeof(AudioFormat)); | ||||
| } | ||||
|  | ||||
| extern AudioOutputPlugin alsaPlugin; | ||||
| extern AudioOutputPlugin aoPlugin; | ||||
| extern AudioOutputPlugin shoutPlugin; | ||||
| extern AudioOutputPlugin ossPlugin; | ||||
| extern AudioOutputPlugin shoutPlugin; | ||||
|  | ||||
| /* make sure initPlayerData is called before this function!! */ | ||||
| void initAudioDriver() { | ||||
| @@ -66,9 +67,10 @@ void initAudioDriver() { | ||||
| 	int i; | ||||
|  | ||||
| 	initAudioOutputPlugins(); | ||||
| 	loadAudioOutputPlugin(&alsaPlugin); | ||||
| 	loadAudioOutputPlugin(&aoPlugin); | ||||
| 	loadAudioOutputPlugin(&shoutPlugin); | ||||
| 	loadAudioOutputPlugin(&ossPlugin); | ||||
| 	loadAudioOutputPlugin(&shoutPlugin); | ||||
|  | ||||
| 	pdAudioDevicesEnabled = (getPlayerData())->audioDeviceEnabled; | ||||
|  | ||||
|   | ||||
							
								
								
									
										293
									
								
								src/audioOutputs/audioOutput_alsa.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								src/audioOutputs/audioOutput_alsa.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| /* the Music Player Daemon (MPD) | ||||
|  * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) | ||||
|  * 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 "../audioOutput.h" | ||||
|  | ||||
| #include <stdlib.h> | ||||
|  | ||||
| #ifdef HAVE_ALSA | ||||
|  | ||||
| #define ALSA_PCM_NEW_HW_PARAMS_API | ||||
| #define ALSA_PCM_NEW_SW_PARAMS_API | ||||
|  | ||||
| #define MPD_ALSA_BUFFER_TIME 500000 | ||||
| #define MPD_ALSA_PERIOD_TIME 50000 | ||||
|  | ||||
| #include "../conf.h" | ||||
| #include "../log.h" | ||||
| #include "../sig_handlers.h" | ||||
|  | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
| #include <signal.h> | ||||
|  | ||||
| #include <alsa/asoundlib.h> | ||||
|  | ||||
| typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t *pcm, const void *buffer, | ||||
|                                                 snd_pcm_uframes_t size); | ||||
|  | ||||
| typedef struct _AlsaData { | ||||
| 	char * device; | ||||
| 	snd_pcm_t * pcm_handle; | ||||
| 	int mmap; | ||||
| 	alsa_writei_t * writei; | ||||
| } AlsaData; | ||||
|  | ||||
| static AlsaData * newAlsaData() { | ||||
| 	AlsaData * ret = malloc(sizeof(AlsaData)); | ||||
|  | ||||
| 	ret->device = NULL; | ||||
| 	ret->pcm_handle = NULL; | ||||
| 	ret->writei = snd_pcm_writei; | ||||
| 	ret->mmap = 0; | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| static void freeAlsaData(AlsaData * ad) { | ||||
| 	if(ad->device) free(ad->device); | ||||
|  | ||||
| 	free(ad); | ||||
| } | ||||
|  | ||||
| static int alsa_initDriver(AudioOutput * audioOutput, ConfigParam * param) { | ||||
| 	BlockParam * bp = getBlockParam(param, "device"); | ||||
| 	AlsaData * ad = newAlsaData(); | ||||
| 	 | ||||
| 	audioOutput->data = ad; | ||||
|  | ||||
| 	ad->device = bp ? strdup(bp->value) : strdup("default"); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void alsa_finishDriver(AudioOutput * audioOutput) { | ||||
| 	AlsaData * ad = audioOutput->data; | ||||
|  | ||||
| 	freeAlsaData(ad); | ||||
| } | ||||
|  | ||||
| static int alsa_openDevice(AudioOutput * audioOutput)  | ||||
| { | ||||
| 	AlsaData * ad = audioOutput->data; | ||||
| 	AudioFormat * audioFormat = &audioOutput->outAudioFormat; | ||||
| 	snd_pcm_format_t bitformat; | ||||
| 	snd_pcm_hw_params_t * hwparams; | ||||
| 	snd_pcm_sw_params_t * swparams; | ||||
| 	unsigned int sampleRate = audioFormat->sampleRate; | ||||
| 	snd_pcm_uframes_t alsa_buffer_size; | ||||
| 	snd_pcm_uframes_t alsa_period_size; | ||||
| 	unsigned int alsa_buffer_time = MPD_ALSA_BUFFER_TIME; | ||||
| 	unsigned int alsa_period_time = MPD_ALSA_PERIOD_TIME; | ||||
| 	int err; | ||||
|  | ||||
| 	switch(audioFormat->bits) { | ||||
| 	case 8: | ||||
| 		bitformat = SND_PCM_FORMAT_S8; | ||||
| 		break; | ||||
| 	case 16: | ||||
| 		bitformat = SND_PCM_FORMAT_S16; | ||||
| 		break; | ||||
| 	case 24: | ||||
| 		bitformat = SND_PCM_FORMAT_S16; | ||||
| 		break; | ||||
| 	case 32: | ||||
| 		bitformat = SND_PCM_FORMAT_S16; | ||||
| 		break; | ||||
| 	default: | ||||
| 		ERROR("Alsa device \"%s\" doesn't support %i bit audio\n", | ||||
| 				ad->device, audioFormat->bits); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_open(&ad->pcm_handle, ad->device,  | ||||
| 			SND_PCM_STREAM_PLAYBACK, 0); | ||||
| 	if(err < 0) { | ||||
| 		ad->pcm_handle = NULL; | ||||
| 		goto error; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_nonblock(ad->pcm_handle, 0); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	// configure HW params | ||||
| 	snd_pcm_hw_params_alloca(&hwparams); | ||||
|  | ||||
| 	err = snd_pcm_hw_params_any(ad->pcm_handle, hwparams); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	if(ad->mmap) { | ||||
| 		err = snd_pcm_hw_params_set_access(ad->pcm_handle, hwparams, | ||||
| 				SND_PCM_ACCESS_MMAP_INTERLEAVED); | ||||
| 		if(err < 0) { | ||||
| 			ERROR("Cannot set mmap'ed mode on alsa device \"%s\": " | ||||
| 					" %s\n", ad->device,  | ||||
| 					snd_strerror(-err)); | ||||
| 			ERROR("Falling back to direct write mode\n"); | ||||
| 			ad->mmap = 0; | ||||
| 		} | ||||
| 		else ad->writei = snd_pcm_mmap_writei; | ||||
| 	} | ||||
|  | ||||
| 	if(!ad->mmap) { | ||||
| 		err = snd_pcm_hw_params_set_access(ad->pcm_handle, hwparams, | ||||
| 				SND_PCM_ACCESS_RW_INTERLEAVED); | ||||
| 		if(err < 0) goto error; | ||||
| 		ad->writei = snd_pcm_mmap_writei; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_hw_params_set_format(ad->pcm_handle, hwparams, bitformat); | ||||
| 	if(err < 0) { | ||||
| 		ERROR("Alsa device \"%s\" does not support %i bit audio: " | ||||
| 				"%s\n", ad->device, (int)bitformat,  | ||||
| 				snd_strerror(-err)); | ||||
| 		goto fail; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_hw_params_set_channels(ad->pcm_handle, hwparams,  | ||||
| 			(unsigned int)audioFormat->channels); | ||||
| 	if(err < 0) { | ||||
| 		ERROR("Alsa device \"%s\" does not support %i channels: " | ||||
| 				"%s\n", ad->device, (int)audioFormat->channels,  | ||||
| 				snd_strerror(-err)); | ||||
| 		goto fail; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_hw_params_set_rate_near(ad->pcm_handle, hwparams,  | ||||
| 			&sampleRate, 0); | ||||
| 	if(err < 0 || sampleRate == 0) { | ||||
| 		ERROR("Alsa device \"%s\" does not support %i Hz audio\n", | ||||
| 				ad->device, (int)audioFormat->sampleRate); | ||||
| 		goto fail; | ||||
| 	} | ||||
|  | ||||
| 	err = snd_pcm_hw_params_set_buffer_time_near(ad->pcm_handle, hwparams, | ||||
| 			&alsa_buffer_time, 0); | ||||
| 	if(err < 0) goto error; | ||||
| 	 | ||||
| 	err = snd_pcm_hw_params_set_period_time_near(ad->pcm_handle, hwparams, | ||||
| 			&alsa_period_time, 0); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	err = snd_pcm_hw_params(ad->pcm_handle, hwparams); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, 0); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	// configure SW params | ||||
| 	snd_pcm_sw_params_alloca(&swparams); | ||||
| 	snd_pcm_sw_params_current(ad->pcm_handle, swparams); | ||||
|  | ||||
| 	err = snd_pcm_sw_params_set_start_threshold(ad->pcm_handle, swparams, | ||||
| 			alsa_buffer_size - alsa_period_size); | ||||
| 	if(err < 0) goto error; | ||||
|  | ||||
| 	err = snd_pcm_sw_params(ad->pcm_handle, swparams); | ||||
| 	if(err < 0) goto error; | ||||
| 	 | ||||
| 	audioOutput->open = 1; | ||||
|  | ||||
| 	return 0; | ||||
|  | ||||
| error: | ||||
| 	ERROR("Error opening alsa device \"%s\": %s\n", ad->device,  | ||||
| 			snd_strerror(-err)); | ||||
| fail: | ||||
| 	if(ad->pcm_handle) snd_pcm_close(ad->pcm_handle); | ||||
| 	audioOutput->open = 0; | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| static void alsa_closeDevice(AudioOutput * audioOutput) { | ||||
| 	AlsaData * ad = audioOutput->data; | ||||
|  | ||||
| 	if(ad->pcm_handle) { | ||||
| 		snd_pcm_drain(ad->pcm_handle); | ||||
| 		ad->pcm_handle = NULL; | ||||
| 	} | ||||
|  | ||||
| 	audioOutput->open = 0; | ||||
| } | ||||
|  | ||||
| inline static int alsa_errorRecovery(AlsaData * ad, int err) { | ||||
| 	if(err == -EPIPE) { | ||||
| 		DEBUG("Underrun on alsa device \"%s\"\n", ad->device); | ||||
| 		err = snd_pcm_prepare(ad->pcm_handle); | ||||
| 		if(err < 0) return -1; | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| static int alsa_playAudio(AudioOutput * audioOutput, char * playChunk,  | ||||
| 		int size)  | ||||
| { | ||||
| 	AlsaData * ad = audioOutput->data; | ||||
| 	int ret; | ||||
|  | ||||
| 	while (size > 0) { | ||||
| 		ret = ad->writei(ad->pcm_handle, playChunk, size); | ||||
|  | ||||
| 		if(ret == -EAGAIN) continue; | ||||
| 		 | ||||
| 		if(ret < 0 && alsa_errorRecovery(ad, ret) < 0) { | ||||
| 			ERROR("closing alsa device \"%s\" due to write error:" | ||||
| 					" %s\n", ad->device,  | ||||
| 					snd_strerror(-errno)); | ||||
| 			alsa_closeDevice(audioOutput); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		playChunk += ret; | ||||
| 		size -= ret; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| AudioOutputPlugin alsaPlugin = | ||||
| { | ||||
| 	"alsa", | ||||
| 	alsa_initDriver, | ||||
| 	alsa_finishDriver, | ||||
| 	alsa_openDevice, | ||||
| 	alsa_playAudio, | ||||
| 	alsa_closeDevice, | ||||
| 	NULL /* sendMetadataFunc */ | ||||
| }; | ||||
|  | ||||
| #else /* HAVE ALSA */ | ||||
|  | ||||
| AudioOutputPlugin alsaPlugin = | ||||
| { | ||||
| 	NULL, | ||||
| 	NULL, | ||||
| 	NULL, | ||||
| 	NULL, | ||||
| 	NULL, | ||||
| 	NULL, | ||||
| 	NULL /* sendMetadataFunc */ | ||||
| }; | ||||
|  | ||||
| #endif /* HAVE_ALSA */ | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Warren Dukes
					Warren Dukes