2015-05-24 06:55:12 +02:00
// Copyright 2009 Dolphin Emulator Project
2015-05-18 01:08:10 +02:00
// Licensed under GPLv2+
2013-04-21 02:17:03 -04:00
// Refer to the license.txt file included.
2009-09-09 21:26:33 +00:00
2015-07-07 15:30:27 +02:00
#include <mutex>
2014-02-17 05:18:15 -05:00
#include "AudioCommon/AlsaSoundStream.h"
2014-09-07 20:06:58 -05:00
#include "Common/CommonTypes.h"
2014-02-17 05:18:15 -05:00
#include "Common/Thread.h"
2015-09-26 17:13:07 -04:00
#include "Common/Logging/Log.h"
2009-09-09 21:26:33 +00:00
2015-05-24 04:13:02 -04:00
AlsaSound :: AlsaSound ()
: m_thread_status ( ALSAThreadStatus :: STOPPED )
2015-05-10 02:30:24 -04:00
, handle ( nullptr )
, frames_to_deliver ( FRAME_COUNT_MIN )
2009-09-09 21:26:33 +00:00
{
}
bool AlsaSound :: Start ()
{
2015-05-10 02:30:24 -04:00
m_thread_status . store ( ALSAThreadStatus :: RUNNING );
2015-08-09 00:37:48 +02:00
if ( ! AlsaInit ())
{
m_thread_status . store ( ALSAThreadStatus :: STOPPED );
return false ;
}
2014-09-08 13:39:51 -04:00
thread = std :: thread ( & AlsaSound :: SoundLoop , this );
2009-09-09 21:26:33 +00:00
return true ;
}
void AlsaSound :: Stop ()
{
2015-05-10 02:30:24 -04:00
m_thread_status . store ( ALSAThreadStatus :: STOPPING );
2015-07-07 15:30:27 +02:00
//Give the opportunity to the audio thread
//to realize we are stopping the emulation
cv . notify_one ();
2011-01-27 21:34:37 +00:00
thread . join ();
2009-09-09 21:26:33 +00:00
}
void AlsaSound :: Update ()
{
// don't need to do anything here.
}
// Called on audio thread.
void AlsaSound :: SoundLoop ()
{
2011-12-30 20:22:48 -08:00
Common :: SetCurrentThreadName ( "Audio thread - alsa" );
2015-09-30 00:51:34 +13:00
while ( m_thread_status . load () != ALSAThreadStatus :: STOPPING )
2009-09-09 21:26:33 +00:00
{
2015-09-30 00:51:34 +13:00
while ( m_thread_status . load () == ALSAThreadStatus :: RUNNING )
2009-09-09 21:26:33 +00:00
{
2015-09-30 00:51:34 +13:00
m_mixer -> Mix ( mix_buffer , frames_to_deliver );
int rc = snd_pcm_writei ( handle , mix_buffer , frames_to_deliver );
if ( rc == - EPIPE )
{
// Underrun
snd_pcm_prepare ( handle );
}
else if ( rc < 0 )
{
ERROR_LOG ( AUDIO , "writei fail: %s" , snd_strerror ( rc ));
}
2009-09-09 21:26:33 +00:00
}
2015-09-30 00:51:34 +13:00
if ( m_thread_status . load () == ALSAThreadStatus :: PAUSED )
2009-09-09 21:26:33 +00:00
{
2015-09-30 00:51:34 +13:00
snd_pcm_drop ( handle ); // Stop sound output
// Block until thread status changes.
std :: unique_lock < std :: mutex > lock ( cv_m );
cv . wait ( lock , [ this ]{ return m_thread_status . load () != ALSAThreadStatus :: PAUSED ; });
snd_pcm_prepare ( handle ); // resume sound output
2009-09-09 21:26:33 +00:00
}
}
AlsaShutdown ();
2015-05-10 02:30:24 -04:00
m_thread_status . store ( ALSAThreadStatus :: STOPPED );
2009-09-09 21:26:33 +00:00
}
2015-07-07 15:30:27 +02:00
void AlsaSound :: Clear ( bool muted )
{
m_muted = muted ;
2015-09-30 00:51:34 +13:00
m_thread_status . store ( muted ? ALSAThreadStatus :: PAUSED : ALSAThreadStatus :: RUNNING );
cv . notify_one (); // Notify thread that status has changed
2015-07-07 15:30:27 +02:00
}
2009-09-09 21:26:33 +00:00
bool AlsaSound :: AlsaInit ()
{
unsigned int sample_rate = m_mixer -> GetSampleRate ();
int err ;
int dir ;
snd_pcm_sw_params_t * swparams ;
snd_pcm_hw_params_t * hwparams ;
2010-05-21 22:48:57 +00:00
snd_pcm_uframes_t buffer_size , buffer_size_max ;
2010-05-07 23:14:40 +00:00
unsigned int periods ;
2013-03-19 21:51:12 -04:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_open ( & handle , "default" , SND_PCM_STREAM_PLAYBACK , 0 );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Audio open error: %s \n " , snd_strerror ( err ));
return false ;
}
snd_pcm_hw_params_alloca ( & hwparams );
2013-03-19 21:51:12 -04:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_hw_params_any ( handle , hwparams );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Broken configuration for this PCM: %s \n " , snd_strerror ( err ));
return false ;
}
2013-10-29 01:23:17 -04:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_hw_params_set_access ( handle , hwparams , SND_PCM_ACCESS_RW_INTERLEAVED );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Access type not available: %s \n " , snd_strerror ( err ));
return false ;
}
2013-03-19 21:51:12 -04:00
err = snd_pcm_hw_params_set_format ( handle , hwparams , SND_PCM_FORMAT_S16_LE );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Sample format not available: %s \n " , snd_strerror ( err ));
return false ;
}
2010-07-06 16:16:07 +00:00
2010-12-27 15:05:18 +00:00
dir = 0 ;
2009-10-15 02:04:27 +00:00
err = snd_pcm_hw_params_set_rate_near ( handle , hwparams , & sample_rate , & dir );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Rate not available: %s \n " , snd_strerror ( err ));
return false ;
}
2013-03-19 21:51:12 -04:00
2015-09-30 16:41:33 +08:00
err = snd_pcm_hw_params_set_channels ( handle , hwparams , CHANNEL_COUNT );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Channels count not available: %s \n " , snd_strerror ( err ));
return false ;
}
2013-03-19 21:51:12 -04:00
2010-05-21 22:48:57 +00:00
periods = BUFFER_SIZE_MAX / FRAME_COUNT_MIN ;
err = snd_pcm_hw_params_set_periods_max ( handle , hwparams , & periods , & dir );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2010-05-07 23:14:40 +00:00
{
2015-09-30 16:41:33 +08:00
ERROR_LOG ( AUDIO , "Cannot set maximum periods per buffer: %s \n " , snd_strerror ( err ));
2010-05-07 23:14:40 +00:00
return false ;
}
2010-05-21 22:48:57 +00:00
buffer_size_max = BUFFER_SIZE_MAX ;
err = snd_pcm_hw_params_set_buffer_size_max ( handle , hwparams , & buffer_size_max );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2010-05-07 23:14:40 +00:00
{
2015-09-30 16:41:33 +08:00
ERROR_LOG ( AUDIO , "Cannot set maximum buffer size: %s \n " , snd_strerror ( err ));
2010-05-07 23:14:40 +00:00
return false ;
}
2009-09-09 21:26:33 +00:00
err = snd_pcm_hw_params ( handle , hwparams );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-19 21:51:12 -04:00
ERROR_LOG ( AUDIO , "Unable to install hw params: %s \n " , snd_strerror ( err ));
2009-09-09 21:26:33 +00:00
return false ;
}
2013-03-19 21:51:12 -04:00
2010-05-21 22:48:57 +00:00
err = snd_pcm_hw_params_get_buffer_size ( hwparams , & buffer_size );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2010-05-21 22:48:57 +00:00
{
ERROR_LOG ( AUDIO , "Cannot get buffer size: %s \n " , snd_strerror ( err ));
return false ;
}
err = snd_pcm_hw_params_get_periods_max ( hwparams , & periods , & dir );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2010-05-21 22:48:57 +00:00
{
ERROR_LOG ( AUDIO , "Cannot get periods: %s \n " , snd_strerror ( err ));
return false ;
}
2013-10-29 01:23:17 -04:00
//periods is the number of fragments alsa can wait for during one
2010-05-21 22:48:57 +00:00
//buffer_size
frames_to_deliver = buffer_size / periods ;
//limit the minimum size. pulseaudio advertises a minimum of 32 samples.
if ( frames_to_deliver < FRAME_COUNT_MIN )
2013-03-19 21:51:12 -04:00
frames_to_deliver = FRAME_COUNT_MIN ;
2010-05-21 22:48:57 +00:00
//it is probably a bad idea to try to send more than one buffer of data
if (( unsigned int ) frames_to_deliver > buffer_size )
2013-03-19 21:51:12 -04:00
frames_to_deliver = buffer_size ;
2010-12-05 09:04:34 +00:00
NOTICE_LOG ( AUDIO , "ALSA gave us a %ld sample \" hardware \" buffer with %d periods. Will send %d samples per fragments. \n " , buffer_size , periods , frames_to_deliver );
2010-05-21 22:48:57 +00:00
2009-09-09 21:26:33 +00:00
snd_pcm_sw_params_alloca ( & swparams );
err = snd_pcm_sw_params_current ( handle , swparams );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-19 21:51:12 -04:00
ERROR_LOG ( AUDIO , "cannot init sw params: %s \n " , snd_strerror ( err ));
2009-09-09 21:26:33 +00:00
return false ;
}
2013-03-19 21:51:12 -04:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_sw_params_set_start_threshold ( handle , swparams , 0U );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-19 21:51:12 -04:00
ERROR_LOG ( AUDIO , "cannot set start thresh: %s \n " , snd_strerror ( err ));
2009-09-09 21:26:33 +00:00
return false ;
}
2013-03-19 21:51:12 -04:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_sw_params ( handle , swparams );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-19 21:51:12 -04:00
ERROR_LOG ( AUDIO , "cannot set sw params: %s \n " , snd_strerror ( err ));
2009-09-09 21:26:33 +00:00
return false ;
}
err = snd_pcm_prepare ( handle );
2013-10-29 01:23:17 -04:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "Unable to prepare: %s \n " , snd_strerror ( err ));
return false ;
}
NOTICE_LOG ( AUDIO , "ALSA successfully initialized. \n " );
return true ;
}
void AlsaSound :: AlsaShutdown ()
{
2014-03-09 21:14:26 +01:00
if ( handle != nullptr )
2009-09-09 21:26:33 +00:00
{
snd_pcm_drop ( handle );
snd_pcm_close ( handle );
2014-03-09 21:14:26 +01:00
handle = nullptr ;
2009-09-09 21:26:33 +00:00
}
}