Skip to content

Commit 2a80183

Browse files
authored
Merge pull request #93 from OPENSPHERE-Inc/fix-crash-on-connection-failure-for-mac-linux
Patch 1.0.6
2 parents 9146d8e + 59f9af4 commit 2a80183

File tree

4 files changed

+97
-52
lines changed

4 files changed

+97
-52
lines changed

buildspec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"name": "osi-branch-output",
4040
"displayName": "Branch Output Plugin",
41-
"version": "1.0.5",
41+
"version": "1.0.6",
4242
"author": "OPENSPHERE Inc.",
4343
"website": "https://opensphere.co.jp/",
4444
"email": "info@opensphere.co.jp",

src/audio/audio-capture.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,9 @@ SourceAudioCapture::SourceAudioCapture(
238238
SourceAudioCapture::~SourceAudioCapture()
239239
{
240240
OBSSourceAutoRelease source = obs_weak_source_get_source(weakSource);
241-
obs_source_remove_audio_capture_callback(source, sourceAudioCallback, this);
241+
if (source) {
242+
obs_source_remove_audio_capture_callback(source, sourceAudioCallback, this);
243+
}
242244

243245
obs_log(LOG_DEBUG, "%s: Source audio capture destroyed.", obs_source_get_name(source));
244246
}

src/plugin-main.cpp

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
3535
#define FILTER_ID "osi_branch_output"
3636
#define OUTPUT_MAX_RETRIES 7
3737
#define OUTPUT_RETRY_DELAY_SECS 1
38-
#define CONNECT_ATTEMPTING_TIMEOUT_NS 15000000000ULL
39-
#define DISCONNECT_ATTEMPTING_TIMEOUT_NS 2000000000ULL
4038
#define RECONNECT_ATTEMPTING_TIMEOUT_NS 2000000000ULL
4139
#define AVAILAVILITY_CHECK_INTERVAL_NS 1000000000ULL
4240
#define TASK_INTERVAL_MS 1000
@@ -216,9 +214,19 @@ void BranchOutputFilter::removeCallback()
216214
// Unregsiter hotkeys
217215
if (toggleEnableHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
218216
obs_hotkey_pair_unregister(toggleEnableHotkeyPairId);
217+
toggleEnableHotkeyPairId = OBS_INVALID_HOTKEY_PAIR_ID;
219218
}
220219
if (splitRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) {
221220
obs_hotkey_unregister(splitRecordingHotkeyId);
221+
splitRecordingHotkeyId = OBS_INVALID_HOTKEY_ID;
222+
}
223+
if (togglePauseRecordingHotkeyPairId != OBS_INVALID_HOTKEY_PAIR_ID) {
224+
obs_hotkey_pair_unregister(togglePauseRecordingHotkeyPairId);
225+
togglePauseRecordingHotkeyPairId = OBS_INVALID_HOTKEY_PAIR_ID;
226+
}
227+
if (addChapterToRecordingHotkeyId != OBS_INVALID_HOTKEY_ID) {
228+
obs_hotkey_unregister(addChapterToRecordingHotkeyId);
229+
addChapterToRecordingHotkeyId = OBS_INVALID_HOTKEY_ID;
222230
}
223231

224232
obs_log(LOG_INFO, "%s: Filter removed", qUtf8Printable(name));
@@ -361,12 +369,12 @@ obs_data_t *BranchOutputFilter::createRecordingSettings(obs_data_t *settings, bo
361369
if (!mux.isEmpty()) {
362370
muxFrag += " " + mux;
363371
}
364-
obs_data_set_string(settings, "muxer_settings", qUtf8Printable(muxFrag));
372+
obs_data_set_string(recordingSettings, "muxer_settings", qUtf8Printable(muxFrag));
365373
} else {
366374
if (isFragmented) {
367375
obs_log(LOG_WARNING, "User enabled fragmented recording, but custom muxer settings contained movflags.");
368376
} else {
369-
obs_data_set_string(settings, "muxer_settings", qUtf8Printable(mux));
377+
obs_data_set_string(recordingSettings, "muxer_settings", qUtf8Printable(mux));
370378
}
371379
}
372380

@@ -498,10 +506,11 @@ BranchOutputFilter::createSreamingOutput(obs_data_t *settings, size_t index)
498506
}
499507
}
500508

