2009-09-09 15:26:33 -06:00
// Copyright (C) 2003 Dolphin Project.
// 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, version 2.0.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
2011-01-31 01:19:27 -07:00
# include <functional>
2009-09-09 15:26:33 -06:00
# include "Common.h"
# include "Thread.h"
# include "AlsaSoundStream.h"
2010-05-21 16:48:57 -06:00
# define FRAME_COUNT_MIN 256
# define BUFFER_SIZE_MAX 8192
# define BUFFER_SIZE_BYTES (BUFFER_SIZE_MAX*2*2)
2009-09-09 15:26:33 -06:00
2010-05-21 16:48:57 -06:00
AlsaSound : : AlsaSound ( CMixer * mixer ) : SoundStream ( mixer ) , thread_data ( 0 ) , handle ( NULL ) , frames_to_deliver ( FRAME_COUNT_MIN )
2009-09-09 15:26:33 -06:00
{
mix_buffer = new u8 [ BUFFER_SIZE_BYTES ] ;
}
AlsaSound : : ~ AlsaSound ( )
{
delete [ ] mix_buffer ;
}
bool AlsaSound : : Start ( )
{
2011-01-31 01:19:27 -07:00
thread = std : : thread ( std : : mem_fun ( & AlsaSound : : SoundLoop ) , this ) ;
2009-09-09 15:26:33 -06:00
thread_data = 0 ;
return true ;
}
void AlsaSound : : Stop ( )
{
thread_data = 1 ;
2011-01-27 14:34:37 -07:00
thread . join ( ) ;
2009-09-09 15:26:33 -06:00
}
void AlsaSound : : Update ( )
{
// don't need to do anything here.
}
// Called on audio thread.
void AlsaSound : : SoundLoop ( )
{
2010-05-21 16:48:57 -06:00
if ( ! AlsaInit ( ) ) {
thread_data = 2 ;
return ;
}
2011-12-30 21:22:48 -07:00
Common : : SetCurrentThreadName ( " Audio thread - alsa " ) ;
2009-09-09 15:26:33 -06:00
while ( ! thread_data )
{
m_mixer - > Mix ( reinterpret_cast < short * > ( mix_buffer ) , frames_to_deliver ) ;
2009-12-13 04:51:29 -07:00
int rc = m_muted ? 1337 : snd_pcm_writei ( handle , mix_buffer , frames_to_deliver ) ;
2009-09-09 15:26:33 -06:00
if ( rc = = - EPIPE )
{
// Underrun
snd_pcm_prepare ( handle ) ;
}
else if ( rc < 0 )
{
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 16:48:57 -06:00
snd_pcm_uframes_t buffer_size , buffer_size_max ;
2010-05-07 17:14:40 -06:00
unsigned int periods ;
2013-03-19 19:51:12 -06:00
2009-09-09 15:26:33 -06:00
err = snd_pcm_open ( & handle , " default " , SND_PCM_STREAM_PLAYBACK , 0 ) ;
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Audio open error: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
snd_pcm_hw_params_alloca ( & hwparams ) ;
2013-03-19 19:51:12 -06:00
2009-09-09 15:26:33 -06:00
err = snd_pcm_hw_params_any ( handle , hwparams ) ;
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Broken configuration for this PCM: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
err = snd_pcm_hw_params_set_access ( handle , hwparams , SND_PCM_ACCESS_RW_INTERLEAVED ) ;
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Access type not available: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2013-03-19 19:51:12 -06:00
err = snd_pcm_hw_params_set_format ( handle , hwparams , SND_PCM_FORMAT_S16_LE ) ;
2009-09-09 15:26:33 -06:00
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Sample format not available: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2010-07-06 10:16:07 -06:00
2010-12-27 08:05:18 -07:00
dir = 0 ;
2009-10-14 20:04:27 -06:00
err = snd_pcm_hw_params_set_rate_near ( handle , hwparams , & sample_rate , & dir ) ;
2009-09-09 15:26:33 -06:00
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Rate not available: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2013-03-19 19:51:12 -06:00
err = snd_pcm_hw_params_set_channels ( handle , hwparams , 2 ) ;
2009-09-09 15:26:33 -06:00
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Channels count not available: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2013-03-19 19:51:12 -06:00
2010-05-21 16:48:57 -06:00
periods = BUFFER_SIZE_MAX / FRAME_COUNT_MIN ;
err = snd_pcm_hw_params_set_periods_max ( handle , hwparams , & periods , & dir ) ;
2010-05-07 17:14:40 -06:00
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Cannot set Minimum periods: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2010-05-21 16:48:57 -06:00
buffer_size_max = BUFFER_SIZE_MAX ;
err = snd_pcm_hw_params_set_buffer_size_max ( handle , hwparams , & buffer_size_max ) ;
2010-05-07 17:14:40 -06:00
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Cannot set minimum buffer size: %s \n " , snd_strerror ( err ) ) ;
return false ;
}
2010-05-21 16:48:57 -06:00
err = snd_pcm_hw_params ( handle , hwparams ) ;
2010-05-07 17:14:40 -06:00
if ( err < 0 )
{
2013-03-19 19:51:12 -06:00
ERROR_LOG ( AUDIO , " Unable to install hw params: %s \n " , snd_strerror ( err ) ) ;
2010-05-21 16:48:57 -06:00
return false ;
}
2013-03-19 19:51:12 -06:00
2010-05-21 16:48:57 -06:00
err = snd_pcm_hw_params_get_buffer_size ( hwparams , & buffer_size ) ;
if ( err < 0 )
{
ERROR_LOG ( AUDIO , " Cannot get buffer size: %s \n " , snd_strerror ( err ) ) ;
2010-05-07 17:14:40 -06:00
return false ;
}
2009-09-09 15:26:33 -06:00
2010-05-21 16:48:57 -06:00
err = snd_pcm_hw_params_get_periods_max ( hwparams , & periods , & dir ) ;
2009-09-09 15:26:33 -06:00
if ( err < 0 )
{
2010-05-21 16:48:57 -06:00
ERROR_LOG ( AUDIO , " Cannot get periods: %s \n " , snd_strerror ( err ) ) ;
2009-09-09 15:26:33 -06:00
return false ;
}
2010-05-21 16:48:57 -06:00
//periods is the number of fragments alsa can wait for during one
//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 19:51:12 -06:00
frames_to_deliver = FRAME_COUNT_MIN ;
2010-05-21 16:48:57 -06: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 19:51:12 -06:00
frames_to_deliver = buffer_size ;
2010-12-05 02:04:34 -07: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 16:48:57 -06:00
2009-09-09 15:26:33 -06:00
snd_pcm_sw_params_alloca ( & swparams ) ;
err = snd_pcm_sw_params_current ( handle , swparams ) ;
if ( err < 0 )
{
2013-03-19 19:51:12 -06:00
ERROR_LOG ( AUDIO , " cannot init sw params: %s \n " , snd_strerror ( err ) ) ;
2009-09-09 15:26:33 -06:00
return false ;
}
2013-03-19 19:51:12 -06:00
2009-09-09 15:26:33 -06:00
err = snd_pcm_sw_params_set_start_threshold ( handle , swparams , 0U ) ;
if ( err < 0 )
{
2013-03-19 19:51:12 -06:00
ERROR_LOG ( AUDIO , " cannot set start thresh: %s \n " , snd_strerror ( err ) ) ;
2009-09-09 15:26:33 -06:00
return false ;
}
2013-03-19 19:51:12 -06:00
2009-09-09 15:26:33 -06:00
err = snd_pcm_sw_params ( handle , swparams ) ;
if ( err < 0 )
{
2013-03-19 19:51:12 -06:00
ERROR_LOG ( AUDIO , " cannot set sw params: %s \n " , snd_strerror ( err ) ) ;
2009-09-09 15:26:33 -06:00
return false ;
}
err = snd_pcm_prepare ( handle ) ;
if ( err < 0 )
{
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 ( )
{
if ( handle ! = NULL )
{
snd_pcm_drop ( handle ) ;
snd_pcm_close ( handle ) ;
handle = NULL ;
}
}