Skip to content

Commit

Permalink
Reconnect filter sender on source settings changes
Browse files Browse the repository at this point in the history
Without this for example if a VLC source was changed from limited color range to full the filter sender would have still transmitted limited color range and never noticed the change, now it does and reconnects with the new settings.
  • Loading branch information
YorVeX committed Sep 14, 2023
1 parent 452f5bb commit 21bad66
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 1 deletion.
23 changes: 23 additions & 0 deletions BeamSender.cs
Expand Up @@ -198,6 +198,21 @@ public unsafe bool SetVideoParameters(BeamSenderProperties properties, video_for
return true;
}

public unsafe bool VideoParametersChanged(video_format format, uint width, uint height, uint fps_num, uint fps_den, byte full_range, float* color_matrix, float* color_range_min, float* color_range_max)
{
var videoHeader = _videoHeader;
return ((_videoHeader.Width != width) ||
(_videoHeader.Height != height) ||
(_videoHeader.Fps != fps_num) ||
(_videoHeader.FpsDenominator != fps_den) ||
(_videoHeader.Format != format) ||
(_videoHeader.FullRange != full_range) ||
new ReadOnlySpan<float>(color_matrix, 16).SequenceEqual(new ReadOnlySpan<float>(videoHeader.ColorMatrix, 16)) == false ||
new ReadOnlySpan<float>(color_range_min, 3).SequenceEqual(new ReadOnlySpan<float>(videoHeader.ColorRangeMin, 3)) == false ||
new ReadOnlySpan<float>(color_range_max, 3).SequenceEqual(new ReadOnlySpan<float>(videoHeader.ColorRangeMax, 3)) == false
);
}