501-
// Create stream output
502-
context.output = obs_output_create(type, qUtf8Printable(name), streamingSettings, nullptr);
509+
// Create streaming output
510+
context.output =
511+
obs_output_create(type, qUtf8Printable(QString("%1 (%2)").arg(name).arg(index)), streamingSettings, nullptr);
503512
if (!context.output) {
504-
obs_log(LOG_ERROR, "%s: Streaming %zu output creation failed", qUtf8Printable(name), index);
513+
obs_log(LOG_ERROR, "%s (%zu): Streaming output creation failed", qUtf8Printable(name), index);
505514
return {0};
506515
}
507516
obs_output_set_reconnect_settings(context.output, OUTPUT_MAX_RETRIES, OUTPUT_RETRY_DELAY_SECS);
@@ -532,29 +541,66 @@ void BranchOutputFilter::startStreamingOutput(size_t index)
532541
auto audioContext = &audios[i];
533542
if (audioContext->encoder) {
534543
obs_log(
535-
LOG_WARNING, "%s: No audio encoder selected for streaming %zu, using track %d",
544+
LOG_WARNING, "%s (%zu): No audio encoder selected for streaming output, using track %d",
536545
qUtf8Printable(name), index, i + 1
537546
);
538547
obs_output_set_audio_encoder(streamings[index].output, audioContext->encoder, encIndex++);
539548
break;
540549
}
541550
}
542551
if (!encIndex) {
543-
obs_log(LOG_ERROR, "%s: No audio encoder for streaming %zu", qUtf8Printable(name), index);
552+
obs_log(LOG_ERROR, "%s (%zu): No audio encoder for streaming output", qUtf8Printable(name), index);
544553
return;
545554
}
546555
}
547556

548557
obs_output_set_video_encoder(streamings[index].output, videoEncoder);
549558

550-
streamings[index].connectAttemptingAt = os_gettime_ns();
559+
streamings[index].outputStarting = true;
560+
561+
// Track starting signal
562+
streamings[index].outputStartingSignal.Connect(
563+
obs_output_get_signal_handler(streamings[index].output), "starting",
564+
[](void *_data, calldata_t *) {
565+
auto context = static_cast<BranchOutputStreamingContext *>(_data);
566+
context->outputStarting = true;
567+
obs_log(LOG_DEBUG, "%s: Streaming output is starting", obs_output_get_name(context->output));
568+
},
569+
&streamings[index]
570+
);
571+
572+
// Track activate signal
573+
streamings[index].outputActivateSignal.Connect(
574+
obs_output_get_signal_handler(streamings[index].output), "activate",
575+
[](void *_data, calldata_t *) {
576+
auto context = static_cast<BranchOutputStreamingContext *>(_data);
577+
context->outputStarting = false;
578+
obs_log(LOG_DEBUG, "%s: Streaming output has activated", obs_output_get_name(context->output));
579+
},
580+
&streamings[index]
581+
);
551582

