|
@@ -0,0 +1,280 @@
|
|
|
|
|
+// Filename: audio_linux_traits.C
|
|
|
|
|
+// Created by: cary (02Oct00)
|
|
|
|
|
+//
|
|
|
|
|
+////////////////////////////////////////////////////////////////////
|
|
|
|
|
+
|
|
|
|
|
+#include "audio_linux_traits.h"
|
|
|
|
|
+#include "audio_manager.h"
|
|
|
|
|
+#include "config_audio.h"
|
|
|
|
|
+#include <ipc_thread.h>
|
|
|
|
|
+#include <ipc_mutex.h>
|
|
|
|
|
+#include <set>
|
|
|
|
|
+
|
|
|
|
|
+typedef set<Buffer*> BufferSet;
|
|
|
|
|
+
|
|
|
|
|
+static bool have_initialized = false;
|
|
|
|
|
+static mutex buffer_mutex;
|
|
|
|
|
+static byte* buffer1;
|
|
|
|
|
+static byte* buffer2;
|
|
|
|
|
+static byte* current_buffer;
|
|
|
|
|
+static byte* back_buffer;
|
|
|
|
|
+static byte* zero_buffer;
|
|
|
|
|
+static byte* scratch_buffer;
|
|
|
|
|
+static byte* fetch_buffer;
|
|
|
|
|
+static int want_buffers, have_buffers;
|
|
|
|
|
+static bool initializing = false;
|
|
|
|
|
+static int output_fd;
|
|
|
|
|
+static thread* update_thread;
|
|
|
|
|
+static int sample_size = sizeof(short);
|
|
|
|
|
+
|
|
|
|
|
+BufferSet buffers;
|
|
|
|
|
+
|
|
|
|
|
+static void swap_buffers(void) {
|
|
|
|
|
+ byte *tmp = current_buffer;
|
|
|
|
|
+ current_buffer = back_buffer;
|
|
|
|
|
+ back_buffer = tmp;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+INLINE static signed short read_buffer(byte* buf, int idx) {
|
|
|
|
|
+ signed short ret = 0;
|
|
|
|
|
+ switch (sample_size) {
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ ret = *((signed char *)(&buf[idx]));
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ ret = *((signed short *)(&buf[idx*2]));
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ audio_cat->debug() << "unknown sample size (" << sample_size << ")"
|
|
|
|
|
+ << endl;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+INLINE static void write_buffer(byte* buf, int idx, signed short val) {
|
|
|
|
|
+ switch (sample_size) {
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ *((signed char *)(&buf[idx])) = val & 0xff;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ *((signed short *)(&buf[idx*2])) = val;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ audio_cat->debug() << "unknown sample size (" << sample_size << ")"
|
|
|
|
|
+ << endl;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+INLINE static signed short sound_clamp(signed int value) {
|
|
|
|
|
+ signed short ret = 0;
|
|
|
|
|
+ switch (sample_size) {
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ ret = (value > 127)?127:((value < -128)?(-128):(value));
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ ret = (value > 32767)?32767:((value < -32768)?(-32768):(value));
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ audio_cat->debug() << "unknown sample size (" << sample_size << ")"
|
|
|
|
|
+ << endl;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return ret;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void mix_in(byte* buf, byte* from, float vol, float pan) {
|
|
|
|
|
+ int done = audio_buffer_size / sample_size;
|
|
|
|
|
+ for (int i=0; i<done; i+=2) {
|
|
|
|
|
+ signed short left = read_buffer(buf, i);
|
|
|
|
|
+ signed short right = read_buffer(buf, i+1);
|
|
|
|
|
+
|
|
|
|
|
+ // now get the incoming data
|
|
|
|
|
+ signed short in_left = read_buffer(from, i);
|
|
|
|
|
+ signed short in_right = read_buffer(from, i+1);
|
|
|
|
|
+
|
|
|
|
|
+ // figure out panning at some point
|
|
|
|
|
+ in_left *= vol;
|
|
|
|
|
+ in_right *= vol;
|
|
|
|
|
+
|
|
|
|
|
+ // compute mixed values
|
|
|
|
|
+ left = sound_clamp(left+in_left);
|
|
|
|
|
+ right = sound_clamp(right+in_right);
|
|
|
|
|
+
|
|
|
|
|
+ // write it back to the buffer
|
|
|
|
|
+ write_buffer(buf, i, left);
|
|
|
|
|
+ write_buffer(buf, i+1, right);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void mix_buffer(byte* buf) {
|
|
|
|
|
+ memcpy(scratch_buffer, zero_buffer, audio_buffer_size);
|
|
|
|
|
+ // do stuff
|
|
|
|
|
+ for (BufferSet::iterator i=buffers.begin(); i!=buffers.end(); ++i)
|
|
|
|
|
+ min_in(scratch, (*i)->get_buffer(fetch_buffer), 1., 0.);
|
|
|
|
|
+ BufferSet to_del;
|
|
|
|
|
+ for (BufferSet::iterator j=buffers.begin(); j!=buffers.end(); ++j)
|
|
|
|
|
+ if ((*j)->is_done())
|
|
|
|
|
+ to_del.insert(*j);
|
|
|
|
|
+ {
|
|
|
|
|
+ mutex_lock m(buffer_mutex);
|
|
|
|
|
+ memcpy(buf, scratch_buffer, audio_buffer_size);
|
|
|
|
|
+ --want_buffers;
|
|
|
|
|
+ ++have_buffers;
|
|
|
|
|
+ for (BufferSet::iterator k=to_del.begin(); k!=to_del.end(); ++k) {
|
|
|
|
|
+ buffers.erase(*k);
|
|
|
|
|
+ (*k)->reset();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void update_linux(void) {
|
|
|
|
|
+ if (buffers.empty())
|
|
|
|
|
+ return;
|
|
|
|
|
+ switch (want_buffers) {
|
|
|
|
|
+ case 0:
|
|
|
|
|
+ // all buffers are full right now. This is a good state.
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ // mix a buffer and put it in place.
|
|
|
|
|
+ mix_buffer(back_buffer);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ if (!initializing)
|
|
|
|
|
+ audio_cat->warning() << "audio buffers are being starved" << endl;
|
|
|
|
|
+ // mix 2 buffers and put them in place.
|
|
|
|
|
+ mix_buffer(current_buffer);
|
|
|
|
|
+ mix_buffer(back_buffer);
|
|
|
|
|
+ initializing = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ audio_cat->error() << "audio system wants more then 2 buffers!" << endl;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void internal_update(void*) {
|
|
|
|
|
+ if ((output_fd = open(audio_device, O_WRONLY, 0)) == -1) {
|
|
|
|
|
+ audio_cat->error() << "could not open '" << audio_device << "'" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // this one I don't know about
|
|
|
|
|
+ int fragsize = 0x0004000c;
|
|
|
|
|
+ if (ioctl(output_fd, SNDCTL_DSP_SETFRAGMENT, &fragsize) == -1) {
|
|
|
|
|
+ audio_cat->error() << "faied to set fragment size" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // for now signed, 16-bit, little endian
|
|
|
|
|
+ int format = AFMT_S16_LE;
|
|
|
|
|
+ if (ioctl(output_fd, SNDCTL_DSP_SETFMT, &format) == -1) {
|
|
|
|
|
+ audio_cat->error() << "failed to set format on the dsp" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // set stereo
|
|
|
|
|
+ int stereo = 1;
|
|
|
|
|
+ if (ioctl(output_fd, SNDCTL_DSP_STEREO, &stereo) == -1) {
|
|
|
|
|
+ audio_cat->error() << "failed to set stereo on the dsp" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // set the frequency
|
|
|
|
|
+ if (ioctl(output_fd, SNDCTL_DSP_SPEED, &audio_mix_freq) == -1) {
|
|
|
|
|
+ audio_cat->error() << "failed to set frequency on the dsp" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ while (1) {
|
|
|
|
|
+ if (have_buffers == 0) {
|
|
|
|
|
+ ipc_traits::sleep(0, 10000);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ write(output_fd, current_buffer, audio_buffer_size);
|
|
|
|
|
+ {
|
|
|
|
|
+ mutex_lock m(buffer_mutex);
|
|
|
|
|
+ swap_buffers();
|
|
|
|
|
+ --have_buffers;
|
|
|
|
|
+ ++want_buffers;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void initialize(void) {
|
|
|
|
|
+ if (have_initialized)
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ buffer1 = new byte[audio_buffer_size];
|
|
|
|
|
+ buffer2 = new byte[audio_buffer_size];
|
|
|
|
|
+ scratch_buffer = new byte[audio_buffer_size];
|
|
|
|
|
+ fetch_buffer = new byte[audio_buffer_size];
|
|
|
|
|
+ zero_buffer = new byte[audio_buffer_size];
|
|
|
|
|
+
|
|
|
|
|
+ for (int i=0; i<audio_buffer_size; ++i)
|
|
|
|
|
+ zero_buffer[i] = 0;
|
|
|
|
|
+
|
|
|
|
|
+ current_buffer = buffer1;
|
|
|
|
|
+ back_buffer = buffer2;
|
|
|
|
|
+
|
|
|
|
|
+ want_buffers = 2;
|
|
|
|
|
+ have_buffers = 0;
|
|
|
|
|
+ initializing = true;
|
|
|
|
|
+
|
|
|
|
|
+ audio_cat->info() << "spawning internal update thread" << endl;
|
|
|
|
|
+ update_thread = thread::create(internal_update, (void*)0L,
|
|
|
|
|
+ thread_PRIORITY_NORMAL);
|
|
|
|
|
+
|
|
|
|
|
+ AudioManager::set_update_func(update_linux);
|
|
|
|
|
+ have_initialized = true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LinuxSample::~LinuxSample(void) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+float LinuxSample::length(void) {
|
|
|
|
|
+ return (_data->get_size()) / (audio_mix_freq * sample_size);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+AudioTraits::SampleClass::SampleStatus LinuxSample::status(void) {
|
|
|
|
|
+ BufferSet::iterator i = buffers.find(_data);
|
|
|
|
|
+ if (i != buffers.end())
|
|
|
|
|
+ return AudioTraits::SampleClass::PLAYING;
|
|
|
|
|
+ return AudioTraits::SampleClass::READY;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void LinuxSample::destroy(AudioTraits::SampleClass* sample) {
|
|
|
|
|
+ delete sample;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LinuxSample* LinuxSample::load_raw(byte* data, unsigned long size) {
|
|
|
|
|
+ return new LinuxSample(new Buffer(data, size));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LinuxMusic::~LinuxMusic(void) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+AudioTraits::MusicClass::MusicStatus LinuxMusic::status(void) {
|
|
|
|
|
+ return AudioTraits::MusicClass::READY;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LinuxPlayer* LinuxPlayer::_global_instance = (LinuxPlayer*)0L;
|
|
|
|
|
+
|
|
|
|
|
+LinuxPlayer::~LinuxPlayer(void) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void LinuxPlayer::play_sample(AudioTraits::SampleClass* sample) {
|
|
|
|
|
+ initialize();
|
|
|
|
|
+ LinuxSample* lsample = (LinuxSample*)sample;
|
|
|
|
|
+ buffers.insert(lsample->get_data());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void LinuxPlayer::play_music(AudioTraits::MusicClass*) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void LinuxPlayer::set_volume(AudioTraits::SampleClass*, int) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void LinuxPlayer::set_volume(AudioTraits::MusicClass*, int) {
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+LinuxPlayer* LinuxPlayer::get_instance(void) {
|
|
|
|
|
+ if (_global_instance == (LinuxPlayer*)0L)
|
|
|
|
|
+ _global_instance = new LinuxPlayer();
|
|
|
|
|
+ return _global_instance;
|
|
|
|
|
+}
|