Files
UltraGrid/src/video_capture/rtsp.cpp
Martin Pulec 13b3705098 RTSP cap.: fixed init crash
Fixed a crash when init fails and done function is called - check if
threads that are to be destroyed were actually created.
2022-05-02 16:12:30 +02:00

1147 lines
36 KiB
C++

/*
* AUTHOR: Gerard Castillo <gerard.castillo@i2cat.net>,
* Martin German <martin.german@i2cat.net>
*
* Copyright (c) 2005-2010 Fundació i2CAT, Internet I Innovació Digital a Catalunya
* Copyright (c) 2015-2021 CESNET
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by the University of Southern
* California Information Sciences Institute.
*
* 4. Neither the name of the University nor of the Institute may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <glib.h>
#include "audio/types.h"
#include "debug.h"
#include "host.h"
#include "lib_common.h"
#include "tv.h"
#include "rtp/rtp.h"
#include "rtp/rtp_callback.h"
#include "rtp/rtpdec_h264.h"
#include "rtsp/rtsp_utils.h"
#include "video_decompress.h"
#include "pdb.h"
#include "rtp/pbuf.h"
#include "video.h"
#include "video_codec.h"
#include "video_capture.h"
#include <curl/curl.h>
#include <chrono>
#include <memory>
#define KEEPALIVE_INTERVAL_S 5
#define MOD_NAME "[rtsp] "
#define VERSION_STR "V1.0"
//TODO set lower initial video recv buffer size (to find the minimal?)
#define DEFAULT_VIDEO_FRAME_WIDTH 1920
#define DEFAULT_VIDEO_FRAME_HEIGHT 1080
#define INITIAL_VIDEO_RECV_BUFFER_SIZE ((0.1*DEFAULT_VIDEO_FRAME_WIDTH*DEFAULT_VIDEO_FRAME_HEIGHT)*110/100) //command line net.core setup: sysctl -w net.core.rmem_max=9123840
/* error handling macros */
#define my_curl_easy_setopt(A, B, C, action_fail) \
if ((res = curl_easy_setopt((A), (B), (C))) != CURLE_OK){ \
fprintf(stderr, "[rtsp error] curl_easy_setopt(%s, %s, %s) failed: %s (%d)\n", #A, #B, #C, curl_easy_strerror(res), res); \
printf("[rtsp error] could not configure rtsp capture properly, \n\t\tplease check your parameters. \nExiting...\n\n"); \
action_fail; \
}
#define my_curl_easy_perform(A) \
if ((res = curl_easy_perform((A))) != CURLE_OK){ \
fprintf(stderr, "[rtsp error] curl_easy_perform(%s) failed: %s (%d)\n", #A, curl_easy_strerror(res), res); \
printf("[rtsp error] could not configure rtsp capture properly, \n\t\tplease check your parameters. \nExiting...\n\n"); \
return NULL; \
}
/* send RTSP GET_PARAMETERS request */
static int
rtsp_get_parameters(CURL *curl, const char *uri);
/* send RTSP OPTIONS request */
static int
rtsp_options(CURL *curl, const char *uri);
/* send RTSP DESCRIBE request and write sdp response to a file */
static bool
rtsp_describe(CURL *curl, const char *uri, FILE *sdp_fp);
/* send RTSP SETUP request */
static int
rtsp_setup(CURL *curl, const char *uri, const char *transport);
/* send RTSP PLAY request */
static int
rtsp_play(CURL *curl, const char *uri, const char *range);
/* send RTSP TEARDOWN request */
static int
rtsp_teardown(CURL *curl, const char *uri);
static int
get_nals(FILE *sdp_file, char *nals, int *width, int *height);
bool setup_codecs_and_controls_from_sdp(FILE *sdp_file, void *state);
static int
init_rtsp(struct rtsp_state *s);
static int
init_decompressor(struct video_rtsp_state *sr, struct video_desc desc);
static void *
vidcap_rtsp_thread(void *args);
static void
show_help(void);
void getNewLine(const char* buffer, int* i, char* line);
void
rtsp_keepalive(void *state);
int decode_frame_by_pt(struct coded_data *cdata, void *decode_data, struct pbuf_stats *);
static void vidcap_rtsp_done(void *state);
static const uint8_t start_sequence[] = { 0, 0, 0, 1 };
/**
* @struct rtsp_state
*/
struct video_rtsp_state {
const char *codec;
struct video_desc desc;
struct video_frame *out_frame;
//struct std_frame_received *rx_data;
bool decompress;
struct state_decompress *sd;
struct video_desc decompress_desc;
int port;
char *control;
struct rtp *device;
struct pdb *participants;
double rtcp_bw;
int ttl;
char *mcast_if;
int required_connections;
pthread_t vrtsp_thread_id; //the worker_id
pthread_mutex_t lock;
pthread_cond_t worker_cv;
volatile bool worker_waiting;
pthread_cond_t boss_cv;
volatile bool boss_waiting;
unsigned int h264_offset_len;
unsigned char *h264_offset_buffer;
int pt;
};
struct audio_rtsp_state {
struct audio_frame audio;
int play_audio_frame;
const char *codec;
struct timeval last_audio_time;
unsigned int grab_audio:1;
int port;
char *control;
struct rtp *device;
struct pdb *participants;
double rtcp_bw;
int ttl;
char *mcast_if;
int required_connections;
pthread_t artsp_thread_id; //the worker_id
pthread_mutex_t lock;
pthread_cond_t worker_cv;
volatile bool worker_waiting;
pthread_cond_t boss_cv;
volatile bool boss_waiting;
};
struct rtsp_state {
CURL *curl;
char uri[1024];
rtps_types_t avType;
const char *addr;
char *sdp;
volatile bool should_exit;
struct audio_rtsp_state artsp_state;
struct video_rtsp_state vrtsp_state;
pthread_t keep_alive_rtsp_thread_id; //the worker_id
pthread_mutex_t lock;
pthread_cond_t keepalive_cv;
};
static void curl_print_error(const char *msg, CURLcode err) {
error_msg("%s: %s\n", msg, curl_easy_strerror(err));
}
static void
show_help() {
printf("[rtsp] usage:\n");
printf("\t-t rtsp:<uri>[:rtp_rx_port=<port>][:decompress][size=<width>x<height>]\n");
printf("\t\t <uri> - RTSP server URI\n");
printf("\t\t <port> - receiver port number \n");
printf(
"\t\t decompress - decompress the stream (default: disabled)\n\n");
}
static void *
keep_alive_thread(void *arg){
struct rtsp_state *s = (struct rtsp_state *) arg;
pthread_mutex_lock(&s->lock);
while (1) {
struct timeval tp;
gettimeofday(&tp, NULL);
struct timespec timeout = { .tv_sec = tp.tv_sec + KEEPALIVE_INTERVAL_S, .tv_nsec = tp.tv_usec * 1000 };
pthread_cond_timedwait(&s->keepalive_cv, &s->lock, &timeout);
if (s->should_exit) {
pthread_mutex_unlock(&s->vrtsp_state.lock);
break;
}
pthread_mutex_unlock(&s->vrtsp_state.lock);
// actuall keepalive
if (rtsp_get_parameters(s->curl, s->uri) == 0) {
s->should_exit = TRUE;
exit_uv(1);
}
}
return NULL;
}
int decode_frame_by_pt(struct coded_data *cdata, void *decode_data, struct pbuf_stats *) {
rtp_packet *pckt = NULL;
pckt = cdata->data;
struct decode_data_h264 *d = (struct decode_data_h264 *) decode_data;
if (pckt->pt == d->video_pt) {
return decode_frame_h264(cdata,decode_data);
} else {
error_msg("Wrong Payload type: %u\n", pckt->pt);
return FALSE;
}
}
static void *
vidcap_rtsp_thread(void *arg) {
struct rtsp_state *s;
s = (struct rtsp_state *) arg;
time_ns_t start_time = get_time_in_ns();
struct video_frame *frame = vf_alloc_desc_data(s->vrtsp_state.desc);
while (!s->should_exit) {
time_ns_t curr_time = get_time_in_ns();
uint32_t timestamp = (curr_time - start_time) / 100'000 * 9; // at 90000 Hz
rtp_update(s->vrtsp_state.device, curr_time);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
if (!rtp_recv_r(s->vrtsp_state.device, &timeout, timestamp)) {
pdb_iter_t it;
struct pdb_e *cp = pdb_iter_init(s->vrtsp_state.participants, &it);
while (cp != NULL) {
struct decode_data_h264 d;
d.frame = frame;
d.offset_len = s->vrtsp_state.h264_offset_len;
d.video_pt = s->vrtsp_state.pt;
if (pbuf_decode(cp->playout_buffer, curr_time,
decode_frame_by_pt, &d))
{
pthread_mutex_lock(&s->vrtsp_state.lock);
while (s->vrtsp_state.out_frame != NULL && !s->should_exit) {
s->vrtsp_state.worker_waiting = true;
pthread_cond_wait(&s->vrtsp_state.worker_cv, &s->vrtsp_state.lock);
s->vrtsp_state.worker_waiting = false;
}
if (s->vrtsp_state.out_frame == NULL) {
s->vrtsp_state.out_frame = frame;
frame = vf_alloc_desc_data(s->vrtsp_state.desc); // alloc new
if (s->vrtsp_state.boss_waiting)
pthread_cond_signal(&s->vrtsp_state.boss_cv);
pthread_mutex_unlock(&s->vrtsp_state.lock);
} else {
pthread_mutex_unlock(&s->vrtsp_state.lock);
}
}
pbuf_remove(cp->playout_buffer, curr_time);
cp = pdb_iter_next(&it);
}
pdb_iter_done(&it);
}
}
vf_free(frame);
return NULL;
}
static struct video_frame *
vidcap_rtsp_grab(void *state, struct audio_frame **audio) {
struct rtsp_state *s;
s = (struct rtsp_state *) state;
*audio = NULL;
if(pthread_mutex_trylock(&s->vrtsp_state.lock)==0){
{
while (s->vrtsp_state.out_frame == NULL && !s->should_exit) {
struct timeval tp;
gettimeofday(&tp, NULL);
struct timespec timeout = { .tv_sec = tp.tv_sec, .tv_nsec = (tp.tv_usec + 100*1000) * 1000 };
if (timeout.tv_nsec >= 1000L*1000*1000) {
timeout.tv_nsec -= 1000L*1000*1000;
timeout.tv_sec += 1;
}
s->vrtsp_state.boss_waiting = true;
if (pthread_cond_timedwait(&s->vrtsp_state.boss_cv, &s->vrtsp_state.lock, &timeout) == ETIMEDOUT) {
pthread_mutex_unlock(&s->vrtsp_state.lock);
return NULL;
}
s->vrtsp_state.boss_waiting = false;
}
if (s->should_exit) {
pthread_mutex_unlock(&s->vrtsp_state.lock);
return NULL;
}
struct video_frame *frame = s->vrtsp_state.out_frame;
s->vrtsp_state.out_frame = NULL;
pthread_mutex_unlock(&s->vrtsp_state.lock);
pthread_cond_signal(&s->vrtsp_state.worker_cv);
if(s->vrtsp_state.h264_offset_len>0 && frame->frame_type == INTRA){
memcpy(frame->tiles[0].data, s->vrtsp_state.h264_offset_buffer, s->vrtsp_state.h264_offset_len);
}
if (s->vrtsp_state.decompress) {
struct video_desc curr_desc = video_desc_from_frame(frame);
curr_desc.color_spec = H264;
if (!video_desc_eq(s->vrtsp_state.decompress_desc, curr_desc)) {
decompress_done(s->vrtsp_state.sd);
if (init_decompressor(&s->vrtsp_state, curr_desc) == 0) {
pthread_mutex_unlock(&s->vrtsp_state.lock);
return NULL;
}
s->vrtsp_state.decompress_desc = curr_desc;
}
struct video_desc out_desc = s->vrtsp_state.decompress_desc;
out_desc.color_spec = UYVY;
struct video_frame *decompressed = vf_alloc_desc_data(out_desc);
decompress_frame(s->vrtsp_state.sd, (unsigned char *) decompressed->tiles[0].data,
(unsigned char *) frame->tiles[0].data,
frame->tiles[0].data_len, 0, nullptr, nullptr);
vf_free(frame);
frame = decompressed;
}
frame->callbacks.dispose = vf_free;
return frame;
}
} else {
return NULL;
}
}
#define INIT_FAIL(msg) log_msg(LOG_LEVEL_ERROR, MOD_NAME msg); \
free(tmp); \
vidcap_rtsp_done(s); \
show_help(); \
return VIDCAP_INIT_FAIL
static int
vidcap_rtsp_init(struct vidcap_params *params, void **state) {
log_msg(LOG_LEVEL_WARNING, "RTSP capture module is most likely broken, "
"please contact " PACKAGE_BUGREPORT " if you wish to use it.\n");
if (vidcap_params_get_fmt(params)
&& strcmp(vidcap_params_get_fmt(params), "help") == 0)
{
show_help();
return VIDCAP_INIT_NOERR;
}
struct rtsp_state *s = (struct rtsp_state *) calloc(1, sizeof(struct rtsp_state));
if (s == NULL) {
return VIDCAP_INIT_FAIL;
}
//TODO now static codec assignment, to be dynamic as a function of supported codecs
s->vrtsp_state.codec = "";
s->artsp_state.codec = "";
s->artsp_state.control = strdup("");
s->artsp_state.control = strdup("");
int len = -1;
char *save_ptr = NULL;
s->avType = none; //-1 none, 0 a&v, 1 v, 2 a
s->addr = "127.0.0.1";
s->vrtsp_state.device = NULL;
s->vrtsp_state.rtcp_bw = 5 * 1024 * 1024; /* FIXME */
s->vrtsp_state.ttl = 255;
s->vrtsp_state.mcast_if = NULL;
s->vrtsp_state.required_connections = 1;
s->vrtsp_state.participants = pdb_init(0);
s->vrtsp_state.h264_offset_buffer = (unsigned char *) malloc(2048);
s->vrtsp_state.h264_offset_len = 0;
s->curl = NULL;
char *fmt = NULL;
pthread_mutex_init(&s->lock, NULL);
pthread_cond_init(&s->keepalive_cv, NULL);
pthread_mutex_init(&s->vrtsp_state.lock, NULL);
pthread_cond_init(&s->vrtsp_state.boss_cv, NULL);
pthread_cond_init(&s->vrtsp_state.worker_cv, NULL);
char *tmp, *item;
fmt = strdup(vidcap_params_get_fmt(params));
tmp = fmt;
strcpy(s->uri, "rtsp://");
s->vrtsp_state.desc.tile_count = 1;
s->vrtsp_state.desc.width = DEFAULT_VIDEO_FRAME_WIDTH/2;
s->vrtsp_state.desc.height = DEFAULT_VIDEO_FRAME_HEIGHT/2;
bool in_uri = true;
while ((item = strtok_r(fmt, ":", &save_ptr))) {
fmt = NULL;
bool option_given = true;
if (strstr(item, "rtp_rx_port=") == item) {
s->vrtsp_state.port = atoi(strchr(item, '=') + 1);
} else if (strcmp(item, "decompress") == 0) {
s->vrtsp_state.decompress = TRUE;
} else if (strstr(item, "size=")) {
assert(strchr(item, 'x') != NULL);
item = strchr(item, '=') + 1;
s->vrtsp_state.desc.width = atoi(item);
s->vrtsp_state.desc.height = atoi(strchr(item, 'x') + 1);
} else {
option_given = false;
if (in_uri) {
if (strcmp(item, "rtsp") == 0) { // rtsp:
continue;
}
if (strstr(item, "//") == item) { // rtsp://
item += 2;
}
if (strcmp(s->uri, "rtsp://") != 0) {
strncat(s->uri, ":", sizeof s->uri - strlen(s->uri) - 1);
}
strncat(s->uri, item, sizeof s->uri - strlen(s->uri) - 1);
} else {
INIT_FAIL("Unknown option\n");
}
}
if (option_given) {
in_uri = false;
}
}
free(tmp);
tmp = NULL;
//re-check parameters
if (strcmp(s->uri, "rtsp://") == 0) {
INIT_FAIL("No URI given!\n");
}
s->vrtsp_state.device = rtp_init_if("localhost", s->vrtsp_state.mcast_if, s->vrtsp_state.port, 0, s->vrtsp_state.ttl, s->vrtsp_state.rtcp_bw,
0, rtp_recv_callback, (uint8_t *) s->vrtsp_state.participants, 0, false);
if (s->vrtsp_state.device == NULL) {
log_msg(LOG_LEVEL_ERROR, "[rtsp] Cannot intialize RTP device!\n");
vidcap_rtsp_done(s);
return VIDCAP_INIT_FAIL;
}
if (!rtp_set_option(s->vrtsp_state.device, RTP_OPT_WEAK_VALIDATION, 1)) {
debug_msg("[rtsp] RTP INIT - set option\n");
return VIDCAP_INIT_FAIL;
}
if (!rtp_set_sdes(s->vrtsp_state.device, rtp_my_ssrc(s->vrtsp_state.device),
RTCP_SDES_TOOL, PACKAGE_STRING, strlen(PACKAGE_STRING))) {
debug_msg("[rtsp] RTP INIT - set sdes\n");
return VIDCAP_INIT_FAIL;
}
int ret = rtp_set_recv_buf(s->vrtsp_state.device, INITIAL_VIDEO_RECV_BUFFER_SIZE);
if (!ret) {
debug_msg("[rtsp] RTP INIT - set recv buf \nset command: sudo sysctl -w net.core.rmem_max=9123840\n");
return VIDCAP_INIT_FAIL;
}
if (!rtp_set_send_buf(s->vrtsp_state.device, 1024 * 56)) {
debug_msg("[rtsp] RTP INIT - set send buf\n");
return VIDCAP_INIT_FAIL;
}
ret=pdb_add(s->vrtsp_state.participants, rtp_my_ssrc(s->vrtsp_state.device));
debug_msg("[rtsp] rtp receiver init done\n");
if (s->vrtsp_state.port == 0) {
s->vrtsp_state.port = rtp_get_udp_rx_port(s->vrtsp_state.device);
assert(s->vrtsp_state.port != 0);
}
debug_msg("[rtsp] selected flags:\n");
debug_msg("\t uri: %s\n",s->uri);
debug_msg("\t port: %d\n", s->vrtsp_state.port);
debug_msg("\t decompress: %d\n\n",s->vrtsp_state.decompress);
len = init_rtsp(s);
if(len < 0){
vidcap_rtsp_done(s);
return VIDCAP_INIT_FAIL;
}else{
s->vrtsp_state.h264_offset_len = len;
}
//TODO fps should be autodetected, now reset and controlled at vidcap_grab function
s->vrtsp_state.desc.fps = 30;
s->vrtsp_state.desc.interlacing = PROGRESSIVE;
s->should_exit = FALSE;
s->vrtsp_state.boss_waiting = false;
s->vrtsp_state.worker_waiting = false;
if (s->vrtsp_state.decompress) {
struct video_desc decompress_desc = s->vrtsp_state.desc;
decompress_desc.color_spec = H264;
if (init_decompressor(&s->vrtsp_state, decompress_desc) == 0) {
vidcap_rtsp_done(s);
return VIDCAP_INIT_FAIL;
}
}
pthread_create(&s->vrtsp_state.vrtsp_thread_id, NULL, vidcap_rtsp_thread, s);
pthread_create(&s->keep_alive_rtsp_thread_id, NULL, keep_alive_thread, s);
debug_msg("[rtsp] rtsp capture init done\n");
*state = s;
return VIDCAP_INIT_OK;
}
static CURL *init_curl() {
CURL *curl;
/* initialize curl */
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK) {
fprintf(stderr, "[rtsp] curl_global_init(%s) failed: %s (%d)\n",
"CURL_GLOBAL_ALL", curl_easy_strerror(res), res);
return NULL;
}
curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
fprintf(stderr, "[rtsp] cURL V%s loaded\n", data->version);
/* initialize this curl session */
curl = curl_easy_init();
if (curl == NULL) {
curl_global_cleanup();
fprintf(stderr, "[rtsp] curl_easy_init() failed\n");
return NULL;
}
return curl;
}
/**
* Initializes rtsp state and internal parameters
*/
static int
init_rtsp(struct rtsp_state *s) {
/* initialize curl */
s->curl = init_curl();
if (!s->curl) {
return -1;
}
const char *range = "0.000-";
int len_nals = -1;
debug_msg("\n[rtsp] request %s\n", VERSION_STR);
debug_msg(" Project web site: http://code.google.com/p/rtsprequest/\n");
debug_msg(" Requires cURL V7.20 or greater\n\n");
char Atransport[256];
char Vtransport[256];
memset(Atransport, 0, 256);
memset(Vtransport, 0, 256);
int port = s->vrtsp_state.port;
CURLcode res;
FILE *sdp_file = tmpfile();
if (sdp_file == NULL) {
sdp_file = fopen("rtsp.sdp", "w+");
if (sdp_file == NULL) {
perror("Creating SDP file");
goto error;
}
}
sprintf(Vtransport, "RTP/AVP;unicast;client_port=%d-%d", port, port + 1);
//THIS AUDIO PORTS ARE AS DEFAULT UG AUDIO PORTS BUT AREN'T RELATED...
sprintf(Atransport, "RTP/AVP;unicast;client_port=%d-%d", port+2, port + 3);
my_curl_easy_setopt(s->curl, CURLOPT_NOSIGNAL, 1, goto error); //This tells curl not to use any functions that install signal handlers or cause signals to be sent to your process.
//my_curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, 1);
my_curl_easy_setopt(s->curl, CURLOPT_VERBOSE, 0L, goto error);
my_curl_easy_setopt(s->curl, CURLOPT_NOPROGRESS, 1L, goto error);
my_curl_easy_setopt(s->curl, CURLOPT_WRITEHEADER, stdout, goto error);
my_curl_easy_setopt(s->curl, CURLOPT_URL, s->uri, goto error);
//TODO TO CHECK CONFIGURING ERRORS
//CURLOPT_ERRORBUFFER
//http://curl.haxx.se/libcurl/c/curl_easy_perform.html
/* request server options */
if(rtsp_options(s->curl, s->uri)==0){
goto error;
}
/* request session description and write response to sdp file */
if (!rtsp_describe(s->curl, s->uri, sdp_file)) {
goto error;
}
if (log_level >= LOG_LEVEL_VERBOSE) {
fprintf(stderr, "SDP:\n");
while (!feof(sdp_file)) {
putc(getc(sdp_file), stderr);
}
rewind(sdp_file);
fprintf(stderr, "\n\n");
}
if (!setup_codecs_and_controls_from_sdp(sdp_file, s)) {
goto error;
}
if (strcmp(s->vrtsp_state.codec, "H264") == 0){
s->vrtsp_state.desc.color_spec = H264;
char uri[strlen(s->uri) + 1 + strlen(s->vrtsp_state.control) + 1];
strcpy(uri, s->uri);
strcat(uri, "/");
strcat(uri, s->vrtsp_state.control);
debug_msg("\n V URI = %s\n", uri);
if (rtsp_setup(s->curl, uri, Vtransport) == 0) {
goto error;
}
}
if (strcmp(s->artsp_state.codec, "PCMU") == 0){
char uri[strlen(s->uri) + 1 + strlen(s->artsp_state.control) + 1];
strcpy(uri, s->uri);
strcat(uri, "/");
strcat(uri, s->artsp_state.control);
debug_msg("\n A URI = %s\n", uri);
if (rtsp_setup(s->curl, uri, Atransport) == 0) {
goto error;
}
}
if (strlen(s->artsp_state.codec) == 0 && strlen(s->vrtsp_state.codec) == 0){
goto error;
}
else{
if(rtsp_play(s->curl, s->uri, range)==0){
goto error;
}
}
/* get start nal size attribute from sdp file */
len_nals = get_nals(sdp_file, (char *) s->vrtsp_state.h264_offset_buffer, (int *) &s->vrtsp_state.desc.width, (int *) &s->vrtsp_state.desc.height);
debug_msg("[rtsp] playing video from server (size: WxH = %d x %d)...\n", s->vrtsp_state.desc.width,s->vrtsp_state.desc.height);
fclose(sdp_file);
return len_nals;
error:
if(sdp_file)
fclose(sdp_file);
return -1;
}
#define LEN 10
bool setup_codecs_and_controls_from_sdp(FILE *sdp_file, void *state) {
struct rtsp_state *rtspState;
rtspState = (struct rtsp_state *) state;
int n=0;
char *line = (char*) malloc(1024);
char* tmpBuff;
int countT = 0;
int countC = 0;
char codecs[2][LEN] = { { 0 } };
char tracks[2][LEN] = { { 0 } };
fseek(sdp_file, 0, SEEK_END);
long fileSize = ftell(sdp_file);
if (fileSize < 0) {
perror("RTSP ftell");
free(line);
return false;
}
rewind(sdp_file);
auto buffer = std::make_unique<char[]>(fileSize + 1);
unsigned long readResult = fread(buffer.get(), sizeof(char), fileSize, sdp_file);
if (ferror(sdp_file)){
perror(MOD_NAME "SDP file read failed");
free(line);
return false;
}
buffer[readResult] = '\0';
while (buffer[n] != '\0'){
getNewLine(buffer.get(),&n,line);
sscanf(line, " a = control: %*s");
tmpBuff = strstr(line, "track");
if(tmpBuff!=NULL){
if ((unsigned) countT < sizeof tracks / sizeof tracks[0]) {
//debug_msg("track = %s\n",tmpBuff);
strncpy(tracks[countT],tmpBuff,MIN(strlen(tmpBuff)-2, sizeof tracks[countT] - 1));
tracks[countT][MIN(strlen(tmpBuff)-2, sizeof tracks[countT] - 1)] = '\0';
countT++;
} else {
log_msg(LOG_LEVEL_WARNING, "skipping track = %s\n",tmpBuff);
}
}
tmpBuff=NULL;
int pt = 0;
sscanf(line, " a=rtpmap:%d %*s", &pt);
tmpBuff = strstr(line, "H264");
if(tmpBuff!=NULL){
if ((unsigned) countC < sizeof codecs / sizeof codecs[0]) {
//debug_msg("codec = %s\n",tmpBuff);
strncpy(codecs[countC],tmpBuff,4);
codecs[countC][4] = '\0';
countC++;
if (pt == 0) {
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Missing video PT for H.264!\n");
return false;
}
rtspState->vrtsp_state.pt = pt;
} else {
log_msg(LOG_LEVEL_WARNING, "skipping codec = %s\n",tmpBuff);
}
}
tmpBuff=NULL;
sscanf(line, " a=rtpmap:97 %*s");
tmpBuff = strstr(line, "PCMU");
if(tmpBuff!=NULL){
if ((unsigned) countC < sizeof codecs / sizeof codecs[0]) {
//debug_msg("codec = %s\n",tmpBuff);
strncpy(codecs[countC],tmpBuff,4);
codecs[countC][4] = '\0';
countC++;
} else {
log_msg(LOG_LEVEL_WARNING, "skipping codec = %s\n",tmpBuff);
}
}
tmpBuff=NULL;
if(countT > 1 && countC > 1) break;
}
debug_msg("\nTRACK = %s FOR CODEC = %s",tracks[0],codecs[0]);
debug_msg("\nTRACK = %s FOR CODEC = %s\n",tracks[1],codecs[1]);
for(int p=0;p<2;p++){
if(strncmp(codecs[p],"H264",4)==0){
rtspState->vrtsp_state.codec = "H264";
rtspState->vrtsp_state.control = strdup(tracks[p]);
}if(strncmp(codecs[p],"PCMU",4)==0){
rtspState->artsp_state.codec = "PCMU";
rtspState->artsp_state.control = strdup(tracks[p]);
}
}
free(line);
rewind(sdp_file);
return true;
}
void getNewLine(const char* buffer, int* i, char* line){
int j=0;
while(buffer[*i] != '\n' && buffer[*i] != '\0'){
j++;
(*i)++;
}
if(buffer[*i] == '\n'){
j++;
(*i)++;
}
if(j>0){
memcpy(line,buffer+(*i)-j,j);
if (line[j - 1] == '\r') {
line[j - 1] = '\0';
}
}
line[j] = '\0';
}
/**
* Initializes decompressor if required by decompress flag
*/
static int
init_decompressor(struct video_rtsp_state *sr, struct video_desc desc) {
if (decompress_init_multi(H264, VIDEO_CODEC_NONE, UYVY, &sr->sd, 1)) {
decompress_reconfigure(sr->sd, desc, 16, 8, 0,
vc_get_linesize(desc.width, UYVY), UYVY);
} else
return 0;
return 1;
}
/**
* send RTSP GET PARAMS request
*/
static int
rtsp_get_parameters(CURL *curl, const char *uri) {
CURLcode res = CURLE_OK;
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri, return -1);
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST,
(long )CURL_RTSPREQ_GET_PARAMETER, return -1);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP GET PARAMETERS] curl_easy_perform failed", res);
error_msg("[RTSP GET PARAMETERS] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
return 0;
}else{
return 1;
}
}
/**
* send RTSP OPTIONS request
*/
static int
rtsp_options(CURL *curl, const char *uri) {
char control[1500] = "",
user[1500] = "",
pass[1500] = "",
*strtoken;
CURLcode res = CURLE_OK;
debug_msg("\n[rtsp] OPTIONS %s\n", uri);
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri, return -1);
sscanf(uri, "rtsp://%1500s", control);
strtoken = strtok(control, ":");
assert(strtoken != NULL);
strncpy(user, strtoken, sizeof user - 1);
strtoken = strtok(NULL, "@");
if (strtoken != NULL) {
strncpy(pass, strtoken, sizeof pass - 1);
my_curl_easy_setopt(curl, CURLOPT_USERNAME, user, return -1);
my_curl_easy_setopt(curl, CURLOPT_PASSWORD, pass, return -1);
}
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long )CURL_RTSPREQ_OPTIONS, return -1);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP OPTIONS] curl_easy_perform failed", res);
error_msg("[RTSP OPTIONS] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
return 0;
}else{
return 1;
}
}
/**
* send RTSP DESCRIBE request and write sdp response to a file
*/
static bool
rtsp_describe(CURL *curl, const char *uri, FILE *sdp_fp) {
CURLcode res = CURLE_OK;
debug_msg("\n[rtsp] DESCRIBE %s\n", uri);
my_curl_easy_setopt(curl, CURLOPT_WRITEDATA, sdp_fp, goto error);
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST,
(long )CURL_RTSPREQ_DESCRIBE, goto error);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP DESCRIGE] curl_easy_perform failed", res);
error_msg("[RTSP DESCRIBE] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
goto error;
}
my_curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout, goto error);
rewind(sdp_fp);
return true;
error:
return false;
}
/**
* send RTSP SETUP request
*/
static int
rtsp_setup(CURL *curl, const char *uri, const char *transport) {
CURLcode res = CURLE_OK;
debug_msg("\n[rtsp] SETUP %s\n", uri);
debug_msg("\t TRANSPORT %s\n", transport);
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri, return -1);
my_curl_easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, transport, return -1);
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long )CURL_RTSPREQ_SETUP, return -1);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP SETUP] curl_easy_perform failed", res);
error_msg("[RTSP SETUP] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
return 0;
}else{
return 1;
}
}
/**
* send RTSP PLAY request
*/
static int
rtsp_play(CURL *curl, const char *uri, const char * /* range */) {
CURLcode res = CURLE_OK;
debug_msg("\n[rtsp] PLAY %s\n", uri);
my_curl_easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, uri, return -1);
//my_curl_easy_setopt(curl, CURLOPT_RANGE, range); //range not set because we want (right now) no limit range for streaming duration
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, (long )CURL_RTSPREQ_PLAY, return -1);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP PLAY] curl_easy_perform failed", res);
error_msg("[RTSP PLAY] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
return 0;
}else{
return 1;
}
}
/**
* send RTSP TEARDOWN request
*/
static int
rtsp_teardown(CURL *curl, const char *uri) {
CURLcode res = CURLE_OK;
debug_msg("\n[rtsp] TEARDOWN %s\n", uri);
my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST,
(long )CURL_RTSPREQ_TEARDOWN, return -1);
if ((res = curl_easy_perform(curl)) != CURLE_OK){
curl_print_error("[RTSP TEAR DOWN] curl_easy_perform failed", res);
error_msg("[RTSP TEARD DOWN] could not configure rtsp capture properly, \n\t\tplease check your parameters. \ncleaning...\n\n");
return 0;
}else{
return 1;
}
}
static struct vidcap_type *
vidcap_rtsp_probe(bool verbose, void (**deleter)(void *)) {
UNUSED(verbose);
*deleter = free;
struct vidcap_type *vt;
vt = (struct vidcap_type *) calloc(1, sizeof(struct vidcap_type));
if (vt != NULL) {
vt->name = "rtsp";
vt->description = "Video capture from RTSP remote server";
}
return vt;
}
static void
vidcap_rtsp_done(void *state) {
struct rtsp_state *s = (struct rtsp_state *) state;
pthread_mutex_lock(&s->lock);
s->should_exit = TRUE;
pthread_cond_signal(&s->keepalive_cv);
pthread_mutex_unlock(&s->lock);
if (s->vrtsp_state.vrtsp_thread_id) {
pthread_join(s->vrtsp_state.vrtsp_thread_id, NULL);
}
if (s->keep_alive_rtsp_thread_id) {
pthread_join(s->keep_alive_rtsp_thread_id, NULL);
}
if(s->vrtsp_state.sd)
decompress_done(s->vrtsp_state.sd);
if (s->vrtsp_state.device != nullptr) {
rtp_done(s->vrtsp_state.device);
}
if(s->vrtsp_state.h264_offset_buffer!=NULL) free(s->vrtsp_state.h264_offset_buffer);
vf_free(s->vrtsp_state.out_frame);
free(s->vrtsp_state.control);
free(s->artsp_state.control);
rtsp_teardown(s->curl, s->uri);
curl_easy_cleanup(s->curl);
curl_global_cleanup();
s->curl = NULL;
pthread_mutex_destroy(&s->lock);
pthread_cond_destroy(&s->keepalive_cv);
pthread_mutex_destroy(&s->vrtsp_state.lock);
pthread_cond_destroy(&s->vrtsp_state.boss_cv);
pthread_cond_destroy(&s->vrtsp_state.worker_cv);
free(s);
}
/**
* scan sdp file for media control attributes to generate coded frame required params (WxH and offset)
*/
static int
get_nals(FILE *sdp_file, char *nals, int *width, int *height) {
uint8_t nalInfo;
uint8_t type;
uint8_t nri __attribute__((unused));
int max_len = 1500, len_nals = 0;
char *s = (char *) malloc(max_len);
char *sprop;
memset(s, 0, max_len);
nals[0] = '\0';
while (fgets(s, max_len - 2, sdp_file) != NULL) {
sprop = strstr(s, "sprop-parameter-sets=");
if (sprop != NULL) {
gsize length; //gsize is an unsigned int.
char *nal_aux, *nal;
memcpy(nals, start_sequence, sizeof(start_sequence));
len_nals = sizeof(start_sequence);
nal_aux = strstr(sprop, "=");
nal_aux++;
nal = strtok(nal_aux, ",;");
if (nal == nullptr) {
continue;
}
//convert base64 to hex
guchar *nals_aux = g_base64_decode(nal, &length);
memcpy(nals + len_nals, nals_aux, length);
g_free(nals_aux);
len_nals += length;
nalInfo = (uint8_t) nals[4];
type = nalInfo & 0x1f;
nri = nalInfo & 0x60;
if (type == 7){
width_height_from_SDP(width, height , (unsigned char *) (nals+4), length);
}
while ((nal = strtok(NULL, ",;")) != NULL) {
guchar *nals_aux = g_base64_decode(nal, &length);
if (length) {
//convert base64 to hex
memcpy(nals+len_nals, start_sequence, sizeof(start_sequence));
len_nals += sizeof(start_sequence);
memcpy(nals + len_nals, nals_aux, length);
len_nals += length;
nalInfo = (uint8_t) nals[len_nals - length];
type = nalInfo & 0x1f;
nri = nalInfo & 0x60;
if (type == 7){
width_height_from_SDP(width, height , (unsigned char *) (nals+(len_nals - length)), length);
}
//assure start sequence injection between sps, pps and other nals
memcpy(nals+len_nals, start_sequence, sizeof(start_sequence));
len_nals += sizeof(start_sequence);
}
g_free(nals_aux);
}
}
}
free(s);
rewind(sdp_file);
return len_nals;
}
static const struct video_capture_info vidcap_rtsp_info = {
vidcap_rtsp_probe,
vidcap_rtsp_init,
vidcap_rtsp_done,
vidcap_rtsp_grab,
true
};
REGISTER_MODULE(rtsp, &vidcap_rtsp_info, LIBRARY_CLASS_VIDEO_CAPTURE, VIDEO_CAPTURE_ABI_VERSION);
/* vim: set expandtab sw=4: */