552583
// Track reconnect signal
553584
streamings[index].outputReconnectSignal.Connect(
554585
obs_output_get_signal_handler(streamings[index].output), "reconnect",
555586
[](void *_data, calldata_t *) {
556587
auto context = static_cast<BranchOutputStreamingContext *>(_data);
557588
context->reconnectAttemptingAt = os_gettime_ns();
589+
obs_log(LOG_DEBUG, "%s: Streaming output is reconnecting", obs_output_get_name(context->output));
590+
},
591+
&streamings[index]
592+
);
593+
594+
// Track stop signal
595+
streamings[index].outputStopSignal.Connect(
596+
obs_output_get_signal_handler(streamings[index].output), "stop",
597+
[](void *_data, calldata_t *cd) {
598+
auto context = static_cast<BranchOutputStreamingContext *>(_data);
599+
context->outputStarting = false;
600+
auto code = calldata_int(cd, "code");
601+
obs_log(
602+
LOG_DEBUG, "%s: Streaming output has stopped with code=%lld", obs_output_get_name(context->output), code
603+
);
558604
},
559605
&streamings[index]
560606
);
@@ -563,9 +609,9 @@ void BranchOutputFilter::startStreamingOutput(size_t index)
563609
if (obs_output_start(streamings[index].output)) {
564610
streamings[index].active = true;
565611
obs_source_inc_showing(obs_filter_get_parent(filterSource));
566-
obs_log(LOG_INFO, "%s: Starting streaming %zu output succeeded", qUtf8Printable(name), index);
612+
obs_log(LOG_INFO, "%s (%zu): Starting streaming output succeeded", qUtf8Printable(name), index);
567613
} else {
568-
obs_log(LOG_ERROR, "%s: Starting streaming %zu output failed", qUtf8Printable(name), index);
614+
obs_log(LOG_ERROR, "%s (%zu): Starting streaming output failed", qUtf8Printable(name), index);
569615
}
570616
}
571617

@@ -574,14 +620,19 @@ void BranchOutputFilter::stopStreamingOutput(size_t index)
574620
if (streamings[index].output && streamings[index].active) {
575621
obs_source_dec_showing(obs_filter_get_parent(filterSource));
576622
obs_output_stop(streamings[index].output);
577-
obs_log(LOG_INFO, "%s: Stopping streaming %zu output succeeded", qUtf8Printable(name), index);
623+
obs_log(LOG_INFO, "%s (%zu): Stopping streaming output succeeded", qUtf8Printable(name), index);
578624
}
579625

626+
// Ensure signals are disconnected to prevent leaks across restarts
627+
streamings[index].outputStartingSignal.Disconnect();
628+
streamings[index].outputActivateSignal.Disconnect();
629+
streamings[index].outputReconnectSignal.Disconnect();
630+
streamings[index].outputStopSignal.Disconnect();
631+
580632
streamings[index].output = nullptr;
581633
streamings[index].service = nullptr;
582-
streamings[index].connectAttemptingAt = 0;
583-
streamings[index].disconnectAttemptingAt = 0;
584634
streamings[index].reconnectAttemptingAt = 0;
635+
streamings[index].outputStarting = false;
585636
streamings[index].active = false;
586637
streamings[index].stopping = false;
587638
}
@@ -714,7 +765,7 @@ void BranchOutputFilter::startOutput(obs_data_t *settings)
714765
// Update active revision with stored settings.
715766
activeSettingsRev = storedSettingsRev;
716767

717-
//--- Create service and open stream output ---//
768+
//--- Create service and open streaming output ---//
718769
auto serviceCount = (size_t)obs_data_get_int(settings, "service_count");
719770
for (size_t i = 0; i < MAX_SERVICES && i < serviceCount; i++) {
720771
streamings[i] = createSreamingOutput(settings, i);
@@ -917,12 +968,10 @@ void BranchOutputFilter::reconnectStreamingOutput(size_t index)
917968
OBSMutexAutoUnlock locked(&outputMutex);
918969

919970
if (streamings[index].active) {
920-
obs_output_force_stop(streamings[index].output);
921-
922-
streamings[index].connectAttemptingAt = os_gettime_ns();
971+
obs_output_stop(streamings[index].output);
923972

924973
if (!obs_output_start(streamings[index].output)) {
925-
obs_log(LOG_ERROR, "%s: Reconnect streaming %zu output failed", qUtf8Printable(name), index);
974+
obs_log(LOG_ERROR, "%s (%zu): Reconnect streaming output failed", qUtf8Printable(name), index);
926975
}
927976
}
928977
}
@@ -935,7 +984,7 @@ void BranchOutputFilter::restartRecordingOutput()
935984
OBSMutexAutoUnlock locked(&outputMutex);
936985

