Skip to content
Snippets Groups Projects
ffserver.c 151 KiB
Newer Older
            st->codec= avcodec_alloc_context();
            c->fmt_ctx.streams[i] = st;
            /* if file or feed, then just take streams from FFStream struct */
            if (!c->stream->feed ||
                src = c->stream->streams[i];
                src = c->stream->feed->streams[c->stream->feed_streams[i]];

            *st = *src;
            st->priv_data = 0;
            st->codec->frame_number = 0; /* XXX: should be done in
            /* 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,
Fabrice Bellard's avatar
Fabrice Bellard committed
                                    c->stream->feed->feed_write_index,
                                    c->stream->feed->feed_size);
            }
            if (c->stream->max_time &&
                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 (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;
                        }
                        c->first_pts = av_rescale_q(pkt.dts, c->fmt_in->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q);
                    /* 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) {
                                /* 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
                                 * 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;
                    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) {
                            AVStream *st;
                            /* compute send time and duration */
                            st = c->fmt_in->streams[pkt.stream_index];
                            c->cur_pts = av_rescale_q(pkt.dts, st->time_base, AV_TIME_BASE_Q);
                                c->cur_pts -= av_rescale_q(st->start_time, st->time_base, AV_TIME_BASE_Q);
                            c->cur_frame_duration = av_rescale_q(pkt.duration, st->time_base, AV_TIME_BASE_Q);
#if 0
                            printf("index=%d pts=%0.3f duration=%0.6f\n",
                                   pkt.stream_index,
                                   (double)c->cur_pts /
                                   (double)c->cur_frame_duration /
                            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;
                            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);
                            int max_packet_size;
                            if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP)
                                max_packet_size = RTSP_TCP_MAX_PACKET_SIZE;
                            else
                                max_packet_size = url_get_max_packet_size(c->rtp_handles[c->packet_stream_index]);
                            ret = url_open_dyn_packet_buf(&ctx->pb, max_packet_size);
                        } else {
                            ret = url_open_dyn_buf(&ctx->pb);
                        }
                        if (ret < 0) {
                            /* XXX: potential leak */
                            return -1;
                        }
                        if (av_write_frame(ctx, &pkt)) {
                        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
                }
            }
        }
        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 */
/* send data starting at c->buffer_ptr to the output connection
   (either UDP or TCP connection) */
