2015-05-24 04:55:12 +00:00
// Copyright 2009 Dolphin Emulator Project
2015-05-17 23:08:10 +00:00
// Licensed under GPLv2+
2013-04-21 06:17:03 +00:00
// Refer to the license.txt file included.
2009-09-09 21:26:33 +00:00
2015-07-07 13:30:27 +00:00
# include <mutex>
2014-02-17 10:18:15 +00:00
# include "AudioCommon/AlsaSoundStream.h"
2014-09-08 01:06:58 +00:00
# include "Common/CommonTypes.h"
2014-02-17 10:18:15 +00:00
# include "Common/Thread.h"
2015-09-26 21:13:07 +00:00
# include "Common/Logging/Log.h"
2009-09-09 21:26:33 +00:00
2015-05-24 08:13:02 +00:00
AlsaSound : : AlsaSound ( )
: m_thread_status ( ALSAThreadStatus : : STOPPED )
2015-05-10 06:30:24 +00:00
, handle ( nullptr )
, frames_to_deliver ( FRAME_COUNT_MIN )
2009-09-09 21:26:33 +00:00
{
}
bool AlsaSound : : Start ( )
{
2015-05-10 06:30:24 +00:00
m_thread_status . store ( ALSAThreadStatus : : RUNNING ) ;
2015-08-08 22:37:48 +00:00
if ( ! AlsaInit ( ) )
{
m_thread_status . store ( ALSAThreadStatus : : STOPPED ) ;
return false ;
}
2014-09-08 17:39:51 +00:00
thread = std : : thread ( & AlsaSound : : SoundLoop , this ) ;
2009-09-09 21:26:33 +00:00
return true ;
}
void AlsaSound : : Stop ( )
{
2015-05-10 06:30:24 +00:00
m_thread_status . store ( ALSAThreadStatus : : STOPPING ) ;
2015-07-07 13:30:27 +00: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-31 04:22:48 +00:00
Common : : SetCurrentThreadName ( " Audio thread - alsa " ) ;
2015-09-29 11:51:34 +00:00
while ( m_thread_status . load ( ) ! = ALSAThreadStatus : : STOPPING )
2009-09-09 21:26:33 +00:00
{
2015-09-29 11:51:34 +00:00
while ( m_thread_status . load ( ) = = ALSAThreadStatus : : RUNNING )
2009-09-09 21:26:33 +00:00
{
2015-09-29 11:51:34 +00: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-29 11:51:34 +00:00
if ( m_thread_status . load ( ) = = ALSAThreadStatus : : PAUSED )
2009-09-09 21:26:33 +00:00
{
2015-09-29 11:51:34 +00: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 06:30:24 +00:00
m_thread_status . store ( ALSAThreadStatus : : STOPPED ) ;
2009-09-09 21:26:33 +00:00
}
2015-07-07 13:30:27 +00:00
void AlsaSound : : Clear ( bool muted )
{
m_muted = muted ;
2015-09-29 11:51:34 +00:00
m_thread_status . store ( muted ? ALSAThreadStatus : : PAUSED : ALSAThreadStatus : : RUNNING ) ;
cv . notify_one ( ) ; // Notify thread that status has changed
2015-07-07 13:30:27 +00: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-20 01:51:12 +00:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_open ( & handle , " default " , SND_PCM_STREAM_PLAYBACK , 0 ) ;
2013-10-29 05:23:17 +00: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-20 01:51:12 +00:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_hw_params_any ( handle , hwparams ) ;
2013-10-29 05:23:17 +00: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 05:23:17 +00: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 05:23:17 +00: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-20 01:51:12 +00:00
err = snd_pcm_hw_params_set_format ( handle , hwparams , SND_PCM_FORMAT_S16_LE ) ;
2013-10-29 05:23:17 +00: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 05:23:17 +00: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-20 01:51:12 +00:00
2015-09-30 08:41:33 +00:00
err = snd_pcm_hw_params_set_channels ( handle , hwparams , CHANNEL_COUNT ) ;
2013-10-29 05:23:17 +00: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-20 01:51:12 +00: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 05:23:17 +00:00
if ( err < 0 )
2010-05-07 23:14:40 +00:00
{
2015-09-30 08:41:33 +00: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 05:23:17 +00:00
if ( err < 0 )
2010-05-07 23:14:40 +00:00
{
2015-09-30 08:41:33 +00:00
ERROR_LOG ( AUDIO , " Cannot set maximum buffer size: %s \n " , snd_strerror ( err ) ) ;
2010-05-07 23:14:40 +00:00
return false ;
}
2010-05-21 22:48:57 +00:00
err = snd_pcm_hw_params ( handle , hwparams ) ;
2013-10-29 05:23:17 +00:00
if ( err < 0 )
2010-05-07 23:14:40 +00:00
{
2013-03-20 01:51:12 +00:00
ERROR_LOG ( AUDIO , " Unable to install hw params: %s \n " , snd_strerror ( err ) ) ;
2010-05-21 22:48:57 +00:00
return false ;
}
2013-03-20 01:51:12 +00:00
2010-05-21 22:48:57 +00:00
err = snd_pcm_hw_params_get_buffer_size ( hwparams , & buffer_size ) ;
2013-10-29 05:23:17 +00:00
if ( err < 0 )
2010-05-21 22:48:57 +00:00
{
ERROR_LOG ( AUDIO , " Cannot get 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
2010-05-21 22:48:57 +00:00
err = snd_pcm_hw_params_get_periods_max ( hwparams , & periods , & dir ) ;
2013-10-29 05:23:17 +00:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2010-05-21 22:48:57 +00:00
ERROR_LOG ( AUDIO , " Cannot get periods: %s \n " , snd_strerror ( err ) ) ;
2009-09-09 21:26:33 +00:00
return false ;
}
2010-05-21 22:48:57 +00:00
2013-10-29 05:23:17 +00: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-20 01:51:12 +00: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-20 01:51:12 +00: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 05:23:17 +00:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-20 01:51:12 +00: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-20 01:51:12 +00:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_sw_params_set_start_threshold ( handle , swparams , 0U ) ;
2013-10-29 05:23:17 +00:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-20 01:51:12 +00: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-20 01:51:12 +00:00
2009-09-09 21:26:33 +00:00
err = snd_pcm_sw_params ( handle , swparams ) ;
2013-10-29 05:23:17 +00:00
if ( err < 0 )
2009-09-09 21:26:33 +00:00
{
2013-03-20 01:51:12 +00: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 05:23:17 +00: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 20:14:26 +00:00
if ( handle ! = nullptr )
2009-09-09 21:26:33 +00:00
{
snd_pcm_drop ( handle ) ;
snd_pcm_close ( handle ) ;
2014-03-09 20:14:26 +00:00
handle = nullptr ;
2009-09-09 21:26:33 +00:00
}
}