Skip to content

Commit f833d0d

Browse files
authored
Add MoQ Source for receiving broadcasts (#14)
* Add hang_source: MoQ video source plugin Implement hang_source, a new OBS source that consumes MoQ streams from a relay using the libmoq library. Features: - Configurable URL and BROADCAST fields in OBS source properties - H.264 decoder-safe: waits for keyframes with SPS/PPS before decoding - Realtime-first: drops stale frames to minimize latency - Clean session handling: fully destroys and recreates context on URL/broadcast changes to prevent stale frame processing Files added: - src/hang-source.c/cpp/h: Core source implementation Files modified: - CMakeLists.txt: Build integration for hang_source - src/logger.h: Enhanced logging support - src/moq-output.cpp/h: Output module updates - src/obs-moq.cpp: Plugin registration * Improve thread safety and remove per-frame debug logging - Rename hang_source_disconnect -> hang_source_disconnect_locked and hang_source_destroy_decoder -> hang_source_destroy_decoder_locked to indicate they require the mutex - Add mutex locking in hang_source_destroy() - Add stale callback checks in on_session_status(), on_catalog(), and on_video_frame() to ignore callbacks from disconnected sessions - Handle generation changes in on_catalog() to clean up tracks if reconnection happened during setup - Add origin validity check in hang_source_start_consume() - Refactor hang_source_init_decoder() to prepare new decoder state outside the mutex, then swap atomically inside the mutex - Add comprehensive mutex protection throughout hang_source_decode_frame() with early exits on invalid decoder state - Ensure ctx->frame.data[0] is set to NULL when frame buffer is freed - Remove noisy per-frame LOG_DEBUG calls for video frame receive/output * Fix API renames and race conditions in hang-source API updates: - moq_consume_video_track -> moq_consume_video_ordered - moq_consume_video_track_close -> moq_consume_video_close - moq_publish_media_init -> moq_publish_media_ordered Thread safety fixes in hang-source.cpp: - Add mutex protection for ctx->url and ctx->broadcast in hang_source_update - Pass generation number through on_session_status to hang_source_start_consume - Capture origin handle and broadcast copy while holding mutex in start_consume - Verify generation hasn't changed after moq_origin_consume completes - Create origin/session into local variables in hang_source_reconnect - Re-verify generation before committing new handles to context - Clean up stale resources if generation changed during reconnect setup These fixes prevent race conditions when the user rapidly changes broadcast settings, which could cause callbacks from old sessions to interfere with new connections. * debounced and smoothed out the video preview when changing hang_source broadcast (or URL) * Fix segfault on shutdown by adding shutting_down flag Add a volatile bool shutting_down flag to prevent use-after-free when MoQ callbacks fire during destruction. The issue was that closing MoQ handles triggers async callbacks, but by the time they fire the context may already be freed. Changes: - Add shutting_down flag to hang_source struct - Set flag at start of hang_source_destroy before disconnecting - Add 100ms sleep after disconnect to let callbacks drain - Add early-exit checks for shutting_down in all callbacks: - on_session_status - on_catalog - on_video_frame - hang_source_video_tick - hang_source_decode_frame Callbacks now check the flag while holding the mutex and exit immediately if shutting down, avoiding access to freed memory. * removed redundant hang-source.c file * hang-source: Fix thread safety and dynamic resolution handling Thread safety improvements: - Replace volatile with std::atomic for shutting_down and generation fields - Use proper atomic operations (.load()/.store()) for generation counter - Copy url/broadcast while holding mutex before logging in video_tick to prevent use-after-free race condition Memory safety fixes: - Use av_mallocz instead of av_malloc for codec extradata to ensure padding bytes are zero-initialized - Detect mid-stream resolution changes and reinitialize scaler context and frame buffer to prevent out-of-bounds memory access Documentation: - Add detailed comment explaining the 100ms callback drain delay limitation and suggest reference counting as a more robust solution * hang-source: Remove hardcoded codec and format assumptions Replace static H.264/YUV420P/1080p assumptions with dynamic detection from moq_video_config and decoded frame properties. Codec detection: - Add codec_string_to_id() to map catalog codec strings to FFmpeg IDs - Support H.264 (h264/avc/avc1), HEVC (hevc/h265/hev1/hvc1), VP9 (vp9/vp09), AV1 (av1/av01), and VP8 - Read codec from config->codec/codec_len instead of hardcoding H.264 Pixel format handling: - Add current_pix_fmt field to track actual decoded pixel format - Query frame->format after decode instead of assuming YUV420P - Recreate swscale context when pixel format changes - Supports any format: YUV420P, YUV420P10LE, YUV444P, NV12, etc. Resolution handling: - Remove FRAME_WIDTH/FRAME_HEIGHT (1920x1080) constants - Initialize frame dimensions to 0, set dynamically from stream - Try to get dimensions from codec context after avcodec_open2() if not available in moq_video_config - Existing mid-stream resolution change handling preserved Deferred initialization: - Defer sws_ctx and frame_buffer allocation to first decoded frame - Allows proper format detection before committing to scaler config - Reduces wasted allocations when config dimensions are unavailable Logging improvements: - Include av_get_pix_fmt_name() for readable format names - Log codec string, dimensions, and pixel format on initialization - Log format/dimension changes when scaler is reinitialized * updated defaults to localhost * Simplify hang_source: remove debounce mechanism and reduce mutex contention Major refactoring to simplify the hang_source plugin architecture: Removed debounce mechanism: - Removed pending_url, pending_broadcast, settings_changed_time, reconnect_pending fields - Removed DEBOUNCE_DELAY_MS constant and video_tick polling function - Settings now apply immediately when user clicks OK (not on every keystroke) Simplified settings flow: - hang_source_update() now detects if settings actually changed - Auto-reconnects when settings change to valid values - Auto-disconnects and blanks video when settings become invalid - No more pending vs active settings distinction Reduced mutex contention: - Added fast-path shutting_down.load() checks before acquiring mutex in callbacks - on_session_status, on_catalog, on_video_frame, hang_source_decode_frame all check the atomic flag first for early exit without lock acquisition Result: 62 fewer lines of code, cleaner architecture, same functionality. * Rebranding: Hang Source ➡️ MoQ Source hang_source ➡️ moq_source (filenames and variables too) * removed redundant check, match all "avc" and "h264" to AV_CODEC_ID_H264
1 parent c1bff71 commit f833d0d

File tree

6 files changed

+1039
-7
lines changed

6 files changed

+1039
-7
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,17 @@ if(ENABLE_QT)
5151
)
5252
endif()
5353

