obs-ffmpeg: Native SRT/RIST for mpegts output

Currently the ffmpeg_mpegts_muxer output is integrated with ffmpeg-mux.
Both use obs native encoders in contrast with obs-ffmpeg-output which
relies on avcodec library.
This allowed easy implementation of SRT, RIST & HLS protocols through
avformat library.
The main drawback is that obs-ffmpeg-mux exe doesn't allow for easy
debugging nor logging of the protocols.
It was written initially as a separate binary designed for recording so
that if obs fails for some reason, the recording can still terminate
gracefully.

In this commit the ffmpeg_mpegts_muxer is rewritten so that a pipe to
the ffmpeg-mux binary is not used any more.
The muxing to mpegts is still delegated to avformat.
But it can be traced more easily in all its steps.
Also the protocol part for SRT & RIST is implemented natively.
Custom avio_contexts for SRT & RIST are used to that end.
This allows to pass our own implementation of librist and libsrt
libraries instead of relying on avformat. This is very advantageous :
- this allows better logging.
- this allows better bug fixing and maintainance without having to rely
on hypothetical upstream fixes.

One immediate bonus of native implementation is that fixes bugs which
were not previously fixable.

Fixes: SRT & RIST auto-reconnect partly broken  #6749
Fixes: SRT: OBS unusable and uncloseable after starting stream to
invalid srt server #5791

Signed-off-by: pkv <pkv@obsproject.com>
master
pkv 2022-02-27 15:30:40 +01:00
parent 5f40084d25
commit e7d097cab8
No known key found for this signature in database
GPG Key ID: A95102E0A16ABD47
10 changed files with 2677 additions and 56 deletions

View File