static int http_send_data(HTTPContext *c)
Fabrice Bellard's avatar
Fabrice Bellard committed
{
Fabrice Bellard's avatar
Fabrice Bellard committed

    for(;;) {
        if (c->buffer_ptr >= c->buffer_end) {
            ret = http_prepare_data(c);
            if (ret < 0)
                return -1;
            else if (ret != 0) {
                /* state change requested */
                break;
            if (c->is_packetized) {
                /* RTP data output */
                len = c->buffer_end - c->buffer_ptr;
                if (len < 4) {
                    /* fail safe - should never happen */
                fail1:
                    c->buffer_ptr = c->buffer_end;
                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;
                if ((get_packet_send_clock(c) - get_server_clock(c)) > 0) {
                    /* nothing to send yet: we can wait */
                    return 0;
                }

                c->data_count += len;
                update_datarate(&c->datarate, c->data_count);
                if (c->stream)
                    c->stream->bytes_served += len;

                if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) {
                    /* RTP packets are sent inside the RTSP TCP connection */
                    ByteIOContext pb1, *pb = &pb1;
                    int interleaved_index, size;
                    uint8_t header[4];
                    HTTPContext *rtsp_c;
                    rtsp_c = c->rtsp_c;
                    /* if no RTSP connection left, error */
                    if (!rtsp_c)
                        return -1;
                    /* if already sending something, then wait. */
                    if (rtsp_c->state != RTSPSTATE_WAIT_REQUEST) {
                        break;
                    }
                    if (url_open_dyn_buf(pb) < 0)
                        goto fail1;
                    interleaved_index = c->packet_stream_index * 2;
                    /* RTCP packets are sent at odd indexes */
                    if (c->buffer_ptr[1] == 200)
                        interleaved_index++;
                    /* write RTSP TCP header */
                    header[0] = '$';
                    header[1] = interleaved_index;
                    header[2] = len >> 8;
                    header[3] = len;
                    put_buffer(pb, header, 4);
                    /* write RTP packet data */
                    c->buffer_ptr += 4;
                    put_buffer(pb, c->buffer_ptr, len);
                    size = url_close_dyn_buf(pb, &c->packet_buffer);
                    /* prepare asynchronous TCP sending */
                    rtsp_c->packet_buffer_ptr = c->packet_buffer;
                    rtsp_c->packet_buffer_end = c->packet_buffer + size;
                    len = write(rtsp_c->fd, rtsp_c->packet_buffer_ptr,
                                rtsp_c->packet_buffer_end - rtsp_c->packet_buffer_ptr);
                    if (len > 0) {
                        rtsp_c->packet_buffer_ptr += len;
                    if (rtsp_c->packet_buffer_ptr < rtsp_c->packet_buffer_end) {
                        /* if we could not send all the data, we will
                           send it later, so a new state is needed to
                           "lock" the RTSP TCP connection */
                        rtsp_c->state = RTSPSTATE_SEND_PACKET;
                        break;
                    } else {
                        /* all data has been sent */
                        av_freep(&c->packet_buffer);
                    }
                } else {
                    /* send RTP packet directly in UDP */
                    c->buffer_ptr += 4;
                    url_write(c->rtp_handles[c->packet_stream_index],
                              c->buffer_ptr, len);
                    c->buffer_ptr += len;
                    /* here we continue as we can send several packets per 10 ms slot */
                }
            } 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;
                }
                c->data_count += len;
                update_datarate(&c->datarate, c->data_count);
                if (c->stream)
                    c->stream->bytes_served += len;
                break;
Fabrice Bellard's avatar
Fabrice Bellard committed
        }
    } /* for(;;) */
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;
Fabrice Bellard's avatar
Fabrice Bellard committed
    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;
}
Fabrice Bellard's avatar
Fabrice Bellard committed
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);
    if (c->buffer_ptr - c->buffer >= 2 && c->data_count > FFM_PACKET_SIZE) {
        if (c->buffer[0] != 'f' ||
            c->buffer[1] != 'm') {
            http_log("Feed stream has become desynchronized -- disconnecting\n");
            goto fail;
        }
    }

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%"PRIx64" size=0x%"PRIx64"\n", feed->feed_write_index, feed->feed_size);
Fabrice Bellard's avatar
Fabrice Bellard committed
            /* 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);
Fabrice Bellard's avatar
Fabrice Bellard committed
            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 (c->stream->feed_max_size && feed->feed_write_index >= c->stream->feed_max_size)
Fabrice Bellard's avatar
Fabrice Bellard committed
                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 &&
Fabrice Bellard's avatar
Fabrice Bellard committed
                    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;
    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;
    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 */
    private_payload_type = RTP_PT_PRIVATE;
    for(i = 0; i < stream->nb_streams; i++) {
        st = stream->streams[i];
        if (st->codec->codec_id == CODEC_ID_MPEG2TS) {
            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;
        }
        url_fprintf(pb, "m=%s %d RTP/AVP %d\n",
            /* for private payload type, we need to give more info */
            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 */
                        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;
    url_split(NULL, 0, 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_muxer &&
            !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;
    url_split(NULL, 0, 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_muxer) {
            /* 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) {
        /* 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_new_connection(&c->from_addr, stream, h->session_id,
                                   th->protocol);
        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;
        }
    }
    /* 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);
    }
    if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, c) < 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 */
    url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
    if(!strcmp(path, rtp_c->stream->filename)) return rtp_c;
    for(s=0; s<rtp_c->stream->nb_streams; ++s) {
      snprintf(buf, sizeof(buf), "%s/streamid=%d",
        rtp_c->stream->filename, s);
      if(!strncmp(path, buf, sizeof(buf))) {
    // XXX: Should we reply with RTSP_STATUS_ONLY_AGGREGATE if nb_streams>1?
        return rtp_c;
      }
    }
    return NULL;
}

static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h)
{
    HTTPContext *rtp_c;

    rtp_c = find_rtp_session_with_url(url, h->session_id);
    if (!rtp_c) {
        rtsp_reply_error(c, RTSP_STATUS_SESSION);
        return;
    }
    if (rtp_c->state != HTTPSTATE_SEND_DATA &&
        rtp_c->state != HTTPSTATE_WAIT_FEED &&
        rtp_c->state != HTTPSTATE_READY) {
        rtsp_reply_error(c, RTSP_STATUS_STATE);
        return;
    }

#if 0
    /* XXX: seek in stream */
    if (h->range_start != AV_NOPTS_VALUE) {
        printf("range_start=%0.3f\n", (double)h->range_start / AV_TIME_BASE);
        av_seek_frame(rtp_c->fmt_in, -1, h->range_start);
    }
#endif

    /* 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);
    url_fprintf(c->pb, "\r\n");
}

static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h)