Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / cinelerra / audiooss.C
diff --git a/cinelerra-5.1/cinelerra/audiooss.C b/cinelerra-5.1/cinelerra/audiooss.C
new file mode 100644 (file)
index 0000000..73340ff
--- /dev/null
@@ -0,0 +1,494 @@
+
+/*
+ * CINELERRA
+ * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "audioconfig.h"
+#include "audiodevice.h"
+#include "audiooss.h"
+#include "clip.h"
+#include "condition.h"
+#include "errno.h"
+#include "playbackconfig.h"
+#include "preferences.h"
+#include "recordconfig.h"
+#include "mutex.h"
+
+#include <string.h>
+
+#ifdef HAVE_OSS
+
+// These are only available in commercial OSS
+
+#ifndef AFMT_S32_LE
+#define AFMT_S32_LE     0x00001000
+#define AFMT_S32_BE     0x00002000
+#endif
+
+
+// Synchronie multiple devices by using threads
+
+OSSThread::OSSThread(AudioOSS *device)
+ : Thread(1, 0, 0)
+{
+       rd = 0;
+       wr = 0;
+       done = 0;
+       this->device = device;
+       input_lock = new Condition(0, "OSSThread::input_lock");
+       output_lock = new Condition(1, "OSSThread::output_lock");
+       read_lock = new Condition(0, "OSSThread::read_lock");
+       write_lock = new Condition(0, "OSSThread::write_lock");
+
+       // delay / timing
+       bytes_written = 0;
+       timer = new Timer;
+       delay = 0;
+       timer_lock = new Mutex("OSSThread::timer_lock");
+       timer->update();
+}
+
+OSSThread::~OSSThread()
+{
+       done = 1;
+       input_lock->unlock();
+       Thread::join();
+       delete input_lock;
+       delete output_lock;
+       delete read_lock;
+       delete write_lock;
+       delete timer_lock;
+       delete timer;
+}
+
+void OSSThread::run()
+{
+       while(!done) {
+               input_lock->lock("OSSThread::run 1");
+               if(done) return;
+               if(rd) {
+                       read(fd, data, bytes);
+                       read_lock->unlock();
+               }
+               else if(wr) {
+                       int count = bytes;
+                       unsigned char *bp = data;
+                       while( count > 0 ) {
+                               Thread::enable_cancel();
+                               int ret = write(fd, bp, bytes);
+                               Thread::disable_cancel();
+                               if( ret < 0 ) break;
+                               bp += ret;
+                               count -= ret;
+                               int delay = 0;
+                               timer_lock->lock("OSSThread::run");
+                               if( !ioctl(fd, SNDCTL_DSP_GETODELAY, &delay) )
+                                       this->delay = delay;
+                               bytes_written += ret;
+                               timer_lock->unlock();
+                       }
+                       write_lock->unlock();
+               }
+               output_lock->unlock();
+       }
+}
+
+void OSSThread::write_data(int fd, unsigned char *data, int bytes)
+{
+       output_lock->lock("OSSThread::write_data");
+       wr = 1;
+       rd = 0;
+       this->data = data;
+       this->bytes = bytes;
+       this->fd = fd;
+       input_lock->unlock();
+}
+
+void OSSThread::read_data(int fd, unsigned char *data, int bytes)
+{
+       output_lock->lock("OSSThread::read_data");
+       wr = 0;
+       rd = 1;
+       this->data = data;
+       this->bytes = bytes;
+       this->fd = fd;
+       input_lock->unlock();
+}
+
+void OSSThread::wait_read()
+{
+       read_lock->lock("OSSThread::wait_read");
+}
+
+void OSSThread::wait_write()
+{
+       write_lock->lock("OSSThread::wait_write");
+}
+
+
+// bytes not samples
+int64_t OSSThread::device_position()
+{
+       timer_lock->lock("OSSThread::device_position");
+       int64_t ret = bytes_written - delay;
+       timer_lock->unlock();
+       return ret;
+}
+
+
+AudioOSS::AudioOSS(AudioDevice *device)
+ : AudioLowLevel(device)
+{
+       for(int i = 0; i < MAXDEVICES; i++) {
+               dsp_in[i] = dsp_out[i] = -1;
+               thread[i] = 0;
+               data[i] = 0;
+               data_allocated[i] = 0;
+       }
+}
+
+AudioOSS::~AudioOSS()
+{
+}
+
+int AudioOSS::open_input()
+{
+       device->in_bits = device->in_config->oss_in_bits;
+// 24 bits not available in OSS
+       if(device->in_bits == 24) device->in_bits = 32;
+
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if(device->in_config->oss_enable[i]) {
+//printf("AudioOSS::open_input 10\n");
+                       dsp_in[i] = open(device->in_config->oss_in_device[i], O_RDONLY/* | O_NDELAY*/);
+//printf("AudioOSS::open_input 20\n");
+                       if(dsp_in[i] < 0) {
+                               fprintf(stderr, "AudioOSS::open_input %s: %s\n",
+                                       device->in_config->oss_in_device[i], strerror(errno));
+                               close_all();
+                               return 1;
+                       }
+
+                       int format = get_fmt(device->in_config->oss_in_bits);
+                       int buffer_info = sizetofrag(device->in_samples,
+                               device->get_ichannels(),
+                               device->in_config->oss_in_bits);
+
+                       set_cloexec_flag(dsp_in[i], 1);
+
+// For the ice1712 the buffer must be maximum or no space will be allocated.
+                       if(device->idriver == AUDIO_OSS_ENVY24)
+                               buffer_info = 0x7fff000f;
+                       int ret = 1;
+                       if(ioctl(dsp_in[i], SNDCTL_DSP_SETFRAGMENT, &buffer_info))
+                               fprintf(stderr, "SNDCTL_DSP_SETFRAGMENT failed.\n");
+                       else if(ioctl(dsp_in[i], SNDCTL_DSP_SETFMT, &format) < 0)
+                               fprintf(stderr, "SNDCTL_DSP_SETFMT failed\n");
+                       else {
+                               int channels = device->get_ichannels();
+                               if(ioctl(dsp_in[i], SNDCTL_DSP_CHANNELS, &channels) < 0)
+                                       fprintf(stderr, "SNDCTL_DSP_CHANNELS failed\n");
+                               else if(ioctl(dsp_in[i], SNDCTL_DSP_SPEED, &device->in_samplerate) < 0)
+                                       fprintf(stderr, "SNDCTL_DSP_SPEED failed\n");
+                               else
+                                       ret = 0;
+                       }
+                       if( ret ) {
+                               close_all();
+                               return ret;
+                       }
+
+                       audio_buf_info recinfo;
+                       ioctl(dsp_in[i], SNDCTL_DSP_GETISPACE, &recinfo);
+
+//printf("AudioOSS::open_input fragments=%d fragstotal=%d fragsize=%d bytes=%d\n",
+//     recinfo.fragments, recinfo.fragstotal, recinfo.fragsize, recinfo.bytes);
+
+                       thread[i] = new OSSThread(this);
+                       thread[i]->start();
+               }
+       }
+       return 0;
+}
+
+int AudioOSS::open_output()
+{
+       device->out_bits = device->out_config->oss_out_bits;
+// OSS only supports 8, 16, and 32
+       if(device->out_bits == 24) device->out_bits = 32;
+
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if(device->out_config->oss_enable[i]) {
+// Linux 2.4.18 no longer supports allocating the maximum buffer size.
+// Need the shrink fragment size in preferences until it works.
+                       dsp_out[i] =
+                               open(device->out_config->oss_out_device[i],
+                                       O_WRONLY /*| O_NDELAY*/);
+                       if(dsp_out[i] < 0) {
+                               perror("AudioOSS::open_output");
+                               close_all();
+                               return 1;
+                       }
+
+                       int format = get_fmt(device->out_config->oss_out_bits);
+                       int buffer_info = sizetofrag(device->out_samples,
+                               device->get_ochannels(),
+                               device->out_config->oss_out_bits);
+
+                       set_cloexec_flag(dsp_out[i], 1);
+
+// For the ice1712 the buffer must be maximum or no space will be allocated.
+                       if(device->odriver == AUDIO_OSS_ENVY24)
+                               buffer_info = 0x7fff000f;
+                       int ret = 1;
+                       if(ioctl(dsp_out[i], SNDCTL_DSP_SETFRAGMENT, &buffer_info))
+                               fprintf(stderr,"SNDCTL_DSP_SETFRAGMENT 2 failed.\n");
+                       else if(ioctl(dsp_out[i], SNDCTL_DSP_SETFMT, &format) < 0)
+                               fprintf(stderr,"SNDCTL_DSP_SETFMT 2 failed\n");
+                       else {
+                               int channels = device->get_ochannels();
+                               if(ioctl(dsp_out[i], SNDCTL_DSP_CHANNELS, &channels) < 0)
+                                       fprintf(stderr,"SNDCTL_DSP_CHANNELS 2 failed\n");
+                               else if(ioctl(dsp_out[i], SNDCTL_DSP_SPEED, &device->out_samplerate) < 0)
+                                       fprintf(stderr,"SNDCTL_DSP_SPEED 2 failed\n");
+                               else
+                                       ret = 0;
+                       }
+                       if( ret ) {
+                               close_all();
+                               return ret;
+                       }
+
+                       audio_buf_info playinfo;
+                       ioctl(dsp_out[i], SNDCTL_DSP_GETOSPACE, &playinfo);
+// printf("AudioOSS::open_output fragments=%d fragstotal=%d fragsize=%d bytes=%d\n",
+// playinfo.fragments, playinfo.fragstotal, playinfo.fragsize, playinfo.bytes);
+                       device->device_buffer = playinfo.bytes;
+                       thread[i] = new OSSThread(this);
+                       thread[i]->start();
+               }
+       }
+       return 0;
+}
+
+int AudioOSS::sizetofrag(int samples, int channels, int bits)
+{
+       int testfrag = 2, fragsize = 1, grain = 4;
+       samples *= channels * bits / (8 * grain);
+       while(testfrag < samples) {
+               fragsize++;
+               testfrag *= 2;
+       }
+//printf("AudioOSS::sizetofrag %d\n", fragsize);
+       return (grain << 16) | fragsize;
+}
+
+int AudioOSS::get_fmt(int bits)
+{
+       switch(bits) {
+       case 32: return AFMT_S32_LE; break;
+       case 16: return AFMT_S16_LE; break;
+       case 8:  return AFMT_S8;  break;
+       }
+       return AFMT_S16_LE;
+}
+
+
+int AudioOSS::close_all()
+{
+//printf("AudioOSS::close_all 1\n");
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if(dsp_in[i] >= 0) {
+                       ioctl(dsp_in[i], SNDCTL_DSP_RESET, 0);
+                       close(dsp_in[i]);
+                       dsp_in[i] = -1;
+               }
+
+               if(dsp_out[i] >= 0) {
+//printf("AudioOSS::close_all 2\n");
+                       ioctl(dsp_out[i], SNDCTL_DSP_RESET, 0);
+                       close(dsp_out[i]);
+                       dsp_out[i] = -1;
+               }
+
+               if(thread[i]) delete thread[i];
+               if(data[i]) delete [] data[i];
+       }
+       return 0;
+}
+
+int AudioOSS::set_cloexec_flag(int desc, int value)
+{
+       int oldflags = fcntl (desc, F_GETFD, 0);
+       if (oldflags < 0) return oldflags;
+       if(value != 0)
+               oldflags |= FD_CLOEXEC;
+       else
+               oldflags &= ~FD_CLOEXEC;
+       return fcntl(desc, F_SETFD, oldflags);
+}
+
+int64_t AudioOSS::device_position()
+{
+       count_info info;
+       if(!ioctl(get_output(0), SNDCTL_DSP_GETOPTR, &info))
+       {
+//printf("AudioOSS::device_position %d %d %d\n", info.bytes, device->get_obits(), device->get_ochannels());
+// workaround for ALSA OSS emulation driver's bug
+// the problem is that if the first write to sound device was not full lenght fragment then
+// _GETOPTR returns insanely large numbers at first moments of play
+               if (info.bytes > 2100000000) return 0;
+               int frame = device->get_ochannels() * device->get_obits()/8;
+               return info.bytes / frame;
+       }
+       for (int i = 0; i < MAXDEVICES; i++)
+       {
+               if (thread[i])
+                       return thread[i]->device_position() /
+                               device->get_ochannels() /
+                               (device->get_obits()/8) +
+                               thread[i]->timer->get_scaled_difference(device->out_samplerate);
+       }
+       return 0;
+}
+
+int AudioOSS::interrupt_playback()
+{
+//printf("AudioOSS::interrupt_playback 1\n");
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if(thread[i]) {
+                       thread[i]->done = 1;
+                       thread[i]->input_lock->unlock();
+                       thread[i]->write_lock->unlock();
+                       thread[i]->cancel();
+               }
+       }
+//printf("AudioOSS::interrupt_playback 100\n");
+       return 0;
+}
+
+int AudioOSS::read_buffer(char *buffer, int bytes)
+{
+       int sample_size = device->get_ibits() / 8;
+       int out_frame_size = device->get_ichannels() * sample_size;
+       int samples = bytes / out_frame_size;
+
+//printf("AudioOSS::read_buffer 1 %d\n", bytes);
+// Fill temp buffers
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if(thread[i]) {
+                       int in_frame_size = device->get_ichannels() * sample_size;
+
+                       if(data[i] && data_allocated[i] < bytes) {
+                               delete [] data[i];
+                               data[i] = 0;
+                       }
+                       if(!data[i]) {
+                               data[i] = new unsigned char[bytes];
+                               data_allocated[i] = bytes;
+                       }
+
+                       thread[i]->read_data(get_input(i), data[i], samples * in_frame_size);
+               }
+       }
+
+//printf("AudioOSS::read_buffer 1 %d\n", device->get_ibits());
+       for(int i = 0, out_channel = 0; i < MAXDEVICES; i++) {
+               if(thread[i]) {
+                       thread[i]->wait_read();
+
+                       for(int in_channel = 0;
+                               in_channel < device->get_ichannels();
+                               in_channel++) {
+                               int in_frame_size = device->get_ichannels() * sample_size;
+
+                               for(int k = 0; k < samples; k++) {
+                                       for(int l = 0;
+                                               l < sample_size;
+                                               l++) {
+                                               buffer[out_channel * sample_size + k * out_frame_size + l] =
+                                                       data[i][in_channel * sample_size + k * in_frame_size + l];
+                                       }
+                               }
+                               out_channel++;
+                       }
+               }
+       }
+//printf("AudioOSS::read_buffer 2\n");
+       return 0;
+}
+
+int AudioOSS::write_buffer(char *buffer, int bytes)
+{
+       int sample_size = device->get_obits() / 8;
+       int in_frame_size = device->get_ochannels() * sample_size;
+       int samples = bytes / in_frame_size;
+
+       for(int i = 0, in_channel = 0; i < MAXDEVICES; i++) {
+               if( thread[i] ) {
+                       int out_frame_size = device->get_ochannels() * sample_size;
+                       if(data[i] && data_allocated[i] < bytes) {
+                               delete [] data[i];
+                               data[i] = 0;
+                       }
+                       if(!data[i]) {
+                               data[i] = new unsigned char[bytes];
+                               data_allocated[i] = bytes;
+                       }
+
+                       for(int out_channel = 0;
+                               out_channel < device->get_ochannels();
+                               out_channel++) {
+
+                               for(int k = 0; k < samples; k++) {
+                                       for(int l = 0; l < sample_size; l++) {
+                                               data[i][out_channel * sample_size + k * out_frame_size + l] =
+                                                       buffer[in_channel * sample_size + k * in_frame_size + l];
+                                       }
+                               }
+                               in_channel++;
+                       }
+
+                       thread[i]->write_data(get_output(i), data[i], samples * out_frame_size);
+               }
+       }
+       for(int i = 0; i < MAXDEVICES; i++) {
+               if( thread[i] ) thread[i]->wait_write();
+       }
+       return 0;
+}
+
+int AudioOSS::flush_device()
+{
+       for(int i = 0; i < MAXDEVICES; i++)
+               if(thread[i]) ioctl(get_output(i), SNDCTL_DSP_SYNC, 0);
+       return 0;
+}
+
+int AudioOSS::get_output(int number)
+{
+       return device->w ? dsp_out[number] : -1;
+}
+
+int AudioOSS::get_input(int number)
+{
+       return device->r ? dsp_in[number] : -1;
+}
+
+#endif // HAVE_OSS