Add execute handler to struct writer.
[paraslash.git] / oss_write.c
1 /*
2  * Copyright (C) 2009 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file oss_write.c Paraslash's oss output plugin. */
8
9 #include <regex.h>
10 #include <sys/ioctl.h>
11 #include <fcntl.h>
12 #include <dirent.h>
13 #include <sys/soundcard.h>
14 #include <stdbool.h>
15
16 #include "para.h"
17 #include "fd.h"
18 #include "string.h"
19 #include "list.h"
20 #include "sched.h"
21 #include "ggo.h"
22 #include "buffer_tree.h"
23 #include "write.h"
24 #include "oss_write.cmdline.h"
25 #include "error.h"
26
27 /** Always use 16 bit little endian. */
28 #define FORMAT AFMT_S16_LE
29
30 /** Data specific to the oss writer. */
31 struct private_oss_write_data {
32         /** The file handle of the device. */
33         int fd;
34         /**
35          * The samplerate given by command line option or the decoder
36          * of the writer node group.
37          */
38         int samplerate;
39         /**
40          * The number of channels, given by command line option or the
41          * decoder of the writer node group.
42          */
43         int channels;
44         /** Four bytes for stereo streams, two bytes for mono streams. */
45         int bytes_per_frame;
46 };
47
48 static int oss_pre_select(struct sched *s, struct writer_node *wn)
49 {
50         struct private_oss_write_data *powd = wn->private_data;
51         struct writer_node_group *wng = wn->wng;
52
53         if (*wng->loaded - wn->written < powd->bytes_per_frame)
54                 return 0;
55         para_fd_set(powd->fd, &s->wfds, &s->max_fileno);
56         return 1;
57 }
58
59 static int oss_post_select(struct sched *s, struct writer_node *wn)
60 {
61         int ret;
62         struct private_oss_write_data *powd = wn->private_data;
63         struct writer_node_group *wng = wn->wng;
64         size_t frames, bytes = *wng->loaded - wn->written;
65         char *data = *wng->bufp + wn->written;
66
67         if (*wng->input_error < 0 && bytes < powd->bytes_per_frame) {
68                 wn->written = *wng->loaded;
69                 return *wng->input_error;
70         }
71         frames = bytes / powd->bytes_per_frame;
72         if (!frames) /* less than a single frame available */
73                 goto out;
74         if (!FD_ISSET(powd->fd, &s->wfds))
75                 goto out;
76         ret = write_nonblock(powd->fd, data, frames * powd->bytes_per_frame, 0);
77         if (ret < 0)
78                 return ret;
79         wn->written += ret;
80 out:
81         return 1;
82 }
83
84 static void oss_close(struct writer_node *wn)
85 {
86         struct private_oss_write_data *powd = wn->private_data;
87
88         close(powd->fd);
89         free(powd);
90 }
91
92 /*
93  * The Open Sound System Programmer's Guide sayeth:
94  *
95  * Set sampling parameters always so that number of channels (mono/stereo) is
96  * set before selecting sampling rate (speed).  Failing to do this will make
97  * your program incompatible with cards such as the SoundBlaster Pro which
98  * supports 44.1 kHz in mono but just 22.05 kHz in stereo. A program which
99  * selects 44.1 kHz speed and then sets the device to stereo mode will
100  * incorrectly believe that the device is still in 44.1 kHz mode when actually
101  * the speed is decreased to 22.05 kHz.
102  */
103 static int oss_open(struct writer_node *wn)
104 {
105         int ret, format = FORMAT, channels, samplerate;
106         struct oss_write_args_info *conf = wn->conf;
107         struct writer_node_group *wng = wn->wng;
108         struct private_oss_write_data *powd;
109
110         PARA_INFO_LOG("opening %s\n", conf->device_arg);
111         ret = para_open(conf->device_arg, O_WRONLY, 0);
112         if (ret < 0)
113                 return ret;
114         powd = para_calloc(sizeof(*powd));
115         wn->private_data = powd;
116         powd->fd = ret;
117         ret = mark_fd_nonblocking(powd->fd);
118         if (ret < 0)
119                 goto err;
120         /* set PCM format */
121         ret = ioctl(powd->fd, SNDCTL_DSP_SETFMT, &format);
122         if (ret < 0) {
123                 ret = -ERRNO_TO_PARA_ERROR(errno);
124                 goto err;
125         }
126         ret = -E_BAD_SAMPLE_FORMAT;
127         if (format != FORMAT)
128                 goto err;
129         /* set number of channels */
130         if (!conf->channels_given && wng->channels)
131                 channels = *wng->channels;
132         else
133                 channels = conf->channels_arg;
134         ret = -E_BAD_CHANNEL_COUNT;
135         if (channels == 0)
136                 goto err;
137         powd->channels = channels;
138         ret = ioctl(powd->fd, SNDCTL_DSP_CHANNELS, &channels);
139         if (ret < 0) {
140                 ret = -ERRNO_TO_PARA_ERROR(errno);
141                 goto err;
142         }
143         if (powd->channels != channels)
144                 goto err;
145         powd->bytes_per_frame = channels * 2;
146
147         /*
148          * Set sampling rate
149          *
150          * If we request a higher sampling rate than is supported by the
151          * device, the the highest possible speed is automatically used. The
152          * value actually used is returned as the new value of the argument.
153          */
154         if (!conf->samplerate_given && wng->samplerate)
155                 samplerate = *wng->samplerate;
156         else
157                 samplerate = conf->samplerate_arg;
158         powd->samplerate = samplerate;
159         ret = ioctl(powd->fd, SNDCTL_DSP_SPEED, &samplerate);
160         if (ret < 0) {
161                 ret = -ERRNO_TO_PARA_ERROR(errno);
162                 goto err;
163         }
164         if (samplerate != powd->samplerate) {
165                 int min = PARA_MIN(samplerate, powd->samplerate),
166                         max = PARA_MAX(samplerate, powd->samplerate);
167                 /*
168                  * Check whether the returned sample rate differs significantly
169                  * from the requested one.
170                  */
171                 ret = -E_BAD_SAMPLERATE;
172                 if (100 * max > 110 * min) /* more than 10% deviation */
173                         goto err;
174                 PARA_NOTICE_LOG("using %dHz rather than %dHz\n", samplerate,
175                         powd->samplerate);
176         }
177
178         return 1;
179 err:
180         close(powd->fd);
181         free(powd);
182         return ret;
183 }
184
185 __malloc static void *oss_parse_config(const char *options)
186 {
187         int ret;
188         struct oss_write_args_info *conf = para_calloc(sizeof(*conf));
189
190         PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t"));
191         ret = oss_cmdline_parser_string(options, conf, "oss_write");
192         if (ret)
193                 goto err_out;
194         return conf;
195 err_out:
196         free(conf);
197         return NULL;
198 }
199
200 /**
201  * The init function of the oss writer.
202  *
203  * \param w Pointer to the writer to initialize.
204  *
205  * \sa struct writer.
206  */
207 void oss_write_init(struct writer *w)
208 {
209         struct oss_write_args_info dummy;
210
211         oss_cmdline_parser_init(&dummy);
212         w->open = oss_open;
213         w->close = oss_close;
214         w->pre_select = oss_pre_select;
215         w->post_select = oss_post_select;
216         w->parse_config = oss_parse_config;
217         w->shutdown = NULL;
218         w->help = (struct ggo_help) {
219                 .short_help = oss_write_args_info_help,
220                 .detailed_help = oss_write_args_info_detailed_help
221         };
222         oss_cmdline_parser_free(&dummy);
223 }