937986
if (recordingActive) {
938-
obs_output_force_stop(recordingOutput);
987+
obs_output_stop(recordingOutput);
939988

940989
if (!obs_output_start(recordingOutput)) {
941990
obs_log(LOG_ERROR, "%s: Restart recording output failed", qUtf8Printable(name));
@@ -1039,32 +1088,20 @@ void BranchOutputFilter::restartOutput()
10391088
}
10401089
}
10411090

1042-
bool BranchOutputFilter::connectAttemptingTimedOut(size_t index)
1043-
{
1044-
return streamings[index].connectAttemptingAt &&
1045-
os_gettime_ns() - streamings[index].connectAttemptingAt > CONNECT_ATTEMPTING_TIMEOUT_NS;
1046-
}
1047-
1048-
bool BranchOutputFilter::disconnectAttemptingTimedOut(size_t index)
1049-
{
1050-
return streamings[index].disconnectAttemptingAt &&
1051-
os_gettime_ns() - streamings[index].disconnectAttemptingAt > DISCONNECT_ATTEMPTING_TIMEOUT_NS;
1052-
}
1053-
10541091
bool BranchOutputFilter::reconnectAttemptingTimedOut(size_t index)
10551092
{
10561093
return streamings[index].reconnectAttemptingAt &&
10571094
os_gettime_ns() - streamings[index].reconnectAttemptingAt > RECONNECT_ATTEMPTING_TIMEOUT_NS;
10581095
}
10591096

