1 /*
2  * Copyright (C) 2004 2005, Magnus Hjorth
3  *
4  * This file is part of mhWaveEdit.
5  *
6  * mhWaveEdit is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * mhWaveEdit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with mhWaveEdit; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <errno.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <sys/fcntl.h>
26 #include <sys/ioctl.h>
27 #include <sys/audio.h>
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include "gettext.h"
31 
32 /* Constant for output endian-ness */
33 /* IS_BIGENDIAN = same as CPU, TRUE = Always big endian,
34  * FALSE = Always little endian */
35 #define SUNAUD_OUTPUT_ENDIAN IS_BIGENDIAN
36 
37 static gchar *sunaud_dev;
38 static int sunaud_fd = -1;
39 static Ringbuf *sunaud_output_buffer;
40 static gboolean sunaud_output_drained;
41 static Dataformat sunaud_format;
42 static int sunaud_input_overruns;
43 
44 static void sunaud_output_flush(void);
45 
sunaud_init(gboolean silent)46 static gboolean sunaud_init(gboolean silent)
47 {
48      sunaud_dev = getenv("AUDIODEV");
49      if (!sunaud_dev) sunaud_dev = "/dev/audio";
50      sunaud_output_buffer = ringbuf_new(
51 	  inifile_get_guint32(INI_SETTING_SOUNDBUFSIZE,
52 			      INI_SETTING_SOUNDBUFSIZE_DEFAULT));
53      return TRUE;
54 }
55 
sunaud_quit(void)56 static void sunaud_quit(void)
57 {
58      ringbuf_free(sunaud_output_buffer);
59 }
60 
sunaud_select_format(gboolean rec,Dataformat * format,gboolean silent)61 static gboolean sunaud_select_format(gboolean rec, Dataformat *format,
62 				     gboolean silent)
63 {
64      audio_info_t info;
65      audio_prinfo_t *prinfo;
66      gchar *c;
67      g_assert(sunaud_fd == -1);
68      if (format->type == DATAFORMAT_FLOAT || !format->sign ||
69 	 XOR(format->bigendian,SUNAUD_OUTPUT_ENDIAN)) return TRUE;
70      sunaud_fd = open(sunaud_dev,rec?O_RDONLY:O_WRONLY,O_NONBLOCK);
71      if (sunaud_fd == -1) {
72 	  c = g_strdup_printf(_("SunAudio: Couldn't open %s"),sunaud_dev);
73 	  user_perror(c);
74 	  g_free(c);
75 	  return TRUE;
76      }
77      AUDIO_INITINFO(&info);
78      if (rec) prinfo = &(info.record); else prinfo = &(info.play);
79      prinfo->sample_rate = format->samplerate;
80      prinfo->channels = format->channels;
81      prinfo->precision = format->samplesize * 8;
82      prinfo->encoding = AUDIO_ENCODING_LINEAR;
83      if (ioctl(sunaud_fd, AUDIO_SETINFO, &info) < 0) {
84 	  close(sunaud_fd);
85 	  sunaud_fd = -1;
86 	  return TRUE;
87      }
88      memcpy(&sunaud_format,format,sizeof(Dataformat));
89      if (!rec) {
90 	  ringbuf_drain(sunaud_output_buffer);
91 	  sunaud_output_drained = TRUE;
92      }
93      else sunaud_input_overruns = 0;
94      return FALSE;
95 }
96 
sunaud_output_select_format(Dataformat * format,gboolean silent)97 static gint sunaud_output_select_format(Dataformat *format, gboolean silent)
98 {
99      return sunaud_select_format(FALSE,format,silent) ? -1 : 0;
100 }
101 
sunaud_output_suggest_format(Dataformat * format,Dataformat * result)102 static gboolean sunaud_output_suggest_format(Dataformat *format,
103 					     Dataformat *result)
104 {
105      if (format->type == DATAFORMAT_FLOAT) return FALSE;
106 
107      memcpy(result,format,sizeof(Dataformat));
108      result->sign = TRUE;
109      result->bigendian = SUNAUD_OUTPUT_ENDIAN;
110      return TRUE;
111 }
112 
sunaud_input_select_format(Dataformat * format,gboolean silent)113 static gint sunaud_input_select_format(Dataformat *format, gboolean silent)
114 {
115      return sunaud_select_format(TRUE,format,silent) ? -1 : 0;
116 }
117 
sunaud_output_stop(gboolean must_flush)118 static gboolean sunaud_output_stop(gboolean must_flush)
119 {
120      if (must_flush)
121 	  while (sunaud_fd >= 0 && !ringbuf_isempty(sunaud_output_buffer)) {
122 	       sunaud_output_flush();
123 	       do_yield(TRUE);
124 	  }
125      if (sunaud_fd > 0) close(sunaud_fd);
126      sunaud_fd = -1;
127      return must_flush;
128 }
129 
sunaud_output_want_data(void)130 static gboolean sunaud_output_want_data(void)
131 {
132      static int call_count = 0;
133      call_count++;
134      if ((call_count & 4) == 0) sunaud_output_flush();
135      return !ringbuf_isfull(sunaud_output_buffer);
136 }
137 
sunaud_output_flush(void)138 static void sunaud_output_flush(void)
139 {
140      static gchar buf[512];
141      static int bufsize=0,bufpos=0;
142      int loopcount = 0;
143      ssize_t sst;
144      if (sunaud_output_drained) {
145 	  bufsize = bufpos = 0;
146 	  sunaud_output_drained = FALSE;
147      }
148      for (; loopcount < 5; loopcount++) {
149 	  if (bufpos == bufsize) {
150 	       bufsize = ringbuf_dequeue(sunaud_output_buffer,buf,sizeof(buf));
151 	       bufpos = 0;
152 	  }
153 	  if (bufsize == 0 || sunaud_fd < 0) return;
154 	  sst = write(sunaud_fd,buf+bufpos,bufsize-bufpos);
155 	  if (sst == 0) return;
156 	  if (sst < 0) {
157 	       if (errno == EAGAIN || errno == EBUSY || errno == EWOULDBLOCK)
158 		    return;
159 	       console_perror(_("SunAudio: Error writing to sound device"));
160 	       close(sunaud_fd);
161 	       sunaud_fd = -1;
162 	       return;
163 	  }
164 	  bufpos += sst;
165      }
166 }
167 
sunaud_output_play(gchar * buffer,guint bufsize)168 static guint sunaud_output_play(gchar *buffer, guint bufsize)
169 {
170      guint32 ui,r;
171      if (ringbuf_freespace(sunaud_output_buffer) < bufsize) {
172 	  sunaud_output_flush();
173 	  ui = ringbuf_freespace(sunaud_output_buffer);
174 	  if (ui > bufsize) ui = bufsize;
175 	  ui -= ui % sunaud_format.samplebytes;
176      } else
177 	  ui = bufsize;
178      r = ringbuf_enqueue(sunaud_output_buffer,buffer,ui);
179      g_assert(r == ui);
180      sunaud_output_flush();
181      return r;
182 }
183 
sunaud_output_play_min_delay_time(void)184 static gfloat sunaud_output_play_min_delay_time(void)
185 {
186      return 0.0;
187 }
188 
sunaud_output_passive_buffer(void)189 static guint32 sunaud_output_passive_buffer(void)
190 {
191      return ringbuf_available(sunaud_output_buffer);
192 }
193 
sunaud_output_clear_buffers(void)194 static void sunaud_output_clear_buffers(void)
195 {
196      ringbuf_drain(sunaud_output_buffer);
197      sunaud_output_drained = TRUE;
198      ioctl(sunaud_fd,AUDIO_DRAIN,NULL);
199 }
200 
sunaud_input_stop(void)201 static void sunaud_input_stop(void)
202 {
203      if (sunaud_fd >= 0) {
204 	  close(sunaud_fd);
205 	  sunaud_fd = -1;
206      }
207 }
208 
sunaud_input_overrun_check(void)209 static void sunaud_input_overrun_check(void)
210 {
211      static gboolean last_state = FALSE;
212      audio_info_t info;
213      int i;
214      gboolean b;
215      if (ioctl(sunaud_fd,AUDIO_GETINFO,&info) < 0) {
216 	  console_perror("ioctl");
217 	  return;
218      }
219      b = info.record.error != 0;
220      if (!last_state && b) sunaud_input_overruns ++;
221      last_state = b;
222 }
223 
sunaud_input_store(Ringbuf * buffer)224 static void sunaud_input_store(Ringbuf *buffer)
225 {
226      gchar b[4096];
227      guint ui;
228      ssize_t sst;
229      gchar *c;
230      guint32 r;
231      sunaud_input_overrun_check();
232      ui = ringbuf_freespace(buffer);
233      if (ui > sizeof(b)) ui = sizeof(b);
234      ui -= ui % sunaud_format.samplebytes;
235      if (ui == 0) return;
236      if (sunaud_fd >= 0) {
237 	  sst = read(sunaud_fd,b,ui);
238 	  if (sst == -1 && (errno == EBUSY || errno == EAGAIN ||
239 			    errno == EWOULDBLOCK)) return;
240 	  if (sst == -1) {
241 	       user_perror(_("SunAudio: Error reading from sound device"));
242 	       user_error(c);
243 	       g_free(c);
244 	       close(sunaud_fd);
245 	       sunaud_fd = -1;
246 	       return;
247 	  }
248 	  r = ringbuf_enqueue(buffer,b,sst);
249 	  g_assert(r == sst);
250      }
251 }
252 
sunaud_input_overrun_count(void)253 static int sunaud_input_overrun_count(void)
254 {
255      return sunaud_input_overruns;
256 }
257