public unsafe void SetAudioParameters(audio_format format, speaker_layout speakers, uint samples_per_sec, uint frames)
{
Beam.GetAudioPlaneInfo(format, speakers, out _audioPlanes, out _audioBlockSize);
Expand All @@ -214,6 +229,14 @@ public unsafe void SetAudioParameters(audio_format format, speaker_layout speake
Module.Log($"{_audioHeader.Type} output feed initialized with {_audioPlanes} audio planes and a block size of {_audioBlockSize} bytes.", ObsLogLevel.Debug);
}

public unsafe bool AudioParametersChanged(audio_format format, speaker_layout speakers, uint samples_per_sec)
{
return ((_audioHeader.Format != format) ||
(_audioHeader.Speakers != speakers) ||
(_audioHeader.SampleRate != samples_per_sec)
);
}

public bool CanStart
{
get
Expand Down
1 change: 0 additions & 1 deletion BeamSenderProperties.cs
Expand Up @@ -277,7 +277,6 @@ private unsafe void Initialize(obs_data* settings)
#region Callbacks
public unsafe obs_properties* settings_get_properties(void* data)
{
// NeedSenderRestart = false; // settings have been freshly opened, reset this
_initializedEventHandlers.Clear(); // settings have been freshly opened, reset this
var properties = ObsProperties.obs_properties_create();
ObsProperties.obs_properties_set_flags(properties, ObsProperties.OBS_PROPERTIES_DEFER_UPDATE);
Expand Down
68 changes: 68 additions & 0 deletions Filter.cs
Expand Up @@ -29,6 +29,8 @@ public unsafe struct Context
public BeamSenderProperties Properties { get; private set; }
public bool IsEnabled { get; private set; }
public bool IsActive { get; private set; }
public bool CheckSourceChangesVideo { get; set; }
public bool CheckSourceChangesAudio { get; set; }
bool _isFirstVideoFrame = true;
bool _isFirstAudioFrame = true;
readonly BeamSender _beamSender;
Expand Down Expand Up @@ -78,6 +80,15 @@ public void Disable()
StopSender();
}

private void RestartSender()
{
if (IsActive)
{
Disable();
Task.Delay(1000).ContinueWith((t) => Enable()); // a bit of delay is necessary if the filter was started before
}
}

private void StartSenderIfPossible()
{
Module.Log($"{UniquePrefix} {_filterType} StartSenderIfPossible(): IsEnabled={IsEnabled}, IsActive={IsActive}, CanStart={_beamSender.CanStart}", ObsLogLevel.Debug);
Expand All @@ -100,6 +111,8 @@ private void StopSender()
{
_beamSender.Stop();
IsActive = false;
CheckSourceChangesVideo = false;
CheckSourceChangesAudio = false;
_isFirstVideoFrame = true;
_isFirstAudioFrame = true;
}
Expand All @@ -125,6 +138,24 @@ private unsafe void ProcessVideo(void* data, obs_source_frame* frame)
return;

_lastFrame = DateTime.UtcNow;

if (CheckSourceChangesVideo)
{
CheckSourceChangesVideo = false;
var obsVideoInfo = ObsBmem.bzalloc<obs_video_info>();
if (Convert.ToBoolean(Obs.obs_get_video_info(obsVideoInfo)) && (obsVideoInfo != null))
{
if (_beamSender.VideoParametersChanged(frame->format, frame->width, frame->height, obsVideoInfo->fps_num, obsVideoInfo->fps_den, frame->full_range, frame->color_matrix, frame->color_range_min, frame->color_range_max))
{
ObsBmem.bfree(obsVideoInfo);
Module.Log($"{UniquePrefix} {_filterType} source video configuration changed, restarting sender.", ObsLogLevel.Info);
RestartSender();
return;
}
}
ObsBmem.bfree(obsVideoInfo);
}

if (_isFirstVideoFrame) // this is the first frame since the last output (re)start, get video info
{
_isFirstVideoFrame = false;
Expand All @@ -136,7 +167,11 @@ private unsafe void ProcessVideo(void* data, obs_source_frame* frame)
// background: obs_filter_get_parent() is not guaranteed to work in filter_create, but it should be in filter_add, and then we don't need this here anymore where it might be too late and also doubled for first audio and video frame
ContextPointer->ParentSource = Obs.obs_filter_get_parent(ContextPointer->Source);
if (ContextPointer->ParentSource != null)
{
_parentSourceName = Marshal.PtrToStringUTF8((IntPtr)Obs.obs_source_get_name(ContextPointer->ParentSource))!;
fixed (byte* signalName = "update"u8) // register for source settings update to restart the sender if necessary
ObsSignal.signal_handler_connect(Obs.obs_source_get_signal_handler(ContextPointer->ParentSource), (sbyte*)signalName, &SourceUpdateSignalEventHandler, ContextPointer);
}
}

var requiredVideoFormatConversion = Properties.GetRequiredVideoFormatConversion(frame->format);
Expand Down Expand Up @@ -173,6 +208,26 @@ private unsafe void ProcessAudio(void* data, obs_audio_data* frame)
return;

_lastFrame = DateTime.UtcNow;

if (CheckSourceChangesAudio)
{
CheckSourceChangesAudio = false;
var audioInfo = ObsBmem.bzalloc<obs_audio_info>(); // need this for samples_per_sec info
var audioOutputInfo = ObsAudio.audio_output_get_info(Obs.obs_get_audio()); // need this for format info, it's not in the global audio info
var speakerLayout = (ContextPointer->ParentSource != null ? Obs.obs_source_get_speaker_layout(ContextPointer->ParentSource) : audioInfo->speakers);
if (Convert.ToBoolean(Obs.obs_get_audio_info(audioInfo)) && (audioInfo != null) && (audioOutputInfo != null))
{
if (_beamSender.AudioParametersChanged(audioOutputInfo->format, speakerLayout, audioInfo->samples_per_sec))
{
ObsBmem.bfree(audioInfo);
Module.Log($"{UniquePrefix} {_filterType} source audio configuration changed, restarting sender.", ObsLogLevel.Info);
RestartSender();
return;
}
}
ObsBmem.bfree(audioInfo);
}

if (_isFirstAudioFrame) // this is the first frame since the last output (re)start, get audio info
{
_isFirstAudioFrame = false;
Expand Down Expand Up @@ -210,6 +265,17 @@ private unsafe void ProcessAudio(void* data, obs_audio_data* frame)
}
#endregion Instance methods

[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
public static unsafe void SourceUpdateSignalEventHandler(void* data, calldata* callData)
{
var senderFilter = GetFilter(data);
if (senderFilter != null)
{
senderFilter.CheckSourceChangesVideo = true;
senderFilter.CheckSourceChangesAudio = true;
}
}

#region Helper methods
public static unsafe void Register()
{
Expand Down Expand Up @@ -357,6 +423,8 @@ private static unsafe Filter GetFilter(void* data)
public static unsafe void filter_remove(void* data, obs_source* source)
{
Module.Log("filter_remove called", ObsLogLevel.Debug);
fixed (byte* signalName = "update"u8)
ObsSignal.signal_handler_disconnect(Obs.obs_source_get_signal_handler(source), (sbyte*)signalName, &SourceUpdateSignalEventHandler, GetFilter(data).ContextPointer);
}

[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
Expand Down

0 comments on commit 21bad66

Please sign in to comment.