/* * openmpt123_sdl2.hpp * ------------------- * Purpose: libopenmpt command line player * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #ifndef OPENMPT123_SDL2_HPP #define OPENMPT123_SDL2_HPP #include "openmpt123_config.hpp" #include "openmpt123.hpp" #if defined(MPT_WITH_SDL2) #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wimplicit-fallthrough" #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif // __clang__ #include #if defined(__clang__) #pragma clang diagnostic pop #endif // __clang__ #ifdef main #undef main #endif #ifdef SDL_main #undef SDL_main #endif #if (SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 4)) MPT_WARNING("Support for SDL2 < 2.0.4 has been deprecated and will be removed in a future openmpt123 version.") #endif namespace openmpt123 { struct sdl2_exception : public exception { private: static std::string text_from_code( int code ) { std::ostringstream s; s << code; return s.str(); } public: sdl2_exception( int code, const char * error ) : exception( text_from_code( code ) + " (" + ( error ? std::string(error) : std::string("NULL") ) + ")" ) { } }; static void check_sdl2_error( int e ) { if ( e < 0 ) { throw sdl2_exception( e, SDL_GetError() ); } } class sdl2_raii { public: sdl2_raii( Uint32 flags ) { check_sdl2_error( SDL_Init( flags ) ); } ~sdl2_raii() { SDL_Quit(); } }; class sdl2_stream_raii : public write_buffers_interface { private: std::ostream & log; sdl2_raii sdl2; int dev; std::size_t channels; bool use_float; std::size_t sampleQueueMaxFrames; std::vector sampleBufFloat; std::vector sampleBufInt; protected: std::uint32_t round_up_power2(std::uint32_t x) { std::uint32_t result = 1; while ( result < x ) { result *= 2; } return result; } public: sdl2_stream_raii( commandlineflags & flags, std::ostream & log_ ) : log(log_) , sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_TIMER | SDL_INIT_AUDIO ) , dev(-1) , channels(flags.channels) , use_float(flags.use_float) , sampleQueueMaxFrames(0) { if ( flags.buffer == default_high ) { flags.buffer = 160; } else if ( flags.buffer == default_low ) { flags.buffer = 80; } if ( flags.period == default_high ) { flags.period = 20; } else if ( flags.period == default_low ) { flags.period = 10; } flags.apply_default_buffer_sizes(); SDL_AudioSpec audiospec; std::memset( &audiospec, 0, sizeof( SDL_AudioSpec ) ); audiospec.freq = flags.samplerate; audiospec.format = ( flags.use_float ? AUDIO_F32SYS : AUDIO_S16SYS ); audiospec.channels = flags.channels; audiospec.silence = 0; audiospec.samples = round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) ); audiospec.size = audiospec.samples * audiospec.channels * ( flags.use_float ? sizeof( float ) : sizeof( std::int16_t ) ); audiospec.callback = NULL; audiospec.userdata = NULL; if ( flags.verbose ) { log << "SDL2:" << std::endl; log << " latency: " << ( audiospec.samples * 2.0 / flags.samplerate ) << " (2 * " << audiospec.samples << ")" << std::endl; log << std::endl; } sampleQueueMaxFrames = round_up_power2( ( flags.buffer * flags.samplerate ) / ( 1000 * 2 ) ); SDL_AudioSpec audiospec_obtained; std::memset( &audiospec_obtained, 0, sizeof( SDL_AudioSpec ) ); std::memcpy( &audiospec_obtained, &audiospec, sizeof( SDL_AudioSpec ) ); dev = SDL_OpenAudioDevice( NULL, 0, &audiospec, &audiospec_obtained, 0 ); if ( dev < 0 ) { check_sdl2_error( dev ); } else if ( dev == 0 ) { check_sdl2_error( -1 ); } SDL_PauseAudioDevice( dev, 0 ); } ~sdl2_stream_raii() { SDL_PauseAudioDevice( dev, 1 ); SDL_CloseAudioDevice( dev ); } private: std::size_t get_num_writeable_frames() { std::size_t num_queued_frames = SDL_GetQueuedAudioSize( dev ) / ( use_float ? sizeof( float ) : sizeof( std::int16_t ) ) / channels; if ( num_queued_frames > sampleQueueMaxFrames ) { return 0; } return sampleQueueMaxFrames - num_queued_frames; } template void write_frames( const Tsample * buffer, std::size_t frames ) { while ( frames > 0 ) { std::size_t chunk_frames = std::min( frames, get_num_writeable_frames() ); if ( chunk_frames > 0 ) { check_sdl2_error( SDL_QueueAudio( dev, buffer, chunk_frames * channels * ( use_float ? sizeof( float ) : sizeof( std::int16_t ) ) ) ); frames -= chunk_frames; buffer += chunk_frames * channels; } else { SDL_Delay( 1 ); } } } public: void write( const std::vector buffers, std::size_t frames ) override { sampleBufFloat.clear(); for ( std::size_t frame = 0; frame < frames; ++frame ) { for ( std::size_t channel = 0; channel < channels; ++channel ) { sampleBufFloat.push_back( buffers[channel][frame] ); } } write_frames( sampleBufFloat.data(), frames ); } void write( const std::vector buffers, std::size_t frames ) override { sampleBufInt.clear(); for ( std::size_t frame = 0; frame < frames; ++frame ) { for ( std::size_t channel = 0; channel < channels; ++channel ) { sampleBufInt.push_back( buffers[channel][frame] ); } } write_frames( sampleBufInt.data(), frames ); } bool pause() override { SDL_PauseAudioDevice( dev, 1 ); return true; } bool unpause() override { SDL_PauseAudioDevice( dev, 0 ); return true; } bool sleep( int ms ) override { SDL_Delay( ms ); return true; } }; static std::string show_sdl2_devices( std::ostream & /* log */ ) { std::ostringstream devices; std::size_t device_index = 0; devices << " SDL2:" << std::endl; sdl2_raii sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_AUDIO ); for ( int driver = 0; driver < SDL_GetNumAudioDrivers(); ++driver ) { const char * driver_name = SDL_GetAudioDriver( driver ); if ( !driver_name ) { continue; } if ( std::string( driver_name ).empty() ) { continue; } if ( SDL_AudioInit( driver_name ) < 0 ) { continue; } for ( int device = 0; device < SDL_GetNumAudioDevices( 0 ); ++device ) { const char * device_name = SDL_GetAudioDeviceName( device, 0 ); if ( !device_name ) { continue; } if ( std::string( device_name ).empty() ) { continue; } devices << " " << device_index << ": " << driver_name << " - " << device_name << std::endl; device_index++; } SDL_AudioQuit(); } return devices.str(); } } // namespace openmpt123 #endif // MPT_WITH_SDL2 #endif // OPENMPT123_SDL2_HPP