Newer
Older
static int open_input_stream(HTTPContext *c, const char *info)
{
char buf[128];
char input_filename[1024];
/* find file name */
if (c->stream->feed) {
strcpy(input_filename, c->stream->feed->feed_filename);
buf_size = FFM_PACKET_SIZE;
if (av_find_info_tag(buf, sizeof(buf), "date", info)) {
if ((ret = av_parse_time(&stream_pos, buf, 0)) < 0) {
http_log("Invalid date specification '%s' for stream\n", buf);
} else if (av_find_info_tag(buf, sizeof(buf), "buffer", info)) {
Philip Gladstone
committed
int prebuffer = strtol(buf, 0, 10);
stream_pos = av_gettime() - prebuffer * (int64_t)1000000;
stream_pos = av_gettime() - c->stream->prebuffer * (int64_t)1000;
} else {
strcpy(input_filename, c->stream->feed_filename);
if (av_find_info_tag(buf, sizeof(buf), "date", info)) {
if ((ret = av_parse_time(&stream_pos, buf, 1)) < 0) {
http_log("Invalid date specification '%s' for stream\n", buf);
if (!input_filename[0]) {
http_log("No filename was specified for stream\n");
return AVERROR(EINVAL);
}
if ((ret = avformat_open_input(&s, input_filename, c->stream->ifmt, &c->stream->in_opts)) < 0) {
http_log("Could not open input '%s': %s\n", input_filename, av_err2str(ret));
return ret;
Fabrice Bellard
committed
}
Miroslav Slugeň
committed
/* set buffer size */
if (buf_size > 0) ffio_set_buf_size(s->pb, buf_size);
s->flags |= AVFMT_FLAG_GENPTS;
if (strcmp(s->iformat->name, "ffm") &&
(ret = avformat_find_stream_info(c->fmt_in, NULL)) < 0) {
http_log("Could not find stream info for input '%s'\n", input_filename);
avformat_close_input(&s);
/* choose stream as clock source (we favor the video stream if
* present) for packet sending */
Fabrice Bellard
committed
c->pts_stream_index = 0;
for(i=0;i<c->stream->nb_streams;i++) {
if (c->pts_stream_index == 0 &&
c->stream->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
Fabrice Bellard
committed
c->pts_stream_index = i;
}
}
if (c->fmt_in->iformat->read_seek)
Baptiste Coudurier
committed
av_seek_frame(c->fmt_in, -1, stream_pos, 0);
Fabrice Bellard
committed
/* set the start time (needed for maxtime and RTP packet timing) */
c->start_time = cur_time;
c->first_pts = AV_NOPTS_VALUE;
Fabrice Bellard
committed
/* return the server clock (in us) */
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:
ctx = avformat_alloc_context();
c->fmt_ctx = *ctx;
av_freep(&ctx);
av_dict_copy(&(c->fmt_ctx.metadata), c->stream->metadata, 0);
c->fmt_ctx.streams = av_mallocz_array(c->stream->nb_streams, sizeof(AVStream *));
Mike Williams
committed
for(i=0;i<c->stream->nb_streams;i++) {
Mike Williams
committed
c->fmt_ctx.streams[i] = av_mallocz(sizeof(AVStream));
/* if file or feed, then just take streams from FFServerStream struct */
Fabrice Bellard
committed
c->stream->feed == c->stream)
Fabrice Bellard
committed
else
src = c->stream->feed->streams[c->stream->feed_streams[i]];
Mike Williams
committed
*(c->fmt_ctx.streams[i]) = *src;
c->fmt_ctx.streams[i]->priv_data = 0;
/* XXX: should be done in AVStream, not in codec */
c->fmt_ctx.streams[i]->codec->frame_number = 0;
Fabrice Bellard
committed
}
/* 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 (avio_open_dyn_buf(&c->fmt_ctx.pb) < 0) {
Fabrice Bellard
committed
/* XXX: potential leak */
return -1;
}
c->fmt_ctx.pb->seekable = 0;
Fabrice Bellard
committed
Baptiste Coudurier
committed
/*
* HACK to avoid MPEG-PS muxer to spit many underflow errors
Baptiste Coudurier
committed
* Default value from FFmpeg
* Try to set it using configuration option
Baptiste Coudurier
committed
*/
c->fmt_ctx.max_delay = (int)(0.7*AV_TIME_BASE);
if ((ret = avformat_write_header(&c->fmt_ctx, NULL)) < 0) {
http_log("Error writing output header for stream '%s': %s\n",
c->stream->filename, av_err2str(ret));
return ret;
av_dict_free(&c->fmt_ctx.metadata);
Fabrice Bellard
committed
len = avio_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:
ret = av_read_frame(c->fmt_in, &pkt);
if (ret < 0) {
if (c->stream->feed) {
/* 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 (ret == AVERROR(EAGAIN)) {
/* input not ready, come back later */
return 0;
Fabrice Bellard
committed
} else {
avformat_close_input(&c->fmt_in);
if (open_input_stream(c, "") < 0)
goto no_loop;
goto redo;
} else {
no_loop:
/* must send trailer now because EOF or error */
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 & AV_PKT_FLAG_KEY)
if (c->switch_feed_streams[i] >= 0)
c->switch_pending = 1;
if (c->stream->feed_streams[i] == pkt.stream_index) {
AVStream *st = c->fmt_in->streams[source_index];
if (pkt.flags & AV_PKT_FLAG_KEY &&
(st->codec->codec_type == AVMEDIA_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];
* output streams (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);
Martin Storsjö
committed
c->cur_pts -= c->first_pts;
c->cur_frame_duration = av_rescale_q(pkt.duration, ist->time_base, AV_TIME_BASE_Q);
/* 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;
Ronald S. Bultje
committed
if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP)
max_packet_size = RTSP_TCP_MAX_PACKET_SIZE;
else
max_packet_size = c->rtp_handles[c->packet_stream_index]->max_packet_size;
ret = ffio_open_dyn_packet_buf(&ctx->pb, max_packet_size);
ret = avio_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 ((ret = av_write_frame(ctx, &pkt)) < 0) {
http_log("Error writing frame to output for stream '%s': %s\n",
c->stream->filename, av_err2str(ret));
len = avio_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;
if (avio_open_dyn_buf(&ctx->pb) < 0) {
Fabrice Bellard
committed
/* XXX: potential leak */
return -1;
}
c->fmt_ctx.pb->seekable = 0;
Fabrice Bellard
committed
av_write_trailer(ctx);
len = avio_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
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;
else if (ret)
/* 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;
Ronald S. Bultje
committed
if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP) {
/* RTP packets are sent inside the RTSP TCP connection */
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 (avio_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;
avio_write(pb, header, 4);
/* write RTP packet data */
c->buffer_ptr += 4;
avio_write(pb, c->buffer_ptr, len);
size = avio_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 */
ffurl_write(c->rtp_handles[c->packet_stream_index],
c->buffer_ptr, len);
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() != AVERROR(EAGAIN) &&
ff_neterrno() != AVERROR(EINTR))
/* error : close connection */
return -1;
}
c->buffer_ptr += len;
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) {
http_log("Stream feed '%s' was not opened\n", c->stream->feed_filename);
return AVERROR(EINVAL);
}
Philip Gladstone
committed
/* Don't permit writing to this one */
if (c->stream->readonly) {
http_log("Cannot write to read-only file '%s'\n", c->stream->feed_filename);
return AVERROR(EINVAL);
}
Philip Gladstone
committed
/* open feed */
fd = open(c->stream->feed_filename, O_RDWR);
ret = AVERROR(errno);
http_log("Could not open feed file '%s': %s\n",
c->stream->feed_filename, strerror(errno));
return ret;
if (c->stream->truncate) {
/* truncate feed file */
ffm_write_write_index(c->feed_fd, FFM_PACKET_SIZE);
http_log("Truncating feed file '%s'\n", c->stream->feed_filename);
if (ftruncate(c->feed_fd, FFM_PACKET_SIZE) < 0) {
ret = AVERROR(errno);
http_log("Error truncating feed file '%s': %s\n",
c->stream->feed_filename, strerror(errno));
return ret;
} else {
ret = ffm_read_write_index(fd);
if (ret < 0) {
http_log("Error reading write index from feed file '%s': %s\n",
c->stream->feed_filename, strerror(errno));
return ret;
} else {
c->stream->feed_write_index = ret;
}
c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE);
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;
c->chunked_encoding = !!av_stristr(c->buffer, "Transfer-Encoding: chunked");
static int http_receive_data(HTTPContext *c)
{
HTTPContext *c1;
Ronald S. Bultje
committed
int len, loop_run = 0;
Ronald S. Bultje
committed
while (c->chunked_encoding && !c->chunk_size &&
c->buffer_end > c->buffer_ptr) {
/* read chunk header, if present */
len = recv(c->fd, c->buffer_ptr, 1, 0);
if (len < 0) {
if (ff_neterrno() != AVERROR(EAGAIN) &&
ff_neterrno() != AVERROR(EINTR))
Ronald S. Bultje
committed
/* error : close connection */
goto fail;
Ronald S. Bultje
committed
} else if (len == 0) {
/* end of connection : close it */
goto fail;
} else if (c->buffer_ptr - c->buffer >= 2 &&
!memcmp(c->buffer_ptr - 1, "\r\n", 2)) {
c->chunk_size = strtol(c->buffer, 0, 16);
if (c->chunk_size == 0) // end of stream
goto fail;
c->buffer_ptr = c->buffer;
break;
} else if (++loop_run > 10) {
/* no chunk header, abort */
goto fail;
} else {
c->buffer_ptr++;
}
}
Ronald S. Bultje
committed
if (c->buffer_end > c->buffer_ptr) {
len = recv(c->fd, c->buffer_ptr,
FFMIN(c->chunk_size, c->buffer_end - c->buffer_ptr), 0);
if (len < 0) {
if (ff_neterrno() != AVERROR(EAGAIN) &&
ff_neterrno() != AVERROR(EINTR))
/* error : close connection */
goto fail;
} else if (len == 0)
/* end of connection : close it */
goto fail;
Ronald S. Bultje
committed
c->chunk_size -= len;
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;
}
}
FFServerStream *feed = c->stream;
/* a packet has been received : write it in the store, except
if header */
if (c->data_count > FFM_PACKET_SIZE) {
/* XXX: use llseek or url_seek
* XXX: Should probably fail? */
if (lseek(c->feed_fd, feed->feed_write_index, SEEK_SET) == -1)
http_log("Seek to %"PRId64" failed\n", feed->feed_write_index);
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 */
if (ffm_write_write_index(c->feed_fd, feed->feed_write_index) < 0) {
http_log("Error writing index to feed file: %s\n", strerror(errno));
goto fail;
}
for(c1 = first_http_ctx; c1; 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 = avformat_alloc_context();
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;
pb = avio_alloc_context(c->buffer, c->buffer_end - c->buffer,
0, NULL, NULL, NULL, NULL);
s->pb = pb;
if (avformat_open_input(&s, c->stream->feed_filename, fmt_in, NULL) < 0) {
av_freep(&pb);
Philip Gladstone
committed
/* Now we have the actual streams */
avformat_close_input(&s);
av_freep(&pb);
http_log("Feed '%s' stream number does not match registered feed\n",
c->stream->feed_filename);
Philip Gladstone
committed
goto fail;
}
for (i = 0; i < s->nb_streams; i++) {
AVStream *fst = feed->streams[i];
AVStream *st = s->streams[i];
Martin Storsjö
committed
avcodec_copy_context(fst->codec, st->codec);
avformat_close_input(&s);
av_freep(&pb);
}
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; 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;
Fabrice Bellard
committed
char buf2[32];
str = RTSP_STATUS_CODE2STRING(error_number);
if (!str)
Fabrice Bellard
committed
str = "Unknown Error";
avio_printf(c->pb, "RTSP/1.0 %d %s\r\n", error_number, str);
avio_printf(c->pb, "CSeq: %d\r\n", c->seq);
Fabrice Bellard
committed
/* output GMT time */
ti = time(NULL);
tm = gmtime(&ti);
strftime(buf2, sizeof(buf2), "%a, %d %b %Y %H:%M:%S", tm);
avio_printf(c->pb, "Date: %s GMT\r\n", buf2);
Fabrice Bellard
committed
}
static void rtsp_reply_error(HTTPContext *c, enum RTSPStatusCode error_number)
{
rtsp_reply_header(c, error_number);
Fabrice Bellard
committed
}
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;
RTSPMessageHeader header1 = { 0 }, *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 (avio_open_dyn_buf(&c->pb) < 0) {
Fabrice Bellard
committed
/* XXX: cannot do more */
c->pb = NULL; /* safety */
return -1;
}
/* check version name */
if (strcmp(protocol, "RTSP/1.0")) {
Fabrice Bellard
committed
rtsp_reply_error(c, RTSP_STATUS_VERSION);
goto the_end;
}
/* parse each header line */
/* skip to next line */
while (*p != '\n' && *p != '\0')
p++;
if (*p == '\n')
p++;
while (*p != '\0') {
p1 = memchr(p, '\n', (char *)c->buffer_ptr - p);
Fabrice Bellard
committed
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';
ff_rtsp_parse_line(header, line, NULL, NULL);
Fabrice Bellard
committed
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"))
rtsp_cmd_interrupt(c, url, header, 1);
else if (!strcmp(cmd, "TEARDOWN"))
rtsp_cmd_interrupt(c, url, header, 0);
Fabrice Bellard
committed
rtsp_reply_error(c, RTSP_STATUS_METHOD);
Fabrice Bellard
committed
the_end:
len = avio_close_dyn_buf(c->pb, &c->pb_buffer);
Fabrice Bellard
committed
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(FFServerStream *stream, uint8_t **pbuffer,
Fabrice Bellard
committed
struct in_addr my_ip)
Fabrice Bellard
committed
{
AVStream *avs = NULL;
AVOutputFormat *rtp_format = av_guess_format("rtp", NULL, NULL);
AVDictionaryEntry *entry = av_dict_get(stream->metadata, "title", NULL, 0);
*pbuffer = NULL;
avc = avformat_alloc_context();
if (!avc || !rtp_format) {
Fabrice Bellard
committed
return -1;
av_dict_set(&avc->metadata, "title",
entry ? entry->value : "No Title", 0);
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);
} else {
snprintf(avc->filename, 1024, "rtp://0.0.0.0");
if (avc->nb_streams >= INT_MAX/sizeof(*avc->streams) ||
!(avc->streams = av_malloc(avc->nb_streams * sizeof(*avc->streams))))
goto sdp_done;
if (avc->nb_streams >= INT_MAX/sizeof(*avs) ||
!(avs = av_malloc(avc->nb_streams * sizeof(*avs))))
goto sdp_done;
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
}
av_sdp_create(&avc, 1, *pbuffer, 2048);
sdp_done:
av_freep(&avc->streams);
av_dict_free(&avc->metadata);
av_free(avs);
return *pbuffer ? strlen(*pbuffer) : AVERROR(ENOMEM);
Fabrice Bellard
committed
}
static void rtsp_cmd_options(HTTPContext *c, const char *url)
{
// rtsp_reply_header(c, RTSP_STATUS_OK);
avio_printf(c->pb, "RTSP/1.0 %d %s\r\n", RTSP_STATUS_OK, "OK");
avio_printf(c->pb, "CSeq: %d\r\n", c->seq);
avio_printf(c->pb, "Public: %s\r\n", "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE");
avio_printf(c->pb, "\r\n");
}
Fabrice Bellard
committed
static void rtsp_cmd_describe(HTTPContext *c, const char *url)
{
FFServerStream *stream;
Fabrice Bellard
committed
char path1[1024];
const char *path;
int content_length;
socklen_t len;
Fabrice Bellard
committed
struct sockaddr_in my_addr;
av_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url);
Fabrice Bellard
committed
path = path1;
if (*path == '/')
path++;
for(stream = config.first_stream; stream; 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_NOT_FOUND);
Fabrice Bellard
committed
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
if (content_length < 0) {
rtsp_reply_error(c, RTSP_STATUS_INTERNAL);
return;
}
rtsp_reply_header(c, RTSP_STATUS_OK);
avio_printf(c->pb, "Content-Base: %s/\r\n", url);
avio_printf(c->pb, "Content-Type: application/sdp\r\n");
avio_printf(c->pb, "Content-Length: %d\r\n", content_length);
avio_printf(c->pb, "\r\n");
avio_write(c->pb, content, content_length);
Fabrice Bellard
committed
}
static HTTPContext *find_rtp_session(const char *session_id)
{
HTTPContext *c;
if (session_id[0] == '\0')
return NULL;
for(c = first_http_ctx; c; c = c->next) {
Fabrice Bellard
committed
if (!strcmp(c->session_id, session_id))
return c;
}
return NULL;
}
static RTSPTransportField *find_transport(RTSPMessageHeader *h, enum RTSPLowerTransport lower_transport)
Fabrice Bellard
committed
{
RTSPTransportField *th;
int i;
for(i=0;i<h->nb_transports;i++) {
th = &h->transports[i];
Ronald S. Bultje
committed
if (th->lower_transport == lower_transport)
Fabrice Bellard
committed
return th;
}
return NULL;
}
static void rtsp_cmd_setup(HTTPContext *c, const char *url,
RTSPMessageHeader *h)
Fabrice Bellard
committed
{
FFServerStream *stream;
Fabrice Bellard
committed
char buf[1024];
char path1[1024];
const char *path;
HTTPContext *rtp_c;
RTSPTransportField *th;
struct sockaddr_in dest_addr;
RTSPActionServerSetup setup;
av_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 = config.first_stream; stream; 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') {
unsigned random0 = av_lfg_get(&random_state);
unsigned random1 = av_lfg_get(&random_state);
snprintf(h->session_id, sizeof(h->session_id), "%08x%08x",
Fabrice Bellard
committed
/* find RTP session, and create it if none found */
Fabrice Bellard
committed
rtp_c = find_rtp_session(h->session_id);
if (!rtp_c) {
Ronald S. Bultje
committed
th = find_transport(h, RTSP_LOWER_TRANSPORT_UDP);
Ronald S. Bultje
committed
th = find_transport(h, RTSP_LOWER_TRANSPORT_TCP);
if (!th) {
rtsp_reply_error(c, RTSP_STATUS_TRANSPORT);
return;
}
}
rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id,
Ronald S. Bultje
committed
th->lower_transport);
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;
}
}