@ -0,0 +1,74 @@
# Once done these will be defined:
#
# LIBRIST_FOUND LIBRIST_INCLUDE_DIRS LIBRIST_LIBRARIES
#
# For use in OBS:
#
# LIBRIST_INCLUDE_DIR
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_LIBRIST QUIET librist)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
find_path(
LIBRIST_INCLUDE_DIR
NAMES librist.h librist/librist.h
HINTS ENV LIBRIST_PATH ${LIBRIST_PATH} ${CMAKE_SOURCE_DIR}/${LIBRIST_PATH}
${_LIBRIST_INCLUDE_DIRS} ${DepsPath}
PATHS /usr/include /usr/local/include /opt/local/include /sw/include
PATH_SUFFIXES include)
find_library(
LIBRIST_LIB
NAMES ${_LIBRIST_LIBRARIES} librist rist
HINTS ENV LIBRIST_PATH ${LIBRIST_PATH} ${CMAKE_SOURCE_DIR}/${LIBRIST_PATH}
${_LIBRIST_LIBRARY_DIRS} ${DepsPath}
PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix}
lib
libs${_lib_suffix}
libs
bin${_lib_suffix}
bin
../lib${_lib_suffix}
../lib
../libs${_lib_suffix}
../libs
../bin${_lib_suffix}
../bin)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Librist DEFAULT_MSG LIBRIST_LIB
LIBRIST_INCLUDE_DIR)
mark_as_advanced(LIBRIST_INCLUDE_DIR LIBRIST_LIB)
if(LIBRIST_FOUND)
set(LIBRIST_INCLUDE_DIRS ${LIBRIST_INCLUDE_DIR})
set(LIBRIST_LIBRARIES ${LIBRIST_LIB})
if(NOT TARGET Librist::Librist)
if(IS_ABSOLUTE "${LIBRIST_LIBRARIES}")
add_library(Librist::Librist UNKNOWN IMPORTED)
set_target_properties(Librist::Librist PROPERTIES IMPORTED_LOCATION
"${LIBRIST_LIBRARIES}")
else()
add_library(Librist::Librist INTERFACE IMPORTED)
set_target_properties(Librist::Librist PROPERTIES IMPORTED_LIBNAME
"${LIBRIST_LIBRARIES}")
endif()
set_target_properties(
Librist::Librist PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${LIBRIST_INCLUDE_DIRS}")
endif()
else()
message(STATUS "librist library not found")
endif()

View File

@ -0,0 +1,74 @@
# Once done these will be defined:
#
# LIBSRT_FOUND LIBSRT_INCLUDE_DIRS LIBSRT_LIBRARIES
#
# For use in OBS:
#
# LIBSRT_INCLUDE_DIR
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_LIBSRT QUIET libsrt)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
find_path(
LIBSRT_INCLUDE_DIR
NAMES srt.h srt/srt.h
HINTS ENV LIBSRT_PATH ${LIBSRT_PATH} ${CMAKE_SOURCE_DIR}/${LIBSRT_PATH}
${_LIBSRT_INCLUDE_DIRS} ${DepsPath}
PATHS /usr/include /usr/local/include /opt/local/include /sw/include
PATH_SUFFIXES include)
find_library(
LIBSRT_LIB
NAMES ${_LIBSRT_LIBRARIES} srt libsrt
HINTS ENV LIBSRT_PATH ${LIBSRT_PATH} ${CMAKE_SOURCE_DIR}/${LIBSRT_PATH}
${_LIBSRT_LIBRARY_DIRS} ${DepsPath}
PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix}
lib
libs${_lib_suffix}
libs
bin${_lib_suffix}
bin
../lib${_lib_suffix}
../lib
../libs${_lib_suffix}
../libs
../bin${_lib_suffix}
../bin)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libsrt DEFAULT_MSG LIBSRT_LIB
LIBSRT_INCLUDE_DIR)
mark_as_advanced(LIBSRT_INCLUDE_DIR LIBSRT_LIB)
if(LIBSRT_FOUND)
set(LIBSRT_INCLUDE_DIRS ${LIBSRT_INCLUDE_DIR})
set(LIBSRT_LIBRARIES ${LIBSRT_LIB})
if(NOT TARGET Libsrt::Libsrt)
if(IS_ABSOLUTE "${LIBSRT_LIBRARIES}")
add_library(Libsrt::Libsrt UNKNOWN IMPORTED)
set_target_properties(Libsrt::Libsrt PROPERTIES IMPORTED_LOCATION
"${LIBSRT_LIBRARIES}")
else()
add_library(Libsrt::Libsrt INTERFACE IMPORTED)
set_target_properties(Libsrt::Libsrt PROPERTIES IMPORTED_LIBNAME
"${LIBSRT_LIBRARIES}")
endif()
set_target_properties(
Libsrt::Libsrt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${LIBSRT_INCLUDE_DIRS}")
endif()
else()
message(STATUS "libsrt library not found")
endif()

View File

@ -14,6 +14,9 @@ find_package(
add_library(obs-ffmpeg MODULE)
add_library(OBS::ffmpeg ALIAS obs-ffmpeg)
find_package(Librist QUIET)
find_package(Libsrt QUIET)
add_subdirectory(ffmpeg-mux)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs-ffmpeg-config.h.in
@ -27,13 +30,17 @@ target_sources(
obs-ffmpeg-av1.c
obs-ffmpeg-nvenc.c
obs-ffmpeg-output.c
obs-ffmpeg-mpegts.c
obs-ffmpeg-mux.c
obs-ffmpeg-mux.h
obs-ffmpeg-hls-mux.c
obs-ffmpeg-source.c
obs-ffmpeg-compat.h
obs-ffmpeg-formats.h
${CMAKE_BINARY_DIR}/config/obs-ffmpeg-config.h)
${CMAKE_BINARY_DIR}/config/obs-ffmpeg-config.h
obs-ffmpeg-srt.h
obs-ffmpeg-rist.h
obs-ffmpeg-url.h)
target_include_directories(obs-ffmpeg PRIVATE ${CMAKE_BINARY_DIR}/config)
@ -48,7 +55,9 @@ target_link_libraries(
FFmpeg::avdevice
FFmpeg::avutil
FFmpeg::swscale
FFmpeg::swresample)
FFmpeg::swresample
Librist::Librist
Libsrt::Libsrt)
if(ENABLE_FFMPEG_LOGGING)
target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-logging.c)
@ -68,6 +77,7 @@ if(OS_WINDOWS)
if(MSVC)
target_link_libraries(obs-ffmpeg PRIVATE OBS::w32-pthreads)
endif()
target_link_libraries(obs-ffmpeg PRIVATE ws2_32.lib)
set(MODULE_DESCRIPTION "OBS FFmpeg module")
configure_file(${CMAKE_SOURCE_DIR}/cmake/bundle/windows/obs-module.rc.in

File diff suppressed because it is too large Load Diff

View File

@ -36,12 +36,6 @@ static const char *ffmpeg_mux_getname(void *type)
return obs_module_text("FFmpegMuxer");
}
static const char *ffmpeg_mpegts_mux_getname(void *type)
{
UNUSED_PARAMETER(type);
return obs_module_text("FFmpegMpegtsMuxer");
}
static inline void replay_buffer_clear(struct ffmpeg_muxer *stream)
{
while (stream->packets.size > 0) {
@ -898,30 +892,6 @@ static int connect_time(struct ffmpeg_muxer *stream)
return 0;
}
static int ffmpeg_mpegts_mux_connect_time(void *data)
{
struct ffmpeg_muxer *stream = data;
/* TODO */
return connect_time(stream);
}
struct obs_output_info ffmpeg_mpegts_muxer = {
.id = "ffmpeg_mpegts_muxer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_SERVICE,
.encoded_video_codecs = "h264;av1",
.encoded_audio_codecs = "aac",
.get_name = ffmpeg_mpegts_mux_getname,
.create = ffmpeg_mux_create,
.destroy = ffmpeg_mux_destroy,
.start = ffmpeg_mux_start,
.stop = ffmpeg_mux_stop,
.encoded_packet = ffmpeg_mux_data,
.get_total_bytes = ffmpeg_mux_total_bytes,
.get_properties = ffmpeg_mux_properties,
.get_connect_time_ms = ffmpeg_mpegts_mux_connect_time,
};
/* ------------------------------------------------------------------------ */
static const char *replay_buffer_getname(void *type)

View File

@ -28,30 +28,6 @@
#include <libavutil/channel_layout.h>
#include <libavutil/mastering_display_metadata.h>
struct ffmpeg_output {
obs_output_t *output;
volatile bool active;
struct ffmpeg_data ff_data;
bool connecting;
pthread_t start_thread;
uint64_t total_bytes;
uint64_t audio_start_ts;
uint64_t video_start_ts;
uint64_t stop_ts;
volatile bool stopping;
bool write_thread_active;
pthread_mutex_t write_mutex;
pthread_t write_thread;
os_sem_t *write_sem;
os_event_t *stop_event;
DARRAY(AVPacket *) packets;
};
/* ------------------------------------------------------------------------- */
static void ffmpeg_output_set_last_error(struct ffmpeg_data *data,

View File

@ -5,12 +5,14 @@
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include "obs-ffmpeg-url.h"
struct ffmpeg_cfg {
const char *url;
const char *format_name;
const char *format_mime_type;
const char *muxer_settings;
const char *protocol_settings; // not used yet for SRT nor RIST
int gop_size;
int video_bitrate;
int audio_bitrate;
@ -32,6 +34,7 @@ struct ffmpeg_cfg {
int scale_height;
int width;
int height;
int frame_size; // audio frame size
};
struct ffmpeg_audio_info {
@ -74,5 +77,38 @@ struct ffmpeg_data {
char *last_error;
};
struct ffmpeg_output {
obs_output_t *output;
volatile bool active;
struct ffmpeg_data ff_data;
bool connecting;
pthread_t start_thread;
uint64_t total_bytes;
uint64_t audio_start_ts;
uint64_t video_start_ts;
uint64_t stop_ts;
volatile bool stopping;
bool write_thread_active;
pthread_mutex_t write_mutex;
pthread_t write_thread;
os_sem_t *write_sem;
os_event_t *stop_event;
DARRAY(AVPacket *) packets;
/* used for SRT & RIST */
URLContext *h;
AVIOContext *s;
bool got_headers;
};
bool ffmpeg_data_init(struct ffmpeg_data *data, struct ffmpeg_cfg *config);
void ffmpeg_data_free(struct ffmpeg_data *data);
#define SRT_PROTO "srt"
#define UDP_PROTO "udp"
#define TCP_PROTO "tcp"
#define HTTP_PROTO "http"
#define RIST_PROTO "rist"

View File

@ -0,0 +1,269 @@
/*
* The following code is a port of FFmpeg/avformat/librist.c for obs-studio.
* Port by pkv@obsproject.com.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <obs-module.h>
#include "obs-ffmpeg-url.h"
#include <librist/librist.h>
#include <librist/version.h>
// RIST_MAX_PACKET_SIZE - 28 minimum protocol overhead
#define RIST_MAX_PAYLOAD_SIZE (10000 - 28)
#define FF_LIBRIST_MAKE_VERSION(major, minor, patch) \
((patch) + ((minor)*0x100) + ((major)*0x10000))
#define FF_LIBRIST_VERSION \
FF_LIBRIST_MAKE_VERSION(LIBRIST_API_VERSION_MAJOR, \
LIBRIST_API_VERSION_MINOR, \
LIBRIST_API_VERSION_PATCH)
#define FF_LIBRIST_VERSION_41 FF_LIBRIST_MAKE_VERSION(4, 1, 0)
#define FF_LIBRIST_VERSION_42 FF_LIBRIST_MAKE_VERSION(4, 2, 0)
#define FF_LIBRIST_FIFO_DEFAULT_SHIFT 13
typedef struct RISTContext {
int profile;
int buffer_size;
int packet_size;
int log_level;
int encryption;
int fifo_shift;
bool overrun_nonfatal;
char *secret;
struct rist_logging_settings logging_settings;
struct rist_peer_config peer_config;
struct rist_peer *peer;
struct rist_ctx *ctx;
int statsinterval;
struct rist_stats_sender_peer *stats_list;
} RISTContext;
static int risterr2ret(int err)
{
switch (err) {
case RIST_ERR_MALLOC:
return AVERROR(ENOMEM);
default:
return AVERROR_EXTERNAL;
}
}
static int log_cb(void *arg, enum rist_log_level log_level, const char *msg)
{
int level;
switch (log_level) {
case RIST_LOG_ERROR:
level = AV_LOG_ERROR;
break;
case RIST_LOG_WARN:
level = AV_LOG_WARNING;
break;
case RIST_LOG_NOTICE:
level = AV_LOG_INFO;
break;
case RIST_LOG_INFO:
level = AV_LOG_VERBOSE;
break;
case RIST_LOG_DEBUG:
level = AV_LOG_DEBUG;
break;
case RIST_LOG_DISABLE:
level = AV_LOG_QUIET;
break;
default:
level = AV_LOG_WARNING;
}
av_log(arg, level, "%s", msg);
return 0;
}
static int librist_close(URLContext *h)
{
RISTContext *s = h->priv_data;
int ret = 0;
s->peer = NULL;
if (s->ctx)
ret = rist_destroy(s->ctx);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : failed to close properly %s\n",
h->url);
return -1;
}
s->ctx = NULL;
return 0;
}
static int cb_stats(void *arg, const struct rist_stats *stats_container)
{
RISTContext *s = (RISTContext *)arg;
rist_log(&s->logging_settings, RIST_LOG_INFO, "%s\n",
stats_container->stats_json);
if (stats_container->stats_type == RIST_STATS_SENDER_PEER) {
blog(LOG_DEBUG,
"[obs-ffmpeg mpegts muxer / librist] RIST STATS\n\n"
"bandwidth [%.3f Mbps]\npackets sent [%llu]\npkts received [%llu]\n"
"pkts retransmitted [%llu]\nquality (pkt sent over sent+retransmitted+skipped) [%.2f] \n"
"rtt [%" PRIu32 " ms]\n\n",
(double)(stats_container->stats.sender_peer.bandwidth) /
1000000.0,
stats_container->stats.sender_peer.sent,
stats_container->stats.sender_peer.received,
stats_container->stats.sender_peer.retransmitted,
stats_container->stats.sender_peer.quality,
stats_container->stats.sender_peer.rtt);
}
rist_stats_free(stats_container);
return 0;
}
static int librist_open(URLContext *h, const char *uri)
{
RISTContext *s = h->priv_data;
struct rist_logging_settings *logging_settings = &s->logging_settings;
struct rist_peer_config *peer_config = &s->peer_config;
int ret;
s->buffer_size = 3000;
s->profile = RIST_PROFILE_MAIN;
s->packet_size = 1316;
s->log_level = RIST_LOG_INFO;
s->encryption = 0;
s->secret = NULL;
s->overrun_nonfatal = 0;
s->fifo_shift = FF_LIBRIST_FIFO_DEFAULT_SHIFT;
s->logging_settings =
(struct rist_logging_settings)LOGGING_SETTINGS_INITIALIZER;
s->statsinterval = 60000; // log stats every 60 seconds
ret = rist_logging_set(&logging_settings, s->log_level, log_cb, h, NULL,
NULL);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : Failed to initialize logging settings.");
return OBS_OUTPUT_CONNECT_FAILED;
}
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / librist] : \n librist version %s & API = %s .",
librist_version(), librist_api_version());
h->max_packet_size = s->packet_size;
ret = rist_sender_create(&s->ctx, s->profile, 0, logging_settings);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : failed to create a sender \n");
goto err;
}
ret = rist_peer_config_defaults_set(peer_config);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : failed to set peer config defaults.\n");
goto err;
}
#if FF_LIBRIST_VERSION < FF_LIBRIST_VERSION_41
ret = rist_parse_address(
uri, (const struct rist_peer_config **)&peer_config);
#else
ret = rist_parse_address2(uri, &peer_config);
#endif
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : failed to parse the url %s\n",
uri);
librist_close(h);
return OBS_OUTPUT_INVALID_STREAM;
}
if (((s->encryption == 128 || s->encryption == 256) && !s->secret) ||
((peer_config->key_size == 128 || peer_config->key_size == 256) &&
!peer_config->secret[0])) {
blog(LOG_ERROR,
"secret is mandatory if encryption is enabled\n");
librist_close(h);
return OBS_OUTPUT_INVALID_STREAM;
}
if (s->secret && peer_config->secret[0] == 0)
av_strlcpy(peer_config->secret, s->secret,
RIST_MAX_STRING_SHORT);
if (s->secret && (s->encryption == 128 || s->encryption == 256))
peer_config->key_size = s->encryption;
if (s->buffer_size) {
peer_config->recovery_length_min = s->buffer_size;
peer_config->recovery_length_max = s->buffer_size;
}
ret = rist_peer_create(s->ctx, &s->peer, &s->peer_config);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : failed to create a peer. \n");
goto err;
}
ret = rist_start(s->ctx);
if (ret < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / librist] : rist failed to start \n");
goto err;
}
if (rist_stats_callback_set(s->ctx, s->statsinterval, cb_stats,
(void *)s) == -1) {
rist_log(&s->logging_settings, RIST_LOG_ERROR,
"Could not enable stats callback\n");
;
}
return 0;
err:
librist_close(h);
return OBS_OUTPUT_CONNECT_FAILED;
}
static int librist_write(URLContext *h, const uint8_t *buf, int size)
{
RISTContext *s = h->priv_data;
struct rist_data_block data_block = {0};
int ret;
data_block.ts_ntp = 0;
data_block.payload = buf;
data_block.payload_len = size;
ret = rist_sender_data_write(s->ctx, &data_block);
if (ret < 0) {
blog(LOG_WARNING,
"[obs-ffmpeg mpegts muxer / librist] : failed to send data of size %i bytes",
size);
return risterr2ret(ret);
}
return ret;
}

View File

@ -0,0 +1,834 @@
/*
* The following code is a port of FFmpeg/avformat/libsrt.c for obs-studio.
* Port by pkv@obsproject.com.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <obs-module.h>
#include "obs-ffmpeg-url.h"
#include <srt/srt.h>
#include <libavformat/avformat.h>
#define POLLING_TIME 100 /// Time in milliseconds between interrupt check
/* This is for MPEG-TS (7 TS packets) */
#ifndef SRT_LIVE_DEFAULT_PAYLOAD_SIZE
#define SRT_LIVE_DEFAULT_PAYLOAD_SIZE 1316
#endif
enum SRTMode {
SRT_MODE_CALLER = 0,
SRT_MODE_LISTENER = 1,
SRT_MODE_RENDEZVOUS = 2
};
typedef struct SRTContext {
SRTSOCKET fd;
int eid;
int64_t rw_timeout;
int64_t listen_timeout;
int recv_buffer_size;
int send_buffer_size;
int64_t maxbw;
int pbkeylen;
char *passphrase;
#if SRT_VERSION_VALUE >= 0x010302
int enforced_encryption;
int kmrefreshrate;
int kmpreannounce;
int64_t snddropdelay;
#endif
int mss;
int ffs;
int ipttl;
int iptos;
int64_t inputbw;
int oheadbw;
int64_t latency;
int tlpktdrop;
int nakreport;
int64_t connect_timeout;
int payload_size;
int64_t rcvlatency;
int64_t peerlatency;
enum SRTMode mode;
int sndbuf;
int rcvbuf;
int lossmaxttl;
int minversion;
char *streamid;
char *smoother;
int messageapi;
SRT_TRANSTYPE transtype;
int linger;
int tsbpd;
double time; // time in s in order to post logs at definite intervals
} SRTContext;
static int libsrt_neterrno(URLContext *h)
{
int os_errno;
int err = srt_getlasterror(&os_errno);
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt] : %s\n",
srt_getlasterror_str());
if (err == SRT_EASYNCRCV || err == SRT_EASYNCSND)
return AVERROR(EAGAIN);
return os_errno ? AVERROR(os_errno) : AVERROR_UNKNOWN;
}
static int libsrt_getsockopt(URLContext *h, SRTSOCKET fd, SRT_SOCKOPT optname,
const char *optnamestr, void *optval, int *optlen)
{
if (srt_getsockopt(fd, 0, optname, optval, optlen) < 0) {
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / libsrt] : failed to get option %s on socket: %s\n",
optnamestr, srt_getlasterror_str());
return AVERROR(EIO);
}
return 0;
}
static int libsrt_socket_nonblock(SRTSOCKET socket, int enable)
{
int ret, blocking = enable ? 0 : 1;
/* Setting SRTO_{SND,RCV}SYN options to 1 enable blocking mode, setting them to 0 enable non-blocking mode. */
ret = srt_setsockopt(socket, 0, SRTO_SNDSYN, &blocking,
sizeof(blocking));
if (ret < 0)
return ret;
return srt_setsockopt(socket, 0, SRTO_RCVSYN, &blocking,
sizeof(blocking));
}
static int libsrt_epoll_create(URLContext *h, SRTSOCKET fd, int write)
{
int modes = SRT_EPOLL_ERR | (write ? SRT_EPOLL_OUT : SRT_EPOLL_IN);
int eid = srt_epoll_create();
if (eid < 0)
return libsrt_neterrno(h);
if (srt_epoll_add_usock(eid, fd, &modes) < 0) {
srt_epoll_release(eid);
return libsrt_neterrno(h);
}
return eid;
}
static int libsrt_network_wait_fd(URLContext *h, int eid, int write)
{
int ret, len = 1, errlen = 1;
SRTSOCKET ready[1];
SRTSOCKET error[1];
if (write) {
ret = srt_epoll_wait(eid, error, &errlen, ready, &len,
POLLING_TIME, 0, 0, 0, 0);
} else {
ret = srt_epoll_wait(eid, ready, &len, error, &errlen,
POLLING_TIME, 0, 0, 0, 0);
}
if (ret < 0) {
if (srt_getlasterror(NULL) == SRT_ETIMEOUT)
ret = AVERROR(EAGAIN);
else
ret = libsrt_neterrno(h);
} else {
ret = errlen ? AVERROR(EIO) : 0;
}
return ret;
}
int check_interrupt(AVIOInterruptCB *cb)
{
if (cb && cb->callback)
return cb->callback(cb->opaque);
return 0;
}
static int libsrt_network_wait_fd_timeout(URLContext *h, int eid, int write,
int64_t timeout,
AVIOInterruptCB *int_cb)
{
int ret;
int64_t wait_start = 0;
while (1) {
if (check_interrupt(int_cb))
return AVERROR_EXIT;
ret = libsrt_network_wait_fd(h, eid, write);
if (ret != AVERROR(EAGAIN))
return ret;
if (timeout > 0) {
if (!wait_start)
wait_start = av_gettime_relative();
else if (av_gettime_relative() - wait_start > timeout)
return AVERROR(ETIMEDOUT);
}
}
}
static int libsrt_listen(int eid, SRTSOCKET fd, const struct sockaddr *addr,
socklen_t addrlen, URLContext *h, int64_t timeout)
{
int ret;
int reuse = 1;
/* Max streamid length plus an extra space for the terminating null character */
char streamid[513];
int streamid_len = sizeof(streamid);
if (srt_setsockopt(fd, SOL_SOCKET, SRTO_REUSEADDR, &reuse,
sizeof(reuse))) {
blog(LOG_WARNING,
"[obs-ffmpeg mpegts muxer / libsrt] : setsockopt(SRTO_REUSEADDR) failed\n");
}
if (srt_bind(fd, addr, addrlen))
return libsrt_neterrno(h);
if (srt_listen(fd, 1))
return libsrt_neterrno(h);
ret = libsrt_network_wait_fd_timeout(h, eid, 1, timeout,
&h->interrupt_callback);
if (ret < 0)
return ret;
ret = srt_accept(fd, NULL, NULL);
if (ret < 0)
return libsrt_neterrno(h);
if (libsrt_socket_nonblock(ret, 1) < 0)
blog(LOG_DEBUG,
"[obs-ffmpeg mpegts muxer / libsrt] : libsrt_socket_nonblock failed\n");
if (!libsrt_getsockopt(h, ret, SRTO_STREAMID, "SRTO_STREAMID", streamid,
&streamid_len))
/* Note: returned streamid_len doesn't count the terminating null character */
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / libsrt] : accept streamid [%s], length %d\n",
streamid, streamid_len);
return ret;
}
static int libsrt_listen_connect(int eid, SRTSOCKET fd,
const struct sockaddr *addr, socklen_t addrlen,
int64_t timeout, URLContext *h,
int will_try_next)
{
int ret;
if (srt_connect(fd, addr, addrlen) < 0)
return libsrt_neterrno(h);
ret = libsrt_network_wait_fd_timeout(h, eid, 1, timeout,
&h->interrupt_callback);
if (ret < 0) {
if (will_try_next) {
blog(LOG_WARNING,
"[obs-ffmpeg mpegts muxer / libsrt] : Connection to %s failed (%s), trying next address\n",
h->url, av_err2str(ret));
} else {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt] : Connection to %s failed: %s\n",
h->url, av_err2str(ret));
}
}
return ret;
}
static int libsrt_setsockopt(URLContext *h, SRTSOCKET fd, SRT_SOCKOPT optname,
const char *optnamestr, const void *optval,
int optlen)
{
if (srt_setsockopt(fd, 0, optname, optval, optlen) < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt] : failed to set option %s on socket: %s\n",
optnamestr, srt_getlasterror_str());
return AVERROR(EIO);
}
return 0;
}
/* - The "POST" options can be altered any time on a connected socket.
They MAY have also some meaning when set prior to connecting; such
option is SRTO_RCVSYN, which makes connect/accept call asynchronous.
Because of that this option is treated special way in this app. */
static int libsrt_set_options_post(URLContext *h, SRTSOCKET fd)
{
SRTContext *s = (SRTContext *)h->priv_data;
if ((s->inputbw >= 0 &&
libsrt_setsockopt(h, fd, SRTO_INPUTBW, "SRTO_INPUTBW", &s->inputbw,
sizeof(s->inputbw)) < 0) ||
(s->oheadbw >= 0 &&
libsrt_setsockopt(h, fd, SRTO_OHEADBW, "SRTO_OHEADBW", &s->oheadbw,
sizeof(s->oheadbw)) < 0)) {
return AVERROR(EIO);
}
return 0;
}
/* - The "PRE" options must be set prior to connecting and can't be altered
on a connected socket, however if set on a listening socket, they are
derived by accept-ed socket. */
static int libsrt_set_options_pre(URLContext *h, SRTSOCKET fd)
{
SRTContext *s = (SRTContext *)h->priv_data;
int yes = 1;
int latency = (int)(s->latency / 1000);
int rcvlatency = (int)(s->rcvlatency / 1000);
int peerlatency = (int)(s->peerlatency / 1000);
#if SRT_VERSION_VALUE >= 0x010302
int snddropdelay = s->snddropdelay > 0 ? (int)(s->snddropdelay / 1000)
: (int)(s->snddropdelay);
#endif
int connect_timeout = (int)(s->connect_timeout);
if ((s->mode == SRT_MODE_RENDEZVOUS &&
libsrt_setsockopt(h, fd, SRTO_RENDEZVOUS, "SRTO_RENDEZVOUS", &yes,
sizeof(yes)) < 0) ||
(s->transtype != SRTT_INVALID &&
libsrt_setsockopt(h, fd, SRTO_TRANSTYPE, "SRTO_TRANSTYPE",
&s->transtype, sizeof(s->transtype)) < 0) ||
(s->maxbw >= 0 &&
libsrt_setsockopt(h, fd, SRTO_MAXBW, "SRTO_MAXBW", &s->maxbw,
sizeof(s->maxbw)) < 0) ||
(s->pbkeylen >= 0 &&
libsrt_setsockopt(h, fd, SRTO_PBKEYLEN, "SRTO_PBKEYLEN",
&s->pbkeylen, sizeof(s->pbkeylen)) < 0) ||
(s->passphrase &&
libsrt_setsockopt(h, fd, SRTO_PASSPHRASE, "SRTO_PASSPHRASE",
s->passphrase,
(int)strlen(s->passphrase)) < 0) ||
#if SRT_VERSION_VALUE >= 0x010302
#if SRT_VERSION_VALUE >= 0x010401
(s->enforced_encryption >= 0 &&
libsrt_setsockopt(h, fd, SRTO_ENFORCEDENCRYPTION,
"SRTO_ENFORCEDENCRYPTION",
&s->enforced_encryption,
sizeof(s->enforced_encryption)) < 0) ||
#else
/* SRTO_STRICTENC == SRTO_ENFORCEDENCRYPTION (53), but for compatibility, we used SRTO_STRICTENC */
(s->enforced_encryption >= 0 &&
libsrt_setsockopt(h, fd, SRTO_STRICTENC, "SRTO_STRICTENC",
&s->enforced_encryption,
sizeof(s->enforced_encryption)) < 0) ||
#endif
(s->kmrefreshrate >= 0 &&
libsrt_setsockopt(h, fd, SRTO_KMREFRESHRATE, "SRTO_KMREFRESHRATE",
&s->kmrefreshrate,
sizeof(s->kmrefreshrate)) < 0) ||
(s->kmpreannounce >= 0 &&
libsrt_setsockopt(h, fd, SRTO_KMPREANNOUNCE, "SRTO_KMPREANNOUNCE",
&s->kmpreannounce,
sizeof(s->kmpreannounce)) < 0) ||
(s->snddropdelay >= -1 &&
libsrt_setsockopt(h, fd, SRTO_SNDDROPDELAY, "SRTO_SNDDROPDELAY",
&snddropdelay, sizeof(snddropdelay)) < 0) ||
#endif
(s->mss >= 0 && libsrt_setsockopt(h, fd, SRTO_MSS, "SRTO_MSS",
&s->mss, sizeof(s->mss)) < 0) ||
(s->ffs >= 0 && libsrt_setsockopt(h, fd, SRTO_FC, "SRTO_FC",
&s->ffs, sizeof(s->ffs)) < 0) ||
(s->ipttl >= 0 &&
libsrt_setsockopt(h, fd, SRTO_IPTTL, "SRTO_IPTTL", &s->ipttl,
sizeof(s->ipttl)) < 0) ||
(s->iptos >= 0 &&
libsrt_setsockopt(h, fd, SRTO_IPTOS, "SRTO_IPTOS", &s->iptos,
sizeof(s->iptos)) < 0) ||
(s->latency >= 0 &&
libsrt_setsockopt(h, fd, SRTO_LATENCY, "SRTO_LATENCY", &latency,
sizeof(latency)) < 0) ||
(s->rcvlatency >= 0 &&
libsrt_setsockopt(h, fd, SRTO_RCVLATENCY, "SRTO_RCVLATENCY",
&rcvlatency, sizeof(rcvlatency)) < 0) ||
(s->peerlatency >= 0 &&
libsrt_setsockopt(h, fd, SRTO_PEERLATENCY, "SRTO_PEERLATENCY",
&peerlatency, sizeof(peerlatency)) < 0) ||
(s->tlpktdrop >= 0 &&
libsrt_setsockopt(h, fd, SRTO_TLPKTDROP, "SRTO_TLPKTDROP",
&s->tlpktdrop, sizeof(s->tlpktdrop)) < 0) ||
(s->nakreport >= 0 &&
libsrt_setsockopt(h, fd, SRTO_NAKREPORT, "SRTO_NAKREPORT",
&s->nakreport, sizeof(s->nakreport)) < 0) ||
(connect_timeout >= 0 &&
libsrt_setsockopt(h, fd, SRTO_CONNTIMEO, "SRTO_CONNTIMEO",
&connect_timeout,
sizeof(connect_timeout)) < 0) ||
(s->sndbuf >= 0 &&
libsrt_setsockopt(h, fd, SRTO_SNDBUF, "SRTO_SNDBUF", &s->sndbuf,
sizeof(s->sndbuf)) < 0) ||
(s->rcvbuf >= 0 &&
libsrt_setsockopt(h, fd, SRTO_RCVBUF, "SRTO_RCVBUF", &s->rcvbuf,
sizeof(s->rcvbuf)) < 0) ||
(s->lossmaxttl >= 0 &&
libsrt_setsockopt(h, fd, SRTO_LOSSMAXTTL, "SRTO_LOSSMAXTTL",
&s->lossmaxttl, sizeof(s->lossmaxttl)) < 0) ||
(s->minversion >= 0 &&
libsrt_setsockopt(h, fd, SRTO_MINVERSION, "SRTO_MINVERSION",
&s->minversion, sizeof(s->minversion)) < 0) ||
(s->streamid &&
libsrt_setsockopt(h, fd, SRTO_STREAMID, "SRTO_STREAMID",
s->streamid, (int)strlen(s->streamid)) < 0) ||
#if SRT_VERSION_VALUE >= 0x010401
(s->smoother &&
libsrt_setsockopt(h, fd, SRTO_CONGESTION, "SRTO_CONGESTION",
s->smoother, (int)strlen(s->smoother)) < 0) ||
#else
(s->smoother &&
libsrt_setsockopt(h, fd, SRTO_SMOOTHER, "SRTO_SMOOTHER",
s->smoother, (int)strlen(s->smoother)) < 0) ||
#endif
(s->messageapi >= 0 &&
libsrt_setsockopt(h, fd, SRTO_MESSAGEAPI, "SRTO_MESSAGEAPI",
&s->messageapi, sizeof(s->messageapi)) < 0) ||
(s->payload_size >= 0 &&
libsrt_setsockopt(h, fd, SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE",
&s->payload_size,
sizeof(s->payload_size)) < 0) ||
(/*(h->flags & AVIO_FLAG_WRITE) &&*/
libsrt_setsockopt(h, fd, SRTO_SENDER, "SRTO_SENDER", &yes,
sizeof(yes)) < 0) ||
(s->tsbpd >= 0 &&
libsrt_setsockopt(h, fd, SRTO_TSBPDMODE, "SRTO_TSBPDMODE",
&s->tsbpd, sizeof(s->tsbpd)) < 0)) {
return AVERROR(EIO);
}
if (s->linger >= 0) {
struct linger lin;
lin.l_linger = s->linger;
lin.l_onoff = lin.l_linger > 0 ? 1 : 0;
if (libsrt_setsockopt(h, fd, SRTO_LINGER, "SRTO_LINGER", &lin,
sizeof(lin)) < 0)
return AVERROR(EIO);
}
return 0;
}
static int libsrt_setup(URLContext *h, const char *uri)
{
struct addrinfo hints = {0}, *ai, *cur_ai;
int port;
SRTSOCKET fd;
SRTContext *s = (SRTContext *)h->priv_data;
const char *p;
char buf[1024];
int ret;
char hostname[1024], proto[1024], path[1024];
char portstr[10];
int64_t open_timeout = 0;
int eid, write_eid;
av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
&port, path, sizeof(path), uri);
if (strcmp(proto, "srt")) // should not happen !
return AVERROR(EINVAL);
if (port <= 0 || port >= 65536) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt] : Port missing in uri\n");
return OBS_OUTPUT_CONNECT_FAILED;
}
p = strchr(uri, '?');
if (p) {
if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
s->rw_timeout = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
s->listen_timeout = strtoll(buf, NULL, 10);
}
}
if (s->rw_timeout >= 0) {
open_timeout = h->rw_timeout = s->rw_timeout;
}
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
snprintf(portstr, sizeof(portstr), "%d", port);
if (s->mode == SRT_MODE_LISTENER)
hints.ai_flags |= AI_PASSIVE;
ret = getaddrinfo(hostname[0] ? hostname : NULL, portstr, &hints, &ai);
if (ret) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt] : Failed to resolve hostname %s: %s\n",
hostname, gai_strerror(ret));
return OBS_OUTPUT_CONNECT_FAILED;
}
cur_ai = ai;
restart:
fd = srt_create_socket();
if (fd < 0) {
ret = libsrt_neterrno(h);
goto fail;
}
if ((ret = libsrt_set_options_pre(h, fd)) < 0) {
goto fail;
}
/* Set the socket's send or receive buffer sizes, if specified.
If unspecified or setting fails, system default is used. */
if (s->recv_buffer_size > 0) {
srt_setsockopt(fd, SOL_SOCKET, SRTO_UDP_RCVBUF,
&s->recv_buffer_size,
sizeof(s->recv_buffer_size));
}
if (s->send_buffer_size > 0) {
srt_setsockopt(fd, SOL_SOCKET, SRTO_UDP_SNDBUF,
&s->send_buffer_size,
sizeof(s->send_buffer_size));
}
if (libsrt_socket_nonblock(fd, 1) < 0)
blog(LOG_DEBUG,
"[obs-ffmpeg mpegts muxer / libsrt] : libsrt_socket_nonblock failed\n");
ret = write_eid = libsrt_epoll_create(h, fd, 1);
if (ret < 0)
goto fail1;
if (s->mode == SRT_MODE_LISTENER) {
// multi-client
ret = libsrt_listen(write_eid, fd, cur_ai->ai_addr,
(socklen_t)cur_ai->ai_addrlen, h,
s->listen_timeout);
srt_epoll_release(write_eid);
if (ret < 0)
goto fail1;
srt_close(fd);
fd = ret;
} else {
if (s->mode == SRT_MODE_RENDEZVOUS) {
if (srt_bind(fd, cur_ai->ai_addr,
(int)(cur_ai->ai_addrlen))) {
ret = libsrt_neterrno(h);
srt_epoll_release(write_eid);
goto fail1;
}
}
ret = libsrt_listen_connect(write_eid, fd, cur_ai->ai_addr,
(socklen_t)(cur_ai->ai_addrlen),
open_timeout, h, !!cur_ai->ai_next);
srt_epoll_release(write_eid);
if (ret < 0) {
if (ret == AVERROR_EXIT)
goto fail1;
else
goto fail;
}
}
if ((ret = libsrt_set_options_post(h, fd)) < 0) {
goto fail;
}
int packet_size = 0;
int optlen = sizeof(packet_size);
ret = libsrt_getsockopt(h, fd, SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE",
&packet_size, &optlen);
if (ret < 0)
goto fail1;
if (packet_size > 0)
h->max_packet_size = packet_size;
ret = eid = libsrt_epoll_create(h, fd, 1 /*flags & AVIO_FLAG_WRITE*/);
if (eid < 0)
goto fail1;
s->fd = fd;
s->eid = eid;
freeaddrinfo(ai);
return 0;
fail:
if (cur_ai->ai_next) {
/* Retry with the next sockaddr */
cur_ai = cur_ai->ai_next;
if (fd >= 0)
srt_close(fd);
ret = 0;
goto restart;
}
fail1:
if (fd >= 0)
srt_close(fd);
freeaddrinfo(ai);
return ret;
}
static void libsrt_set_defaults(SRTContext *s)
{
s->rw_timeout = -1;
s->listen_timeout = -1;
s->send_buffer_size = -1;
s->recv_buffer_size = -1;
s->payload_size = SRT_LIVE_DEFAULT_PAYLOAD_SIZE;
s->maxbw = -1;
s->pbkeylen = -1;
s->passphrase = NULL;
s->mss = -1;
s->ffs = -1;
s->ipttl = -1;
s->iptos = -1;
s->inputbw = -1;
s->oheadbw = -1;
s->latency = -1;
s->rcvlatency = -1;
s->peerlatency = -1;
s->tlpktdrop = -1;
s->nakreport = -1;
s->connect_timeout = -1;
s->mode = SRT_MODE_CALLER;
s->sndbuf = -1;
s->rcvbuf = -1;
s->lossmaxttl = -1;
s->minversion = -1;
s->streamid = NULL;
s->smoother = NULL;
s->messageapi = -1;
s->transtype = SRTT_LIVE;
s->linger = -1;
s->tsbpd = -1;
}
static int libsrt_open(URLContext *h, const char *uri)
{
SRTContext *s = (SRTContext *)h->priv_data;
const char *p;
char buf[1024];
int ret = 0;
if (srt_startup() < 0) {
blog(LOG_ERROR,
"[obs-ffmpeg mpegts muxer / libsrt] : libsrt failed to load.\n");
return OBS_OUTPUT_CONNECT_FAILED;
} else {
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / libsrt] : libsrt v.%s loaded.\n",
SRT_VERSION_STRING);
}
libsrt_set_defaults(s);
/* SRT options (srt/srt.h) */
p = strchr(uri, '?');
if (p) {
if (av_find_info_tag(buf, sizeof(buf), "maxbw", p)) {
s->maxbw = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "pbkeylen", p)) {
s->pbkeylen = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "passphrase", p)) {
av_freep(&s->passphrase);
s->passphrase = av_strndup(buf, strlen(buf));
}
#if SRT_VERSION_VALUE >= 0x010302
if (av_find_info_tag(buf, sizeof(buf), "enforced_encryption",
p)) {
s->enforced_encryption = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "kmrefreshrate", p)) {
s->kmrefreshrate = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "kmpreannounce", p)) {
s->kmpreannounce = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "snddropdelay", p)) {
s->snddropdelay = strtoll(buf, NULL, 10);
}
#endif
if (av_find_info_tag(buf, sizeof(buf), "mss", p)) {
s->mss = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "ffs", p)) {
s->ffs = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "ipttl", p)) {
s->ipttl = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "iptos", p)) {
s->iptos = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "inputbw", p)) {
s->inputbw = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "oheadbw", p)) {
s->oheadbw = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "latency", p)) {
s->latency = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "tsbpddelay", p)) {
s->latency = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "rcvlatency", p)) {
s->rcvlatency = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "peerlatency", p)) {
s->peerlatency = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "tlpktdrop", p)) {
s->tlpktdrop = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "nakreport", p)) {
s->nakreport = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "connect_timeout", p)) {
s->connect_timeout = strtoll(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "payload_size", p) ||
av_find_info_tag(buf, sizeof(buf), "pkt_size", p)) {
s->payload_size = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "mode", p)) {
if (!strcmp(buf, "caller")) {
s->mode = SRT_MODE_CALLER;
} else if (!strcmp(buf, "listener")) {
s->mode = SRT_MODE_LISTENER;
} else if (!strcmp(buf, "rendezvous")) {
s->mode = SRT_MODE_RENDEZVOUS;
} else {
ret = AVERROR(EINVAL);
goto err;
}
}
if (av_find_info_tag(buf, sizeof(buf), "sndbuf", p)) {
s->sndbuf = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "rcvbuf", p)) {
s->rcvbuf = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "lossmaxttl", p)) {
s->lossmaxttl = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "minversion", p)) {
s->minversion = strtol(buf, NULL, 0);
}
if (av_find_info_tag(buf, sizeof(buf), "streamid", p)) {
av_freep(&s->streamid);
s->streamid = av_strdup(buf);
if (!s->streamid) {
ret = AVERROR(ENOMEM);
goto err;
}
}
if (av_find_info_tag(buf, sizeof(buf), "smoother", p)) {
av_freep(&s->smoother);
s->smoother = av_strdup(buf);
if (!s->smoother) {
ret = AVERROR(ENOMEM);
goto err;
}
}
if (av_find_info_tag(buf, sizeof(buf), "messageapi", p)) {
s->messageapi = strtol(buf, NULL, 10);
}
if (av_find_info_tag(buf, sizeof(buf), "transtype", p)) {
if (!strcmp(buf, "live")) {
s->transtype = SRTT_LIVE;
} else if (!strcmp(buf, "file")) {
s->transtype = SRTT_FILE;
} else {
ret = AVERROR(EINVAL);
goto err;
}
}
if (av_find_info_tag(buf, sizeof(buf), "linger", p)) {
s->linger = strtol(buf, NULL, 10);
}
}
ret = libsrt_setup(h, uri);
if (ret < 0)
goto err;
struct timeb timebuffer;
ftime(&timebuffer);
s->time = (double)timebuffer.time + 0.001 * (double)timebuffer.millitm;
return 0;
err:
av_freep(&s->smoother);
av_freep(&s->streamid);
srt_cleanup();
return ret;
}
static int libsrt_write(URLContext *h, const uint8_t *buf, int size)
{
SRTContext *s = (SRTContext *)h->priv_data;
int ret;
SRT_TRACEBSTATS perf;
ret = libsrt_network_wait_fd_timeout(h, s->eid, 1, h->rw_timeout,
&h->interrupt_callback);
if (ret)
return ret;
ret = srt_send(s->fd, (char *)buf, size);
if (ret < 0) {
ret = libsrt_neterrno(h);
} else {
/* log every 60 seconds the rtt and link bandwidth
* rtt: round-trip time
* link bandwidth: bandwidth from ingest to egress
*/
struct timeb timebuffer;
ftime(&timebuffer);
double time = (double)timebuffer.time +
0.001 * (double)timebuffer.millitm;
if (time > (s->time + 60.0)) {
srt_bistats(s->fd, &perf, 0, 1);
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / libsrt] : rtt [%.2f ms], link bandwidth [%.1f Mbps]\n",
perf.msRTT, perf.mbpsBandwidth);
s->time = time;
}
}
return ret;
}
static int libsrt_close(URLContext *h)
{
SRTContext *s = (SRTContext *)h->priv_data;
/* Log stream stats. */
SRT_TRACEBSTATS perf;
srt_bstats(s->fd, &perf, 1);
blog(LOG_INFO,
"[obs-ffmpeg mpegts muxer / libsrt] : Stream stats\n\n"
"time elapsed [%.1f sec]\nmean speed [%.1f Mbp]\n"
"total bytes sent [%.1f MB]\nbytes retransmitted [%.1f %%]\n"
"bytes dropped [%.1f %%]\n\n",
(double)perf.msTimeStamp / 1000.0, perf.mbpsSendRate,
(double)perf.byteSentTotal / 1000000.0,
perf.byteSentTotal
? perf.byteRetransTotal / perf.byteSentTotal * 100.0
: 0,
perf.byteSentTotal
? perf.byteSndDropTotal / perf.byteSentTotal * 100.0
: 0);
srt_epoll_release(s->eid);
int err = srt_close(s->fd);
if (err < 0) {
blog(LOG_ERROR, "[obs-ffmpeg mpegts muxer / libsrt] : %s\n",
srt_getlasterror_str());
return -1;
}
srt_cleanup();
blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt] : closing srt");
return 0;
}

