Cog/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_portaudio.hpp
Christopher Snowhill 731e52c440 Build libOpenMPT from source once again
Bundle libOpenMPT as a dynamic framework, which should be safe once
again, now that there is only one version to bundle. Also, now it is
using the versions of libvorbisfile and libmpg123 that are bundled with
the player, instead of compiling minimp3 and stbvorbis.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-30 22:57:30 -07:00

288 lines
9.8 KiB
C++

/*
* openmpt123_portaudio.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_PORTAUDIO_HPP
#define OPENMPT123_PORTAUDIO_HPP
#include "openmpt123_config.hpp"
#include "openmpt123.hpp"
#if defined(MPT_WITH_PORTAUDIO)
#include <portaudio.h>
namespace openmpt123 {
struct portaudio_exception : public exception {
portaudio_exception( PaError code ) : exception( Pa_GetErrorText( code ) ) { }
};
typedef void (*PaUtilLogCallback ) (const char *log);
#ifdef _MSC_VER
extern "C" void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb);
#endif
class portaudio_raii {
private:
std::ostream & log;
bool log_set;
bool portaudio_initialized;
static std::ostream * portaudio_log_stream;
private:
static void portaudio_log_function( const char * log ) {
if ( portaudio_log_stream ) {
*portaudio_log_stream << "PortAudio: " << log;
}
}
protected:
void check_portaudio_error( PaError e ) {
if ( e > 0 ) {
return;
}
if ( e == paNoError ) {
return;
}
if ( e == paOutputUnderflowed ) {
log << "PortAudio warning: " << Pa_GetErrorText( e ) << std::endl;
return;
}
throw portaudio_exception( e );
}
public:
portaudio_raii( bool verbose, std::ostream & log ) : log(log), log_set(false), portaudio_initialized(false) {
if ( verbose ) {
portaudio_log_stream = &log;
} else {
portaudio_log_stream = 0;
}
#ifdef _MSC_VER
PaUtil_SetDebugPrintFunction( portaudio_log_function );
log_set = true;
#endif
check_portaudio_error( Pa_Initialize() );
portaudio_initialized = true;
if ( verbose ) {
*portaudio_log_stream << std::endl;
}
}
~portaudio_raii() {
if ( portaudio_initialized ) {
check_portaudio_error( Pa_Terminate() );
portaudio_initialized = false;
}
if ( log_set ) {
#ifdef _MSC_VER
PaUtil_SetDebugPrintFunction( NULL );
log_set = false;
#endif
}
portaudio_log_stream = 0;
}
};
std::ostream * portaudio_raii::portaudio_log_stream = 0;
class portaudio_stream_blocking_raii : public portaudio_raii, public write_buffers_interface {
private:
PaStream * stream;
bool interleaved;
std::size_t channels;
std::vector<float> sampleBufFloat;
std::vector<std::int16_t> sampleBufInt;
public:
portaudio_stream_blocking_raii( commandlineflags & flags, std::ostream & log )
: portaudio_raii(flags.verbose, log)
, stream(NULL)
, interleaved(false)
, channels(flags.channels)
{
PaStreamParameters streamparameters;
std::memset( &streamparameters, 0, sizeof(PaStreamParameters) );
std::istringstream device_string( flags.device );
int device = -1;
device_string >> device;
streamparameters.device = ( device == -1 ) ? Pa_GetDefaultOutputDevice() : device;
streamparameters.channelCount = flags.channels;
streamparameters.sampleFormat = ( flags.use_float ? paFloat32 : paInt16 ) | paNonInterleaved;
if ( flags.buffer == default_high ) {
streamparameters.suggestedLatency = Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency;
flags.buffer = static_cast<std::int32_t>( Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency * 1000.0 );
} else if ( flags.buffer == default_low ) {
streamparameters.suggestedLatency = Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency;
flags.buffer = static_cast<std::int32_t>( Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency * 1000.0 );
} else {
streamparameters.suggestedLatency = flags.buffer * 0.001;
}
unsigned long framesperbuffer = 0;
if ( flags.mode != Mode::UI ) {
framesperbuffer = paFramesPerBufferUnspecified;
flags.period = 50;
flags.period = std::min( flags.period, flags.buffer / 3 );
} else if ( flags.period == default_high ) {
framesperbuffer = paFramesPerBufferUnspecified;
flags.period = 50;
flags.period = std::min( flags.period, flags.buffer / 3 );
} else if ( flags.period == default_low ) {
framesperbuffer = paFramesPerBufferUnspecified;
flags.period = 10;
flags.period = std::min( flags.period, flags.buffer / 3 );
} else {
framesperbuffer = flags.period * flags.samplerate / 1000;
}
if ( flags.period <= 0 ) {
flags.period = 1;
}
flags.apply_default_buffer_sizes();
if ( flags.verbose ) {
log << "PortAudio:" << std::endl;
log << " device: "
<< streamparameters.device
<< " [ " << Pa_GetHostApiInfo( Pa_GetDeviceInfo( streamparameters.device )->hostApi )->name << " / " << Pa_GetDeviceInfo( streamparameters.device )->name << " ] "
<< std::endl;
log << " low latency: " << Pa_GetDeviceInfo( streamparameters.device )->defaultLowOutputLatency << std::endl;
log << " high latency: " << Pa_GetDeviceInfo( streamparameters.device )->defaultHighOutputLatency << std::endl;
log << " suggested latency: " << streamparameters.suggestedLatency << std::endl;
log << " frames per buffer: " << framesperbuffer << std::endl;
log << " ui redraw: " << flags.period << std::endl;
}
PaError e = PaError();
e = Pa_OpenStream( &stream, NULL, &streamparameters, flags.samplerate, framesperbuffer, ( flags.dither > 0 ) ? paNoFlag : paDitherOff, NULL, NULL );
if ( e != paNoError ) {
// Non-interleaved failed, try interleaved next.
// This might help broken portaudio on MacOS X.
streamparameters.sampleFormat &= ~paNonInterleaved;
e = Pa_OpenStream( &stream, NULL, &streamparameters, flags.samplerate, framesperbuffer, ( flags.dither > 0 ) ? paNoFlag : paDitherOff, NULL, NULL );
if ( e == paNoError ) {
interleaved = true;
}
check_portaudio_error( e );
}
check_portaudio_error( Pa_StartStream( stream ) );
if ( flags.verbose ) {
log << " channels: " << streamparameters.channelCount << std::endl;
log << " sampleformat: " << ( ( ( streamparameters.sampleFormat & ~paNonInterleaved ) == paFloat32 ) ? "paFloat32" : "paInt16" ) << std::endl;
log << " latency: " << Pa_GetStreamInfo( stream )->outputLatency << std::endl;
log << " samplerate: " << Pa_GetStreamInfo( stream )->sampleRate << std::endl;
log << std::endl;
}
}
~portaudio_stream_blocking_raii() {
if ( stream ) {
PaError stopped = Pa_IsStreamStopped( stream );
check_portaudio_error( stopped );
if ( !stopped ) {
check_portaudio_error( Pa_StopStream( stream ) );
}
check_portaudio_error( Pa_CloseStream( stream ) );
stream = NULL;
}
}
private:
template<typename Tsample>
void write_frames( const Tsample * buffer, std::size_t frames ) {
while ( frames > 0 ) {
unsigned long chunk_frames = static_cast<unsigned long>( std::min( static_cast<std::uint64_t>( frames ), static_cast<std::uint64_t>( std::numeric_limits<unsigned long>::max() ) ) );
check_portaudio_error( Pa_WriteStream( stream, buffer, chunk_frames ) );
buffer += chunk_frames * channels;
frames -= chunk_frames;
}
}
template<typename Tsample>
void write_frames( std::vector<Tsample*> buffers, std::size_t frames ) {
while ( frames > 0 ) {
unsigned long chunk_frames = static_cast<unsigned long>( std::min( static_cast<std::uint64_t>( frames ), static_cast<std::uint64_t>( std::numeric_limits<unsigned long>::max() ) ) );
check_portaudio_error( Pa_WriteStream( stream, buffers.data(), chunk_frames ) );
for ( std::size_t channel = 0; channel < channels; ++channel ) {
buffers[channel] += chunk_frames;
}
frames -= chunk_frames;
}
}
public:
void write( const std::vector<float*> buffers, std::size_t frames ) override {
if ( interleaved ) {
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 );
} else {
write_frames( buffers, frames );
}
}
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
if ( interleaved ) {
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 );
} else {
write_frames( buffers, frames );
}
}
bool unpause() override {
check_portaudio_error( Pa_StartStream( stream ) );
return true;
}
bool pause() override {
check_portaudio_error( Pa_StopStream( stream ) );
return true;
}
bool sleep( int ms ) override {
Pa_Sleep( ms );
return true;
}
};
#define portaudio_stream_raii portaudio_stream_blocking_raii
static std::string show_portaudio_devices( std::ostream & log ) {
std::ostringstream devices;
devices << " portaudio:" << std::endl;
portaudio_raii portaudio( false, log );
for ( PaDeviceIndex i = 0; i < Pa_GetDeviceCount(); ++i ) {
if ( Pa_GetDeviceInfo( i ) && Pa_GetDeviceInfo( i )->maxOutputChannels > 0 ) {
devices << " " << i << ": ";
if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) && Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name ) {
devices << Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name;
} else {
devices << "Host API " << Pa_GetDeviceInfo( i )->hostApi;
}
if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) ) {
if ( i == Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->defaultOutputDevice ) {
devices << " (default)";
}
}
devices << " - ";
if ( Pa_GetDeviceInfo( i )->name ) {
devices << Pa_GetDeviceInfo( i )->name;
} else {
devices << "Device " << i;
}
devices << " (";
devices << "high latency: " << Pa_GetDeviceInfo( i )->defaultHighOutputLatency;
devices << ", ";
devices << "low latency: " << Pa_GetDeviceInfo( i )->defaultLowOutputLatency;
devices << ")";
devices << std::endl;
}
}
return devices.str();
}
} // namespace openmpt123
#endif // MPT_WITH_PORTAUDIO
#endif // OPENMPT123_PORTAUDIO_HPP