diff --git a/stella/src/ui/sound/SoundALSA.cxx b/stella/src/ui/sound/SoundALSA.cxx new file mode 100644 index 000000000..446dd9cfd --- /dev/null +++ b/stella/src/ui/sound/SoundALSA.cxx @@ -0,0 +1,308 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2002 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SoundALSA.cxx,v 1.1 2002-12-01 02:12:26 stephena Exp $ +//============================================================================ + +#include +#include + +#include "SoundALSA.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundALSA::SoundALSA() + : myIsInitializedFlag(false), + myPcmHandle(0), + myMixerHandle(0), + myMixerElem(0), + myOriginalVolumeLeft(-1), + myOriginalVolumeRight(-1), + myBufferSize(0), + mySampleRate(0) +{ + Int32 err; + char pcmName[] = "plughw:0,0"; + char mixerName[] = "PCM"; + char mixerCard[] = "default"; + + snd_pcm_hw_params_t* hwparams; + + // Allocate the snd_pcm_hw_params_t structure on the stack + snd_pcm_hw_params_alloca(&hwparams); + + // Open the PCM device for writing + if((err = snd_pcm_open(&myPcmHandle, pcmName, SND_PCM_STREAM_PLAYBACK, 0)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + return; + } + + // Init hwparams with full configuration space + if((err = snd_pcm_hw_params_any(myPcmHandle, hwparams)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + return; + } + + // Set interleaved access + if((err = snd_pcm_hw_params_set_access(myPcmHandle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + return; + } + + // Set the audio data format + if((err = snd_pcm_hw_params_set_format(myPcmHandle, hwparams, SND_PCM_FORMAT_U8)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + return; + } + + // Set the number of audio channels to 1 (mono mode) + if((err = snd_pcm_hw_params_set_channels(myPcmHandle, hwparams, 1)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + return; + } + + // Set the audio sample rate. If the exact rate is not supported by + // the hardware, use nearest possible rate + mySampleRate = 31400; + if((err = snd_pcm_hw_params_set_rate_near(myPcmHandle, hwparams, mySampleRate, 0)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + mySampleRate = 0; + return; + } + + // Set number of fragments to 2 + if((err = snd_pcm_hw_params_set_periods(myPcmHandle, hwparams, 2, 0)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + mySampleRate = 0; + return; + } + + // Set size of fragments to 512 bytes + myBufferSize = 512; + if((err = snd_pcm_hw_params_set_period_size(myPcmHandle, hwparams, + myBufferSize, 0)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + mySampleRate = 0; + return; + } + + // Apply HW parameter settings to PCM device + if((err = snd_pcm_hw_params(myPcmHandle, hwparams)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + mySampleRate = 0; + return; + } + + //////////////////////////////////////////////////////////// + // Now, open the mixer so we'll be able to change the volume + //////////////////////////////////////////////////////////// + + snd_mixer_selem_id_t* mixerID; + + // Allocate simple mixer ID + snd_mixer_selem_id_alloca(&mixerID); + + // Sets simple mixer ID and name + snd_mixer_selem_id_set_index(mixerID, 0); + snd_mixer_selem_id_set_name(mixerID, mixerName); + + // Open the mixer device + if((err = snd_mixer_open(&myMixerHandle, 0)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + mySampleRate = 0; + return; + } + + // Attach the mixer to the default sound card + if((err = snd_mixer_attach(myMixerHandle, mixerCard)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + snd_mixer_close(myMixerHandle); + mySampleRate = 0; + return; + } + + // Register the mixer with the sound system + if((err = snd_mixer_selem_register(myMixerHandle, NULL, NULL)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + snd_mixer_close(myMixerHandle); + mySampleRate = 0; + return; + } + + if((err = snd_mixer_load(myMixerHandle)) < 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + snd_mixer_close(myMixerHandle); + mySampleRate = 0; + return; + } + + // Get the mixer element that will be used to control volume + if((myMixerElem = snd_mixer_find_selem(myMixerHandle, mixerID)) == 0) + { + cerr << "SoundALSA: " << snd_strerror(err) << endl; + snd_pcm_close(myPcmHandle); + snd_mixer_close(myMixerHandle); + mySampleRate = 0; + return; + } + + // Save the original volume so we can restore it on exit + snd_mixer_selem_get_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 0, + &myOriginalVolumeLeft); + snd_mixer_selem_get_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 1, + &myOriginalVolumeRight); + + // Prepare the audio device for playback + snd_pcm_prepare(myPcmHandle); + + // Indicate that the sound system is fully initialized + myIsInitializedFlag = true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundALSA::~SoundALSA() +{ + closeDevice(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundALSA::closeDevice() +{ + if(myIsInitializedFlag) + { + // Restore original volume + if(myMixerHandle) + { + if((myOriginalVolumeLeft != -1) && (myOriginalVolumeRight != -1)) + { + snd_mixer_selem_set_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 0, + myOriginalVolumeLeft); + snd_mixer_selem_set_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 1, + myOriginalVolumeRight); + } + + snd_mixer_close(myMixerHandle); + } + + if(myPcmHandle) + snd_pcm_close(myPcmHandle); + } + + myIsInitializedFlag = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundALSA::getSampleRate() const +{ + return myIsInitializedFlag ? mySampleRate : 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundALSA::isSuccessfullyInitialized() const +{ + return myIsInitializedFlag; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundALSA::setSoundVolume(uInt32 volume) +{ + if(myIsInitializedFlag && myMixerElem) + { + if(volume < 0) + { + volume = 0; + } + if(volume > 100) + { + volume = 100; + } + + long int lowerBound, upperBound, newVolume; + snd_mixer_selem_get_playback_volume_range(myMixerElem, &lowerBound, &upperBound); + + newVolume = (long int) (((upperBound - lowerBound) * volume / 100.0) + lowerBound); + snd_mixer_selem_set_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 0, + newVolume); + snd_mixer_selem_set_playback_volume(myMixerElem, (_snd_mixer_selem_channel_id) 1, + newVolume); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundALSA::updateSound(MediaSource& mediaSource) +{ + if(myIsInitializedFlag) + { + snd_pcm_sframes_t frames; + uInt8 periodCount = 0; + + // Dequeue samples as long as full fragments are available + while(mediaSource.numberOfAudioSamples() >= myBufferSize) + { + uInt8 buffer[myBufferSize]; + mediaSource.dequeueAudioSamples(buffer, myBufferSize); + + if((frames = snd_pcm_writei(myPcmHandle, buffer, myBufferSize)) == -EPIPE) + { + snd_pcm_prepare(myPcmHandle); + break; + } + periodCount++; + } + + // Fill any unused fragments with silence so that we have a lower + // risk of having playback underruns + for(int i = 0; i < 1-periodCount; ++i) + { + frames = snd_pcm_avail_update(myPcmHandle); + if (frames > 0) + { + uInt8 buffer[frames]; + memset((void*)buffer, 0, frames); + snd_pcm_writei(myPcmHandle, buffer, frames); + } + else if(frames == -EPIPE) // this should never happen + { + cerr << "EPIPE after write\n"; + break; + } + } + } +} diff --git a/stella/src/ui/sound/SoundALSA.hxx b/stella/src/ui/sound/SoundALSA.hxx new file mode 100644 index 000000000..4fa11a34e --- /dev/null +++ b/stella/src/ui/sound/SoundALSA.hxx @@ -0,0 +1,107 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2002 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SoundALSA.hxx,v 1.1 2002-12-01 02:12:26 stephena Exp $ +//============================================================================ + +#ifndef SOUNDALSA_HXX +#define SOUNDALSA_HXX + +#include + +#include "Sound.hxx" +#include "bspf.hxx" +#include "MediaSrc.hxx" + +/** + This class implements a sound class using the + Advanced Linux Sound Architecture (ALSA) version 0.9.x API. + + @author Stephen Anthony + @version $Id: SoundALSA.hxx,v 1.1 2002-12-01 02:12:26 stephena Exp $ +*/ +class SoundALSA : public Sound +{ + public: + /** + Create a new sound object + */ + SoundALSA(); + + /** + Destructor + */ + virtual ~SoundALSA(); + + public: + /** + Closes the sound device + */ + void closeDevice(); + + /** + Return the playback sample rate for the sound device. + + @return The playback sample rate + */ + uInt32 getSampleRate() const; + + /** + Return true iff the sound device was successfully initlaized. + + @return true iff the sound device was successfully initlaized. + */ + bool isSuccessfullyInitialized() const; + + /** + Sets the volume of the sound device to the specified level. The + volume is given as a precentage from 0 to 100. + + @param volume The new volume for the sound device + */ + void setSoundVolume(uInt32 volume); + + /** + Update the sound device using the audio sample from the specified + media source. + + @param mediaSource The media source to get audio samples from. + */ + void updateSound(MediaSource& mediaSource); + + private: + // Indicates if the sound device was successfully initialized + bool myIsInitializedFlag; + + // Handle for the PCM device + snd_pcm_t* myPcmHandle; + + // Handle for the mixer device + snd_mixer_t* myMixerHandle; + + // Mixer elem, used to set volume + snd_mixer_elem_t* myMixerElem; + + // Original mixer volume when the sound device was opened + long int myOriginalVolumeLeft; + long int myOriginalVolumeRight; + + // PCM buffer size + uInt32 myBufferSize; + + // PCM sample rate + uInt32 mySampleRate; +}; +#endif