ffserver_config.c 50 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

21
#include <float.h>
22 23 24 25 26 27 28 29 30
#include "libavutil/opt.h"
#include "libavutil/parseutils.h"
#include "libavutil/avstring.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avassert.h"

#include "cmdutils.h"
#include "ffserver_config.h"

31 32
#define MAX_CHILD_ARGS 64

33
static int ffserver_save_avoption(const char *opt, const char *arg, int type,
34
                                  FFServerConfig *config);
35 36 37 38 39 40
static void vreport_config_error(const char *filename, int line_num,
                                 int log_level, int *errors, const char *fmt,
                                 va_list vl);
static void report_config_error(const char *filename, int line_num,
                                int log_level, int *errors, const char *fmt,
                                ...);
41

42 43
#define ERROR(...) report_config_error(config->filename, config->line_num,\
                                       AV_LOG_ERROR, &config->errors, __VA_ARGS__)
44 45 46
#define WARNING(...) report_config_error(config->filename, config->line_num,\
                                         AV_LOG_WARNING, &config->warnings, __VA_ARGS__)

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
/* FIXME: make ffserver work with IPv6 */
/* resolve host with also IP address parsing */
static int resolve_host(struct in_addr *sin_addr, const char *hostname)
{

    if (!ff_inet_aton(hostname, sin_addr)) {
#if HAVE_GETADDRINFO
        struct addrinfo *ai, *cur;
        struct addrinfo hints = { 0 };
        hints.ai_family = AF_INET;
        if (getaddrinfo(hostname, NULL, &hints, &ai))
            return -1;
        /* getaddrinfo returns a linked list of addrinfo structs.
         * Even if we set ai_family = AF_INET above, make sure
         * that the returned one actually is of the correct type. */
        for (cur = ai; cur; cur = cur->ai_next) {
            if (cur->ai_family == AF_INET) {
                *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
                freeaddrinfo(ai);
                return 0;
            }
        }
        freeaddrinfo(ai);
        return -1;
#else
        struct hostent *hp;
        hp = gethostbyname(hostname);
        if (!hp)
            return -1;
        memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr));
#endif
    }
    return 0;
}

void ffserver_get_arg(char *buf, int buf_size, const char **pp)
{
    const char *p;
    char *q;
86
    int quote = 0;
87 88 89

    p = *pp;
    q = buf;
90 91 92

    while (av_isspace(*p)) p++;

93 94
    if (*p == '\"' || *p == '\'')
        quote = *p++;
95 96 97

    while (*p != '\0') {
        if (quote && *p == quote || !quote && av_isspace(*p))
98 99 100 101 102
            break;
        if ((q - buf) < buf_size - 1)
            *q++ = *p;
        p++;
    }
103

104 105 106 107 108 109 110 111 112 113 114 115
    *q = '\0';
    if (quote && *p == quote)
        p++;
    *pp = p;
}

void ffserver_parse_acl_row(FFServerStream *stream, FFServerStream* feed,
                            FFServerIPAddressACL *ext_acl,
                            const char *p, const char *filename, int line_num)
{
    char arg[1024];
    FFServerIPAddressACL acl;
116 117
    FFServerIPAddressACL *nacl;
    FFServerIPAddressACL **naclp;
118 119 120 121 122 123 124

    ffserver_get_arg(arg, sizeof(arg), &p);
    if (av_strcasecmp(arg, "allow") == 0)
        acl.action = IP_ALLOW;
    else if (av_strcasecmp(arg, "deny") == 0)
        acl.action = IP_DENY;
    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 131
    }

    ffserver_get_arg(arg, sizeof(arg), &p);

