228 lines
6.4 KiB
C++
228 lines
6.4 KiB
C++
|
/*
|
||
|
* 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 <SDL.h>
|
||
|
#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<float> sampleBufFloat;
|
||
|
std::vector<std::int16_t> 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<typename Tsample>
|
||
|
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<float*> 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<std::int16_t*> 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
|
||
|
|