diff --git a/Makefile b/Makefile index ba39b14a..c5e2b37d 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ SRCS += dvb.c dvb_support.c dvb_pmt.c dvb_dvr.c dvb_muxconfig.c SRCS += iptv_input.c iptv_output.c -SRCS += htsclient.c +SRCS += htsclient.c rtsp.c rtp.c SRCS += v4l.c diff --git a/main.c b/main.c index 6f0e016d..c12ba213 100644 --- a/main.c +++ b/main.c @@ -47,6 +47,7 @@ #include "dispatch.h" #include "transports.h" #include "iptv_output.h" +#include "rtsp.h" int running; int xmltvreload; @@ -178,6 +179,8 @@ main(int argc, char **argv) transport_scheduler_init(); + rtsp_start(); + running = 1; diff --git a/rtp.c b/rtp.c new file mode 100644 index 00000000..e02f1c35 --- /dev/null +++ b/rtp.c @@ -0,0 +1,223 @@ +/* + * tvheadend, RTP interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvhead.h" +#include "channels.h" +#include "rtp.h" +#include "dispatch.h" + +#include +#include + +#define TSBLKS_PER_PKT 7 + + +typedef struct th_rtp_pkt { + TAILQ_ENTRY(th_rtp_pkt) trp_link; + uint32_t trp_ts; /* 90kHz clock */ + uint8_t trp_ts_valid; + uint8_t trp_blocks; /* no of 188 byte blocks stored so far */ + int64_t trp_time; + th_rtp_streamer_t *trp_trs; + + void *trp_timer; + + unsigned char trp_pkt[12 + 188 * TSBLKS_PER_PKT]; +} th_rtp_pkt_t; + + + + + +static void +rtp_send(void *aux) +{ + th_rtp_pkt_t *pkt = aux; + th_rtp_streamer_t *trs = pkt->trp_trs; + + TAILQ_REMOVE(&trs->trs_sendq, pkt, trp_link); + + sendto(trs->trs_fd, pkt->trp_pkt, pkt->trp_blocks * 188 + 12, 0, + (struct sockaddr *)&trs->trs_dest, sizeof(struct sockaddr_in)); + + free(pkt); + + pkt = TAILQ_FIRST(&trs->trs_sendq); + if(pkt == NULL) + return; + + pkt->trp_timer = stimer_add_hires(rtp_send, pkt, pkt->trp_time); +} + + + + +static void +rtp_schedule(th_rtp_streamer_t *trs, th_rtp_pkt_t *last, int64_t next_ts) +{ + int64_t now, sched; + th_rtp_pkt_t *first, *pkt; + uint32_t tsdelta, ts; + int ipd, ipdu; + int i = 0; + int sendq_empty = !TAILQ_FIRST(&trs->trs_sendq); + + first = TAILQ_FIRST(&trs->trs_pktq); + + assert(first->trp_ts_valid); + + tsdelta = next_ts - first->trp_ts; + ipd = tsdelta / (trs->trs_qlen + 1); + ipdu = (tsdelta * 1000000) / 90000 / (trs->trs_qlen + 1); + + now = getclock_hires(); + + do { + trs->trs_seq++; + + pkt = TAILQ_FIRST(&trs->trs_pktq); + TAILQ_REMOVE(&trs->trs_pktq, pkt, trp_link); + trs->trs_qlen--; + + pkt->trp_pkt[0] = 0x80; + pkt->trp_pkt[1] = 33; + pkt->trp_pkt[2] = trs->trs_seq >> 8; + pkt->trp_pkt[3] = trs->trs_seq; + + ts = first->trp_ts + i * ipd; + + pkt->trp_pkt[4] = ts >> 24; + pkt->trp_pkt[5] = ts >> 16; + pkt->trp_pkt[6] = ts >> 8; + pkt->trp_pkt[7] = ts; + + pkt->trp_pkt[8] = 0; + pkt->trp_pkt[9] = 0; + pkt->trp_pkt[10] = 0; + pkt->trp_pkt[11] = 0; + + sched = now + i * ipdu; + + pkt->trp_time = sched; + + TAILQ_INSERT_TAIL(&trs->trs_sendq, pkt, trp_link); + i++; + pkt->trp_trs = trs; + + } while(pkt != last); + + printf("Sending %d packets\n", i); + + if(sendq_empty) + rtp_send(TAILQ_FIRST(&trs->trs_sendq)); +} + + + +void +rtp_streamer(struct th_subscription *s, uint8_t *buf, th_pid_t *pi, + int64_t pcr) +{ + th_rtp_streamer_t *trs = s->ths_opaque; + th_rtp_pkt_t *pkt; + + if(buf == NULL) + return; + if(pcr != AV_NOPTS_VALUE) + printf("RTP PCR = %lld\n", pcr); + + pkt = TAILQ_LAST(&trs->trs_pktq, th_rtp_pkt_queue); + + if(trs->trs_qlen > 1 && pcr != AV_NOPTS_VALUE) { + rtp_schedule(trs, pkt, pcr); + pkt = TAILQ_LAST(&trs->trs_pktq, th_rtp_pkt_queue); + } + + if(pkt == NULL && pcr == AV_NOPTS_VALUE) + return; /* make sure first packet in queue always has pcr */ + + if(pkt == NULL || pkt->trp_blocks == TSBLKS_PER_PKT) { + pkt = malloc(sizeof(th_rtp_pkt_t)); + pkt->trp_blocks = 0; + pkt->trp_ts_valid = 0; + pkt->trp_trs = trs; + pkt->trp_timer = NULL; + + TAILQ_INSERT_TAIL(&trs->trs_pktq, pkt, trp_link); + trs->trs_qlen++; + } + + if(pkt->trp_ts_valid == 0 && pcr != AV_NOPTS_VALUE) { + pkt->trp_ts = pcr; + pkt->trp_ts_valid = 1; + } + + memcpy(&pkt->trp_pkt[12 + pkt->trp_blocks * 188], buf, 188); + pkt->trp_blocks++; +} + + + +void +rtp_streamer_init(th_rtp_streamer_t *trs, int fd, struct sockaddr_in *dst) +{ + printf("RTP: Streaming to %s:%d (fd = %d)\n", + inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), fd); + + trs->trs_fd = fd; + trs->trs_dest = *dst; + + TAILQ_INIT(&trs->trs_pktq); + TAILQ_INIT(&trs->trs_sendq); + trs->trs_qlen = 0; + trs->trs_seq = 0; +} + + + + +void +rtp_streamer_deinit(th_rtp_streamer_t *trs) +{ + th_rtp_pkt_t *pkt; + + while((pkt = TAILQ_FIRST(&trs->trs_pktq)) != NULL) { + TAILQ_REMOVE(&trs->trs_pktq, pkt, trp_link); + free(pkt); + } + + while((pkt = TAILQ_FIRST(&trs->trs_sendq)) != NULL) { + TAILQ_REMOVE(&trs->trs_sendq, pkt, trp_link); + if(pkt->trp_timer != NULL) + stimer_del(pkt->trp_timer); + free(pkt); + } +} diff --git a/rtp.h b/rtp.h new file mode 100644 index 00000000..c8a92d03 --- /dev/null +++ b/rtp.h @@ -0,0 +1,45 @@ +/* + * tvheadend, RTP interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RTP_H_ +#define RTP_H_ + +TAILQ_HEAD(th_rtp_pkt_queue, th_rtp_pkt); + + +typedef struct th_rtp_streamer { + struct th_rtp_pkt_queue trs_pktq; + struct th_rtp_pkt_queue trs_sendq; + int trs_qlen; + int16_t trs_seq; + int trs_fd; + struct sockaddr_in trs_dest; + int64_t trs_last_ts; + +} th_rtp_streamer_t; + +void rtp_streamer_init(th_rtp_streamer_t *trs, int fd, + struct sockaddr_in *dst); + +void rtp_streamer_deinit(th_rtp_streamer_t *trs); + +void rtp_streamer(struct th_subscription *s, uint8_t *buf, th_pid_t *pi, + int64_t pcr); + + +#endif /* RTP_H_ */ diff --git a/rtsp.c b/rtsp.c new file mode 100644 index 00000000..f4282336 --- /dev/null +++ b/rtsp.c @@ -0,0 +1,839 @@ +/* + * tvheadend, RTSP interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tvhead.h" +#include "channels.h" +#include "transports.h" +#include "pvr.h" +#include "epg.h" +#include "teletext.h" +#include "dispatch.h" +#include "dvb.h" +#include "strtab.h" +#include "rtp.h" + +#include +#include +#include + +static AVRandomState rtsp_rnd; + +#define RTSP_MAX_LINE_LEN 1000 + +LIST_HEAD(rtsp_session_head, rtsp_session); +static struct rtsp_session_head rtsp_sessions; + + +typedef struct rtsp_session { + LIST_ENTRY(rtsp_session) rs_global_link; + LIST_ENTRY(rtsp_session) rs_con_link; + + uint32_t rs_id; + + int rs_fd[2]; + int rs_server_port[2]; + + th_subscription_t *rs_s; + + th_rtp_streamer_t rs_rtp_streamer; + +} rtsp_session_t; + + + + + +typedef struct rtsp_arg { + LIST_ENTRY(rtsp_arg) link; + char *key; + char *val; +} rtsp_arg_t; + + + +typedef struct rtsp_connection { + int rc_fd; + void *rc_dispatch_handle; + char *rc_url; + + LIST_HEAD(, rtsp_arg) rc_args; + + enum { + RTSP_CON_WAIT_REQUEST, + RTSP_CON_READ_HEADER, + RTSP_CON_END, + } rc_state; + + enum { + RTSP_CMD_NONE, + RTSP_CMD_DESCRIBE, + RTSP_CMD_OPTIONS, + RTSP_CMD_SETUP, + RTSP_CMD_PLAY, + + } rc_cmd; + + struct rtsp_session_head rc_sessions; + + int rc_input_buf_ptr; + char rc_input_buf[RTSP_MAX_LINE_LEN]; + struct sockaddr_in rc_from; + +} rtsp_connection_t; + + +static struct strtab RTSP_cmdtab[] = { + { "DESCRIBE", RTSP_CMD_DESCRIBE }, + { "OPTIONS", RTSP_CMD_OPTIONS }, + { "SETUP", RTSP_CMD_SETUP }, + { "PLAY", RTSP_CMD_PLAY }, +}; + +/** + * Resolve an URL into a channel + */ + +static th_channel_t * +rtsp_channel_by_url(char *url) +{ + char *c; + int chid; + + c = strrchr(url, '/'); + if(c != NULL && c[1] == 0) { + /* remove trailing slash */ + *c = 0; + c = strrchr(url, '/'); + } + + if(c == NULL || c[1] == 0 || (url != c && c[-1] == '/')) + return NULL; + c++; + + printf("URL RESOLVER: %s\n", c); + + + if(sscanf(c, "chid-%d", &chid) != 1) + return NULL; + printf("\t\t\t == %d\n", chid); + + return channel_by_index(chid); +} + +/** + * Create an RTSP session + */ + +static rtsp_session_t * +rtsp_session_create(th_channel_t *ch, struct sockaddr_in *dst) +{ + rtsp_session_t *rs; + uint32_t id; + struct sockaddr_in sin; + socklen_t slen; + int max_tries = 100; + + /* generate a random id (but make sure we do not collide) */ + + do { + id = av_random(&rtsp_rnd); + id &= 0x7fffffffUL; /* dont want any signed issues */ + + LIST_FOREACH(rs, &rtsp_sessions, rs_global_link) + if(rs->rs_id == id) + break; + } while(rs != NULL); + + rs = calloc(1, sizeof(rtsp_session_t)); + rs->rs_id = id; + + while(--max_tries) { + rs->rs_fd[0] = socket(AF_INET, SOCK_DGRAM, 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + + if(bind(rs->rs_fd[0], (struct sockaddr *)&sin, sizeof(sin)) == -1) { + close(rs->rs_fd[0]); + free(rs); + return NULL; + } + + slen = sizeof(struct sockaddr_in); + getsockname(rs->rs_fd[0], (struct sockaddr *)&sin, &slen); + + rs->rs_server_port[0] = ntohs(sin.sin_port); + printf("rtpserver: bound to port %d\n", rs->rs_server_port[0]); + rs->rs_server_port[1] = rs->rs_server_port[0] + 1; + + sin.sin_port = htons(rs->rs_server_port[1]); + + rs->rs_fd[1] = socket(AF_INET, SOCK_DGRAM, 0); + + if(bind(rs->rs_fd[1], (struct sockaddr *)&sin, sizeof(sin)) == -1) { + close(rs->rs_fd[0]); + close(rs->rs_fd[1]); + continue; + } + + printf("bound server_port %d-%d\n", + rs->rs_server_port[0], rs->rs_server_port[1]); + LIST_INSERT_HEAD(&rtsp_sessions, rs, rs_global_link); + + rtp_streamer_init(&rs->rs_rtp_streamer, rs->rs_fd[0], dst); + + rs->rs_s = channel_subscribe(ch, &rs->rs_rtp_streamer, + rtp_streamer, 600, "RTSP"); + return rs; + } + + free(rs); + return NULL; +} + +/** + * Destroy an RTSP session + */ + +static void +rtsp_session_destroy(rtsp_session_t *rs) +{ + subscription_unsubscribe(rs->rs_s); + close(rs->rs_fd[0]); + close(rs->rs_fd[1]); + LIST_REMOVE(rs, rs_global_link); + LIST_REMOVE(rs, rs_con_link); + rtp_streamer_deinit(&rs->rs_rtp_streamer); + + free(rs); +} + +/* + * Prints data on rtsp connection + */ + +static void +rcprintf(rtsp_connection_t *rc, const char *fmt, ...) +{ + va_list ap; + char buf[5000]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + write(rc->rc_fd, buf, strlen(buf)); +} + + +/* + * Delete all arguments associated with an RTSP connection + */ + +static void +rtsp_con_flush_args(rtsp_connection_t *rc) +{ + rtsp_arg_t *ra; + while((ra = LIST_FIRST(&rc->rc_args)) != NULL) { + LIST_REMOVE(ra, link); + free(ra->key); + free(ra->val); + free(ra); + } +} + + +/** + * Find an argument associated with an RTSP connection + */ + +static char * +rtsp_con_get_arg(rtsp_connection_t *rc, char *name) +{ + rtsp_arg_t *ra; + LIST_FOREACH(ra, &rc->rc_args, link) + if(!strcasecmp(ra->key, name)) + return ra->val; + return NULL; +} + + +/** + * Set an argument associated with an RTSP connection + */ + +static void +rtsp_con_set_arg(rtsp_connection_t *rc, char *key, char *val) +{ + rtsp_arg_t *ra; + LIST_FOREACH(ra, &rc->rc_args, link) + if(!strcasecmp(ra->key, key)) + break; + + if(ra == NULL) { + ra = malloc(sizeof(rtsp_arg_t)); + LIST_INSERT_HEAD(&rc->rc_args, ra, link); + ra->key = strdup(key); + } else { + free(ra->val); + } + ra->val = strdup(val); +} + + +/* + * + */ + +static int +tokenize(char *buf, char **vec, int vecsize, int delimiter) +{ + int n = 0; + + while(1) { + while((*buf > 0 && *buf < 33) || *buf == delimiter) + buf++; + if(*buf == 0) + break; + vec[n++] = buf; + if(n == vecsize) + break; + while(*buf > 32 && *buf != delimiter) + buf++; + if(*buf == 0) + break; + *buf = 0; + buf++; + } + return n; +} + +/* + * RTSP return code to string + */ + +static const char * +rtsp_err2str(int err) +{ + switch(err) { + case RTSP_STATUS_OK: return "OK"; + case RTSP_STATUS_METHOD: return "Method Not Allowed"; + case RTSP_STATUS_BANDWIDTH: return "Not Enough Bandwidth"; + case RTSP_STATUS_SESSION: return "Session Not Found"; + case RTSP_STATUS_STATE: return "Method Not Valid in This State"; + case RTSP_STATUS_AGGREGATE: return "Aggregate operation not allowed"; + case RTSP_STATUS_ONLY_AGGREGATE: return "Only aggregate operation allowed"; + case RTSP_STATUS_TRANSPORT: return "Unsupported transport"; + case RTSP_STATUS_INTERNAL: return "Internal Server Error"; + case RTSP_STATUS_SERVICE: return "Service Unavailable"; + case RTSP_STATUS_VERSION: return "RTSP Version not supported"; + default: + return "Unknown Error"; + break; + } +} + +/* + * + */ +static int +rtsp_reply_error(rtsp_connection_t *rc, int error) +{ + char *c; + + syslog(LOG_NOTICE, "RTSP error %d %s", error, rtsp_err2str(error)); + + rcprintf(rc, "RTSP/1.0 %d %s\r\n", error, rtsp_err2str(error)); + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + rcprintf(rc, "\r\n"); + return ECONNRESET; +} + + + +/* + * RTSP PLAY + */ + +static int +rtsp_cmd_play(rtsp_connection_t *rc) +{ + char *ses, *c; + int sesid; + rtsp_session_t *rs; + th_channel_t *ch; + + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + + printf(">>> PLAY\n"); + + if((ses = rtsp_con_get_arg(rc, "session:")) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SESSION); + + sesid = atoi(ses); + printf("\t\tsesid = %u\n", sesid); + LIST_FOREACH(rs, &rtsp_sessions, rs_global_link) + if(rs->rs_id == sesid) + break; + + if(rs == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SESSION); + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Session: %u\r\n", + rs->rs_id); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + + rcprintf(rc, "\r\n"); + + return 0; +} + +/* + * RTSP SETUP + */ + +static int +rtsp_cmd_setup(rtsp_connection_t *rc) +{ + char *transports[10]; + char *params[10]; + char *avp[2]; + char *ports[2]; + char *t, *c; + int nt, i, np, j, navp, nports, ismulticast; + int client_ports[2]; + rtsp_session_t *rs; + th_channel_t *ch; + struct sockaddr_in dst; + + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + + client_ports[0] = 0; + client_ports[1] = 0; + + printf(">>> SETUP\n"); + if((t = rtsp_con_get_arg(rc, "transport:")) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + + nt = tokenize(t, transports, 10, ','); + + /* Select a transport we can accept */ + + for(i = 0; i < nt; i++) { + np = tokenize(transports[i], params, 10, ';'); + + if(np == 0) + continue; + if(strcasecmp(params[0], "RTP/AVP/UDP") && + strcasecmp(params[0], "RTP/AVP")) + continue; + + ismulticast = 1; /* multicast is default according to RFC */ + client_ports[0] = 0; + client_ports[1] = 0; + + + for(j = 1; j < np; j++) { + if((navp = tokenize(params[j], avp, 2, '=')) == 0) + continue; + + printf("%s = %s\n", avp[0], navp == 2 ? avp[1] : ""); + + if(navp == 1 && !strcmp(avp[0], "unicast")) { + ismulticast = 0; + } else if(navp == 2 && !strcmp(avp[0], "client_port")) { + nports = tokenize(avp[1], ports, 2, '-'); + if(nports > 0) client_ports[0] = atoi(ports[0]); + if(nports > 1) client_ports[1] = atoi(ports[1]); + } + } + printf("\t%d %d %d\n", + ismulticast, client_ports[0], client_ports[1]); + + if(!ismulticast && client_ports[0] && client_ports[1]) + break; + } + + if(i == nt) /* couldnt find a suitable transport */ { + printf(">>> SETUP; no transport\n"); + return rtsp_reply_error(rc, RTSP_STATUS_TRANSPORT); + } + + dst = rc->rc_from; + dst.sin_port = htons(client_ports[0]); + + if((rs = rtsp_session_create(ch, &dst)) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_INTERNAL); + + LIST_INSERT_HEAD(&rc->rc_sessions, rs, rs_con_link); + + printf(">>> SETUP, rending reply\n"); + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Session: %u\r\n" + "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d;" + "server_port=%d-%d\r\n", + rs->rs_id, + client_ports[0], + client_ports[1], + rs->rs_server_port[0], + rs->rs_server_port[1]); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + + rcprintf(rc, "\r\n"); + return 0; +} + + + +/* + * RTSP DESCRIBE + */ + +static int +rtsp_cmd_describe(rtsp_connection_t *rc) +{ + char sdpreply[1000]; + th_channel_t *ch; + char *c; + + if((ch = rtsp_channel_by_url(rc->rc_url)) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + + snprintf(sdpreply, sizeof(sdpreply), + "v=0\r\n" + "o=- 0 0 IN IPV4 127.0.0.1\r\n" + "s=%s\r\n" + "a=tool:hts tvheadend\r\n" + "m=video 0 RTP/AVP 33\r\n", + ch->ch_name); + + printf("sdpreply = %s\n", sdpreply); + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Content-Type: application/sdp\r\n" + "Content-Length: %d\r\n", + strlen(sdpreply)); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + + rcprintf(rc, "\r\n%s", sdpreply); + return 0; +} + + +/* + * RTSP OPTIONS + */ + +static int +rtsp_cmd_options(rtsp_connection_t *rc) +{ + char *c; + + if(strcmp(rc->rc_url, "*") && rtsp_channel_by_url(rc->rc_url) == NULL) + return rtsp_reply_error(rc, RTSP_STATUS_SERVICE); + + rcprintf(rc, + "RTSP/1.0 200 OK\r\n" + "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n"); + + if((c = rtsp_con_get_arg(rc, "cseq")) != NULL) + rcprintf(rc, "CSeq: %s\r\n", c); + rcprintf(rc, "\r\n"); + return 0; +} + + +/* + * RTSP connection state machine & parser + */ +static int +rtsp_con_parse(rtsp_connection_t *rc, char *buf) +{ + int n; + char *argv[3]; + + switch(rc->rc_state) { + case RTSP_CON_WAIT_REQUEST: + rtsp_con_flush_args(rc); + if(rc->rc_url != NULL) { + free(rc->rc_url); + rc->rc_url = NULL; + } + + printf(">>>> %s\n", buf); + n = tokenize(buf, argv, 3, -1); + + if(n < 3) + return EBADRQC; + + if(strcmp(argv[2], "RTSP/1.0")) { + rtsp_reply_error(rc, RTSP_STATUS_VERSION); + return ECONNRESET; + } + rc->rc_cmd = str2val(argv[0], RTSP_cmdtab); + rc->rc_url = strdup(argv[1]); + rc->rc_state = RTSP_CON_READ_HEADER; + break; + + case RTSP_CON_READ_HEADER: + if(*buf == 0) { + rc->rc_state = RTSP_CON_WAIT_REQUEST; + printf("cmd = %d\n", rc->rc_cmd); + switch(rc->rc_cmd) { + default: + return rtsp_reply_error(rc, RTSP_STATUS_METHOD); + case RTSP_CMD_DESCRIBE: + return rtsp_cmd_describe(rc); + case RTSP_CMD_SETUP: + return rtsp_cmd_setup(rc); + case RTSP_CMD_PLAY: + return rtsp_cmd_play(rc); + case RTSP_CMD_OPTIONS: + return rtsp_cmd_options(rc); + + } + break; + } + + n = tokenize(buf, argv, 2, -1); + if(n < 2) + break; + + printf("'%s' '%s'\n", argv[0], argv[1]); + + rtsp_con_set_arg(rc, argv[0], argv[1]); + break; + + case RTSP_CON_END: + break; + } + return 0; +} + + +/* + * client error, teardown connection + */ +static void +rtsp_con_teardown(rtsp_connection_t *rc, int err) +{ + rtsp_session_t *rs; + syslog(LOG_INFO, "RTSP disconnected -- %s", strerror(err)); + + while((rs = LIST_FIRST(&rc->rc_sessions)) != NULL) + rtsp_session_destroy(rs); + + close(dispatch_delfd(rc->rc_dispatch_handle)); + + free(rc->rc_url); + rtsp_con_flush_args(rc); + free(rc); +} + +/* + * data available on socket + */ +static void +rtsp_con_data_read(rtsp_connection_t *rc) +{ + int space = sizeof(rc->rc_input_buf) - rc->rc_input_buf_ptr - 1; + int r, cr = 0, i, err; + char buf[RTSP_MAX_LINE_LEN]; + + if(space < 1) { + rtsp_con_teardown(rc, EBADMSG); + return; + } + + r = read(rc->rc_fd, rc->rc_input_buf + rc->rc_input_buf_ptr, space); + if(r < 0) { + rtsp_con_teardown(rc, errno); + return; + } + + if(r == 0) { + rtsp_con_teardown(rc, ECONNRESET); + return; + } + + rc->rc_input_buf_ptr += r; + rc->rc_input_buf[rc->rc_input_buf_ptr] = 0; + + while(1) { + cr = 0; + + for(i = 0; i < rc->rc_input_buf_ptr; i++) + if(rc->rc_input_buf[i] == 0xa) + break; + + if(i == rc->rc_input_buf_ptr) + break; + + memcpy(buf, rc->rc_input_buf, i); + buf[i] = 0; + i++; + memmove(rc->rc_input_buf, rc->rc_input_buf + i, + sizeof(rc->rc_input_buf) - i); + rc->rc_input_buf_ptr -= i; + + i = strlen(buf); + while(i > 0 && buf[i-1] < 32) + buf[--i] = 0; + + if((err = rtsp_con_parse(rc, buf)) != 0) { + rtsp_con_teardown(rc, err); + break; + } + } +} + + +/* + * dispatcher callback + */ +static void +rtsp_con_socket_callback(int events, void *opaque, int fd) +{ + rtsp_connection_t *rc = opaque; + + if(events & DISPATCH_ERR) { + rtsp_con_teardown(rc, ECONNRESET); + return; + } + + if(events & DISPATCH_READ) + rtsp_con_data_read(rc); +} + + + +/* + * + */ +static void +rtsp_connect_callback(int events, void *opaque, int fd) +{ + struct sockaddr_in from; + socklen_t socklen = sizeof(struct sockaddr_in); + int newfd; + int val; + rtsp_connection_t *rc; + char txt[30]; + + if(!(events & DISPATCH_READ)) + return; + + newfd = accept(fd, (struct sockaddr *)&from, &socklen); + if(newfd == -1) + return; + + fcntl(newfd, F_SETFL, fcntl(newfd, F_GETFL) | O_NONBLOCK); + + val = 1; + setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + + val = 30; + setsockopt(newfd, SOL_TCP, TCP_KEEPIDLE, &val, sizeof(val)); + + val = 15; + setsockopt(newfd, SOL_TCP, TCP_KEEPINTVL, &val, sizeof(val)); + + val = 5; + setsockopt(newfd, SOL_TCP, TCP_KEEPCNT, &val, sizeof(val)); + + val = 1; + setsockopt(newfd, SOL_TCP, TCP_NODELAY, &val, sizeof(val)); + + rc = calloc(1, sizeof(rtsp_connection_t)); + rc->rc_fd = newfd; + + snprintf(txt, sizeof(txt), "%s:%d", + inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + + syslog(LOG_INFO, "Got RTSP/TCP connection from %s", txt); + + rc->rc_from = from; + + rc->rc_dispatch_handle = dispatch_addfd(newfd, rtsp_con_socket_callback, rc, + DISPATCH_READ); +} + + + +/* + * Fire up RTSP server + */ + +void +rtsp_start(void) +{ + struct sockaddr_in sin; + int s; + int one = 1; + s = socket(AF_INET, SOCK_STREAM, 0); + memset(&sin, 0, sizeof(sin)); + + sin.sin_family = AF_INET; + sin.sin_port = htons(9908); + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); + + fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK); + + syslog(LOG_INFO, "Listening for RTSP/TCP connections on %s:%d", + inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + + if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + syslog(LOG_ERR, "Unable to bind socket for incomming RTSP/TCP connections" + "%s:%d -- %s", + inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), + strerror(errno)); + return; + } + + av_init_random(time(NULL), &rtsp_rnd); + + listen(s, 1); + dispatch_addfd(s, rtsp_connect_callback, NULL, DISPATCH_READ); +} diff --git a/rtsp.h b/rtsp.h new file mode 100644 index 00000000..d3057952 --- /dev/null +++ b/rtsp.h @@ -0,0 +1,24 @@ +/* + * tvheadend, RTSP interface + * Copyright (C) 2007 Andreas Öman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef RTSP_H_ +#define RTSP_H_ + +void rtsp_start(void); + +#endif /* RTSP_H_ */