|
|
@@ -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);
|