54+
# FFmpeg dependency
55+
include(FindPkgConfig)
56+
pkg_check_modules(FFMPEG REQUIRED libavcodec libavutil libswscale libswresample)
57+
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${FFMPEG_INCLUDE_DIRS})
58+
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE ${FFMPEG_LIBRARY_DIRS})
59+
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${FFMPEG_LIBRARIES})
60+
5461
target_sources(
5562
${CMAKE_PROJECT_NAME}
5663
PRIVATE src/obs-moq.cpp src/moq-output.h src/moq-service.h src/moq-output.cpp src/moq-service.cpp
64+
src/moq-source.cpp src/moq-source.h
5765
)
5866

5967
set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})

src/logger.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#include <iostream>
22

3-
// Logging macros
4-
#define LOG(level, format, ...) blog(level, "[obs-moq] " format, ##__VA_ARGS__)
5-
#define LOG_DEBUG(format, ...) LOG(LOG_DEBUG, format, ##__VA_ARGS__)
6-
#define LOG_INFO(format, ...) LOG(LOG_INFO, format, ##__VA_ARGS__)
7-
#define LOG_WARNING(format, ...) LOG(LOG_WARNING, format, ##__VA_ARGS__)
8-
#define LOG_ERROR(format, ...) LOG(LOG_ERROR, format, ##__VA_ARGS__)
3+
// Logging macros - use MOQ_ prefix to avoid conflicts with OBS log level constants
4+
#define MOQ_LOG(level, format, ...) blog(level, "[obs-moq] " format, ##__VA_ARGS__)
5+
#define LOG_DEBUG(format, ...) MOQ_LOG(400, format, ##__VA_ARGS__)
6+
#define LOG_INFO(format, ...) MOQ_LOG(300, format, ##__VA_ARGS__)
7+
#define LOG_WARNING(format, ...) MOQ_LOG(200, format, ##__VA_ARGS__)
8+
#define LOG_ERROR(format, ...) MOQ_LOG(100, format, ##__VA_ARGS__)

src/moq-output.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ bool MoQOutput::Start()
8484

8585
// Start establishing a session with the MoQ server
8686
// NOTE: You could publish the same broadcasts to multiple sessions if you want (redundant ingest).
87-
session = moq_session_connect(server_url.data(), server_url.size(), origin, NULL, session_connect_callback, this);
87+
session = moq_session_connect(server_url.data(), server_url.size(), origin, 0, session_connect_callback, this);
8888
if (session < 0) {
8989
LOG_ERROR("Failed to initialize MoQ server: %d", session);
9090
return false;

0 commit comments

Comments
 (0)