2 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
4 * This file is part of FFmpeg.
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.
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.
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
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"
28 // FIXME those are internal headers, ffserver _really_ shouldn't use them
29 #include "libavformat/ffm.h"
32 #include "ffserver_config.h"
34 #define MAX_CHILD_ARGS 64
36 static int ffserver_save_avoption(const char *opt
, const char *arg
, int type
,
37 FFServerConfig
*config
);
38 static void vreport_config_error(const char *filename
, int line_num
, int log_level
,
39 int *errors
, const char *fmt
, va_list vl
);
40 static void report_config_error(const char *filename
, int line_num
, int log_level
,
41 int *errors
, const char *fmt
, ...);
43 #define ERROR(...) report_config_error(config->filename, config->line_num,\
44 AV_LOG_ERROR, &config->errors, __VA_ARGS__)
45 #define WARNING(...) report_config_error(config->filename, config->line_num,\
46 AV_LOG_WARNING, &config->warnings, __VA_ARGS__)
48 /* FIXME: make ffserver work with IPv6 */
49 /* resolve host with also IP address parsing */
50 static int resolve_host(struct in_addr
*sin_addr
, const char *hostname
)
53 if (!ff_inet_aton(hostname
, sin_addr
)) {
55 struct addrinfo
*ai
, *cur
;
56 struct addrinfo hints
= { 0 };
57 hints
.ai_family
= AF_INET
;
58 if (getaddrinfo(hostname
, NULL
, &hints
, &ai
))
60 /* getaddrinfo returns a linked list of addrinfo structs.
61 * Even if we set ai_family = AF_INET above, make sure
62 * that the returned one actually is of the correct type. */
63 for (cur
= ai
; cur
; cur
= cur
->ai_next
) {
64 if (cur
->ai_family
== AF_INET
) {
65 *sin_addr
= ((struct sockaddr_in
*)cur
->ai_addr
)->sin_addr
;
74 hp
= gethostbyname(hostname
);
77 memcpy(sin_addr
, hp
->h_addr_list
[0], sizeof(struct in_addr
));
83 void ffserver_get_arg(char *buf
, int buf_size
, const char **pp
)
90 while (av_isspace(*p
)) p
++;
93 if (*p
== '\"' || *p
== '\'')
105 if ((q
- buf
) < buf_size
- 1)
110 if (quote
&& *p
== quote
)
115 void ffserver_parse_acl_row(FFServerStream
*stream
, FFServerStream
* feed
,
116 FFServerIPAddressACL
*ext_acl
,
117 const char *p
, const char *filename
, int line_num
)
120 FFServerIPAddressACL acl
;
123 ffserver_get_arg(arg
, sizeof(arg
), &p
);
124 if (av_strcasecmp(arg
, "allow") == 0)
125 acl
.action
= IP_ALLOW
;
126 else if (av_strcasecmp(arg
, "deny") == 0)
127 acl
.action
= IP_DENY
;
129 fprintf(stderr
, "%s:%d: ACL action '%s' is not ALLOW or DENY\n",
130 filename
, line_num
, arg
);
134 ffserver_get_arg(arg
, sizeof(arg
), &p
);
136 if (resolve_host(&acl
.first
, arg
)) {
137 fprintf(stderr
, "%s:%d: ACL refers to invalid host or IP address '%s'\n",
138 filename
, line_num
, arg
);
141 acl
.last
= acl
.first
;
143 ffserver_get_arg(arg
, sizeof(arg
), &p
);
146 if (resolve_host(&acl
.last
, arg
)) {
148 "%s:%d: ACL refers to invalid host or IP address '%s'\n",
149 filename
, line_num
, arg
);
155 FFServerIPAddressACL
*nacl
= av_mallocz(sizeof(*nacl
));
156 FFServerIPAddressACL
**naclp
= 0;
162 naclp
= &stream
->acl
;
168 fprintf(stderr
, "%s:%d: ACL found not in <stream> or <feed>\n",
175 naclp
= &(*naclp
)->next
;
183 /* add a codec and set the default parameters */
184 static void add_codec(FFServerStream
*stream
, AVCodecContext
*av
,
185 FFServerConfig
*config
)
188 AVDictionary
**opts
, *recommended
= NULL
;
191 if(stream
->nb_streams
>= FF_ARRAY_ELEMS(stream
->streams
))
194 opts
= av
->codec_type
== AVMEDIA_TYPE_AUDIO
?
195 &config
->audio_opts
: &config
->video_opts
;
196 av_dict_copy(&recommended
, *opts
, 0);
197 av_opt_set_dict2(av
->priv_data
, opts
, AV_OPT_SEARCH_CHILDREN
);
198 av_opt_set_dict2(av
, opts
, AV_OPT_SEARCH_CHILDREN
);
199 if (av_dict_count(*opts
))
200 av_log(NULL
, AV_LOG_WARNING
,
201 "Something is wrong, %d options are not set!\n", av_dict_count(*opts
));
203 if (config
->stream_use_defaults
) {
205 /* compute default parameters */
206 switch(av
->codec_type
) {
207 case AVMEDIA_TYPE_AUDIO
:
208 if (!av_dict_get(recommended
, "ab", NULL
, 0)) {
209 av
->bit_rate
= 64000;
210 av_dict_set_int(&recommended
, "ab", av
->bit_rate
, 0);
211 WARNING("Setting default value for audio bit rate = %d. "
212 "Use NoDefaults to disable it.\n",
215 if (!av_dict_get(recommended
, "ar", NULL
, 0)) {
216 av
->sample_rate
= 22050;
217 av_dict_set_int(&recommended
, "ar", av
->sample_rate
, 0);
218 WARNING("Setting default value for audio sample rate = %d. "
219 "Use NoDefaults to disable it.\n",
222 if (!av_dict_get(recommended
, "ac", NULL
, 0)) {
224 av_dict_set_int(&recommended
, "ac", av
->channels
, 0);
225 WARNING("Setting default value for audio channel count = %d. "
226 "Use NoDefaults to disable it.\n",
230 case AVMEDIA_TYPE_VIDEO
:
231 if (!av_dict_get(recommended
, "b", NULL
, 0)) {
232 av
->bit_rate
= 64000;
233 av_dict_set_int(&recommended
, "b", av
->bit_rate
, 0);
234 WARNING("Setting default value for video bit rate = %d. "
235 "Use NoDefaults to disable it.\n",
238 if (!av_dict_get(recommended
, "time_base", NULL
, 0)){
239 av
->time_base
.den
= 5;
240 av
->time_base
.num
= 1;
241 av_dict_set(&recommended
, "time_base", "1/5", 0);
242 WARNING("Setting default value for video frame rate = %d. "
243 "Use NoDefaults to disable it.\n",
246 if (!av_dict_get(recommended
, "video_size", NULL
, 0)) {
249 av_dict_set(&recommended
, "video_size", "160x128", 0);
250 WARNING("Setting default value for video size = %dx%d. "
251 "Use NoDefaults to disable it.\n",
252 av
->width
, av
->height
);
254 /* Bitrate tolerance is less for streaming */
255 if (!av_dict_get(recommended
, "bt", NULL
, 0)) {
256 av
->bit_rate_tolerance
= FFMAX(av
->bit_rate
/ 4,
257 (int64_t)av
->bit_rate
*av
->time_base
.num
/av
->time_base
.den
);
258 av_dict_set_int(&recommended
, "bt", av
->bit_rate_tolerance
, 0);
259 WARNING("Setting default value for video bit rate tolerance = %d. "
260 "Use NoDefaults to disable it.\n",
261 av
->bit_rate_tolerance
);
264 if (!av_dict_get(recommended
, "rc_eq", NULL
, 0)) {
265 av
->rc_eq
= av_strdup("tex^qComp");
266 av_dict_set(&recommended
, "rc_eq", "tex^qComp", 0);
267 WARNING("Setting default value for video rate control equation = %s. "
268 "Use NoDefaults to disable it.\n",
271 if (!av_dict_get(recommended
, "maxrate", NULL
, 0)) {
272 av
->rc_max_rate
= av
->bit_rate
* 2;
273 av_dict_set_int(&recommended
, "maxrate", av
->rc_max_rate
, 0);
274 WARNING("Setting default value for video max rate = %d. "
275 "Use NoDefaults to disable it.\n",
279 if (av
->rc_max_rate
&& !av_dict_get(recommended
, "bufsize", NULL
, 0)) {
280 av
->rc_buffer_size
= av
->rc_max_rate
;
281 av_dict_set_int(&recommended
, "bufsize", av
->rc_buffer_size
, 0);
282 WARNING("Setting default value for video buffer size = %d. "
283 "Use NoDefaults to disable it.\n",
291 switch(av
->codec_type
) {
292 case AVMEDIA_TYPE_AUDIO
:
293 if (av
->bit_rate
== 0)
294 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
295 &config
->errors
, "audio bit rate is not set\n");
296 if (av
->sample_rate
== 0)
297 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
298 &config
->errors
, "audio sample rate is not set\n");
300 case AVMEDIA_TYPE_VIDEO
:
301 if (av
->width
== 0 || av
->height
== 0)
302 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
303 &config
->errors
, "video size is not set\n");
310 st
= av_mallocz(sizeof(AVStream
));
313 av_dict_get_string(recommended
, &enc_config
, '=', ',');
314 av_dict_free(&recommended
);
315 av_stream_set_recommended_encoder_configuration(st
, enc_config
);
317 stream
->streams
[stream
->nb_streams
++] = st
;
320 static int ffserver_set_codec(AVCodecContext
*ctx
, const char *codec_name
, FFServerConfig
*config
)
323 AVCodec
*codec
= avcodec_find_encoder_by_name(codec_name
);
324 if (!codec
|| codec
->type
!= ctx
->codec_type
) {
325 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
326 &config
->errors
, "Invalid codec name: %s\n", codec_name
);
329 if (ctx
->codec_id
== AV_CODEC_ID_NONE
&& !ctx
->priv_data
) {
330 if ((ret
= avcodec_get_context_defaults3(ctx
, codec
)) < 0)
334 if (ctx
->codec_id
!= codec
->id
)
335 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
, &config
->errors
,
336 "Inconsistent configuration: trying to set %s codec option, but %s codec is used previously\n",
337 codec_name
, avcodec_get_name(ctx
->codec_id
));
341 static int ffserver_opt_preset(const char *arg
, int type
, FFServerConfig
*config
)
344 char filename
[1000], tmp
[1000], tmp2
[1000], line
[1000];
346 AVCodecContext
*avctx
;
347 const AVCodec
*codec
;
350 case AV_OPT_FLAG_AUDIO_PARAM
:
351 avctx
= config
->dummy_actx
;
353 case AV_OPT_FLAG_VIDEO_PARAM
:
354 avctx
= config
->dummy_vctx
;
359 codec
= avcodec_find_encoder(avctx
->codec_id
);
361 if (!(f
= get_preset_file(filename
, sizeof(filename
), arg
, 0,
362 codec
? codec
->name
: NULL
))) {
363 av_log(NULL
, AV_LOG_ERROR
, "File for preset '%s' not found\n", arg
);
364 return AVERROR(EINVAL
);
368 int e
= fscanf(f
, "%999[^\n]\n", line
) - 1;
369 if(line
[0] == '#' && !e
)
371 e
|= sscanf(line
, "%999[^=]=%999[^\n]\n", tmp
, tmp2
) - 2;
373 av_log(NULL
, AV_LOG_ERROR
, "%s: Invalid syntax: '%s'\n", filename
, line
);
374 ret
= AVERROR(EINVAL
);
377 if ((!strcmp(tmp
, "acodec") && avctx
->codec_type
== AVMEDIA_TYPE_AUDIO
) ||
378 !strcmp(tmp
, "vcodec") && avctx
->codec_type
== AVMEDIA_TYPE_VIDEO
)
380 if (ffserver_set_codec(avctx
, tmp2
, config
) < 0)
382 } else if (!strcmp(tmp
, "scodec")) {
383 av_log(NULL
, AV_LOG_ERROR
, "Subtitles preset found.\n");
384 ret
= AVERROR(EINVAL
);
386 } else if (ffserver_save_avoption(tmp
, tmp2
, type
, config
) < 0)
395 static AVOutputFormat
*ffserver_guess_format(const char *short_name
, const char *filename
, const char *mime_type
)
397 AVOutputFormat
*fmt
= av_guess_format(short_name
, filename
, mime_type
);
400 AVOutputFormat
*stream_fmt
;
401 char stream_format_name
[64];
403 snprintf(stream_format_name
, sizeof(stream_format_name
), "%s_stream",
405 stream_fmt
= av_guess_format(stream_format_name
, NULL
, NULL
);
414 static void vreport_config_error(const char *filename
, int line_num
, int log_level
, int *errors
, const char *fmt
, va_list vl
)
416 av_log(NULL
, log_level
, "%s:%d: ", filename
, line_num
);
417 av_vlog(NULL
, log_level
, fmt
, vl
);
422 static void report_config_error(const char *filename
, int line_num
, int log_level
, int *errors
, const char *fmt
, ...)
426 vreport_config_error(filename
, line_num
, log_level
, errors
, fmt
, vl
);
430 static int ffserver_set_int_param(int *dest
, const char *value
, int factor
, int min
, int max
,
431 FFServerConfig
*config
, const char *error_msg
, ...)
435 if (!value
|| !value
[0])
438 tmp
= strtol(value
, &tailp
, 0);
439 if (tmp
< min
|| tmp
> max
)
442 if (FFABS(tmp
) > INT_MAX
/ FFABS(factor
))
446 if (tailp
[0] || errno
)
454 va_start(vl
, error_msg
);
455 vreport_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
456 &config
->errors
, error_msg
, vl
);
459 return AVERROR(EINVAL
);
462 static int ffserver_set_float_param(float *dest
, const char *value
, float factor
, float min
, float max
,
463 FFServerConfig
*config
, const char *error_msg
, ...)
467 if (!value
|| !value
[0])
470 tmp
= strtod(value
, &tailp
);
471 if (tmp
< min
|| tmp
> max
)
475 if (tailp
[0] || errno
)
483 va_start(vl
, error_msg
);
484 vreport_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
485 &config
->errors
, error_msg
, vl
);
488 return AVERROR(EINVAL
);
491 static int ffserver_save_avoption(const char *opt
, const char *arg
, int type
, FFServerConfig
*config
)
493 static int hinted
= 0;
495 AVDictionaryEntry
*e
;
496 const AVOption
*o
= NULL
;
497 const char *option
= NULL
;
498 const char *codec_name
= NULL
;
502 enum AVCodecID guessed_codec_id
;
505 case AV_OPT_FLAG_VIDEO_PARAM
:
506 ctx
= config
->dummy_vctx
;
507 dict
= &config
->video_opts
;
508 guessed_codec_id
= config
->guessed_video_codec_id
!= AV_CODEC_ID_NONE
?
509 config
->guessed_video_codec_id
: AV_CODEC_ID_H264
;
511 case AV_OPT_FLAG_AUDIO_PARAM
:
512 ctx
= config
->dummy_actx
;
513 dict
= &config
->audio_opts
;
514 guessed_codec_id
= config
->guessed_audio_codec_id
!= AV_CODEC_ID_NONE
?
515 config
->guessed_audio_codec_id
: AV_CODEC_ID_AAC
;
521 if (strchr(opt
, ':')) {
522 //explicit private option
523 snprintf(buff
, sizeof(buff
), "%s", opt
);
525 option
= strchr(buff
, ':');
526 buff
[option
- buff
] = '\0';
528 if ((ret
= ffserver_set_codec(ctx
, codec_name
, config
)) < 0)
530 if (!ctx
->codec
|| !ctx
->priv_data
)
536 o
= av_opt_find(ctx
, option
, NULL
, type
| AV_OPT_FLAG_ENCODING_PARAM
, AV_OPT_SEARCH_CHILDREN
);
537 if (!o
&& (!strcmp(option
, "time_base") || !strcmp(option
, "pixel_format") ||
538 !strcmp(option
, "video_size") || !strcmp(option
, "codec_tag")))
539 o
= av_opt_find(ctx
, option
, NULL
, 0, 0);
541 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
542 &config
->errors
, "Option not found: %s\n", opt
);
543 if (!hinted
&& ctx
->codec_id
== AV_CODEC_ID_NONE
) {
545 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
, NULL
,
546 "If '%s' is a codec private option, then prefix it with codec name, "
547 "for example '%s:%s %s' or define codec earlier.\n",
548 opt
, avcodec_get_name(guessed_codec_id
) ,opt
, arg
);
550 } else if ((ret
= av_opt_set(ctx
, option
, arg
, AV_OPT_SEARCH_CHILDREN
)) < 0) {
551 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
552 &config
->errors
, "Invalid value for option %s (%s): %s\n", opt
,
553 arg
, av_err2str(ret
));
554 } else if ((e
= av_dict_get(*dict
, option
, NULL
, 0))) {
555 if ((o
->type
== AV_OPT_TYPE_FLAGS
) && arg
&& (arg
[0] == '+' || arg
[0] == '-'))
556 return av_dict_set(dict
, option
, arg
, AV_DICT_APPEND
);
557 report_config_error(config
->filename
, config
->line_num
, AV_LOG_ERROR
,
559 "Redeclaring value of the option %s, previous value: %s\n",
561 } else if (av_dict_set(dict
, option
, arg
, 0) < 0) {
562 return AVERROR(ENOMEM
);
567 static int ffserver_save_avoption_int(const char *opt
, int64_t arg
,
568 int type
, FFServerConfig
*config
)
571 snprintf(buf
, sizeof(buf
), "%"PRId64
, arg
);
572 return ffserver_save_avoption(opt
, buf
, type
, config
);
575 static int ffserver_parse_config_global(FFServerConfig
*config
, const char *cmd
,
580 if (!av_strcasecmp(cmd
, "Port") || !av_strcasecmp(cmd
, "HTTPPort")) {
581 if (!av_strcasecmp(cmd
, "Port"))
582 WARNING("Port option is deprecated, use HTTPPort instead\n");
583 ffserver_get_arg(arg
, sizeof(arg
), p
);
584 ffserver_set_int_param(&val
, arg
, 0, 1, 65535, config
,
585 "Invalid port: %s\n", arg
);
587 WARNING("Trying to use IETF assigned system port: %d\n", val
);
588 config
->http_addr
.sin_port
= htons(val
);
589 } else if (!av_strcasecmp(cmd
, "HTTPBindAddress") || !av_strcasecmp(cmd
, "BindAddress")) {
590 if (!av_strcasecmp(cmd
, "BindAddress"))
591 WARNING("BindAddress option is deprecated, use HTTPBindAddress instead\n");
592 ffserver_get_arg(arg
, sizeof(arg
), p
);
593 if (resolve_host(&config
->http_addr
.sin_addr
, arg
))
594 ERROR("Invalid host/IP address: %s\n", arg
);
595 } else if (!av_strcasecmp(cmd
, "NoDaemon")) {
596 WARNING("NoDaemon option has no effect, you should remove it\n");
597 } else if (!av_strcasecmp(cmd
, "RTSPPort")) {
598 ffserver_get_arg(arg
, sizeof(arg
), p
);
599 ffserver_set_int_param(&val
, arg
, 0, 1, 65535, config
,
600 "Invalid port: %s\n", arg
);
601 config
->rtsp_addr
.sin_port
= htons(val
);
602 } else if (!av_strcasecmp(cmd
, "RTSPBindAddress")) {
603 ffserver_get_arg(arg
, sizeof(arg
), p
);
604 if (resolve_host(&config
->rtsp_addr
.sin_addr
, arg
))
605 ERROR("Invalid host/IP address: %s\n", arg
);
606 } else if (!av_strcasecmp(cmd
, "MaxHTTPConnections")) {
607 ffserver_get_arg(arg
, sizeof(arg
), p
);
608 ffserver_set_int_param(&val
, arg
, 0, 1, 65535, config
,
609 "Invalid MaxHTTPConnections: %s\n", arg
);
610 config
->nb_max_http_connections
= val
;
611 if (config
->nb_max_connections
> config
->nb_max_http_connections
)
612 ERROR("Inconsistent configuration: MaxClients(%d) > MaxHTTPConnections(%d)\n",
613 config
->nb_max_connections
, config
->nb_max_http_connections
);
614 } else if (!av_strcasecmp(cmd
, "MaxClients")) {
615 ffserver_get_arg(arg
, sizeof(arg
), p
);
616 ffserver_set_int_param(&val
, arg
, 0, 1, 65535, config
,
617 "Invalid MaxClients: %s\n", arg
);
618 config
->nb_max_connections
= val
;
619 if (config
->nb_max_connections
> config
->nb_max_http_connections
)
620 ERROR("Inconsistent configuration: MaxClients(%d) > MaxHTTPConnections(%d)\n",
621 config
->nb_max_connections
, config
->nb_max_http_connections
);
622 } else if (!av_strcasecmp(cmd
, "MaxBandwidth")) {
625 ffserver_get_arg(arg
, sizeof(arg
), p
);
627 llval
= strtoll(arg
, &tailp
, 10);
628 if (llval
< 10 || llval
> 10000000 || tailp
[0] || errno
)
629 ERROR("Invalid MaxBandwidth: %s\n", arg
);
631 config
->max_bandwidth
= llval
;
632 } else if (!av_strcasecmp(cmd
, "CustomLog")) {
634 ffserver_get_arg(config
->logfilename
, sizeof(config
->logfilename
), p
);
635 } else if (!av_strcasecmp(cmd
, "LoadModule")) {
636 ERROR("Loadable modules are no longer supported\n");
637 } else if (!av_strcasecmp(cmd
, "NoDefaults")) {
638 config
->use_defaults
= 0;
639 } else if (!av_strcasecmp(cmd
, "UseDefaults")) {
640 config
->use_defaults
= 1;
642 ERROR("Incorrect keyword: '%s'\n", cmd
);
646 static int ffserver_parse_config_feed(FFServerConfig
*config
, const char *cmd
, const char **p
,
647 FFServerStream
**pfeed
)
649 FFServerStream
*feed
;
653 if (!av_strcasecmp(cmd
, "<Feed")) {
656 feed
= av_mallocz(sizeof(FFServerStream
));
658 return AVERROR(ENOMEM
);
659 ffserver_get_arg(feed
->filename
, sizeof(feed
->filename
), p
);
660 q
= strrchr(feed
->filename
, '>');
664 for (s
= config
->first_feed
; s
; s
= s
->next
) {
665 if (!strcmp(feed
->filename
, s
->filename
))
666 ERROR("Feed '%s' already registered\n", s
->filename
);
669 feed
->fmt
= av_guess_format("ffm", NULL
, NULL
);
670 /* default feed file */
671 snprintf(feed
->feed_filename
, sizeof(feed
->feed_filename
),
672 "/tmp/%s.ffm", feed
->filename
);
673 feed
->feed_max_size
= 5 * 1024 * 1024;
675 feed
->feed
= feed
; /* self feeding :-) */
680 if (!av_strcasecmp(cmd
, "Launch")) {
683 feed
->child_argv
= av_mallocz_array(MAX_CHILD_ARGS
, sizeof(char *));
684 if (!feed
->child_argv
)
685 return AVERROR(ENOMEM
);
686 for (i
= 0; i
< MAX_CHILD_ARGS
- 2; i
++) {
687 ffserver_get_arg(arg
, sizeof(arg
), p
);
691 feed
->child_argv
[i
] = av_strdup(arg
);
692 if (!feed
->child_argv
[i
])
693 return AVERROR(ENOMEM
);
696 feed
->child_argv
[i
] =
697 av_asprintf("http://%s:%d/%s",
698 (config
->http_addr
.sin_addr
.s_addr
== INADDR_ANY
) ? "127.0.0.1" :
699 inet_ntoa(config
->http_addr
.sin_addr
), ntohs(config
->http_addr
.sin_port
),
701 if (!feed
->child_argv
[i
])
702 return AVERROR(ENOMEM
);
703 } else if (!av_strcasecmp(cmd
, "ACL")) {
704 ffserver_parse_acl_row(NULL
, feed
, NULL
, *p
, config
->filename
,
706 } else if (!av_strcasecmp(cmd
, "File") || !av_strcasecmp(cmd
, "ReadOnlyFile")) {
707 ffserver_get_arg(feed
->feed_filename
, sizeof(feed
->feed_filename
), p
);
708 feed
->readonly
= !av_strcasecmp(cmd
, "ReadOnlyFile");
709 } else if (!av_strcasecmp(cmd
, "Truncate")) {
710 ffserver_get_arg(arg
, sizeof(arg
), p
);
711 /* assume Truncate is true in case no argument is specified */
715 WARNING("Truncate N syntax in configuration file is deprecated, "
716 "use Truncate alone with no arguments\n");
717 feed
->truncate
= strtod(arg
, NULL
);
719 } else if (!av_strcasecmp(cmd
, "FileMaxSize")) {
723 ffserver_get_arg(arg
, sizeof(arg
), p
);
725 fsize
= strtod(p1
, &p1
);
726 switch(av_toupper(*p1
)) {
731 fsize
*= 1024 * 1024;
734 fsize
*= 1024 * 1024 * 1024;
737 ERROR("Invalid file size: %s\n", arg
);
740 feed
->feed_max_size
= (int64_t)fsize
;
741 if (feed
->feed_max_size
< FFM_PACKET_SIZE
*4)
742 ERROR("Feed max file size is too small, must be at least %d\n",
744 } else if (!av_strcasecmp(cmd
, "</Feed>")) {
747 ERROR("Invalid entry '%s' inside <Feed></Feed>\n", cmd
);
752 static int ffserver_parse_config_stream(FFServerConfig
*config
, const char *cmd
, const char **p
,
753 FFServerStream
**pstream
)
755 char arg
[1024], arg2
[1024];
756 FFServerStream
*stream
;
762 if (!av_strcasecmp(cmd
, "<Stream")) {
765 stream
= av_mallocz(sizeof(FFServerStream
));
767 return AVERROR(ENOMEM
);
768 config
->dummy_actx
= avcodec_alloc_context3(NULL
);
769 config
->dummy_vctx
= avcodec_alloc_context3(NULL
);
770 if (!config
->dummy_vctx
|| !config
->dummy_actx
) {
772 avcodec_free_context(&config
->dummy_vctx
);
773 avcodec_free_context(&config
->dummy_actx
);
774 return AVERROR(ENOMEM
);
776 config
->dummy_actx
->codec_type
= AVMEDIA_TYPE_AUDIO
;
777 config
->dummy_vctx
->codec_type
= AVMEDIA_TYPE_VIDEO
;
778 ffserver_get_arg(stream
->filename
, sizeof(stream
->filename
), p
);
779 q
= strrchr(stream
->filename
, '>');
783 for (s
= config
->first_stream
; s
; s
= s
->next
) {
784 if (!strcmp(stream
->filename
, s
->filename
))
785 ERROR("Stream '%s' already registered\n", s
->filename
);
788 stream
->fmt
= ffserver_guess_format(NULL
, stream
->filename
, NULL
);
790 config
->guessed_audio_codec_id
= stream
->fmt
->audio_codec
;
791 config
->guessed_video_codec_id
= stream
->fmt
->video_codec
;
793 config
->guessed_audio_codec_id
= AV_CODEC_ID_NONE
;
794 config
->guessed_video_codec_id
= AV_CODEC_ID_NONE
;
796 config
->stream_use_defaults
= config
->use_defaults
;
801 if (!av_strcasecmp(cmd
, "Feed")) {
802 FFServerStream
*sfeed
;
803 ffserver_get_arg(arg
, sizeof(arg
), p
);
804 sfeed
= config
->first_feed
;
806 if (!strcmp(sfeed
->filename
, arg
))
808 sfeed
= sfeed
->next_feed
;
811 ERROR("Feed with name '%s' for stream '%s' is not defined\n", arg
,
814 stream
->feed
= sfeed
;
815 } else if (!av_strcasecmp(cmd
, "Format")) {
816 ffserver_get_arg(arg
, sizeof(arg
), p
);
817 if (!strcmp(arg
, "status")) {
818 stream
->stream_type
= STREAM_TYPE_STATUS
;
821 stream
->stream_type
= STREAM_TYPE_LIVE
;
822 /* JPEG cannot be used here, so use single frame MJPEG */
823 if (!strcmp(arg
, "jpeg"))
824 strcpy(arg
, "mjpeg");
825 stream
->fmt
= ffserver_guess_format(arg
, NULL
, NULL
);
827 ERROR("Unknown Format: %s\n", arg
);
830 config
->guessed_audio_codec_id
= stream
->fmt
->audio_codec
;
831 config
->guessed_video_codec_id
= stream
->fmt
->video_codec
;
833 } else if (!av_strcasecmp(cmd
, "InputFormat")) {
834 ffserver_get_arg(arg
, sizeof(arg
), p
);
835 stream
->ifmt
= av_find_input_format(arg
);
837 ERROR("Unknown input format: %s\n", arg
);
838 } else if (!av_strcasecmp(cmd
, "FaviconURL")) {
839 if (stream
->stream_type
== STREAM_TYPE_STATUS
)
840 ffserver_get_arg(stream
->feed_filename
,
841 sizeof(stream
->feed_filename
), p
);
843 ERROR("FaviconURL only permitted for status streams\n");
844 } else if (!av_strcasecmp(cmd
, "Author") ||
845 !av_strcasecmp(cmd
, "Comment") ||
846 !av_strcasecmp(cmd
, "Copyright") ||
847 !av_strcasecmp(cmd
, "Title")) {
850 ffserver_get_arg(arg
, sizeof(arg
), p
);
851 for (i
= 0; i
< strlen(cmd
); i
++)
852 key
[i
] = av_tolower(cmd
[i
]);
854 WARNING("'%s' option in configuration file is deprecated, "
855 "use 'Metadata %s VALUE' instead\n", cmd
, key
);
856 if (av_dict_set(&stream
->metadata
, key
, arg
, 0) < 0)
858 } else if (!av_strcasecmp(cmd
, "Metadata")) {
859 ffserver_get_arg(arg
, sizeof(arg
), p
);
860 ffserver_get_arg(arg2
, sizeof(arg2
), p
);
861 if (av_dict_set(&stream
->metadata
, arg
, arg2
, 0) < 0)
863 } else if (!av_strcasecmp(cmd
, "Preroll")) {
864 ffserver_get_arg(arg
, sizeof(arg
), p
);
865 stream
->prebuffer
= atof(arg
) * 1000;
866 } else if (!av_strcasecmp(cmd
, "StartSendOnKey")) {
867 stream
->send_on_key
= 1;
868 } else if (!av_strcasecmp(cmd
, "AudioCodec")) {
869 ffserver_get_arg(arg
, sizeof(arg
), p
);
870 ffserver_set_codec(config
->dummy_actx
, arg
, config
);
871 } else if (!av_strcasecmp(cmd
, "VideoCodec")) {
872 ffserver_get_arg(arg
, sizeof(arg
), p
);
873 ffserver_set_codec(config
->dummy_vctx
, arg
, config
);
874 } else if (!av_strcasecmp(cmd
, "MaxTime")) {
875 ffserver_get_arg(arg
, sizeof(arg
), p
);
876 stream
->max_time
= atof(arg
) * 1000;
877 } else if (!av_strcasecmp(cmd
, "AudioBitRate")) {
879 ffserver_get_arg(arg
, sizeof(arg
), p
);
880 ffserver_set_float_param(&f
, arg
, 1000, -FLT_MAX
, FLT_MAX
, config
,
881 "Invalid %s: %s\n", cmd
, arg
);
882 if (ffserver_save_avoption_int("ab", (int64_t)lrintf(f
), AV_OPT_FLAG_AUDIO_PARAM
, config
) < 0)
884 } else if (!av_strcasecmp(cmd
, "AudioChannels")) {
885 ffserver_get_arg(arg
, sizeof(arg
), p
);
886 if (ffserver_save_avoption("ac", arg
, AV_OPT_FLAG_AUDIO_PARAM
, config
) < 0)
888 } else if (!av_strcasecmp(cmd
, "AudioSampleRate")) {
889 ffserver_get_arg(arg
, sizeof(arg
), p
);
890 if (ffserver_save_avoption("ar", arg
, AV_OPT_FLAG_AUDIO_PARAM
, config
) < 0)
892 } else if (!av_strcasecmp(cmd
, "VideoBitRateRange")) {
893 int minrate
, maxrate
;
895 ffserver_get_arg(arg
, sizeof(arg
), p
);
896 dash
= strchr(arg
, '-');
900 if (ffserver_set_int_param(&minrate
, arg
, 1000, 0, INT_MAX
, config
, "Invalid %s: %s", cmd
, arg
) >= 0 &&
901 ffserver_set_int_param(&maxrate
, dash
, 1000, 0, INT_MAX
, config
, "Invalid %s: %s", cmd
, arg
) >= 0) {
902 if (ffserver_save_avoption_int("minrate", minrate
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0 ||
903 ffserver_save_avoption_int("maxrate", maxrate
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
907 ERROR("Incorrect format for VideoBitRateRange -- should be "
908 "<min>-<max>: %s\n", arg
);
909 } else if (!av_strcasecmp(cmd
, "Debug")) {
910 ffserver_get_arg(arg
, sizeof(arg
), p
);
911 if (ffserver_save_avoption("debug", arg
, AV_OPT_FLAG_AUDIO_PARAM
, config
) < 0 ||
912 ffserver_save_avoption("debug", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
914 } else if (!av_strcasecmp(cmd
, "Strict")) {
915 ffserver_get_arg(arg
, sizeof(arg
), p
);
916 if (ffserver_save_avoption("strict", arg
, AV_OPT_FLAG_AUDIO_PARAM
, config
) < 0 ||
917 ffserver_save_avoption("strict", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
919 } else if (!av_strcasecmp(cmd
, "VideoBufferSize")) {
920 ffserver_get_arg(arg
, sizeof(arg
), p
);
921 ffserver_set_int_param(&val
, arg
, 8*1024, 0, INT_MAX
, config
,
922 "Invalid %s: %s", cmd
, arg
);
923 if (ffserver_save_avoption_int("bufsize", val
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
925 } else if (!av_strcasecmp(cmd
, "VideoBitRateTolerance")) {
926 ffserver_get_arg(arg
, sizeof(arg
), p
);
927 ffserver_set_int_param(&val
, arg
, 1000, INT_MIN
, INT_MAX
, config
,
928 "Invalid %s: %s", cmd
, arg
);
929 if (ffserver_save_avoption_int("bt", val
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
931 } else if (!av_strcasecmp(cmd
, "VideoBitRate")) {
932 ffserver_get_arg(arg
, sizeof(arg
), p
);
933 ffserver_set_int_param(&val
, arg
, 1000, INT_MIN
, INT_MAX
, config
,
934 "Invalid %s: %s", cmd
, arg
);
935 if (ffserver_save_avoption_int("b", val
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
937 } else if (!av_strcasecmp(cmd
, "VideoSize")) {
939 ffserver_get_arg(arg
, sizeof(arg
), p
);
940 ret
= av_parse_video_size(&w
, &h
, arg
);
942 ERROR("Invalid video size '%s'\n", arg
);
945 WARNING("Image size is not a multiple of 2\n");
946 if (ffserver_save_avoption("video_size", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
949 } else if (!av_strcasecmp(cmd
, "VideoFrameRate")) {
950 ffserver_get_arg(&arg
[2], sizeof(arg
) - 2, p
);
951 arg
[0] = '1'; arg
[1] = '/';
952 if (ffserver_save_avoption("time_base", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
954 } else if (!av_strcasecmp(cmd
, "PixelFormat")) {
955 enum AVPixelFormat pix_fmt
;
956 ffserver_get_arg(arg
, sizeof(arg
), p
);
957 pix_fmt
= av_get_pix_fmt(arg
);
958 if (pix_fmt
== AV_PIX_FMT_NONE
)
959 ERROR("Unknown pixel format: %s\n", arg
);
960 else if (ffserver_save_avoption("pixel_format", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
962 } else if (!av_strcasecmp(cmd
, "VideoGopSize")) {
963 ffserver_get_arg(arg
, sizeof(arg
), p
);
964 if (ffserver_save_avoption("g", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
966 } else if (!av_strcasecmp(cmd
, "VideoIntraOnly")) {
967 if (ffserver_save_avoption("g", "1", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
969 } else if (!av_strcasecmp(cmd
, "VideoHighQuality")) {
970 if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
972 } else if (!av_strcasecmp(cmd
, "Video4MotionVector")) {
973 if (ffserver_save_avoption("mbd", "+bits", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0 || //FIXME remove
974 ffserver_save_avoption("flags", "+mv4", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
976 } else if (!av_strcasecmp(cmd
, "AVOptionVideo") ||
977 !av_strcasecmp(cmd
, "AVOptionAudio")) {
979 ffserver_get_arg(arg
, sizeof(arg
), p
);
980 ffserver_get_arg(arg2
, sizeof(arg2
), p
);
981 if (!av_strcasecmp(cmd
, "AVOptionVideo"))
982 ret
= ffserver_save_avoption(arg
, arg2
, AV_OPT_FLAG_VIDEO_PARAM
, config
);
984 ret
= ffserver_save_avoption(arg
, arg2
, AV_OPT_FLAG_AUDIO_PARAM
, config
);
987 } else if (!av_strcasecmp(cmd
, "AVPresetVideo") ||
988 !av_strcasecmp(cmd
, "AVPresetAudio")) {
989 ffserver_get_arg(arg
, sizeof(arg
), p
);
990 if (!av_strcasecmp(cmd
, "AVPresetVideo"))
991 ffserver_opt_preset(arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
);
993 ffserver_opt_preset(arg
, AV_OPT_FLAG_AUDIO_PARAM
, config
);
994 } else if (!av_strcasecmp(cmd
, "VideoTag")) {
995 ffserver_get_arg(arg
, sizeof(arg
), p
);
996 if (strlen(arg
) == 4 &&
997 ffserver_save_avoption_int("codec_tag", MKTAG(arg
[0], arg
[1], arg
[2], arg
[3]),
998 AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1000 } else if (!av_strcasecmp(cmd
, "BitExact")) {
1001 if (ffserver_save_avoption("flags", "+bitexact", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1003 } else if (!av_strcasecmp(cmd
, "DctFastint")) {
1004 if (ffserver_save_avoption("dct", "fastint", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1006 } else if (!av_strcasecmp(cmd
, "IdctSimple")) {
1007 if (ffserver_save_avoption("idct", "simple", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1009 } else if (!av_strcasecmp(cmd
, "Qscale")) {
1010 ffserver_get_arg(arg
, sizeof(arg
), p
);
1011 ffserver_set_int_param(&val
, arg
, 0, INT_MIN
, INT_MAX
, config
,
1012 "Invalid Qscale: %s\n", arg
);
1013 if (ffserver_save_avoption("flags", "+qscale", AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0 ||
1014 ffserver_save_avoption_int("global_quality", FF_QP2LAMBDA
* val
,
1015 AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1017 } else if (!av_strcasecmp(cmd
, "VideoQDiff")) {
1018 ffserver_get_arg(arg
, sizeof(arg
), p
);
1019 if (ffserver_save_avoption("qdiff", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1021 } else if (!av_strcasecmp(cmd
, "VideoQMax")) {
1022 ffserver_get_arg(arg
, sizeof(arg
), p
);
1023 if (ffserver_save_avoption("qmax", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1025 } else if (!av_strcasecmp(cmd
, "VideoQMin")) {
1026 ffserver_get_arg(arg
, sizeof(arg
), p
);
1027 if (ffserver_save_avoption("qmin", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1029 } else if (!av_strcasecmp(cmd
, "LumiMask")) {
1030 ffserver_get_arg(arg
, sizeof(arg
), p
);
1031 if (ffserver_save_avoption("lumi_mask", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1033 } else if (!av_strcasecmp(cmd
, "DarkMask")) {
1034 ffserver_get_arg(arg
, sizeof(arg
), p
);
1035 if (ffserver_save_avoption("dark_mask", arg
, AV_OPT_FLAG_VIDEO_PARAM
, config
) < 0)
1037 } else if (!av_strcasecmp(cmd
, "NoVideo")) {
1038 config
->no_video
= 1;
1039 } else if (!av_strcasecmp(cmd
, "NoAudio")) {
1040 config
->no_audio
= 1;
1041 } else if (!av_strcasecmp(cmd
, "ACL")) {
1042 ffserver_parse_acl_row(stream
, NULL
, NULL
, *p
, config
->filename
,
1044 } else if (!av_strcasecmp(cmd
, "DynamicACL")) {
1045 ffserver_get_arg(stream
->dynamic_acl
, sizeof(stream
->dynamic_acl
), p
);
1046 } else if (!av_strcasecmp(cmd
, "RTSPOption")) {
1047 ffserver_get_arg(arg
, sizeof(arg
), p
);
1048 av_freep(&stream
->rtsp_option
);
1049 stream
->rtsp_option
= av_strdup(arg
);
1050 } else if (!av_strcasecmp(cmd
, "MulticastAddress")) {
1051 ffserver_get_arg(arg
, sizeof(arg
), p
);
1052 if (resolve_host(&stream
->multicast_ip
, arg
))
1053 ERROR("Invalid host/IP address: %s\n", arg
);
1054 stream
->is_multicast
= 1;
1055 stream
->loop
= 1; /* default is looping */
1056 } else if (!av_strcasecmp(cmd
, "MulticastPort")) {
1057 ffserver_get_arg(arg
, sizeof(arg
), p
);
1058 ffserver_set_int_param(&val
, arg
, 0, 1, 65535, config
,
1059 "Invalid MulticastPort: %s\n", arg
);
1060 stream
->multicast_port
= val
;
1061 } else if (!av_strcasecmp(cmd
, "MulticastTTL")) {
1062 ffserver_get_arg(arg
, sizeof(arg
), p
);
1063 ffserver_set_int_param(&val
, arg
, 0, INT_MIN
, INT_MAX
, config
,
1064 "Invalid MulticastTTL: %s\n", arg
);
1065 stream
->multicast_ttl
= val
;
1066 } else if (!av_strcasecmp(cmd
, "NoLoop")) {
1068 } else if (!av_strcasecmp(cmd
, "</Stream>")) {
1069 config
->stream_use_defaults
&= 1;
1070 if (stream
->feed
&& stream
->fmt
&& strcmp(stream
->fmt
->name
, "ffm")) {
1071 if (config
->dummy_actx
->codec_id
== AV_CODEC_ID_NONE
)
1072 config
->dummy_actx
->codec_id
= config
->guessed_audio_codec_id
;
1073 if (!config
->no_audio
&& config
->dummy_actx
->codec_id
!= AV_CODEC_ID_NONE
) {
1074 AVCodecContext
*audio_enc
= avcodec_alloc_context3(avcodec_find_encoder(config
->dummy_actx
->codec_id
));
1075 add_codec(stream
, audio_enc
, config
);
1077 if (config
->dummy_vctx
->codec_id
== AV_CODEC_ID_NONE
)
1078 config
->dummy_vctx
->codec_id
= config
->guessed_video_codec_id
;
1079 if (!config
->no_video
&& config
->dummy_vctx
->codec_id
!= AV_CODEC_ID_NONE
) {
1080 AVCodecContext
*video_enc
= avcodec_alloc_context3(avcodec_find_encoder(config
->dummy_vctx
->codec_id
));
1081 add_codec(stream
, video_enc
, config
);
1084 av_dict_free(&config
->video_opts
);
1085 av_dict_free(&config
->audio_opts
);
1086 avcodec_free_context(&config
->dummy_vctx
);
1087 avcodec_free_context(&config
->dummy_actx
);
1089 } else if (!av_strcasecmp(cmd
, "File") || !av_strcasecmp(cmd
, "ReadOnlyFile")) {
1090 ffserver_get_arg(stream
->feed_filename
, sizeof(stream
->feed_filename
),
1092 } else if (!av_strcasecmp(cmd
, "UseDefaults")) {
1093 if (config
->stream_use_defaults
> 1)
1094 WARNING("Multiple UseDefaults/NoDefaults entries.\n");
1095 config
->stream_use_defaults
= 3;
1096 } else if (!av_strcasecmp(cmd
, "NoDefaults")) {
1097 if (config
->stream_use_defaults
> 1)
1098 WARNING("Multiple UseDefaults/NoDefaults entries.\n");
1099 config
->stream_use_defaults
= 2;
1101 ERROR("Invalid entry '%s' inside <Stream></Stream>\n", cmd
);
1105 av_log(NULL
, AV_LOG_ERROR
, "Out of memory. Aborting.\n");
1106 av_dict_free(&config
->video_opts
);
1107 av_dict_free(&config
->audio_opts
);
1108 avcodec_free_context(&config
->dummy_vctx
);
1109 avcodec_free_context(&config
->dummy_actx
);
1110 return AVERROR(ENOMEM
);
1113 static int ffserver_parse_config_redirect(FFServerConfig
*config
, const char *cmd
, const char **p
,
1114 FFServerStream
**predirect
)
1116 FFServerStream
*redirect
;
1117 av_assert0(predirect
);
1118 redirect
= *predirect
;
1120 if (!av_strcasecmp(cmd
, "<Redirect")) {
1122 redirect
= av_mallocz(sizeof(FFServerStream
));
1124 return AVERROR(ENOMEM
);
1126 ffserver_get_arg(redirect
->filename
, sizeof(redirect
->filename
), p
);
1127 q
= strrchr(redirect
->filename
, '>');
1130 redirect
->stream_type
= STREAM_TYPE_REDIRECT
;
1131 *predirect
= redirect
;
1134 av_assert0(redirect
);
1135 if (!av_strcasecmp(cmd
, "URL")) {
1136 ffserver_get_arg(redirect
->feed_filename
,
1137 sizeof(redirect
->feed_filename
), p
);
1138 } else if (!av_strcasecmp(cmd
, "</Redirect>")) {
1139 if (!redirect
->feed_filename
[0])
1140 ERROR("No URL found for <Redirect>\n");
1143 ERROR("Invalid entry '%s' inside <Redirect></Redirect>\n", cmd
);
1148 int ffserver_parse_ffconfig(const char *filename
, FFServerConfig
*config
)
1154 FFServerStream
**last_stream
, *stream
= NULL
, *redirect
= NULL
;
1155 FFServerStream
**last_feed
, *feed
= NULL
;
1160 config
->line_num
= 0;
1161 f
= fopen(filename
, "r");
1163 ret
= AVERROR(errno
);
1164 av_log(NULL
, AV_LOG_ERROR
,
1165 "Could not open the configuration file '%s'\n", filename
);
1169 config
->first_stream
= NULL
;
1170 last_stream
= &config
->first_stream
;
1171 config
->first_feed
= NULL
;
1172 last_feed
= &config
->first_feed
;
1173 config
->errors
= config
->warnings
= 0;
1176 if (fgets(line
, sizeof(line
), f
) == NULL
)
1180 while (av_isspace(*p
))
1182 if (*p
== '\0' || *p
== '#')
1185 ffserver_get_arg(cmd
, sizeof(cmd
), &p
);
1187 if (feed
|| !av_strcasecmp(cmd
, "<Feed")) {
1188 int opening
= !av_strcasecmp(cmd
, "<Feed");
1189 if (opening
&& (stream
|| feed
|| redirect
)) {
1190 ERROR("Already in a tag\n");
1192 if ((ret
= ffserver_parse_config_feed(config
, cmd
, &p
, &feed
)) < 0)
1195 /* add in stream list */
1196 *last_stream
= feed
;
1197 last_stream
= &feed
->next
;
1198 /* add in feed list */
1200 last_feed
= &feed
->next_feed
;
1203 } else if (stream
|| !av_strcasecmp(cmd
, "<Stream")) {
1204 int opening
= !av_strcasecmp(cmd
, "<Stream");
1205 if (opening
&& (stream
|| feed
|| redirect
)) {
1206 ERROR("Already in a tag\n");
1208 if ((ret
= ffserver_parse_config_stream(config
, cmd
, &p
, &stream
)) < 0)
1211 /* add in stream list */
1212 *last_stream
= stream
;
1213 last_stream
= &stream
->next
;
1216 } else if (redirect
|| !av_strcasecmp(cmd
, "<Redirect")) {
1217 int opening
= !av_strcasecmp(cmd
, "<Redirect");
1218 if (opening
&& (stream
|| feed
|| redirect
))
1219 ERROR("Already in a tag\n");
1221 if ((ret
= ffserver_parse_config_redirect(config
, cmd
, &p
, &redirect
)) < 0)
1224 /* add in stream list */
1225 *last_stream
= redirect
;
1226 last_stream
= &redirect
->next
;
1230 ffserver_parse_config_global(config
, cmd
, &p
);
1233 if (stream
|| feed
|| redirect
)
1234 ERROR("Not closed tag %s\n", stream
? "<Stream>" : (feed
? "<Feed>" : "<Redirect>"));
1240 return AVERROR(EINVAL
);
1248 void ffserver_free_child_args(void *argsp
)
1254 args
= *(char ***)argsp
;
1257 for (i
= 0; i
< MAX_CHILD_ARGS
; i
++)