Skip to content
Snippets Groups Projects
ffserver.c 146 KiB
Newer Older
  • Learn to ignore specific revisions
  •             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_t)s->pts_den * st->codec.frame_size;
    
                                         (int64_t)s->pts_num * st->codec.sample_rate);
    
                            st->pts_incr = (int64_t)s->pts_den * st->codec.frame_rate_base;
    
                                         (int64_t)s->pts_num * st->codec.frame_rate);
    
                            break;
                        default:
                            av_abort();
                        }
                    }
                    av_frac_add(&st->pts, st->pts_incr);
                    return 0;
                } else {
                    s->cur_ptr = s->cur_pkt.data;
                    s->cur_len = s->cur_pkt.size;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                }
            }
    
        int64_t cur_pts, delta_pts, next_pts;
    
        int delay1;
        
        /* compute current pts value from system time */
    
        cur_pts = ((int64_t)(cur_time - c->start_time) * c->fmt_in->pts_den) / 
    
            (c->fmt_in->pts_num * 1000LL);
        /* compute the delta from the stream we choose as
           main clock (we do that to avoid using explicit
           buffers to do exact packet reordering for each
           stream */
        /* XXX: really need to fix the number of streams */
        if (c->pts_stream_index >= c->fmt_in->nb_streams)
            next_pts = cur_pts;
        else
            next_pts = c->fmt_in->streams[c->pts_stream_index]->pts.val;
        delta_pts = next_pts - cur_pts;
        if (delta_pts <= 0) {
            delay1 = 0;
        } else {
            delay1 = (delta_pts * 1000 * c->fmt_in->pts_num) / c->fmt_in->pts_den;
        }
        return delay1;
    }
    #else
    
    /* just fall backs */
    
    static int av_read_frame(AVFormatContext *s, AVPacket *pkt)
    
    {
        return av_read_packet(s, pkt);
    }
    
    static int compute_send_delay(HTTPContext *c)
    {
    
        int datarate = 8 * get_longterm_datarate(&c->datarate, c->data_count); 
    
        if (datarate > c->stream->bandwidth * 2000) {
    
        if(!c->stream->feed && c->first_pts!=AV_NOPTS_VALUE) {
          time_pts = ((int64_t)(cur_time - c->start_time) * c->fmt_in->pts_den) / 
            ((int64_t) c->fmt_in->pts_num*1000);
          delta_pts = c->cur_pts - time_pts;
          m_delay = (delta_pts * 1000 * c->fmt_in->pts_num) / c->fmt_in->pts_den;
          return m_delay>0 ? m_delay : 0;
        } else return 0;
    
    }
    
    #endif
        
    static int http_prepare_data(HTTPContext *c)
    {
        int i, len, ret;
        AVFormatContext *ctx;
    
        switch(c->state) {
        case HTTPSTATE_SEND_DATA_HEADER:
            memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx));
            pstrcpy(c->fmt_ctx.author, sizeof(c->fmt_ctx.author), 
                    c->stream->author);
            pstrcpy(c->fmt_ctx.comment, sizeof(c->fmt_ctx.comment), 
                    c->stream->comment);
            pstrcpy(c->fmt_ctx.copyright, sizeof(c->fmt_ctx.copyright), 
                    c->stream->copyright);
            pstrcpy(c->fmt_ctx.title, sizeof(c->fmt_ctx.title), 
                    c->stream->title);
    
            /* open output stream by using specified codecs */
            c->fmt_ctx.oformat = c->stream->fmt;
            c->fmt_ctx.nb_streams = c->stream->nb_streams;
            for(i=0;i<c->fmt_ctx.nb_streams;i++) {
                AVStream *st;
                st = av_mallocz(sizeof(AVStream));
                c->fmt_ctx.streams[i] = st;
                /* if file or feed, then just take streams from FFStream struct */
                if (!c->stream->feed || 
                    c->stream->feed == c->stream)
                    memcpy(st, c->stream->streams[i], sizeof(AVStream));
                else
                    memcpy(st, c->stream->feed->streams[c->stream->feed_streams[i]],
                               sizeof(AVStream));
                st->codec.frame_number = 0; /* XXX: should be done in
                                               AVStream, not in codec */
    
                /* I'm pretty sure that this is not correct...
                 * However, without it, we crash
                 */
                st->codec.coded_frame = &dummy_frame;
    
            }
            c->got_key_frame = 0;
    
            /* prepare header and save header data in a stream */
            if (url_open_dyn_buf(&c->fmt_ctx.pb) < 0) {
                /* XXX: potential leak */
                return -1;
            }
            c->fmt_ctx.pb.is_streamed = 1;
    
    
            av_set_parameters(&c->fmt_ctx, NULL);
    
            av_write_header(&c->fmt_ctx);
    
            len = url_close_dyn_buf(&c->fmt_ctx.pb, &c->pb_buffer);
            c->buffer_ptr = c->pb_buffer;
            c->buffer_end = c->pb_buffer + len;
    
            c->state = HTTPSTATE_SEND_DATA;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            c->last_packet_sent = 0;
            break;
        case HTTPSTATE_SEND_DATA:
            /* find a new packet */
            {
                AVPacket pkt;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                /* read a packet from the input stream */
                if (c->stream->feed) {
                    ffm_set_write_index(c->fmt_in, 
                                        c->stream->feed->feed_write_index,
                                        c->stream->feed->feed_size);
                }
    
                    c->stream->max_time + c->start_time - cur_time < 0) {
    
                    /* We have timed out */
                    c->state = HTTPSTATE_SEND_DATA_TRAILER;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                } else {
    
                        if (compute_send_delay(c) > 0) {
                            c->state = HTTPSTATE_WAIT;
                            return 1; /* state changed */
                        }
                    }
    
                    if (av_read_frame(c->fmt_in, &pkt) < 0) {
                        if (c->stream->feed && c->stream->feed->feed_opened) {
                            /* if coming from feed, it means we reached the end of the
                               ffm file, so must wait for more data */
                            c->state = HTTPSTATE_WAIT_FEED;
                            return 1; /* state changed */
                        } else {
    
                            if (c->stream->loop) {
                                av_close_input_file(c->fmt_in);
                                c->fmt_in = NULL;
                                if (open_input_stream(c, "") < 0)
                                    goto no_loop;
                                goto redo;
                            } else {
                            no_loop:
                                /* must send trailer now because eof or error */
                                c->state = HTTPSTATE_SEND_DATA_TRAILER;
                            }
    
                        /* send it to the appropriate stream */
                        if (c->stream->feed) {
                            /* if coming from a feed, select the right stream */
                            if (c->switch_pending) {
                                c->switch_pending = 0;
                                for(i=0;i<c->stream->nb_streams;i++) {
                                    if (c->switch_feed_streams[i] == pkt.stream_index) {
                                        if (pkt.flags & PKT_FLAG_KEY) {
                                            do_switch_stream(c, i);
                                        }
                                    }
                                    if (c->switch_feed_streams[i] >= 0) {
                                        c->switch_pending = 1;
                                    }
                                }
                            }
    
                            for(i=0;i<c->stream->nb_streams;i++) {
    
                                if (c->feed_streams[i] == pkt.stream_index) {
                                    pkt.stream_index = i;
    
                                    if (pkt.flags & PKT_FLAG_KEY) {
    
                                        c->got_key_frame |= 1 << i;
                                    }
                                    /* See if we have all the key frames, then 
                                     * we start to send. This logic is not quite
                                     * right, but it works for the case of a 
                                     * single video stream with one or more
                                     * audio streams (for which every frame is 
                                     * typically a key frame). 
                                     */
                                    if (!c->stream->send_on_key || 
                                        ((c->got_key_frame + 1) >> c->stream->nb_streams)) {
                                        goto send_it;
    
                        } else {
                            AVCodecContext *codec;
                            
                        send_it:
                            /* specific handling for RTP: we use several
                               output stream (one for each RTP
                               connection). XXX: need more abstract handling */
                            if (c->is_packetized) {
                                c->packet_stream_index = pkt.stream_index;
                                ctx = c->rtp_ctx[c->packet_stream_index];
    
                                /* only one stream per RTP connection */
                                pkt.stream_index = 0;
    
                            } else {
                                ctx = &c->fmt_ctx;
                                /* Fudge here */
                                codec = &ctx->streams[pkt.stream_index]->codec;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                            }
    
                            codec->coded_frame->key_frame = ((pkt.flags & PKT_FLAG_KEY) != 0);
    
                            if (codec->codec_type == CODEC_TYPE_AUDIO) {
                                codec->frame_size = (codec->sample_rate * pkt.duration + 500000) / 1000000;
                                /* printf("Calculated size %d, from sr %d, duration %d\n", codec->frame_size, codec->sample_rate, pkt.duration); */
                            }
    #endif
                            
                            if (c->is_packetized) {
                                ret = url_open_dyn_packet_buf(&ctx->pb, 
                                                              url_get_max_packet_size(c->rtp_handles[c->packet_stream_index]));
                                c->packet_byte_count = 0;
                                c->packet_start_time_us = av_gettime();
                            } else {
                                ret = url_open_dyn_buf(&ctx->pb);
                            }
                            if (ret < 0) {
                                /* XXX: potential leak */
                                return -1;
                            }
    
                            if (av_write_frame(ctx, pkt.stream_index, pkt.data, pkt.size)) {
    
                                c->state = HTTPSTATE_SEND_DATA_TRAILER;
                            }
                            
                            len = url_close_dyn_buf(&ctx->pb, &c->pb_buffer);
                            c->buffer_ptr = c->pb_buffer;
                            c->buffer_end = c->pb_buffer + len;
                            
                            codec->frame_number++;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    }
                }
            }
            break;
        default:
        case HTTPSTATE_SEND_DATA_TRAILER:
            /* last packet test ? */
    
            if (c->last_packet_sent || c->is_packetized)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                return -1;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            /* prepare header */
    
            if (url_open_dyn_buf(&ctx->pb) < 0) {
                /* XXX: potential leak */
                return -1;
            }
            av_write_trailer(ctx);
            len = url_close_dyn_buf(&ctx->pb, &c->pb_buffer);
            c->buffer_ptr = c->pb_buffer;
            c->buffer_end = c->pb_buffer + len;
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            c->last_packet_sent = 1;
            break;
        }
        return 0;
    }
    
    
    /* in bit/s */
    #define SHORT_TERM_BANDWIDTH 8000000
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    /* should convert the format at the same time */
    
    static int http_send_data(HTTPContext *c)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    {
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        while (c->buffer_ptr >= c->buffer_end) {
    
            ret = http_prepare_data(c);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            if (ret < 0)
                return -1;
            else if (ret == 0) {
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            } else {
                /* state change requested */
                return 0;
            }
        }
    
    
        if (c->buffer_ptr < c->buffer_end) {
            if (c->is_packetized) {
                /* RTP/UDP data output */
                len = c->buffer_end - c->buffer_ptr;
                if (len < 4) {
                    /* fail safe - should never happen */
                fail1:
                    c->buffer_ptr = c->buffer_end;
                    return 0;
                }
                len = (c->buffer_ptr[0] << 24) |
                    (c->buffer_ptr[1] << 16) |
                    (c->buffer_ptr[2] << 8) |
                    (c->buffer_ptr[3]);
                if (len > (c->buffer_end - c->buffer_ptr))
                    goto fail1;
                
                /* short term bandwidth limitation */
                dt = av_gettime() - c->packet_start_time_us;
                if (dt < 1)
                    dt = 1;
    
    
                if ((c->packet_byte_count + len) * (int64_t)1000000 >= 
                    (SHORT_TERM_BANDWIDTH / 8) * (int64_t)dt) {
    
                    /* bandwidth overflow : wait at most one tick and retry */
                    c->state = HTTPSTATE_WAIT_SHORT;
                    return 0;
    
    
                c->buffer_ptr += 4;
                url_write(c->rtp_handles[c->packet_stream_index], 
                          c->buffer_ptr, len);
    
                c->packet_byte_count += len;
            } else {
                /* TCP data output */
                len = write(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
                if (len < 0) {
                    if (errno != EAGAIN && errno != EINTR) {
                        /* error : close connection */
                        return -1;
                    } else {
                        return 0;
                    }
                } else {
                    c->buffer_ptr += len;
                }
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            }
    
            c->data_count += len;
            update_datarate(&c->datarate, c->data_count);
            if (c->stream)
                c->stream->bytes_served += len;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        }
        return 0;
    }
    
    static int http_start_receive_data(HTTPContext *c)
    {
        int fd;
    
        if (c->stream->feed_opened)
            return -1;
    
    
        /* Don't permit writing to this one */
        if (c->stream->readonly)
            return -1;
    
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        /* open feed */
        fd = open(c->stream->feed_filename, O_RDWR);
        if (fd < 0)
            return -1;
        c->feed_fd = fd;
        
        c->stream->feed_write_index = ffm_read_write_index(fd);
        c->stream->feed_size = lseek(fd, 0, SEEK_END);
        lseek(fd, 0, SEEK_SET);
    
        /* init buffer input */
        c->buffer_ptr = c->buffer;
        c->buffer_end = c->buffer + FFM_PACKET_SIZE;
        c->stream->feed_opened = 1;
        return 0;
    }
        
    static int http_receive_data(HTTPContext *c)
    {
        HTTPContext *c1;
    
    
        if (c->buffer_end > c->buffer_ptr) {
            int len;
    
            len = read(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr);
            if (len < 0) {
                if (errno != EAGAIN && errno != EINTR) {
                    /* error : close connection */
                    goto fail;
                }
            } else if (len == 0) {
                /* end of connection : close it */
                goto fail;
            } else {
                c->buffer_ptr += len;
                c->data_count += len;
    
                update_datarate(&c->datarate, c->data_count);
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        if (c->buffer_ptr >= c->buffer_end) {
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            /* a packet has been received : write it in the store, except
               if header */
            if (c->data_count > FFM_PACKET_SIZE) {
                
                //            printf("writing pos=0x%Lx size=0x%Lx\n", feed->feed_write_index, feed->feed_size);
                /* XXX: use llseek or url_seek */
                lseek(c->feed_fd, feed->feed_write_index, SEEK_SET);
                write(c->feed_fd, c->buffer, FFM_PACKET_SIZE);
                
                feed->feed_write_index += FFM_PACKET_SIZE;
                /* update file size */
                if (feed->feed_write_index > c->stream->feed_size)
                    feed->feed_size = feed->feed_write_index;
    
                /* handle wrap around if max file size reached */
                if (feed->feed_write_index >= c->stream->feed_max_size)
                    feed->feed_write_index = FFM_PACKET_SIZE;
    
                /* write index */
                ffm_write_write_index(c->feed_fd, feed->feed_write_index);
    
                /* wake up any waiting connections */
                for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) {
                    if (c1->state == HTTPSTATE_WAIT_FEED && 
                        c1->stream->feed == c->stream->feed) {
                        c1->state = HTTPSTATE_SEND_DATA;
                    }
                }
    
            } else {
                /* We have a header in our hands that contains useful data */
                AVFormatContext s;
    
                AVInputFormat *fmt_in;
    
                ByteIOContext *pb = &s.pb;
                int i;
    
                memset(&s, 0, sizeof(s));
    
                url_open_buf(pb, c->buffer, c->buffer_end - c->buffer, URL_RDONLY);
                pb->buf_end = c->buffer_end;        /* ?? */
                pb->is_streamed = 1;
    
    
                /* use feed output format name to find corresponding input format */
                fmt_in = av_find_input_format(feed->fmt->name);
                if (!fmt_in)
                    goto fail;
    
    
                if (fmt_in->priv_data_size > 0) {
                    s.priv_data = av_mallocz(fmt_in->priv_data_size);
                    if (!s.priv_data)
                        goto fail;
    	    } else
    	        s.priv_data = NULL;
    
                if (fmt_in->read_header(&s, 0) < 0) {
    
                    goto fail;
                }
    
                /* Now we have the actual streams */
                if (s.nb_streams != feed->nb_streams) {
    
                    memcpy(&feed->streams[i]->codec, 
                           &s.streams[i]->codec, sizeof(AVCodecContext));
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
            }
            c->buffer_ptr = c->buffer;
        }
    
        return 0;
     fail:
        c->stream->feed_opened = 0;
        close(c->feed_fd);
        return -1;
    }
    
    
    /********************************************************************/
    /* RTSP handling */
    
    static void rtsp_reply_header(HTTPContext *c, enum RTSPStatusCode error_number)
    {
        const char *str;
        time_t ti;
        char *p;
        char buf2[32];
    
        switch(error_number) {
    #define DEF(n, c, s) case c: str = s; break; 
    #include "rtspcodes.h"
    #undef DEF
        default:
            str = "Unknown Error";
            break;
        }
         
        url_fprintf(c->pb, "RTSP/1.0 %d %s\r\n", error_number, str);
        url_fprintf(c->pb, "CSeq: %d\r\n", c->seq);
    
        /* output GMT time */
        ti = time(NULL);
        p = ctime(&ti);
        strcpy(buf2, p);
        p = buf2 + strlen(p) - 1;
        if (*p == '\n')
            *p = '\0';
        url_fprintf(c->pb, "Date: %s GMT\r\n", buf2);
    }
    
    static void rtsp_reply_error(HTTPContext *c, enum RTSPStatusCode error_number)
    {
        rtsp_reply_header(c, error_number);
        url_fprintf(c->pb, "\r\n");
    }
    
    static int rtsp_parse_request(HTTPContext *c)
    {
        const char *p, *p1, *p2;
        char cmd[32];
        char url[1024];
        char protocol[32];
        char line[1024];
        ByteIOContext pb1;
        int len;
        RTSPHeader header1, *header = &header1;
        
        c->buffer_ptr[0] = '\0';
        p = c->buffer;
        
        get_word(cmd, sizeof(cmd), &p);
        get_word(url, sizeof(url), &p);
        get_word(protocol, sizeof(protocol), &p);
    
        pstrcpy(c->method, sizeof(c->method), cmd);
        pstrcpy(c->url, sizeof(c->url), url);
        pstrcpy(c->protocol, sizeof(c->protocol), protocol);
    
        c->pb = &pb1;
        if (url_open_dyn_buf(c->pb) < 0) {
            /* XXX: cannot do more */
            c->pb = NULL; /* safety */
            return -1;
        }
    
        /* check version name */
        if (strcmp(protocol, "RTSP/1.0") != 0) {
            rtsp_reply_error(c, RTSP_STATUS_VERSION);
            goto the_end;
        }
    
        /* parse each header line */
        memset(header, 0, sizeof(RTSPHeader));
        /* skip to next line */
        while (*p != '\n' && *p != '\0')
            p++;
        if (*p == '\n')
            p++;
        while (*p != '\0') {
            p1 = strchr(p, '\n');
            if (!p1)
                break;
            p2 = p1;
            if (p2 > p && p2[-1] == '\r')
                p2--;
            /* skip empty line */
            if (p2 == p)
                break;
            len = p2 - p;
            if (len > sizeof(line) - 1)
                len = sizeof(line) - 1;
            memcpy(line, p, len);
            line[len] = '\0';
            rtsp_parse_line(header, line);
            p = p1 + 1;
        }
    
        /* handle sequence number */
        c->seq = header->seq;
    
        if (!strcmp(cmd, "DESCRIBE")) {
            rtsp_cmd_describe(c, url);
    
        } else if (!strcmp(cmd, "OPTIONS")) {
            rtsp_cmd_options(c, url);
    
        } else if (!strcmp(cmd, "SETUP")) {
            rtsp_cmd_setup(c, url, header);
        } else if (!strcmp(cmd, "PLAY")) {
            rtsp_cmd_play(c, url, header);
        } else if (!strcmp(cmd, "PAUSE")) {
            rtsp_cmd_pause(c, url, header);
        } else if (!strcmp(cmd, "TEARDOWN")) {
            rtsp_cmd_teardown(c, url, header);
        } else {
            rtsp_reply_error(c, RTSP_STATUS_METHOD);
        }
     the_end:
        len = url_close_dyn_buf(c->pb, &c->pb_buffer);
        c->pb = NULL; /* safety */
        if (len < 0) {
            /* XXX: cannot do more */
            return -1;
        }
        c->buffer_ptr = c->pb_buffer;
        c->buffer_end = c->pb_buffer + len;
        c->state = RTSPSTATE_SEND_REPLY;
        return 0;
    }
    
    
    /* XXX: move that to rtsp.c, but would need to replace FFStream by
       AVFormatContext */
    
    static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, 
    
        int i, payload_type, port, private_payload_type, j;
    
        const char *ipstr, *title, *mediatype;
        AVStream *st;
        
        if (url_open_dyn_buf(pb) < 0)
            return -1;
        
        /* general media info */
    
        url_fprintf(pb, "v=0\n");
    
        url_fprintf(pb, "o=- 0 0 IN IP4 %s\n", ipstr);
        title = stream->title;
        if (title[0] == '\0')
            title = "No Title";
        url_fprintf(pb, "s=%s\n", title);
        if (stream->comment[0] != '\0')
            url_fprintf(pb, "i=%s\n", stream->comment);
    
        if (stream->is_multicast) {
            url_fprintf(pb, "c=IN IP4 %s\n", inet_ntoa(stream->multicast_ip));
        }
    
        /* for each stream, we output the necessary info */
    
        for(i = 0; i < stream->nb_streams; i++) {
            st = stream->streams[i];
            switch(st->codec.codec_type) {
            case CODEC_TYPE_AUDIO:
                mediatype = "audio";
                break;
            case CODEC_TYPE_VIDEO:
                mediatype = "video";
                break;
            default:
                mediatype = "application";
                break;
            }
    
            /* NOTE: the port indication is not correct in case of
               unicast. It is not an issue because RTSP gives it */
    
            payload_type = rtp_get_payload_type(&st->codec);
    
            if (payload_type < 0)
                payload_type = private_payload_type++;
    
            if (stream->is_multicast) {
                port = stream->multicast_port + 2 * i;
            } else {
                port = 0;
            }
    
            if (payload_type >= 96) {
                /* for private payload type, we need to give more info */
                switch(st->codec.codec_id) {
                case CODEC_ID_MPEG4:
                    {
                        uint8_t *data;
                        url_fprintf(pb, "a=rtpmap:%d MP4V-ES/%d\n", 
                                    payload_type, 90000);
                        /* we must also add the mpeg4 header */
                        data = st->codec.extradata;
                        if (data) {
    
                            for(j=0;j<st->codec.extradata_size;j++) {
                                url_fprintf(pb, "%02x", data[j]);
                            }
                            url_fprintf(pb, "\n");
                        }
                    }
                    break;
                default:
                    /* XXX: add other codecs ? */
                    goto fail;
                }
            }
    
            url_fprintf(pb, "a=control:streamid=%d\n", i);
        }
        return url_close_dyn_buf(pb, pbuffer);
    
     fail:
        url_close_dyn_buf(pb, pbuffer);
        av_free(*pbuffer);
        return -1;
    
    static void rtsp_cmd_options(HTTPContext *c, const char *url)
    {
    //    rtsp_reply_header(c, RTSP_STATUS_OK);
        url_fprintf(c->pb, "RTSP/1.0 %d %s\r\n", RTSP_STATUS_OK, "OK");
        url_fprintf(c->pb, "CSeq: %d\r\n", c->seq);
        url_fprintf(c->pb, "Public: %s\r\n", "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE");
        url_fprintf(c->pb, "\r\n");
    }
    
    
    static void rtsp_cmd_describe(HTTPContext *c, const char *url)
    {
        FFStream *stream;
        char path1[1024];
        const char *path;
    
        uint8_t *content;
    
        int content_length, len;
        struct sockaddr_in my_addr;
    
        
        /* find which url is asked */
        url_split(NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
        path = path1;
        if (*path == '/')
            path++;
    
        for(stream = first_stream; stream != NULL; stream = stream->next) {
            if (!stream->is_feed && stream->fmt == &rtp_mux &&
                !strcmp(path, stream->filename)) {
                goto found;
            }
        }
        /* no stream found */
        rtsp_reply_error(c, RTSP_STATUS_SERVICE); /* XXX: right error ? */
        return;
    
     found:
        /* prepare the media description in sdp format */
    
    
        /* get the host IP */
        len = sizeof(my_addr);
        getsockname(c->fd, (struct sockaddr *)&my_addr, &len);
        
        content_length = prepare_sdp_description(stream, &content, my_addr.sin_addr);
    
        if (content_length < 0) {
            rtsp_reply_error(c, RTSP_STATUS_INTERNAL);
            return;
        }
        rtsp_reply_header(c, RTSP_STATUS_OK);
        url_fprintf(c->pb, "Content-Type: application/sdp\r\n");
        url_fprintf(c->pb, "Content-Length: %d\r\n", content_length);
        url_fprintf(c->pb, "\r\n");
        put_buffer(c->pb, content, content_length);
    }
    
    static HTTPContext *find_rtp_session(const char *session_id)
    {
        HTTPContext *c;
    
        if (session_id[0] == '\0')
            return NULL;
    
        for(c = first_http_ctx; c != NULL; c = c->next) {
            if (!strcmp(c->session_id, session_id))
                return c;
        }
        return NULL;
    }
    
    
    static RTSPTransportField *find_transport(RTSPHeader *h, enum RTSPProtocol protocol)
    
    {
        RTSPTransportField *th;
        int i;
    
        for(i=0;i<h->nb_transports;i++) {
            th = &h->transports[i];
            if (th->protocol == protocol)
                return th;
        }
        return NULL;
    }
    
    static void rtsp_cmd_setup(HTTPContext *c, const char *url, 
                               RTSPHeader *h)
    {
        FFStream *stream;
        int stream_index, port;
        char buf[1024];
        char path1[1024];
        const char *path;
        HTTPContext *rtp_c;
        RTSPTransportField *th;
        struct sockaddr_in dest_addr;
        RTSPActionServerSetup setup;
        
        /* find which url is asked */
        url_split(NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
        path = path1;
        if (*path == '/')
            path++;
    
        /* now check each stream */
        for(stream = first_stream; stream != NULL; stream = stream->next) {
            if (!stream->is_feed && stream->fmt == &rtp_mux) {
                /* accept aggregate filenames only if single stream */
                if (!strcmp(path, stream->filename)) {
                    if (stream->nb_streams != 1) {
                        rtsp_reply_error(c, RTSP_STATUS_AGGREGATE);
                        return;
                    }
                    stream_index = 0;
                    goto found;
                }
                    
                for(stream_index = 0; stream_index < stream->nb_streams;
                    stream_index++) {
                    snprintf(buf, sizeof(buf), "%s/streamid=%d", 
                             stream->filename, stream_index);
                    if (!strcmp(path, buf))
                        goto found;
                }
            }
        }
        /* no stream found */
        rtsp_reply_error(c, RTSP_STATUS_SERVICE); /* XXX: right error ? */
        return;
     found:
    
        /* generate session id if needed */
        if (h->session_id[0] == '\0') {
            snprintf(h->session_id, sizeof(h->session_id), 
                     "%08x%08x", (int)random(), (int)random());
        }
    
        /* find rtp session, and create it if none found */
        rtp_c = find_rtp_session(h->session_id);
        if (!rtp_c) {
    
            rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id);
    
            if (!rtp_c) {
                rtsp_reply_error(c, RTSP_STATUS_BANDWIDTH);
                return;
            }
    
            /* open input stream */
            if (open_input_stream(rtp_c, "") < 0) {
                rtsp_reply_error(c, RTSP_STATUS_INTERNAL);
                return;
            }
    
            /* always prefer UDP */
            th = find_transport(h, RTSP_PROTOCOL_RTP_UDP);
            if (!th) {
                th = find_transport(h, RTSP_PROTOCOL_RTP_TCP);
                if (!th) {
                    rtsp_reply_error(c, RTSP_STATUS_TRANSPORT);
                    return;
                }
            }
            rtp_c->rtp_protocol = th->protocol;
        }
        
        /* test if stream is OK (test needed because several SETUP needs
           to be done for a given file) */
        if (rtp_c->stream != stream) {
            rtsp_reply_error(c, RTSP_STATUS_SERVICE);
            return;
        }
        
        /* test if stream is already set up */
        if (rtp_c->rtp_ctx[stream_index]) {
            rtsp_reply_error(c, RTSP_STATUS_STATE);
            return;
        }
    
        /* check transport */
        th = find_transport(h, rtp_c->rtp_protocol);
        if (!th || (th->protocol == RTSP_PROTOCOL_RTP_UDP && 
                    th->client_port_min <= 0)) {
            rtsp_reply_error(c, RTSP_STATUS_TRANSPORT);
            return;
        }
    
        /* setup default options */
        setup.transport_option[0] = '\0';
        dest_addr = rtp_c->from_addr;
        dest_addr.sin_port = htons(th->client_port_min);
        
        /* add transport option if needed */
        if (ff_rtsp_callback) {
            setup.ipaddr = ntohl(dest_addr.sin_addr.s_addr);
            if (ff_rtsp_callback(RTSP_ACTION_SERVER_SETUP, rtp_c->session_id, 
                                 (char *)&setup, sizeof(setup),
                                 stream->rtsp_option) < 0) {
                rtsp_reply_error(c, RTSP_STATUS_TRANSPORT);
                return;
            }
            dest_addr.sin_addr.s_addr = htonl(setup.ipaddr);
        }
        
        /* setup stream */
        if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr) < 0) {
            rtsp_reply_error(c, RTSP_STATUS_TRANSPORT);
            return;
        }
    
        /* now everything is OK, so we can send the connection parameters */
        rtsp_reply_header(c, RTSP_STATUS_OK);
        /* session ID */
        url_fprintf(c->pb, "Session: %s\r\n", rtp_c->session_id);
    
        switch(rtp_c->rtp_protocol) {
        case RTSP_PROTOCOL_RTP_UDP:
            port = rtp_get_local_port(rtp_c->rtp_handles[stream_index]);
            url_fprintf(c->pb, "Transport: RTP/AVP/UDP;unicast;"
                        "client_port=%d-%d;server_port=%d-%d",
                        th->client_port_min, th->client_port_min + 1,
                        port, port + 1);
            break;
        case RTSP_PROTOCOL_RTP_TCP:
            url_fprintf(c->pb, "Transport: RTP/AVP/TCP;interleaved=%d-%d",
                        stream_index * 2, stream_index * 2 + 1);
            break;
        default:
            break;
        }
        if (setup.transport_option[0] != '\0') {
            url_fprintf(c->pb, ";%s", setup.transport_option);
        }
        url_fprintf(c->pb, "\r\n");
        
    
        url_fprintf(c->pb, "\r\n");
    }
    
    
    /* find an rtp connection by using the session ID. Check consistency
       with filename */
    static HTTPContext *find_rtp_session_with_url(const char *url, 
                                                  const char *session_id)
    {
        HTTPContext *rtp_c;
        char path1[1024];
        const char *path;
    
        char buf[1024];
        int s;
    
    
        rtp_c = find_rtp_session(session_id);
        if (!rtp_c)
            return NULL;
    
        /* find which url is asked */