Skip to content
Snippets Groups Projects
ffserver.c 142 KiB
Newer Older
  • Learn to ignore specific revisions
  •     for (i = 0; i < req->nb_streams; i++) {
            AVCodecContext *codec = &req->streams[i]->codec;
    
            switch(rates[i]) {
                case 0:
    
                    c->switch_feed_streams[i] = req->feed_streams[i];
    
                    c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 2);
    
                    /* Wants off or slow */
                    c->switch_feed_streams[i] = find_stream_in_feed(req->feed, codec, codec->bit_rate / 4);
    #ifdef WANTS_OFF
                    /* This doesn't work well when it turns off the only stream! */
                    c->switch_feed_streams[i] = -2;
                    c->feed_streams[i] = -2;
    #endif
    
            if (c->switch_feed_streams[i] >= 0 && c->switch_feed_streams[i] != c->feed_streams[i])
                action_required = 1;
        }
    
        return action_required;
    }
    
    static void do_switch_stream(HTTPContext *c, int i)
    {
        if (c->switch_feed_streams[i] >= 0) {
    #ifdef PHILIP        
            c->feed_streams[i] = c->switch_feed_streams[i];
    #endif
    
            /* Now update the stream */
    
        c->switch_feed_streams[i] = -1;
    
    /* XXX: factorize in utils.c ? */
    /* XXX: take care with different space meaning */
    static void skip_spaces(const char **pp)
    {
        const char *p;
        p = *pp;
        while (*p == ' ' || *p == '\t')
            p++;
        *pp = p;
    }
    
    static void get_word(char *buf, int buf_size, const char **pp)
    {
        const char *p;
        char *q;
    
        p = *pp;
        skip_spaces(&p);
        q = buf;
        while (!isspace(*p) && *p != '\0') {
            if ((q - buf) < buf_size - 1)
                *q++ = *p;
            p++;
        }
        if (buf_size > 0)
            *q = '\0';
        *pp = p;
    }
    
    
    static int validate_acl(FFStream *stream, HTTPContext *c)
    {
        enum IPAddressAction last_action = IP_DENY;
        IPAddressACL *acl;
        struct in_addr *src = &c->from_addr.sin_addr;
    
        for (acl = stream->acl; acl; acl = acl->next) {
            if (src->s_addr >= acl->first.s_addr && src->s_addr <= acl->last.s_addr) {
                return (acl->action == IP_ALLOW) ? 1 : 0;
            }
            last_action = acl->action;
        }
    
        /* Nothing matched, so return not the last action */
        return (last_action == IP_DENY) ? 1 : 0;
    }
    
    
    /* compute the real filename of a file by matching it without its
       extensions to all the stream filenames */
    static void compute_real_filename(char *filename, int max_size)
    {
        char file1[1024];
        char file2[1024];
        char *p;
        FFStream *stream;
    
        /* compute filename by matching without the file extensions */
        pstrcpy(file1, sizeof(file1), filename);
        p = strrchr(file1, '.');
        if (p)
            *p = '\0';
        for(stream = first_stream; stream != NULL; stream = stream->next) {
            pstrcpy(file2, sizeof(file2), stream->filename);
            p = strrchr(file2, '.');
            if (p)
                *p = '\0';
            if (!strcmp(file1, file2)) {
                pstrcpy(filename, max_size, stream->filename);
                break;
            }
        }
    }
    
    enum RedirType {
        REDIR_NONE,
        REDIR_ASX,
        REDIR_RAM,
        REDIR_ASF,
        REDIR_RTSP,
        REDIR_SDP,
    };
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    /* parse http request and prepare header */
    static int http_parse_request(HTTPContext *c)
    {
        char *p;
        int post;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        char cmd[32];
        char info[1024], *filename;
        char url[1024], *q;
        char protocol[32];
        char msg[1024];
        const char *mime_type;
        FFStream *stream;
    
        char *useragent = 0;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
        p = c->buffer;
    
        get_word(cmd, sizeof(cmd), (const char **)&p);
    
        pstrcpy(c->method, sizeof(c->method), cmd);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        if (!strcmp(cmd, "GET"))
            post = 0;
        else if (!strcmp(cmd, "POST"))
            post = 1;
        else
            return -1;
    
    
        get_word(url, sizeof(url), (const char **)&p);
    
        pstrcpy(c->url, sizeof(c->url), url);
    
        get_word(protocol, sizeof(protocol), (const char **)&p);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        if (strcmp(protocol, "HTTP/1.0") && strcmp(protocol, "HTTP/1.1"))
            return -1;
    
        pstrcpy(c->protocol, sizeof(c->protocol), protocol);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        
        /* find the filename and the optional info string in the request */
        p = url;
        if (*p == '/')
            p++;
        filename = p;
        p = strchr(p, '?');
        if (p) {
    
            pstrcpy(info, sizeof(info), p);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            *p = '\0';
        } else {
            info[0] = '\0';
        }
    
    
        for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
            if (strncasecmp(p, "User-Agent:", 11) == 0) {
                useragent = p + 11;
                if (*useragent && *useragent != '\n' && isspace(*useragent))
                    useragent++;
                break;
            }
            p = strchr(p, '\n');
            if (!p)
                break;
    
            p++;
        }
    
    
        redir_type = REDIR_NONE;
        if (match_ext(filename, "asx")) {
            redir_type = REDIR_ASX;
    
            filename[strlen(filename)-1] = 'f';
    
            (!useragent || strncasecmp(useragent, "NSPlayer", 8) != 0)) {
            /* if this isn't WMP or lookalike, return the redirector file */
    
            redir_type = REDIR_ASF;
        } else if (match_ext(filename, "rpm,ram")) {
            redir_type = REDIR_RAM;
    
            strcpy(filename + strlen(filename)-2, "m");
    
        } else if (match_ext(filename, "rtsp")) {
            redir_type = REDIR_RTSP;
            compute_real_filename(filename, sizeof(url) - 1);
        } else if (match_ext(filename, "sdp")) {
            redir_type = REDIR_SDP;
            compute_real_filename(filename, sizeof(url) - 1);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        stream = first_stream;
        while (stream != NULL) {
    
            if (!strcmp(stream->filename, filename) && validate_acl(stream, c))
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                break;
            stream = stream->next;
        }
        if (stream == NULL) {
            sprintf(msg, "File '%s' not found", url);
            goto send_error;
        }
    
        c->stream = stream;
        memcpy(c->feed_streams, stream->feed_streams, sizeof(c->feed_streams));
        memset(c->switch_feed_streams, -1, sizeof(c->switch_feed_streams));
    
        if (stream->stream_type == STREAM_TYPE_REDIRECT) {
            c->http_error = 301;
            q = c->buffer;
            q += sprintf(q, "HTTP/1.0 301 Moved\r\n");
            q += sprintf(q, "Location: %s\r\n", stream->feed_filename);
            q += sprintf(q, "Content-type: text/html\r\n");
            q += sprintf(q, "\r\n");
            q += sprintf(q, "<html><head><title>Moved</title></head><body>\r\n");
            q += sprintf(q, "You should be <a href=\"%s\">redirected</a>.\r\n", stream->feed_filename);
            q += sprintf(q, "</body></html>\r\n");
    
            /* prepare output buffer */
            c->buffer_ptr = c->buffer;
            c->buffer_end = q;
            c->state = HTTPSTATE_SEND_HEADER;
            return 0;
        }
    
    
        /* If this is WMP, get the rate information */
        if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) {
    
            if (modify_current_stream(c, ratebuf)) {
                for (i = 0; i < sizeof(c->feed_streams) / sizeof(c->feed_streams[0]); i++) {
                    if (c->switch_feed_streams[i] >= 0)
                        do_switch_stream(c, i);
                }
            }
    
        if (post == 0 && stream->stream_type == STREAM_TYPE_LIVE) {
    
            current_bandwidth += stream->bandwidth;
    
        
        if (post == 0 && max_bandwidth < current_bandwidth) {
    
            c->http_error = 200;
            q = c->buffer;
            q += sprintf(q, "HTTP/1.0 200 Server too busy\r\n");
            q += sprintf(q, "Content-type: text/html\r\n");
            q += sprintf(q, "\r\n");
            q += sprintf(q, "<html><head><title>Too busy</title></head><body>\r\n");
            q += sprintf(q, "The server is too busy to serve your request at this time.<p>\r\n");
            q += sprintf(q, "The bandwidth being served (including your stream) is %dkbit/sec, and this exceeds the limit of %dkbit/sec\r\n",
    
            q += sprintf(q, "</body></html>\r\n");
    
            /* prepare output buffer */
            c->buffer_ptr = c->buffer;
            c->buffer_end = q;
            c->state = HTTPSTATE_SEND_HEADER;
            return 0;
        }
        
    
            char *hostinfo = 0;
            
            for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
                if (strncasecmp(p, "Host:", 5) == 0) {
                    hostinfo = p + 5;
                    break;
                }
                p = strchr(p, '\n');
                if (!p)
                    break;
    
                p++;
            }
    
            if (hostinfo) {
                char *eoh;
                char hostbuf[260];
    
                while (isspace(*hostinfo))
                    hostinfo++;
    
                eoh = strchr(hostinfo, '\n');
                if (eoh) {
                    if (eoh[-1] == '\r')
                        eoh--;
    
                    if (eoh - hostinfo < sizeof(hostbuf) - 1) {
                        memcpy(hostbuf, hostinfo, eoh - hostinfo);
                        hostbuf[eoh - hostinfo] = 0;
    
                        c->http_error = 200;
                        q = c->buffer;
    
                            q += sprintf(q, "HTTP/1.0 200 ASX Follows\r\n");
                            q += sprintf(q, "Content-type: video/x-ms-asf\r\n");
                            q += sprintf(q, "\r\n");
                            q += sprintf(q, "<ASX Version=\"3\">\r\n");
                            q += sprintf(q, "<!-- Autogenerated by ffserver -->\r\n");
                            q += sprintf(q, "<ENTRY><REF HREF=\"http://%s/%s%s\"/></ENTRY>\r\n", 
                                    hostbuf, filename, info);
                            q += sprintf(q, "</ASX>\r\n");
    
                            q += sprintf(q, "HTTP/1.0 200 RAM Follows\r\n");
                            q += sprintf(q, "Content-type: audio/x-pn-realaudio\r\n");
                            q += sprintf(q, "\r\n");
                            q += sprintf(q, "# Autogenerated by ffserver\r\n");
                            q += sprintf(q, "http://%s/%s%s\r\n", 
                                    hostbuf, filename, info);
    
                            q += sprintf(q, "HTTP/1.0 200 ASF Redirect follows\r\n");
                            q += sprintf(q, "Content-type: video/x-ms-asf\r\n");
                            q += sprintf(q, "\r\n");
                            q += sprintf(q, "[Reference]\r\n");
                            q += sprintf(q, "Ref1=http://%s/%s%s\r\n", 
                                    hostbuf, filename, info);
    
                            break;
                        case REDIR_RTSP:
                            {
                                char hostname[256], *p;
                                /* extract only hostname */
                                pstrcpy(hostname, sizeof(hostname), hostbuf);
                                p = strrchr(hostname, ':');
                                if (p)
                                    *p = '\0';
                                q += sprintf(q, "HTTP/1.0 200 RTSP Redirect follows\r\n");
                                /* XXX: incorrect mime type ? */
                                q += sprintf(q, "Content-type: application/x-rtsp\r\n");
                                q += sprintf(q, "\r\n");
                                q += sprintf(q, "rtsp://%s:%d/%s\r\n", 
                                             hostname, ntohs(my_rtsp_addr.sin_port), 
                                             filename);
                            }
                            break;
                        case REDIR_SDP:
                            {
                                UINT8 *sdp_data;
                                int sdp_data_size, len;
                                struct sockaddr_in my_addr;
    
                                q += sprintf(q, "HTTP/1.0 200 OK\r\n");
                                q += sprintf(q, "Content-type: application/sdp\r\n");
                                q += sprintf(q, "\r\n");
    
                                len = sizeof(my_addr);
                                getsockname(c->fd, (struct sockaddr *)&my_addr, &len);
                                
                                /* XXX: should use a dynamic buffer */
                                sdp_data_size = prepare_sdp_description(stream, 
                                                                        &sdp_data, 
                                                                        my_addr.sin_addr);
                                if (sdp_data_size > 0) {
                                    memcpy(q, sdp_data, sdp_data_size);
                                    q += sdp_data_size;
                                    *q = '\0';
                                    av_free(sdp_data);
                                }
                            }
                            break;
                        default:
    
    
                        /* prepare output buffer */
                        c->buffer_ptr = c->buffer;
                        c->buffer_end = q;
                        c->state = HTTPSTATE_SEND_HEADER;
                        return 0;
                    }
                }
            }
    
    
            sprintf(msg, "ASX/RAM file not handled");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        }
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        /* XXX: add there authenticate and IP match */
    
        if (post) {
            /* if post, it means a feed is being sent */
            if (!stream->is_feed) {
    
                /* However it might be a status report from WMP! Lets log the data
                 * as it might come in handy one day
                 */
                char *logline = 0;
    
                
                for (p = c->buffer; *p && *p != '\r' && *p != '\n'; ) {
                    if (strncasecmp(p, "Pragma: log-line=", 17) == 0) {
                        logline = p;
                        break;
                    }
    
                    if (strncasecmp(p, "Pragma: client-id=", 18) == 0) {
                        client_id = strtol(p + 18, 0, 10);
                    }
    
                    p = strchr(p, '\n');
                    if (!p)
                        break;
    
                    p++;
                }
    
                if (logline) {
                    char *eol = strchr(logline, '\n');
    
                    logline += 17;
    
                    if (eol) {
                        if (eol[-1] == '\r')
                            eol--;
                        http_log("%.*s\n", eol - logline, logline);
                        c->suppress_log = 1;
                    }
                }
    
    #ifdef DEBUG_WMP
                http_log("\nGot request:\n%s\n", c->buffer);
    
    #endif
    
                if (client_id && extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) {
                    HTTPContext *wmpc;
    
                    /* Now we have to find the client_id */
                    for (wmpc = first_http_ctx; wmpc; wmpc = wmpc->next) {
                        if (wmpc->wmp_client_id == client_id)
                            break;
                    }
    
                    if (wmpc) {
    
                        if (modify_current_stream(wmpc, ratebuf)) {
                            wmpc->switch_pending = 1;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                sprintf(msg, "POST command not handled");
                goto send_error;
            }
            if (http_start_receive_data(c) < 0) {
                sprintf(msg, "could not open feed");
                goto send_error;
            }
            c->http_error = 0;
            c->state = HTTPSTATE_RECEIVE_DATA;
            return 0;
        }
    
    
    #ifdef DEBUG_WMP
    
        if (strcmp(stream->filename + strlen(stream->filename) - 4, ".asf") == 0) {
    
            http_log("\nGot request:\n%s\n", c->buffer);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        if (c->stream->stream_type == STREAM_TYPE_STATUS)
            goto send_stats;
    
        /* open input stream */
        if (open_input_stream(c, info) < 0) {
            sprintf(msg, "Input stream corresponding to '%s' not found", url);
            goto send_error;
        }
    
        /* prepare http header */
        q = c->buffer;
        q += sprintf(q, "HTTP/1.0 200 OK\r\n");
        mime_type = c->stream->fmt->mime_type;
        if (!mime_type)
            mime_type = "application/x-octet_stream";
        q += sprintf(q, "Pragma: no-cache\r\n");
    
        /* for asf, we need extra headers */
    
        if (!strcmp(c->stream->fmt->name,"asf_stream")) {
    
            c->wmp_client_id = random() & 0x7fffffff;
    
    
            q += sprintf(q, "Server: Cougar 4.1.0.3923\r\nCache-Control: no-cache\r\nPragma: client-id=%d\r\nPragma: features=\"broadcast\"\r\n", c->wmp_client_id);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        }
    
        q += sprintf(q, "Content-Type: %s\r\n", mime_type);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        q += sprintf(q, "\r\n");
        
        /* prepare output buffer */
        c->http_error = 0;
        c->buffer_ptr = c->buffer;
        c->buffer_end = q;
        c->state = HTTPSTATE_SEND_HEADER;
        return 0;
     send_error:
        c->http_error = 404;
        q = c->buffer;
        q += sprintf(q, "HTTP/1.0 404 Not Found\r\n");
        q += sprintf(q, "Content-type: %s\r\n", "text/html");
        q += sprintf(q, "\r\n");
        q += sprintf(q, "<HTML>\n");
        q += sprintf(q, "<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n");
        q += sprintf(q, "<BODY>%s</BODY>\n", msg);
        q += sprintf(q, "</HTML>\n");
    
        /* prepare output buffer */
        c->buffer_ptr = c->buffer;
        c->buffer_end = q;
        c->state = HTTPSTATE_SEND_HEADER;
        return 0;
     send_stats:
        compute_stats(c);
        c->http_error = 200; /* horrible : we use this value to avoid
                                going to the send data state */
        c->state = HTTPSTATE_SEND_HEADER;
        return 0;
    }
    
    
    static void fmt_bytecount(ByteIOContext *pb, INT64 count)
    
    {
        static const char *suffix = " kMGTP";
        const char *s;
    
        for (s = suffix; count >= 100000 && s[1]; count /= 1000, s++) {
        }
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    static void compute_stats(HTTPContext *c)
    {
        HTTPContext *c1;
        FFStream *stream;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        time_t ti;
    
        if (url_open_dyn_buf(pb) < 0) {
            /* XXX: return an error ? */
    
            c->buffer_ptr = c->buffer;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
    
        url_fprintf(pb, "HTTP/1.0 200 OK\r\n");
        url_fprintf(pb, "Content-type: %s\r\n", "text/html");
        url_fprintf(pb, "Pragma: no-cache\r\n");
        url_fprintf(pb, "\r\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        
    
        url_fprintf(pb, "<HEAD><TITLE>FFServer Status</TITLE>\n");
    
        if (c->stream->feed_filename) {
    
            url_fprintf(pb, "<link rel=\"shortcut icon\" href=\"%s\">\n", c->stream->feed_filename);
    
        url_fprintf(pb, "</HEAD>\n<BODY>");
        url_fprintf(pb, "<H1>FFServer Status</H1>\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        /* format status */
    
        url_fprintf(pb, "<H2>Available Streams</H2>\n");
        url_fprintf(pb, "<TABLE cellspacing=0 cellpadding=4>\n");
        url_fprintf(pb, "<TR><Th valign=top>Path<th align=left>Served<br>Conns<Th><br>bytes<Th valign=top>Format<Th>Bit rate<br>kbits/s<Th align=left>Video<br>kbits/s<th><br>Codec<Th align=left>Audio<br>kbits/s<th><br>Codec<Th align=left valign=top>Feed\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        stream = first_stream;
        while (stream != NULL) {
    
            char sfilename[1024];
            char *eosf;
    
    
                pstrcpy(sfilename, sizeof(sfilename) - 10, stream->filename);
    
                eosf = sfilename + strlen(sfilename);
                if (eosf - sfilename >= 4) {
                    if (strcmp(eosf - 4, ".asf") == 0) {
                        strcpy(eosf - 4, ".asx");
                    } else if (strcmp(eosf - 3, ".rm") == 0) {
                        strcpy(eosf - 3, ".ram");
    
                        /* generate a sample RTSP director if
                           unicast. Generate an SDP redirector if
                           multicast */
    
                        eosf = strrchr(sfilename, '.');
                        if (!eosf)
                            eosf = sfilename + strlen(sfilename);
    
                        if (stream->is_multicast)
                            strcpy(eosf, ".sdp");
                        else
                            strcpy(eosf, ".rtsp");
    
                url_fprintf(pb, "<TR><TD><A HREF=\"/%s\">%s</A> ", 
    
                url_fprintf(pb, "<td align=right> %d <td align=right> ",
    
                switch(stream->stream_type) {
                case STREAM_TYPE_LIVE:
                    {
                        int audio_bit_rate = 0;
                        int video_bit_rate = 0;
    
    Zdenek Kabelac's avatar
    Zdenek Kabelac committed
                        const char *audio_codec_name = "";
                        const char *video_codec_name = "";
                        const char *audio_codec_name_extra = "";
                        const char *video_codec_name_extra = "";
    
    
                        for(i=0;i<stream->nb_streams;i++) {
                            AVStream *st = stream->streams[i];
                            AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
                            switch(st->codec.codec_type) {
                            case CODEC_TYPE_AUDIO:
                                audio_bit_rate += st->codec.bit_rate;
                                if (codec) {
                                    if (*audio_codec_name)
                                        audio_codec_name_extra = "...";
                                    audio_codec_name = codec->name;
                                }
                                break;
                            case CODEC_TYPE_VIDEO:
                                video_bit_rate += st->codec.bit_rate;
                                if (codec) {
                                    if (*video_codec_name)
                                        video_codec_name_extra = "...";
                                    video_codec_name = codec->name;
                                }
                                break;
                            default:
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                        }
    
                        url_fprintf(pb, "<TD align=center> %s <TD align=right> %d <TD align=right> %d <TD> %s %s <TD align=right> %d <TD> %s %s", 
    
                                     video_bit_rate / 1000, video_codec_name, video_codec_name_extra,
                                     audio_bit_rate / 1000, audio_codec_name, audio_codec_name_extra);
                        if (stream->feed) {
    
                            url_fprintf(pb, "<TD>%s", stream->feed->filename);
    
                            url_fprintf(pb, "<TD>%s", stream->feed_filename);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    }
    
                    url_fprintf(pb, "<TD align=center> - <TD align=right> - <TD align=right> - <td><td align=right> - <TD>\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                }
            }
            stream = stream->next;
        }
    
    
        stream = first_stream;
        while (stream != NULL) {
            if (stream->feed == stream) {
    
                url_fprintf(pb, "<h2>Feed %s</h2>", stream->filename);
    
                if (stream->pid) {
    
                    url_fprintf(pb, "Running as pid %d.\n", stream->pid);
    
    #if defined(linux) && !defined(CONFIG_NOCUTILS)
                    {
                        FILE *pid_stat;
                        char ps_cmd[64];
    
                        /* This is somewhat linux specific I guess */
                        snprintf(ps_cmd, sizeof(ps_cmd), 
                                 "ps -o \"%%cpu,cputime\" --no-headers %d", 
                                 stream->pid);
                        
                        pid_stat = popen(ps_cmd, "r");
                        if (pid_stat) {
                            char cpuperc[10];
                            char cpuused[64];
                            
                            if (fscanf(pid_stat, "%10s %64s", cpuperc, 
                                       cpuused) == 2) {
                                url_fprintf(pb, "Currently using %s%% of the cpu. Total time used %s.\n",
                                             cpuperc, cpuused);
                            }
                            fclose(pid_stat);
    
                url_fprintf(pb, "<table cellspacing=0 cellpadding=4><tr><th>Stream<th>type<th>kbits/s<th align=left>codec<th align=left>Parameters\n");
    
    
                for (i = 0; i < stream->nb_streams; i++) {
                    AVStream *st = stream->streams[i];
                    AVCodec *codec = avcodec_find_encoder(st->codec.codec_id);
                    char *type = "unknown";
    
    
                    switch(st->codec.codec_type) {
                    case CODEC_TYPE_AUDIO:
                        type = "audio";
                        break;
                    case CODEC_TYPE_VIDEO:
                        type = "video";
    
                        sprintf(parameters, "%dx%d, q=%d-%d, fps=%d", st->codec.width, st->codec.height,
                                    st->codec.qmin, st->codec.qmax, st->codec.frame_rate / FRAME_RATE_BASE);
    
                    url_fprintf(pb, "<tr><td align=right>%d<td>%s<td align=right>%d<td>%s<td>%s\n",
    
                            i, type, st->codec.bit_rate/1000, codec ? codec->name : "", parameters);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        
    #if 0
        {
            float avg;
            AVCodecContext *enc;
            char buf[1024];
            
            /* feed status */
            stream = first_feed;
            while (stream != NULL) {
    
                url_fprintf(pb, "<H1>Feed '%s'</H1>\n", stream->filename);
                url_fprintf(pb, "<TABLE>\n");
                url_fprintf(pb, "<TR><TD>Parameters<TD>Frame count<TD>Size<TD>Avg bitrate (kbits/s)\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                for(i=0;i<stream->nb_streams;i++) {
                    AVStream *st = stream->streams[i];
                    FeedData *fdata = st->priv_data;
                    enc = &st->codec;
                
                    avcodec_string(buf, sizeof(buf), enc);
                    avg = fdata->avg_frame_size * (float)enc->rate * 8.0;
                    if (enc->codec->type == CODEC_TYPE_AUDIO && enc->frame_size > 0)
                        avg /= enc->frame_size;
    
                    url_fprintf(pb, "<TR><TD>%s <TD> %d <TD> %Ld <TD> %0.1f\n", 
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                                 buf, enc->frame_number, fdata->data_count, avg / 1000.0);
                }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                stream = stream->next_feed;
            }
        }
    #endif
    
        /* connection status */
    
        url_fprintf(pb, "<H2>Connection Status</H2>\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
    
        url_fprintf(pb, "Number of connections: %d / %d<BR>\n",
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                     nb_connections, nb_max_connections);
    
    
        url_fprintf(pb, "Bandwidth in use: %dk / %dk<BR>\n",
    
        url_fprintf(pb, "<TABLE>\n");
        url_fprintf(pb, "<TR><th>#<th>File<th>IP<th>Proto<th>State<th>Target bits/sec<th>Actual bits/sec<th>Bytes transferred\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        c1 = first_http_ctx;
        i = 0;
    
            int bitrate;
            int j;
    
            bitrate = 0;
    
            if (c1->stream) {
                for (j = 0; j < c1->stream->nb_streams; j++) {
                    if (!c1->stream->feed) {
                        bitrate += c1->stream->streams[j]->codec.bit_rate;
                    } else {
                        if (c1->feed_streams[j] >= 0) {
                            bitrate += c1->stream->feed->streams[c1->feed_streams[j]]->codec.bit_rate;
                        }
                    }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            i++;
            p = inet_ntoa(c1->from_addr.sin_addr);
    
            url_fprintf(pb, "<TR><TD><B>%d</B><TD>%s%s<TD>%s<TD>%s<TD>%s<td align=right>", 
                        i, 
                        c1->stream ? c1->stream->filename : "", 
                        c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "",
                        p, 
                        c1->protocol,
                        http_state[c1->state]);
            fmt_bytecount(pb, bitrate);
            url_fprintf(pb, "<td align=right>");
            fmt_bytecount(pb, compute_datarate(&c1->datarate, c1->data_count) * 8);
            url_fprintf(pb, "<td align=right>");
            fmt_bytecount(pb, c1->data_count);
            url_fprintf(pb, "\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            c1 = c1->next;
        }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        
        /* date */
        ti = time(NULL);
        p = ctime(&ti);
    
        url_fprintf(pb, "<HR size=1 noshade>Generated at %s", p);
        url_fprintf(pb, "</BODY>\n</HTML>\n");
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
    
        len = url_close_dyn_buf(pb, &c->pb_buffer);
        c->buffer_ptr = c->pb_buffer;
        c->buffer_end = c->pb_buffer + len;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    }
    
    
    /* check if the parser needs to be opened for stream i */
    static void open_parser(AVFormatContext *s, int i)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    {
    
        if (!st->codec.codec) {
            codec = avcodec_find_decoder(st->codec.codec_id);
            if (codec && (codec->capabilities & CODEC_CAP_PARSE_ONLY)) {
                st->codec.parse_only = 1;
                if (avcodec_open(&st->codec, codec) < 0) {
                    st->codec.parse_only = 0;
                }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    }
    
    static int open_input_stream(HTTPContext *c, const char *info)
    {
        char buf[128];
        char input_filename[1024];
        AVFormatContext *s;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        INT64 stream_pos;
    
        /* find file name */
        if (c->stream->feed) {
            strcpy(input_filename, c->stream->feed->feed_filename);
            buf_size = FFM_PACKET_SIZE;
            /* compute position (absolute time) */
            if (find_info_tag(buf, sizeof(buf), "date", info)) {
                stream_pos = parse_date(buf, 0);
    
            } else if (find_info_tag(buf, sizeof(buf), "buffer", info)) {
                int prebuffer = strtol(buf, 0, 10);
    
                stream_pos = av_gettime() - prebuffer * (INT64)1000000;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            } else {
    
                stream_pos = av_gettime() - c->stream->prebuffer * (INT64)1000;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            }
        } else {
            strcpy(input_filename, c->stream->feed_filename);
            buf_size = 0;
            /* compute position (relative time) */
            if (find_info_tag(buf, sizeof(buf), "date", info)) {
                stream_pos = parse_date(buf, 1);
            } else {
                stream_pos = 0;
            }
        }
        if (input_filename[0] == '\0')
            return -1;
    
    
    #if 0
        { time_t when = stream_pos / 1000000;
        http_log("Stream pos = %lld, time=%s", stream_pos, ctime(&when));
        }
    #endif
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        /* open stream */
    
        if (av_open_input_file(&s, input_filename, NULL, buf_size, NULL) < 0) {
            http_log("%s not found", input_filename);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            return -1;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        c->fmt_in = s;
    
        
        /* open each parser */
        for(i=0;i<s->nb_streams;i++)
            open_parser(s, i);
    
        /* choose stream as clock source (we favorize video stream if
           present) for packet sending */
        c->pts_stream_index = 0;
        for(i=0;i<c->stream->nb_streams;i++) {
            if (c->pts_stream_index == 0 && 
                c->stream->streams[i]->codec.codec_type == CODEC_TYPE_VIDEO) {
                c->pts_stream_index = i;
            }
        }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
    
        if (c->fmt_in->iformat->read_seek) {
            c->fmt_in->iformat->read_seek(c->fmt_in, stream_pos);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        }
    
        /* set the start time (needed for maxtime and RTP packet timing) */
        c->start_time = cur_time;
        c->first_pts = AV_NOPTS_VALUE;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        return 0;
    }
    
    
    /* currently desactivated because the new PTS handling is not
       satisfactory yet */
    //#define AV_READ_FRAME
    #ifdef AV_READ_FRAME
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    
    
    /* XXX: generalize that in ffmpeg for picture/audio/data. Currently
       the return packet MUST NOT be freed */
    int av_read_frame(AVFormatContext *s, AVPacket *pkt)
    {
        AVStream *st;
        int len, ret, old_nb_streams, i;
    
        /* see if remaining frames must be parsed */
        for(;;) {
            if (s->cur_len > 0) {
                st = s->streams[s->cur_pkt.stream_index];
                len = avcodec_parse_frame(&st->codec, &pkt->data, &pkt->size, 
                                          s->cur_ptr, s->cur_len);
                if (len < 0) {
                    /* error: get next packet */
                    s->cur_len = 0;
                } else {
                    s->cur_ptr += len;
                    s->cur_len -= len;
                    if (pkt->size) {
                        /* init pts counter if not done */
                        if (st->pts.den == 0) {
                            switch(st->codec.codec_type) {
                            case CODEC_TYPE_AUDIO:
                                st->pts_incr = (INT64)s->pts_den;
                                av_frac_init(&st->pts, st->pts.val, 0, 
                                             (INT64)s->pts_num * st->codec.sample_rate);
                                break;
                            case CODEC_TYPE_VIDEO:
                                st->pts_incr = (INT64)s->pts_den * FRAME_RATE_BASE;
                                av_frac_init(&st->pts, st->pts.val, 0,
                                             (INT64)s->pts_num * st->codec.frame_rate);
                                break;
                            default:
                                av_abort();
                            }
                        }
                        
                        /* a frame was read: return it */
                        pkt->pts = st->pts.val;
    #if 0
                        printf("add pts=%Lx num=%Lx den=%Lx incr=%Lx\n",
                               st->pts.val, st->pts.num, st->pts.den, st->pts_incr);
    #endif
                        switch(st->codec.codec_type) {
                        case CODEC_TYPE_AUDIO:
                            av_frac_add(&st->pts, st->pts_incr * st->codec.frame_size);
                            break;
                        case CODEC_TYPE_VIDEO:
                            av_frac_add(&st->pts, st->pts_incr);
                            break;
                        default:
                            av_abort();
                        }
                        pkt->stream_index = s->cur_pkt.stream_index;
                        /* we use the codec indication because it is
                           more accurate than the demux flags */
                        pkt->flags = 0;
    
                        if (st->codec.coded_frame->key_frame) 
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                }
            } else {
    
                /* free previous packet */
                av_free_packet(&s->cur_pkt); 
    
                old_nb_streams = s->nb_streams;
                ret = av_read_packet(s, &s->cur_pkt);
                if (ret)
                    return ret;
                /* open parsers for each new streams */
                for(i = old_nb_streams; i < s->nb_streams; i++)
                    open_parser(s, i);
                st = s->streams[s->cur_pkt.stream_index];
    
                /* update current pts (XXX: dts handling) from packet, or
                   use current pts if none given */
                if (s->cur_pkt.pts != AV_NOPTS_VALUE) {
                    av_frac_set(&st->pts, s->cur_pkt.pts);
                } else {
                    s->cur_pkt.pts = st->pts.val;
                }
                if (!st->codec.codec) {
                    /* no codec opened: just return the raw packet */
                    *pkt = s->cur_pkt;
    
                    /* no codec opened: just update the pts by considering we
                       have one frame and free the packet */
                    if (st->pts.den == 0) {
                        switch(st->codec.codec_type) {
                        case CODEC_TYPE_AUDIO:
                            st->pts_incr = (INT64)s->pts_den * st->codec.frame_size;
                            av_frac_init(&st->pts, st->pts.val, 0,