89#define KEEPALIVE_INTERVAL_S 5
90#define MAGIC to_fourcc('R', 'T', 'S', 'c')
91#define MOD_NAME "[rtsp] "
92#define VERSION_STR "V1.0"
95#define DEFAULT_VIDEO_FRAME_WIDTH 1920
96#define DEFAULT_VIDEO_FRAME_HEIGHT 1080
97#define INITIAL_VIDEO_RECV_BUFFER_SIZE ((0.1*DEFAULT_VIDEO_FRAME_WIDTH*DEFAULT_VIDEO_FRAME_HEIGHT)*110/100)
100#ifndef CURL_WRITEFUNC_ERROR
101#define CURL_WRITEFUNC_ERROR 0xFFFFFFFF
109 _Thread_local
static char buf[100];
110 snprintf(buf,
sizeof buf,
"%ld", l);
115 _Thread_local
static char buf[100];
116 snprintf(buf,
sizeof buf,
"%p", p);
128 char *: cstr_identity, \
129 const char *: cstr_identity, \
130 default: pointer_to_str)(X)
133#define my_curl_easy_setopt_ex(l, A, B, C, action_fail) \
135 log_msg(l, MOD_NAME "Setting " #B " to %s\n", get_s(C)); \
136 const CURLcode res = curl_easy_setopt((A), (B), (C)); \
137 if (res != CURLE_OK) { \
138 log_msg(LOG_LEVEL_ERROR, MOD_NAME "curl_easy_setopt(%s, %s, %s) failed: %s (%d)\n", #A, #B, #C, curl_easy_strerror(res), res); \
139 printf("[rtsp error] could not configure rtsp capture properly, \n\t\tplease check your parameters. \nExiting...\n\n"); \
144#define my_curl_easy_perform_ex(l, A, action_fail) \
146 log_msg(l, "Performing cURL operation(s)\n"); \
147 const CURLcode res = curl_easy_perform((A)); \
148 if (res != CURLE_OK) { \
149 log_msg(LOG_LEVEL_ERROR, MOD_NAME "[%s] curl_easy_perform(%s) failed: %s (%d)\n", __func__, #A, curl_easy_strerror(res), res); \
150 printf("[rtsp error] could not configure rtsp capture properly, \n\t\tplease check your parameters. \nExiting...\n\n"); \
155#define my_curl_easy_setopt(A, B, C, action_fail) \
156 my_curl_easy_setopt_ex(LOG_LEVEL_VERBOSE, A, B, C, action_fail)
157#define my_curl_easy_perform(A, action_fail) \
158 my_curl_easy_perform_ex(LOG_LEVEL_VERBOSE, A, action_fail)
178rtsp_setup(CURL *curl,
const char *uri,
const char *transport);
182rtsp_play(CURL *curl,
const char *uri,
const char *range);
189get_nals(FILE *sdp_file,
char *nals,
int *width,
int *height);
306 printf(
"\t " TBOLD(
"decompress")
" - decompress the stream "
307 "(default: disabled)\n");
309 printf(
"\t " TBOLD(
"<port>")
" - video RTP receiver port number\n");
314 color_printf(
"\t\t- capture streem on implict port (554)\n");
316 TBOLD(
"uv -t rtsp://192.168.0.20:8554/mystream")
"\n");
317 color_printf(
"\t\t- capture streem on port 8554 (optionally with "
318 "authentization)\n");
320 TBOLD(
"uv -t rtsp://user:pass@[fe80::30]/mystream")
"\n");
321 color_printf(
"\t\t- capture streem on default port with authentization\n");
323 "\t" TBOLD(
"uv -t rtsp://192.168.0.20/mystream:decompress")
"\n");
324 color_printf(
"\t\t- same as first case but decompress the stream "
325 "(to allow to use a different compression)\n");
329 "Supported audio codecs: none (support is currently broken/WIP)\n");
331 "Supported video codecs: " TBOLD(
"H.264")
", " TBOLD(
"JPEG")
"\n");
341 gettimeofday(&tp, NULL);
342 struct timespec timeout = { .tv_sec = tp.tv_sec +
KEEPALIVE_INTERVAL_S, .tv_nsec = tp.tv_usec * 1000 };
343 pthread_mutex_lock(&s->
lock);
349 pthread_mutex_unlock(&s->
lock);
352 pthread_mutex_unlock(&s->
lock);
372 error_msg(
"Wrong Payload type: %u\n", pckt->pt);
375 return d->
decode(cdata, decode_data);
393 struct timeval timeout;
395 timeout.tv_usec = 10000;
450 gettimeofday(&tp, NULL);
451 struct timespec timeout = { .tv_sec = tp.tv_sec, .tv_nsec = (tp.tv_usec + 100*1000) * 1000 };
452 if (timeout.tv_nsec >= 1000L*1000*1000) {
453 timeout.tv_nsec -= 1000L*1000*1000;
477 MSG(VERBOSE,
"Setting the stream size to %ux%u\n",
488 "Dropped zero-sized frame - the size was not published "
489 "in RTSP, waiting for first SPS...\n");
512 frame = decompressed;
533 const char *rtsp_uri_pref =
"rtsp://";
534 if (strcmp(uri, rtsp_uri_pref) == 0) {
535 MSG(ERROR,
"No URI given!\n");
538 char *authority = uri + strlen(rtsp_uri_pref);
539 char *host = authority;
540 if (strchr(authority,
'@') != NULL) {
541 host = strchr(authority,
'@') + 1;
543 if (strchr(host,
':') == NULL) {
545 if (strchr(host,
'/') != NULL) {
546 path = strdup(strchr(host,
'/') + 1);
547 *strchr(host,
'/') =
'\0';
549 snprintf(uri + strlen(uri), uri_len - strlen(uri),
":%d",
552 snprintf(uri + strlen(uri), uri_len - strlen(uri),
"/%s",
557 char *port = strchr(host,
':') + 1;
559 strtol(port, &endptr, 10);
560 if (endptr == port) {
561 MSG(ERROR,
"Non-numeric port \"%s\" (wrong option?)\n", port);
564 if (strchr(port,
':') != NULL) {
565 MSG(WARNING,
"Colon in URI path - possibly wrong option?\n");
568 MSG(INFO,
"Using URI %s\n", uri);
572#define FAIL_SHOW_HELP \
573 vidcap_rtsp_done(s); \
575 return VIDCAP_INIT_FAIL;
581 if (strcmp(
fmt,
"help") == 0 || strcmp(
fmt,
"fullhelp") == 0) {
588 "Please contact " PACKAGE_BUGREPORT
" if you wish to use it.\n");
603 char *save_ptr = NULL;
607 s->
addr =
"127.0.0.1";
621 pthread_mutex_init(&s->
lock, NULL);
629 strcpy(s->
uri,
"rtsp://");
634 while ((
item = strtok_r(tmp,
":", &save_ptr))) {
636 bool option_given =
true;
637 if (strstr(
item,
"rtp_rx_port=") ==
item) {
639 }
else if (strcmp(
item,
"decompress") == 0) {
641 }
else if (strstr(
item,
"size=")) {
642 MSG(WARNING,
"size= parameter is not used! Will be removed in "
645 option_given =
false;
647 if (strcmp(
item,
"rtsp") == 0) {
653 if (strcmp(s->
uri,
"rtsp://") != 0) {
654 strncat(s->
uri,
":",
sizeof s->
uri - strlen(s->
uri) - 1);
658 MSG(ERROR,
"Unknown option: %s\n",
item);
681 error_msg(
"[rtsp] RTP INIT failed - set option\n");
686 error_msg(
"[rtsp] RTP INIT failed - set sdes\n");
692 error_msg(
"[rtsp] RTP INIT failed - set recv buf \nset command: sudo sysctl -w net.core.rmem_max=9123840\n");
697 error_msg(
"[rtsp] RTP INIT failed - set send buf\n");
749 CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
750 if (res != CURLE_OK) {
751 fprintf(stderr,
"[rtsp] curl_global_init(%s) failed: %s (%d)\n",
752 "CURL_GLOBAL_ALL", curl_easy_strerror(res), res);
755 curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
756 fprintf(stderr,
"[rtsp] cURL V%s loaded\n", data->version);
759 curl = curl_easy_init();
761 curl_global_cleanup();
762 fprintf(stderr,
"[rtsp] curl_easy_init() failed\n");
770 int aggregate_size = size * nitems;
773 bool error_occured =
false;
774 if (strncmp(buffer,
"RTSP/1.0 ",
MIN(strlen(
"RTSP/1.0 "), (
size_t) aggregate_size)) == 0) {
775 int code = atoi(buffer + strlen(
"RTSP/1.0 "));
776 error_occured = code != 200;
780 MOD_NAME "%.*s", aggregate_size, buffer);
792 char *save_ptr = NULL;
795 char dup[CURL_MAX_HTTP_HEADER];
796 memcpy(dup, buffer,
MIN(nitems * size,
sizeof dup));
797 dup[
MIN(
sizeof dup - 1, nitems * size)] =
'\0';
799 char *
item = strtok_r(dup,
" ", &save_ptr);
803 if ((strcasecmp(
item,
"Content-Base:") != 0 &&
804 strcasecmp(
item,
"Content-Location:") != 0)) {
807 item = strtok_r(NULL,
" ", &save_ptr);
814 while (end >= s->
base_url && (*end ==
'\r' || *end ==
'\n')) {
819 if (end >= s->
base_url && *end !=
'/') {
823 MSG(VERBOSE,
"Using base URL from headers: %s\n", s->
base_url);
839 const char *range =
"0.000-";
843 char Atransport[256] =
"";
844 char Vtransport[256] =
"";
846 FILE *sdp_file = tmpfile();
847 if (sdp_file == NULL) {
848 sdp_file = fopen(
"rtsp.sdp",
"w+");
849 if (sdp_file == NULL) {
850 perror(
"Creating SDP file");
855 snprintf(Vtransport,
sizeof Vtransport,
"RTP/AVP;unicast;client_port=%d-%d", port, port + 1);
858 snprintf(Atransport,
sizeof Atransport,
"RTP/AVP;unicast;client_port=%d-%d", port+2, port + 3);
886 while ((ch = getc(sdp_file)) != EOF) {
893 fprintf(stderr,
"\n\n");
921 const int len_nals =
get_nals(sdp_file,
926 MSG(VERBOSE,
"playing H264 video from server (size: WxH = %d x %d)..."
931 MSG(ERROR,
"Video codec %s not yet supported by UG.\n",
964 } media = MEDIA_NONE;
966 int advertised_pt = -1;
968 while (fgets(line,
sizeof line, sdp_file) != NULL) {
972 if (sscanf(line,
"m=%2000s %*d RTP/AVP %d", buf, &pt) == 2) {
974 if (strcmp(buf,
"audio") == 0) {
976 }
else if (strcmp(buf,
"video") == 0) {
980 MSG(VERBOSE,
"Unknown media: %s\n", buf);
983 if ((media == MEDIA_AUDIO &&
986 MSG(WARNING,
"Multiple media of same type, "
987 "using last one...");
989 *(media == MEDIA_AUDIO
995 if (media == MEDIA_NONE) {
999 if (sscanf(line,
"a=control:%2000s", buf) == 1) {
1000 const char *rtsp_scheme =
"rtsp://";
1001 if (strncasecmp(buf, rtsp_scheme,
1002 strlen(rtsp_scheme)) != 0) {
1003 char relative_url[
sizeof buf];
1004 strcpy(relative_url, buf);
1005 snprintf(buf,
sizeof buf,
"%s%s",
1006 rtspState->
base_url, relative_url);
1008 *(media == MEDIA_AUDIO
1014 if (sscanf(line,
"a=rtpmap:%d %2000[^/]", &pt, buf) == 2) {
1015 char *
codec = media == MEDIA_AUDIO
1018 if (pt != advertised_pt) {
1020 "media packet type %d doesn't match "
1021 "media advertised PT %d!\n",
1063 (
long )CURL_RTSPREQ_GET_PARAMETER,
return false);
1071 char *save_ptr = NULL;
1072 char *user = strtok_r(user_pass,
":", &save_ptr);
1073 assert(user != NULL);
1075 char *pass = strtok_r(NULL,
":", &save_ptr);
1087 char control[1501] =
"";
1092 sscanf(
uri,
"rtsp://%1500s", control);
1094 if (strchr(control,
'@') != NULL) {
1095 *strchr(control,
'@') =
'\0';
1116 (
long )CURL_RTSPREQ_DESCRIBE,
return false);
1165 (
long )CURL_RTSPREQ_TEARDOWN,
return);
1172 *available_cards = NULL;
1180 pthread_mutex_lock(&s->
lock);
1184 pthread_mutex_unlock(&s->
lock);
1207 if (s->
curl != NULL) {
1212 curl_easy_cleanup(s->
curl);
1213 curl_global_cleanup();
1217 pthread_mutex_destroy(&s->
lock);
1230get_nals(FILE *sdp_file,
char *nals,
int *width,
int *height) {
1231 int max_len = 1500, len_nals = 0;
1232 char *s = (
char *) malloc(max_len);
1234 memset(s, 0, max_len);
1237 while (fgets(s, max_len - 2, sdp_file) != NULL) {
1238 sprop = strstr(s,
"sprop-parameter-sets=");
1239 if (sprop == NULL) {
1242 char *sprop_val = strstr(sprop,
"=") + 1;
1243 char *term = strchr(sprop_val,
';');
1249 while ((nal = strtok(sprop_val,
","))) {
1251 unsigned int length = 0;
1261 memcpy(nals + len_nals, nal_decoded, length);
1265 uint8_t nalInfo = (uint8_t) nals[len_nals - length];
1266 uint8_t
type = nalInfo & 0x1f;
struct video_frame * frame
struct audio_frame ** audio
int void(** deleter)(void *))
struct video_frame * vf_alloc(int count)
Allocates blank video frame.
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
const char * get_codec_name(codec_t codec)
This file contains definition of some common audio types.
int color_printf(const char *format,...)
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_VERBOSE
#define debug_msg(...)
#define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, "[pid/%d +%d %s] ", getpid(), LINE,...
#define VIDCAP_INIT_NOERR
state not initialized, other action performed (typically help)
#define VIDCAP_INIT_OK
initialization successful
#define VIDEO_CAPTURE_ABI_VERSION
#define VIDCAP_INIT_FAIL
error ocured
decompress_status decompress_frame(struct state_decompress *s, unsigned char *dst, unsigned char *compressed, unsigned int compressed_len, int frame_seq, struct video_frame_callbacks *callbacks, struct pixfmt_desc *internal_prop)
Decompresses video frame.
void decompress_done(struct state_decompress *s)
int decompress_reconfigure(struct state_decompress *s, struct video_desc desc, int rshift, int gshift, int bshift, int pitch, codec_t out_codec)
bool decompress_init_multi(codec_t compression, struct pixfmt_desc internal_prop, codec_t out_codec, struct state_decompress **out, int count)
Initializes (multiple) decompress states.
@ LIBRARY_CLASS_VIDEO_CAPTURE
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
@ STR_LEN
"standard" string length placeholder
int pbuf_decode(struct pbuf *playout_buf, time_ns_t curr_time, decode_frame_t decode_func, void *data)
void pbuf_remove(struct pbuf *playout_buf, time_ns_t curr_time)
void pdb_iter_done(pdb_iter_t *it)
struct pdb_e * pdb_iter_next(pdb_iter_t *it)
struct pdb_e * pdb_iter_init(struct pdb *db, pdb_iter_t *it)
struct pdb * pdb_init(volatile int *delay_ms)
int pdb_add(struct pdb *db, uint32_t ssrc)
bool rtp_set_send_buf(struct rtp *session, int bufsize)
bool rtp_set_recv_buf(struct rtp *session, int bufsize)
bool rtp_set_option(struct rtp *session, rtp_option optname, int optval)
void rtp_update(struct rtp *session, time_ns_t curr_time)
struct rtp * rtp_init_if(const char *addr, const char *iface, uint16_t rx_port, uint16_t tx_port, int ttl, double rtcp_bw, int tfrc_on, rtp_callback callback, uint8_t *userdata, int force_ip_version, bool multithreaded)
uint32_t rtp_my_ssrc(struct rtp *session)
bool rtp_set_sdes(struct rtp *session, uint32_t ssrc, rtcp_sdes_type type, const char *value, int length)
int rtp_get_udp_rx_port(struct rtp *session)
bool rtp_recv_r(struct rtp *session, struct timeval *timeout, uint32_t curr_rtp_ts)
Receive RTP packets and dispatch them.
void rtp_done(struct rtp *session)
@ RTP_OPT_WEAK_VALIDATION
void rtp_recv_callback(struct rtp *session, rtp_event *e)
int width_height_from_SDP(int *widthOut, int *heightOut, unsigned char *data, int data_len)
int decode_frame_h264(struct coded_data *cdata, void *decode_data)
struct video_frame * get_sps_pps_frame(const struct video_desc *desc, struct decode_data_rtsp *decode_data)
int decode_frame_jpeg(struct coded_data *cdata, void *decode_data)
static int vidcap_rtsp_init(struct vidcap_params *params, void **state)
static bool rtsp_describe(CURL *curl, const char *uri, FILE *sdp_fp)
#define KEEPALIVE_INTERVAL_S
static int init_decompressor(struct video_rtsp_state *sr, struct video_desc desc)
static void vidcap_rtsp_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
static const uint8_t start_sequence[]
static void rtsp_teardown(CURL *curl, const char *uri)
static struct video_frame * vidcap_rtsp_grab(void *state, struct audio_frame **audio)
static const char * pointer_to_str(void *p)
static bool rtsp_options(CURL *curl, const char *uri)
static void vidcap_rtsp_done(void *state)
static size_t process_rtsp_describe_header(char *buffer, size_t size, size_t nitems, void *userdata)
currently only searches for Content-Base or Content-Location header
static const char * long_to_str(long l)
#define my_curl_easy_setopt_ex(l, A, B, C, action_fail)
#define CURL_WRITEFUNC_ERROR
static bool rtsp_get_parameters(CURL *curl, const char *uri)
#define my_curl_easy_setopt(A, B, C, action_fail)
static int get_nals(FILE *sdp_file, char *nals, int *width, int *height)
static bool rtsp_setup(CURL *curl, const char *uri, const char *transport)
static bool init_rtsp(struct rtsp_state *s)
#define INITIAL_VIDEO_RECV_BUFFER_SIZE
static void rtsp_set_user_pass(CURL *curl, char *user_pass)
static bool setup_codecs_and_controls_from_sdp(FILE *sdp_file, struct rtsp_state *rtspState)
static bool rtsp_play(CURL *curl, const char *uri, const char *range)
static CURL * init_curl()
static void * vidcap_rtsp_thread(void *args)
void rtsp_keepalive(void *state)
#define my_curl_easy_perform(A, action_fail)
#define my_curl_easy_perform_ex(l, A, action_fail)
static const char * cstr_identity(const char *c)
static const struct video_capture_info vidcap_rtsp_info
static bool check_uri(size_t uri_len, char *uri)
check URI validity + append port if not given
static size_t print_rtsp_header(char *buffer, size_t size, size_t nitems, void *userdata)
static void * keep_alive_thread(void *arg)
static int decode_frame_by_pt(struct coded_data *cdata, void *decode_data, struct pbuf_stats *stats)
codec_t get_video_codec_from_pt_rtpmap(int pt, const char *rtpmap_codec_name)
get UG video codec to given params
struct used to comunicate with audio capture/playback device
volatile bool worker_waiting
volatile bool boss_waiting
struct pdb * participants
struct timeval last_audio_time
pthread_t artsp_thread_id
struct decode_data_rtsp::@46::@48 h264
struct video_frame * frame
int(* decode)(struct coded_data *cdata, void *decode_data)
unsigned char offset_buffer[2048]
SPS/PPS headers extracted from RTSP.
struct pbuf * playout_buffer
pthread_t keep_alive_rtsp_thread_id
struct video_rtsp_state vrtsp_state
pthread_cond_t keepalive_cv
_Bool sps_pps_emitted
emit SPS/PPS once first to reduce decoding errors
struct audio_rtsp_state artsp_state
volatile bool should_exit
char base_url[1024]
for control URLs with relative path; '/' included
char * data
Raw tile data. Pointer must be at least 4B aligned.
unsigned int data_len
length of the data
enum interlacing_t interlacing
unsigned int width
width of every tile (!)
unsigned int height
height of every tile (!)
void(* dispose)(struct video_frame *)
Struct video_frame represents a video frame and contains video description.
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
struct video_frame_callbacks callbacks
volatile bool worker_waiting
struct pdb * participants
struct video_desc decompress_desc
pthread_t vrtsp_thread_id
struct video_frame * out_frame
struct state_decompress * sd
volatile bool boss_waiting
struct decode_data_rtsp decode_data
static struct timeval start_time
static time_ns_t get_time_in_ns()
@ PROGRESSIVE
progressive frame
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
@ JPEG
JPEG image, restart intervals may be used. Compatible with GPUJPEG.
unsigned char * base64_decode(const char *in, unsigned int *length)
This is an umbrella header for video functions.
const char * vidcap_params_get_fmt(const struct vidcap_params *params)
unsigned int vidcap_params_get_flags(const struct vidcap_params *params)
#define VIDCAP_FLAG_AUDIO_ANY
API for video decompress drivers.
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.