blob: 54135be989c90cdf6ddded0a12c4e29169d44d75
1 | /* |
2 | * Copyright (c) 2000, 2001, 2002 Fabrice Bellard |
3 | * |
4 | * This file is part of FFmpeg. |
5 | * |
6 | * FFmpeg is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * FFmpeg 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 GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with FFmpeg; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | #include <float.h> |
22 | #include "libavutil/opt.h" |
23 | #include "libavutil/parseutils.h" |
24 | #include "libavutil/avstring.h" |
25 | #include "libavutil/pixdesc.h" |
26 | #include "libavutil/avassert.h" |
27 | |
28 | #include "cmdutils.h" |
29 | #include "ffserver_config.h" |
30 | |
31 | #define MAX_CHILD_ARGS 64 |
32 | |
33 | static int ffserver_save_avoption(const char *opt, const char *arg, int type, |
34 | FFServerConfig *config); |
35 | static void vreport_config_error(const char *filename, int line_num, |
36 | int log_level, int *errors, const char *fmt, |
37 | va_list vl); |
38 | static void report_config_error(const char *filename, int line_num, |
39 | int log_level, int *errors, const char *fmt, |
40 | ...); |
41 | |
42 | #define ERROR(...) report_config_error(config->filename, config->line_num,\ |
43 | AV_LOG_ERROR, &config->errors, __VA_ARGS__) |
44 | #define WARNING(...) report_config_error(config->filename, config->line_num,\ |
45 | AV_LOG_WARNING, &config->warnings, __VA_ARGS__) |
46 | |
47 | /* FIXME: make ffserver work with IPv6 */ |
48 | /* resolve host with also IP address parsing */ |
49 | static int resolve_host(struct in_addr *sin_addr, const char *hostname) |
50 | { |
51 | |
52 | if (!ff_inet_aton(hostname, sin_addr)) { |
53 | #if HAVE_GETADDRINFO |
54 | struct addrinfo *ai, *cur; |
55 | struct addrinfo hints = { 0 }; |
56 | hints.ai_family = AF_INET; |
57 | if (getaddrinfo(hostname, NULL, &hints, &ai)) |
58 | return -1; |
59 | /* getaddrinfo returns a linked list of addrinfo structs. |
60 | * Even if we set ai_family = AF_INET above, make sure |
61 | * that the returned one actually is of the correct type. */ |
62 | for (cur = ai; cur; cur = cur->ai_next) { |
63 | if (cur->ai_family == AF_INET) { |
64 | *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr; |
65 | freeaddrinfo(ai); |
66 | return 0; |
67 | } |
68 | } |
69 | freeaddrinfo(ai); |
70 | return -1; |
71 | #else |
72 | struct hostent *hp; |
73 | hp = gethostbyname(hostname); |
74 | if (!hp) |
75 | return -1; |
76 | memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr)); |
77 | #endif |
78 | } |
79 | return 0; |
80 | } |
81 | |
82 | void ffserver_get_arg(char *buf, int buf_size, const char **pp) |
83 | { |
84 | const char *p; |
85 | char *q; |
86 | int quote = 0; |
87 | |
88 | p = *pp; |
89 | q = buf; |
90 | |
91 | while (av_isspace(*p)) p++; |
92 | |
93 | if (*p == '\"' || *p == '\'') |
94 | quote = *p++; |
95 | |
96 | while (*p != '\0') { |
97 | if (quote && *p == quote || !quote && av_isspace(*p)) |
98 | break; |
99 | if ((q - buf) < buf_size - 1) |
100 | *q++ = *p; |
101 | p++; |
102 | } |
103 | |
104 | *q = '\0'; |
105 | if (quote && *p == quote) |
106 | p++; |
107 | *pp = p; |
108 | } |
109 | |
110 | void ffserver_parse_acl_row(FFServerStream *stream, FFServerStream* feed, |
111 | FFServerIPAddressACL *ext_acl, |
112 | const char *p, const char *filename, int line_num) |
113 | { |
114 | char arg[1024]; |
115 | FFServerIPAddressACL acl; |
116 | FFServerIPAddressACL *nacl; |
117 | FFServerIPAddressACL **naclp; |
118 | |
119 | ffserver_get_arg(arg, sizeof(arg), &p); |
120 | if (av_strcasecmp(arg, "allow") == 0) |
121 | acl.action = IP_ALLOW; |
122 | else if (av_strcasecmp(arg, "deny") == 0) |
123 | acl.action = IP_DENY; |
124 | else { |
125 | fprintf(stderr, "%s:%d: ACL action '%s' should be ALLOW or DENY.\n", |
126 | filename, line_num, arg); |
127 | goto bail; |
128 | } |
129 | |
130 | ffserver_get_arg(arg, sizeof(arg), &p); |
131 | |
132 | if (resolve_host(&acl.first, arg)) { |
133 | fprintf(stderr, |
134 | "%s:%d: ACL refers to invalid host or IP address '%s'\n", |
135 | filename, line_num, arg); |
136 | goto bail; |
137 | } |
138 | |
139 | acl.last = acl.first; |
140 | |
141 | ffserver_get_arg(arg, sizeof(arg), &p); |
142 | |
143 | if (arg[0]) { |
144 | if (resolve_host(&acl.last, arg)) { |
145 | fprintf(stderr, |
146 | "%s:%d: ACL refers to invalid host or IP address '%s'\n", |
147 | filename, line_num, arg); |
148 | goto bail; |
149 | } |
150 | } |
151 | |
152 | nacl = av_mallocz(sizeof(*nacl)); |
153 | if (!nacl) { |
154 | fprintf(stderr, "Failed to allocate FFServerIPAddressACL\n"); |
155 | goto bail; |
156 | } |
157 | |
158 | naclp = 0; |
159 | |
160 | acl.next = 0; |
161 | *nacl = acl; |
162 | |
163 | if (stream) |
164 | naclp = &stream->acl; |
165 | else if (feed) |
166 | naclp = &feed->acl; |
167 | else if (ext_acl) |
168 | naclp = &ext_acl; |
169 | else |
170 | fprintf(stderr, "%s:%d: ACL found not in <Stream> or <Feed>\n", |
171 | filename, line_num); |
172 | |
173 | if (naclp) { |
174 | while (*naclp) |
175 | naclp = &(*naclp)->next; |
176 | |
177 | *naclp = nacl; |
178 | } else |
179 | av_free(nacl); |
180 | |
181 | bail: |
182 | return; |
183 | |
184 | } |
185 | |
186 | /* add a codec and set the default parameters */ |
187 | static void add_codec(FFServerStream *stream, AVCodecContext *av, |
188 | FFServerConfig *config) |
189 | { |
190 | LayeredAVStream *st; |
191 | AVDictionary **opts, *recommended = NULL; |
192 | char *enc_config; |
193 | |
194 | if(stream->nb_streams >= FF_ARRAY_ELEMS(stream->streams)) |
195 | return; |
196 | |
197 | opts = av->codec_type == AVMEDIA_TYPE_AUDIO ? |
198 | &config->audio_opts : &config->video_opts; |
199 | av_dict_copy(&recommended, *opts, 0); |
200 | av_opt_set_dict2(av->priv_data, opts, AV_OPT_SEARCH_CHILDREN); |
201 | av_opt_set_dict2(av, opts, AV_OPT_SEARCH_CHILDREN); |
202 | |
203 | if (av_dict_count(*opts)) |
204 | av_log(NULL, AV_LOG_WARNING, |
205 | "Something is wrong, %d options are not set!\n", |
206 | av_dict_count(*opts)); |
207 | |
208 | if (!config->stream_use_defaults) { |
209 | switch(av->codec_type) { |
210 | case AVMEDIA_TYPE_AUDIO: |
211 | if (av->bit_rate == 0) |
212 | report_config_error(config->filename, config->line_num, |
213 | AV_LOG_ERROR, &config->errors, |
214 | "audio bit rate is not set\n"); |
215 | if (av->sample_rate == 0) |
216 | report_config_error(config->filename, config->line_num, |
217 | AV_LOG_ERROR, &config->errors, |
218 | "audio sample rate is not set\n"); |
219 | break; |
220 | case AVMEDIA_TYPE_VIDEO: |
221 | if (av->width == 0 || av->height == 0) |
222 | report_config_error(config->filename, config->line_num, |
223 | AV_LOG_ERROR, &config->errors, |
224 | "video size is not set\n"); |
225 | break; |
226 | default: |
227 | av_assert0(0); |
228 | } |
229 | goto done; |
230 | } |
231 | |
232 | /* stream_use_defaults = true */ |
233 | |
234 | /* compute default parameters */ |
235 | switch(av->codec_type) { |
236 | case AVMEDIA_TYPE_AUDIO: |
237 | if (!av_dict_get(recommended, "b", NULL, 0)) { |
238 | av->bit_rate = 64000; |
239 | av_dict_set_int(&recommended, "b", av->bit_rate, 0); |
240 | WARNING("Setting default value for audio bit rate = %d. " |
241 | "Use NoDefaults to disable it.\n", |
242 | av->bit_rate); |
243 | } |
244 | if (!av_dict_get(recommended, "ar", NULL, 0)) { |
245 | av->sample_rate = 22050; |
246 | av_dict_set_int(&recommended, "ar", av->sample_rate, 0); |
247 | WARNING("Setting default value for audio sample rate = %d. " |
248 | "Use NoDefaults to disable it.\n", |
249 | av->sample_rate); |
250 | } |
251 | if (!av_dict_get(recommended, "ac", NULL, 0)) { |
252 | av->channels = 1; |
253 | av_dict_set_int(&recommended, "ac", av->channels, 0); |
254 | WARNING("Setting default value for audio channel count = %d. " |
255 | "Use NoDefaults to disable it.\n", |
256 | av->channels); |
257 | } |
258 | break; |
259 | case AVMEDIA_TYPE_VIDEO: |
260 | if (!av_dict_get(recommended, "b", NULL, 0)) { |
261 | av->bit_rate = 64000; |
262 | av_dict_set_int(&recommended, "b", av->bit_rate, 0); |
263 | WARNING("Setting default value for video bit rate = %d. " |
264 | "Use NoDefaults to disable it.\n", |
265 | av->bit_rate); |
266 | } |
267 | if (!av_dict_get(recommended, "time_base", NULL, 0)){ |
268 | av->time_base.den = 5; |
269 | av->time_base.num = 1; |
270 | av_dict_set(&recommended, "time_base", "1/5", 0); |
271 | WARNING("Setting default value for video frame rate = %d. " |
272 | "Use NoDefaults to disable it.\n", |
273 | av->time_base.den); |
274 | } |
275 | if (!av_dict_get(recommended, "video_size", NULL, 0)) { |
276 | av->width = 160; |
277 | av->height = 128; |
278 | av_dict_set(&recommended, "video_size", "160x128", 0); |
279 | WARNING("Setting default value for video size = %dx%d. " |
280 | "Use NoDefaults to disable it.\n", |
281 | av->width, av->height); |
282 | } |
283 | /* Bitrate tolerance is less for streaming */ |
284 | if (!av_dict_get(recommended, "bt", NULL, 0)) { |
285 | av->bit_rate_tolerance = FFMAX(av->bit_rate / 4, |
286 | (int64_t)av->bit_rate*av->time_base.num/av->time_base.den); |
287 | av_dict_set_int(&recommended, "bt", av->bit_rate_tolerance, 0); |
288 | WARNING("Setting default value for video bit rate tolerance = %d. " |
289 | "Use NoDefaults to disable it.\n", |
290 | av->bit_rate_tolerance); |
291 | } |
292 | |
293 | if (!av_dict_get(recommended, "rc_eq", NULL, 0)) { |
294 | av->rc_eq = av_strdup("tex^qComp"); |
295 | av_dict_set(&recommended, "rc_eq", "tex^qComp", 0); |
296 | WARNING("Setting default value for video rate control equation = " |
297 | "%s. Use NoDefaults to disable it.\n", |
298 | av->rc_eq); |
299 | } |
300 | if (!av_dict_get(recommended, "maxrate", NULL, 0)) { |
301 | av->rc_max_rate = av->bit_rate * 2; |
302 | av_dict_set_int(&recommended, "maxrate", av->rc_max_rate, 0); |
303 | WARNING("Setting default value for video max rate = %d. " |
304 | "Use NoDefaults to disable it.\n", |
305 | av->rc_max_rate); |
306 | } |
307 | |
308 | if (av->rc_max_rate && !av_dict_get(recommended, "bufsize", NULL, 0)) { |
309 | av->rc_buffer_size = av->rc_max_rate; |
310 | av_dict_set_int(&recommended, "bufsize", av->rc_buffer_size, 0); |
311 | WARNING("Setting default value for video buffer size = %d. " |
312 | "Use NoDefaults to disable it.\n", |
313 | av->rc_buffer_size); |
314 | } |
315 | break; |
316 | default: |
317 | abort(); |
318 | } |
319 | |
320 | done: |
321 | st = av_mallocz(sizeof(*st)); |
322 | if (!st) |
323 | return; |
324 | av_dict_get_string(recommended, &enc_config, '=', ','); |
325 | av_dict_free(&recommended); |
326 | st->recommended_encoder_configuration = enc_config; |
327 | st->codec = av; |
328 | st->codecpar = avcodec_parameters_alloc(); |
329 | avcodec_parameters_from_context(st->codecpar, av); |
330 | stream->streams[stream->nb_streams++] = st; |
331 | } |
332 | |
333 | static int ffserver_set_codec(AVCodecContext *ctx, const char *codec_name, |
334 | FFServerConfig *config) |
335 | { |
336 | int ret; |
337 | AVCodec *codec = avcodec_find_encoder_by_name(codec_name); |
338 | if (!codec || codec->type != ctx->codec_type) { |
339 | report_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
340 | &config->errors, |
341 | "Invalid codec name: '%s'\n", codec_name); |
342 | return 0; |
343 | } |
344 | if (ctx->codec_id == AV_CODEC_ID_NONE && !ctx->priv_data) { |
345 | if ((ret = avcodec_get_context_defaults3(ctx, codec)) < 0) |
346 | return ret; |
347 | ctx->codec = codec; |
348 | } |
349 | if (ctx->codec_id != codec->id) |
350 | report_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
351 | &config->errors, |
352 | "Inconsistent configuration: trying to set '%s' " |
353 | "codec option, but '%s' codec is used previously\n", |
354 | codec_name, avcodec_get_name(ctx->codec_id)); |
355 | return 0; |
356 | } |
357 | |
358 | static int ffserver_opt_preset(const char *arg, int type, FFServerConfig *config) |
359 | { |
360 | FILE *f=NULL; |
361 | char filename[1000], tmp[1000], tmp2[1000], line[1000]; |
362 | int ret = 0; |
363 | AVCodecContext *avctx; |
364 | const AVCodec *codec; |
365 | |
366 | switch(type) { |
367 | case AV_OPT_FLAG_AUDIO_PARAM: |
368 | avctx = config->dummy_actx; |
369 | break; |
370 | case AV_OPT_FLAG_VIDEO_PARAM: |
371 | avctx = config->dummy_vctx; |
372 | break; |
373 | default: |
374 | av_assert0(0); |
375 | } |
376 | codec = avcodec_find_encoder(avctx->codec_id); |
377 | |
378 | if (!(f = get_preset_file(filename, sizeof(filename), arg, 0, |
379 | codec ? codec->name : NULL))) { |
380 | av_log(NULL, AV_LOG_ERROR, "File for preset '%s' not found\n", arg); |
381 | return AVERROR(EINVAL); |
382 | } |
383 | |
384 | while(!feof(f)){ |
385 | int e= fscanf(f, "%999[^\n]\n", line) - 1; |
386 | if(line[0] == '#' && !e) |
387 | continue; |
388 | e|= sscanf(line, "%999[^=]=%999[^\n]\n", tmp, tmp2) - 2; |
389 | if(e){ |
390 | av_log(NULL, AV_LOG_ERROR, "%s: Invalid syntax: '%s'\n", filename, |
391 | line); |
392 | ret = AVERROR(EINVAL); |
393 | break; |
394 | } |
395 | if (!strcmp(tmp, "acodec") && avctx->codec_type == AVMEDIA_TYPE_AUDIO || |
396 | !strcmp(tmp, "vcodec") && avctx->codec_type == AVMEDIA_TYPE_VIDEO) |
397 | { |
398 | if (ffserver_set_codec(avctx, tmp2, config) < 0) |
399 | break; |
400 | } else if (!strcmp(tmp, "scodec")) { |
401 | av_log(NULL, AV_LOG_ERROR, "Subtitles preset found.\n"); |
402 | ret = AVERROR(EINVAL); |
403 | break; |
404 | } else if (ffserver_save_avoption(tmp, tmp2, type, config) < 0) |
405 | break; |
406 | } |
407 | |
408 | fclose(f); |
409 | |
410 | return ret; |
411 | } |
412 | |
413 | static AVOutputFormat *ffserver_guess_format(const char *short_name, |
414 | const char *filename, |
415 | const char *mime_type) |
416 | { |
417 | AVOutputFormat *fmt = av_guess_format(short_name, filename, mime_type); |
418 | |
419 | if (fmt) { |
420 | AVOutputFormat *stream_fmt; |
421 | char stream_format_name[64]; |
422 | |
423 | snprintf(stream_format_name, sizeof(stream_format_name), "%s_stream", |
424 | fmt->name); |
425 | stream_fmt = av_guess_format(stream_format_name, NULL, NULL); |
426 | |
427 | if (stream_fmt) |
428 | fmt = stream_fmt; |
429 | } |
430 | |
431 | return fmt; |
432 | } |
433 | |
434 | static void vreport_config_error(const char *filename, int line_num, |
435 | int log_level, int *errors, const char *fmt, |
436 | va_list vl) |
437 | { |
438 | av_log(NULL, log_level, "%s:%d: ", filename, line_num); |
439 | av_vlog(NULL, log_level, fmt, vl); |
440 | if (errors) |
441 | (*errors)++; |
442 | } |
443 | |
444 | static void report_config_error(const char *filename, int line_num, |
445 | int log_level, int *errors, |
446 | const char *fmt, ...) |
447 | { |
448 | va_list vl; |
449 | va_start(vl, fmt); |
450 | vreport_config_error(filename, line_num, log_level, errors, fmt, vl); |
451 | va_end(vl); |
452 | } |
453 | |
454 | static int ffserver_set_int_param(int *dest, const char *value, int factor, |
455 | int min, int max, FFServerConfig *config, |
456 | const char *error_msg, ...) |
457 | { |
458 | int tmp; |
459 | char *tailp; |
460 | if (!value || !value[0]) |
461 | goto error; |
462 | errno = 0; |
463 | tmp = strtol(value, &tailp, 0); |
464 | if (tmp < min || tmp > max) |
465 | goto error; |
466 | if (factor) { |
467 | if (tmp == INT_MIN || FFABS(tmp) > INT_MAX / FFABS(factor)) |
468 | goto error; |
469 | tmp *= factor; |
470 | } |
471 | if (tailp[0] || errno) |
472 | goto error; |
473 | if (dest) |
474 | *dest = tmp; |
475 | return 0; |
476 | error: |
477 | if (config) { |
478 | va_list vl; |
479 | va_start(vl, error_msg); |
480 | vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
481 | &config->errors, error_msg, vl); |
482 | va_end(vl); |
483 | } |
484 | return AVERROR(EINVAL); |
485 | } |
486 | |
487 | static int ffserver_set_float_param(float *dest, const char *value, |
488 | float factor, float min, float max, |
489 | FFServerConfig *config, |
490 | const char *error_msg, ...) |
491 | { |
492 | double tmp; |
493 | char *tailp; |
494 | if (!value || !value[0]) |
495 | goto error; |
496 | errno = 0; |
497 | tmp = strtod(value, &tailp); |
498 | if (tmp < min || tmp > max) |
499 | goto error; |
500 | if (factor) |
501 | tmp *= factor; |
502 | if (tailp[0] || errno) |
503 | goto error; |
504 | if (dest) |
505 | *dest = tmp; |
506 | return 0; |
507 | error: |
508 | if (config) { |
509 | va_list vl; |
510 | va_start(vl, error_msg); |
511 | vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
512 | &config->errors, error_msg, vl); |
513 | va_end(vl); |
514 | } |
515 | return AVERROR(EINVAL); |
516 | } |
517 | |
518 | static int ffserver_save_avoption(const char *opt, const char *arg, int type, |
519 | FFServerConfig *config) |
520 | { |
521 | static int hinted = 0; |
522 | int ret = 0; |
523 | AVDictionaryEntry *e; |
524 | const AVOption *o = NULL; |
525 | const char *option = NULL; |
526 | const char *codec_name = NULL; |
527 | char buff[1024]; |
528 | AVCodecContext *ctx; |
529 | AVDictionary **dict; |
530 | enum AVCodecID guessed_codec_id; |
531 | |
532 | switch (type) { |
533 | case AV_OPT_FLAG_VIDEO_PARAM: |
534 | ctx = config->dummy_vctx; |
535 | dict = &config->video_opts; |
536 | guessed_codec_id = config->guessed_video_codec_id != AV_CODEC_ID_NONE ? |
537 | config->guessed_video_codec_id : AV_CODEC_ID_H264; |
538 | break; |
539 | case AV_OPT_FLAG_AUDIO_PARAM: |
540 | ctx = config->dummy_actx; |
541 | dict = &config->audio_opts; |
542 | guessed_codec_id = config->guessed_audio_codec_id != AV_CODEC_ID_NONE ? |
543 | config->guessed_audio_codec_id : AV_CODEC_ID_AAC; |
544 | break; |
545 | default: |
546 | av_assert0(0); |
547 | } |
548 | |
549 | if (strchr(opt, ':')) { |
550 | //explicit private option |
551 | snprintf(buff, sizeof(buff), "%s", opt); |
552 | codec_name = buff; |
553 | if(!(option = strchr(buff, ':'))){ |
554 | report_config_error(config->filename, config->line_num, |
555 | AV_LOG_ERROR, &config->errors, |
556 | "Syntax error. Unmatched ':'\n"); |
557 | return -1; |
558 | |
559 | } |
560 | buff[option - buff] = '\0'; |
561 | option++; |
562 | if ((ret = ffserver_set_codec(ctx, codec_name, config)) < 0) |
563 | return ret; |
564 | if (!ctx->codec || !ctx->priv_data) |
565 | return -1; |
566 | } else { |
567 | option = opt; |
568 | } |
569 | |
570 | o = av_opt_find(ctx, option, NULL, type | AV_OPT_FLAG_ENCODING_PARAM, |
571 | AV_OPT_SEARCH_CHILDREN); |
572 | if (!o && |
573 | (!strcmp(option, "time_base") || !strcmp(option, "pixel_format") || |
574 | !strcmp(option, "video_size") || !strcmp(option, "codec_tag"))) |
575 | o = av_opt_find(ctx, option, NULL, 0, 0); |
576 | if (!o) { |
577 | report_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
578 | &config->errors, "Option not found: '%s'\n", opt); |
579 | if (!hinted && ctx->codec_id == AV_CODEC_ID_NONE) { |
580 | hinted = 1; |
581 | report_config_error(config->filename, config->line_num, |
582 | AV_LOG_ERROR, NULL, "If '%s' is a codec private" |
583 | "option, then prefix it with codec name, for " |
584 | "example '%s:%s %s' or define codec earlier.\n", |
585 | opt, avcodec_get_name(guessed_codec_id) ,opt, |
586 | arg); |
587 | } |
588 | } else if ((ret = av_opt_set(ctx, option, arg, AV_OPT_SEARCH_CHILDREN)) < 0) { |
589 | report_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
590 | &config->errors, "Invalid value for option %s (%s): %s\n", opt, |
591 | arg, av_err2str(ret)); |
592 | } else if ((e = av_dict_get(*dict, option, NULL, 0))) { |
593 | if ((o->type == AV_OPT_TYPE_FLAGS) && arg && |
594 | (arg[0] == '+' || arg[0] == '-')) |
595 | return av_dict_set(dict, option, arg, AV_DICT_APPEND); |
596 | report_config_error(config->filename, config->line_num, AV_LOG_ERROR, |
597 | &config->errors, "Redeclaring value of option '%s'." |
598 | "Previous value was: '%s'.\n", opt, e->value); |
599 | } else if (av_dict_set(dict, option, arg, 0) < 0) { |
600 | return AVERROR(ENOMEM); |
601 | } |
602 | return 0; |
603 | } |
604 | |
605 | static int ffserver_save_avoption_int(const char *opt, int64_t arg, |
606 | int type, FFServerConfig *config) |
607 | { |
608 | char buf[22]; |
609 | snprintf(buf, sizeof(buf), "%"PRId64, arg); |
610 | return ffserver_save_avoption(opt, buf, type, config); |
611 | } |
612 | |
613 | static int ffserver_parse_config_global(FFServerConfig *config, const char *cmd, |
614 | const char **p) |
615 | { |
616 | int val; |
617 | char arg[1024]; |
618 | if (!av_strcasecmp(cmd, "Port") || !av_strcasecmp(cmd, "HTTPPort")) { |
619 | if (!av_strcasecmp(cmd, "Port")) |
620 | WARNING("Port option is deprecated. Use HTTPPort instead.\n"); |
621 | ffserver_get_arg(arg, sizeof(arg), p); |
622 | ffserver_set_int_param(&val, arg, 0, 1, 65535, config, |
623 | "Invalid port: %s\n", arg); |
624 | if (val < 1024) |
625 | WARNING("Trying to use IETF assigned system port: '%d'\n", val); |
626 | config->http_addr.sin_port = htons(val); |
627 | } else if (!av_strcasecmp(cmd, "HTTPBindAddress") || |
628 | !av_strcasecmp(cmd, "BindAddress")) { |
629 | if (!av_strcasecmp(cmd, "BindAddress")) |
630 | WARNING("BindAddress option is deprecated. Use HTTPBindAddress " |
631 | "instead.\n"); |
632 | ffserver_get_arg(arg, sizeof(arg), p); |
633 | if (resolve_host(&config->http_addr.sin_addr, arg)) |
634 | ERROR("Invalid host/IP address: '%s'\n", arg); |
635 | } else if (!av_strcasecmp(cmd, "NoDaemon")) { |
636 | WARNING("NoDaemon option has no effect. You should remove it.\n"); |
637 | } else if (!av_strcasecmp(cmd, "RTSPPort")) { |
638 | ffserver_get_arg(arg, sizeof(arg), p); |
639 | ffserver_set_int_param(&val, arg, 0, 1, 65535, config, |
640 | "Invalid port: %s\n", arg); |
641 | config->rtsp_addr.sin_port = htons(val); |
642 | } else if (!av_strcasecmp(cmd, "RTSPBindAddress")) { |
643 | ffserver_get_arg(arg, sizeof(arg), p); |
644 | if (resolve_host(&config->rtsp_addr.sin_addr, arg)) |
645 | ERROR("Invalid host/IP address: %s\n", arg); |
646 | } else if (!av_strcasecmp(cmd, "MaxHTTPConnections")) { |
647 | ffserver_get_arg(arg, sizeof(arg), p); |
648 | ffserver_set_int_param(&val, arg, 0, 1, 65535, config, |
649 | "Invalid MaxHTTPConnections: %s\n", arg); |
650 | config->nb_max_http_connections = val; |
651 | if (config->nb_max_connections > config->nb_max_http_connections) { |
652 | ERROR("Inconsistent configuration: MaxClients(%d) > " |
653 | "MaxHTTPConnections(%d)\n", config->nb_max_connections, |
654 | config->nb_max_http_connections); |
655 | } |
656 | } else if (!av_strcasecmp(cmd, "MaxClients")) { |
657 | ffserver_get_arg(arg, sizeof(arg), p); |
658 | ffserver_set_int_param(&val, arg, 0, 1, 65535, config, |
659 | "Invalid MaxClients: '%s'\n", arg); |
660 | config->nb_max_connections = val; |
661 | if (config->nb_max_connections > config->nb_max_http_connections) { |
662 | ERROR("Inconsistent configuration: MaxClients(%d) > " |
663 | "MaxHTTPConnections(%d)\n", config->nb_max_connections, |
664 | config->nb_max_http_connections); |
665 | } |
666 | } else if (!av_strcasecmp(cmd, "MaxBandwidth")) { |
667 | int64_t llval; |
668 | char *tailp; |
669 | ffserver_get_arg(arg, sizeof(arg), p); |
670 | errno = 0; |
671 | llval = strtoll(arg, &tailp, 10); |
672 | if (llval < 10 || llval > 10000000 || tailp[0] || errno) |
673 | ERROR("Invalid MaxBandwidth: '%s'\n", arg); |
674 | else |
675 | config->max_bandwidth = llval; |
676 | } else if (!av_strcasecmp(cmd, "CustomLog")) { |
677 | if (!config->debug) { |
678 | ffserver_get_arg(config->logfilename, sizeof(config->logfilename), |
679 | p); |
680 | } |
681 | } else if (!av_strcasecmp(cmd, "LoadModule")) { |
682 | ERROR("Loadable modules are no longer supported\n"); |
683 | } else if (!av_strcasecmp(cmd, "NoDefaults")) { |
684 | config->use_defaults = 0; |
685 | } else if (!av_strcasecmp(cmd, "UseDefaults")) { |
686 | config->use_defaults = 1; |
687 | } else |
688 | ERROR("Incorrect keyword: '%s'\n", cmd); |
689 | return 0; |
690 | } |
691 | |
692 | static int ffserver_parse_config_feed(FFServerConfig *config, const char *cmd, |
693 | const char **p, FFServerStream **pfeed) |
694 | { |
695 | FFServerStream *feed; |
696 | char arg[1024]; |
697 | av_assert0(pfeed); |
698 | feed = *pfeed; |
699 | if (!av_strcasecmp(cmd, "<Feed")) { |
700 | char *q; |
701 | FFServerStream *s; |
702 | feed = av_mallocz(sizeof(FFServerStream)); |
703 | if (!feed) |
704 | return AVERROR(ENOMEM); |
705 | ffserver_get_arg(feed->filename, sizeof(feed->filename), p); |
706 | q = strrchr(feed->filename, '>'); |
707 | if (*q) |
708 | *q = '\0'; |
709 | |
710 | for (s = config->first_feed; s; s = s->next) { |
711 | if (!strcmp(feed->filename, s->filename)) |
712 | ERROR("Feed '%s' already registered\n", s->filename); |
713 | } |
714 | |
715 | feed->fmt = av_guess_format("ffm", NULL, NULL); |
716 | /* default feed file */ |
717 | snprintf(feed->feed_filename, sizeof(feed->feed_filename), |
718 | "/tmp/%s.ffm", feed->filename); |
719 | feed->feed_max_size = 5 * 1024 * 1024; |
720 | feed->is_feed = 1; |
721 | feed->feed = feed; /* self feeding :-) */ |
722 | *pfeed = feed; |
723 | return 0; |
724 | } |
725 | av_assert0(feed); |
726 | if (!av_strcasecmp(cmd, "Launch")) { |
727 | int i; |
728 | |
729 | feed->child_argv = av_mallocz_array(MAX_CHILD_ARGS, sizeof(char *)); |
730 | if (!feed->child_argv) |
731 | return AVERROR(ENOMEM); |
732 | for (i = 0; i < MAX_CHILD_ARGS - 2; i++) { |
733 | ffserver_get_arg(arg, sizeof(arg), p); |
734 | if (!arg[0]) |
735 | break; |
736 | |
737 | feed->child_argv[i] = av_strdup(arg); |
738 | if (!feed->child_argv[i]) |
739 | return AVERROR(ENOMEM); |
740 | } |
741 | |
742 | feed->child_argv[i] = |
743 | av_asprintf("http://%s:%d/%s", |
744 | (config->http_addr.sin_addr.s_addr == INADDR_ANY) ? |
745 | "127.0.0.1" : inet_ntoa(config->http_addr.sin_addr), |
746 | ntohs(config->http_addr.sin_port), feed->filename); |
747 | if (!feed->child_argv[i]) |
748 | return AVERROR(ENOMEM); |
749 | } else if (!av_strcasecmp(cmd, "ACL")) { |
750 | ffserver_parse_acl_row(NULL, feed, NULL, *p, config->filename, |
751 | config->line_num); |
752 | } else if (!av_strcasecmp(cmd, "File") || |
753 | !av_strcasecmp(cmd, "ReadOnlyFile")) { |
754 | ffserver_get_arg(feed->feed_filename, sizeof(feed->feed_filename), p); |
755 | feed->readonly = !av_strcasecmp(cmd, "ReadOnlyFile"); |
756 | } else if (!av_strcasecmp(cmd, "Truncate")) { |
757 | ffserver_get_arg(arg, sizeof(arg), p); |
758 | /* assume Truncate is true in case no argument is specified */ |
759 | if (!arg[0]) { |
760 | feed->truncate = 1; |
761 | } else { |
762 | WARNING("Truncate N syntax in configuration file is deprecated. " |
763 | "Use Truncate alone with no arguments.\n"); |
764 | feed->truncate = strtod(arg, NULL); |
765 | } |
766 | } else if (!av_strcasecmp(cmd, "FileMaxSize")) { |
767 | char *p1; |
768 | double fsize; |
769 | |
770 | ffserver_get_arg(arg, sizeof(arg), p); |
771 | p1 = arg; |
772 | fsize = strtod(p1, &p1); |
773 | switch(av_toupper(*p1)) { |
774 | case 'K': |
775 | fsize *= 1024; |
776 | break; |
777 | case 'M': |
778 | fsize *= 1024 * 1024; |
779 | break; |
780 | case 'G': |
781 | fsize *= 1024 * 1024 * 1024; |
782 | break; |
783 | default: |
784 | ERROR("Invalid file size: '%s'\n", arg); |
785 | break; |
786 | } |
787 | feed->feed_max_size = (int64_t)fsize; |
788 | if (feed->feed_max_size < FFM_PACKET_SIZE*4) { |
789 | ERROR("Feed max file size is too small. Must be at least %d.\n", |
790 | FFM_PACKET_SIZE*4); |
791 | } |
792 | } else if (!av_strcasecmp(cmd, "</Feed>")) { |
793 | *pfeed = NULL; |
794 | } else { |
795 | ERROR("Invalid entry '%s' inside <Feed></Feed>\n", cmd); |
796 | } |
797 | return 0; |
798 | } |
799 | |
800 | static int ffserver_parse_config_stream(FFServerConfig *config, const char *cmd, |
801 | const char **p, |
802 | FFServerStream **pstream) |
803 | { |
804 | char arg[1024], arg2[1024]; |
805 | FFServerStream *stream; |
806 | int val; |
807 | |
808 | av_assert0(pstream); |
809 | stream = *pstream; |
810 | |
811 | if (!av_strcasecmp(cmd, "<Stream")) { |
812 | char *q; |
813 | FFServerStream *s; |
814 | stream = av_mallocz(sizeof(FFServerStream)); |
815 | if (!stream) |
816 | return AVERROR(ENOMEM); |
817 | config->dummy_actx = avcodec_alloc_context3(NULL); |
818 | config->dummy_vctx = avcodec_alloc_context3(NULL); |
819 | if (!config->dummy_vctx || !config->dummy_actx) { |
820 | av_free(stream); |
821 | avcodec_free_context(&config->dummy_vctx); |
822 | avcodec_free_context(&config->dummy_actx); |
823 | return AVERROR(ENOMEM); |
824 | } |
825 | config->dummy_actx->codec_type = AVMEDIA_TYPE_AUDIO; |
826 | config->dummy_vctx->codec_type = AVMEDIA_TYPE_VIDEO; |
827 | ffserver_get_arg(stream->filename, sizeof(stream->filename), p); |
828 | q = strrchr(stream->filename, '>'); |
829 | if (q) |
830 | *q = '\0'; |
831 | |
832 | for (s = config->first_stream; s; s = s->next) { |
833 | if (!strcmp(stream->filename, s->filename)) |
834 | ERROR("Stream '%s' already registered\n", s->filename); |
835 | } |
836 | |
837 | stream->fmt = ffserver_guess_format(NULL, stream->filename, NULL); |
838 | if (stream->fmt) { |
839 | config->guessed_audio_codec_id = stream->fmt->audio_codec; |
840 | config->guessed_video_codec_id = stream->fmt->video_codec; |
841 | } else { |
842 | config->guessed_audio_codec_id = AV_CODEC_ID_NONE; |
843 | config->guessed_video_codec_id = AV_CODEC_ID_NONE; |
844 | } |
845 | config->stream_use_defaults = config->use_defaults; |
846 | *pstream = stream; |
847 | return 0; |
848 | } |
849 | av_assert0(stream); |
850 | if (!av_strcasecmp(cmd, "Feed")) { |
851 | FFServerStream *sfeed; |
852 | ffserver_get_arg(arg, sizeof(arg), p); |
853 | sfeed = config->first_feed; |
854 | while (sfeed) { |
855 | if (!strcmp(sfeed->filename, arg)) |
856 | break; |
857 | sfeed = sfeed->next_feed; |
858 | } |
859 | if (!sfeed) |
860 | ERROR("Feed with name '%s' for stream '%s' is not defined\n", arg, |
861 | stream->filename); |
862 | else |
863 | stream->feed = sfeed; |
864 | } else if (!av_strcasecmp(cmd, "Format")) { |
865 | ffserver_get_arg(arg, sizeof(arg), p); |
866 | if (!strcmp(arg, "status")) { |
867 | stream->stream_type = STREAM_TYPE_STATUS; |
868 | stream->fmt = NULL; |
869 | } else { |
870 | stream->stream_type = STREAM_TYPE_LIVE; |
871 | /* JPEG cannot be used here, so use single frame MJPEG */ |
872 | if (!strcmp(arg, "jpeg")) { |
873 | strcpy(arg, "singlejpeg"); |
874 | stream->single_frame=1; |
875 | } |
876 | stream->fmt = ffserver_guess_format(arg, NULL, NULL); |
877 | if (!stream->fmt) |
878 | ERROR("Unknown Format: '%s'\n", arg); |
879 | } |
880 | if (stream->fmt) { |
881 | config->guessed_audio_codec_id = stream->fmt->audio_codec; |
882 | config->guessed_video_codec_id = stream->fmt->video_codec; |
883 | } |
884 | } else if (!av_strcasecmp(cmd, "InputFormat")) { |
885 | ffserver_get_arg(arg, sizeof(arg), p); |
886 | stream->ifmt = av_find_input_format(arg); |
887 | if (!stream->ifmt) |
888 | ERROR("Unknown input format: '%s'\n", arg); |
889 | } else if (!av_strcasecmp(cmd, "FaviconURL")) { |
890 | if (stream->stream_type == STREAM_TYPE_STATUS) |
891 | ffserver_get_arg(stream->feed_filename, |
892 | sizeof(stream->feed_filename), p); |
893 | else |
894 | ERROR("FaviconURL only permitted for status streams\n"); |
895 | } else if (!av_strcasecmp(cmd, "Author") || |
896 | !av_strcasecmp(cmd, "Comment") || |
897 | !av_strcasecmp(cmd, "Copyright") || |
898 | !av_strcasecmp(cmd, "Title")) { |
899 | char key[32]; |
900 | int i; |
901 | ffserver_get_arg(arg, sizeof(arg), p); |
902 | for (i = 0; i < strlen(cmd); i++) |
903 | key[i] = av_tolower(cmd[i]); |
904 | key[i] = 0; |
905 | WARNING("Deprecated '%s' option in configuration file. Use " |
906 | "'Metadata %s VALUE' instead.\n", cmd, key); |
907 | if (av_dict_set(&stream->metadata, key, arg, 0) < 0) |
908 | goto nomem; |
909 | } else if (!av_strcasecmp(cmd, "Metadata")) { |
910 | ffserver_get_arg(arg, sizeof(arg), p); |
911 | ffserver_get_arg(arg2, sizeof(arg2), p); |
912 | if (av_dict_set(&stream->metadata, arg, arg2, 0) < 0) |
913 | goto nomem; |
914 | } else if (!av_strcasecmp(cmd, "Preroll")) { |
915 | ffserver_get_arg(arg, sizeof(arg), p); |
916 | stream->prebuffer = atof(arg) * 1000; |
917 | } else if (!av_strcasecmp(cmd, "StartSendOnKey")) { |
918 | stream->send_on_key = 1; |
919 | } else if (!av_strcasecmp(cmd, "AudioCodec")) { |
920 | ffserver_get_arg(arg, sizeof(arg), p); |
921 | ffserver_set_codec(config->dummy_actx, arg, config); |
922 | } else if (!av_strcasecmp(cmd, "VideoCodec")) { |
923 | ffserver_get_arg(arg, sizeof(arg), p); |
924 | ffserver_set_codec(config->dummy_vctx, arg, config); |
925 | } else if (!av_strcasecmp(cmd, "MaxTime")) { |
926 | ffserver_get_arg(arg, sizeof(arg), p); |
927 | stream->max_time = atof(arg) * 1000; |
928 | } else if (!av_strcasecmp(cmd, "AudioBitRate")) { |
929 | float f; |
930 | ffserver_get_arg(arg, sizeof(arg), p); |
931 | ffserver_set_float_param(&f, arg, 1000, -FLT_MAX, FLT_MAX, config, |
932 | "Invalid %s: '%s'\n", cmd, arg); |
933 | if (ffserver_save_avoption_int("b", (int64_t)lrintf(f), |
934 | AV_OPT_FLAG_AUDIO_PARAM, config) < 0) |
935 | goto nomem; |
936 | } else if (!av_strcasecmp(cmd, "AudioChannels")) { |
937 | ffserver_get_arg(arg, sizeof(arg), p); |
938 | if (ffserver_save_avoption("ac", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0) |
939 | goto nomem; |
940 | } else if (!av_strcasecmp(cmd, "AudioSampleRate")) { |
941 | ffserver_get_arg(arg, sizeof(arg), p); |
942 | if (ffserver_save_avoption("ar", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0) |
943 | goto nomem; |
944 | } else if (!av_strcasecmp(cmd, "VideoBitRateRange")) { |
945 | int minrate, maxrate; |
946 | char *dash; |
947 | ffserver_get_arg(arg, sizeof(arg), p); |
948 | dash = strchr(arg, '-'); |
949 | if (dash) { |
950 | *dash = '\0'; |
951 | dash++; |
952 | if (ffserver_set_int_param(&minrate, arg, 1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0 && |
953 | ffserver_set_int_param(&maxrate, dash, 1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0) { |
954 | if (ffserver_save_avoption_int("minrate", minrate, AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || |
955 | ffserver_save_avoption_int("maxrate", maxrate, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
956 | goto nomem; |
957 | } |
958 | } else |
959 | ERROR("Incorrect format for VideoBitRateRange. It should be " |
960 | "<min>-<max>: '%s'.\n", arg); |
961 | } else if (!av_strcasecmp(cmd, "Debug")) { |
962 | ffserver_get_arg(arg, sizeof(arg), p); |
963 | if (ffserver_save_avoption("debug", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 || |
964 | ffserver_save_avoption("debug", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
965 | goto nomem; |
966 | } else if (!av_strcasecmp(cmd, "Strict")) { |
967 | ffserver_get_arg(arg, sizeof(arg), p); |
968 | if (ffserver_save_avoption("strict", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 || |
969 | ffserver_save_avoption("strict", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
970 | goto nomem; |
971 | } else if (!av_strcasecmp(cmd, "VideoBufferSize")) { |
972 | ffserver_get_arg(arg, sizeof(arg), p); |
973 | ffserver_set_int_param(&val, arg, 8*1024, 0, INT_MAX, config, |
974 | "Invalid %s: '%s'", cmd, arg); |
975 | if (ffserver_save_avoption_int("bufsize", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
976 | goto nomem; |
977 | } else if (!av_strcasecmp(cmd, "VideoBitRateTolerance")) { |
978 | ffserver_get_arg(arg, sizeof(arg), p); |
979 | ffserver_set_int_param(&val, arg, 1000, INT_MIN, INT_MAX, config, |
980 | "Invalid %s: '%s'", cmd, arg); |
981 | if (ffserver_save_avoption_int("bt", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
982 | goto nomem; |
983 | } else if (!av_strcasecmp(cmd, "VideoBitRate")) { |
984 | ffserver_get_arg(arg, sizeof(arg), p); |
985 | ffserver_set_int_param(&val, arg, 1000, INT_MIN, INT_MAX, config, |
986 | "Invalid %s: '%s'", cmd, arg); |
987 | if (ffserver_save_avoption_int("b", val, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
988 | goto nomem; |
989 | } else if (!av_strcasecmp(cmd, "VideoSize")) { |
990 | int ret, w, h; |
991 | ffserver_get_arg(arg, sizeof(arg), p); |
992 | ret = av_parse_video_size(&w, &h, arg); |
993 | if (ret < 0) |
994 | ERROR("Invalid video size '%s'\n", arg); |
995 | else { |
996 | if (w % 2 || h % 2) |
997 | WARNING("Image size is not a multiple of 2\n"); |
998 | if (ffserver_save_avoption("video_size", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
999 | goto nomem; |
1000 | } |
1001 | } else if (!av_strcasecmp(cmd, "VideoFrameRate")) { |
1002 | ffserver_get_arg(&arg[2], sizeof(arg) - 2, p); |
1003 | arg[0] = '1'; arg[1] = '/'; |
1004 | if (ffserver_save_avoption("time_base", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1005 | goto nomem; |
1006 | } else if (!av_strcasecmp(cmd, "PixelFormat")) { |
1007 | enum AVPixelFormat pix_fmt; |
1008 | ffserver_get_arg(arg, sizeof(arg), p); |
1009 | pix_fmt = av_get_pix_fmt(arg); |
1010 | if (pix_fmt == AV_PIX_FMT_NONE) |
1011 | ERROR("Unknown pixel format: '%s'\n", arg); |
1012 | else if (ffserver_save_avoption("pixel_format", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1013 | goto nomem; |
1014 | } else if (!av_strcasecmp(cmd, "VideoGopSize")) { |
1015 | ffserver_get_arg(arg, sizeof(arg), p); |
1016 | if (ffserver_save_avoption("g", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1017 | goto nomem; |
1018 | } else if (!av_strcasecmp(cmd, "VideoIntraOnly")) { |
1019 | if (ffserver_save_avoption("g", "1", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1020 | goto nomem; |
1021 | } else if (!av_strcasecmp(cmd, "VideoHighQuality")) { |
1022 | if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1023 | goto nomem; |
1024 | } else if (!av_strcasecmp(cmd, "Video4MotionVector")) { |
1025 | if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || //FIXME remove |
1026 | ffserver_save_avoption("flags", "+mv4", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1027 | goto nomem; |
1028 | } else if (!av_strcasecmp(cmd, "AVOptionVideo") || |
1029 | !av_strcasecmp(cmd, "AVOptionAudio")) { |
1030 | int ret; |
1031 | ffserver_get_arg(arg, sizeof(arg), p); |
1032 | ffserver_get_arg(arg2, sizeof(arg2), p); |
1033 | if (!av_strcasecmp(cmd, "AVOptionVideo")) |
1034 | ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_VIDEO_PARAM, |
1035 | config); |
1036 | else |
1037 | ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_AUDIO_PARAM, |
1038 | config); |
1039 | if (ret < 0) |
1040 | goto nomem; |
1041 | } else if (!av_strcasecmp(cmd, "AVPresetVideo") || |
1042 | !av_strcasecmp(cmd, "AVPresetAudio")) { |
1043 | ffserver_get_arg(arg, sizeof(arg), p); |
1044 | if (!av_strcasecmp(cmd, "AVPresetVideo")) |
1045 | ffserver_opt_preset(arg, AV_OPT_FLAG_VIDEO_PARAM, config); |
1046 | else |
1047 | ffserver_opt_preset(arg, AV_OPT_FLAG_AUDIO_PARAM, config); |
1048 | } else if (!av_strcasecmp(cmd, "VideoTag")) { |
1049 | ffserver_get_arg(arg, sizeof(arg), p); |
1050 | if (strlen(arg) == 4 && |
1051 | ffserver_save_avoption_int("codec_tag", |
1052 | MKTAG(arg[0], arg[1], arg[2], arg[3]), |
1053 | AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1054 | goto nomem; |
1055 | } else if (!av_strcasecmp(cmd, "BitExact")) { |
1056 | config->bitexact = 1; |
1057 | if (ffserver_save_avoption("flags", "+bitexact", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1058 | goto nomem; |
1059 | } else if (!av_strcasecmp(cmd, "DctFastint")) { |
1060 | if (ffserver_save_avoption("dct", "fastint", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1061 | goto nomem; |
1062 | } else if (!av_strcasecmp(cmd, "IdctSimple")) { |
1063 | if (ffserver_save_avoption("idct", "simple", AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1064 | goto nomem; |
1065 | } else if (!av_strcasecmp(cmd, "Qscale")) { |
1066 | ffserver_get_arg(arg, sizeof(arg), p); |
1067 | ffserver_set_int_param(&val, arg, 0, INT_MIN, INT_MAX, config, |
1068 | "Invalid Qscale: '%s'\n", arg); |
1069 | if (ffserver_save_avoption("flags", "+qscale", AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || |
1070 | ffserver_save_avoption_int("global_quality", FF_QP2LAMBDA * val, |
1071 | AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1072 | goto nomem; |
1073 | } else if (!av_strcasecmp(cmd, "VideoQDiff")) { |
1074 | ffserver_get_arg(arg, sizeof(arg), p); |
1075 | if (ffserver_save_avoption("qdiff", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1076 | goto nomem; |
1077 | } else if (!av_strcasecmp(cmd, "VideoQMax")) { |
1078 | ffserver_get_arg(arg, sizeof(arg), p); |
1079 | if (ffserver_save_avoption("qmax", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1080 | goto nomem; |
1081 | } else if (!av_strcasecmp(cmd, "VideoQMin")) { |
1082 | ffserver_get_arg(arg, sizeof(arg), p); |
1083 | if (ffserver_save_avoption("qmin", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1084 | goto nomem; |
1085 | } else if (!av_strcasecmp(cmd, "LumiMask")) { |
1086 | ffserver_get_arg(arg, sizeof(arg), p); |
1087 | if (ffserver_save_avoption("lumi_mask", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1088 | goto nomem; |
1089 | } else if (!av_strcasecmp(cmd, "DarkMask")) { |
1090 | ffserver_get_arg(arg, sizeof(arg), p); |
1091 | if (ffserver_save_avoption("dark_mask", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0) |
1092 | goto nomem; |
1093 | } else if (!av_strcasecmp(cmd, "NoVideo")) { |
1094 | config->no_video = 1; |
1095 | } else if (!av_strcasecmp(cmd, "NoAudio")) { |
1096 | config->no_audio = 1; |
1097 | } else if (!av_strcasecmp(cmd, "ACL")) { |
1098 | ffserver_parse_acl_row(stream, NULL, NULL, *p, config->filename, |
1099 | config->line_num); |
1100 | } else if (!av_strcasecmp(cmd, "DynamicACL")) { |
1101 | ffserver_get_arg(stream->dynamic_acl, sizeof(stream->dynamic_acl), p); |
1102 | } else if (!av_strcasecmp(cmd, "RTSPOption")) { |
1103 | ffserver_get_arg(arg, sizeof(arg), p); |
1104 | av_freep(&stream->rtsp_option); |
1105 | stream->rtsp_option = av_strdup(arg); |
1106 | } else if (!av_strcasecmp(cmd, "MulticastAddress")) { |
1107 | ffserver_get_arg(arg, sizeof(arg), p); |
1108 | if (resolve_host(&stream->multicast_ip, arg)) |
1109 | ERROR("Invalid host/IP address: '%s'\n", arg); |
1110 | stream->is_multicast = 1; |
1111 | stream->loop = 1; /* default is looping */ |
1112 | } else if (!av_strcasecmp(cmd, "MulticastPort")) { |
1113 | ffserver_get_arg(arg, sizeof(arg), p); |
1114 | ffserver_set_int_param(&val, arg, 0, 1, 65535, config, |
1115 | "Invalid MulticastPort: '%s'\n", arg); |
1116 | stream->multicast_port = val; |
1117 | } else if (!av_strcasecmp(cmd, "MulticastTTL")) { |
1118 | ffserver_get_arg(arg, sizeof(arg), p); |
1119 | ffserver_set_int_param(&val, arg, 0, INT_MIN, INT_MAX, config, |
1120 | "Invalid MulticastTTL: '%s'\n", arg); |
1121 | stream->multicast_ttl = val; |
1122 | } else if (!av_strcasecmp(cmd, "NoLoop")) { |
1123 | stream->loop = 0; |
1124 | } else if (!av_strcasecmp(cmd, "</Stream>")) { |
1125 | config->stream_use_defaults &= 1; |
1126 | if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm")) { |
1127 | if (config->dummy_actx->codec_id == AV_CODEC_ID_NONE) |
1128 | config->dummy_actx->codec_id = config->guessed_audio_codec_id; |
1129 | if (!config->no_audio && |
1130 | config->dummy_actx->codec_id != AV_CODEC_ID_NONE) { |
1131 | AVCodecContext *audio_enc = avcodec_alloc_context3(avcodec_find_encoder(config->dummy_actx->codec_id)); |
1132 | add_codec(stream, audio_enc, config); |
1133 | } |
1134 | if (config->dummy_vctx->codec_id == AV_CODEC_ID_NONE) |
1135 | config->dummy_vctx->codec_id = config->guessed_video_codec_id; |
1136 | if (!config->no_video && |
1137 | config->dummy_vctx->codec_id != AV_CODEC_ID_NONE) { |
1138 | AVCodecContext *video_enc = avcodec_alloc_context3(avcodec_find_encoder(config->dummy_vctx->codec_id)); |
1139 | add_codec(stream, video_enc, config); |
1140 | } |
1141 | } |
1142 | av_dict_free(&config->video_opts); |
1143 | av_dict_free(&config->audio_opts); |
1144 | avcodec_free_context(&config->dummy_vctx); |
1145 | avcodec_free_context(&config->dummy_actx); |
1146 | config->no_video = 0; |
1147 | config->no_audio = 0; |
1148 | *pstream = NULL; |
1149 | } else if (!av_strcasecmp(cmd, "File") || |
1150 | !av_strcasecmp(cmd, "ReadOnlyFile")) { |
1151 | ffserver_get_arg(stream->feed_filename, sizeof(stream->feed_filename), |
1152 | p); |
1153 | } else if (!av_strcasecmp(cmd, "UseDefaults")) { |
1154 | if (config->stream_use_defaults > 1) |
1155 | WARNING("Multiple UseDefaults/NoDefaults entries.\n"); |
1156 | config->stream_use_defaults = 3; |
1157 | } else if (!av_strcasecmp(cmd, "NoDefaults")) { |
1158 | if (config->stream_use_defaults > 1) |
1159 | WARNING("Multiple UseDefaults/NoDefaults entries.\n"); |
1160 | config->stream_use_defaults = 2; |
1161 | } else { |
1162 | ERROR("Invalid entry '%s' inside <Stream></Stream>\n", cmd); |
1163 | } |
1164 | return 0; |
1165 | nomem: |
1166 | av_log(NULL, AV_LOG_ERROR, "Out of memory. Aborting.\n"); |
1167 | av_dict_free(&config->video_opts); |
1168 | av_dict_free(&config->audio_opts); |
1169 | avcodec_free_context(&config->dummy_vctx); |
1170 | avcodec_free_context(&config->dummy_actx); |
1171 | return AVERROR(ENOMEM); |
1172 | } |
1173 | |
1174 | static int ffserver_parse_config_redirect(FFServerConfig *config, |
1175 | const char *cmd, const char **p, |
1176 | FFServerStream **predirect) |
1177 | { |
1178 | FFServerStream *redirect; |
1179 | av_assert0(predirect); |
1180 | redirect = *predirect; |
1181 | |
1182 | if (!av_strcasecmp(cmd, "<Redirect")) { |
1183 | char *q; |
1184 | redirect = av_mallocz(sizeof(FFServerStream)); |
1185 | if (!redirect) |
1186 | return AVERROR(ENOMEM); |
1187 | |
1188 | ffserver_get_arg(redirect->filename, sizeof(redirect->filename), p); |
1189 | q = strrchr(redirect->filename, '>'); |
1190 | if (*q) |
1191 | *q = '\0'; |
1192 | redirect->stream_type = STREAM_TYPE_REDIRECT; |
1193 | *predirect = redirect; |
1194 | return 0; |
1195 | } |
1196 | av_assert0(redirect); |
1197 | if (!av_strcasecmp(cmd, "URL")) { |
1198 | ffserver_get_arg(redirect->feed_filename, |
1199 | sizeof(redirect->feed_filename), p); |
1200 | } else if (!av_strcasecmp(cmd, "</Redirect>")) { |
1201 | if (!redirect->feed_filename[0]) |
1202 | ERROR("No URL found for <Redirect>\n"); |
1203 | *predirect = NULL; |
1204 | } else { |
1205 | ERROR("Invalid entry '%s' inside <Redirect></Redirect>\n", cmd); |
1206 | } |
1207 | return 0; |
1208 | } |
1209 | |
1210 | int ffserver_parse_ffconfig(const char *filename, FFServerConfig *config) |
1211 | { |
1212 | FILE *f; |
1213 | char line[1024]; |
1214 | char cmd[64]; |
1215 | const char *p; |
1216 | FFServerStream **last_stream, *stream = NULL, *redirect = NULL; |
1217 | FFServerStream **last_feed, *feed = NULL; |
1218 | int ret = 0; |
1219 | |
1220 | av_assert0(config); |
1221 | |
1222 | f = fopen(filename, "r"); |
1223 | if (!f) { |
1224 | ret = AVERROR(errno); |
1225 | av_log(NULL, AV_LOG_ERROR, |
1226 | "Could not open the configuration file '%s'\n", filename); |
1227 | return ret; |
1228 | } |
1229 | |
1230 | config->first_stream = NULL; |
1231 | config->first_feed = NULL; |
1232 | config->errors = config->warnings = 0; |
1233 | |
1234 | last_stream = &config->first_stream; |
1235 | last_feed = &config->first_feed; |
1236 | |
1237 | config->line_num = 0; |
1238 | while (fgets(line, sizeof(line), f) != NULL) { |
1239 | config->line_num++; |
1240 | p = line; |
1241 | while (av_isspace(*p)) |
1242 | p++; |
1243 | if (*p == '\0' || *p == '#') |
1244 | continue; |
1245 | |
1246 | ffserver_get_arg(cmd, sizeof(cmd), &p); |
1247 | |
1248 | if (feed || !av_strcasecmp(cmd, "<Feed")) { |
1249 | int opening = !av_strcasecmp(cmd, "<Feed"); |
1250 | if (opening && (stream || feed || redirect)) { |
1251 | ERROR("Already in a tag\n"); |
1252 | } else { |
1253 | ret = ffserver_parse_config_feed(config, cmd, &p, &feed); |
1254 | if (ret < 0) |
1255 | break; |
1256 | if (opening) { |
1257 | /* add in stream & feed list */ |
1258 | *last_stream = feed; |
1259 | *last_feed = feed; |
1260 | last_stream = &feed->next; |
1261 | last_feed = &feed->next_feed; |
1262 | } |
1263 | } |
1264 | } else if (stream || !av_strcasecmp(cmd, "<Stream")) { |
1265 | int opening = !av_strcasecmp(cmd, "<Stream"); |
1266 | if (opening && (stream || feed || redirect)) { |
1267 | ERROR("Already in a tag\n"); |
1268 | } else { |
1269 | ret = ffserver_parse_config_stream(config, cmd, &p, &stream); |
1270 | if (ret < 0) |
1271 | break; |
1272 | if (opening) { |
1273 | /* add in stream list */ |
1274 | *last_stream = stream; |
1275 | last_stream = &stream->next; |
1276 | } |
1277 | } |
1278 | } else if (redirect || !av_strcasecmp(cmd, "<Redirect")) { |
1279 | int opening = !av_strcasecmp(cmd, "<Redirect"); |
1280 | if (opening && (stream || feed || redirect)) |
1281 | ERROR("Already in a tag\n"); |
1282 | else { |
1283 | ret = ffserver_parse_config_redirect(config, cmd, &p, |
1284 | &redirect); |
1285 | if (ret < 0) |
1286 | break; |
1287 | if (opening) { |
1288 | /* add in stream list */ |
1289 | *last_stream = redirect; |
1290 | last_stream = &redirect->next; |
1291 | } |
1292 | } |
1293 | } else { |
1294 | ffserver_parse_config_global(config, cmd, &p); |
1295 | } |
1296 | } |
1297 | if (stream || feed || redirect) |
1298 | ERROR("Missing closing </%s> tag\n", |
1299 | stream ? "Stream" : (feed ? "Feed" : "Redirect")); |
1300 | |
1301 | fclose(f); |
1302 | if (ret < 0) |
1303 | return ret; |
1304 | if (config->errors) |
1305 | return AVERROR(EINVAL); |
1306 | else |
1307 | return 0; |
1308 | } |
1309 | |
1310 | #undef ERROR |
1311 | #undef WARNING |
1312 | |
1313 | void ffserver_free_child_args(void *argsp) |
1314 | { |
1315 | int i; |
1316 | char **args; |
1317 | if (!argsp) |
1318 | return; |
1319 | args = *(char ***)argsp; |
1320 | if (!args) |
1321 | return; |
1322 | for (i = 0; i < MAX_CHILD_ARGS; i++) |
1323 | av_free(args[i]); |
1324 | av_freep(argsp); |
1325 | } |
1326 |