From d23f7bead5718063a787933f77ef20da6ede7007 Mon Sep 17 00:00:00 2001 From: pkv Date: Mon, 1 Aug 2022 22:34:40 +0200 Subject: [PATCH 1/5] obs-ffmpeg: Move mpegts to its own module This splits the ffmpeg mpegts muxer output from obs-ffmpeg project. There's some common code between obs-ffmpeg-output.c & obs-ffmpeg-mpegts.c. But most of it had already been factored out to allow for easier maintainance and readability. The goal is to allow simpler maintainance of the mpegts output. Signed-off-by: pkv --- CMakeLists.txt | 8 + plugins/CMakeLists.txt | 6 + plugins/obs-ffmpeg/CMakeLists.txt | 9 +- plugins/obs-ffmpeg/cmake/dependencies.cmake | 19 - plugins/obs-ffmpeg/cmake/legacy.cmake | 25 -- plugins/obs-ffmpeg/obs-ffmpeg-output.h | 9 - plugins/obs-ffmpeg/obs-ffmpeg.c | 4 + plugins/obs-mpegts/CMakeLists.txt | 33 ++ plugins/obs-mpegts/cmake/dependencies.cmake | 30 ++ plugins/obs-mpegts/cmake/legacy.cmake | 42 ++ .../obs-mpegts/cmake/windows/obs-module.rc.in | 24 + plugins/obs-mpegts/data/locale/en-US.ini | 1 + .../obs-mpegts-common.h} | 117 +++++ .../obs-mpegts-rist.h} | 3 +- .../obs-mpegts-srt.h} | 4 +- .../obs-mpegts.c} | 425 ++++++++++++------ 16 files changed, 548 insertions(+), 211 deletions(-) create mode 100644 plugins/obs-mpegts/CMakeLists.txt create mode 100644 plugins/obs-mpegts/cmake/dependencies.cmake create mode 100644 plugins/obs-mpegts/cmake/legacy.cmake create mode 100644 plugins/obs-mpegts/cmake/windows/obs-module.rc.in create mode 100644 plugins/obs-mpegts/data/locale/en-US.ini rename plugins/{obs-ffmpeg/obs-ffmpeg-url.h => obs-mpegts/obs-mpegts-common.h} (55%) rename plugins/{obs-ffmpeg/obs-ffmpeg-rist.h => obs-mpegts/obs-mpegts-rist.h} (99%) rename plugins/{obs-ffmpeg/obs-ffmpeg-srt.h => obs-mpegts/obs-mpegts-srt.h} (99%) rename plugins/{obs-ffmpeg/obs-ffmpeg-mpegts.c => obs-mpegts/obs-mpegts.c} (76%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6f157fbe76011..5fcfa59f4a6e35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,10 @@ if(CMAKE_HOST_SYSTEM_NAME MATCHES "(Darwin)" OR OBS_CMAKE_VERSION VERSION_GREATE option(ENABLE_UI "Enable building with UI (requires Qt)" ON) option(ENABLE_SCRIPTING "Enable scripting support" ON) option(ENABLE_HEVC "Enable HEVC encoders" ON) + option(ENABLE_NEW_MPEGTS_OUTPUT "Use native SRT/RIST mpegts output" ON) + if(ENABLE_NEW_MPEGTS_OUTPUT) + add_compile_definitions(NEW_MPEGTS_OUTPUT) + endif() add_subdirectory(libobs) if(OS_WINDOWS) @@ -65,6 +69,10 @@ option(ENABLE_HEVC "Enable HEVC encoders" ON) if(ENABLE_HEVC) add_compile_definitions(ENABLE_HEVC) endif() +option(ENABLE_NEW_MPEGTS_OUTPUT "Use native SRT/RIST mpegts output" ON) +if(ENABLE_NEW_MPEGTS_OUTPUT) + add_compile_definitions(NEW_MPEGTS_OUTPUT) +endif() option(BUILD_FOR_DISTRIBUTION "Build for distribution (enables optimizations)" OFF) option(ENABLE_UI "Enable building with UI (requires Qt)" ON) option(ENABLE_SCRIPTING "Enable scripting support" ON) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 90bea35be27345..8ad6b897a0254d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -62,6 +62,9 @@ if(OBS_CMAKE_VERSION VERSION_GREATER_EQUAL 3.0.0) check_obs_browser() add_subdirectory(obs-ffmpeg) add_subdirectory(obs-filters) + if(ENABLE_NEW_MPEGTS_OUTPUT) + add_subdirectory(obs-mpegts) + endif() add_subdirectory(obs-outputs) if(OS_WINDOWS) add_subdirectory(obs-qsv11) @@ -185,3 +188,6 @@ add_subdirectory(rtmp-services) add_subdirectory(text-freetype2) add_subdirectory(aja) add_subdirectory(obs-webrtc) +if(ENABLE_NEW_MPEGTS_OUTPUT) + add_subdirectory(obs-mpegts) +endif() diff --git a/plugins/obs-ffmpeg/CMakeLists.txt b/plugins/obs-ffmpeg/CMakeLists.txt index 77c5ad8e4950bb..4d017f6d35efc1 100644 --- a/plugins/obs-ffmpeg/CMakeLists.txt +++ b/plugins/obs-ffmpeg/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.22...3.25) legacy_check() option(ENABLE_FFMPEG_LOGGING "Enables obs-ffmpeg logging" OFF) -option(ENABLE_NEW_MPEGTS_OUTPUT "Use native SRT/RIST mpegts output" ON) include(cmake/dependencies.cmake) @@ -16,10 +15,6 @@ target_sources( obs-ffmpeg PRIVATE # cmake-format: sortable $<$:obs-ffmpeg-logging.c> - $<$:obs-ffmpeg-mpegts.c> - $<$:obs-ffmpeg-rist.h> - $<$:obs-ffmpeg-srt.h> - $<$:obs-ffmpeg-url.h> $<$:obs-ffmpeg-vaapi.c> $<$:vaapi-utils.c> $<$:vaapi-utils.h> @@ -65,9 +60,7 @@ target_link_libraries( $<$:ws2_32> $<$:Libva::va> $<$:Libva::drm> - $<$:Libpci::pci> - $<$:Librist::Librist> - $<$:Libsrt::Libsrt>) + $<$:Libpci::pci>) if(OS_WINDOWS) configure_file(cmake/windows/obs-module.rc.in obs-ffmpeg.rc) diff --git a/plugins/obs-ffmpeg/cmake/dependencies.cmake b/plugins/obs-ffmpeg/cmake/dependencies.cmake index 5a52c751ac24ac..f79f11dfd4369d 100644 --- a/plugins/obs-ffmpeg/cmake/dependencies.cmake +++ b/plugins/obs-ffmpeg/cmake/dependencies.cmake @@ -42,22 +42,3 @@ elseif( find_package(Libva REQUIRED) find_package(Libpci REQUIRED) endif() - -if(ENABLE_NEW_MPEGTS_OUTPUT) - find_package(Librist QUIET) - find_package(Libsrt QUIET) - - foreach(_output_lib IN ITEMS Librist Libsrt) - if(NOT TARGET ${_output_lib}::${_output_lib}) - list(APPEND _error_messages "MPEGTS output library ${_output_lib} not found.") - endif() - endforeach() - - if(_error_messages) - list(JOIN _error_messages "\n" _error_string) - message( - FATAL_ERROR - "${_error_string}\n Disable this error by setting ENABLE_NEW_MPEGTS_OUTPUT to OFF or providing the build system with required SRT and Rist libraries." - ) - endif() -endif() diff --git a/plugins/obs-ffmpeg/cmake/legacy.cmake b/plugins/obs-ffmpeg/cmake/legacy.cmake index cf92a9ea58d7e8..32755cf0e610ca 100644 --- a/plugins/obs-ffmpeg/cmake/legacy.cmake +++ b/plugins/obs-ffmpeg/cmake/legacy.cmake @@ -1,7 +1,6 @@ project(obs-ffmpeg) option(ENABLE_FFMPEG_LOGGING "Enables obs-ffmpeg logging" OFF) -option(ENABLE_NEW_MPEGTS_OUTPUT "Use native SRT/RIST mpegts output" ON) find_package( FFmpeg REQUIRED @@ -17,20 +16,6 @@ add_library(obs-ffmpeg MODULE) add_library(OBS::ffmpeg ALIAS obs-ffmpeg) add_subdirectory(ffmpeg-mux) -if(ENABLE_NEW_MPEGTS_OUTPUT) - find_package(Librist QUIET) - find_package(Libsrt QUIET) - - if(NOT TARGET Librist::Librist AND NOT TARGET Libsrt::Libsrt) - obs_status( - FATAL_ERROR - "SRT and RIST libraries not found! Please install SRT and RIST libraries or set ENABLE_NEW_MPEGTS_OUTPUT=OFF.") - elseif(NOT TARGET Libsrt::Libsrt) - obs_status(FATAL_ERROR "SRT library not found! Please install SRT library or set ENABLE_NEW_MPEGTS_OUTPUT=OFF.") - elseif(NOT TARGET Librist::Librist) - obs_status(FATAL_ERROR "RIST library not found! Please install RIST library or set ENABLE_NEW_MPEGTS_OUTPUT=OFF.") - endif() -endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/obs-ffmpeg-config.h.in ${CMAKE_BINARY_DIR}/config/obs-ffmpeg-config.h) @@ -66,16 +51,6 @@ target_link_libraries( FFmpeg::swscale FFmpeg::swresample) -if(ENABLE_NEW_MPEGTS_OUTPUT) - target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-mpegts.c obs-ffmpeg-srt.h obs-ffmpeg-rist.h obs-ffmpeg-url.h) - - target_link_libraries(obs-ffmpeg PRIVATE Librist::Librist Libsrt::Libsrt) - if(OS_WINDOWS) - target_link_libraries(obs-ffmpeg PRIVATE ws2_32.lib) - endif() - target_compile_definitions(obs-ffmpeg PRIVATE NEW_MPEGTS_OUTPUT) -endif() - if(ENABLE_FFMPEG_LOGGING) target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-logging.c) endif() diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.h b/plugins/obs-ffmpeg/obs-ffmpeg-output.h index 7b898f9a21d9d8..6d1c030a7a011a 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-output.h +++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.h @@ -5,9 +5,6 @@ #include #include #include -#ifdef NEW_MPEGTS_OUTPUT -#include "obs-ffmpeg-url.h" -#endif struct ffmpeg_cfg { const char *url; @@ -107,12 +104,6 @@ struct ffmpeg_output { os_event_t *stop_event; DARRAY(AVPacket *) packets; -#ifdef NEW_MPEGTS_OUTPUT - /* used for SRT & RIST */ - URLContext *h; - AVIOContext *s; - bool got_headers; -#endif }; bool ffmpeg_data_init(struct ffmpeg_data *data, struct ffmpeg_cfg *config); void ffmpeg_data_free(struct ffmpeg_data *data); diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index bca4ca1153988c..c355bf64775ace 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -27,7 +27,9 @@ MODULE_EXPORT const char *obs_module_description(void) extern struct obs_source_info ffmpeg_source; extern struct obs_output_info ffmpeg_output; extern struct obs_output_info ffmpeg_muxer; +#ifndef NEW_MPEGTS_OUTPUT extern struct obs_output_info ffmpeg_mpegts_muxer; +#endif extern struct obs_output_info replay_buffer; extern struct obs_output_info ffmpeg_hls_muxer; extern struct obs_encoder_info aac_encoder_info; @@ -362,7 +364,9 @@ bool obs_module_load(void) obs_register_source(&ffmpeg_source); obs_register_output(&ffmpeg_output); obs_register_output(&ffmpeg_muxer); +#ifndef NEW_MPEGTS_OUTPUT obs_register_output(&ffmpeg_mpegts_muxer); +#endif obs_register_output(&ffmpeg_hls_muxer); obs_register_output(&replay_buffer); obs_register_encoder(&aac_encoder_info); diff --git a/plugins/obs-mpegts/CMakeLists.txt b/plugins/obs-mpegts/CMakeLists.txt new file mode 100644 index 00000000000000..74bf6964a13d99 --- /dev/null +++ b/plugins/obs-mpegts/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.22...3.25) + +legacy_check() + +include(cmake/dependencies.cmake) + +add_library(obs-mpegts MODULE) +add_library(OBS::mpegts ALIAS obs-mpegts) + +find_package(Librist REQUIRED) +find_package(Libsrt REQUIRED) + +target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h) +target_compile_options(obs-ffmpeg PRIVATE $<$:-Wno-shorten-64-to-32>) +target_link_libraries( + obs-mpegts + PRIVATE OBS::libobs + FFmpeg::avcodec + FFmpeg::avformat + FFmpeg::avutil + $<$:OBS::w32-pthreads> + $<$:ws2_32> + Librist::Librist + Libsrt::Libsrt) + +if(OS_WINDOWS) + configure_file(cmake/windows/obs-module.rc.in obs-mpegts.rc) + target_sources(obs-mpegts PRIVATE obs-mpegts.rc) +endif() + +# cmake-format: off +set_target_properties_obs(obs-mpegts PROPERTIES FOLDER plugins/obs-mpegts PREFIX "") +# cmake-format: on diff --git a/plugins/obs-mpegts/cmake/dependencies.cmake b/plugins/obs-mpegts/cmake/dependencies.cmake new file mode 100644 index 00000000000000..8048f5a7bb283b --- /dev/null +++ b/plugins/obs-mpegts/cmake/dependencies.cmake @@ -0,0 +1,30 @@ +# cmake-format: off +if(OS_WINDOWS OR OS_MACOS) + set(ffmpeg_version 6) +else() + set(ffmpeg_version 4.4) +endif() + +find_package( + FFmpeg ${ffmpeg_version} + REQUIRED avcodec + avutil + avformat) +# cmake-format: on + +find_package(Librist QUIET) +find_package(Libsrt QUIET) + +foreach(_output_lib IN ITEMS Librist Libsrt) + if(NOT TARGET ${_output_lib}::${_output_lib}) + list(APPEND _error_messages "MPEGTS output library ${_output_lib} not found.") + endif() +endforeach() + +if(_error_messages) + list(JOIN "\n" _error_string _error_string) + message( + FATAL_ERROR + "${_error_string}\n Disable this error by setting ENABLE_NEW_MPEGTS_OUTPUT to OFF or providing the build system with required SRT and Rist libraries." + ) +endif() diff --git a/plugins/obs-mpegts/cmake/legacy.cmake b/plugins/obs-mpegts/cmake/legacy.cmake new file mode 100644 index 00000000000000..c57f57c49fd29d --- /dev/null +++ b/plugins/obs-mpegts/cmake/legacy.cmake @@ -0,0 +1,42 @@ +project(obs-mpegts) + +find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) + +add_library(obs-mpegts MODULE) +add_library(OBS::mpegts ALIAS obs-mpegts) + +find_package(Librist REQUIRED) +find_package(Libsrt REQUIRED) + +foreach(_output_lib IN ITEMS Librist Libsrt) + if(NOT TARGET ${_output_lib}::${_output_lib}) + list(APPEND _error_messages "MPEGTS output library ${_output_lib} not found.") + endif() +endforeach() + +if(_error_messages) + list(JOIN "\n" _error_string _error_string) + message( + FATAL_ERROR + "${_error_string}\n Disable this error by setting ENABLE_NEW_MPEGTS_OUTPUT to OFF or providing the build system with required SRT and Rist libraries." + ) +endif() + +target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h) + +target_link_libraries(obs-mpegts PRIVATE OBS::libobs FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil Librist::Librist + Libsrt::Libsrt) + +set_target_properties(obs-mpegts PROPERTIES FOLDER "plugins/obs-mpegts" PREFIX "") + +if(OS_WINDOWS) + target_link_libraries(obs-mpegts PRIVATE ws2_32.lib) + if(MSVC) + target_link_libraries(obs-mpegts PRIVATE OBS::w32-pthreads) + endif() + + set(MODULE_DESCRIPTION "OBS MPEG-TS muxer module") + configure_file(${CMAKE_SOURCE_DIR}/cmake/bundle/windows/obs-module.rc.in obs-mpegts.rc) +endif() + +setup_plugin_target(obs-mpegts) diff --git a/plugins/obs-mpegts/cmake/windows/obs-module.rc.in b/plugins/obs-mpegts/cmake/windows/obs-module.rc.in new file mode 100644 index 00000000000000..c0e0656eef471e --- /dev/null +++ b/plugins/obs-mpegts/cmake/windows/obs-module.rc.in @@ -0,0 +1,24 @@ +1 VERSIONINFO +FILEVERSION ${OBS_VERSION_MAJOR},${OBS_VERSION_MINOR},${OBS_VERSION_PATCH},0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "${OBS_COMPANY_NAME}" + VALUE "FileDescription", "OBS MPEG-TS muxer module" + VALUE "FileVersion", "${OBS_VERSION_CANONICAL}" + VALUE "ProductName", "${OBS_PRODUCT_NAME}" + VALUE "ProductVersion", "${OBS_VERSION_CANONICAL}" + VALUE "Comments", "${OBS_COMMENTS}" + VALUE "LegalCopyright", "${OBS_LEGAL_COPYRIGHT}" + VALUE "InternalName", "obs-mpegts" + VALUE "OriginalFilename", "obs-mpegts" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x04B0 + END +END diff --git a/plugins/obs-mpegts/data/locale/en-US.ini b/plugins/obs-mpegts/data/locale/en-US.ini new file mode 100644 index 00000000000000..02a46eb8e6b7bf --- /dev/null +++ b/plugins/obs-mpegts/data/locale/en-US.ini @@ -0,0 +1 @@ +FFmpegMpegts="FFmpeg MPEG-TS Muxer" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-url.h b/plugins/obs-mpegts/obs-mpegts-common.h similarity index 55% rename from plugins/obs-ffmpeg/obs-ffmpeg-url.h rename to plugins/obs-mpegts/obs-mpegts-common.h index 2dd18599cd986f..17c39023009c13 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-url.h +++ b/plugins/obs-mpegts/obs-mpegts-common.h @@ -1,10 +1,24 @@ #pragma once +#include +#include +#include +#include +#include +#include + +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include + #include #include @@ -23,6 +37,109 @@ typedef struct URLContext { int64_t rw_timeout; /* max time to wait for write completion in mcs */ } URLContext; +struct mpegts_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; + const char *video_encoder; + int video_encoder_id; + const char *audio_encoder; + int audio_encoder_id; + int audio_bitrates[MAX_AUDIO_MIXES]; // multi-track + const char *video_settings; + const char *audio_settings; + int audio_mix_count; + int audio_tracks; + enum AVPixelFormat format; + enum AVColorRange color_range; + enum AVColorPrimaries color_primaries; + enum AVColorTransferCharacteristic color_trc; + enum AVColorSpace colorspace; + int max_luminance; + int scale_width; + int scale_height; + int width; + int height; + int frame_size; // audio frame size + const char *username; + const char *password; + const char *stream_id; + const char *encrypt_passphrase; +}; + +struct mpegts_audio_info { + AVStream *stream; + AVCodecContext *ctx; +}; + +struct mpegts_data { + AVStream *video; + AVCodecContext *video_ctx; + struct mpegts_audio_info *audio_infos; + const AVCodec *acodec; + const AVCodec *vcodec; + AVFormatContext *output; + struct SwsContext *swscale; + + int64_t total_frames; + AVFrame *vframe; + int frame_size; + + uint64_t start_timestamp; + + int64_t total_samples[MAX_AUDIO_MIXES]; + uint32_t audio_samplerate; + enum audio_format audio_format; + size_t audio_planes; + size_t audio_size; + int num_audio_streams; + + /* audio_tracks is a bitmask storing the indices of the mixes */ + int audio_tracks; + struct deque excess_frames[MAX_AUDIO_MIXES][MAX_AV_PLANES]; + uint8_t *samples[MAX_AUDIO_MIXES][MAX_AV_PLANES]; + AVFrame *aframe[MAX_AUDIO_MIXES]; + + struct mpegts_cfg config; + + bool initialized; + + char *last_error; +}; + +struct mpegts_output { + obs_output_t *output; + volatile bool active; + struct mpegts_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; +}; + #define UDP_DEFAULT_PAYLOAD_SIZE 1316 /* We need to override libsrt/win/syslog_defs.h due to conflicts w/ some libobs diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-rist.h b/plugins/obs-mpegts/obs-mpegts-rist.h similarity index 99% rename from plugins/obs-ffmpeg/obs-ffmpeg-rist.h rename to plugins/obs-mpegts/obs-mpegts-rist.h index 215f80ae181c86..eb86c43ddcc8bd 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-rist.h +++ b/plugins/obs-mpegts/obs-mpegts-rist.h @@ -16,8 +16,7 @@ */ #pragma once -#include -#include "obs-ffmpeg-url.h" +#include "obs-mpegts-common.h" #include #include diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-srt.h b/plugins/obs-mpegts/obs-mpegts-srt.h similarity index 99% rename from plugins/obs-ffmpeg/obs-ffmpeg-srt.h rename to plugins/obs-mpegts/obs-mpegts-srt.h index 274ec17953545d..4bf4dbcbd3f140 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-srt.h +++ b/plugins/obs-mpegts/obs-mpegts-srt.h @@ -16,10 +16,8 @@ */ #pragma once -#include -#include "obs-ffmpeg-url.h" +#include "obs-mpegts-common.h" #include -#include #ifdef _WIN32 #include #endif diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c b/plugins/obs-mpegts/obs-mpegts.c similarity index 76% rename from plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c rename to plugins/obs-mpegts/obs-mpegts.c index ed85bd975f4678..412805d72ea11b 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c +++ b/plugins/obs-mpegts/obs-mpegts.c @@ -15,32 +15,168 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include - -#include "obs-ffmpeg-output.h" -#include "obs-ffmpeg-formats.h" -#include "obs-ffmpeg-compat.h" -#include "obs-ffmpeg-rist.h" -#include "obs-ffmpeg-srt.h" -#include -#include +#include "obs-mpegts-rist.h" +#include "obs-mpegts-srt.h" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("obs-mpegts", "en-US") +MODULE_EXPORT const char *obs_module_description(void) +{ + return "MPEG-TS muxer to stream with srt or rist protocols"; +} /* ------------------------------------------------------------------------- */ -#define do_log(level, format, ...) \ - blog(level, "[obs-ffmpeg mpegts muxer: '%s']: " format, \ +#define do_log(level, format, ...) \ + blog(level, "[obs-mpegts muxer: '%s']: " format, \ obs_output_get_name(stream->output), ##__VA_ARGS__) #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__) #define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__) -static void ffmpeg_mpegts_set_last_error(struct ffmpeg_data *data, - const char *error) +/* ------------------------------------------------------------------------- */ + +static inline int64_t rescale_ts(int64_t val, AVCodecContext *context, + AVRational new_base) +{ + return av_rescale_q_rnd(val, context->time_base, new_base, + AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); +} + +static inline enum AVPixelFormat +obs_to_mpegts_video_format(enum video_format format) +{ + switch (format) { + case VIDEO_FORMAT_I444: + return AV_PIX_FMT_YUV444P; + case VIDEO_FORMAT_I412: + return AV_PIX_FMT_YUV444P12LE; + case VIDEO_FORMAT_I420: + return AV_PIX_FMT_YUV420P; + case VIDEO_FORMAT_NV12: + return AV_PIX_FMT_NV12; + case VIDEO_FORMAT_YUY2: + return AV_PIX_FMT_YUYV422; + case VIDEO_FORMAT_UYVY: + return AV_PIX_FMT_UYVY422; + case VIDEO_FORMAT_YVYU: + return AV_PIX_FMT_YVYU422; + case VIDEO_FORMAT_RGBA: + return AV_PIX_FMT_RGBA; + case VIDEO_FORMAT_BGRA: + return AV_PIX_FMT_BGRA; + case VIDEO_FORMAT_BGRX: + return AV_PIX_FMT_BGRA; + case VIDEO_FORMAT_Y800: + return AV_PIX_FMT_GRAY8; + case VIDEO_FORMAT_BGR3: + return AV_PIX_FMT_BGR24; + case VIDEO_FORMAT_I422: + return AV_PIX_FMT_YUV422P; + case VIDEO_FORMAT_I210: + return AV_PIX_FMT_YUV422P10LE; + case VIDEO_FORMAT_I40A: + return AV_PIX_FMT_YUVA420P; + case VIDEO_FORMAT_I42A: + return AV_PIX_FMT_YUVA422P; + case VIDEO_FORMAT_YUVA: + return AV_PIX_FMT_YUVA444P; + case VIDEO_FORMAT_YA2L: +#if LIBAVUTIL_BUILD >= AV_VERSION_INT(56, 31, 100) + return AV_PIX_FMT_YUVA444P12LE; +#else + return AV_PIX_FMT_NONE; +#endif + case VIDEO_FORMAT_I010: + return AV_PIX_FMT_YUV420P10LE; + case VIDEO_FORMAT_P010: + return AV_PIX_FMT_P010LE; + case VIDEO_FORMAT_NONE: + case VIDEO_FORMAT_AYUV: + /* not supported by FFmpeg */ + return AV_PIX_FMT_NONE; + } + + return AV_PIX_FMT_NONE; +} + +static enum AVChromaLocation +determine_chroma_location(enum AVPixelFormat pix_fmt, + enum AVColorSpace colorspace) +{ + const AVPixFmtDescriptor *const desc = av_pix_fmt_desc_get(pix_fmt); + if (desc) { + const unsigned log_chroma_w = desc->log2_chroma_w; + const unsigned log_chroma_h = desc->log2_chroma_h; + switch (log_chroma_h) { + case 0: + switch (log_chroma_w) { + case 0: + /* 4:4:4 */ + return AVCHROMA_LOC_CENTER; + case 1: + /* 4:2:2 */ + return AVCHROMA_LOC_LEFT; + } + break; + case 1: + if (log_chroma_w == 1) { + /* 4:2:0 */ + return (colorspace == AVCOL_SPC_BT2020_NCL) + ? AVCHROMA_LOC_TOPLEFT + : AVCHROMA_LOC_LEFT; + } + } + } + + return AVCHROMA_LOC_UNSPECIFIED; +} + +static inline enum audio_format +convert_mpegts_sample_format(enum AVSampleFormat format) +{ + switch (format) { + case AV_SAMPLE_FMT_U8: + return AUDIO_FORMAT_U8BIT; + case AV_SAMPLE_FMT_S16: + return AUDIO_FORMAT_16BIT; + case AV_SAMPLE_FMT_S32: + return AUDIO_FORMAT_32BIT; + case AV_SAMPLE_FMT_FLT: + return AUDIO_FORMAT_FLOAT; + case AV_SAMPLE_FMT_U8P: + return AUDIO_FORMAT_U8BIT_PLANAR; + case AV_SAMPLE_FMT_S16P: + return AUDIO_FORMAT_16BIT_PLANAR; + case AV_SAMPLE_FMT_S32P: + return AUDIO_FORMAT_32BIT_PLANAR; + case AV_SAMPLE_FMT_FLTP: + return AUDIO_FORMAT_FLOAT_PLANAR; + default: + return AUDIO_FORMAT_16BIT; + } + + /* shouldn't get here */ + return AUDIO_FORMAT_16BIT; +} + +/* LIBAVCODEC_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \ + ((LIBAVCODEC_VERSION_MICRO < 100 && \ + LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c)) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && \ + LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e))) + +#if !LIBAVCODEC_VERSION_CHECK(54, 28, 0, 59, 100) +#define avcodec_free_frame av_freep +#endif + +/* ------------------------------------------------------------------------- */ + +static void mpegts_set_last_error(struct mpegts_data *data, const char *error) { if (data->last_error) bfree(data->last_error); @@ -48,8 +184,8 @@ static void ffmpeg_mpegts_set_last_error(struct ffmpeg_data *data, data->last_error = bstrdup(error); } -void ffmpeg_mpegts_log_error(int log_level, struct ffmpeg_data *data, - const char *format, ...) +void mpegts_log_error(int log_level, struct mpegts_data *data, + const char *format, ...) { va_list args; char out[4096]; @@ -58,24 +194,24 @@ void ffmpeg_mpegts_log_error(int log_level, struct ffmpeg_data *data, vsnprintf(out, sizeof(out), format, args); va_end(args); - ffmpeg_mpegts_set_last_error(data, out); + mpegts_set_last_error(data, out); blog(log_level, "%s", out); } -static bool is_rist(struct ffmpeg_output *stream) +static bool is_rist(struct mpegts_output *stream) { return !strncmp(stream->ff_data.config.url, RIST_PROTO, sizeof(RIST_PROTO) - 1); } -static bool is_srt(struct ffmpeg_output *stream) +static bool is_srt(struct mpegts_output *stream) { return !strncmp(stream->ff_data.config.url, SRT_PROTO, sizeof(SRT_PROTO) - 1); } -static bool proto_is_allowed(struct ffmpeg_output *stream) +static bool proto_is_allowed(struct mpegts_output *stream) { return !strncmp(stream->ff_data.config.url, UDP_PROTO, sizeof(UDP_PROTO) - 1) || @@ -85,15 +221,15 @@ static bool proto_is_allowed(struct ffmpeg_output *stream) sizeof(HTTP_PROTO) - 1); } -static bool new_stream(struct ffmpeg_data *data, AVStream **stream, +static bool new_stream(struct mpegts_data *data, AVStream **stream, const char *name) { *stream = avformat_new_stream(data->output, NULL); if (!*stream) { - ffmpeg_mpegts_log_error( - LOG_WARNING, data, - "Couldn't create stream for encoder '%s'", name); + mpegts_log_error(LOG_WARNING, data, + "Couldn't create stream for encoder '%s'", + name); return false; } @@ -101,8 +237,8 @@ static bool new_stream(struct ffmpeg_data *data, AVStream **stream, return true; } -static bool get_audio_headers(struct ffmpeg_output *stream, - struct ffmpeg_data *data, int idx) +static bool get_audio_headers(struct mpegts_output *stream, + struct mpegts_data *data, int idx) { AVCodecParameters *par = data->audio_infos[idx].stream->codecpar; obs_encoder_t *aencoder = @@ -120,8 +256,8 @@ static bool get_audio_headers(struct ffmpeg_output *stream, return 0; } -static bool get_video_headers(struct ffmpeg_output *stream, - struct ffmpeg_data *data) +static bool get_video_headers(struct mpegts_output *stream, + struct mpegts_data *data) { AVCodecParameters *par = data->video->codecpar; obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output); @@ -138,14 +274,14 @@ static bool get_video_headers(struct ffmpeg_output *stream, return 0; } -static bool create_video_stream(struct ffmpeg_output *stream, - struct ffmpeg_data *data) +static bool create_video_stream(struct mpegts_output *stream, + struct mpegts_data *data) { AVCodecContext *context; struct obs_video_info ovi; if (!obs_get_video_info(&ovi)) { - ffmpeg_mpegts_log_error(LOG_WARNING, data, "No active video"); + mpegts_log_error(LOG_WARNING, data, "No active video"); return false; } const char *name = data->config.video_encoder; @@ -241,8 +377,8 @@ static bool create_video_stream(struct ffmpeg_output *stream, return true; } -static bool create_audio_stream(struct ffmpeg_output *stream, - struct ffmpeg_data *data, int idx) +static bool create_audio_stream(struct mpegts_output *stream, + struct mpegts_data *data, int idx) { AVCodecContext *context; AVStream *avstream; @@ -257,7 +393,7 @@ static bool create_audio_stream(struct ffmpeg_output *stream, } if (!obs_get_audio_info(&aoi)) { - ffmpeg_mpegts_log_error(LOG_WARNING, data, "No active audio"); + mpegts_log_error(LOG_WARNING, data, "No active audio"); return false; } @@ -294,7 +430,7 @@ static bool create_audio_stream(struct ffmpeg_output *stream, avstream->time_base = context->time_base; data->audio_samplerate = aoi.samples_per_sec; - data->audio_format = convert_ffmpeg_sample_format(context->sample_fmt); + data->audio_format = convert_mpegts_sample_format(context->sample_fmt); data->audio_planes = get_audio_planes(data->audio_format, aoi.speakers); data->audio_size = get_audio_size(data->audio_format, aoi.speakers, 1); @@ -305,8 +441,8 @@ static bool create_audio_stream(struct ffmpeg_output *stream, return true; } -static inline bool init_streams(struct ffmpeg_output *stream, - struct ffmpeg_data *data) +static inline bool init_streams(struct mpegts_output *stream, + struct mpegts_data *data) { if (!create_video_stream(stream, data)) return false; @@ -334,20 +470,20 @@ int ff_network_init(void) return 1; } -static inline int connect_mpegts_url(struct ffmpeg_output *stream, bool is_rist) +static inline int connect_mpegts_url(struct mpegts_output *stream, bool is_rist) { int err = 0; const char *url = stream->ff_data.config.url; if (!ff_network_init()) { - ffmpeg_mpegts_log_error(LOG_ERROR, &stream->ff_data, - "Couldn't initialize network"); + mpegts_log_error(LOG_ERROR, &stream->ff_data, + "Couldn't initialize network"); return AVERROR(EIO); } URLContext *uc = av_mallocz(sizeof(URLContext) + strlen(url) + 1); if (!uc) { - ffmpeg_mpegts_log_error(LOG_ERROR, &stream->ff_data, - "Couldn't allocate memory"); + mpegts_log_error(LOG_ERROR, &stream->ff_data, + "Couldn't allocate memory"); goto fail; } uc->url = (char *)url; @@ -356,8 +492,8 @@ static inline int connect_mpegts_url(struct ffmpeg_output *stream, bool is_rist) uc->priv_data = is_rist ? av_mallocz(sizeof(RISTContext)) : av_mallocz(sizeof(SRTContext)); if (!uc->priv_data) { - ffmpeg_mpegts_log_error(LOG_ERROR, &stream->ff_data, - "Couldn't allocate memory"); + mpegts_log_error(LOG_ERROR, &stream->ff_data, + "Couldn't allocate memory"); goto fail; } /* For SRT, pass streamid & passphrase; for RIST, pass passphrase, username @@ -419,7 +555,7 @@ static inline int connect_mpegts_url(struct ffmpeg_output *stream, bool is_rist) return err; } -static inline int allocate_custom_aviocontext(struct ffmpeg_output *stream, +static inline int allocate_custom_aviocontext(struct mpegts_output *stream, bool is_rist) { /* allocate buffers */ @@ -455,8 +591,8 @@ static inline int allocate_custom_aviocontext(struct ffmpeg_output *stream, return AVERROR(ENOMEM); } -static inline int open_output_file(struct ffmpeg_output *stream, - struct ffmpeg_data *data) +static inline int open_output_file(struct mpegts_output *stream, + struct mpegts_data *data) { int ret; bool rist = is_rist(stream); @@ -471,7 +607,7 @@ static inline int open_output_file(struct ffmpeg_output *stream, if ((ret = av_dict_parse_string(&dict, data->config.protocol_settings, "=", " ", 0))) { - ffmpeg_mpegts_log_error( + mpegts_log_error( LOG_WARNING, data, "Failed to parse protocol settings: %s, %s", data->config.protocol_settings, @@ -519,10 +655,9 @@ static inline int open_output_file(struct ffmpeg_output *stream, ret == OBS_OUTPUT_INVALID_STREAM)) { error("Failed to open the url or invalid stream"); } else { - ffmpeg_mpegts_log_error(LOG_WARNING, data, - "Couldn't open '%s', %s", - data->config.url, - av_err2str(ret)); + mpegts_log_error(LOG_WARNING, data, + "Couldn't open '%s', %s", + data->config.url, av_err2str(ret)); av_dict_free(&dict); } return ret; @@ -559,12 +694,12 @@ static inline int open_output_file(struct ffmpeg_output *stream, return 0; } -static void close_video(struct ffmpeg_data *data) +static void close_video(struct mpegts_data *data) { avcodec_free_context(&data->video_ctx); } -static void close_audio(struct ffmpeg_data *data) +static void close_audio(struct mpegts_data *data) { for (int idx = 0; idx < data->num_audio_streams; idx++) { for (size_t i = 0; i < MAX_AV_PLANES; i++) @@ -580,7 +715,7 @@ static void close_audio(struct ffmpeg_data *data) } } -static void close_mpegts_url(struct ffmpeg_output *stream, bool is_rist) +static void close_mpegts_url(struct mpegts_output *stream, bool is_rist) { int err = 0; AVIOContext *s = stream->s; @@ -610,8 +745,7 @@ static void close_mpegts_url(struct ffmpeg_output *stream, bool is_rist) stream->ff_data.config.url); } -void ffmpeg_mpegts_data_free(struct ffmpeg_output *stream, - struct ffmpeg_data *data) +void mpegts_data_free(struct mpegts_output *stream, struct mpegts_data *data) { if (data->initialized) av_write_trailer(data->output); @@ -639,14 +773,13 @@ void ffmpeg_mpegts_data_free(struct ffmpeg_output *stream, if (data->last_error) bfree(data->last_error); - memset(data, 0, sizeof(struct ffmpeg_data)); + memset(data, 0, sizeof(struct mpegts_data)); } -bool ffmpeg_mpegts_data_init(struct ffmpeg_output *stream, - struct ffmpeg_data *data, - struct ffmpeg_cfg *config) +bool mpegts_data_init(struct mpegts_output *stream, struct mpegts_data *data, + struct mpegts_cfg *config) { - memset(data, 0, sizeof(struct ffmpeg_data)); + memset(data, 0, sizeof(struct mpegts_data)); data->config = *config; data->num_audio_streams = config->audio_mix_count; @@ -664,8 +797,8 @@ bool ffmpeg_mpegts_data_init(struct ffmpeg_output *stream, output_format = av_guess_format("mpegts", NULL, "video/M2PT"); if (output_format == NULL) { - ffmpeg_mpegts_log_error(LOG_WARNING, data, - "Couldn't set output format to mpegts"); + mpegts_log_error(LOG_WARNING, data, + "Couldn't set output format to mpegts"); goto fail; } else { info("Output format name and long_name: %s, %s", @@ -682,33 +815,33 @@ bool ffmpeg_mpegts_data_init(struct ffmpeg_output *stream, 0); if (!data->output) { - ffmpeg_mpegts_log_error(LOG_WARNING, data, - "Couldn't create avformat context"); + mpegts_log_error(LOG_WARNING, data, + "Couldn't create avformat context"); goto fail; } return true; fail: - warn("ffmpeg_data_init failed"); + warn("mpegts_data_init failed"); return false; } /* ------------------------------------------------------------------------- */ -static inline bool stopping(struct ffmpeg_output *output) +static inline bool stopping(struct mpegts_output *output) { return os_atomic_load_bool(&output->stopping); } -static const char *ffmpeg_mpegts_getname(void *unused) +static const char *mpegts_getname(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("FFmpegMpegts"); } -static void ffmpeg_mpegts_log_callback(void *param, int level, - const char *format, va_list args) +static void mpegts_log_callback(void *param, int level, const char *format, + va_list args) { if (level <= AV_LOG_INFO) blogva(LOG_DEBUG, format, args); @@ -716,9 +849,9 @@ static void ffmpeg_mpegts_log_callback(void *param, int level, UNUSED_PARAMETER(param); } -static void *ffmpeg_mpegts_create(obs_data_t *settings, obs_output_t *output) +static void *mpegts_create(obs_data_t *settings, obs_output_t *output) { - struct ffmpeg_output *data = bzalloc(sizeof(struct ffmpeg_output)); + struct mpegts_output *data = bzalloc(sizeof(struct mpegts_output)); pthread_mutex_init_value(&data->write_mutex); data->output = output; @@ -729,7 +862,7 @@ static void *ffmpeg_mpegts_create(obs_data_t *settings, obs_output_t *output) if (os_sem_init(&data->write_sem, 0) != 0) goto fail; - av_log_set_callback(ffmpeg_mpegts_log_callback); + av_log_set_callback(mpegts_log_callback); UNUSED_PARAMETER(settings); return data; @@ -741,18 +874,18 @@ static void *ffmpeg_mpegts_create(obs_data_t *settings, obs_output_t *output) return NULL; } -static void ffmpeg_mpegts_full_stop(void *data); -static void ffmpeg_mpegts_deactivate(struct ffmpeg_output *output); +static void mpegts_full_stop(void *data); +static void mpegts_deactivate(struct mpegts_output *output); -static void ffmpeg_mpegts_destroy(void *data) +static void mpegts_destroy(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; if (output) { if (output->connecting) pthread_join(output->start_thread, NULL); - ffmpeg_mpegts_full_stop(output); + mpegts_full_stop(output); pthread_mutex_destroy(&output->write_mutex); os_sem_destroy(output->write_sem); @@ -761,10 +894,10 @@ static void ffmpeg_mpegts_destroy(void *data) } } -static uint64_t get_packet_sys_dts(struct ffmpeg_output *output, +static uint64_t get_packet_sys_dts(struct mpegts_output *output, AVPacket *packet) { - struct ffmpeg_data *data = &output->ff_data; + struct mpegts_data *data = &output->ff_data; uint64_t pause_offset = obs_output_get_pause_offset(output->output); uint64_t start_ts; @@ -783,7 +916,7 @@ static uint64_t get_packet_sys_dts(struct ffmpeg_output *output, (AVRational){1, 1000000000}); } -static int mpegts_process_packet(struct ffmpeg_output *output) +static int mpegts_process_packet(struct mpegts_output *output) { AVPacket *packet = NULL; int ret = 0; @@ -817,10 +950,9 @@ static int mpegts_process_packet(struct ffmpeg_output *output) av_freep(&buf); if (ret < 0) { - ffmpeg_mpegts_log_error( - LOG_WARNING, &output->ff_data, - "process_packet: Error writing packet: %s", - av_err2str(ret)); + mpegts_log_error(LOG_WARNING, &output->ff_data, + "process_packet: Error writing packet: %s", + av_err2str(ret)); /* Treat "Invalid data found when processing input" and * "Invalid argument" as non-fatal */ @@ -835,7 +967,7 @@ static int mpegts_process_packet(struct ffmpeg_output *output) static void *write_thread(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; while (os_sem_wait(output->write_sem) == 0) { /* check to see if shutting down */ @@ -853,7 +985,7 @@ static void *write_thread(void *data) code = OBS_OUTPUT_NO_SPACE; obs_output_signal_stop(output->output, code); - ffmpeg_mpegts_deactivate(output); + mpegts_deactivate(output); break; } } @@ -862,9 +994,9 @@ static void *write_thread(void *data) return NULL; } -static bool get_extradata(struct ffmpeg_output *stream) +static bool get_extradata(struct mpegts_output *stream) { - struct ffmpeg_data *ff_data = &stream->ff_data; + struct mpegts_data *ff_data = &stream->ff_data; /* get extradata for av headers from encoders */ if (!get_video_headers(stream, ff_data)) @@ -877,10 +1009,10 @@ static bool get_extradata(struct ffmpeg_output *stream) return true; } -/* set ffmpeg_config & init write_thread & capture */ -static bool set_config(struct ffmpeg_output *stream) +/* set mpegts_config & init write_thread & capture */ +static bool set_config(struct mpegts_output *stream) { - struct ffmpeg_cfg config; + struct mpegts_cfg config; bool success; int ret; int code; @@ -922,7 +1054,7 @@ static bool set_config(struct ffmpeg_output *stream) // 2.c) set video format from OBS to FFmpeg video_t *video = obs_encoder_video(vencoder); config.format = - obs_to_ffmpeg_video_format(video_output_get_format(video)); + obs_to_mpegts_video_format(video_output_get_format(video)); if (config.format == AV_PIX_FMT_NONE) { blog(LOG_WARNING, @@ -1018,17 +1150,17 @@ static bool set_config(struct ffmpeg_output *stream) config.video_settings = ""; config.audio_settings = ""; - success = ffmpeg_mpegts_data_init(stream, &stream->ff_data, &config); + success = mpegts_data_init(stream, &stream->ff_data, &config); if (!success) { if (stream->ff_data.last_error) { obs_output_set_last_error(stream->output, stream->ff_data.last_error); } - ffmpeg_mpegts_data_free(stream, &stream->ff_data); + mpegts_data_free(stream, &stream->ff_data); code = OBS_OUTPUT_INVALID_STREAM; goto fail; } - struct ffmpeg_data *ff_data = &stream->ff_data; + struct mpegts_data *ff_data = &stream->ff_data; if (!stream->got_headers) { if (!init_streams(stream, ff_data)) { error("mpegts avstream failed to be created"); @@ -1049,10 +1181,9 @@ static bool set_config(struct ffmpeg_output *stream) ret = pthread_create(&stream->write_thread, NULL, write_thread, stream); if (ret != 0) { - ffmpeg_mpegts_log_error( - LOG_WARNING, &stream->ff_data, - "ffmpeg_output_start: Failed to create write " - "thread."); + mpegts_log_error(LOG_WARNING, &stream->ff_data, + "mpegts_output_start: Failed to create write " + "thread."); code = OBS_OUTPUT_ERROR; goto fail; } @@ -1064,21 +1195,21 @@ static bool set_config(struct ffmpeg_output *stream) return true; fail: obs_output_signal_stop(stream->output, code); - ffmpeg_mpegts_full_stop(stream); + mpegts_full_stop(stream); return false; } static void *start_thread(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; set_config(output); output->connecting = false; return NULL; } -static bool ffmpeg_mpegts_start(void *data) +static bool mpegts_start(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; int ret; if (output->connecting) @@ -1094,19 +1225,19 @@ static bool ffmpeg_mpegts_start(void *data) return (output->connecting = (ret == 0)); } -static void ffmpeg_mpegts_full_stop(void *data) +static void mpegts_full_stop(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; if (output->active) { obs_output_end_data_capture(output->output); - ffmpeg_mpegts_deactivate(output); + mpegts_deactivate(output); } } -static void ffmpeg_mpegts_stop(void *data, uint64_t ts) +static void mpegts_stop(void *data, uint64_t ts) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; if (output->active) { if (ts > 0) { @@ -1114,13 +1245,13 @@ static void ffmpeg_mpegts_stop(void *data, uint64_t ts) os_atomic_set_bool(&output->stopping, true); } - ffmpeg_mpegts_full_stop(output); + mpegts_full_stop(output); } else { obs_output_signal_stop(output->output, OBS_OUTPUT_SUCCESS); } } -static void ffmpeg_mpegts_deactivate(struct ffmpeg_output *output) +static void mpegts_deactivate(struct mpegts_output *output) { if (output->write_thread_active) { os_event_signal(output->stop_event); @@ -1137,12 +1268,12 @@ static void ffmpeg_mpegts_deactivate(struct ffmpeg_output *output) pthread_mutex_unlock(&output->write_mutex); - ffmpeg_mpegts_data_free(output, &output->ff_data); + mpegts_data_free(output, &output->ff_data); } -static uint64_t ffmpeg_mpegts_total_bytes(void *data) +static uint64_t mpegts_total_bytes(void *data) { - struct ffmpeg_output *output = data; + struct mpegts_output *output = data; return output->total_bytes; } @@ -1157,7 +1288,7 @@ static inline int64_t rescale_ts2(AVStream *stream, AVRational codec_time_base, /* Convert obs encoder_packet to FFmpeg AVPacket and write to circular buffer * where it will be processed in the write_thread by process_packet. */ -void mpegts_write_packet(struct ffmpeg_output *stream, +void mpegts_write_packet(struct mpegts_output *stream, struct encoder_packet *encpacket) { if (stopping(stream) || !stream->ff_data.video || @@ -1204,17 +1335,16 @@ void mpegts_write_packet(struct ffmpeg_output *stream, av_packet_free(&packet); } -static bool write_header(struct ffmpeg_output *stream, struct ffmpeg_data *data) +static bool write_header(struct mpegts_output *stream, struct mpegts_data *data) { AVDictionary *dict = NULL; int ret; /* get mpegts muxer settings (can be used with rist, srt, rtp, etc ... */ if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "=", " ", 0))) { - ffmpeg_mpegts_log_error( - LOG_WARNING, data, - "Failed to parse muxer settings: %s, %s", - data->config.muxer_settings, av_err2str(ret)); + mpegts_log_error(LOG_WARNING, data, + "Failed to parse muxer settings: %s, %s", + data->config.muxer_settings, av_err2str(ret)); av_dict_free(&dict); return false; @@ -1235,10 +1365,9 @@ static bool write_header(struct ffmpeg_output *stream, struct ffmpeg_data *data) /* Allocate the stream private data and write the stream header. */ ret = avformat_write_header(data->output, &dict); if (ret < 0) { - ffmpeg_mpegts_log_error( - LOG_WARNING, data, - "Error setting stream header for '%s': %s", - data->config.url, av_err2str(ret)); + mpegts_log_error(LOG_WARNING, data, + "Error setting stream header for '%s': %s", + data->config.url, av_err2str(ret)); return false; } @@ -1260,10 +1389,10 @@ static bool write_header(struct ffmpeg_output *stream, struct ffmpeg_data *data) return true; } -static void ffmpeg_mpegts_data(void *data, struct encoder_packet *packet) +static void mpegts_receive_data(void *data, struct encoder_packet *packet) { - struct ffmpeg_output *stream = data; - struct ffmpeg_data *ff_data = &stream->ff_data; + struct mpegts_output *stream = data; + struct mpegts_data *ff_data = &stream->ff_data; int code; if (!stream->got_headers) { if (get_extradata(stream)) { @@ -1288,13 +1417,13 @@ static void ffmpeg_mpegts_data(void *data, struct encoder_packet *packet) /* encoder failure */ if (!packet) { obs_output_signal_stop(stream->output, OBS_OUTPUT_ENCODE_ERROR); - ffmpeg_mpegts_deactivate(stream); + mpegts_deactivate(stream); return; } if (stopping(stream)) { if (packet->sys_dts_usec >= (int64_t)stream->stop_ts) { - ffmpeg_mpegts_deactivate(stream); + mpegts_deactivate(stream); return; } } @@ -1303,10 +1432,10 @@ static void ffmpeg_mpegts_data(void *data, struct encoder_packet *packet) return; fail: obs_output_signal_stop(stream->output, code); - ffmpeg_mpegts_full_stop(stream); + mpegts_full_stop(stream); } -static obs_properties_t *ffmpeg_mpegts_properties(void *unused) +static obs_properties_t *mpegts_properties(void *unused) { UNUSED_PARAMETER(unused); @@ -1317,7 +1446,7 @@ static obs_properties_t *ffmpeg_mpegts_properties(void *unused) return props; } -struct obs_output_info ffmpeg_mpegts_muxer = { +struct obs_output_info mpegts_muxer = { .id = "ffmpeg_mpegts_muxer", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK | OBS_OUTPUT_SERVICE, @@ -1328,12 +1457,18 @@ struct obs_output_info ffmpeg_mpegts_muxer = { .encoded_video_codecs = "h264", #endif .encoded_audio_codecs = "aac;opus", - .get_name = ffmpeg_mpegts_getname, - .create = ffmpeg_mpegts_create, - .destroy = ffmpeg_mpegts_destroy, - .start = ffmpeg_mpegts_start, - .stop = ffmpeg_mpegts_stop, - .encoded_packet = ffmpeg_mpegts_data, - .get_total_bytes = ffmpeg_mpegts_total_bytes, - .get_properties = ffmpeg_mpegts_properties, + .get_name = mpegts_getname, + .create = mpegts_create, + .destroy = mpegts_destroy, + .start = mpegts_start, + .stop = mpegts_stop, + .encoded_packet = mpegts_receive_data, + .get_total_bytes = mpegts_total_bytes, + .get_properties = mpegts_properties, }; + +bool obs_module_load(void) +{ + obs_register_output(&mpegts_muxer); + return true; +} From a0fe3b343cac53115543c749ee842a68cf812614 Mon Sep 17 00:00:00 2001 From: pkv Date: Mon, 5 Jun 2023 09:00:30 +0200 Subject: [PATCH 2/5] obs-mpegts: Add stats proc handler for SRT This adds a proc handler for statistics for SRT protocol. Signed-off-by: pkv --- plugins/obs-mpegts/obs-mpegts-srt.h | 75 ++++++++++++++++++++++++++--- plugins/obs-mpegts/obs-mpegts.c | 5 ++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/plugins/obs-mpegts/obs-mpegts-srt.h b/plugins/obs-mpegts/obs-mpegts-srt.h index 4bf4dbcbd3f140..b78adf05212df7 100644 --- a/plugins/obs-mpegts/obs-mpegts-srt.h +++ b/plugins/obs-mpegts/obs-mpegts-srt.h @@ -35,6 +35,16 @@ enum SRTMode { SRT_MODE_RENDEZVOUS = 2 }; +struct srt_stats { + uint64_t total_packets_sent; + int packets_dropped; + int packets_retransmitted; + double rtt; + double peer_latency; + double latency; + double bandwidth; +}; + typedef struct SRTContext { SRTSOCKET fd; int eid; @@ -79,8 +89,41 @@ typedef struct SRTContext { int linger; int tsbpd; double time; // time in s in order to post logs at definite intervals + struct srt_stats *stats; } SRTContext; +static void get_srt_stats(void *data, calldata_t *cd) +{ + struct mpegts_output *output = (struct mpegts_output *)data; + SRTContext *s = (SRTContext *)output->h->priv_data; + if (s) { + if (s->stats) { + calldata_set_int(cd, "srt_total_pkts", + s->stats->total_packets_sent); + calldata_set_int(cd, "srt_dropped_pkts", + s->stats->packets_dropped); + calldata_set_int(cd, "srt_retransmitted_pkts", + s->stats->packets_retransmitted); + calldata_set_float(cd, "srt_rtt", (float)s->stats->rtt); + calldata_set_float(cd, "srt_peer_latency", + (float)s->stats->peer_latency); + calldata_set_float(cd, "srt_latency", + (float)s->stats->latency); + calldata_set_float(cd, "srt_bandwidth", + (float)s->stats->bandwidth); + } else { + calldata_set_int(cd, "srt_total_pkts", 0); + calldata_set_int(cd, "srt_dropped_pkts", 0); + calldata_set_int(cd, "srt_retransmitted_pkts", 0); + calldata_set_float(cd, "srt_rtt", 0); + calldata_set_float(cd, "srt_peer_latency", + (float)s->stats->peer_latency); + calldata_set_float(cd, "srt_latency", 0); + calldata_set_float(cd, "srt_bandwidth", 0); + } + } +} + static int libsrt_neterrno(URLContext *h) { SRTContext *s = (SRTContext *)h->priv_data; @@ -662,6 +705,7 @@ static int libsrt_open(URLContext *h, const char *uri) const char *p; char buf[1024]; int ret = 0; + s->stats = bzalloc(sizeof(struct srt_stats)); if (srt_startup() < 0) { blog(LOG_ERROR, @@ -861,13 +905,22 @@ static int libsrt_write(URLContext *h, const uint8_t *buf, int size) double time = (double)timesp.tv_sec + 0.000000001 * (double)timesp.tv_nsec; #endif - if (time > (s->time + 60.0)) { - srt_bistats(s->fd, &perf, 0, 1); + srt_bistats(s->fd, &perf, 0, 1); + s->stats->rtt = perf.msRTT; + s->stats->bandwidth = perf.mbpsBandwidth; + s->stats->peer_latency = perf.msSndTsbPdDelay; + s->stats->latency = perf.msRcvTsbPdDelay; + if (time > (s->time + 10.0)) { blog(LOG_DEBUG, - "[obs-ffmpeg mpegts muxer / libsrt]: RTT [%.2f ms], Link Bandwidth [%.1f Mbps]", - perf.msRTT, perf.mbpsBandwidth); + "[obs-ffmpeg mpegts muxer / libsrt]: RTT [%.2f ms], Link Bandwidth [%.1f Mbps], peer latency [%ld ms], SRTO_RCVLATENCY [%ld ms]", + perf.msRTT, perf.mbpsBandwidth, + perf.msSndTsbPdDelay, perf.msRcvTsbPdDelay); s->time = time; } + srt_bstats(s->fd, &perf, 1); + s->stats->total_packets_sent = perf.pktSentTotal; + s->stats->packets_retransmitted = perf.pktRetransTotal; + s->stats->packets_dropped = perf.pktSndDropTotal; } return ret; @@ -889,16 +942,22 @@ static int libsrt_close(URLContext *h) "\ttime elapsed [%.1f sec]\n" "\tmean speed [%.1f Mbp]\n" "\ttotal bytes sent [%.1f MB]\n" - "\tbytes retransmitted [%.1f %%]\n" - "\tbytes dropped [%.1f %%]\n", + "\tbytes retransmitted [%.2f %%, %.1f MB]\n" + "\tbytes dropped [%.2f %%, %.1f MB]\n" + "\tpackets transmitted [%ld]\n" + "\tpackets retransmitted [%ld]\n" + "\tpackets dropped [%ld]", (double)perf.msTimeStamp / 1000.0, perf.mbpsSendRate, (double)perf.byteSentTotal / 1000000.0, perf.byteSentTotal ? perf.byteRetransTotal / perf.byteSentTotal * 100.0 : 0, + (double)perf.byteRetransTotal / 1000000.0, perf.byteSentTotal ? perf.byteSndDropTotal / perf.byteSentTotal * 100.0 - : 0); + : 0, + (double)perf.byteSndDropTotal / 1000000.0, perf.pktSentTotal, + perf.pktRetransTotal, perf.pktSndDropTotal); srt_epoll_release(s->eid); int err = srt_close(s->fd); @@ -907,7 +966,7 @@ static int libsrt_close(URLContext *h) srt_getlasterror_str()); return -1; } - + bfree(s->stats); srt_cleanup(); blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt]: SRT connection closed"); diff --git a/plugins/obs-mpegts/obs-mpegts.c b/plugins/obs-mpegts/obs-mpegts.c index 412805d72ea11b..48035171643768 100644 --- a/plugins/obs-mpegts/obs-mpegts.c +++ b/plugins/obs-mpegts/obs-mpegts.c @@ -863,6 +863,11 @@ static void *mpegts_create(obs_data_t *settings, obs_output_t *output) goto fail; av_log_set_callback(mpegts_log_callback); + proc_handler_t *ph = obs_output_get_proc_handler(output); + proc_handler_add( + ph, + "void get_srt_stats(out int srt_total_pkts, int srt_dropped_pkts, int srt_retransmitted_pkts, float srt_rtt, float srt_bandwidth)", + get_srt_stats, data); UNUSED_PARAMETER(settings); return data; From ae4022c3089bf09f4bc4ac60617b36f92fbfd675 Mon Sep 17 00:00:00 2001 From: pkv Date: Mon, 5 Jun 2023 08:19:11 +0200 Subject: [PATCH 3/5] cmake: Copy QCharts dll and its dependencies This copies QtCharts.dll as well as QtOpenGL.dll & QtOpenGLWidgets.dll on which it depends. Signed-off-by: pkv --- cmake/Modules/CopyMSVCBins.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/Modules/CopyMSVCBins.cmake b/cmake/Modules/CopyMSVCBins.cmake index 390a49edf51643..542e0915c71950 100644 --- a/cmake/Modules/CopyMSVCBins.cmake +++ b/cmake/Modules/CopyMSVCBins.cmake @@ -182,6 +182,9 @@ file( "${QtCore_BIN_DIR}/Qt6Svgd.dll" "${QtCore_BIN_DIR}/Qt6Xmld.dll" "${QtCore_BIN_DIR}/Qt6Networkd.dll" + "${QtCore_BIN_DIR}/Qt6Chartsd.dll" + "${QtCore_BIN_DIR}/Qt6OpenGLd.dll" + "${QtCore_BIN_DIR}/Qt6OpenGLWidgetsd.dll" "${QtCore_BIN_DIR}/libGLESv2d.dll" "${QtCore_BIN_DIR}/libEGLd.dll") file(GLOB QT_DEBUG_PLAT_BIN_FILES "${QtCore_PLUGIN_DIR}/platforms/qwindowsd.dll") @@ -199,6 +202,9 @@ file( "${QtCore_BIN_DIR}/Qt6Svg.dll" "${QtCore_BIN_DIR}/Qt6Xml.dll" "${QtCore_BIN_DIR}/Qt6Network.dll" + "${QtCore_BIN_DIR}/Qt6Charts.dll" + "${QtCore_BIN_DIR}/Qt6OpenGL.dll" + "${QtCore_BIN_DIR}/Qt6OpenGLWidgets.dll" "${QtCore_BIN_DIR}/libGLESv2.dll" "${QtCore_BIN_DIR}/libEGL.dll") file(GLOB QT_PLAT_BIN_FILES "${QtCore_PLUGIN_DIR}/platforms/qwindows.dll") From d3e53c4cc69bcac2e6e2f8c37de36e5636a3b89e Mon Sep 17 00:00:00 2001 From: pkv Date: Mon, 26 Jun 2023 17:10:08 +0200 Subject: [PATCH 4/5] obs-mpegts: Add SRT stats dock This adds a new SRT Stats dock with several important statistics for the SRT protocol (RTT, retransmitted packets, dropped packets) in a chart which is updated live. Signed-off-by: pkv --- plugins/obs-mpegts/CMakeLists.txt | 28 +- plugins/obs-mpegts/cmake/dependencies.cmake | 1 + plugins/obs-mpegts/cmake/legacy.cmake | 25 +- plugins/obs-mpegts/data/locale/en-US.ini | 34 ++ plugins/obs-mpegts/obs-mpegts-rist.h | 1 - plugins/obs-mpegts/obs-mpegts-srt-stats.cpp | 613 ++++++++++++++++++++ plugins/obs-mpegts/obs-mpegts-srt-stats.hpp | 137 +++++ plugins/obs-mpegts/obs-mpegts-srt.h | 33 +- plugins/obs-mpegts/obs-mpegts.c | 17 +- 9 files changed, 850 insertions(+), 39 deletions(-) create mode 100644 plugins/obs-mpegts/obs-mpegts-srt-stats.cpp create mode 100644 plugins/obs-mpegts/obs-mpegts-srt-stats.hpp diff --git a/plugins/obs-mpegts/CMakeLists.txt b/plugins/obs-mpegts/CMakeLists.txt index 74bf6964a13d99..4b25f9e74b3727 100644 --- a/plugins/obs-mpegts/CMakeLists.txt +++ b/plugins/obs-mpegts/CMakeLists.txt @@ -10,18 +10,24 @@ add_library(OBS::mpegts ALIAS obs-mpegts) find_package(Librist REQUIRED) find_package(Libsrt REQUIRED) -target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h) +target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h + obs-mpegts-srt-stats.cpp obs-mpegts-srt-stats.hpp) + target_compile_options(obs-ffmpeg PRIVATE $<$:-Wno-shorten-64-to-32>) target_link_libraries( - obs-mpegts - PRIVATE OBS::libobs - FFmpeg::avcodec - FFmpeg::avformat - FFmpeg::avutil - $<$:OBS::w32-pthreads> - $<$:ws2_32> - Librist::Librist - Libsrt::Libsrt) + obs-mpegts + PRIVATE OBS::libobs + OBS::frontend-api + FFmpeg::avcodec + FFmpeg::avformat + FFmpeg::avutil + $<$:OBS::w32-pthreads> + $<$:ws2_32> + Librist::Librist + Libsrt::Libsrt + Qt::Core + Qt::Widgets + Qt::Charts) if(OS_WINDOWS) configure_file(cmake/windows/obs-module.rc.in obs-mpegts.rc) @@ -29,5 +35,5 @@ if(OS_WINDOWS) endif() # cmake-format: off -set_target_properties_obs(obs-mpegts PROPERTIES FOLDER plugins/obs-mpegts PREFIX "") +set_target_properties_obs(obs-mpegts PROPERTIES FOLDER plugins/obs-mpegts PREFIX "" AUTOMOC ON AUTOUIC ON AUTORCC ON) # cmake-format: on diff --git a/plugins/obs-mpegts/cmake/dependencies.cmake b/plugins/obs-mpegts/cmake/dependencies.cmake index 8048f5a7bb283b..342283905aecf0 100644 --- a/plugins/obs-mpegts/cmake/dependencies.cmake +++ b/plugins/obs-mpegts/cmake/dependencies.cmake @@ -12,6 +12,7 @@ find_package( avformat) # cmake-format: on +find_qt(COMPONENTS Core Widgets Charts) find_package(Librist QUIET) find_package(Libsrt QUIET) diff --git a/plugins/obs-mpegts/cmake/legacy.cmake b/plugins/obs-mpegts/cmake/legacy.cmake index c57f57c49fd29d..bf583e0bb2efba 100644 --- a/plugins/obs-mpegts/cmake/legacy.cmake +++ b/plugins/obs-mpegts/cmake/legacy.cmake @@ -7,6 +7,7 @@ add_library(OBS::mpegts ALIAS obs-mpegts) find_package(Librist REQUIRED) find_package(Libsrt REQUIRED) +find_qt(COMPONENTS Core Widgets Charts) foreach(_output_lib IN ITEMS Librist Libsrt) if(NOT TARGET ${_output_lib}::${_output_lib}) @@ -22,12 +23,28 @@ if(_error_messages) ) endif() -target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h) - -target_link_libraries(obs-mpegts PRIVATE OBS::libobs FFmpeg::avcodec FFmpeg::avformat FFmpeg::avutil Librist::Librist - Libsrt::Libsrt) +target_sources(obs-mpegts PRIVATE obs-mpegts.c obs-mpegts-common.h obs-mpegts-rist.h obs-mpegts-srt.h + obs-mpegts-srt-stats.cpp obs-mpegts-srt-stats.hpp) + +target_link_libraries( + obs-mpegts + PRIVATE OBS::libobs + OBS::frontend-api + FFmpeg::avcodec + FFmpeg::avformat + FFmpeg::avutil + Librist::Librist + Libsrt::Libsrt + Qt::Core + Qt::Widgets + Qt::Charts) set_target_properties(obs-mpegts PROPERTIES FOLDER "plugins/obs-mpegts" PREFIX "") +set_target_properties( + obs-mpegts + PROPERTIES AUTOMOC ON + AUTOUIC ON + AUTORCC ON) if(OS_WINDOWS) target_link_libraries(obs-mpegts PRIVATE ws2_32.lib) diff --git a/plugins/obs-mpegts/data/locale/en-US.ini b/plugins/obs-mpegts/data/locale/en-US.ini index 02a46eb8e6b7bf..0dd5b4ed9f6001 100644 --- a/plugins/obs-mpegts/data/locale/en-US.ini +++ b/plugins/obs-mpegts/data/locale/en-US.ini @@ -1 +1,35 @@ FFmpegMpegts="FFmpeg MPEG-TS Muxer" + +SRT.Stats="SRT Stats" +SRT.Stats.Reset="Reset" +SRT.Stats.Status="Status" +SRT.Stats.Status.Live="LIVE" +SRT.Stats.Status.Reconnecting="Reconnecting" +SRT.Stats.Status.Inactive="Inactive" +SRT.Stats.Status.Active="Active" +SRT.Stats.DroppedFrames="Dropped Frames" +SRT.Stats.MegabytesSent="Total Data Output" +SRT.Stats.Bitrate="Bitrate" +SRT.Stats.RTT="RTT" +SRT.Stats.RTT.ms="RTT (ms)" +SRT.Stats.Bandwidth="Link Bandwidth" +SRT.Stats.Packets="Packets per second" +SRT.Stats.Total.Pkts="Total Sent" +SRT.Stats.Retransmitted.Pkts="Total Retransmitted" +SRT.Stats.Retransmitted.Pkts.Rate="Retransmitted (pkt/sec)." +SRT.Stats.Dropped.Pkts="Total Dropped" +SRT.Stats.Dropped.Pkts.Rate="Dropped (pkt/sec)." +SRT.Stats.Peer.Latency="Peer Latency" +SRT.Stats.Latency="Latency" +SRT.Stats.Time="Time elapsed (minutes)" +SRT.Stats.Time.HMS="Time (hh:mm:ss)" +SRT.Stats.Time.Selector="Chart time span" +SRT.Stats.Time.Selector.MN="1 min" +SRT.Stats.Time.Selector.3MN="3 min" +SRT.Stats.Time.Selector.5MN="5 min" +SRT.Stats.Time.Selector.10MN="10 min" +SRT.Stats.Time.Selector.HR="1 hr" +SRT.Stats.Time.Selector.2HR="2 hr" +SRT.Stats.Time.Selector.4HR="4 hr" +SRT.Stats.Warning="Warning" +SRT.Stats.Tooltip="Right-click to zoom out.\nSelect a vertical span to zoom in." diff --git a/plugins/obs-mpegts/obs-mpegts-rist.h b/plugins/obs-mpegts/obs-mpegts-rist.h index eb86c43ddcc8bd..d6b4b186aaa28c 100644 --- a/plugins/obs-mpegts/obs-mpegts-rist.h +++ b/plugins/obs-mpegts/obs-mpegts-rist.h @@ -14,7 +14,6 @@ * 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-mpegts-common.h" #include diff --git a/plugins/obs-mpegts/obs-mpegts-srt-stats.cpp b/plugins/obs-mpegts/obs-mpegts-srt-stats.cpp new file mode 100644 index 00000000000000..ff3340d9bce861 --- /dev/null +++ b/plugins/obs-mpegts/obs-mpegts-srt-stats.cpp @@ -0,0 +1,613 @@ +/****************************************************************************** + Copyright (C) 2023 by pkv + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ +#include "obs-mpegts-srt-stats.hpp" + +#define TIMER_INTERVAL 1000 +#define QT_UTF8(str) QString::fromUtf8(str, -1) +#define QT_TO_UTF8(str) str.toUtf8().constData() + +SRTStats *_statsFrame = nullptr; + +bool WindowPositionValid(QRect rect) +{ + for (QScreen *screen : QGuiApplication::screens()) { + if (screen->availableGeometry().intersects(rect)) + return true; + } + return false; +} + +void setThemeID(QWidget *widget, const QString &themeID) +{ + if (widget->property("themeID").toString() != themeID) { + widget->setProperty("themeID", themeID); + + /* force style sheet recalculation */ + QString qss = widget->styleSheet(); + widget->setStyleSheet("/* */"); + widget->setStyleSheet(qss); + } +} + +extern "C" void load_srt_stats() +{ + // initialize stats + obs_frontend_push_ui_translation(obs_module_get_string); + _statsFrame = new SRTStats(); + obs_frontend_pop_ui_translation(); + + // add the stats dock to the docks menu + if (!obs_frontend_add_dock_by_id(obs_module_text("SRT.Stats"), + obs_module_text("SRT.Stats"), + _statsFrame)) + blog(LOG_ERROR, "SRT Stats dock could not be created"); +} + +void SRTStats::OBSFrontendEvent(enum obs_frontend_event event, void *ptr) +{ + SRTStats *stats = reinterpret_cast(ptr); + + switch (event) { + case OBS_FRONTEND_EVENT_STREAMING_STARTED: + stats->chart->connect(); + break; + case OBS_FRONTEND_EVENT_STREAMING_STOPPED: + stats->chart->disconnect(); + break; + case OBS_FRONTEND_EVENT_FINISHED_LOADING: + stats->Update(); + default: + break; + } +} + +SRTStats::SRTStats() : timer(this) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(); + outputLayout = new QGridLayout(); + + /* --------------------------------------------- */ + QPushButton *resetButton = + new QPushButton(obs_module_text("SRT.Stats.Reset")); + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget(resetButton); + + /* --------------------------------------------- */ + int col = 0; + auto addOutputCol = [&](const char *loc) { + QLabel *label = new QLabel(QT_UTF8(loc), this); + label->setStyleSheet("font-weight: bold"); + outputLayout->addWidget(label, 0, col++); + }; + + addOutputCol(obs_module_text("SRT.Stats.Status")); + addOutputCol(obs_module_text("SRT.Stats.DroppedFrames")); + addOutputCol(obs_module_text("SRT.Stats.MegabytesSent")); + addOutputCol(obs_module_text("SRT.Stats.Bitrate")); + addOutputCol(obs_module_text("SRT.Stats.RTT")); + addOutputCol(obs_module_text("SRT.Stats.Bandwidth")); + + col = 0; + auto extraAddOutputCol = [&](const char *loc) { + QLabel *label = new QLabel(QT_UTF8(loc), this); + label->setStyleSheet("font-weight: bold"); + outputLayout->addWidget(label, 2, col++); + }; + extraAddOutputCol(obs_module_text("SRT.Stats.Total.Pkts")); + extraAddOutputCol(obs_module_text("SRT.Stats.Retransmitted.Pkts")); + extraAddOutputCol(obs_module_text("SRT.Stats.Dropped.Pkts")); + extraAddOutputCol(obs_module_text("SRT.Stats.Peer.Latency")); + extraAddOutputCol(obs_module_text("SRT.Stats.Latency")); + + /* --------------------------------------------- */ + AddOutputLabels(); + /* --------------------------------------------- */ + QLabel *timeLabel = new QLabel( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector")), this); + timeLabel->setStyleSheet("font-weight: bold"); + outputLayout->addWidget(timeLabel, 4, 0); + timeBox = new QComboBox(); + outputLayout->addWidget(timeBox, 4, 1); + timeBox->addItem(QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.MN")), + (int)SRT_CHART_TIME_MN); + timeBox->addItem( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.3MN")), + (int)SRT_CHART_TIME_3MN); + timeBox->addItem( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.5MN")), + (int)SRT_CHART_TIME_5MN); + timeBox->addItem( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.10MN")), + (int)SRT_CHART_TIME_10MN); + timeBox->addItem(QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.HR")), + (int)SRT_CHART_TIME_HR); + timeBox->addItem( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.2HR")), + (int)SRT_CHART_TIME_2HR); + timeBox->addItem( + QT_UTF8(obs_module_text("SRT.Stats.Time.Selector.4HR")), + (int)SRT_CHART_TIME_4HR); + QObject::connect(timeBox, SIGNAL(currentIndexChanged(int)), this, + SLOT(timeBoxItemChanged(int))); + + chart = new Chart; + /* --------------------------------------------- */ + + QVBoxLayout *outputContainerLayout = new QVBoxLayout(); + outputContainerLayout->addLayout(outputLayout); + outputContainerLayout->addStretch(); + + QWidget *widget = new QWidget(this); + widget->setLayout(outputContainerLayout); + widget->setMaximumHeight(120); + + /* --------------------------------------------- */ + // add chart and chartview + QChartView *chartView = new QChartView(chart); + chartView->setRenderHint(QPainter::Antialiasing); + chartView->setMinimumSize(600, 300); + chartView->setRubberBand(QChartView::VerticalRubberBand); + chartView->setToolTip( + QString::fromUtf8(obs_module_text("SRT.Stats.Tooltip"))); + chartView->setToolTipDuration(2000); + + /* --------------------------------------------- */ + mainLayout->addWidget(widget); + mainLayout->addWidget(chartView); + mainLayout->addLayout(buttonLayout); + setLayout(mainLayout); + + /* --------------------------------------------- */ + connect(resetButton, &QPushButton::clicked, [this]() { Reset(); }); + + setWindowTitle(obs_module_text("SRT.Stats")); +#ifdef __APPLE__ + setWindowIcon( + QIcon::fromTheme("obs", QIcon(":/res/images/obs_256x256.png"))); +#else + setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png"))); +#endif + + setWindowModality(Qt::NonModal); + setAttribute(Qt::WA_DeleteOnClose, true); + + QObject::connect(&timer, &QTimer::timeout, this, &SRTStats::Update); + timer.setInterval(TIMER_INTERVAL); + + if (isVisible()) + timer.start(); + + config_t *conf = obs_frontend_get_global_config(); + const char *geometry = config_get_string(conf, "SRTStats", "geometry"); + if (geometry != NULL) { + QByteArray byteArray = + QByteArray::fromBase64(QByteArray(geometry)); + restoreGeometry(byteArray); + + QRect windowGeometry = normalGeometry(); + if (!WindowPositionValid(windowGeometry)) { + QRect rect = + QGuiApplication::primaryScreen()->geometry(); + setGeometry(QStyle::alignedRect(Qt::LeftToRight, + Qt::AlignCenter, size(), + rect)); + } + } + + obs_frontend_add_event_callback(OBSFrontendEvent, this); +} + +void SRTStats::closeEvent(QCloseEvent *event) +{ + config_t *conf = obs_frontend_get_global_config(); + if (isVisible()) { + config_set_string(conf, "SRTStats", "geometry", + saveGeometry().toBase64().constData()); + config_save_safe(conf, "tmp", nullptr); + } + QWidget::closeEvent(event); +} + +SRTStats::~SRTStats() +{ + obs_frontend_remove_event_callback(OBSFrontendEvent, this); +} + +void SRTStats::AddOutputLabels() +{ + outputLabels.status = new QLabel(this); + outputLabels.droppedFrames = new QLabel(this); + outputLabels.megabytesSent = new QLabel(this); + outputLabels.bitrate = new QLabel(this); + outputLabels.srt_total_pkts = new QLabel(this); + outputLabels.srt_retransmitted_pkts = new QLabel(this); + outputLabels.srt_dropped_pkts = new QLabel(this); + outputLabels.srt_rtt = new QLabel(this); + outputLabels.srt_peer_latency = new QLabel(this); + outputLabels.srt_latency = new QLabel(this); + outputLabels.srt_bandwidth = new QLabel(this); + + int col = 0; + outputLayout->addWidget(outputLabels.status, 1, col++); + outputLayout->addWidget(outputLabels.droppedFrames, 1, col++); + outputLayout->addWidget(outputLabels.megabytesSent, 1, col++); + outputLayout->addWidget(outputLabels.bitrate, 1, col++); + outputLayout->addWidget(outputLabels.srt_rtt, 1, col++); + outputLayout->addWidget(outputLabels.srt_bandwidth, 1, col++); + col = 0; + outputLayout->addWidget(outputLabels.srt_total_pkts, 3, col++); + outputLayout->addWidget(outputLabels.srt_retransmitted_pkts, 3, col++); + outputLayout->addWidget(outputLabels.srt_dropped_pkts, 3, col++); + outputLayout->addWidget(outputLabels.srt_peer_latency, 3, col++); + outputLayout->addWidget(outputLabels.srt_latency, 3, col++); +} + +void SRTStats::Update() +{ + OBSOutputAutoRelease strOutput = obs_frontend_get_streaming_output(); + if (!strOutput) + return; + /* update general stats */ + outputLabels.Update(strOutput); + /* update srt specific stats */ + outputLabels.SRTUpdate(strOutput); + chart->output = strOutput; +} + +void SRTStats::Reset() +{ + timer.start(); + OBSOutputAutoRelease strOutput = obs_frontend_get_streaming_output(); + outputLabels.Reset(strOutput); + Update(); + if (obs_frontend_streaming_active()) { + chart->disconnect(); + chart->connect(); + } +} + +void SRTStats::timeBoxItemChanged(int index) +{ + int time_mn = timeBox->itemData(index).toInt(); + chart->changeTimeSpan(time_mn); +} + +void SRTStats::OutputLabels::Update(obs_output_t *output) +{ + uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0; + uint64_t curTime = os_gettime_ns(); + uint64_t bytesSent = totalBytes; + + if (bytesSent < lastBytesSent) + bytesSent = 0; + if (bytesSent == 0) + lastBytesSent = 0; + + uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8; + long double timePassed = + (long double)(curTime - lastBytesSentTime) / 1000000000.0l; + kbps = (long double)bitsBetween / timePassed / 1000.0l; + + if (timePassed < 0.01l) + kbps = 0.0l; + + QString str = obs_module_text("SRT.Stats.Status.Inactive"); + QString themeID; + bool active = output ? obs_output_active(output) : false; + + if (active) { + bool reconnecting = output ? obs_output_reconnecting(output) + : false; + + if (reconnecting) { + str = obs_module_text("SRT.Stats.Status.Reconnecting"); + themeID = "error"; + } else { + str = obs_module_text("SRT.Stats.Status.Live"); + themeID = "good"; + } + } + + status->setText(str); + setThemeID(status, themeID); + + long double num = (long double)totalBytes / (1024.0l * 1024.0l); + + megabytesSent->setText( + QString("%1 MB").arg(QString::number(num, 'f', 1))); + bitrate->setText(QString("%1 kb/s").arg(QString::number(kbps, 'f', 0))); + + int total = output ? obs_output_get_total_frames(output) : 0; + int dropped = output ? obs_output_get_frames_dropped(output) : 0; + + if (total < first_total || dropped < first_dropped) { + first_total = 0; + first_dropped = 0; + } + + total -= first_total; + dropped -= first_dropped; + + num = total ? (long double)dropped / (long double)total * 100.0l : 0.0l; + + str = QString("%1 / %2 (%3%)") + .arg(QString::number(dropped), QString::number(total), + QString::number(num, 'f', 1)); + droppedFrames->setText(str); + + if (num > 5.0l) + setThemeID(droppedFrames, "error"); + else if (num > 1.0l) + setThemeID(droppedFrames, "warning"); + else + setThemeID(droppedFrames, ""); + + lastBytesSent = bytesSent; + lastBytesSentTime = curTime; +} + +void SRTStats::OutputLabels::SRTUpdate(obs_output_t *output) +{ + total_pkts = 0; + int retransmitted_pkts = 0; + int dropped_pkts = 0; + float rtt = 0; + float peer_latency = 0; + float latency = 0; + float bandwidth = 0; + + calldata_t cd = {0}; + proc_handler_t *ph = obs_output_get_proc_handler(output); + proc_handler_call(ph, "get_srt_stats", &cd); + total_pkts = (qreal)calldata_int(&cd, "srt_total_pkts"); + retransmitted_pkts = calldata_int(&cd, "srt_retransmitted_pkts"); + dropped_pkts = calldata_int(&cd, "srt_dropped_pkts"); + rtt = calldata_float(&cd, "srt_rtt"); + peer_latency = calldata_float(&cd, "srt_peer_latency"); + latency = calldata_float(&cd, "srt_latency"); + bandwidth = calldata_float(&cd, "srt_bandwidth"); + calldata_free(&cd); + + srt_total_pkts->setText(QString::number(total_pkts).append(" pkts")); + float num = (float)retransmitted_pkts / (float)total_pkts; + QString str = QString::number(retransmitted_pkts); + str += QString(" pkts (%1%)").arg(QString::number(num, 'f', 1)); + srt_retransmitted_pkts->setText(str); + num = (float)dropped_pkts / (float)total_pkts; + str = QString::number(dropped_pkts); + str += QString(" pkts (%1%)").arg(QString::number(num, 'f', 1)); + srt_dropped_pkts->setText(str); + srt_rtt->setText(QString::number(rtt, 'f', 1).append(" ms")); + srt_peer_latency->setText( + QString::number(peer_latency, 'f', 1).append(" ms")); + srt_latency->setText(QString::number(latency, 'f', 1).append(" ms")); + srt_bandwidth->setText( + QString::number(bandwidth, 'f', 1).append(" Mbps")); +} + +void SRTStats::OutputLabels::Reset(obs_output_t *output) +{ + if (!output) + return; + + first_total = obs_output_get_total_frames(output); + first_dropped = obs_output_get_frames_dropped(output); +} + +void SRTStats::showEvent(QShowEvent *) +{ + timer.start(TIMER_INTERVAL); +} + +void SRTStats::hideEvent(QHideEvent *) +{ + timer.stop(); +} + +Chart::Chart(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QChart(QChart::ChartTypeCartesian, parent, wFlags), + retrans_pkt_series(new QSplineSeries), + dropped_pkt_series(new QSplineSeries), + rtt_series(new QSplineSeries), + dummy_series(new QLineSeries), + m_axisX(new QValueAxis()), + m_axisY(new QValueAxis()), + m_axisZ(new QValueAxis()), + m_axisT(new QDateTimeAxis()), + m_x(0), + m_y(0), + m_t(0), + old_retrans(0), + old_dropped(0), + time_span(1), + output(nullptr) +{ + QPen red(Qt::red); + QPen green(Qt::green); + QPen blue(Qt::blue); + green.setWidth(3); + red.setWidth(3); + blue.setWidth(3); + retrans_pkt_series->setPen(red); + dropped_pkt_series->setPen(green); + rtt_series->setPen(blue); + + retrans_pkt_series->setName( + obs_module_text("SRT.Stats.Retransmitted.Pkts.Rate")); + dropped_pkt_series->setName( + obs_module_text("SRT.Stats.Dropped.Pkts.Rate")); + rtt_series->setName(obs_module_text("SRT.Stats.RTT")); + + addSeries(dropped_pkt_series); + addSeries(retrans_pkt_series); + addSeries(rtt_series); + addSeries(dummy_series); + + addAxis(m_axisX, Qt::AlignTop); + addAxis(m_axisY, Qt::AlignLeft); + addAxis(m_axisZ, Qt::AlignRight); + addAxis(m_axisT, Qt::AlignBottom); + m_axisX->setVisible(false); + + retrans_pkt_series->attachAxis(m_axisX); + retrans_pkt_series->attachAxis(m_axisY); + dummy_series->attachAxis(m_axisT); + dummy_series->attachAxis(m_axisY); + dummy_series->hide(); + dropped_pkt_series->attachAxis(m_axisX); + dropped_pkt_series->attachAxis(m_axisY); + rtt_series->attachAxis(m_axisX); + rtt_series->attachAxis(m_axisZ); + + m_axisX->setTickCount(5); + m_axisX->setRange(0, 1); + m_axisX->setTitleText(obs_module_text("SRT.Stats.Time")); + m_axisY->setRange(0, 10); + m_axisY->setTitleText(obs_module_text("SRT.Stats.Packets")); + m_axisZ->setRange(0, 50); + m_axisZ->setTitleText(obs_module_text("SRT.Stats.RTT.ms")); + QBrush blueB(Qt::blue); + m_axisZ->setTitleBrush(blueB); + + m_axisT->setTickCount(5); + m_axisT->setFormat("hh:mm:ss"); + m_axisT->setTitleText(obs_module_text("SRT.Stats.Time.HMS")); + + QTime tm = QTime::currentTime(); + QTime tM = tm.addSecs(60); + QDate date = QDate::currentDate(); + QDateTime min; + min.setDate(date); + min.setTime(tm); + QDateTime max; + max.setDate(date); + max.setTime(tM); + + m_axisT->setRange(min, max); + + m_timer.start(); +} + +void Chart::connect() +{ + // Reset the chart when there's a stream starting. + m_axisX->setTickCount(5); + m_axisX->setRange(0, 60 * time_span); + m_axisY->setRange(0, 10); + m_axisZ->setRange(0, 50); + m_x = 0; + m_y = 0; + m_t = 0; + old_retrans = 0; + old_dropped = 0; + retrans_pkt_series->clear(); + dropped_pkt_series->clear(); + rtt_series->clear(); + QTime tm = QTime::currentTime(); + QTime tM = tm.addSecs(60); + QDate date = QDate::currentDate(); + QDateTime min; + min.setDate(date); + min.setTime(tm); + QDateTime max; + max.setDate(date); + max.setTime(tM); + m_axisT->setRange(min, max); + + // Refresh the chart every second. + QObject::connect(&m_timer, &QTimer::timeout, this, + &Chart::handleTimeout); + m_timer.setInterval(1000); +} + +void Chart::disconnect() +{ + QObject::disconnect(&m_timer, &QTimer::timeout, this, + &Chart::handleTimeout); +} + +void Chart::changeTimeSpan(int time_mn) +{ + time_span = time_mn; + m_axisX->setRange(m_axisX->max() - time_span * 60, m_axisX->max()); + QDateTime max = m_axisT->max(); + QDate date = QDate::currentDate(); + QDateTime min; + min.setDate(date); + QTime tm = max.time().addSecs(-60 * time_span); + min.setTime(tm); + m_axisT->setRange(min, max); +} + +Chart::~Chart() {} + +void Chart::handleTimeout() +{ + // The real width is divided in 60 slots per minute. + qreal slide_x = plotArea().width() / (60 * time_span); + qreal y = 1; + // Retrieve data from SRT output. + calldata_t cd = {0}; + proc_handler_t *ph = obs_output_get_proc_handler(output); + proc_handler_call(ph, "get_srt_stats", &cd); + qreal total_pkts = (qreal)calldata_int(&cd, "srt_total_pkts"); + UNUSED_PARAMETER( + total_pkts); // keeping it for now because we might have to use it later + qreal retransmitted_pkts = calldata_int(&cd, "srt_retransmitted_pkts"); + qreal dropped_pkts = calldata_int(&cd, "srt_dropped_pkts"); + qreal rtt = calldata_float(&cd, "srt_rtt"); + calldata_free(&cd); + + // Add retransmitted data to chart & auto-scale the axis. + m_x += y; + m_y = retransmitted_pkts - old_retrans; + old_retrans = retransmitted_pkts; + + // auto-scale the Y axis (pkts) + if (m_x == 1) + m_axisY->setRange(0, qMax(qCeil(m_y / 10) * 10, 10)); + if (m_axisY->min() < 0) + m_axisY->setRange(0, 10.0 * qCeil(m_y / 10)); + if (m_y >= 0.8 * m_axisY->max()) + m_axisY->setRange(0, qMax(2 * m_axisY->max(), + 20.0 * qCeil(m_y / 10))); + + retrans_pkt_series->append(m_x, m_y); + + // Add dropped data to chart & auto-scale the axis. + m_y = dropped_pkts - old_dropped; + old_dropped = dropped_pkts; + if (m_y >= 0.8 * m_axisY->max()) + m_axisY->setRange(0, qMax(2 * m_axisY->max(), + 20.0 * qCeil(m_y / 10))); + dropped_pkt_series->append(m_x, m_y); + + // Add RTT data to chart & auto-scale the axis. + m_y = rtt; + if (m_x == 1) + m_axisZ->setRange(0, qCeil(m_y / 10) * 10); + if (m_y >= 0.8 * m_axisZ->max()) + m_axisZ->setRange(0, qMax(2 * m_axisZ->max(), + 20.0 * qCeil(m_y / 10))); + + rtt_series->append(m_x, m_y); + + // Scroll once the whole range has been drawn + if (m_x > 60) + scroll(slide_x, 0); +} diff --git a/plugins/obs-mpegts/obs-mpegts-srt-stats.hpp b/plugins/obs-mpegts/obs-mpegts-srt-stats.hpp new file mode 100644 index 00000000000000..37c1da6751d8dd --- /dev/null +++ b/plugins/obs-mpegts/obs-mpegts-srt-stats.hpp @@ -0,0 +1,137 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +enum SRT_CHART_TIME { + SRT_CHART_TIME_MN = 1, + SRT_CHART_TIME_3MN = 3, + SRT_CHART_TIME_5MN = 5, + SRT_CHART_TIME_10MN = 10, + SRT_CHART_TIME_HR = 60, + SRT_CHART_TIME_2HR = 120, + SRT_CHART_TIME_4HR = 240 +}; + +class Chart : public QChart { + Q_OBJECT + +public: + Chart(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {}); + virtual ~Chart(); + QValueAxis *m_axisX; // dummy time axis used for scrolling + QValueAxis *m_axisY; // packets per sec + QValueAxis *m_axisZ; // vertical axis for RTT + QDateTimeAxis *m_axisT; // time axis in QDateTime format + QSplineSeries *retrans_pkt_series = nullptr; + QSplineSeries *dropped_pkt_series = nullptr; + QSplineSeries *rtt_series = nullptr; + QLineSeries *dummy_series = nullptr; + obs_output_t *output; + uint time_span; // horizontal axis width in minutes + +public slots: + void handleTimeout(); + void connect(); + void disconnect(); + void changeTimeSpan(int time); + +private: + QTimer m_timer; + qreal m_x; + qreal m_y; + qreal m_t; + qreal old_retrans; + qreal old_dropped; +}; + +class SRTStats : public QFrame { + Q_OBJECT + + QGridLayout *outputLayout = nullptr; + QWidget *srtWidget = nullptr; + Chart *chart = nullptr; + QChartView *chartView = nullptr; + QComboBox *timeBox = nullptr; + QTimer timer; + uint64_t num_bytes = 0; + std::vector bitrates; + + struct OutputLabels { + QPointer status; + QPointer droppedFrames; + QPointer megabytesSent; + QPointer bitrate; + + QPointer srt_total_pkts; + QPointer srt_retransmitted_pkts; + QPointer srt_dropped_pkts; + QPointer srt_rtt; + QPointer srt_peer_latency; + QPointer srt_latency; + QPointer srt_bandwidth; + qreal total_pkts; + + /*------------------------------------------*/ + + uint64_t lastBytesSent = 0; + uint64_t lastBytesSentTime = 0; + + int first_total = 0; + int first_dropped = 0; + /* Update: general stats; SRTUpdate: SRT stats */ + void Update(obs_output_t *output); + void SRTUpdate(obs_output_t *output); + void Reset(obs_output_t *output); + + long double kbps = 0.0l; + }; + + OutputLabels outputLabels; + + void AddOutputLabels(); + void Update(); + + virtual void closeEvent(QCloseEvent *event) override; + + static void OBSFrontendEvent(enum obs_frontend_event event, void *ptr); + + void Reset(); + +public: + SRTStats(); + ~SRTStats(); + +private Q_SLOTS: + void timeBoxItemChanged(int index); + +protected: + virtual void showEvent(QShowEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; +}; diff --git a/plugins/obs-mpegts/obs-mpegts-srt.h b/plugins/obs-mpegts/obs-mpegts-srt.h index b78adf05212df7..2293d03125338a 100644 --- a/plugins/obs-mpegts/obs-mpegts-srt.h +++ b/plugins/obs-mpegts/obs-mpegts-srt.h @@ -14,7 +14,6 @@ * 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-mpegts-common.h" #include @@ -912,7 +911,7 @@ static int libsrt_write(URLContext *h, const uint8_t *buf, int size) s->stats->latency = perf.msRcvTsbPdDelay; if (time > (s->time + 10.0)) { blog(LOG_DEBUG, - "[obs-ffmpeg mpegts muxer / libsrt]: RTT [%.2f ms], Link Bandwidth [%.1f Mbps], peer latency [%ld ms], SRTO_RCVLATENCY [%ld ms]", + "[obs-ffmpeg mpegts muxer / libsrt]: RTT [%.2f ms], Link Bandwidth [%.1f Mbps], peer latency [%d ms], SRTO_RCVLATENCY [%d ms]", perf.msRTT, perf.mbpsBandwidth, perf.msSndTsbPdDelay, perf.msRcvTsbPdDelay); s->time = time; @@ -940,23 +939,23 @@ static int libsrt_close(URLContext *h) blog(LOG_INFO, "[obs-ffmpeg mpegts muxer / libsrt]: Session Summary\n" "\ttime elapsed [%.1f sec]\n" - "\tmean speed [%.1f Mbp]\n" - "\ttotal bytes sent [%.1f MB]\n" - "\tbytes retransmitted [%.2f %%, %.1f MB]\n" - "\tbytes dropped [%.2f %%, %.1f MB]\n" - "\tpackets transmitted [%ld]\n" - "\tpackets retransmitted [%ld]\n" - "\tpackets dropped [%ld]", - (double)perf.msTimeStamp / 1000.0, perf.mbpsSendRate, - (double)perf.byteSentTotal / 1000000.0, + "\tmean speed [%.1f Mbps]\n" + "\ttotal bytes sent [%.2f MB]\n" + "\tbytes retransmitted [%.2f %%, %.2f MB]\n" + "\tbytes dropped [%.2f %%, %.2f MB]\n" + "\tpackets transmitted [%" PRId64 "]\n" + "\tpackets retransmitted [%d]\n" + "\tpackets dropped [%d]", + (double)perf.msTimeStamp / 1000.0f, perf.mbpsSendRate, + (double)perf.byteSentTotal / 1000000.0f, perf.byteSentTotal - ? perf.byteRetransTotal / perf.byteSentTotal * 100.0 - : 0, - (double)perf.byteRetransTotal / 1000000.0, + ? (perf.byteRetransTotal * 100.0f / perf.byteSentTotal) + : 0.0f, + (double)perf.byteRetransTotal / 1000000.0f, perf.byteSentTotal - ? perf.byteSndDropTotal / perf.byteSentTotal * 100.0 - : 0, - (double)perf.byteSndDropTotal / 1000000.0, perf.pktSentTotal, + ? perf.byteSndDropTotal / perf.byteSentTotal * 100.0f + : 0.0f, + (double)perf.byteSndDropTotal / 1000000.0f, perf.pktSentTotal, perf.pktRetransTotal, perf.pktSndDropTotal); srt_epoll_release(s->eid); diff --git a/plugins/obs-mpegts/obs-mpegts.c b/plugins/obs-mpegts/obs-mpegts.c index 48035171643768..b79ba03018b64d 100644 --- a/plugins/obs-mpegts/obs-mpegts.c +++ b/plugins/obs-mpegts/obs-mpegts.c @@ -81,23 +81,25 @@ obs_to_mpegts_video_format(enum video_format format) return AV_PIX_FMT_YUVA422P; case VIDEO_FORMAT_YUVA: return AV_PIX_FMT_YUVA444P; - case VIDEO_FORMAT_YA2L: #if LIBAVUTIL_BUILD >= AV_VERSION_INT(56, 31, 100) + case VIDEO_FORMAT_YA2L: return AV_PIX_FMT_YUVA444P12LE; -#else - return AV_PIX_FMT_NONE; #endif case VIDEO_FORMAT_I010: return AV_PIX_FMT_YUV420P10LE; case VIDEO_FORMAT_P010: return AV_PIX_FMT_P010LE; +#if LIBAVUTIL_BUILD >= AV_VERSION_INT(57, 17, 100) + case VIDEO_FORMAT_P216: + return AV_PIX_FMT_P216LE; + case VIDEO_FORMAT_P416: + return AV_PIX_FMT_P416LE; +#endif case VIDEO_FORMAT_NONE: case VIDEO_FORMAT_AYUV: - /* not supported by FFmpeg */ + default: return AV_PIX_FMT_NONE; } - - return AV_PIX_FMT_NONE; } static enum AVChromaLocation @@ -1472,8 +1474,11 @@ struct obs_output_info mpegts_muxer = { .get_properties = mpegts_properties, }; +extern void load_srt_stats(void); + bool obs_module_load(void) { obs_register_output(&mpegts_muxer); + load_srt_stats(); return true; } From dbcd77e9c902b7ef533fe94f85e72738505a25bc Mon Sep 17 00:00:00 2001 From: pkv Date: Mon, 19 Feb 2024 18:50:15 +0100 Subject: [PATCH 5/5] CI: Use deps with QCharts (temp, remove once deps are merged) This is to ensure test builds are working. This commit should be removed on merging of the PR. Signed-off-by: pkv --- buildspec.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/buildspec.json b/buildspec.json index 080a78db3ce4bf..75f34975d1ce03 100644 --- a/buildspec.json +++ b/buildspec.json @@ -11,15 +11,15 @@ } }, "qt6": { - "version": "2024-01-27", - "baseUrl": "https://github.com/obsproject/obs-deps/releases/download", + "version": "2024-02-19", + "baseUrl": "https://github.com/pkviet/obs-deps/releases/download", "label": "Pre-Built Qt6", "hashes": { - "macos-universal": "86f6418c3e454b3979947fdba183aa7303ca9d7729c7056435bd023d466cf93a", - "windows-x64": "7a15c4b494bfe2df51cad16e7cbcfcc2b5b69b55839dd584c622214038c7d44c" + "macos-universal": "77bb4ea468524f1f1e4c3c83fb72251b129716390d1918a49b726cdc306ac1fb", + "windows-x64": "9e5c59f3f26690931808ee1173cae3bc912b6058edbeebc71762ac598f8f901a" }, "debugSymbols": { - "windows-x64": "ddf356ce20a4946f7522253b5da44ce7fe29398bb55ff74467431fb31e8ec645" + "windows-x64": "62f993967e3858e7e75ef1ef2d42aef2d9d6474b81bbbc062a92a850abd8159b" } }, "cef": {