View File

@ -0,0 +1,143 @@
#pragma once
#include <libavformat/avformat.h>
#include <libavutil/avassert.h>
#include <libavutil/avstring.h>
#include <libavutil/parseutils.h>
#include <libavutil/time.h>
#include <libavutil/error.h>
#include <stdio.h>
#include <sys/timeb.h>
#include <time.h>
#define HTTP_PROTO "http"
#define RIST_PROTO "rist"
#define SRT_PROTO "srt"
#define TCP_PROTO "tcp"
#define UDP_PROTO "udp"
/* lightened version of a struct used by avformat */
typedef struct URLContext {
void *priv_data; /* SRTContext or RISTContext */
char *url; /* URL */
int max_packet_size;
AVIOInterruptCB interrupt_callback;
int64_t rw_timeout; /* max time to wait for write completion in mcs */
} URLContext;
#define UDP_DEFAULT_PAYLOAD_SIZE 1316
/* We need to override libsrt/win/syslog_defs.h due to conflicts w/ some libobs
* definitions.
*/
#ifndef _WIN32
#define _SYS_SYSLOG_H
#endif
#ifdef _WIN32
#ifndef INC_SRT_WINDOWS_SYSLOG_DEFS_H
#define INC_SRT_WINDOWS_SYSLOG_DEFS_H
#define LOG_EMERG 0
#define LOG_ALERT 1
#define LOG_CRIT 2
#define LOG_ERR 3
//#define LOG_WARNING 4 // this creates issues w/ libobs LOG_WARNING = 200
#define LOG_NOTICE 5
//#define LOG_INFO 6 // issue w/ libobs
//#define LOG_DEBUG 7 // issue w/ libobs
#define LOG_PRIMASK 0x07
#define LOG_PRI(p) ((p)&LOG_PRIMASK)
#define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri))
#define LOG_KERN (0 << 3)
#define LOG_USER (1 << 3)
#define LOG_MAIL (2 << 3)
#define LOG_DAEMON (3 << 3)
#define LOG_AUTH (4 << 3)
#define LOG_SYSLOG (5 << 3)
#define LOG_LPR (6 << 3)
#define LOG_NEWS (7 << 3)
#define LOG_UUCP (8 << 3)
#define LOG_CRON (9 << 3)
#define LOG_AUTHPRIV (10 << 3)
#define LOG_FTP (11 << 3)
/* Codes through 15 are reserved for system use */
#define LOG_LOCAL0 (16 << 3)
#define LOG_LOCAL1 (17 << 3)
#define LOG_LOCAL2 (18 << 3)
#define LOG_LOCAL3 (19 << 3)
#define LOG_LOCAL4 (20 << 3)
#define LOG_LOCAL5 (21 << 3)
#define LOG_LOCAL6 (22 << 3)
#define LOG_LOCAL7 (23 << 3)
#define LOG_NFACILITIES 24
#define LOG_FACMASK 0x03f8
#define LOG_FAC(p) (((p)&LOG_FACMASK) >> 3)
#endif
#endif
/* We need to override libsrt/logging_api.h due to conflicts with some libobs
* definitions.
*/
#define INC_SRT_LOGGING_API_H
// These are required for access functions:
// - adding FA (requires set)
// - setting a log stream (requires iostream)
#ifdef __cplusplus
#include <set>
#include <iostream>
#endif
#ifndef _WIN32
#include <syslog.h>
#endif
// Syslog is included so that it provides log level names.
// Haivision log standard requires the same names plus extra one:
#ifndef LOG_DEBUG_TRACE
#define LOG_DEBUG_TRACE 8
#endif
// Flags
#define SRT_LOGF_DISABLE_TIME 1
#define SRT_LOGF_DISABLE_THREADNAME 2
#define SRT_LOGF_DISABLE_SEVERITY 4
#define SRT_LOGF_DISABLE_EOL 8
// Handler type
typedef void SRT_LOG_HANDLER_FN(void *opaque, int level, const char *file,
int line, const char *area,
const char *message);
#ifdef __cplusplus
namespace srt_logging {
struct LogFA {
private:
int value;
public:
operator int() const { return value; }
LogFA(int v) : value(v) {}
};
const LogFA LOGFA_GENERAL = 0;
namespace LogLevel {
enum type {
fatal = LOG_CRIT,
error = LOG_ERR,
warning = 4, //issue w/ libobs so LOG_WARNING is removed
note = LOG_NOTICE,
debug = 7 //issue w/ libobs so LOG_DEBUG is removed
};
}
class Logger;
}
#endif