Newer
Older
Fabrice Bellard
committed
static int64_t get_server_clock(HTTPContext *c)
Fabrice Bellard
committed
{
Fabrice Bellard
committed
/* compute current pts value from system time */
return (cur_time - c->start_time) * 1000;
Fabrice Bellard
committed
}
Fabrice Bellard
committed
/* return the estimated time at which the current packet must be sent
(in us) */
static int64_t get_packet_send_clock(HTTPContext *c)
Fabrice Bellard
committed
{
Fabrice Bellard
committed
int bytes_left, bytes_sent, frame_bytes;
Fabrice Bellard
committed
frame_bytes = c->cur_frame_bytes;
if (frame_bytes <= 0)
Fabrice Bellard
committed
return c->cur_pts;
Fabrice Bellard
committed
bytes_left = c->buffer_end - c->buffer_ptr;
bytes_sent = frame_bytes - bytes_left;
return c->cur_pts + (c->cur_frame_duration * bytes_sent) / frame_bytes;
Fabrice Bellard
committed
}
}
static int http_prepare_data(HTTPContext *c)
{
int i, len, ret;
AVFormatContext *ctx;
Fabrice Bellard
committed
switch(c->state) {
case HTTPSTATE_SEND_DATA_HEADER:
memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx));
av_strlcpy(c->fmt_ctx.author, c->stream->author,
sizeof(c->fmt_ctx.author));
av_strlcpy(c->fmt_ctx.comment, c->stream->comment,
sizeof(c->fmt_ctx.comment));
av_strlcpy(c->fmt_ctx.copyright, c->stream->copyright,
sizeof(c->fmt_ctx.copyright));
av_strlcpy(c->fmt_ctx.title, c->stream->title,
sizeof(c->fmt_ctx.title));
Fabrice Bellard
committed
for(i=0;i<c->stream->nb_streams;i++) {
Fabrice Bellard
committed
AVStream *st;
Fabrice Bellard
committed
st = av_mallocz(sizeof(AVStream));
c->fmt_ctx.streams[i] = st;
/* if file or feed, then just take streams from FFStream struct */
Fabrice Bellard
committed
c->stream->feed == c->stream)
Fabrice Bellard
committed
else
src = c->stream->feed->streams[c->stream->feed_streams[i]];
*st = *src;
st->priv_data = 0;
Michael Niedermayer
committed
st->codec->frame_number = 0; /* XXX: should be done in
Fabrice Bellard
committed
AVStream, not in codec */
}
/* set output format parameters */
c->fmt_ctx.oformat = c->stream->fmt;
c->fmt_ctx.nb_streams = c->stream->nb_streams;
Fabrice Bellard
committed
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;
Fabrice Bellard
committed
Baptiste Coudurier
committed
/*
* HACK to avoid mpeg ps muxer to spit many underflow errors
* Default value from FFmpeg
* Try to set it use configuration option
*/
c->fmt_ctx.preload = (int)(0.5*AV_TIME_BASE);
c->fmt_ctx.max_delay = (int)(0.7*AV_TIME_BASE);
if (av_write_header(&c->fmt_ctx) < 0) {
http_log("Error writing output header\n");
Alex Beregszaszi
committed
return -1;
Fabrice Bellard
committed
len = url_close_dyn_buf(c->fmt_ctx.pb, &c->pb_buffer);
Fabrice Bellard
committed
c->buffer_ptr = c->pb_buffer;
c->buffer_end = c->pb_buffer + len;
c->state = HTTPSTATE_SEND_DATA;
c->last_packet_sent = 0;
break;
case HTTPSTATE_SEND_DATA:
/* find a new packet */
/* 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);
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;
else {
AVPacket pkt;
redo:
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 */
Fabrice Bellard
committed
} 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;
Giancarlo Formicuccia
committed
}
int source_index = pkt.stream_index;
/* update first pts if needed */
if (c->first_pts == AV_NOPTS_VALUE) {
c->first_pts = av_rescale_q(pkt.dts, c->fmt_in->streams[pkt.stream_index]->time_base, AV_TIME_BASE_Q);
c->start_time = cur_time;
}
/* 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;
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) {
AVStream *st = c->fmt_in->streams[source_index];
if (pkt.flags & PKT_FLAG_KEY &&
(st->codec->codec_type == CODEC_TYPE_VIDEO ||
c->stream->nb_streams == 1))
Baptiste Coudurier
committed
c->got_key_frame = 1;
if (!c->stream->send_on_key || c->got_key_frame)
goto send_it;
}
}
} else {
AVCodecContext *codec;
AVStream *ist, *ost;
send_it:
ist = c->fmt_in->streams[source_index];
/* specific handling for RTP: we use several
output stream (one for each RTP
connection). XXX: need more abstract handling */
if (c->is_packetized) {
/* compute send time and duration */
c->cur_pts = av_rescale_q(pkt.dts, ist->time_base, AV_TIME_BASE_Q);
if (ist->start_time != AV_NOPTS_VALUE)
c->cur_pts -= av_rescale_q(ist->start_time, ist->time_base, AV_TIME_BASE_Q);
c->cur_frame_duration = av_rescale_q(pkt.duration, ist->time_base, AV_TIME_BASE_Q);
Fabrice Bellard
committed
#if 0
printf("index=%d pts=%0.3f duration=%0.6f\n",
pkt.stream_index,
(double)c->cur_pts /
AV_TIME_BASE,
(double)c->cur_frame_duration /
AV_TIME_BASE);
Fabrice Bellard
committed
#endif
/* find RTP context */
c->packet_stream_index = pkt.stream_index;
ctx = c->rtp_ctx[c->packet_stream_index];
if(!ctx) {
codec = ctx->streams[0]->codec;
/* only one stream per RTP connection */
pkt.stream_index = 0;
} else {
ctx = &c->fmt_ctx;
/* Fudge here */
codec = ctx->streams[pkt.stream_index]->codec;
}
if (c->is_packetized) {
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;
}
ost = ctx->streams[pkt.stream_index];
pkt.dts = av_rescale_q(pkt.dts, ist->time_base, ost->time_base);
pkt.pts = av_rescale_q(pkt.pts, ist->time_base, ost->time_base);
pkt.duration = av_rescale_q(pkt.duration, ist->time_base, ost->time_base);
if (av_write_frame(ctx, &pkt) < 0) {
http_log("Error writing frame to output\n");
len = url_close_dyn_buf(ctx->pb, &c->pb_buffer);
c->cur_frame_bytes = len;
c->buffer_ptr = c->pb_buffer;
c->buffer_end = c->pb_buffer + len;
codec->frame_number++;
if (len == 0) {
av_free_packet(&pkt);
goto redo;
Philip Gladstone
committed
}
break;
default:
case HTTPSTATE_SEND_DATA_TRAILER:
/* last packet test ? */
Fabrice Bellard
committed
if (c->last_packet_sent || c->is_packetized)
Fabrice Bellard
committed
ctx = &c->fmt_ctx;
Fabrice Bellard
committed
if (url_open_dyn_buf(&ctx->pb) < 0) {
/* XXX: potential leak */
return -1;
}
c->fmt_ctx.pb->is_streamed = 1;
Fabrice Bellard
committed
av_write_trailer(ctx);
len = url_close_dyn_buf(ctx->pb, &c->pb_buffer);
Fabrice Bellard
committed
c->buffer_ptr = c->pb_buffer;
c->buffer_end = c->pb_buffer + len;
c->last_packet_sent = 1;
break;
}
return 0;
}
/* 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
committed
int len, ret;
for(;;) {
if (c->buffer_ptr >= c->buffer_end) {
ret = http_prepare_data(c);
if (ret < 0)
return -1;
/* state change requested */
break;
Fabrice Bellard
committed
} else {
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;
Fabrice Bellard
committed
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;
Fabrice Bellard
committed
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 *pb;
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)
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;
Fabrice Bellard
committed
c->buffer_ptr += len;
Fabrice Bellard
committed
/* send everything we can NOW */
len = send(rtsp_c->fd, rtsp_c->packet_buffer_ptr,
rtsp_c->packet_buffer_end - rtsp_c->packet_buffer_ptr, 0);
Fabrice Bellard
committed
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;
Fabrice Bellard
committed
/* all data has been sent */
av_freep(&c->packet_buffer);
} else {
/* send RTP packet directly in UDP */
url_write(c->rtp_handles[c->packet_stream_index],
Fabrice Bellard
committed
c->buffer_ptr += len;
/* here we continue as we can send several packets per 10 ms slot */
}
} else {
/* TCP data output */
len = send(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0);
if (ff_neterrno() != FF_NETERROR(EAGAIN) &&
ff_neterrno() != FF_NETERROR(EINTR))
/* error : close connection */
return -1;
Fabrice Bellard
committed
c->data_count += len;
update_datarate(&c->datarate, c->data_count);
if (c->stream)
c->stream->bytes_served += len;
break;
Fabrice Bellard
committed
}
return 0;
}
static int http_start_receive_data(HTTPContext *c)
{
int fd;
if (c->stream->feed_opened)
return -1;
Philip Gladstone
committed
/* Don't permit writing to this one */
if (c->stream->readonly)
return -1;
/* open feed */
fd = open(c->stream->feed_filename, O_RDWR);
if (fd < 0) {
http_log("Error opening feeder file: %s\n", strerror(errno));
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 = recv(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0);
if (len < 0) {
if (ff_neterrno() != FF_NETERROR(EAGAIN) &&
ff_neterrno() != FF_NETERROR(EINTR))
/* error : close connection */
goto fail;
} else if (len == 0)
/* end of connection : close it */
goto fail;
c->buffer_ptr += len;
c->data_count += len;
update_datarate(&c->datarate, c->data_count);
}
}
Philip Gladstone
committed
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;
}
}
Philip Gladstone
committed
FFStream *feed = c->stream;
/* 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);
/* XXX: use llseek or url_seek */
lseek(c->feed_fd, feed->feed_write_index, SEEK_SET);
if (write(c->feed_fd, c->buffer, FFM_PACKET_SIZE) < 0) {
http_log("Error writing to feed file: %s\n", strerror(errno));
goto fail;
}
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)
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)
Philip Gladstone
committed
} else {
/* We have a header in our hands that contains useful data */
AVFormatContext *s = NULL;
ByteIOContext *pb;
Philip Gladstone
committed
int i;
/* use feed output format name to find corresponding input format */
fmt_in = av_find_input_format(feed->fmt->name);
if (!fmt_in)
goto fail;
url_open_buf(&pb, c->buffer, c->buffer_end - c->buffer, URL_RDONLY);
pb->is_streamed = 1;
if (av_open_input_stream(&s, pb, c->stream->feed_filename, fmt_in, NULL) < 0) {
av_free(pb);
goto fail;
}
Philip Gladstone
committed
/* Now we have the actual streams */
if (s->nb_streams != feed->nb_streams) {
av_close_input_stream(s);
Philip Gladstone
committed
goto fail;
}
for (i = 0; i < s->nb_streams; i++) {
AVStream *fst = feed->streams[i];
AVStream *st = s->streams[i];
memcpy(fst->codec, st->codec, sizeof(AVCodecContext));
if (fst->codec->extradata_size) {
fst->codec->extradata = av_malloc(fst->codec->extradata_size);
if (!fst->codec->extradata)
goto fail;
memcpy(fst->codec->extradata, st->codec->extradata,
fst->codec->extradata_size);
}
}
}
c->buffer_ptr = c->buffer;
}
return 0;
fail:
c->stream->feed_opened = 0;
close(c->feed_fd);
/* wake up any waiting connections to stop waiting for feed */
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_TRAILER;
}
Fabrice Bellard
committed
/********************************************************************/
/* 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) {
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
case RTSP_STATUS_OK:
str = "OK";
break;
case RTSP_STATUS_METHOD:
str = "Method Not Allowed";
break;
case RTSP_STATUS_BANDWIDTH:
str = "Not Enough Bandwidth";
break;
case RTSP_STATUS_SESSION:
str = "Session Not Found";
break;
case RTSP_STATUS_STATE:
str = "Method Not Valid in This State";
break;
case RTSP_STATUS_AGGREGATE:
str = "Aggregate operation not allowed";
break;
case RTSP_STATUS_ONLY_AGGREGATE:
str = "Only aggregate operation allowed";
break;
case RTSP_STATUS_TRANSPORT:
str = "Unsupported transport";
break;
case RTSP_STATUS_INTERNAL:
str = "Internal Server Error";
break;
case RTSP_STATUS_SERVICE:
str = "Service Unavailable";
break;
case RTSP_STATUS_VERSION:
str = "RTSP Version not supported";
break;
Fabrice Bellard
committed
default:
str = "Unknown Error";
break;
}
Fabrice Bellard
committed
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
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];
int len;
RTSPHeader header1, *header = &header1;
Fabrice Bellard
committed
c->buffer_ptr[0] = '\0';
p = c->buffer;
Fabrice Bellard
committed
get_word(cmd, sizeof(cmd), &p);
get_word(url, sizeof(url), &p);
get_word(protocol, sizeof(protocol), &p);
av_strlcpy(c->method, cmd, sizeof(c->method));
av_strlcpy(c->url, url, sizeof(c->url));
av_strlcpy(c->protocol, protocol, sizeof(c->protocol));
Fabrice Bellard
committed
if (url_open_dyn_buf(&c->pb) < 0) {
Fabrice Bellard
committed
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
/* 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"))
Fabrice Bellard
committed
rtsp_cmd_describe(c, url);
else if (!strcmp(cmd, "OPTIONS"))
rtsp_cmd_options(c, url);
else if (!strcmp(cmd, "SETUP"))
Fabrice Bellard
committed
rtsp_cmd_setup(c, url, header);
else if (!strcmp(cmd, "PLAY"))
Fabrice Bellard
committed
rtsp_cmd_play(c, url, header);
else if (!strcmp(cmd, "PAUSE"))
Fabrice Bellard
committed
rtsp_cmd_pause(c, url, header);
else if (!strcmp(cmd, "TEARDOWN"))
Fabrice Bellard
committed
rtsp_cmd_teardown(c, url, header);
Fabrice Bellard
committed
rtsp_reply_error(c, RTSP_STATUS_METHOD);
Fabrice Bellard
committed
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;
}
static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer,
Fabrice Bellard
committed
struct in_addr my_ip)
Fabrice Bellard
committed
{
AVFormatContext *avc;
AVStream avs[MAX_STREAMS];
int i;
avc = av_alloc_format_context();
if (avc == NULL) {
Fabrice Bellard
committed
return -1;
}
if (stream->title[0] != 0) {
av_strlcpy(avc->title, stream->title, sizeof(avc->title));
} else {
av_strlcpy(avc->title, "No Title", sizeof(avc->title));
}
avc->nb_streams = stream->nb_streams;
if (stream->is_multicast) {
snprintf(avc->filename, 1024, "rtp://%s:%d?multicast=1?ttl=%d",
inet_ntoa(stream->multicast_ip),
stream->multicast_port, stream->multicast_ttl);
}
Fabrice Bellard
committed
for(i = 0; i < stream->nb_streams; i++) {
avc->streams[i] = &avs[i];
avc->streams[i]->codec = stream->streams[i]->codec;
Fabrice Bellard
committed
}
*pbuffer = av_mallocz(2048);
avf_sdp_create(&avc, 1, *pbuffer, 2048);
av_free(avc);
return strlen(*pbuffer);
Fabrice Bellard
committed
}
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");
}
Fabrice Bellard
committed
static void rtsp_cmd_describe(HTTPContext *c, const char *url)
{
FFStream *stream;
char path1[1024];
const char *path;
Fabrice Bellard
committed
int content_length, len;
struct sockaddr_in my_addr;
Fabrice Bellard
committed
/* find which url is asked */
Petr Doubek
committed
url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
Fabrice Bellard
committed
path = path1;
if (*path == '/')
path++;
for(stream = first_stream; stream != NULL; stream = stream->next) {
if (!stream->is_feed &&
stream->fmt && !strcmp(stream->fmt->name, "rtp") &&
Fabrice Bellard
committed
!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 */
Fabrice Bellard
committed
/* 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);
Fabrice Bellard
committed
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
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)
Fabrice Bellard
committed
{
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,
Fabrice Bellard
committed
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;
Fabrice Bellard
committed
/* find which url is asked */
Petr Doubek
committed
url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
Fabrice Bellard
committed
path = path1;
if (*path == '/')
path++;
/* now check each stream */
for(stream = first_stream; stream != NULL; stream = stream->next) {
if (!stream->is_feed &&
stream->fmt && !strcmp(stream->fmt->name, "rtp")) {
Fabrice Bellard
committed
/* 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;
}
Fabrice Bellard
committed
for(stream_index = 0; stream_index < stream->nb_streams;
stream_index++) {
snprintf(buf, sizeof(buf), "%s/streamid=%d",
Fabrice Bellard
committed
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",
av_random(&random_state), av_random(&random_state));
Fabrice Bellard
committed
/* 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);
Fabrice Bellard
committed
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;
}
}
Fabrice Bellard
committed
/* 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;
}
Fabrice Bellard
committed
/* 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 &&
Fabrice Bellard
committed
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);
Fabrice Bellard
committed
/* setup stream */
if (rtp_new_av_stream(rtp_c, stream_index, &dest_addr, c) < 0) {
Fabrice Bellard
committed
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
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')
Fabrice Bellard
committed
url_fprintf(c->pb, ";%s", setup.transport_option);
url_fprintf(c->pb, "\r\n");
Fabrice Bellard
committed
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,
Fabrice Bellard
committed
const char *session_id)
{
HTTPContext *rtp_c;
char path1[1024];
const char *path;
Fabrice Bellard
committed
rtp_c = find_rtp_session(session_id);
if (!rtp_c)
return NULL;
/* find which url is asked */
Petr Doubek
committed
url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
Fabrice Bellard
committed
path = path1;
if (*path == '/')
path++;
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;
Fabrice Bellard
committed
}
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;
}
Fabrice Bellard
committed
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;
}
Fabrice Bellard
committed
#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);