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