132
    if (resolve_host(&acl.first, arg)) {
133 134
        fprintf(stderr,
                "%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 142 143

    ffserver_get_arg(arg, sizeof(arg), &p);

    if (arg[0]) {
144
        if (resolve_host(&acl.last, arg)) {
145 146
            fprintf(stderr,
                    "%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 154 155 156 157
    if (!nacl) {
        fprintf(stderr, "Failed to allocate FFServerIPAddressACL\n");
        goto bail;
    }

158
    naclp = 0;
159

160 161 162 163 164 165 166 167 168
    acl.next = 0;
    *nacl = acl;

    if (stream)
        naclp = &stream->acl;
    else if (feed)
        naclp = &feed->acl;
    else if (ext_acl)
        naclp = &ext_acl;
169
    else
170 171 172 173 174 175 176 177 178 179 180
        fprintf(stderr, "%s:%d: ACL found not in <Stream> or <Feed>\n",
                filename, line_num);

    if (naclp) {
        while (*naclp)
            naclp = &(*naclp)->next;

        *naclp = nacl;
    } else
        av_free(nacl);

181 182 183
bail:
  return;

184 185 186
}

/* add a codec and set the default parameters */
187 188
static void add_codec(FFServerStream *stream, AVCodecContext *av,
                      FFServerConfig *config)
189
{
190
    LayeredAVStream *st;
191 192
    AVDictionary **opts, *recommended = NULL;
    char *enc_config;
193 194 195 196

    if(stream->nb_streams >= FF_ARRAY_ELEMS(stream->streams))
        return;

197 198
    opts = av->codec_type == AVMEDIA_TYPE_AUDIO ?
           &config->audio_opts : &config->video_opts;
199
    av_dict_copy(&recommended, *opts, 0);
200 201
    av_opt_set_dict2(av->priv_data, opts, AV_OPT_SEARCH_CHILDREN);
    av_opt_set_dict2(av, opts, AV_OPT_SEARCH_CHILDREN);
202

203 204
    if (av_dict_count(*opts))
        av_log(NULL, AV_LOG_WARNING,
205 206
               "Something is wrong, %d options are not set!\n",
               av_dict_count(*opts));
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    if (!config->stream_use_defaults) {
        switch(av->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            if (av->bit_rate == 0)
                report_config_error(config->filename, config->line_num,
                                    AV_LOG_ERROR, &config->errors,
                                    "audio bit rate is not set\n");
            if (av->sample_rate == 0)
                report_config_error(config->filename, config->line_num,
                                    AV_LOG_ERROR, &config->errors,
                                    "audio sample rate is not set\n");
            break;
        case AVMEDIA_TYPE_VIDEO:
            if (av->width == 0 || av->height == 0)
                report_config_error(config->filename, config->line_num,
                                    AV_LOG_ERROR, &config->errors,
                                    "video size is not set\n");
            break;
        default:
            av_assert0(0);
        }
        goto done;
    }

    /* stream_use_defaults = true */

234 235 236
    /* compute default parameters */
    switch(av->codec_type) {
    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 241 242
            WARNING("Setting default value for audio bit rate = %d. "
                    "Use NoDefaults to disable it.\n",
                    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 248 249
            WARNING("Setting default value for audio sample rate = %d. "
                    "Use NoDefaults to disable it.\n",
                    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 255 256
            WARNING("Setting default value for audio channel count = %d. "
                    "Use NoDefaults to disable it.\n",
                    av->channels);
257
        }
258 259
        break;
    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 264 265
            WARNING("Setting default value for video bit rate = %d. "
                    "Use NoDefaults to disable it.\n",
                    av->bit_rate);
266
        }
267
        if (!av_dict_get(recommended, "time_base", NULL, 0)){
268 269
            av->time_base.den = 5;
            av->time_base.num = 1;
270
            av_dict_set(&recommended, "time_base", "1/5", 0);
271 272 273
            WARNING("Setting default value for video frame rate = %d. "
                    "Use NoDefaults to disable it.\n",
                    av->time_base.den);
274
        }
275
        if (!av_dict_get(recommended, "video_size", NULL, 0)) {
276 277
            av->width = 160;
            av->height = 128;
278
            av_dict_set(&recommended, "video_size", "160x128", 0);
279 280 281
            WARNING("Setting default value for video size = %dx%d. "
                    "Use NoDefaults to disable it.\n",
                    av->width, av->height);
282 283
        }
        /* Bitrate tolerance is less for streaming */
284
        if (!av_dict_get(recommended, "bt", NULL, 0)) {
285 286
            av->bit_rate_tolerance = FFMAX(av->bit_rate / 4,
                      (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 289 290
            WARNING("Setting default value for video bit rate tolerance = %d. "
                    "Use NoDefaults to disable it.\n",
                    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 297
            WARNING("Setting default value for video rate control equation = "
                    "%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 304 305
            WARNING("Setting default value for video max rate = %d. "
                    "Use NoDefaults to disable it.\n",
                    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 312 313
            WARNING("Setting default value for video buffer size = %d. "
                    "Use NoDefaults to disable it.\n",
                    av->rc_buffer_size);
314 315 316 317 318 319
        }
        break;
    default:
        abort();
    }

320
done:
321
    st = av_mallocz(sizeof(*st));
322 323
    if (!st)
        return;
324 325
    av_dict_get_string(recommended, &enc_config, '=', ',');
    av_dict_free(&recommended);
326
    st->recommended_encoder_configuration = enc_config;
327
    st->codec = av;
328 329
    st->codecpar = avcodec_parameters_alloc();
    avcodec_parameters_from_context(st->codecpar, av);
330 331 332
    stream->streams[stream->nb_streams++] = st;
}

333 334
static int ffserver_set_codec(AVCodecContext *ctx, const char *codec_name,
                              FFServerConfig *config)
335
{
336 337 338
    int ret;
    AVCodec *codec = avcodec_find_encoder_by_name(codec_name);
    if (!codec || codec->type != ctx->codec_type) {
339
        report_config_error(config->filename, config->line_num, AV_LOG_ERROR,
340 341
                            &config->errors,
                            "Invalid codec name: '%s'\n", codec_name);
342 343 344 345 346 347 348 349
        return 0;
    }
    if (ctx->codec_id == AV_CODEC_ID_NONE && !ctx->priv_data) {
        if ((ret = avcodec_get_context_defaults3(ctx, codec)) < 0)
            return ret;
        ctx->codec = codec;
    }
    if (ctx->codec_id != codec->id)
350 351 352 353
        report_config_error(config->filename, config->line_num, AV_LOG_ERROR,
                            &config->errors,
                            "Inconsistent configuration: trying to set '%s' "
                            "codec option, but '%s' codec is used previously\n",
354 355
                            codec_name, avcodec_get_name(ctx->codec_id));
    return 0;
356 357
}

358
static int ffserver_opt_preset(const char *arg, int type, FFServerConfig *config)
359 360 361 362
{
    FILE *f=NULL;
    char filename[1000], tmp[1000], tmp2[1000], line[1000];
    int ret = 0;
363 364 365 366 367 368 369 370 371 372 373 374 375 376
    AVCodecContext *avctx;
    const AVCodec *codec;

    switch(type) {
    case AV_OPT_FLAG_AUDIO_PARAM:
        avctx = config->dummy_actx;
        break;
    case AV_OPT_FLAG_VIDEO_PARAM:
        avctx = config->dummy_vctx;
        break;
    default:
        av_assert0(0);
    }
    codec = avcodec_find_encoder(avctx->codec_id);
377 378 379

    if (!(f = get_preset_file(filename, sizeof(filename), arg, 0,
                              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 385 386 387 388 389
    }

    while(!feof(f)){
        int e= fscanf(f, "%999[^\n]\n", line) - 1;
        if(line[0] == '#' && !e)
            continue;
        e|= sscanf(line, "%999[^=]=%999[^\n]\n", tmp, tmp2) - 2;
        if(e){
390 391
            av_log(NULL, AV_LOG_ERROR, "%s: Invalid syntax: '%s'\n", filename,
                   line);
392
            ret = AVERROR(EINVAL);
393 394
            break;
        }
395 396
        if (!strcmp(tmp, "acodec") && avctx->codec_type == AVMEDIA_TYPE_AUDIO ||
            !strcmp(tmp, "vcodec") && avctx->codec_type == AVMEDIA_TYPE_VIDEO)
397
        {
398
            if (ffserver_set_codec(avctx, tmp2, config) < 0)
399 400 401 402
                break;
        } else if (!strcmp(tmp, "scodec")) {
            av_log(NULL, AV_LOG_ERROR, "Subtitles preset found.\n");
            ret = AVERROR(EINVAL);
403
            break;
404
        } else if (ffserver_save_avoption(tmp, tmp2, type, config) < 0)
405
            break;
406 407 408 409 410 411 412
    }

    fclose(f);

    return ret;
}

413 414 415
static AVOutputFormat *ffserver_guess_format(const char *short_name,
                                             const char *filename,
                                             const char *mime_type)
416 417 418 419 420 421 422
{
    AVOutputFormat *fmt = av_guess_format(short_name, filename, mime_type);

    if (fmt) {
        AVOutputFormat *stream_fmt;
        char stream_format_name[64];

423 424
        snprintf(stream_format_name, sizeof(stream_format_name), "%s_stream",
                fmt->name);
425 426 427 428 429 430 431 432 433
        stream_fmt = av_guess_format(stream_format_name, NULL, NULL);

        if (stream_fmt)
            fmt = stream_fmt;
    }

    return fmt;
}

434 435 436
static void vreport_config_error(const char *filename, int line_num,
                                 int log_level, int *errors, const char *fmt,
                                 va_list vl)
437 438 439
{
    av_log(NULL, log_level, "%s:%d: ", filename, line_num);
    av_vlog(NULL, log_level, fmt, vl);
440 441
    if (errors)
        (*errors)++;
442 443
}

444 445 446
static void report_config_error(const char *filename, int line_num,
                                int log_level, int *errors,
                                const char *fmt, ...)
447 448 449
{
    va_list vl;
    va_start(vl, fmt);
450
    vreport_config_error(filename, line_num, log_level, errors, fmt, vl);
451
    va_end(vl);
452
}
453

454 455 456
static int ffserver_set_int_param(int *dest, const char *value, int factor,
                                  int min, int max, FFServerConfig *config,
                                  const char *error_msg, ...)
457 458 459 460 461 462 463 464 465 466
{
    int tmp;
    char *tailp;
    if (!value || !value[0])
        goto error;
    errno = 0;
    tmp = strtol(value, &tailp, 0);
    if (tmp < min || tmp > max)
        goto error;
    if (factor) {
467
        if (tmp == INT_MIN || FFABS(tmp) > INT_MAX / FFABS(factor))
468 469 470 471 472 473 474 475 476 477 478 479
            goto error;
        tmp *= factor;
    }
    if (tailp[0] || errno)
        goto error;
    if (dest)
        *dest = tmp;
    return 0;
  error:
    if (config) {
        va_list vl;
        va_start(vl, error_msg);
480
        vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR,
481
                &config->errors, error_msg, vl);
482 483 484 485 486
        va_end(vl);
    }
    return AVERROR(EINVAL);
}

487 488 489 490
static int ffserver_set_float_param(float *dest, const char *value,
                                    float factor, float min, float max,
                                    FFServerConfig *config,
                                    const char *error_msg, ...)
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
{
    double tmp;
    char *tailp;
    if (!value || !value[0])
        goto error;
    errno = 0;
    tmp = strtod(value, &tailp);
    if (tmp < min || tmp > max)
        goto error;
    if (factor)
        tmp *= factor;
    if (tailp[0] || errno)
        goto error;
    if (dest)
        *dest = tmp;
    return 0;
  error:
    if (config) {
        va_list vl;
        va_start(vl, error_msg);
511
        vreport_config_error(config->filename, config->line_num, AV_LOG_ERROR,
512
                &config->errors, error_msg, vl);
513 514 515
        va_end(vl);
    }
    return AVERROR(EINVAL);
516 517
}

518 519
static int ffserver_save_avoption(const char *opt, const char *arg, int type,
                                  FFServerConfig *config)
520
{
521
    static int hinted = 0;
522 523
    int ret = 0;
    AVDictionaryEntry *e;
524 525 526 527
    const AVOption *o = NULL;
    const char *option = NULL;
    const char *codec_name = NULL;
    char buff[1024];
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
    AVCodecContext *ctx;
    AVDictionary **dict;
    enum AVCodecID guessed_codec_id;

    switch (type) {
    case AV_OPT_FLAG_VIDEO_PARAM:
        ctx = config->dummy_vctx;
        dict = &config->video_opts;
        guessed_codec_id = config->guessed_video_codec_id != AV_CODEC_ID_NONE ?
                           config->guessed_video_codec_id : AV_CODEC_ID_H264;
        break;
    case AV_OPT_FLAG_AUDIO_PARAM:
        ctx = config->dummy_actx;
        dict = &config->audio_opts;
        guessed_codec_id = config->guessed_audio_codec_id != AV_CODEC_ID_NONE ?
                           config->guessed_audio_codec_id : AV_CODEC_ID_AAC;
        break;
    default:
        av_assert0(0);
    }
548 549 550 551 552

    if (strchr(opt, ':')) {
        //explicit private option
        snprintf(buff, sizeof(buff), "%s", opt);
        codec_name = buff;
553 554 555 556 557 558 559
        if(!(option = strchr(buff, ':'))){
            report_config_error(config->filename, config->line_num,
                                AV_LOG_ERROR, &config->errors,
                                "Syntax error. Unmatched ':'\n");
            return -1;

        }
560 561
        buff[option - buff] = '\0';
        option++;
562
        if ((ret = ffserver_set_codec(ctx, codec_name, config)) < 0)
563 564 565 566 567 568 569
            return ret;
        if (!ctx->codec || !ctx->priv_data)
            return -1;
    } else {
        option = opt;
    }

570 571 572 573 574
    o = av_opt_find(ctx, option, NULL, type | AV_OPT_FLAG_ENCODING_PARAM,
                    AV_OPT_SEARCH_CHILDREN);
    if (!o &&
        (!strcmp(option, "time_base")  || !strcmp(option, "pixel_format") ||
         !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 580
        if (!hinted && ctx->codec_id == AV_CODEC_ID_NONE) {
            hinted = 1;
581 582 583 584 585 586
            report_config_error(config->filename, config->line_num,
                                AV_LOG_ERROR, NULL, "If '%s' is a codec private"
                                "option, then prefix it with codec name, for "
                                "example '%s:%s %s' or define codec earlier.\n",
                                opt, avcodec_get_name(guessed_codec_id) ,opt,
                                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 591
                &config->errors, "Invalid value for option %s (%s): %s\n", opt,
                arg, av_err2str(ret));
592
    } else if ((e = av_dict_get(*dict, option, NULL, 0))) {
593 594
        if ((o->type == AV_OPT_TYPE_FLAGS) && arg &&
            (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 598
                            &config->errors, "Redeclaring value of option '%s'."
                            "Previous value was: '%s'.\n", opt, e->value);
599
    } else if (av_dict_set(dict, option, arg, 0) < 0) {
600 601 602 603 604
        return AVERROR(ENOMEM);
    }
    return 0;
}

605 606 607 608 609 610 611 612
static int ffserver_save_avoption_int(const char *opt, int64_t arg,
                                      int type, FFServerConfig *config)
{
    char buf[22];
    snprintf(buf, sizeof(buf), "%"PRId64, arg);
    return ffserver_save_avoption(opt, buf, type, config);
}

613
static int ffserver_parse_config_global(FFServerConfig *config, const char *cmd,
614
                                        const char **p)
615 616 617 618 619
{
    int val;
    char arg[1024];
    if (!av_strcasecmp(cmd, "Port") || !av_strcasecmp(cmd, "HTTPPort")) {
        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 628
    } else if (!av_strcasecmp(cmd, "HTTPBindAddress") ||
               !av_strcasecmp(cmd, "BindAddress")) {
629
        if (!av_strcasecmp(cmd, "BindAddress"))
630 631
            WARNING("BindAddress option is deprecated. Use HTTPBindAddress "
                    "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 638
    } else if (!av_strcasecmp(cmd, "RTSPPort")) {
        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 643
    } else if (!av_strcasecmp(cmd, "RTSPBindAddress")) {
        ffserver_get_arg(arg, sizeof(arg), p);
644
        if (resolve_host(&config->rtsp_addr.sin_addr, arg))
645 646 647
            ERROR("Invalid host/IP address: %s\n", arg);
    } else if (!av_strcasecmp(cmd, "MaxHTTPConnections")) {
        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 652 653 654 655
        if (config->nb_max_connections > config->nb_max_http_connections) {
            ERROR("Inconsistent configuration: MaxClients(%d) > "
                  "MaxHTTPConnections(%d)\n", config->nb_max_connections,
                  config->nb_max_http_connections);
        }
656 657
    } else if (!av_strcasecmp(cmd, "MaxClients")) {
        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 662 663 664 665
        if (config->nb_max_connections > config->nb_max_http_connections) {
            ERROR("Inconsistent configuration: MaxClients(%d) > "
                  "MaxHTTPConnections(%d)\n", config->nb_max_connections,
                  config->nb_max_http_connections);
        }
666 667
    } else if (!av_strcasecmp(cmd, "MaxBandwidth")) {
        int64_t llval;
668
        char *tailp;
669
        ffserver_get_arg(arg, sizeof(arg), p);
670 671 672
        errno = 0;
        llval = strtoll(arg, &tailp, 10);
        if (llval < 10 || llval > 10000000 || tailp[0] || errno)
673
            ERROR("Invalid MaxBandwidth: '%s'\n", arg);
674 675 676
        else
            config->max_bandwidth = llval;
    } else if (!av_strcasecmp(cmd, "CustomLog")) {
677 678 679 680
        if (!config->debug) {
            ffserver_get_arg(config->logfilename, sizeof(config->logfilename),
                             p);
        }
681
    } else if (!av_strcasecmp(cmd, "LoadModule")) {
682
        ERROR("Loadable modules are no longer supported\n");
683 684 685 686
    } else if (!av_strcasecmp(cmd, "NoDefaults")) {
        config->use_defaults = 0;
    } else if (!av_strcasecmp(cmd, "UseDefaults")) {
        config->use_defaults = 1;
687 688 689 690 691
    } else
        ERROR("Incorrect keyword: '%s'\n", cmd);
    return 0;
}

692 693
static int ffserver_parse_config_feed(FFServerConfig *config, const char *cmd,
                                      const char **p, FFServerStream **pfeed)
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
{
    FFServerStream *feed;
    char arg[1024];
    av_assert0(pfeed);
    feed = *pfeed;
    if (!av_strcasecmp(cmd, "<Feed")) {
        char *q;
        FFServerStream *s;
        feed = av_mallocz(sizeof(FFServerStream));
        if (!feed)
            return AVERROR(ENOMEM);
        ffserver_get_arg(feed->filename, sizeof(feed->filename), p);
        q = strrchr(feed->filename, '>');
        if (*q)
            *q = '\0';

        for (s = config->first_feed; s; s = s->next) {
            if (!strcmp(feed->filename, s->filename))
                ERROR("Feed '%s' already registered\n", s->filename);
        }

        feed->fmt = av_guess_format("ffm", NULL, NULL);
        /* default feed file */
        snprintf(feed->feed_filename, sizeof(feed->feed_filename),
                 "/tmp/%s.ffm", feed->filename);
        feed->feed_max_size = 5 * 1024 * 1024;
        feed->is_feed = 1;
        feed->feed = feed; /* self feeding :-) */
        *pfeed = feed;
        return 0;
    }
    av_assert0(feed);
    if (!av_strcasecmp(cmd, "Launch")) {
        int i;

729
        feed->child_argv = av_mallocz_array(MAX_CHILD_ARGS, sizeof(char *));
730 731
        if (!feed->child_argv)
            return AVERROR(ENOMEM);
732
        for (i = 0; i < MAX_CHILD_ARGS - 2; i++) {
733 734 735 736 737 738 739 740 741 742 743
            ffserver_get_arg(arg, sizeof(arg), p);
            if (!arg[0])
                break;

            feed->child_argv[i] = av_strdup(arg);
            if (!feed->child_argv[i])
                return AVERROR(ENOMEM);
        }

        feed->child_argv[i] =
            av_asprintf("http://%s:%d/%s",
744 745 746
                        (config->http_addr.sin_addr.s_addr == INADDR_ANY) ?
                        "127.0.0.1" : inet_ntoa(config->http_addr.sin_addr),
                        ntohs(config->http_addr.sin_port), feed->filename);
747 748 749
        if (!feed->child_argv[i])
            return AVERROR(ENOMEM);
    } else if (!av_strcasecmp(cmd, "ACL")) {
750
        ffserver_parse_acl_row(NULL, feed, NULL, *p, config->filename,
751
                config->line_num);
752 753
    } else if (!av_strcasecmp(cmd, "File") ||
               !av_strcasecmp(cmd, "ReadOnlyFile")) {
754 755 756 757 758 759 760 761
        ffserver_get_arg(feed->feed_filename, sizeof(feed->feed_filename), p);
        feed->readonly = !av_strcasecmp(cmd, "ReadOnlyFile");
    } else if (!av_strcasecmp(cmd, "Truncate")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        /* assume Truncate is true in case no argument is specified */
        if (!arg[0]) {
            feed->truncate = 1;
        } else {
762 763
            WARNING("Truncate N syntax in configuration file is deprecated. "
                    "Use Truncate alone with no arguments.\n");
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
            feed->truncate = strtod(arg, NULL);
        }
    } else if (!av_strcasecmp(cmd, "FileMaxSize")) {
        char *p1;
        double fsize;

        ffserver_get_arg(arg, sizeof(arg), p);
        p1 = arg;
        fsize = strtod(p1, &p1);
        switch(av_toupper(*p1)) {
        case 'K':
            fsize *= 1024;
            break;
        case 'M':
            fsize *= 1024 * 1024;
            break;
        case 'G':
            fsize *= 1024 * 1024 * 1024;
            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 791
                  FFM_PACKET_SIZE*4);
        }
792 793 794 795 796 797 798 799
    } else if (!av_strcasecmp(cmd, "</Feed>")) {
        *pfeed = NULL;
    } else {
        ERROR("Invalid entry '%s' inside <Feed></Feed>\n", cmd);
    }
    return 0;
}

800 801
static int ffserver_parse_config_stream(FFServerConfig *config, const char *cmd,
                                        const char **p,
802
                                        FFServerStream **pstream)
803 804 805
{
    char arg[1024], arg2[1024];
    FFServerStream *stream;
806
    int val;
807 808 809 810 811 812 813 814 815 816

    av_assert0(pstream);
    stream = *pstream;

    if (!av_strcasecmp(cmd, "<Stream")) {
        char *q;
        FFServerStream *s;
        stream = av_mallocz(sizeof(FFServerStream));
        if (!stream)
            return AVERROR(ENOMEM);
817 818 819
        config->dummy_actx = avcodec_alloc_context3(NULL);
        config->dummy_vctx = avcodec_alloc_context3(NULL);
        if (!config->dummy_vctx || !config->dummy_actx) {
820
            av_free(stream);
821 822
            avcodec_free_context(&config->dummy_vctx);
            avcodec_free_context(&config->dummy_actx);
823 824
            return AVERROR(ENOMEM);
        }
825 826
        config->dummy_actx->codec_type = AVMEDIA_TYPE_AUDIO;
        config->dummy_vctx->codec_type = AVMEDIA_TYPE_VIDEO;
827 828 829 830 831 832 833 834 835 836 837 838
        ffserver_get_arg(stream->filename, sizeof(stream->filename), p);
        q = strrchr(stream->filename, '>');
        if (q)
            *q = '\0';

        for (s = config->first_stream; s; s = s->next) {
            if (!strcmp(stream->filename, s->filename))
                ERROR("Stream '%s' already registered\n", s->filename);
        }

        stream->fmt = ffserver_guess_format(NULL, stream->filename, NULL);
        if (stream->fmt) {
839 840
            config->guessed_audio_codec_id = stream->fmt->audio_codec;
            config->guessed_video_codec_id = stream->fmt->video_codec;
841
        } else {
842 843
            config->guessed_audio_codec_id = AV_CODEC_ID_NONE;
            config->guessed_video_codec_id = AV_CODEC_ID_NONE;
844
        }
845
        config->stream_use_defaults = config->use_defaults;
846 847 848 849 850 851 852 853 854 855 856 857 858 859
        *pstream = stream;
        return 0;
    }
    av_assert0(stream);
    if (!av_strcasecmp(cmd, "Feed")) {
        FFServerStream *sfeed;
        ffserver_get_arg(arg, sizeof(arg), p);
        sfeed = config->first_feed;
        while (sfeed) {
            if (!strcmp(sfeed->filename, arg))
                break;
            sfeed = sfeed->next_feed;
        }
        if (!sfeed)
860 861
            ERROR("Feed with name '%s' for stream '%s' is not defined\n", arg,
                    stream->filename);
862 863 864 865 866 867 868 869 870 871
        else
            stream->feed = sfeed;
    } else if (!av_strcasecmp(cmd, "Format")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        if (!strcmp(arg, "status")) {
            stream->stream_type = STREAM_TYPE_STATUS;
            stream->fmt = NULL;
        } else {
            stream->stream_type = STREAM_TYPE_LIVE;
            /* JPEG cannot be used here, so use single frame MJPEG */
872 873 874 875
            if (!strcmp(arg, "jpeg")) {
                strcpy(arg, "singlejpeg");
                stream->single_frame=1;
            }
876 877
            stream->fmt = ffserver_guess_format(arg, NULL, NULL);
            if (!stream->fmt)
878
                ERROR("Unknown Format: '%s'\n", arg);
879 880
        }
        if (stream->fmt) {
881 882
            config->guessed_audio_codec_id = stream->fmt->audio_codec;
            config->guessed_video_codec_id = stream->fmt->video_codec;
883 884 885 886 887
        }
    } else if (!av_strcasecmp(cmd, "InputFormat")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        stream->ifmt = av_find_input_format(arg);
        if (!stream->ifmt)
888
            ERROR("Unknown input format: '%s'\n", arg);
889 890
    } else if (!av_strcasecmp(cmd, "FaviconURL")) {
        if (stream->stream_type == STREAM_TYPE_STATUS)
891 892
            ffserver_get_arg(stream->feed_filename,
                    sizeof(stream->feed_filename), p);
893 894 895 896 897 898 899
        else
            ERROR("FaviconURL only permitted for status streams\n");
    } else if (!av_strcasecmp(cmd, "Author")    ||
               !av_strcasecmp(cmd, "Comment")   ||
               !av_strcasecmp(cmd, "Copyright") ||
               !av_strcasecmp(cmd, "Title")) {
        char key[32];
900
        int i;
901 902 903 904
        ffserver_get_arg(arg, sizeof(arg), p);
        for (i = 0; i < strlen(cmd); i++)
            key[i] = av_tolower(cmd[i]);
        key[i] = 0;
905 906
        WARNING("Deprecated '%s' option in configuration file. Use "
                "'Metadata %s VALUE' instead.\n", cmd, key);
907 908
        if (av_dict_set(&stream->metadata, key, arg, 0) < 0)
            goto nomem;
909 910 911
    } else if (!av_strcasecmp(cmd, "Metadata")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        ffserver_get_arg(arg2, sizeof(arg2), p);
912 913
        if (av_dict_set(&stream->metadata, arg, arg2, 0) < 0)
            goto nomem;
914 915 916 917 918 919 920
    } else if (!av_strcasecmp(cmd, "Preroll")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        stream->prebuffer = atof(arg) * 1000;
    } else if (!av_strcasecmp(cmd, "StartSendOnKey")) {
        stream->send_on_key = 1;
    } else if (!av_strcasecmp(cmd, "AudioCodec")) {
        ffserver_get_arg(arg, sizeof(arg), p);
921
        ffserver_set_codec(config->dummy_actx, arg, config);
922 923
    } else if (!av_strcasecmp(cmd, "VideoCodec")) {
        ffserver_get_arg(arg, sizeof(arg), p);
924
        ffserver_set_codec(config->dummy_vctx, arg, config);
925 926 927 928
    } else if (!av_strcasecmp(cmd, "MaxTime")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        stream->max_time = atof(arg) * 1000;
    } 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 937
    } else if (!av_strcasecmp(cmd, "AudioChannels")) {
        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 941
    } else if (!av_strcasecmp(cmd, "AudioSampleRate")) {
        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 945
    } else if (!av_strcasecmp(cmd, "VideoBitRateRange")) {
        int minrate, maxrate;
946
        char *dash;
947
        ffserver_get_arg(arg, sizeof(arg), p);
948 949 950 951
        dash = strchr(arg, '-');
        if (dash) {
            *dash = '\0';
            dash++;
952 953
            if (ffserver_set_int_param(&minrate, arg,  1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0 &&
                ffserver_set_int_param(&maxrate, dash, 1000, 0, INT_MAX, config, "Invalid %s: '%s'", cmd, arg) >= 0) {
954 955
                if (ffserver_save_avoption_int("minrate", minrate, AV_OPT_FLAG_VIDEO_PARAM, config) < 0 ||
                    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 962
    } else if (!av_strcasecmp(cmd, "Debug")) {
        ffserver_get_arg(arg, sizeof(arg), p);
963 964
        if (ffserver_save_avoption("debug", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 ||
            ffserver_save_avoption("debug", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
965
            goto nomem;
966 967
    } else if (!av_strcasecmp(cmd, "Strict")) {
        ffserver_get_arg(arg, sizeof(arg), p);
968 969
        if (ffserver_save_avoption("strict", arg, AV_OPT_FLAG_AUDIO_PARAM, config) < 0 ||
            ffserver_save_avoption("strict", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
970
            goto nomem;
971 972
    } else if (!av_strcasecmp(cmd, "VideoBufferSize")) {
        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 978
    } else if (!av_strcasecmp(cmd, "VideoBitRateTolerance")) {
        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 984
    } else if (!av_strcasecmp(cmd, "VideoBitRate")) {
        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 994
        if (ret < 0)
            ERROR("Invalid video size '%s'\n", arg);
995 996 997 998
        else {
            if (w % 2 || h % 2)
                WARNING("Image size is not a multiple of 2\n");
            if (ffserver_save_avoption("video_size", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
999
                goto nomem;
1000
        }
1001 1002 1003 1004 1005
    } else if (!av_strcasecmp(cmd, "VideoFrameRate")) {
        ffserver_get_arg(&arg[2], sizeof(arg) - 2, p);
        arg[0] = '1'; arg[1] = '/';
        if (ffserver_save_avoption("time_base", arg, AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
            goto nomem;
1006
    } else if (!av_strcasecmp(cmd, "PixelFormat")) {
1007
        enum AVPixelFormat pix_fmt;
1008
        ffserver_get_arg(arg, sizeof(arg), p);
1009 1010
        pix_fmt = av_get_pix_fmt(arg);
        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 1015
    } else if (!av_strcasecmp(cmd, "VideoGopSize")) {
        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 1026
        if (ffserver_save_avoption("mbd", "+bits",  AV_OPT_FLAG_VIDEO_PARAM, config) < 0 || //FIXME remove
            ffserver_save_avoption("flags", "+mv4", AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
1027
            goto nomem;
1028 1029
    } else if (!av_strcasecmp(cmd, "AVOptionVideo") ||
               !av_strcasecmp(cmd, "AVOptionAudio")) {
1030
        int ret;
1031 1032
        ffserver_get_arg(arg, sizeof(arg), p);
        ffserver_get_arg(arg2, sizeof(arg2), p);
1033
        if (!av_strcasecmp(cmd, "AVOptionVideo"))
1034 1035
            ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_VIDEO_PARAM,
                                         config);
1036
        else
1037 1038
            ret = ffserver_save_avoption(arg, arg2, AV_OPT_FLAG_AUDIO_PARAM,
                                         config);
1039
        if (ret < 0)
1040
            goto nomem;
1041 1042 1043
    } else if (!av_strcasecmp(cmd, "AVPresetVideo") ||
               !av_strcasecmp(cmd, "AVPresetAudio")) {
        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 1049
    } else if (!av_strcasecmp(cmd, "VideoTag")) {
        ffserver_get_arg(arg, sizeof(arg), p);
1050
        if (strlen(arg) == 4 &&
1051 1052
            ffserver_save_avoption_int("codec_tag",
                                       MKTAG(arg[0], arg[1], arg[2], arg[3]),
1053 1054
                                       AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
            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 1066
    } else if (!av_strcasecmp(cmd, "Qscale")) {
        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 1070 1071
        if (ffserver_save_avoption("flags", "+qscale", AV_OPT_FLAG_VIDEO_PARAM, config) < 0 ||
            ffserver_save_avoption_int("global_quality", FF_QP2LAMBDA * val,
                                       AV_OPT_FLAG_VIDEO_PARAM, config) < 0)
1072
            goto nomem;
1073 1074
    } else if (!av_strcasecmp(cmd, "VideoQDiff")) {
        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 1078
    } else if (!av_strcasecmp(cmd, "VideoQMax")) {
        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 1082
    } else if (!av_strcasecmp(cmd, "VideoQMin")) {
        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 1086
    } else if (!av_strcasecmp(cmd, "LumiMask")) {
        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 1090
    } else if (!av_strcasecmp(cmd, "DarkMask")) {
        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 1101 1102 1103 1104 1105 1106 1107
    } else if (!av_strcasecmp(cmd, "DynamicACL")) {
        ffserver_get_arg(stream->dynamic_acl, sizeof(stream->dynamic_acl), p);
    } else if (!av_strcasecmp(cmd, "RTSPOption")) {
        ffserver_get_arg(arg, sizeof(arg), p);
        av_freep(&stream->rtsp_option);
        stream->rtsp_option = av_strdup(arg);
    } else if (!av_strcasecmp(cmd, "MulticastAddress")) {
        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 1111 1112 1113
        stream->is_multicast = 1;
        stream->loop = 1; /* default is looping */
    } else if (!av_strcasecmp(cmd, "MulticastPort")) {
        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 1118
    } else if (!av_strcasecmp(cmd, "MulticastTTL")) {
        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 1123 1124
    } else if (!av_strcasecmp(cmd, "NoLoop")) {
        stream->loop = 0;
    } else if (!av_strcasecmp(cmd, "</Stream>")) {
1125
        config->stream_use_defaults &= 1;
1126
        if (stream->feed && stream->fmt && strcmp(stream->fmt->name, "ffm")) {
1127 1128
            if (config->dummy_actx->codec_id == AV_CODEC_ID_NONE)
                config->dummy_actx->codec_id = config->guessed_audio_codec_id;
1129 1130
            if (!config->no_audio &&
                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 1135
            if (config->dummy_vctx->codec_id == AV_CODEC_ID_NONE)
                config->dummy_vctx->codec_id = config->guessed_video_codec_id;
1136 1137
            if (!config->no_video &&
                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 1143
        av_dict_free(&config->video_opts);
        av_dict_free(&config->audio_opts);
1144 1145
        avcodec_free_context(&config->dummy_vctx);
        avcodec_free_context(&config->dummy_actx);
1146 1147
        config->no_video = 0;
        config->no_audio = 0;
1148
        *pstream = NULL;
1149 1150
    } else if (!av_strcasecmp(cmd, "File") ||
               !av_strcasecmp(cmd, "ReadOnlyFile")) {
1151 1152
        ffserver_get_arg(stream->feed_filename, sizeof(stream->feed_filename),
                p);
1153 1154 1155 1156 1157 1158 1159 1160
    } else if (!av_strcasecmp(cmd, "UseDefaults")) {
        if (config->stream_use_defaults > 1)
            WARNING("Multiple UseDefaults/NoDefaults entries.\n");
        config->stream_use_defaults = 3;
    } else if (!av_strcasecmp(cmd, "NoDefaults")) {
        if (config->stream_use_defaults > 1)
            WARNING("Multiple UseDefaults/NoDefaults entries.\n");
        config->stream_use_defaults = 2;
1161 1162 1163 1164
    } else {
        ERROR("Invalid entry '%s' inside <Stream></Stream>\n", cmd);
    }
    return 0;
1165 1166 1167 1168
  nomem:
    av_log(NULL, AV_LOG_ERROR, "Out of memory. Aborting.\n");
    av_dict_free(&config->video_opts);
    av_dict_free(&config->audio_opts);
1169 1170
    avcodec_free_context(&config->dummy_vctx);
    avcodec_free_context(&config->dummy_actx);
1171
    return AVERROR(ENOMEM);
1172 1173
}

1174 1175
static int ffserver_parse_config_redirect(FFServerConfig *config,
                                          const char *cmd, const char **p,
1176
                                          FFServerStream **predirect)
1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
{
    FFServerStream *redirect;
    av_assert0(predirect);
    redirect = *predirect;

    if (!av_strcasecmp(cmd, "<Redirect")) {
        char *q;
        redirect = av_mallocz(sizeof(FFServerStream));
        if (!redirect)
            return AVERROR(ENOMEM);

        ffserver_get_arg(redirect->filename, sizeof(redirect->filename), p);
        q = strrchr(redirect->filename, '>');
        if (*q)
            *q = '\0';
        redirect->stream_type = STREAM_TYPE_REDIRECT;
        *predirect = redirect;
        return 0;
    }
    av_assert0(redirect);
    if (!av_strcasecmp(cmd, "URL")) {
1198 1199
        ffserver_get_arg(redirect->feed_filename,
                sizeof(redirect->feed_filename), p);
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209
    } else if (!av_strcasecmp(cmd, "</Redirect>")) {
        if (!redirect->feed_filename[0])
            ERROR("No URL found for <Redirect>\n");
        *predirect = NULL;
    } else {
        ERROR("Invalid entry '%s' inside <Redirect></Redirect>\n", cmd);
    }
    return 0;
}

1210 1211 1212 1213 1214 1215
int ffserver_parse_ffconfig(const char *filename, FFServerConfig *config)
{
    FILE *f;
    char line[1024];
    char cmd[64];
    const char *p;
1216 1217
    FFServerStream **last_stream, *stream = NULL, *redirect = NULL;
    FFServerStream **last_feed, *feed = NULL;
1218 1219 1220 1221 1222 1223 1224
    int ret = 0;

    av_assert0(config);

    f = fopen(filename, "r");
    if (!f) {
        ret = AVERROR(errno);
1225 1226
        av_log(NULL, AV_LOG_ERROR,
                "Could not open the configuration file '%s'\n", filename);
1227 1228 1229 1230 1231
        return ret;
    }

    config->first_stream = NULL;
    config->first_feed = NULL;
1232
    config->errors = config->warnings = 0;
1233

1234 1235 1236 1237 1238
    last_stream = &config->first_stream;
    last_feed = &config->first_feed;

    config->line_num = 0;
    while (fgets(line, sizeof(line), f) != NULL) {
1239
        config->line_num++;
1240 1241 1242 1243 1244 1245 1246 1247
        p = line;
        while (av_isspace(*p))
            p++;
        if (*p == '\0' || *p == '#')
            continue;

        ffserver_get_arg(cmd, sizeof(cmd), &p);

1248 1249 1250
        if (feed || !av_strcasecmp(cmd, "<Feed")) {
            int opening = !av_strcasecmp(cmd, "<Feed");
            if (opening && (stream || feed || redirect)) {
1251 1252
                ERROR("Already in a tag\n");
            } else {
1253 1254
                ret = ffserver_parse_config_feed(config, cmd, &p, &feed);
                if (ret < 0)
1255
                    break;
1256
                if (opening) {
1257
                    /* add in stream & feed list */
1258 1259
                    *last_stream = feed;
                    *last_feed = feed;
1260
                    last_stream = &feed->next;
1261
                    last_feed = &feed->next_feed;
1262 1263
                }
            }
1264 1265 1266
        } else if (stream || !av_strcasecmp(cmd, "<Stream")) {
            int opening = !av_strcasecmp(cmd, "<Stream");
            if (opening && (stream || feed || redirect)) {
1267 1268
                ERROR("Already in a tag\n");
            } else {
1269 1270
                ret = ffserver_parse_config_stream(config, cmd, &p, &stream);
                if (ret < 0)
1271 1272 1273 1274 1275
                    break;
                if (opening) {
                    /* add in stream list */
                    *last_stream = stream;
                    last_stream = &stream->next;
1276 1277
                }
            }
1278 1279 1280
        } else if (redirect || !av_strcasecmp(cmd, "<Redirect")) {
            int opening = !av_strcasecmp(cmd, "<Redirect");
            if (opening && (stream || feed || redirect))
1281
                ERROR("Already in a tag\n");
1282
            else {
1283 1284 1285
                ret = ffserver_parse_config_redirect(config, cmd, &p,
                                                     &redirect);
                if (ret < 0)
1286 1287 1288 1289 1290
                    break;
                if (opening) {
                    /* add in stream list */
                    *last_stream = redirect;
                    last_stream = &redirect->next;
1291 1292 1293
                }
            }
        } else {
1294
            ffserver_parse_config_global(config, cmd, &p);
1295 1296
        }
    }
1297
    if (stream || feed || redirect)
1298 1299
        ERROR("Missing closing </%s> tag\n",
              stream ? "Stream" : (feed ? "Feed" : "Redirect"));
1300 1301 1302 1303

    fclose(f);
    if (ret < 0)
        return ret;
1304
    if (config->errors)
1305 1306 1307 1308
        return AVERROR(EINVAL);
    else
        return 0;
}
1309 1310 1311

#undef ERROR
#undef WARNING
1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325

void ffserver_free_child_args(void *argsp)
{
    int i;
    char **args;
    if (!argsp)
        return;
    args = *(char ***)argsp;
    if (!args)
        return;
    for (i = 0; i < MAX_CHILD_ARGS; i++)
        av_free(args[i]);
    av_freep(argsp);
}