RageSound_DSound_Software::RageSound_DSound_Software() { shutdown_mixer_thread = false; pcm = NULL; /* If we're emulated, we're better off with the WaveOut driver; DS * emulation tends to be desynced. */ if( ds.IsEmulated() ) RageException::ThrowNonfatal( "Driver unusable (emulated device)" ); max_writeahead = safe_writeahead; if( PREFSMAN->m_iSoundWriteAhead ) max_writeahead = PREFSMAN->m_iSoundWriteAhead; /* Create a DirectSound stream, but don't force it into hardware. */ pcm = new DSoundBuf( ds, DSoundBuf::HW_DONT_CARE, channels, samplerate, 16, max_writeahead ); /* Fill a buffer before we start playing, so we don't play whatever junk is * in the buffer. */ char *locked_buf; unsigned len; while( pcm->get_output_buf(&locked_buf, &len, chunksize()) ) { memset( locked_buf, 0, len ); pcm->release_output_buf(locked_buf, len); } StartDecodeThread(); /* Start playing. */ pcm->Play(); MixingThread.SetName("Mixer thread"); MixingThread.Create( MixerThread_start, this ); }
RString RageSoundDriver_ALSA9_Software::Init() { RString sError = LoadALSA(); if( sError != "" ) return ssprintf( "Driver unusable: %s", sError.c_str() ); g_iMaxWriteahead = safe_writeahead; RString sys; int vers; GetKernel( sys, vers ); LOG->Trace( "OS: %s ver %06i", sys.c_str(), vers ); if( sys == "Linux" && vers >= 20600 ) g_iMaxWriteahead = g_iMaxWriteahead_linux_26; if( PREFSMAN->m_iSoundWriteAhead ) g_iMaxWriteahead = PREFSMAN->m_iSoundWriteAhead; m_pPCM = new Alsa9Buf(); sError = m_pPCM->Init( channels, g_iMaxWriteahead, g_iMaxWriteahead / num_chunks, PREFSMAN->m_iSoundPreferredSampleRate ); if( sError != "" ) return sError; m_iSampleRate = m_pPCM->GetSampleRate(); StartDecodeThread(); m_MixingThread.SetName( "RageSoundDriver_ALSA9_Software" ); m_MixingThread.Create( MixerThread_start, this ); return ""; }
RString RageSoundDriver_WaveOut::Init() { m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate; if( m_iSampleRate == 0 ) m_iSampleRate = 44100; WAVEFORMATEX fmt; fmt.wFormatTag = WAVE_FORMAT_PCM; fmt.nChannels = channels; fmt.cbSize = 0; fmt.nSamplesPerSec = m_iSampleRate; fmt.wBitsPerSample = 16; fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; MMRESULT ret = waveOutOpen( &m_hWaveOut, WAVE_MAPPER, &fmt, (DWORD_PTR) m_hSoundEvent, NULL, CALLBACK_EVENT ); if( ret != MMSYSERR_NOERROR ) return wo_ssprintf( ret, "waveOutOpen failed" ); ZERO( m_aBuffers ); for(int b = 0; b < num_chunks; ++b) { m_aBuffers[b].dwBufferLength = chunksize; m_aBuffers[b].lpData = new char[chunksize]; ret = waveOutPrepareHeader( m_hWaveOut, &m_aBuffers[b], sizeof(m_aBuffers[b]) ); if( ret != MMSYSERR_NOERROR ) return wo_ssprintf( ret, "waveOutPrepareHeader failed" ); m_aBuffers[b].dwFlags |= WHDR_DONE; } LOG->Info( "WaveOut software mixing at %i hz", m_iSampleRate ); /* We have a very large writeahead; make sure we have a large enough decode * buffer to recover cleanly from underruns. */ SetDecodeBufferSize( buffersize_frames * 3/2 ); StartDecodeThread(); MixingThread.SetName( "Mixer thread" ); MixingThread.Create( MixerThread_start, this ); return RString(); }
RageSound_ALSA9_Software::RageSound_ALSA9_Software() { CString err = LoadALSA(); if( err != "" ) RageException::ThrowNonfatal("Driver unusable: %s", err.c_str()); try { shutdown = false; max_writeahead = safe_writeahead; CString sys; int vers; GetKernel( sys, vers ); LOG->Trace( "OS: %s ver %06i", sys.c_str(), vers ); if( sys == "Linux" && vers >= 20600 ) max_writeahead = max_writeahead_linux_26; if( PREFSMAN->m_iSoundWriteAhead ) max_writeahead = PREFSMAN->m_iSoundWriteAhead; pcm = new Alsa9Buf( Alsa9Buf::HW_DONT_CARE, channels ); samplerate = pcm->FindSampleRate( samplerate ); pcm->SetSampleRate( samplerate ); LOG->Info( "ALSA: Software mixing at %ihz", samplerate ); pcm->SetWriteahead( max_writeahead ); pcm->SetChunksize( max_writeahead / num_chunks ); pcm->LogParams(); StartDecodeThread(); MixingThread.SetName( "RageSound_ALSA9_Software" ); MixingThread.Create( MixerThread_start, this ); } catch(...) { UnloadALSA(); throw; } }
RString RageSoundDriver_OSS::Init() { fd = open("/dev/dsp", O_WRONLY|O_NONBLOCK); if( fd == -1 ) return ssprintf( "RageSoundDriver_OSS: Couldn't open /dev/dsp: %s", strerror(errno) ); RString sError = CheckOSSVersion( fd ); if( sError != "" ) return sError; int i = AFMT_S16_LE; if(ioctl(fd, SNDCTL_DSP_SETFMT, &i) == -1) return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFMT, %i): %s", i, strerror(errno) ); if(i != AFMT_S16_LE) return ssprintf( "RageSoundDriver_OSS: Wanted format %i, got %i instead", AFMT_S16_LE, i ); i = channels; if(ioctl(fd, SNDCTL_DSP_CHANNELS, &i) == -1) return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_CHANNELS, %i): %s", i, strerror(errno) ); if(i != channels) return ssprintf( "RageSoundDriver_OSS: Wanted %i channels, got %i instead", channels, i ); i = 44100; if(ioctl(fd, SNDCTL_DSP_SPEED, &i) == -1 ) return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SPEED, %i): %s", i, strerror(errno) ); samplerate = i; LOG->Trace("RageSoundDriver_OSS: sample rate %i", samplerate); i = (num_chunks << 16) + chunk_order; if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &i) == -1) return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFRAGMENT, %i): %s", i, strerror(errno) ); StartDecodeThread(); MixingThread.SetName( "RageSoundDriver_OSS" ); MixingThread.Create( MixerThread_start, this ); return ""; }
CString RageSoundDriver_ALSA9_Software::Init() { CString sError = LoadALSA(); if( sError != "" ) return ssprintf( "Driver unusable: %s", sError.c_str() ); max_writeahead = safe_writeahead; CString sys; int vers; GetKernel( sys, vers ); LOG->Trace( "OS: %s ver %06i", sys.c_str(), vers ); if( sys == "Linux" && vers >= 20600 ) max_writeahead = max_writeahead_linux_26; if( PREFSMAN->m_iSoundWriteAhead ) max_writeahead = PREFSMAN->m_iSoundWriteAhead; pcm = new Alsa9Buf(); sError = pcm->Init( Alsa9Buf::HW_DONT_CARE, channels ); if( sError != "" ) return sError; samplerate = pcm->FindSampleRate( samplerate ); pcm->SetSampleRate( samplerate ); LOG->Info( "ALSA: Software mixing at %ihz", samplerate ); pcm->SetWriteahead( max_writeahead ); pcm->SetChunksize( max_writeahead / num_chunks ); pcm->LogParams(); StartDecodeThread(); MixingThread.SetName( "RageSoundDriver_ALSA9_Software" ); MixingThread.Create( MixerThread_start, this ); return ""; }
RageSound_OSS::RageSound_OSS() { fd = open("/dev/dsp", O_WRONLY|O_NONBLOCK); if(fd == -1) RageException::ThrowNonfatal("RageSound_OSS: Couldn't open /dev/dsp: %s", strerror(errno)); CheckOSSVersion( fd ); shutdown = false; last_cursor_pos = 0; int i = AFMT_S16_LE; if(ioctl(fd, SNDCTL_DSP_SETFMT, &i) == -1) RageException::ThrowNonfatal("RageSound_OSS: ioctl(SNDCTL_DSP_SETFMT, %i): %s", i, strerror(errno)); if(i != AFMT_S16_LE) RageException::ThrowNonfatal("RageSound_OSS: Wanted format %i, got %i instead", AFMT_S16_LE, i); i = channels; if(ioctl(fd, SNDCTL_DSP_CHANNELS, &i) == -1) RageException::ThrowNonfatal("RageSound_OSS: ioctl(SNDCTL_DSP_CHANNELS, %i): %s", i, strerror(errno)); if(i != channels) RageException::ThrowNonfatal("RageSound_OSS: Wanted %i channels, got %i instead", channels, i); i = 44100; if(ioctl(fd, SOUND_PCM_WRITE_RATE, &i) == -1 ) RageException::ThrowNonfatal("RageSound_OSS: ioctl(SOUND_PCM_WRITE_RATE, %i): %s", i, strerror(errno)); samplerate = i; LOG->Trace("RageSound_OSS: sample rate %i", samplerate); i = (num_chunks << 16) + chunk_order; if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &i) == -1) RageException::ThrowNonfatal("RageSound_OSS: ioctl(SNDCTL_DSP_SETFRAGMENT, %i): %s", i, strerror(errno)); StartDecodeThread(); MixingThread.SetName( "RageSound_OSS" ); MixingThread.Create( MixerThread_start, this ); }
/* Initialization */ std::string RageSoundDriver_PulseAudio::Init() { int error = 0; LOG->Trace("Pulse: pa_threaded_mainloop_new()..."); m_PulseMainLoop = pa_threaded_mainloop_new(); if(m_PulseMainLoop == nullptr) { return "pa_threaded_mainloop_new() failed!"; } #ifdef PA_PROP_APPLICATION_NAME /* proplist available only since 0.9.11 */ pa_proplist *plist = pa_proplist_new(); pa_proplist_sets(plist, PA_PROP_APPLICATION_NAME, PACKAGE_NAME); pa_proplist_sets(plist, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); pa_proplist_sets(plist, PA_PROP_MEDIA_ROLE, "game"); LOG->Trace("Pulse: pa_context_new_with_proplist()..."); m_PulseCtx = pa_context_new_with_proplist( pa_threaded_mainloop_get_api(m_PulseMainLoop), "StepMania", plist); pa_proplist_free(plist); if(m_PulseCtx == nullptr) { return "pa_context_new_with_proplist() failed!"; } #else LOG->Trace("Pulse: pa_context_new()..."); m_PulseCtx = pa_context_new( pa_threaded_mainloop_get_api(m_PulseMainLoop), "Stepmania"); if(m_PulseCtx == nullptr) { return "pa_context_new() failed!"; } #endif pa_context_set_state_callback(m_PulseCtx, StaticCtxStateCb, this); LOG->Trace("Pulse: pa_context_connect()..."); error = pa_context_connect(m_PulseCtx, nullptr, (pa_context_flags_t)0, nullptr); if(error < 0) { return fmt::sprintf("pa_contect_connect(): %s", pa_strerror(pa_context_errno(m_PulseCtx))); } LOG->Trace("Pulse: pa_threaded_mainloop_start()..."); error = pa_threaded_mainloop_start(m_PulseMainLoop); if(error < 0) { return fmt::sprintf("pa_threaded_mainloop_start() returned %i", error); } /* Create the decode thread, this will be needed for Mix(), that we * will use as soon as a stream is ready. */ StartDecodeThread(); /* Wait for the pulseaudio stream to be ready before returning. * An error may occur, if it appends, m_Error becomes non-nullptr. */ m_Sem.Wait(); if(m_Error == nullptr) { return ""; } else { return m_Error; } }
RString RageSoundDriver_JACK::Init() { jack_status_t status; RString error; // Open JACK client and call it "StepMania" or whatever client = jack_client_open(PRODUCT_FAMILY, JackNoStartServer, &status); if (client == NULL) return "Couldn't connect to JACK server"; sample_rate = jack_get_sample_rate(client); LOG->Trace("JACK connected at %u Hz", sample_rate); // Start this before callbacks StartDecodeThread(); // Set callback for processing audio if (jack_set_process_callback(client, ProcessTrampoline, this)) { error = "Couldn't set JACK process callback"; goto out_close; } if (jack_set_sample_rate_callback(client, SampleRateTrampoline, this)) { error = "Couldn't set JACK sample-rate callback"; goto out_close; } // TODO Set a jack_on_shutdown callback as well? Probably just stop // caring about sound altogether if that happens. // Create output ports port_l = jack_port_register(client, "out_l", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (port_l == NULL) { error = "Couldn't create JACK port out_l"; goto out_close; } port_r = jack_port_register(client, "out_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (port_r == NULL) { error = "Couldn't create JACK port out_r"; goto out_unreg_l; } // Go! if (jack_activate(client)) { error = "Couldn't activate JACK client"; goto out_unreg_r; } error = ConnectPorts(); if (!error.empty()) // Eh. Not fatal. JACK *is* running and we successfully created // our source ports, so it's unlkely any other driver will // function. LOG->Warn( "RageSoundDriver_JACK: Couldn't connect ports: %s", error.c_str() ); // Success! LOG->Trace("JACK sound driver started successfully"); return RString(); // Not success! out_unreg_r: jack_port_unregister(client, port_r); out_unreg_l: jack_port_unregister(client, port_l); out_close: jack_client_close(client); client = NULL; return error; }
CString RageSound_CA::Init() { try { AudioDeviceID dID = CAAudioHardwareSystem::GetDefaultDevice( false, false ); mOutputDevice = new CAAudioHardwareDevice(dID); } catch (const CAException& e) { return "Couldn't create default output device."; } Float64 nominalSampleRate = 44100.0; try { mOutputDevice->SetNominalSampleRate(nominalSampleRate); } catch (const CAException& e) { LOG->Warn("Couldn't set the nominal sample rate."); nominalSampleRate = mOutputDevice->GetNominalSampleRate(); LOG->Warn("Device's nominal sample rate is %f", nominalSampleRate); } AudioStreamID sID = mOutputDevice->GetStreamByIndex( kAudioDeviceSectionOutput, 0 ); CAAudioHardwareStream stream( sID ); try { mOutputDevice->AddPropertyListener(kAudioPropertyWildcardChannel, kAudioPropertyWildcardSection, kAudioDeviceProcessorOverload, OverloadListener, this); } catch (const CAException& e) { LOG->Warn("Could not install the overload listener."); } // The canonical format // XXX should this be nominalSampleRate or not? Desc IOProcFormat(nominalSampleRate, kAudioFormatLinearPCM, 8, 1, 8, 2, 32, kAudioFormatFlagsNativeFloatPacked); const Desc SMFormat(44100.0, kAudioFormatLinearPCM, kBytesPerPacket, kFramesPerPacket, kBytesPerFrame, kChannelsPerFrame, kBitsPerChannel, kFormatFlags); try { stream.SetCurrentIOProcFormat(IOProcFormat); } catch (const CAException& e) { LOG->Warn("Could not set the IOProc format to the canonical format."); stream.GetCurrentIOProcFormat(IOProcFormat); } if (AudioConverterNew(&SMFormat, &IOProcFormat, &mConverter)) return "Couldn't create the audio converter"; try { UInt32 bufferSize = mOutputDevice->GetIOBufferSize(); LOG->Info("I/O Buffer size: %lu", bufferSize); } catch (const CAException& e) { LOG->Warn("Could not determine buffer size."); } try { UInt32 frames = mOutputDevice->GetLatency(kAudioDeviceSectionOutput); if (stream.HasProperty(0, kAudioDevicePropertyLatency)) { UInt32 t, size = 4; stream.GetPropertyData(0, kAudioDevicePropertyLatency, size, &t); frames += t; LOG->Info("Frames of stream latency: %lu", t); } else LOG->Warn("Stream reports no latency."); mLatency = frames / 44100.0; LOG->Info("Frames of latency: %lu\n" "Seconds of latency: %f", frames, mLatency); } catch (const CAException& e) { return "Couldn't get latency."; } StartDecodeThread(); try { mOutputDevice->AddIOProc(GetData, this); mOutputDevice->StartIOProc(GetData); } catch(const CAException& e) { return "Couldn't start the IOProc."; } return ""; }