199 lines
5.8 KiB
C++
199 lines
5.8 KiB
C++
|
/*
|
||
|
* openmpt123_waveout.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_WAVEOUT_HPP
|
||
|
#define OPENMPT123_WAVEOUT_HPP
|
||
|
|
||
|
#include "openmpt123_config.hpp"
|
||
|
#include "openmpt123.hpp"
|
||
|
|
||
|
#if defined(WIN32)
|
||
|
|
||
|
namespace openmpt123 {
|
||
|
|
||
|
struct waveout_exception : public exception {
|
||
|
waveout_exception() : exception( "waveout" ) { }
|
||
|
};
|
||
|
|
||
|
class waveout_stream_raii : public write_buffers_interface {
|
||
|
private:
|
||
|
HWAVEOUT waveout;
|
||
|
std::size_t num_channels;
|
||
|
std::size_t num_chunks;
|
||
|
std::size_t frames_per_chunk;
|
||
|
std::size_t bytes_per_chunk;
|
||
|
std::vector<WAVEHDR> waveheaders;
|
||
|
std::vector<std::vector<char> > wavebuffers;
|
||
|
std::deque<char> byte_queue;
|
||
|
public:
|
||
|
waveout_stream_raii( commandlineflags & flags )
|
||
|
: waveout(NULL)
|
||
|
, num_channels(0)
|
||
|
, num_chunks(0)
|
||
|
, frames_per_chunk(0)
|
||
|
, bytes_per_chunk(0)
|
||
|
{
|
||
|
if ( flags.buffer == default_high ) {
|
||
|
flags.buffer = 150;
|
||
|
} else if ( flags.buffer == default_low ) {
|
||
|
flags.buffer = 50;
|
||
|
}
|
||
|
if ( flags.period == default_high ) {
|
||
|
flags.period = 30;
|
||
|
} else if ( flags.period == default_low ) {
|
||
|
flags.period = 10;
|
||
|
}
|
||
|
flags.apply_default_buffer_sizes();
|
||
|
WAVEFORMATEX wfx;
|
||
|
ZeroMemory( &wfx, sizeof( wfx ) );
|
||
|
wfx.wFormatTag = flags.use_float ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||
|
wfx.nChannels = static_cast<WORD>( flags.channels );
|
||
|
wfx.nSamplesPerSec = flags.samplerate;
|
||
|
wfx.wBitsPerSample = flags.use_float ? 32 : 16;
|
||
|
wfx.nBlockAlign = ( wfx.wBitsPerSample / 8 ) * wfx.nChannels;
|
||
|
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||
|
wfx.cbSize = 0;
|
||
|
std::istringstream device_string( flags.device );
|
||
|
int device = -1;
|
||
|
device_string >> device;
|
||
|
waveOutOpen( &waveout, device == -1 ? WAVE_MAPPER : device, &wfx, 0, 0, CALLBACK_NULL );
|
||
|
num_channels = flags.channels;
|
||
|
std::size_t frames_per_buffer = flags.samplerate * flags.buffer / 1000;
|
||
|
num_chunks = ( flags.buffer + flags.period - 1 ) / flags.period;
|
||
|
if ( num_chunks < 2 ) {
|
||
|
num_chunks = 2;
|
||
|
}
|
||
|
frames_per_chunk = ( frames_per_buffer + num_chunks - 1 ) / num_chunks;
|
||
|
bytes_per_chunk = wfx.nBlockAlign * frames_per_chunk;
|
||
|
waveheaders.resize( num_chunks );
|
||
|
wavebuffers.resize( num_chunks );
|
||
|
for ( std::size_t i = 0; i < num_chunks; ++i ) {
|
||
|
wavebuffers[i].resize( bytes_per_chunk );
|
||
|
waveheaders[i] = WAVEHDR();
|
||
|
waveheaders[i].lpData = wavebuffers[i].data();
|
||
|
waveheaders[i].dwBufferLength = static_cast<DWORD>( wavebuffers[i].size() );
|
||
|
waveheaders[i].dwFlags = 0;
|
||
|
waveOutPrepareHeader( waveout, &waveheaders[i], sizeof( WAVEHDR ) );
|
||
|
}
|
||
|
}
|
||
|
~waveout_stream_raii() {
|
||
|
if ( waveout ) {
|
||
|
write_or_wait( true );
|
||
|
drain();
|
||
|
waveOutReset( waveout );
|
||
|
for ( std::size_t i = 0; i < num_chunks; ++i ) {
|
||
|
waveheaders[i].dwBufferLength = static_cast<DWORD>( wavebuffers[i].size() );
|
||
|
waveOutUnprepareHeader( waveout, &waveheaders[i], sizeof( WAVEHDR ) );
|
||
|
}
|
||
|
wavebuffers.clear();
|
||
|
waveheaders.clear();
|
||
|
frames_per_chunk = 0;
|
||
|
num_chunks = 0;
|
||
|
waveOutClose( waveout );
|
||
|
waveout = NULL;
|
||
|
}
|
||
|
}
|
||
|
private:
|
||
|
void drain() {
|
||
|
std::size_t empty_chunks = 0;
|
||
|
while ( empty_chunks != num_chunks ) {
|
||
|
empty_chunks = 0;
|
||
|
for ( std::size_t chunk = 0; chunk < num_chunks; ++chunk ) {
|
||
|
DWORD flags = waveheaders[chunk].dwFlags;
|
||
|
if ( !(flags & WHDR_INQUEUE) || (flags & WHDR_DONE) ) {
|
||
|
empty_chunks++;
|
||
|
}
|
||
|
}
|
||
|
if ( empty_chunks != num_chunks ) {
|
||
|
Sleep( 1 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
std::size_t wait_for_empty_chunk() {
|
||
|
while ( true ) {
|
||
|
for ( std::size_t chunk = 0; chunk < num_chunks; ++chunk ) {
|
||
|
DWORD flags = waveheaders[chunk].dwFlags;
|
||
|
if ( !(flags & WHDR_INQUEUE) || (flags & WHDR_DONE) ) {
|
||
|
return chunk;
|
||
|
}
|
||
|
}
|
||
|
Sleep( 1 );
|
||
|
}
|
||
|
}
|
||
|
void write_chunk() {
|
||
|
std::size_t chunk = wait_for_empty_chunk();
|
||
|
std::size_t chunk_bytes = std::min( byte_queue.size(), bytes_per_chunk );
|
||
|
waveheaders[chunk].dwBufferLength = static_cast<DWORD>( chunk_bytes );
|
||
|
for ( std::size_t byte = 0; byte < chunk_bytes; ++byte ) {
|
||
|
wavebuffers[chunk][byte] = byte_queue.front();
|
||
|
byte_queue.pop_front();
|
||
|
}
|
||
|
waveOutWrite( waveout, &waveheaders[chunk], sizeof( WAVEHDR ) );
|
||
|
}
|
||
|
void write_or_wait( bool flush = false ) {
|
||
|
while ( byte_queue.size() >= bytes_per_chunk ) {
|
||
|
write_chunk();
|
||
|
}
|
||
|
if ( flush && !byte_queue.empty() ) {
|
||
|
write_chunk();
|
||
|
}
|
||
|
}
|
||
|
template < typename Tsample >
|
||
|
void write_buffers( const std::vector<Tsample*> buffers, std::size_t frames ) {
|
||
|
for ( std::size_t frame = 0; frame < frames; ++frame ) {
|
||
|
for ( std::size_t channel = 0; channel < buffers.size(); ++channel ) {
|
||
|
Tsample val = buffers[channel][frame];
|
||
|
char buf[ sizeof( Tsample ) ];
|
||
|
std::memcpy( buf, &val, sizeof( Tsample ) );
|
||
|
std::copy( buf, buf + sizeof( Tsample ), std::back_inserter( byte_queue ) );
|
||
|
}
|
||
|
}
|
||
|
write_or_wait();
|
||
|
}
|
||
|
public:
|
||
|
void write( const std::vector<float*> buffers, std::size_t frames ) override {
|
||
|
write_buffers( buffers, frames );
|
||
|
}
|
||
|
void write( const std::vector<std::int16_t*> buffers, std::size_t frames ) override {
|
||
|
write_buffers( buffers, frames );
|
||
|
}
|
||
|
bool pause() override {
|
||
|
waveOutPause( waveout );
|
||
|
return true;
|
||
|
}
|
||
|
bool unpause() override {
|
||
|
waveOutRestart( waveout );
|
||
|
return true;
|
||
|
}
|
||
|
bool sleep( int ms ) override {
|
||
|
Sleep( ms );
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static std::string show_waveout_devices( std::ostream & /*log*/ ) {
|
||
|
std::ostringstream devices;
|
||
|
devices << " waveout:" << std::endl;
|
||
|
for ( UINT i = 0; i < waveOutGetNumDevs(); ++i ) {
|
||
|
devices << " " << i << ": ";
|
||
|
WAVEOUTCAPSW caps;
|
||
|
ZeroMemory( &caps, sizeof( caps ) );
|
||
|
waveOutGetDevCapsW( i, &caps, sizeof( caps ) );
|
||
|
devices << wstring_to_utf8( caps.szPname );
|
||
|
devices << std::endl;
|
||
|
}
|
||
|
return devices.str();
|
||
|
}
|
||
|
|
||
|
} // namespace openmpt123
|
||
|
|
||
|
#endif // WIN32
|
||
|
|
||
|
#endif // OPENMPT123_WAVEOUT_HPP
|