dhewm3/neo/sys/linux/sound.cpp
2011-12-10 15:36:16 +01:00

400 lines
14 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
// OSS sound interface
// http://www.opensound.com/
#include <sys/soundcard.h>
#include "../../idlib/precompiled.h"
#include "../../sound/snd_local.h"
#include "../posix/posix_public.h"
#include "sound.h"
const char *s_driverArgs[] = { "best", "oss", "alsa", NULL };
#ifndef NO_ALSA
static idCVar s_driver( "s_driver", s_driverArgs[0], CVAR_SYSTEM | CVAR_ARCHIVE, "sound driver. 'best' will attempt to use alsa and fallback to OSS if not available", s_driverArgs, idCmdSystem::ArgCompletion_String<s_driverArgs> );
#else
static idCVar s_driver( "s_driver", "oss", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "sound driver. only OSS is supported in this build" );
#endif
idAudioHardware *idAudioHardware::Alloc() {
#ifndef NO_ALSA
if ( !strcmp( s_driver.GetString(), "best" ) ) {
idAudioHardwareALSA *test = new idAudioHardwareALSA;
if ( test->DLOpen() ) {
common->Printf( "Alsa is available\n" );
return test;
}
common->Printf( "Alsa is not available\n" );
delete test;
return new idAudioHardwareOSS;
}
if ( !strcmp( s_driver.GetString(), "alsa" ) ) {
return new idAudioHardwareALSA;
}
#endif
return new idAudioHardwareOSS;
}
// OSS sound ----------------------------------------------------
/*
===============
idAudioHardware::~idAudioHardware
===============
*/
idAudioHardware::~idAudioHardware() { }
/*
=================
idAudioHardwareOSS::~idAudioHardwareOSS
=================
*/
idAudioHardwareOSS::~idAudioHardwareOSS() {
Release();
}
/*
=================
idAudioHardwareOSS::Release
=================
*/
void idAudioHardwareOSS::Release( bool bSilent ) {
if (m_audio_fd) {
if (!bSilent) {
common->Printf("------ OSS Sound Shutdown ------\n");
}
if (m_buffer) {
free( m_buffer );
m_buffer = NULL;
m_buffer_size = 0;
}
common->Printf("close sound device\n");
if (close(m_audio_fd) == -1) {
common->Warning( "failed to close sound device: %s", strerror(errno) );
}
m_audio_fd = 0;
if (!bSilent) {
common->Printf("--------------------------------\n");
}
}
}
/*
=================
idAudioHardwareOSS::InitFailed
=================
*/
void idAudioHardwareOSS::InitFailed() {
Release( true );
cvarSystem->SetCVarBool( "s_noSound", true );
common->Warning( "sound subsystem disabled" );
common->Printf( "--------------------------------------\n" );
}
/*
=================
idAudioHardwareOSS::ExtractOSSVersion
=================
*/
void idAudioHardwareOSS::ExtractOSSVersion( int version, idStr &str ) const {
sprintf( str, "%d.%d.%d", ( version & 0xFF0000 ) >> 16, ( version & 0xFF00 ) >> 8, version & 0xFF );
}
/*
=================
idAudioHardwareOSS::Initialize
http://www.4front-tech.com/pguide/index.html
though OSS API docs (1.1) advertise AFMT_S32_LE, AFMT_S16_LE is the only output format I've found in kernel emu10k1 headers
BSD NOTE: With the GNU library, you can use free to free the blocks that memalign, posix_memalign, and valloc return.
That does not work in BSD, however--BSD does not provide any way to free such blocks.
=================
*/
idCVar s_device( "s_dsp", "/dev/dsp", CVAR_SYSTEM | CVAR_ARCHIVE, "" );
bool idAudioHardwareOSS::Initialize( ) {
common->Printf("------ OSS Sound Initialization ------\n");
int requested_sample_format, caps, oss_version;
idStr s_compiled_oss_version, s_oss_version;
struct audio_buf_info info;
memset( &info, 0, sizeof( info ) );
if (m_audio_fd) {
Release();
}
// open device ------------------------------------------------
if ((m_audio_fd = open( s_device.GetString(), O_WRONLY | O_NONBLOCK, 0)) == -1) {
m_audio_fd = 0;
common->Warning( "failed to open sound device '%s': %s", s_device.GetString(), strerror(errno) );
InitFailed();
return false;
}
// make it blocking - so write overruns don't fail with 'Resource temporarily unavailable'
int flags;
if ( ( flags = fcntl( m_audio_fd, F_GETFL ) ) == -1 ) {
common->Warning( "failed fcntl F_GETFL on sound device '%s': %s", s_device.GetString(), strerror( errno ) );
InitFailed();
return false;
}
flags &= ~O_NONBLOCK;
if ( fcntl( m_audio_fd, F_SETFL, flags ) == -1 ) {
common->Warning( "failed to clear O_NONBLOCK on sound device '%s': %s", s_device.GetString(), strerror( errno ) );
InitFailed();
return false;
}
common->Printf("opened sound device '%s'\n", s_device.GetString());
// verify capabilities -----------------------------------------
#if SOUND_VERSION >= 0x040000
// may only be available starting with OSS API v4.0
// http://www.fi.opensound.com/developer/SNDCTL_SYSINFO.html
// NOTE: at OSS API 4.0 headers, replace OSS_SYSINFO with SNDCTL_SYSINFO
oss_sysinfo si;
if ( ioctl( m_audio_fd, OSS_SYSINFO, &si ) == -1 ) {
common->Printf( "ioctl SNDCTL_SYSINFO failed: %s\nthis ioctl is only available in OSS/Linux implementation. If you run OSS/Free, don't bother.", strerror( errno ) );
} else {
common->Printf( "%s: %s %s\n", s_device.GetString(), si.product, si.version );
}
#endif
if ( ioctl( m_audio_fd, SNDCTL_DSP_GETCAPS, &caps ) == -1 ) {
common->Warning( "ioctl SNDCTL_DSP_GETCAPS failed - driver too old?" );
InitFailed();
return false;
}
common->DPrintf("driver rev %d - capabilities %d\n", caps & DSP_CAP_REVISION, caps);
if (ioctl( m_audio_fd, OSS_GETVERSION, &oss_version ) == -1) {
common->Warning( "ioctl OSS_GETVERSION failed" );
InitFailed();
return false;
}
ExtractOSSVersion( oss_version, s_oss_version );
ExtractOSSVersion( SOUND_VERSION, s_compiled_oss_version );
common->DPrintf( "OSS interface version %s - compile time %s\n", s_oss_version.c_str(), s_compiled_oss_version.c_str() );
if (!(caps & DSP_CAP_MMAP)) {
common->Warning( "driver doesn't have DSP_CAP_MMAP capability" );
InitFailed();
return false;
}
if (!(caps & DSP_CAP_TRIGGER)) {
common->Warning( "driver doesn't have DSP_CAP_TRIGGER capability" );
InitFailed();
return false;
}
// sample format -----------------------------------------------
requested_sample_format = AFMT_S16_LE;
m_sample_format = requested_sample_format;
if (ioctl(m_audio_fd, SNDCTL_DSP_SETFMT, &m_sample_format) == -1) {
common->Warning( "ioctl SNDCTL_DSP_SETFMT %d failed: %s", requested_sample_format, strerror(errno) );
InitFailed();
return false;
}
if ( m_sample_format != requested_sample_format ) {
common->Warning( "ioctl SNDCTL_DSP_SETFMT failed to get the requested sample format %d, got %d", requested_sample_format, m_sample_format );
InitFailed();
return false;
}
// channels ----------------------------------------------------
// sanity over number of speakers
if ( idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 6 && idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
common->Warning( "invalid value for s_numberOfSpeakers. Use either 2 or 6" );
idSoundSystemLocal::s_numberOfSpeakers.SetInteger( 2 );
}
m_channels = idSoundSystemLocal::s_numberOfSpeakers.GetInteger();
if ( ioctl( m_audio_fd, SNDCTL_DSP_CHANNELS, &m_channels ) == -1 ) {
common->Warning( "ioctl SNDCTL_DSP_CHANNELS %d failed: %s", idSoundSystemLocal::s_numberOfSpeakers.GetInteger(), strerror(errno) );
InitFailed();
return false;
}
if ( m_channels != (unsigned int)idSoundSystemLocal::s_numberOfSpeakers.GetInteger() ) {
common->Warning( "ioctl SNDCTL_DSP_CHANNELS failed to get the %d requested channels, got %d", idSoundSystemLocal::s_numberOfSpeakers.GetInteger(), m_channels );
if ( m_channels != 2 && idSoundSystemLocal::s_numberOfSpeakers.GetInteger() != 2 ) {
// we didn't request 2 channels, some drivers reply 1 channel on error but may still let us still get 2 if properly asked
m_channels = 2;
if ( ioctl( m_audio_fd, SNDCTL_DSP_CHANNELS, &m_channels ) == -1 ) {
common->Warning( "ioctl SNDCTL_DSP_CHANNELS fallback to 2 failed: %s", strerror(errno) );
InitFailed();
return false;
}
}
if ( m_channels == 2 ) {
// tell the system to mix 2 channels
common->Warning( "falling back to stereo" );
idSoundSystemLocal::s_numberOfSpeakers.SetInteger( 2 );
} else {
// disable sound
InitFailed();
return false;
}
}
assert( (int)m_channels == idSoundSystemLocal::s_numberOfSpeakers.GetInteger() );
// sampling rate ------------------------------------------------
m_speed = PRIMARYFREQ;
if ( ioctl( m_audio_fd, SNDCTL_DSP_SPEED, &m_speed ) == -1 ) {
common->Warning( "ioctl SNDCTL_DSP_SPEED %d failed: %s", PRIMARYFREQ, strerror(errno) );
InitFailed();
return false;
}
// instead of an exact match, do a very close to
// there is some horrible Ensonic ES1371 which replies 44101 for a 44100 request
if ( abs( m_speed - PRIMARYFREQ ) > 5 ) {
common->Warning( "ioctl SNDCTL_DSP_SPEED failed to get the requested frequency %d, got %d", PRIMARYFREQ, m_speed );
InitFailed();
return false;
}
common->Printf("%s - bit rate: %d, channels: %d, frequency: %d\n", s_device.GetString(), m_sample_format, m_channels, m_speed);
// output buffer ------------------------------------------------
// allocate a final buffer target, the sound engine locks, writes, and we write back to the device
// we want m_buffer_size ( will have to rename those )
// ROOM_SLICES_IN_BUFFER is fixed ( system default, 10 )
// MIXBUFFER_SAMPLES is the number of samples found in a slice
// each sample is m_channels * sizeof( float ) bytes
// in AsyncUpdate we only write one block at a time, so we'd only need to have a final mix buffer sized of a single block
m_buffer_size = MIXBUFFER_SAMPLES * m_channels * 2;
m_buffer = malloc( m_buffer_size );
common->Printf( "allocated a mix buffer of %d bytes\n", m_buffer_size );
// toggle sound -------------------------------------------------
// toggle off before toggling on. that's what OSS source code samples recommends
int flag = 0;
if (ioctl(m_audio_fd, SNDCTL_DSP_SETTRIGGER, &flag) == -1) {
common->Warning( "ioctl SNDCTL_DSP_SETTRIGGER 0 failed: %s", strerror(errno) );
}
flag = PCM_ENABLE_OUTPUT;
if (ioctl(m_audio_fd, SNDCTL_DSP_SETTRIGGER, &flag) == -1) {
common->Warning( "ioctl SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed: %s", strerror(errno) );
}
common->Printf("--------------------------------------\n");
return true;
}
/*
===============
idAudioHardwareOSS::Flush
===============
*/
bool idAudioHardwareOSS::Flush( void ) {
audio_buf_info ospace;
if ( ioctl( m_audio_fd, SNDCTL_DSP_GETOSPACE, &ospace ) == -1 ) {
Sys_Printf( "ioctl SNDCTL_DSP_GETOSPACE failed: %s\n", strerror( errno ) );
return false;
}
// how many chunks can we write to the audio device right now
m_freeWriteChunks = ( ospace.bytes * MIXBUFFER_CHUNKS ) / ( MIXBUFFER_SAMPLES * m_channels * 2 );
if ( m_writeChunks ) {
// flush out any remaining chunks we could now
Write( true );
}
return ( m_freeWriteChunks > 0 );
}
/*
=================
idAudioHardwareOSS::GetMixBufferSize
=================
*/
int idAudioHardwareOSS::GetMixBufferSize() {
// return MIXBUFFER_SAMPLES * 2 * m_channels;
return m_buffer_size;
}
/*
=================
idAudioHardwareOSS::GetMixBuffer
=================
*/
short* idAudioHardwareOSS::GetMixBuffer() {
return (short *)m_buffer;
}
/*
===============
idAudioHardwareOSS::Write
rely on m_freeWriteChunks which has been set in Flush() before engine did the mixing for this MIXBUFFER_SAMPLE
===============
*/
void idAudioHardwareOSS::Write( bool flushing ) {
assert( m_audio_fd );
int ret;
if ( !flushing && m_writeChunks ) {
// if we write after a new mixing loop, we should have m_writeChunk == 0
// otherwise that last remaining chunk that was never flushed out to the audio device has just been overwritten
Sys_Printf( "idAudioHardwareOSS::Write: %d samples were overflowed and dropped\n", m_writeChunks * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS );
}
if ( !flushing ) {
// if running after the mix loop, then we have a full buffer to write out
m_writeChunks = MIXBUFFER_CHUNKS;
}
if ( m_freeWriteChunks == 0 ) {
return;
}
// what to write and how much
uintptr_t pos = (uintptr_t)m_buffer + ( MIXBUFFER_CHUNKS - m_writeChunks ) * m_channels * 2 * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS;
int len = Min( m_writeChunks, m_freeWriteChunks ) * m_channels * 2 * MIXBUFFER_SAMPLES / MIXBUFFER_CHUNKS;
assert( len > 0 );
if ( ( ret = write( m_audio_fd, (void*)pos, len ) ) == -1 ) {
Sys_Printf( "write to audio fd failed: %s\n", strerror( errno ) );
return;
}
if ( len != ret ) {
Sys_Printf( "short write to audio fd: wrote %d out of %d\n", ret, m_buffer_size );
return;
}
m_writeChunks -= Min( m_writeChunks, m_freeWriteChunks );
}
/*
===============
Sys_LoadOpenAL
-===============
*/
bool Sys_LoadOpenAL( void ) {
return false;
}