2013-04-21 02:17:03 -04:00
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
2009-09-09 21:26:33 +00:00
2011-01-31 08:19:27 +00:00
#include <functional>
2014-02-17 05:18:15 -05:00
#include "AudioCommon/AlsaSoundStream.h"
#include "Common/Common.h"
#include "Common/Thread.h"
2009-09-09 21:26:33 +00:00
2010-05-21 22:48:57 +00:00
#define FRAME_COUNT_MIN 256
#define BUFFER_SIZE_MAX 8192
#define BUFFER_SIZE_BYTES (BUFFER_SIZE_MAX*2*2)
2009-09-09 21:26:33 +00:00
2014-03-09 21:14:26 +01:00
AlsaSound :: AlsaSound ( CMixer * mixer ) : SoundStream ( mixer ), thread_data ( 0 ), handle ( nullptr ), frames_to_deliver ( FRAME_COUNT_MIN )
2009-09-09 21:26:33 +00:00
{
mix_buffer = new u8 [ BUFFER_SIZE_BYTES ];
}
AlsaSound ::~ AlsaSound ()
{
delete [] mix_buffer ;
}
bool AlsaSound :: Start ()
{
2014-04-01 12:09:22 -04:00
thread = std :: thread ( std :: mem_fn ( & AlsaSound :: SoundLoop ), this );
2009-09-09 21:26:33 +00:00
thread_data = 0 ;
return true ;
}
void AlsaSound :: Stop ()
{
thread_data = 1 ;
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 ()
{
2010-05-21 22:48:57 +00:00
if ( ! AlsaInit ()) {
thread_data = 2 ;
return ;
}
2011-12-30 20:22:48 -08:00
Common :: SetCurrentThreadName ( "Audio thread - alsa" );
2009-09-09 21:26:33 +00:00
while ( ! thread_data )
{
m_mixer -> Mix ( reinterpret_cast < short *> ( mix_buffer ), frames_to_deliver );
2009-12-13 11:51:29 +00:00
int rc = m_muted ? 1337 : snd_pcm_writei ( handle , mix_buffer , frames_to_deliver );
2009-09-09 21:26:33 +00:00
if ( rc == - EPIPE )
{
// Underrun
snd_pcm_prepare ( handle );
}
2013-10-29 01:23:17 -04:00
else if ( rc < 0 )
2009-09-09 21:26:33 +00:00
{
ERROR_LOG ( AUDIO , "writei fail: %s" , snd_strerror ( rc ));
}
}
AlsaShutdown ();
thread_data = 2 ;
}
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
err = snd_pcm_hw_params_set_channels ( handle , hwparams , 2 );
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
{
ERROR_LOG ( AUDIO , "Cannot set Minimum periods: %s \n " , snd_strerror ( err ));
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
{
ERROR_LOG ( AUDIO , "Cannot set minimum buffer size: %s \n " , snd_strerror ( err ));
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
}
}