Never start playback at an empty chunk.
[paraslash.git] / afh_recv.c
1 /*
2 * Copyright (C) 2011-2014 Andre Noll <maan@systemlinux.org>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file afh_recv.c Receiver for streaming local files. */
8
9 #include <regex.h>
10 #include <sys/types.h>
11 #include <stdbool.h>
12
13 #include "para.h"
14 #include "error.h"
15 #include "list.h"
16 #include "sched.h"
17 #include "ggo.h"
18 #include "buffer_tree.h"
19 #include "recv.h"
20 #include "afh_recv.cmdline.h"
21 #include "string.h"
22 #include "fd.h"
23 #include "afh.h"
24
25 struct private_afh_recv_data {
26 int fd;
27 void *map;
28 size_t map_size;
29 struct afh_info afhi;
30 int audio_format_num;
31 long unsigned first_chunk;
32 long unsigned last_chunk;
33 struct timeval stream_start;
34 uint32_t current_chunk;
35 };
36
37 static int afh_execute(struct btr_node *btrn, const char *cmd, char **result)
38 {
39 struct receiver_node *rn = btr_context(btrn);
40 struct private_afh_recv_data *pard = rn->private_data;
41
42 *result = NULL;
43 if (!strcmp(cmd, "seconds_total")) {
44 *result = make_message("%lu", pard->afhi.seconds_total);
45 return 1;
46 }
47 if (!strcmp(cmd, "chunks_total")) {
48 *result = make_message("%lu", pard->afhi.chunks_total);
49 return 1;
50 }
51 if (!strcmp(cmd, "afhi")) {
52 afh_get_afhi_txt(pard->audio_format_num, &pard->afhi, result);
53 return 1;
54 }
55 if (!strncmp(cmd, "repos", 5)) {
56 int32_t x;
57 int ret = para_atoi32(cmd + 5, &x);
58 if (ret < 0)
59 return ret;
60 if (x >= pard->afhi.chunks_total)
61 return -ERRNO_TO_PARA_ERROR(EINVAL);
62 pard->first_chunk = afh_get_start_chunk(x, &pard->afhi);
63 pard->current_chunk = pard->first_chunk;
64 rn->task.error = 0;
65 return 1;
66 }
67 return -E_BTR_NAVAIL;
68 }
69
70 static void *afh_recv_parse_config(int argc, char **argv)
71 {
72 struct afh_recv_args_info *tmp = para_calloc(sizeof(*tmp));
73
74 afh_recv_cmdline_parser(argc, argv, tmp);
75 return tmp;
76 }
77
78 static void afh_recv_free_config(void *conf)
79 {
80 if (!conf)
81 return;
82 afh_recv_cmdline_parser_free(conf);
83 free(conf);
84 }
85
86 static int afh_recv_open(struct receiver_node *rn)
87 {
88 struct afh_recv_args_info *conf = rn->conf;
89 struct private_afh_recv_data *pard;
90 struct afh_info *afhi;
91 char *filename = conf->filename_arg;
92
93 int ret;
94
95 if (!filename || *filename == '\0')
96 return -E_AFH_RECV_BAD_FILENAME;
97 rn->private_data = pard = para_calloc(sizeof(*pard));
98 afhi = &pard->afhi;
99 ret = mmap_full_file(filename, O_RDONLY, &pard->map,
100 &pard->map_size, &pard->fd);
101 if (ret < 0)
102 goto out;
103 ret = compute_afhi(filename, pard->map, pard->map_size,
104 pard->fd, afhi);
105 if (ret < 0)
106 goto out_unmap;
107 pard->audio_format_num = ret;
108 ret = -ERRNO_TO_PARA_ERROR(EINVAL);
109 if (afhi->chunks_total == 0)
110 goto out_clear_afhi;
111 if (PARA_ABS(conf->begin_chunk_arg) >= afhi->chunks_total)
112 goto out_clear_afhi;
113 if (conf->begin_chunk_arg >= 0)
114 pard->first_chunk = afh_get_start_chunk(
115 conf->begin_chunk_arg, &pard->afhi);
116 else
117 pard->first_chunk = afh_get_start_chunk(
118 afhi->chunks_total + conf->begin_chunk_arg,
119 &pard->afhi);
120 if (conf->end_chunk_given) {
121 ret = -ERRNO_TO_PARA_ERROR(EINVAL);
122 if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total)
123 goto out_clear_afhi;
124 if (conf->end_chunk_arg >= 0)
125 pard->last_chunk = conf->end_chunk_arg;
126 else
127 pard->last_chunk = afhi->chunks_total + conf->end_chunk_arg;
128 } else
129 pard->last_chunk = afhi->chunks_total - 1;
130 ret = -ERRNO_TO_PARA_ERROR(EINVAL);
131 if (pard->first_chunk >= pard->last_chunk)
132 goto out_clear_afhi;
133 pard->current_chunk = pard->first_chunk;
134 return pard->audio_format_num;
135 out_clear_afhi:
136 clear_afhi(afhi);
137 out_unmap:
138 para_munmap(pard->map, pard->map_size);
139 close(pard->fd);
140 out:
141 freep(&rn->private_data);
142 return ret;
143 }
144
145 static void afh_recv_close(struct receiver_node *rn)
146 {
147 struct private_afh_recv_data *pard;
148
149 if (!rn || !rn->private_data)
150 return;
151 pard = rn->private_data;
152 clear_afhi(&pard->afhi);
153 para_munmap(pard->map, pard->map_size);
154 close(pard->fd);
155 freep(&rn->private_data);
156 }
157
158 static void afh_recv_pre_select(struct sched *s, struct task *t)
159 {
160 struct receiver_node *rn = container_of(t, struct receiver_node, task);
161 struct private_afh_recv_data *pard = rn->private_data;
162 struct afh_info *afhi = &pard->afhi;
163 struct afh_recv_args_info *conf = rn->conf;
164 struct timeval chunk_time;
165 int state = generic_recv_pre_select(s, t);
166
167 if (state <= 0)
168 return;
169 if (!conf->just_in_time_given) {
170 sched_min_delay(s);
171 return;
172 }
173 compute_chunk_time(pard->current_chunk - pard->first_chunk,
174 &afhi->chunk_tv, &pard->stream_start, &chunk_time);
175 sched_request_barrier_or_min_delay(&chunk_time, s);
176 }
177
178 static int afh_recv_post_select(__a_unused struct sched *s, struct task *t)
179 {
180 struct receiver_node *rn = container_of(t, struct receiver_node, task);
181 struct afh_recv_args_info *conf = rn->conf;
182 struct private_afh_recv_data *pard = rn->private_data;
183 struct btr_node *btrn = rn->btrn;
184 struct afh_info *afhi = &pard->afhi;
185 int ret;
186 char *buf;
187 const char *start, *end;
188 size_t size;
189 struct timeval chunk_time;
190
191 ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
192 if (ret <= 0)
193 goto out;
194 if (pard->first_chunk > 0 && !conf->no_header_given) {
195 char *header;
196 afh_get_header(afhi, pard->audio_format_num, pard->map,
197 pard->map_size, &header, &size);
198 if (size > 0) {
199 PARA_INFO_LOG("writing header (%zu bytes)\n", size);
200 buf = para_malloc(size);
201 memcpy(buf, header, size);
202 btr_add_output(buf, size, btrn);
203 afh_free_header(header, pard->audio_format_num);
204 }
205 }
206 if (!conf->just_in_time_given) {
207 afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size);
208 afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size);
209 end += size;
210 PARA_INFO_LOG("adding %zu bytes\n", end - start);
211 btr_add_output_dont_free(start, end - start, btrn);
212 ret = -E_RECV_EOF;
213 goto out;
214 }
215 if (pard->current_chunk == pard->first_chunk)
216 pard->stream_start = *now;
217 else {
218 compute_chunk_time(pard->current_chunk - pard->first_chunk,
219 &afhi->chunk_tv, &pard->stream_start, &chunk_time);
220 ret = tv_diff(&chunk_time, now, NULL);
221 if (ret > 0)
222 goto out;
223 }
224 afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size);
225 PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk);
226 btr_add_output_dont_free(start, size, btrn);
227 if (pard->current_chunk >= pard->last_chunk) {
228 ret = -E_RECV_EOF;
229 goto out;
230 }
231 pard->current_chunk++;
232 ret = 1;
233 out:
234 if (ret < 0) {
235 btr_remove_node(&rn->btrn);
236 pard->current_chunk = pard->first_chunk;
237 }
238 return ret;
239 }
240
241 /**
242 * The init function of the afh receiver.
243 *
244 * \param r Pointer to the receiver struct to initialize.
245 *
246 * This initializes all function pointers of \a r.
247 */
248 void afh_recv_init(struct receiver *r)
249 {
250 struct afh_recv_args_info dummy;
251
252 afh_init();
253 afh_recv_cmdline_parser_init(&dummy);
254 r->open = afh_recv_open;
255 r->close = afh_recv_close;
256 r->pre_select = afh_recv_pre_select;
257 r->post_select = afh_recv_post_select;
258 r->parse_config = afh_recv_parse_config;
259 r->free_config = afh_recv_free_config;
260 r->execute = afh_execute;
261 r->help = (struct ggo_help)DEFINE_GGO_HELP(afh_recv);
262 afh_recv_cmdline_parser_free(&dummy);
263 }