Newer
Older
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 */
Philip Gladstone
committed
/* I'm pretty sure that this is not correct...
* However, without it, we crash
*/
Michael Niedermayer
committed
st->codec->coded_frame = &dummy_frame;
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;
Alex Beregszaszi
committed
if (av_write_header(&c->fmt_ctx) < 0)
return -1;
Fabrice Bellard
committed
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;
c->last_packet_sent = 0;
break;
case HTTPSTATE_SEND_DATA:
/* find a new packet */
{
AVPacket pkt;
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;
redo:
Fabrice Bellard
committed
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;
}
Fabrice Bellard
committed
}
} else {
/* update first pts if needed */
Giancarlo Formicuccia
committed
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);
Giancarlo Formicuccia
committed
c->start_time = cur_time;
}
Fabrice Bellard
committed
/* 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)
Fabrice Bellard
committed
do_switch_stream(c, i);
if (c->switch_feed_streams[i] >= 0)
Fabrice Bellard
committed
c->switch_pending = 1;
}
}
Fabrice Bellard
committed
if (c->feed_streams[i] == pkt.stream_index) {
pkt.stream_index = i;
if (pkt.flags & PKT_FLAG_KEY)
Fabrice Bellard
committed
c->got_key_frame |= 1 << i;
/* See if we have all the key frames, then
Fabrice Bellard
committed
* we start to send. This logic is not quite
* right, but it works for the case of a
Fabrice Bellard
committed
* single video stream with one or more
* audio streams (for which every frame is
* typically a key frame).
Fabrice Bellard
committed
*/
if (!c->stream->send_on_key ||
((c->got_key_frame + 1) >> c->stream->nb_streams))
Fabrice Bellard
committed
goto send_it;
Fabrice Bellard
committed
} else {
AVCodecContext *codec;
Fabrice Bellard
committed
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) {
Fabrice Bellard
committed
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);
Fabrice Bellard
committed
if (st->start_time != AV_NOPTS_VALUE)
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);
Fabrice Bellard
committed
#if 0
printf("index=%d pts=%0.3f duration=%0.6f\n",
pkt.stream_index,
Fabrice Bellard
committed
AV_TIME_BASE,
(double)c->cur_frame_duration /
Fabrice Bellard
committed
AV_TIME_BASE);
#endif
/* find RTP context */
Fabrice Bellard
committed
c->packet_stream_index = pkt.stream_index;
ctx = c->rtp_ctx[c->packet_stream_index];
if(!ctx) {
av_free_packet(&pkt);
Giancarlo Formicuccia
committed
break;
}
Michael Niedermayer
committed
codec = ctx->streams[0]->codec;
/* only one stream per RTP connection */
pkt.stream_index = 0;
Fabrice Bellard
committed
} else {
ctx = &c->fmt_ctx;
/* Fudge here */
Michael Niedermayer
committed
codec = ctx->streams[pkt.stream_index]->codec;
codec->coded_frame->key_frame = ((pkt.flags & PKT_FLAG_KEY) != 0);
Fabrice Bellard
committed
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);
Fabrice Bellard
committed
} else {
ret = url_open_dyn_buf(&ctx->pb);
}
if (ret < 0) {
/* XXX: potential leak */
return -1;
}
if (pkt.dts != AV_NOPTS_VALUE)
pkt.dts = av_rescale_q(pkt.dts,
c->fmt_in->streams[pkt.stream_index]->time_base,
ctx->streams[pkt.stream_index]->time_base);
if (pkt.pts != AV_NOPTS_VALUE)
pkt.pts = av_rescale_q(pkt.pts,
c->fmt_in->streams[pkt.stream_index]->time_base,
ctx->streams[pkt.stream_index]->time_base);
if (av_write_frame(ctx, &pkt))
Fabrice Bellard
committed
c->state = HTTPSTATE_SEND_DATA_TRAILER;
Fabrice Bellard
committed
len = url_close_dyn_buf(&ctx->pb, &c->pb_buffer);
Fabrice Bellard
committed
c->cur_frame_bytes = len;
Fabrice Bellard
committed
c->buffer_ptr = c->pb_buffer;
c->buffer_end = c->pb_buffer + len;
Fabrice Bellard
committed
codec->frame_number++;
Fabrice Bellard
committed
if (len == 0)
goto redo;
Philip Gladstone
committed
}
Fabrice Bellard
committed
av_free_packet(&pkt);
}
}
}
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;
}
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;
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 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;
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)
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 = 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);
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 (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;
Philip Gladstone
committed
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) {
av_freep(&s.priv_data);
Philip Gladstone
committed
goto fail;
}
/* Now we have the actual streams */
if (s.nb_streams != feed->nb_streams) {
av_freep(&s.priv_data);
Philip Gladstone
committed
goto fail;
}
for (i = 0; i < s.nb_streams; i++)
memcpy(feed->streams[i]->codec,
Michael Niedermayer
committed
s.streams[i]->codec, sizeof(AVCodecContext));
av_freep(&s.priv_data);
}
c->buffer_ptr = c->buffer;
}
return 0;
fail:
c->stream->feed_opened = 0;
close(c->feed_fd);
return -1;
}
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) {
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
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
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
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;
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
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
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"))
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 && !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
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
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 && !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
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
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);
av_seek_frame(rtp_c->fmt_in, -1, h->range_start);
}
#endif
Fabrice Bellard
committed
rtp_c->state = HTTPSTATE_SEND_DATA;
Fabrice Bellard
committed
/* 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)
{
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) {
rtsp_reply_error(c, RTSP_STATUS_STATE);
return;
}
Fabrice Bellard
committed
rtp_c->state = HTTPSTATE_READY;
Giancarlo Formicuccia
committed
rtp_c->first_pts = AV_NOPTS_VALUE;
Fabrice Bellard
committed
/* 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_teardown(HTTPContext *c, const char *url, RTSPHeader *h)
{
HTTPContext *rtp_c;
Fabrice Bellard
committed
rtp_c = find_rtp_session_with_url(url, h->session_id);
if (!rtp_c) {
rtsp_reply_error(c, RTSP_STATUS_SESSION);
return;
}
av_strlcpy(session_id, rtp_c->session_id, sizeof(session_id));
Fabrice Bellard
committed
/* abort the session */
close_connection(rtp_c);
/* 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", session_id);
Fabrice Bellard
committed
url_fprintf(c->pb, "\r\n");
}
/********************************************************************/
/* RTP handling */
static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr,
FFStream *stream, const char *session_id,
enum RTSPProtocol rtp_protocol)