win-dshow: Use a single thread per device
This prevents certain issues I've encountered with devices where they expect to shut down in a specific thread they started up in, as well as a number of other issues, such as the configuration dialogs. The configuration dialogs require that a message loop be present, and this was not the case previously because everything was in the video thread, which has no windows-specific code. Configuration/crossbar/etc dialogs will now execute correctly.master
parent
1291351a2a
commit
4e78635764
|
@ -5,6 +5,7 @@
|
|||
#include <util/dstr.hpp>
|
||||
#include <util/util.hpp>
|
||||
#include <util/platform.h>
|
||||
#include <util/windows/WinHandle.hpp>
|
||||
#include "libdshowcapture/dshowcapture.hpp"
|
||||
#include "ffmpeg-decode.h"
|
||||
|
||||
|
@ -87,10 +88,49 @@ public:
|
|||
inline operator ffmpeg_decode*() {return &decode;}
|
||||
};
|
||||
|
||||
class CriticalSection {
|
||||
CRITICAL_SECTION mutex;
|
||||
|
||||
public:
|
||||
inline CriticalSection() {InitializeCriticalSection(&mutex);}
|
||||
inline ~CriticalSection() {DeleteCriticalSection(&mutex);}
|
||||
|
||||
inline operator CRITICAL_SECTION*() {return &mutex;}
|
||||
};
|
||||
|
||||
class CriticalScope {
|
||||
CriticalSection &mutex;
|
||||
|
||||
CriticalScope() = delete;
|
||||
CriticalScope& operator=(CriticalScope &cs) = delete;
|
||||
|
||||
public:
|
||||
inline CriticalScope(CriticalSection &mutex_) : mutex(mutex_)
|
||||
{
|
||||
EnterCriticalSection(mutex);
|
||||
}
|
||||
|
||||
inline ~CriticalScope()
|
||||
{
|
||||
LeaveCriticalSection(mutex);
|
||||
}
|
||||
};
|
||||
|
||||
enum class Action {
|
||||
None,
|
||||
Update,
|
||||
Shutdown,
|
||||
ConfigVideo,
|
||||
ConfigAudio,
|
||||
ConfigCrossbar1,
|
||||
ConfigCrossbar2,
|
||||
};
|
||||
|
||||
static DWORD CALLBACK DShowThread(LPVOID ptr);
|
||||
|
||||
struct DShowInput {
|
||||
obs_source_t source;
|
||||
Device device;
|
||||
bool comInitialized;
|
||||
bool deviceHasAudio;
|
||||
|
||||
Decoder audio_decoder;
|
||||
|
@ -102,16 +142,51 @@ struct DShowInput {
|
|||
obs_source_frame frame;
|
||||
obs_source_audio audio;
|
||||
|
||||
WinHandle semaphore;
|
||||
WinHandle thread;
|
||||
CriticalSection mutex;
|
||||
vector<Action> actions;
|
||||
|
||||
inline void QueueAction(Action action)
|
||||
{
|
||||
CriticalScope scope(mutex);
|
||||
actions.push_back(action);
|
||||
ReleaseSemaphore(semaphore, 1, nullptr);
|
||||
}
|
||||
|
||||
inline DShowInput(obs_source_t source_)
|
||||
: source (source_),
|
||||
device (InitGraph::False),
|
||||
comInitialized (false)
|
||||
device (InitGraph::False)
|
||||
{
|
||||
memset(&audio, 0, sizeof(audio));
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
|
||||
av_log_set_level(AV_LOG_WARNING);
|
||||
av_log_set_callback(ffmpeg_log);
|
||||
|
||||
semaphore = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr);
|
||||
if (!semaphore)
|
||||
throw "Failed to create semaphore";
|
||||
|
||||
thread = CreateThread(nullptr, 0, DShowThread, this, 0,
|
||||
nullptr);
|
||||
if (!thread)
|
||||
throw "Failed to create thread";
|
||||
|
||||
QueueAction(Action::Update);
|
||||
}
|
||||
|
||||
inline ~DShowInput()
|
||||
{
|
||||
{
|
||||
CriticalScope scope(mutex);
|
||||
actions.resize(1);
|
||||
actions[0] = Action::Shutdown;
|
||||
}
|
||||
|
||||
ReleaseSemaphore(semaphore, 1, nullptr);
|
||||
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
}
|
||||
|
||||
void OnEncodedVideoData(enum AVCodecID id,
|
||||
|
@ -129,8 +204,86 @@ struct DShowInput {
|
|||
bool UpdateVideoConfig(obs_data_t settings);
|
||||
bool UpdateAudioConfig(obs_data_t settings);
|
||||
void Update(obs_data_t settings);
|
||||
|
||||
void DShowLoop();
|
||||
};
|
||||
|
||||
static DWORD CALLBACK DShowThread(LPVOID ptr)
|
||||
{
|
||||
DShowInput *dshowInput = (DShowInput*)ptr;
|
||||
|
||||
CoInitialize(nullptr);
|
||||
dshowInput->DShowLoop();
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void ProcessMessages()
|
||||
{
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Always keep directshow in a single thread for a given device */
|
||||
void DShowInput::DShowLoop()
|
||||
{
|
||||
while (true) {
|
||||
DWORD ret = MsgWaitForMultipleObjects(1, &semaphore, false,
|
||||
INFINITE, QS_ALLINPUT);
|
||||
if (ret == (WAIT_OBJECT_0 + 1)) {
|
||||
ProcessMessages();
|
||||
continue;
|
||||
} else if (ret != WAIT_OBJECT_0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Action action = Action::None;
|
||||
{
|
||||
CriticalScope scope(mutex);
|
||||
if (actions.size()) {
|
||||
action = actions.front();
|
||||
actions.erase(actions.begin());
|
||||
}
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case Action::Update:
|
||||
{
|
||||
obs_data_t settings;
|
||||
settings = obs_source_get_settings(source);
|
||||
Update(settings);
|
||||
obs_data_release(settings);
|
||||
break;
|
||||
}
|
||||
|
||||
case Action::Shutdown:
|
||||
device.ShutdownGraph();
|
||||
return;
|
||||
|
||||
case Action::ConfigVideo:
|
||||
device.OpenDialog(nullptr, DialogType::ConfigVideo);
|
||||
break;
|
||||
|
||||
case Action::ConfigAudio:
|
||||
device.OpenDialog(nullptr, DialogType::ConfigAudio);
|
||||
break;
|
||||
|
||||
case Action::ConfigCrossbar1:
|
||||
device.OpenDialog(nullptr, DialogType::ConfigCrossbar);
|
||||
break;
|
||||
|
||||
case Action::ConfigCrossbar2:
|
||||
device.OpenDialog(nullptr, DialogType::ConfigCrossbar2);
|
||||
break;
|
||||
|
||||
case Action::None:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define FPS_HIGHEST 0LL
|
||||
#define FPS_MATCHING -1LL
|
||||
|
||||
|
@ -640,11 +793,6 @@ bool DShowInput::UpdateAudioConfig(obs_data_t settings)
|
|||
|
||||
void DShowInput::Update(obs_data_t settings)
|
||||
{
|
||||
if (!comInitialized) {
|
||||
CoInitialize(nullptr);
|
||||
comInitialized = true;
|
||||
}
|
||||
|
||||
if (!device.ResetGraph())
|
||||
return;
|
||||
|
||||
|
@ -685,10 +833,14 @@ static const char *GetDShowInputName(void)
|
|||
|
||||
static void *CreateDShowInput(obs_data_t settings, obs_source_t source)
|
||||
{
|
||||
DShowInput *dshow = new DShowInput(source);
|
||||
DShowInput *dshow = nullptr;
|
||||
|
||||
/* causes a deferred update in the video thread */
|
||||
obs_source_update(source, nullptr);
|
||||
try {
|
||||
dshow = new DShowInput(source);
|
||||
} catch (const char *error) {
|
||||
blog(LOG_ERROR, "Could not create device '%s': %s",
|
||||
obs_source_get_name(source), error);
|
||||
}
|
||||
|
||||
UNUSED_PARAMETER(settings);
|
||||
return dshow;
|
||||
|
@ -701,7 +853,8 @@ static void DestroyDShowInput(void *data)
|
|||
|
||||
static void UpdateDShowInput(void *data, obs_data_t settings)
|
||||
{
|
||||
reinterpret_cast<DShowInput*>(data)->Update(settings);
|
||||
UNUSED_PARAMETER(settings);
|
||||
reinterpret_cast<DShowInput*>(data)->QueueAction(Action::Update);
|
||||
}
|
||||
|
||||
static void GetDShowDefaults(obs_data_t settings)
|
||||
|
@ -959,7 +1112,7 @@ static bool VideoConfigClicked(obs_properties_t props, obs_property_t p,
|
|||
void *data)
|
||||
{
|
||||
DShowInput *input = reinterpret_cast<DShowInput*>(data);
|
||||
input->device.OpenDialog(nullptr, DialogType::ConfigVideo);
|
||||
input->QueueAction(Action::ConfigVideo);
|
||||
|
||||
UNUSED_PARAMETER(props);
|
||||
UNUSED_PARAMETER(p);
|
||||
|
@ -970,7 +1123,7 @@ static bool VideoConfigClicked(obs_properties_t props, obs_property_t p,
|
|||
void *data)
|
||||
{
|
||||
DShowInput *input = reinterpret_cast<DShowInput*>(data);
|
||||
input->device.OpenDialog(nullptr, DialogType::ConfigAudio);
|
||||
input->QueueAction(Action::ConfigAudio);
|
||||
|
||||
UNUSED_PARAMETER(props);
|
||||
UNUSED_PARAMETER(p);
|
||||
|
@ -981,7 +1134,7 @@ static bool CrossbarConfigClicked(obs_properties_t props, obs_property_t p,
|
|||
void *data)
|
||||
{
|
||||
DShowInput *input = reinterpret_cast<DShowInput*>(data);
|
||||
input->device.OpenDialog(nullptr, DialogType::ConfigCrossbar);
|
||||
input->QueueAction(Action::ConfigCrossbar1);
|
||||
|
||||
UNUSED_PARAMETER(props);
|
||||
UNUSED_PARAMETER(p);
|
||||
|
@ -992,7 +1145,7 @@ static bool CrossbarConfigClicked(obs_properties_t props, obs_property_t p,
|
|||
void *data)
|
||||
{
|
||||
DShowInput *input = reinterpret_cast<DShowInput*>(data);
|
||||
input->device.OpenDialog(nullptr, DialogType::ConfigCrossbar2);
|
||||
input->QueueAction(Action::ConfigCrossbar2);
|
||||
|
||||
UNUSED_PARAMETER(props);
|
||||
UNUSED_PARAMETER(p);
|
||||
|
|
Loading…
Reference in New Issue