1060-
bool BranchOutputFilter::everyConnectAttemptingsTimedOut()
1097+
bool BranchOutputFilter::someStreamingsStarting()
10611098
{
10621099
for (size_t i = 0; i < MAX_SERVICES; i++) {
1063-
if (streamings[i].output && !connectAttemptingTimedOut(i)) {
1064-
return false;
1100+
if (streamings[i].outputStarting) {
1101+
return true;
10651102
}
10661103
}
1067-
return true;
1104+
return false;
10681105
}
10691106

10701107
int BranchOutputFilter::countEnabledStreamings(obs_data_t *settings)
@@ -1156,6 +1193,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
11561193

11571194
if (streamingStopping) {
11581195
onStopOutputGracefully();
1196+
return;
11591197
}
11601198

11611199
auto interlockType = statusDock ? statusDock->getInterlockType() : INTERLOCK_TYPE_ALWAYS_ON;
@@ -1205,7 +1243,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12051243
auto recordingAlive = recordingOutput && obs_output_active(recordingOutput);
12061244

12071245
if (sourceEnabled) {
1208-
if (countActiveStreamings() > 0 && !everyConnectAttemptingsTimedOut()) {
1246+
if (someStreamingsStarting()) {
12091247
return;
12101248
}
12111249

@@ -1252,7 +1290,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12521290
sourceHeight += (sourceHeight & 1);
12531291

12541292
if (!sourceInFrontend(parent)) {
1255-
// Stop output when source resolution is zero or source had been removed
1293+
// Stop output when source had been removed
12561294
onStopOutputGracefully();
12571295
return;
12581296
}
@@ -1263,7 +1301,7 @@ void BranchOutputFilter::onIntervalTimerTimeout()
12631301
if (sourceWidth > 0 && sourceHeight > 0) {
12641302
if (!obs_data_get_bool(settings, "keep_output_base_resolution")) {
12651303
// Restart output when source resolution was changed.
1266-
obs_log(LOG_INFO, "%s: Attempting restart the stream output", qUtf8Printable(name));
1304+
obs_log(LOG_INFO, "%s: Attempting restart the streaming output", qUtf8Printable(name));
12671305
startOutput(settings);
12681306
return;
12691307
}
@@ -1328,15 +1366,16 @@ void BranchOutputFilter::onIntervalTimerTimeout()
13281366
}
13291367

13301368
for (size_t i = 0; i < MAX_SERVICES; i++) {
1331-
if (streamings[i].active && streamings[i].output && !obs_output_active(streamings[i].output)) {
1369+
if (streamings[i].active && streamings[i].output && !obs_output_active(streamings[i].output) &&
1370+
!obs_output_reconnecting(streamings[i].output)) {
13321371
// Restart streaming
1333-
obs_log(LOG_INFO, "%s: Attempting reactivate the stream output %zu", qUtf8Printable(name), i);
1372+
obs_log(LOG_INFO, "%s (%zu): Attempting reactivate the streaming output", qUtf8Printable(name), i);
13341373
reconnectStreamingOutput(i);
13351374
}
13361375
}
13371376

13381377
} else {
1339-
if (streamingAlive || recordingAlive || recordingPending) {
1378+
if (streamingActive || recordingActive || recordingPending) {
13401379
// Clicked filter's "Eye" icon (Hide)
13411380
onStopOutputGracefully();
13421381
return;
@@ -1364,14 +1403,13 @@ void BranchOutputFilter::onStopOutputGracefully()
13641403
for (size_t i = 0; i < MAX_SERVICES; i++) {
13651404
if (streamings[i].output && streamings[i].active) {
13661405
if (streamings[i].stopping) {
1367-
if (disconnectAttemptingTimedOut(i) && reconnectAttemptingTimedOut(i)) {
1406+
if (reconnectAttemptingTimedOut(i)) {
13681407
// stop streaming safely
13691408
stopStreamingOutput(i);
13701409
}
13711410
} else if (obs_output_reconnecting(streamings[i].output)) {
13721411
// Waiting few seconds to avoid crash due to stoppoing during reconnecting
13731412
streamings[i].stopping = true;
1374-
streamings[i].disconnectAttemptingAt = os_gettime_ns();
13751413
} else {
13761414
// Stop immediately
13771415
stopStreamingOutput(i);
@@ -1704,6 +1742,11 @@ void obs_module_post_load()
17041742

17051743
void obs_module_unload()
17061744
{
1745+
// Remove and destroy status dock to avoid leaks (hotkeys, timers, widgets)
1746+
if (statusDock) {
1747+
obs_frontend_remove_dock("BranchOutputStatusDock");
1748+
}
1749+
17071750
pthread_mutex_destroy(&pluginMutex);
17081751

17091752
obs_log(LOG_INFO, "Plugin unloaded");

src/plugin-main.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ class BranchOutputFilter : public QObject {
5959
struct BranchOutputStreamingContext {
6060
OBSOutputAutoRelease output;
6161
OBSServiceAutoRelease service;
62-
uint64_t connectAttemptingAt;
63-
uint64_t disconnectAttemptingAt;
6462
uint64_t reconnectAttemptingAt;
63+
bool outputStarting;
6564
bool active;
6665
bool stopping;
66+
OBSSignal outputStartingSignal;
67+
OBSSignal outputActivateSignal;
6768
OBSSignal outputReconnectSignal;
69+
OBSSignal outputStopSignal;
6870
};
6971

7072
QString name;
@@ -123,10 +125,8 @@ class BranchOutputFilter : public QObject {
123125
void loadProfile(obs_data_t *settings);
124126
void loadRecently(obs_data_t *settings);
125127
void restartOutput();
126-
bool connectAttemptingTimedOut(size_t index = 0);
127-
bool disconnectAttemptingTimedOut(size_t index = 0);
128128
bool reconnectAttemptingTimedOut(size_t index = 0);
129-
bool everyConnectAttemptingsTimedOut();
129+
bool someStreamingsStarting();
130130
int countEnabledStreamings(obs_data_t *settings);
131131
int countAliveStreamings();
132132
int countActiveStreamings();

0 commit comments

Comments
 (0)