Doxify the write subsystem
[paraslash.git] / write.c
1 /*
2  * Copyright (C) 2005-2006 Andre Noll <maan@systemlinux.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 #include "para.h"
20 #include "string.h"
21 #include "write.cmdline.h"
22 #include "write.h"
23 #include "write_common.h"
24 #include "fd.h"
25
26 #include <sys/time.h> /* gettimeofday */
27
28 #include "error.h"
29
30 #define WAV_HEADER_LEN 44
31
32 static char *audiobuf;
33 static struct timeval *start_time;
34 struct gengetopt_args_info conf;
35
36 INIT_WRITE_ERRLISTS;
37
38 void para_log(int ll, const char* fmt,...)
39 {
40         va_list argp;
41
42         if (ll < conf.loglevel_arg)
43                 return;
44         va_start(argp, fmt);
45         vfprintf(stderr, fmt, argp);
46         va_end(argp);
47 }
48
49 /**
50  * read WAV_HEADER_LEN bytes from stdin to audio buffer
51  *
52  * \return -E_READ_HDR on errors and on eof before WAV_HEADER_LEN could be
53  * read. A positive return value indicates success.
54  */
55 static int read_wav_header(void)
56 {
57         ssize_t ret, count = 0;
58
59         while (count < WAV_HEADER_LEN) {
60                 ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count);
61                 if (ret <= 0)
62                         return -E_READ_HDR;
63                 count += ret;
64         }
65         return 1;
66 }
67
68 /**
69  * check if current time is later than start_time
70  * \param diff pointer to write remaining time to
71  *
72  * If start_time was not given, or current time is later than given
73  * start_time, return 0. Otherwise, return 1 and write the time
74  * difference between current time and start_time to diff. diff may be
75  * NULL.
76  *
77  */
78 static int start_time_in_future(struct timeval *diff)
79 {
80         struct timeval now;
81
82         if (!conf.start_time_given)
83                 return 0;
84         gettimeofday(&now, NULL);
85         return tv_diff(start_time, &now, diff) > 0? 1 : 0;
86 }
87
88 /**
89  * sleep until time given at command line
90  *
91  * This is called if the initial buffer is filled. It returns
92  * immediately if no start_time was given at the command line
93  * or if the given start time is in the past.
94  *
95  */
96 static void do_initial_delay(struct timeval *delay)
97 {
98         do
99                 para_select(1, NULL, NULL, delay);
100         while (start_time_in_future(delay));
101 }
102
103 static int read_stdin(char *buf, size_t bytes_to_load, size_t *loaded)
104 {
105         ssize_t ret;
106
107         while (*loaded < bytes_to_load) {
108                 ret = read(STDIN_FILENO, buf + *loaded, bytes_to_load - *loaded);
109                 if (ret <= 0) {
110                         if (ret < 0)
111                                 ret = -E_READ_STDIN;
112                         return ret;
113                 }
114                 *loaded += ret;
115         }
116         return 1;
117 }
118 /**
119  * play raw pcm data
120  * \param loaded number of bytes already loaded
121  *
122  * If start_time was given, prebuffer data until buffer is full or
123  * start_time is reached. In any case, do not start playing before
124  * start_time.
125  *
126  * \return positive on success, negative on errors.
127  */
128 static int pcm_write(struct writer_node_group *wng, size_t loaded)
129 {
130         size_t bufsize, prebuf_size, bytes_to_load;
131         struct timeval delay;
132         int ret, not_yet_started = 1;
133
134         ret = wng_open(wng);
135         if (ret < 0)
136                 goto out;
137         PARA_INFO_LOG("max chunk_bytes: %zd\n", wng->max_chunk_bytes);
138         bufsize = (conf.bufsize_arg * 1024 / wng->max_chunk_bytes)
139                 * wng->max_chunk_bytes;
140         audiobuf = para_realloc(audiobuf, bufsize);
141         prebuf_size = conf.prebuffer_arg * bufsize / 100;
142         bytes_to_load =  PARA_MAX(prebuf_size, wng->max_chunk_bytes);
143         ret = read_stdin(audiobuf, bytes_to_load, &loaded);
144         if (ret <= 0 || loaded < bytes_to_load) {
145                 if (ret >= 0)
146                         ret = -E_PREMATURE_END;
147                 goto out;
148         }
149         if (not_yet_started && start_time && start_time_in_future(&delay))
150                 do_initial_delay(&delay);
151         not_yet_started = 0;
152 again:
153         ret = wng_write(wng, audiobuf, &loaded);
154         if (ret <= 0)
155                 goto out;
156         ret = -E_WRITE_OVERRUN;
157         if (loaded >= bufsize)
158                 goto out;
159         bytes_to_load = PARA_MIN(wng->max_chunk_bytes, bufsize);
160         ret = read_stdin(audiobuf, bytes_to_load, &loaded);
161         if (ret < 0)
162                 goto out;
163         if (!ret)
164                 wng->eof = 1;
165         goto again;
166 out:
167         wng_close(wng);
168         return ret;
169 }
170
171 static struct writer_node_group *check_args(void)
172 {
173         int i, ret = -E_WRITE_SYNTAX;
174         static struct timeval tv;
175         struct writer_node_group *wng = NULL;
176
177         if (conf.list_writers_given) {
178                 char *msg = NULL;
179                 FOR_EACH_WRITER(i) {
180                         char *tmp = make_message("%s%s%s",
181                                 i? msg : "",
182                                 i? " " : "",
183                                 writer_names[i]);
184                         free(msg);
185                         msg = tmp;
186                 }
187                 fprintf(stderr, "%s\n", msg);
188                 free(msg);
189                 exit(EXIT_SUCCESS);
190         }
191         if (conf.prebuffer_arg < 0 || conf.prebuffer_arg > 100)
192                 goto out;
193         if (conf.start_time_given) {
194                 long unsigned sec, usec;
195                 if (sscanf(conf.start_time_arg, "%lu:%lu",
196                                 &sec, &usec) != 2)
197                         goto out;
198                 tv.tv_sec = sec;
199                 tv.tv_usec = usec;
200                 start_time = &tv;
201         }
202         if (!conf.writer_given) {
203                 wng = setup_default_wng();
204                 ret = 1;
205                 goto out;
206         }
207         wng = wng_new(conf.writer_given);
208         for (i = 0; i < conf.writer_given; i++) {
209                 ret = check_writer_arg(conf.writer_arg[i]);
210                 if (ret < 0)
211                         goto out;
212                 wng->writer_nodes[i].writer = &writers[ret];
213         }
214         ret = 1;
215 out:
216         if (ret > 0)
217                 return wng;
218         free(wng);
219         return NULL;
220 }
221
222 /**
223  * test if audio buffer contains a valid wave header
224  *
225  * \return If not, return 0, otherwise, store number of channels and sample rate
226  * in struct conf and return WAV_HEADER_LEN.
227  */
228 static size_t check_wave(void)
229 {
230         unsigned char *a = (unsigned char*)audiobuf;
231         if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F')
232                 return WAV_HEADER_LEN;
233         conf.channels_arg = (unsigned) a[22];
234         conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24);
235         return 0;
236 }
237
238 int main(int argc, char *argv[])
239 {
240         int ret = -E_WRITE_SYNTAX;
241         struct writer_node_group *wng = NULL;
242
243         cmdline_parser(argc, argv, &conf);
244         wng = check_args();
245         if (!wng)
246                 goto out;
247         init_supported_writers();
248         audiobuf = para_malloc(WAV_HEADER_LEN);
249         ret = read_wav_header();
250         if (ret < 0)
251                 goto out;
252         ret = pcm_write(wng, check_wave());
253 out:
254         wng_destroy(wng);
255         free(audiobuf);
256         if (ret < 0)
257                 PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret));
258         return ret;
259 }