/* * Connected */ static int iptv_rtsp_header ( http_client_t *hc ) { iptv_mux_t *im = hc->hc_aux; rtsp_priv_t *rp; int r; if (im == NULL) { /* teardown (or teardown timeout) */ if (hc->hc_cmd == RTSP_CMD_TEARDOWN) { pthread_mutex_lock(&global_lock); mtimer_arm_rel(&hc->hc_close_timer, iptv_rtsp_close_cb, hc, 0); pthread_mutex_unlock(&global_lock); } return 0; } if (hc->hc_cmd == RTSP_CMD_GET_PARAMETER && hc->hc_code != HTTP_STATUS_OK) { tvhtrace("iptv", "GET_PARAMETER command returned invalid error code %d for '%s', " "fall back to OPTIONS in keep alive loop.", hc->hc_code, im->mm_iptv_url_raw); hc->hc_rtsp_keep_alive_cmd = RTSP_CMD_OPTIONS; return 0; } if (hc->hc_code != HTTP_STATUS_OK) { tvherror("iptv", "invalid error code %d for '%s'", hc->hc_code, im->mm_iptv_url_raw); return 0; } rp = im->im_data; switch (hc->hc_cmd) { case RTSP_CMD_SETUP: r = rtsp_setup_decode(hc, 0); if (r >= 0) { rtsp_play(hc, rp->path, rp->query); rp->play = 1; } break; case RTSP_CMD_PLAY: // Now let's set peer port for RTCP // Use the HTTP host for sending RTCP reports, NOT the hc_rtp_dest (which is where the stream is sent) if (udp_connect(rp->rtcp_info->connection, "rtcp", hc->hc_host, hc->hc_rtcp_server_port)) { tvhlog(LOG_WARNING, "rtsp", "Can't connect to remote, RTCP receiver reports won't be sent"); } hc->hc_cmd = HTTP_CMD_NONE; pthread_mutex_lock(&global_lock); iptv_input_mux_started(hc->hc_aux); mtimer_arm_rel(&rp->alive_timer, iptv_rtsp_alive_cb, im, sec2mono(MAX(1, (hc->hc_rtp_timeout / 2) - 1))); pthread_mutex_unlock(&global_lock); break; default: break; } return 0; }
/* * Connected */ static int iptv_rtsp_header ( http_client_t *hc ) { iptv_mux_t *im = hc->hc_aux; rtsp_priv_t *rp; int r; if (im == NULL) { /* teardown (or teardown timeout) */ if (hc->hc_cmd == RTSP_CMD_TEARDOWN) { pthread_mutex_lock(&global_lock); gtimer_arm(&hc->hc_close_timer, iptv_rtsp_close_cb, hc, 0); pthread_mutex_unlock(&global_lock); } return 0; } if (hc->hc_code != HTTP_STATUS_OK) { tvherror("iptv", "invalid error code %d for '%s'", hc->hc_code, im->mm_iptv_url); return 0; } rp = im->im_data; switch (hc->hc_cmd) { case RTSP_CMD_SETUP: r = rtsp_setup_decode(hc, 0); if (r >= 0) { rtsp_play(hc, rp->path, rp->query); rp->play = 1; } break; case RTSP_CMD_PLAY: hc->hc_cmd = HTTP_CMD_NONE; pthread_mutex_lock(&global_lock); iptv_input_mux_started(hc->hc_aux); gtimer_arm(&rp->alive_timer, iptv_rtsp_alive_cb, im, MAX(1, (hc->hc_rtp_timeout / 2) - 1)); pthread_mutex_unlock(&global_lock); break; default: break; } return 0; }
/* main app */ int main(int argc, char *const argv[]) { #if 1 const char *transport = "RTP/AVP;unicast;client_port=1234-1235"; /* UDP */ #else const char *transport = "RTP/AVP/TCP;unicast;client_port=1234-1235"; /* TCP */ #endif const char *range = "0.000-"; int rc = EXIT_SUCCESS; char *base_name = NULL; printf("\nRTSP request %s\n", VERSION_STR); printf(" Project web site: http://code.google.com/p/rtsprequest/\n"); printf(" Requires cURL V7.20 or greater\n\n"); /* check command line */ if ((argc != 2) && (argc != 3)) { base_name = strrchr(argv[0], '/'); if (base_name == NULL) { base_name = strrchr(argv[0], '\\'); } if (base_name == NULL) { base_name = argv[0]; } else { base_name++; } printf("Usage: %s url [transport]\n", base_name); printf(" url of video server\n"); printf (" transport (optional) specifier for media stream protocol\n"); printf(" default transport: %s\n", transport); printf("Example: %s rtsp://192.168.0.2/media/video1\n\n", base_name); rc = EXIT_FAILURE; } else { const char *url = argv[1]; char *uri = malloc(strlen(url) + 32); char *sdp_filename = malloc(strlen(url) + 32); char *control = malloc(strlen(url) + 32); CURLcode res; get_sdp_filename(url, sdp_filename); if (argc == 3) { transport = argv[2]; } /* initialize curl */ res = curl_global_init(CURL_GLOBAL_ALL); if (res == CURLE_OK) { curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); CURL *curl; fprintf(stderr, " cURL V%s loaded\n", data->version); /* initialize this curl session */ curl = curl_easy_init(); if (curl != NULL) { my_curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); my_curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); my_curl_easy_setopt(curl, CURLOPT_HEADERDATA, stdout); my_curl_easy_setopt(curl, CURLOPT_URL, url); /* request server options */ sprintf(uri, "%s", url); rtsp_options(curl, uri); /* request session description and write response to sdp file */ rtsp_describe(curl, uri, sdp_filename); /* get media control attribute from sdp file */ get_media_control_attribute(sdp_filename, control); /* setup media stream */ sprintf(uri, "%s/%s", url, control); rtsp_setup(curl, uri, transport); /* start playing media stream */ sprintf(uri, "%s/", url); rtsp_play(curl, uri, range); printf ("Playing video, press any key to stop ..."); _getch(); printf("\n"); /* teardown session */ rtsp_teardown(curl, uri); /* cleanup */ curl_easy_cleanup(curl); curl = NULL; } else { fprintf(stderr, "curl_easy_init() failed\n"); } curl_global_cleanup(); } else { fprintf(stderr, "curl_global_init(%s) failed: %d\n", "CURL_GLOBAL_ALL", res); } free(control); free(sdp_filename); free(uri); } return rc; }
static demuxer_t* demux_open_rtp(demuxer_t* demuxer) { nms_rtsp_hints hints; char * url = demuxer->stream->streaming_ctrl->url->url; rtsp_ctrl * ctl; RTSP_Error reply; rtsp_medium * media; Nemesi_DemuxerStreamData * ndsd = calloc(1, sizeof(Nemesi_DemuxerStreamData)); memset(&hints,0,sizeof(hints)); if (rtsp_port) hints.first_rtp_port = rtsp_port; if (rtsp_transport_tcp) { hints.pref_rtsp_proto = TCP; hints.pref_rtp_proto = TCP; } if (rtsp_transport_sctp) { hints.pref_rtsp_proto = SCTP; hints.pref_rtp_proto = SCTP; } mp_msg(MSGT_DEMUX, MSGL_INFO, "Initializing libNemesi\n"); if ((ctl = rtsp_init(&hints)) == NULL) { free(ndsd); return STREAM_ERROR; } ndsd->rtsp = ctl; demuxer->priv = ndsd; //nms_verbosity_set(1); mp_msg(MSGT_DEMUX, MSGL_INFO, "Opening: %s\n", url); if (rtsp_open(ctl, url)) { mp_msg(MSGT_DEMUX, MSGL_ERR, "rtsp_open failed.\n"); return demuxer; } reply = rtsp_wait(ctl); if (reply.got_error) { mp_msg(MSGT_DEMUX, MSGL_ERR, "OPEN Error from the server: %s\n", reply.message.reply_str); return demuxer; } rtsp_play(ctl, 0, 0); reply = rtsp_wait(ctl); if (reply.got_error) { mp_msg(MSGT_DEMUX, MSGL_ERR, "PLAY Error from the server: %s\n", reply.message.reply_str); return demuxer; } if (!ctl->rtsp_queue) return demuxer; media = ctl->rtsp_queue->media_queue; for (; media; media=media->next) { sdp_medium_info * info = media->medium_info; rtp_session * sess = media->rtp_sess; rtp_buff buff; int media_format = atoi(info->fmts); rtp_pt * ptinfo = rtp_get_pt_info(sess, media_format); char const * format_name = ptinfo ? ptinfo->name : NULL; memset(&buff, 0, sizeof(rtp_buff)); if (sess->parsers[media_format] == NULL) { mp_msg(MSGT_DEMUX, MSGL_ERR, "libNemesi unsupported media format: %s\n", format_name ? format_name : info->fmts); continue; } else { mp_msg(MSGT_DEMUX, MSGL_INFO, "libNemesi supported media: %s\n", format_name); } if (ptinfo->type == AU) { if (ndsd->session[NEMESI_SESSION_AUDIO] == NULL) { sh_audio_t* sh_audio = new_sh_audio(demuxer,0, NULL); WAVEFORMATEX* wf; demux_stream_t* d_audio = demuxer->audio; demuxer->audio->id = 0; mp_msg(MSGT_DEMUX, MSGL_INFO, "Detected as AUDIO stream...\n"); link_session_and_fetch_conf(ndsd, NEMESI_SESSION_AUDIO, sess, &buff, NULL); wf = calloc(1,sizeof(*wf)+buff.len); wf->cbSize = buff.len; memcpy(wf+1, buff.data, buff.len); sh_audio->wf = wf; d_audio->sh = sh_audio; wf->nSamplesPerSec = 0; wf->wFormatTag = sh_audio->format = get4CC(supported_audio, format_name); if ( !(wf->wFormatTag) ) mp_msg(MSGT_DEMUX, MSGL_WARN, "Unknown MPlayer format code for MIME" " type \"audio/%s\"\n", format_name); } else { mp_msg(MSGT_DEMUX, MSGL_ERR, "There is already an audio session registered," " ignoring...\n"); } } else if (ptinfo->type == VI) { if (ndsd->session[NEMESI_SESSION_VIDEO] == NULL) { sh_video_t* sh_video; BITMAPINFOHEADER* bih; demux_stream_t* d_video; int fps = 0; mp_msg(MSGT_DEMUX, MSGL_INFO, "Detected as VIDEO stream...\n"); link_session_and_fetch_conf(ndsd, NEMESI_SESSION_VIDEO, sess, &buff, &fps); bih = calloc(1,sizeof(*bih)+buff.len); bih->biSize = sizeof(*bih)+buff.len; memcpy(bih+1, buff.data, buff.len); sh_video = new_sh_video(demuxer,0); sh_video->bih = bih; d_video = demuxer->video; d_video->sh = sh_video; if (fps) { sh_video->fps = fps; sh_video->frametime = 1.0/fps; } bih->biCompression = sh_video->format = get4CC(supported_video, format_name); if ( !(bih->biCompression) ) { mp_msg(MSGT_DEMUX, MSGL_WARN, "Unknown MPlayer format code for MIME" " type \"video/%s\"\n", format_name); } } else { mp_msg(MSGT_DEMUX, MSGL_ERR, "There is already a video session registered," " ignoring...\n"); } } else { mp_msg(MSGT_DEMUX, MSGL_ERR, "Unsupported media type\n"); } } demuxer->stream->eof = 0; return demuxer; }
int start_server(const char *url, const char *rtspport) { int mediafd = -1, listenfd, tempfd, maxfd; int videofd; struct addrinfo *info; struct sockaddr_storage remoteaddr; socklen_t addrlen = sizeof remoteaddr; fd_set readfds, masterfds; struct timeval *timeout, *timeind = NULL, timenow; int nready, i; int videosize, videoleft; int recvd, sent; char urlhost[URLSIZE], urlpath[URLSIZE], tempstr[URLSIZE]; unsigned char msgbuf[BUFSIZE], sendbuf[BUFSIZE]; char *temp; unsigned char *sps = NULL, *pps = NULL; size_t spslen, ppslen; RTSPMsg rtspmsg; Client streamclient; pthread_t threadid; ThreadInfo *tinfo = NULL; uint16_t rtpseqno_video = (rand() % 1000000); uint16_t rtpseqno_audio = (rand() % 1000000); TimeoutEvent *event; /* The current state of the protocol */ int mediastate = IDLE; int quit = 0; int media_downloaded = 0; timeout = (struct timeval *)malloc(sizeof(struct timeval)); init_client(&streamclient); /* Open the a file where the video is to be stored */ if ((videofd = open("videotemp.mp4", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) < 0) { fatal_error("Error opening the temporary videofile"); } /* Create the RTSP listening socket */ resolve_host(NULL, rtspport, SOCK_STREAM, AI_PASSIVE, &info); listenfd = server_socket(info); maxfd = listenfd; FD_ZERO(&readfds); FD_ZERO(&masterfds); FD_SET(listenfd, &masterfds); while (!quit) { readfds = masterfds; if ((nready = Select(maxfd + 1, &readfds, timeind)) == -1) { printf("Select interrupted by a signal\n"); } /* Timeout handling, used for packet pacing and other timeouts */ else if (nready == 0) { timeind = NULL; lock_mutex(&queuelock); if ((event = pull_event(&queue)) != NULL) { switch (event->type) { case ENDOFSTREAM: printf("MEDIA FINISHED\n"); break; case FRAME: /* Video frame */ if (event->frame->frametype == VIDEO_FRAME) { rtpseqno_video += send_video_frame(sendbuf, event->frame, streamclient.videofds[0], rtpseqno_video); } /* Audio frame */ else { rtpseqno_audio += send_audio_frame(sendbuf, event->frame, streamclient.audiofds[0], rtpseqno_audio); } free(event->frame->data); free(event->frame); break; case CHECKMEDIASTATE: oma_debug_print("Checking media ready for streaming...\n"); if (mediastate != STREAM) { printf("Sending dummy RTP\n"); send_dummy_rtp(sendbuf, streamclient.videofds[0], &rtpseqno_video); push_timeout(&queue, 1000, CHECKMEDIASTATE); } break; default: oma_debug_print("ERRORENOUS EVENT TYPE!\n"); break; } /* If there are elements left in the queue, calculate next timeout */ if (queue.size > 0) { *timeout = calculate_delta(&event->time, &queue.first->time); timeind = timeout; oma_debug_print("Timeout: %ld secs, %ld usecs\n", timeout->tv_sec, timeout->tv_usec); } else { oma_debug_print("The first entry of the queue is NULL!\n"); } if (queue.size < QUEUESIZE / 2) { oma_debug_print("Signaling thread to start filling the queue"); pthread_cond_signal(&queuecond); } free(event); } unlock_mutex(&queuelock); continue; } /* End of timeout handling */ /* Start to loop through the file descriptors */ for (i = 0; i <= maxfd; i++) { if (FD_ISSET(i, &readfds)) { nready--; /* New connection from a client */ if (i == listenfd) { oma_debug_print("Recieved a new RTSP connection\n"); fflush(stdout); if ((tempfd = accept(i, (struct sockaddr *)&remoteaddr, &addrlen)) == -1) { if (errno != EWOULDBLOCK && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) { fatal_error("accept"); } } /* If we are already serving a client, close the new connection. Otherwise, continue. */ if (streamclient.state != NOCLIENT) { printf("Another RTSP client tried to connect. Sorry, we can only serve one client at a time\n"); close (tempfd); } else { streamclient.rtspfd = tempfd; streamclient.state = CLICONNECTED; maxfd = max(2, streamclient.rtspfd, maxfd); FD_SET(streamclient.rtspfd, &masterfds); } } /* Data from the media source */ else if (i == mediafd) { switch (mediastate) { case GETSENT: /* Read ONLY the HTTP message from the socket and store the video size */ recvd = recv_all(i, msgbuf, BUFSIZE, MSG_PEEK); temp = strstr((char *)msgbuf, "\r\n\r\n"); recvd = recv_all(i, msgbuf, (int)(temp + 4 - (char *)msgbuf), 0); printf("Received HTTP response\n%s\n", msgbuf); temp = strstr((char *)msgbuf, "Content-Length:"); sscanf(temp, "Content-Length: %d", &videosize); videoleft = videosize; mediastate = RECVTCP; break; case RECVTCP: if ((recvd = recv_all(i, msgbuf, BUFSIZE, 0)) == 0) { FD_CLR(i, &masterfds); close(i); oma_debug_print("Socket closed\n"); } oma_debug_print("Received data from video source!\n"); writestr(videofd, msgbuf, recvd); videoleft -= recvd; if (videoleft <= 0) { printf("Video download complete.\n"); FD_CLR(mediafd, &masterfds); close(videofd); close(mediafd); media_downloaded = 1; printf("Media socket closed\n"); /* Create the context and the queue filler thread parameter struct */ tinfo = (ThreadInfo *)malloc(sizeof(ThreadInfo)); initialize_context(&tinfo->ctx, "videotemp.mp4", &tinfo->videoIdx, &tinfo->audioIdx, &tinfo->videoRate, &tinfo->audioRate, &sps, &spslen, &pps, &ppslen); /* Launch the queue filler thread */ CHECK((pthread_create(&threadid, NULL, fill_queue, tinfo)) == 0); pthread_detach(threadid); /* Send the sprop-parameters before any other frames */ send_video_frame(sendbuf, create_sprop_frame(sps, spslen, 0), streamclient.videofds[0], rtpseqno_video++); send_video_frame(sendbuf, create_sprop_frame(pps, ppslen, 0), streamclient.videofds[0], rtpseqno_video++); g_free(sps); g_free(pps); lock_mutex(&queuelock); push_timeout(&queue, 1000, CHECKMEDIASTATE); unlock_mutex(&queuelock); mediastate = STREAM; } break; case STREAM: /* close(videofd); close(mediafd); close(listenfd); quit = 1; */ break; default: break; } } /* Data from a client ( i == streamclient.rtspfd) */ else { oma_debug_print("Received data from rtspfd\n"); fflush(stdout); if ((recvd = recv_all(i, msgbuf, BUFSIZE, 0)) == 0) { FD_CLR(i, &masterfds); close(i); oma_debug_print("RTSP client closed the connection\n"); streamclient.state = NOCLIENT; } else { oma_debug_print("%s", msgbuf); parse_rtsp(&rtspmsg, msgbuf); } if (rtspmsg.type == TEARDOWN) { /* Kill thread and empty queue */ lock_mutex(&queuelock); pthread_cancel(threadid); empty_queue(&queue); sleep(1); /* Reply with 200 OK */ sent = rtsp_teardown(&rtspmsg, sendbuf); send_all(i, sendbuf, sent); FD_CLR(i, &masterfds); close(i); close(streamclient.videofds[0]); close(streamclient.videofds[1]); close(streamclient.audiofds[0]); close(streamclient.audiofds[1]); printf("Closing AVFormatContext\n"); close_context(tinfo->ctx); free(tinfo); rtpseqno_video = (rand() % 1000000) + 7; rtpseqno_audio = rtpseqno_video + 9; init_client(&streamclient); printf("Closing RTSP client sockets (RTP&RTCP)\n"); streamclient.state = NOCLIENT; unlock_mutex(&queuelock); pthread_cond_signal(&queuecond); } switch (streamclient.state) { case CLICONNECTED: if (rtspmsg.type == OPTIONS) { sent = rtsp_options(&rtspmsg, &streamclient, sendbuf); send_all(i, sendbuf, sent); } else if (rtspmsg.type == DESCRIBE) { if (media_downloaded == 0) { /* Start fetching the file from the server */ parse_url(url, urlhost, urlpath); resolve_host(urlhost, "80", SOCK_STREAM, 0, &info); mediafd = client_socket(info, 0); FD_SET(mediafd, &masterfds); maxfd = max(2, maxfd, mediafd); /* Send the GET message */ http_get(url, msgbuf); send_all(mediafd, msgbuf, strlen((char *)msgbuf)); mediastate = GETSENT; } else { mediastate = STREAM; } /* Send the SDP without sprop-parameter-sets, those are sent * later in-band */ streamclient.state = SDPSENT; sent = rtsp_describe(&streamclient, sendbuf); send_all(i, sendbuf, sent); } break; case SDPSENT: if (rtspmsg.type == SETUP) { streamclient.setupsreceived++; /* Open up the needed ports and bind them locally. The RTCP ports opened here * are not really used by this application. */ write_remote_ip(tempstr, streamclient.rtspfd); oma_debug_print("Remote IP: %s\n", tempstr); if (streamclient.setupsreceived < 2) { resolve_host(tempstr, rtspmsg.clirtpport, SOCK_DGRAM, 0, &info); streamclient.audiofds[0] = client_socket(info, streamclient.server_rtp_audio_port); resolve_host(tempstr, rtspmsg.clirtcpport, SOCK_DGRAM, 0, &info); streamclient.audiofds[1] = client_socket(info, streamclient.server_rtcp_audio_port); sent = rtsp_setup(&rtspmsg, &streamclient, sendbuf, streamclient.server_rtp_audio_port, streamclient.server_rtcp_audio_port); } else { resolve_host(tempstr, rtspmsg.clirtpport, SOCK_DGRAM, 0, &info); streamclient.videofds[0] = client_socket(info, streamclient.server_rtp_video_port); resolve_host(tempstr, rtspmsg.clirtcpport, SOCK_DGRAM, 0, &info); streamclient.audiofds[1] = client_socket(info, streamclient.server_rtcp_video_port); sent = rtsp_setup(&rtspmsg, &streamclient, sendbuf, streamclient.server_rtp_video_port, streamclient.server_rtcp_video_port); streamclient.state = SETUPCOMPLETE; } oma_debug_print("Sending setup response...\n"); send_all(i, sendbuf, sent); } break; case SETUPCOMPLETE: if (rtspmsg.type == PLAY) { /* Respond to the PLAY request, and start sending dummy RTP packets * to disable the client timeout */ sent = rtsp_play(&rtspmsg, sendbuf); send_all(i, sendbuf, sent); if (media_downloaded == 0) { lock_mutex(&queuelock); push_timeout(&queue, 100, CHECKMEDIASTATE); unlock_mutex(&queuelock); } /* Media has already been once downloaded, initialize context and thread */ else { tinfo = (ThreadInfo *)malloc(sizeof(ThreadInfo)); initialize_context(&tinfo->ctx, "videotemp.mp4", &tinfo->videoIdx, &tinfo->audioIdx, &tinfo->videoRate, &tinfo->audioRate, &sps, &spslen, &pps, &ppslen); /* Launch the queue filler thread */ CHECK((pthread_create(&threadid, NULL, fill_queue, tinfo)) == 0); pthread_detach(threadid); /* Send the sprop-parameters before any other frames */ send_video_frame(sendbuf, create_sprop_frame(sps, spslen, 0), streamclient.videofds[0], rtpseqno_video++); send_video_frame(sendbuf, create_sprop_frame(pps, ppslen, 0), streamclient.videofds[0], rtpseqno_video++); g_free(sps); g_free(pps); /* Dummy timeouts to start queue/timeout mechanism */ push_timeout(&queue, 100, CHECKMEDIASTATE); push_timeout(&queue, 2000, CHECKMEDIASTATE); } } break; default: break; } } } if (nready <= 0) break; } /* Set the timeout value again, since select will mess it up */ lock_mutex(&queuelock); if (queue.size > 0) { CHECK((gettimeofday(&timenow, NULL)) == 0); *timeout = calculate_delta(&timenow, &queue.first->time); /* oma_debug_print("Delta sec: %ld, Delta usec: %ld\n", timeout->tv_sec, timeout->tv_usec); */ if (timeout->tv_sec < 0) { timeout->tv_sec = 0; timeout->tv_usec = 0; } timeind = timeout; } else timeind = NULL; unlock_mutex(&queuelock); } return 1; }