未验证 提交 50ff29e5 编写于 作者: J Jim 提交者: GitHub

Merge pull request #3087 from obsproject/virtualcam

Add Windows Virtual Camera
......@@ -150,6 +150,7 @@ Basic.AutoConfig.StartPage="Usage Information"
Basic.AutoConfig.StartPage.SubTitle="Specify what you want to use the program for"
Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimize for streaming, recording is secondary"
Basic.AutoConfig.StartPage.PrioritizeRecording="Optimize just for recording, I will not be streaming"
Basic.AutoConfig.StartPage.PrioritizeVirtualCam="I will only be using the virtual camera"
Basic.AutoConfig.VideoPage="Video Settings"
Basic.AutoConfig.VideoPage.SubTitle="Specify the desired video settings you would like to use"
Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Use Current (%1x%2)"
......@@ -520,6 +521,7 @@ Basic.Main.StartRecording="Start Recording"
Basic.Main.StartReplayBuffer="Start Replay Buffer"
Basic.Main.SaveReplay="Save Replay"
Basic.Main.StartStreaming="Start Streaming"
Basic.Main.StartVirtualCam="Start Virtual Camera"
Basic.Main.StopRecording="Stop Recording"
Basic.Main.PauseRecording="Pause Recording"
Basic.Main.UnpauseRecording="Unpause Recording"
......@@ -529,6 +531,7 @@ Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..."
Basic.Main.StopStreaming="Stop Streaming"
Basic.Main.StoppingStreaming="Stopping Stream..."
Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)"
Basic.Main.StopVirtualCam="Stop Virtual Camera"
Basic.Main.Group="Group %1"
Basic.Main.GroupItems="Group Selected Items"
Basic.Main.Ungroup="Ungroup"
......
......@@ -373,6 +373,14 @@ Section -FinishSection
ClearErrors
WriteRegDWORD HKLM "Software\Khronos\Vulkan\ImplicitLayers" "$APPDATA\obs-studio-hook\obs-vulkan32.json" 0
# ---------------------------------------
# Register virtual camera dlls
Exec '"$SYSDIR\regsvr32.exe" /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll"'
${if} ${RunningX64}
Exec '"$SYSDIR\regsvr32.exe" /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll"'
${endif}
# ---------------------------------------
ClearErrors
......@@ -420,6 +428,12 @@ Section "un.obs-studio Program Files" UninstallSection1
SetShellVarContext current
ClearErrors
; Unregister virtual camera dlls
Exec '"$SYSDIR\regsvr32.exe" /u /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll"'
${if} ${RunningX64}
Exec '"$SYSDIR\regsvr32.exe" /u /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll"'
${endif}
; Remove from registry...
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
DeleteRegKey HKLM "SOFTWARE\${APPNAME}"
......
......@@ -75,6 +75,7 @@ bool opt_start_streaming = false;
bool opt_start_recording = false;
bool opt_studio_mode = false;
bool opt_start_replaybuffer = false;
bool opt_start_virtualcam = false;
bool opt_minimize_tray = false;
bool opt_allow_opengl = false;
bool opt_always_on_top = false;
......@@ -2425,6 +2426,9 @@ int main(int argc, char *argv[])
} else if (arg_is(argv[i], "--startreplaybuffer", nullptr)) {
opt_start_replaybuffer = true;
} else if (arg_is(argv[i], "--startvirtualcam", nullptr)) {
opt_start_virtualcam = true;
} else if (arg_is(argv[i], "--collection", nullptr)) {
if (++i < argc)
opt_starting_collection = argv[i];
......@@ -2451,7 +2455,8 @@ int main(int argc, char *argv[])
<< "--help, -h: Get list of available commands.\n\n"
<< "--startstreaming: Automatically start streaming.\n"
<< "--startrecording: Automatically start recording.\n"
<< "--startreplaybuffer: Start replay buffer.\n\n"
<< "--startreplaybuffer: Start replay buffer.\n"
<< "--startvirtualcam: Start virtual camera (if available).\n\n"
<< "--collection <string>: Use specific scene collection."
<< "\n"
<< "--profile <string>: Use specific profile.\n"
......
......@@ -225,6 +225,7 @@ extern std::string remuxFilename;
extern bool opt_start_streaming;
extern bool opt_start_recording;
extern bool opt_start_replaybuffer;
extern bool opt_start_virtualcam;
extern bool opt_minimize_tray;
extern bool opt_studio_mode;
extern bool opt_allow_opengl;
......
......@@ -1424,6 +1424,40 @@ static bool Update(wchar_t *cmdLine)
}
}
/* ------------------------------------- *
* Install virtual camera */
if (!bIsPortable) {
wchar_t regsvr[MAX_PATH];
wchar_t src[MAX_PATH];
wchar_t tmp[MAX_PATH];
wchar_t tmp2[MAX_PATH];
SHGetFolderPathW(nullptr, CSIDL_SYSTEM, nullptr,
SHGFP_TYPE_CURRENT, regsvr);
StringCbCat(regsvr, sizeof(regsvr), L"\\regsvr32.exe");
GetCurrentDirectoryW(_countof(src), src);
StringCbCat(src, sizeof(src),
L"\\data\\obs-plugins\\win-dshow\\");
StringCbCopy(tmp, sizeof(tmp), L"\"\"");
StringCbCat(tmp, sizeof(tmp), regsvr);
StringCbCat(tmp, sizeof(tmp), L"\" /s \"");
StringCbCat(tmp, sizeof(tmp), src);
StringCbCat(tmp, sizeof(tmp), L"obs-virtualcam-module");
StringCbCopy(tmp2, sizeof(tmp2), tmp);
StringCbCat(tmp2, sizeof(tmp2), L"32.dll\"\"");
_wsystem(tmp2);
if (is_64bit_windows()) {
StringCbCopy(tmp2, sizeof(tmp2), tmp);
StringCbCat(tmp2, sizeof(tmp2), L"64.dll\"\"");
_wsystem(tmp2);
}
}
/* ------------------------------------- *
* Update hook files and vulkan registry */
......
......@@ -990,7 +990,7 @@ void AutoConfigTestPage::FinalizeResults()
return new QLabel(QTStr(str), this);
};
if (wiz->type != AutoConfig::Type::Recording) {
if (wiz->type == AutoConfig::Type::Streaming) {
const char *serverType = wiz->customServer ? "rtmp_custom"
: "rtmp_common";
......@@ -1093,7 +1093,7 @@ void AutoConfigTestPage::NextStage()
started = true;
}
if (wiz->type == AutoConfig::Type::Recording) {
if (wiz->type != AutoConfig::Type::Streaming) {
stage = Stage::StreamEncoder;
} else if (!wiz->bandwidthTest) {
stage = Stage::BandwidthTest;
......@@ -1163,8 +1163,17 @@ AutoConfigTestPage::~AutoConfigTestPage()
void AutoConfigTestPage::initializePage()
{
if (wiz->type == AutoConfig::Type::VirtualCam) {
wiz->idealResolutionCX = wiz->baseResolutionCX;
wiz->idealResolutionCY = wiz->baseResolutionCY;
wiz->idealFPSNum = 30;
wiz->idealFPSDen = 1;
stage = Stage::Finished;
} else {
stage = Stage::Starting;
}
setSubTitle(QTStr(SUBTITLE_TESTING));
stage = Stage::Starting;
softwareTested = false;
cancel = false;
DeleteLayout(results);
......
......@@ -69,6 +69,18 @@ AutoConfigStartPage::AutoConfigStartPage(QWidget *parent)
ui->setupUi(this);
setTitle(QTStr("Basic.AutoConfig.StartPage"));
setSubTitle(QTStr("Basic.AutoConfig.StartPage.SubTitle"));
OBSBasic *main = OBSBasic::Get();
if (main->VCamEnabled()) {
QRadioButton *prioritizeVCam = new QRadioButton(
QTStr("Basic.AutoConfig.StartPage.PrioritizeVirtualCam"),
this);
QBoxLayout *box = reinterpret_cast<QBoxLayout *>(layout());
box->insertWidget(2, prioritizeVCam);
connect(prioritizeVCam, &QPushButton::clicked, this,
&AutoConfigStartPage::PrioritizeVCam);
}
}
AutoConfigStartPage::~AutoConfigStartPage()
......@@ -78,7 +90,9 @@ AutoConfigStartPage::~AutoConfigStartPage()
int AutoConfigStartPage::nextId() const
{
return AutoConfig::VideoPage;
return wiz->type == AutoConfig::Type::VirtualCam
? AutoConfig::TestPage
: AutoConfig::VideoPage;
}
void AutoConfigStartPage::on_prioritizeStreaming_clicked()
......@@ -91,6 +105,11 @@ void AutoConfigStartPage::on_prioritizeRecording_clicked()
wiz->type = AutoConfig::Type::Recording;
}
void AutoConfigStartPage::PrioritizeVCam()
{
wiz->type = AutoConfig::Type::VirtualCam;
}
/* ------------------------------------------------------------------------- */
#define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x
......
......@@ -33,6 +33,7 @@ class AutoConfig : public QWizard {
Invalid,
Streaming,
Recording,
VirtualCam,
};
enum class Service {
......@@ -139,6 +140,7 @@ public:
public slots:
void on_prioritizeStreaming_clicked();
void on_prioritizeRecording_clicked();
void PrioritizeVCam();
};
class AutoConfigVideoPage : public QWizardPage {
......
......@@ -14,6 +14,7 @@ volatile bool streaming_active = false;
volatile bool recording_active = false;
volatile bool recording_paused = false;
volatile bool replaybuf_active = false;
volatile bool virtualcam_active = false;
#define RTMP_PROTOCOL "rtmp"
......@@ -139,6 +140,30 @@ static void OBSReplayBufferStopping(void *data, calldata_t *params)
UNUSED_PARAMETER(params);
}
static void OBSStartVirtualCam(void *data, calldata_t *params)
{
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
output->virtualCamActive = true;
os_atomic_set_bool(&virtualcam_active, true);
QMetaObject::invokeMethod(output->main, "OnVirtualCamStart");
UNUSED_PARAMETER(params);
}
static void OBSStopVirtualCam(void *data, calldata_t *params)
{
BasicOutputHandler *output = static_cast<BasicOutputHandler *>(data);
int code = (int)calldata_int(params, "code");
output->virtualCamActive = false;
os_atomic_set_bool(&virtualcam_active, false);
QMetaObject::invokeMethod(output->main, "OnVirtualCamStop",
Q_ARG(int, code));
UNUSED_PARAMETER(params);
}
static void FindBestFilename(string &strPath, bool noSpace)
{
int num = 2;
......@@ -197,6 +222,49 @@ static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate,
/* ------------------------------------------------------------------------ */
inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_)
{
if (main->vcamEnabled) {
virtualCam = obs_output_create("virtualcam_output",
"virtualcam_output", nullptr,
nullptr);
obs_output_release(virtualCam);
signal_handler_t *signal =
obs_output_get_signal_handler(virtualCam);
startVirtualCam.Connect(signal, "start", OBSStartVirtualCam,
this);
stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this);
}
}
bool BasicOutputHandler::StartVirtualCam()
{
if (main->vcamEnabled) {
obs_output_set_media(virtualCam, obs_get_video(),
obs_get_audio());
return obs_output_start(virtualCam);
}
return false;
}
void BasicOutputHandler::StopVirtualCam()
{
if (main->vcamEnabled) {
obs_output_stop(virtualCam);
}
}
bool BasicOutputHandler::VirtualCamActive() const
{
if (main->vcamEnabled) {
return obs_output_active(virtualCam);
}
return false;
}
/* ------------------------------------------------------------------------ */
struct SimpleOutput : BasicOutputHandler {
OBSEncoder aacStreaming;
OBSEncoder h264Streaming;
......
......@@ -8,10 +8,12 @@ struct BasicOutputHandler {
OBSOutput fileOutput;
OBSOutput streamOutput;
OBSOutput replayBuffer;
OBSOutput virtualCam;
bool streamingActive = false;
bool recordingActive = false;
bool delayActive = false;
bool replayBufferActive = false;
bool virtualCamActive = false;
OBSBasic *main;
std::string outputType;
......@@ -23,31 +25,36 @@ struct BasicOutputHandler {
OBSSignal stopReplayBuffer;
OBSSignal startStreaming;
OBSSignal stopStreaming;
OBSSignal startVirtualCam;
OBSSignal stopVirtualCam;
OBSSignal streamDelayStarting;
OBSSignal streamStopping;
OBSSignal recordStopping;
OBSSignal replayBufferStopping;
inline BasicOutputHandler(OBSBasic *main_) : main(main_) {}
inline BasicOutputHandler(OBSBasic *main_);
virtual ~BasicOutputHandler(){};
virtual bool StartStreaming(obs_service_t *service) = 0;
virtual bool StartRecording() = 0;
virtual bool StartReplayBuffer() { return false; }
virtual bool StartVirtualCam();
virtual void StopStreaming(bool force = false) = 0;
virtual void StopRecording(bool force = false) = 0;
virtual void StopReplayBuffer(bool force = false) { (void)force; }
virtual void StopVirtualCam();
virtual bool StreamingActive() const = 0;
virtual bool RecordingActive() const = 0;
virtual bool ReplayBufferActive() const { return false; }
virtual bool VirtualCamActive() const;
virtual void Update() = 0;
inline bool Active() const
{
return streamingActive || recordingActive || delayActive ||
replayBufferActive;
replayBufferActive || virtualCamActive;
}
};
......
......@@ -1080,6 +1080,12 @@ retryScene:
opt_start_replaybuffer = false;
}
if (opt_start_virtualcam) {
QMetaObject::invokeMethod(this, "StartVirtualCam",
Qt::QueuedConnection);
opt_start_virtualcam = false;
}
copyStrings.clear();
copyFiltersString = nullptr;
......@@ -1518,6 +1524,18 @@ void OBSBasic::ReplayBufferClicked()
StartReplayBuffer();
};
void OBSBasic::AddVCamButton()
{
vcamButton = new ReplayBufferButton(QTStr("Basic.Main.StartVirtualCam"),
this);
vcamButton->setCheckable(true);
connect(vcamButton.data(), &QPushButton::clicked, this,
&OBSBasic::VCamButtonClicked);
vcamButton->setProperty("themeID", "vcamButton");
ui->buttonsVLayout->insertWidget(2, vcamButton);
}
void OBSBasic::ResetOutputs()
{
ProfileScope("OBSBasic::ResetOutputs");
......@@ -1645,6 +1663,13 @@ void OBSBasic::OBSInit()
cef = obs_browser_init_panel();
#endif
obs_data_t *obsData = obs_get_private_data();
vcamEnabled = obs_data_get_bool(obsData, "vcamEnabled");
if (vcamEnabled) {
AddVCamButton();
}
obs_data_release(obsData);
InitBasicConfigDefaults2();
CheckForSimpleModeX264Fallback();
......@@ -2248,6 +2273,23 @@ void OBSBasic::CreateHotkeys()
LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer",
"OBSBasic.StopReplayBuffer");
if (vcamEnabled) {
vcamHotkeys = obs_hotkey_pair_register_frontend(
"OBSBasic.StartVirtualCam",
Str("Basic.Main.StartVirtualCam"),
"OBSBasic.StopVirtualCam",
Str("Basic.Main.StopVirtualCam"),
MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(),
basic.StartVirtualCam,
"Starting virtual camera"),
MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(),
basic.StopVirtualCam,
"Stopping virtual camera"),
this, this);
LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam",
"OBSBasic.StopVirtualCam");
}
togglePreviewHotkeys = obs_hotkey_pair_register_frontend(
"OBSBasic.EnablePreview",
Str("Basic.Main.PreviewConextMenu.Enable"),
......@@ -5247,6 +5289,10 @@ void OBSBasic::OpenSceneFilters()
"==== Streaming Start ==============================================="
#define STREAMING_STOP \
"==== Streaming Stop ================================================"
#define VIRTUAL_CAM_START \
"==== Virtual Camera Start =========================================="
#define VIRTUAL_CAM_STOP \
"==== Virtual Camera Stop ==========================================="
void OBSBasic::StartStreaming()
{
......@@ -5967,6 +6013,61 @@ void OBSBasic::ReplayBufferStop(int code)
UpdateReplayBuffer(false);
}
void OBSBasic::StartVirtualCam()
{
if (!outputHandler || !outputHandler->virtualCam)
return;
if (outputHandler->VirtualCamActive())
return;
if (disableOutputsRef)
return;
SaveProject();
if (!outputHandler->StartVirtualCam()) {
vcamButton->setChecked(false);
}
}
void OBSBasic::StopVirtualCam()
{
if (!outputHandler || !outputHandler->virtualCam)
return;
SaveProject();
if (outputHandler->VirtualCamActive())
outputHandler->StopVirtualCam();
OnDeactivate();
}
void OBSBasic::OnVirtualCamStart()
{
if (!outputHandler || !outputHandler->virtualCam)
return;
vcamButton->setText(QTStr("Basic.Main.StopVirtualCam"));
vcamButton->setChecked(true);
OnActivate();
blog(LOG_INFO, VIRTUAL_CAM_START);
}
void OBSBasic::OnVirtualCamStop(int)
{
if (!outputHandler || !outputHandler->virtualCam)
return;
vcamButton->setText(QTStr("Basic.Main.StartVirtualCam"));
vcamButton->setChecked(false);
blog(LOG_INFO, VIRTUAL_CAM_STOP);
OnDeactivate();
}
void OBSBasic::on_streamButton_clicked()
{
if (outputHandler->StreamingActive()) {
......@@ -6073,6 +6174,20 @@ void OBSBasic::on_recordButton_clicked()
}
}
void OBSBasic::VCamButtonClicked()
{
if (outputHandler->VirtualCamActive()) {
StopVirtualCam();
} else {
if (!UIValidation::NoSourcesConfirmation(this)) {
vcamButton->setChecked(false);
return;
}
StartVirtualCam();
}
}
void OBSBasic::on_settingsButton_clicked()
{
on_action_Settings_triggered();
......
......@@ -166,6 +166,7 @@ class OBSBasic : public OBSMainWindow {
friend class ReplayBufferButton;
friend class ExtraBrowsersModel;
friend class ExtraBrowsersDelegate;
friend struct BasicOutputHandler;
friend struct OBSStudioAPI;
enum class MoveDir { Up, Down, Left, Right };
......@@ -256,6 +257,9 @@ private:
QScopedPointer<QPushButton> pause;
QScopedPointer<QPushButton> replay;
QPointer<QPushButton> vcamButton;
bool vcamEnabled = false;
QScopedPointer<QSystemTrayIcon> trayIcon;
QPointer<QAction> sysTrayStream;
QPointer<QAction> sysTrayRecord;
......@@ -382,7 +386,7 @@ private:
QModelIndexList GetAllSelectedSourceItems();
obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys,
replayBufHotkeys, togglePreviewHotkeys;
replayBufHotkeys, vcamHotkeys, togglePreviewHotkeys;
obs_hotkey_id forceStreamingStopHotkey;
void InitDefaultTransitions();
......@@ -561,6 +565,12 @@ public slots:
void ReplayBufferStopping();
void ReplayBufferStop(int code);
void StartVirtualCam();
void StopVirtualCam();
void OnVirtualCamStart();
void OnVirtualCamStop(int code);
void SaveProjectDeferred();
void SaveProject();
......@@ -740,6 +750,8 @@ public:
return os_atomic_load_bool(&previewProgramMode);
}
inline bool VCamEnabled() const { return vcamEnabled; }
bool StreamingActive() const;
bool Active() const;
......@@ -747,6 +759,7 @@ public:
int ResetVideo();
bool ResetAudio();
void AddVCamButton();
void ResetOutputs();
void ResetAudioDevice(const char *sourceId, const char *deviceId,
......@@ -887,6 +900,7 @@ private slots:
void on_streamButton_clicked();
void on_recordButton_clicked();
void VCamButtonClicked();
void on_settingsButton_clicked();
void on_actionHelpPortal_triggered();
......
......@@ -21,6 +21,14 @@ set(win-dshow_SOURCES
ffmpeg-decode.c
win-dshow.rc)
set(virtualcam-output_SOURCES
tiny-nv12-scale.c
shared-memory-queue.c
virtualcam.c)
set(virtualcam-output_HEADERS
tiny-nv12-scale.h
shared-memory-queue.h)
set(libdshowcapture_SOURCES
libdshowcapture/source/capture-filter.cpp
libdshowcapture/source/output-filter.cpp
......@@ -54,6 +62,8 @@ set(libdshowcapture_HEADERS
add_library(win-dshow MODULE
${win-dshow_SOURCES}
${win-dshow_HEADERS}
${virtualcam-output_SOURCES}
${virtualcam-output_HEADERS}
${libdshowcapture_SOURCES}
${libdshowcapture_HEADERS})
target_link_libraries(win-dshow
......@@ -61,7 +71,13 @@ target_link_libraries(win-dshow
strmiids
ksuser
wmcodecdspuuid
w32-pthreads
${FFMPEG_LIBRARIES})
set_target_properties(win-dshow PROPERTIES FOLDER "plugins")
set_target_properties(win-dshow PROPERTIES FOLDER "plugins/win-dshow")
source_group("libdshowcapture\\Source Files" FILES ${libdshowcapture_SOURCES})
source_group("libdshowcapture\\Header Files" FILES ${libdshowcapture_HEADERS})
install_obs_plugin_with_data(win-dshow data)
add_subdirectory(virtualcam-module)
#include <obs-module.h>
#include <strsafe.h>
#include <strmif.h>
#include "virtualcam-guid.h"
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("win-dshow", "en-US")
......@@ -10,9 +13,41 @@ MODULE_EXPORT const char *obs_module_description(void)
extern void RegisterDShowSource();
extern void RegisterDShowEncoders();
extern "C" struct obs_output_info virtualcam_info;
static bool vcam_installed(bool b64)
{
wchar_t cls_str[CHARS_IN_GUID];
wchar_t temp[MAX_PATH];
HKEY key = nullptr;
StringFromGUID2(CLSID_OBS_VirtualVideo, cls_str, CHARS_IN_GUID);
StringCbPrintf(temp, sizeof(temp), L"CLSID\\%s", cls_str);
DWORD flags = KEY_READ;
flags |= b64 ? KEY_WOW64_64KEY : KEY_WOW64_32KEY;
LSTATUS status = RegOpenKeyExW(HKEY_CLASSES_ROOT, temp, 0, flags, &key);
if (status != ERROR_SUCCESS) {
return false;
}
RegCloseKey(key);
return true;
}
bool obs_module_load(void)
{
RegisterDShowSource();
RegisterDShowEncoders();
obs_register_output(&virtualcam_info);
if (vcam_installed(false)) {
obs_data_t *obs_settings = obs_data_create();
obs_data_set_bool(obs_settings, "vcamEnabled", true);
obs_apply_private_data(obs_settings);
obs_data_release(obs_settings);
}
return true;
}
Subproject commit 03fbb3814b2ad678f5be4b23b8ca05fb4172e12e
Subproject commit 7a495a9d3844152e3c43d14855a386d044ecd411
#include <windows.h>
#include "shared-memory-queue.h"
#include "tiny-nv12-scale.h"
#define VIDEO_NAME L"OBSVirtualCamVideo"
enum queue_type {
SHARED_QUEUE_TYPE_VIDEO,
};
struct queue_header {
volatile uint32_t write_idx;
volatile uint32_t read_idx;
volatile uint32_t state;
uint32_t offsets[3];
uint32_t type;
uint32_t cx;
uint32_t cy;
uint64_t interval;
uint32_t reserved[8];
};
struct video_queue {
HANDLE handle;
bool ready_to_read;
struct queue_header *header;
uint64_t *ts[3];
uint8_t *frame[3];
long last_inc;
int dup_counter;
bool is_writer;
};
#define ALIGN_SIZE(size, align) size = (((size) + (align - 1)) & (~(align - 1)))
#define FRAME_HEADER_SIZE 32
video_queue_t *video_queue_create(uint32_t cx, uint32_t cy, uint64_t interval)
{
struct video_queue vq = {0};
struct video_queue *pvq;
DWORD frame_size = cx * cy * 3 / 2;
uint32_t offset_frame[3];
DWORD size;
size = sizeof(struct queue_header);
ALIGN_SIZE(size, 32);
offset_frame[0] = size;
size += frame_size + FRAME_HEADER_SIZE;
ALIGN_SIZE(size, 32);
offset_frame[1] = size;
size += frame_size + FRAME_HEADER_SIZE;
ALIGN_SIZE(size, 32);
offset_frame[2] = size;
size += frame_size + FRAME_HEADER_SIZE;
ALIGN_SIZE(size, 32);
struct queue_header header = {0};
header.state = SHARED_QUEUE_STATE_STARTING;
header.cx = cx;
header.cy = cy;
header.interval = interval;
vq.is_writer = true;
for (size_t i = 0; i < 3; i++) {
uint32_t off = offset_frame[i];
header.offsets[i] = off;
}
/* fail if already in use */
vq.handle = OpenFileMappingW(FILE_MAP_READ, false, VIDEO_NAME);
if (vq.handle) {
CloseHandle(vq.handle);
return NULL;
}
vq.handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, size, VIDEO_NAME);
if (!vq.handle) {
return NULL;
}
vq.header = (struct queue_header *)MapViewOfFile(
vq.handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
memcpy(vq.header, &header, sizeof(header));
for (size_t i = 0; i < 3; i++) {
uint32_t off = offset_frame[i];
vq.ts[i] = (uint64_t *)(((uint8_t *)vq.header) + off);
vq.frame[i] = ((uint8_t *)vq.header) + off + FRAME_HEADER_SIZE;
}
pvq = malloc(sizeof(vq));
memcpy(pvq, &vq, sizeof(vq));
return pvq;
}
video_queue_t *video_queue_open()
{
struct video_queue vq = {0};
vq.handle = OpenFileMappingW(FILE_MAP_READ, false, VIDEO_NAME);
if (!vq.handle) {
return NULL;
}
vq.header = (struct queue_header *)MapViewOfFile(
vq.handle, FILE_MAP_READ, 0, 0, 0);
struct video_queue *pvq = malloc(sizeof(vq));
memcpy(pvq, &vq, sizeof(vq));
return pvq;
}
void video_queue_close(video_queue_t *vq)
{
if (!vq) {
return;
}
if (vq->is_writer) {
vq->header->state = SHARED_QUEUE_STATE_STOPPING;
}
UnmapViewOfFile(vq->header);
CloseHandle(vq->handle);
free(vq);
}
void video_queue_get_info(video_queue_t *vq, uint32_t *cx, uint32_t *cy,
uint64_t *interval)
{
struct queue_header *qh = vq->header;
*cx = qh->cx;
*cy = qh->cy;
*interval = qh->interval;
}
#define get_idx(inc) ((unsigned long)inc % 3)
void video_queue_write(video_queue_t *vq, uint8_t **data, uint32_t *linesize,
uint64_t timestamp)
{
struct queue_header *qh = vq->header;
long inc = ++qh->write_idx;
unsigned long idx = get_idx(inc);
size_t size = linesize[0] * qh->cy;
*vq->ts[idx] = timestamp;
memcpy(vq->frame[idx], data[0], size);
memcpy(vq->frame[idx] + size, data[1], size / 2);
qh->read_idx = inc;
qh->state = SHARED_QUEUE_STATE_READY;
}
enum queue_state video_queue_state(video_queue_t *vq)
{
if (!vq) {
return SHARED_QUEUE_STATE_INVALID;
}
enum queue_state state = (enum queue_state)vq->header->state;
if (!vq->ready_to_read && state == SHARED_QUEUE_STATE_READY) {
for (size_t i = 0; i < 3; i++) {
size_t off = vq->header->offsets[i];
vq->ts[i] = (uint64_t *)(((uint8_t *)vq->header) + off);
vq->frame[i] = ((uint8_t *)vq->header) + off +
FRAME_HEADER_SIZE;
}
vq->ready_to_read = true;
}
return state;
}
bool video_queue_read(video_queue_t *vq, nv12_scale_t *scale, void *dst,
uint64_t *ts)
{
struct queue_header *qh = vq->header;
long inc = qh->read_idx;
if (qh->state == SHARED_QUEUE_STATE_STOPPING) {
return false;
}
if (inc == vq->last_inc) {
if (++vq->dup_counter == 10) {
return false;
}
} else {
vq->dup_counter = 0;
vq->last_inc = inc;
}
unsigned long idx = get_idx(inc);
*ts = *vq->ts[idx];
nv12_do_scale(scale, dst, vq->frame[idx]);
return true;
}
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct video_queue;
struct nv12_scale;
typedef struct video_queue video_queue_t;
typedef struct nv12_scale nv12_scale_t;
enum queue_state {
SHARED_QUEUE_STATE_INVALID,
SHARED_QUEUE_STATE_STARTING,
SHARED_QUEUE_STATE_READY,
SHARED_QUEUE_STATE_STOPPING,
};
extern video_queue_t *video_queue_create(uint32_t cx, uint32_t cy,
uint64_t interval);
extern video_queue_t *video_queue_open();
extern void video_queue_close(video_queue_t *vq);
extern void video_queue_get_info(video_queue_t *vq, uint32_t *cx, uint32_t *cy,
uint64_t *interval);
extern void video_queue_write(video_queue_t *vq, uint8_t **data,
uint32_t *linesize, uint64_t timestamp);
extern enum queue_state video_queue_state(video_queue_t *vq);
extern bool video_queue_read(video_queue_t *vq, nv12_scale_t *scale, void *dst,
uint64_t *ts);
#ifdef __cplusplus
}
#endif
#include <string.h>
#include "tiny-nv12-scale.h"
/* TODO: optimize this stuff later, or replace with something better. it's
* kind of garbage. although normally it shouldn't be called that often. plus
* it's nearest neighbor so not really a huge deal. at the very least it
* should be sse2 at some point. */
void nv12_scale_init(nv12_scale_t *s, bool convert_to_i420, int dst_cx,
int dst_cy, int src_cx, int src_cy)
{
s->convert_to_i420 = convert_to_i420;
s->src_cx = src_cx;
s->src_cy = src_cy;
s->dst_cx = dst_cx;
s->dst_cy = dst_cy;
}
static void nv12_scale_nearest(nv12_scale_t *s, uint8_t *dst_start,
const uint8_t *src)
{
register uint8_t *dst = dst_start;
const int src_cx = s->src_cx;
const int src_cy = s->src_cy;
const int dst_cx = s->dst_cx;
const int dst_cy = s->dst_cy;
/* lum */
for (int y = 0; y < dst_cy; y++) {
const int src_line = y * src_cy / dst_cy * s->src_cx;
for (int x = 0; x < dst_cx; x++) {
const int src_x = x * src_cx / dst_cx;
*(dst++) = src[src_line + src_x];
}
}
src += src_cx * src_cy;
/* uv */
const int dst_cx_d2 = dst_cx / 2;
const int dst_cy_d2 = dst_cy / 2;
for (int y = 0; y < dst_cy_d2; y++) {
const int src_line = y * src_cy / dst_cy * src_cx;
for (int x = 0; x < dst_cx_d2; x++) {
const int src_x = x * src_cx / dst_cx * 2;
const int pos = src_line + src_x;
*(dst++) = src[pos];
*(dst++) = src[pos + 1];
}
}
}
static void nv12_scale_nearest_to_i420(nv12_scale_t *s, uint8_t *dst_start,
const uint8_t *src)
{
register uint8_t *dst = dst_start;
const int src_cx = s->src_cx;
const int src_cy = s->src_cy;
const int dst_cx = s->dst_cx;
const int dst_cy = s->dst_cy;
const int size = src_cx * src_cy;
/* lum */
for (int y = 0; y < dst_cy; y++) {
const int src_line = y * src_cy / dst_cy * s->src_cx;
for (int x = 0; x < dst_cx; x++) {
const int src_x = x * src_cx / dst_cx;
*(dst++) = src[src_line + src_x];
}
}
src += size;
/* uv */
const int dst_cx_d2 = dst_cx / 2;
const int dst_cy_d2 = dst_cy / 2;
register uint8_t *dst2 = dst + dst_cx * dst_cy / 4;
for (int y = 0; y < dst_cy_d2; y++) {
const int src_line = y * src_cy / dst_cy * src_cx;
for (int x = 0; x < dst_cx_d2; x++) {
const int src_x = x * src_cx / dst_cx * 2;
const int pos = src_line + src_x;
*(dst++) = src[pos];
*(dst2++) = src[pos + 1];
}
}
}
static void nv12_convert_to_i420(nv12_scale_t *s, uint8_t *dst_start,
const uint8_t *src_start)
{
const int size = s->src_cx * s->src_cy;
const int size_d4 = size / 4;
memcpy(dst_start, src_start, size);
register uint8_t *dst1 = dst_start + size;
register uint8_t *dst2 = dst1 + size_d4;
register uint8_t *dst_end = dst2 + size_d4;
register const uint8_t *src = src_start + size;
while (dst2 < dst_end) {
*(dst1++) = *(src++);
*(dst2++) = *(src++);
}
}
void nv12_do_scale(nv12_scale_t *s, uint8_t *dst, const uint8_t *src)
{
if (s->src_cx == s->dst_cx && s->src_cy == s->dst_cy) {
if (s->convert_to_i420)
nv12_convert_to_i420(s, dst, src);
else
memcpy(dst, src, s->src_cx * s->src_cy * 3 / 2);
} else {
if (s->convert_to_i420)
nv12_scale_nearest_to_i420(s, dst, src);
else
nv12_scale_nearest(s, dst, src);
}
}
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct nv12_scale {
bool convert_to_i420;
int src_cx;
int src_cy;
int dst_cx;
int dst_cy;
};
typedef struct nv12_scale nv12_scale_t;
extern void nv12_scale_init(nv12_scale_t *s, bool convert_to_i420, int dst_cx,
int dst_cy, int src_cx, int src_cy);
extern void nv12_do_scale(nv12_scale_t *s, uint8_t *dst, const uint8_t *src);
#ifdef __cplusplus
}
#endif
#pragma once
#include <windows.h>
#include <initguid.h>
// {A3FCE0F5-3493-419F-958A-ABA1250EC20B}
DEFINE_GUID(CLSID_OBS_VirtualVideo, 0xa3fce0f5, 0x3493, 0x419f, 0x95, 0x8a,
0xab, 0xa1, 0x25, 0xe, 0xc2, 0xb);
cmake_minimum_required(VERSION 3.5)
project(obs-virtualcam-module)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_output_suffix "64")
else()
set(_output_suffix "32")
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/virtualcam-module.def.in"
"${CMAKE_CURRENT_BINARY_DIR}/virtualcam-module.def")
set(libdshowcapture_SOURCES
../libdshowcapture/source/log.cpp
../libdshowcapture/source/dshow-base.cpp
../libdshowcapture/source/dshow-enum.cpp
../libdshowcapture/source/dshow-formats.cpp
../libdshowcapture/source/dshow-media-type.cpp
../libdshowcapture/source/output-filter.cpp
)
set(libdshowcapture_HEADERS
../libdshowcapture/source/ComPtr.hpp
../libdshowcapture/source/CoTaskMemPtr.hpp
../libdshowcapture/source/log.hpp
../libdshowcapture/source/dshow-base.hpp
../libdshowcapture/source/dshow-enum.hpp
../libdshowcapture/source/dshow-formats.hpp
../libdshowcapture/source/dshow-media-type.hpp
../libdshowcapture/source/output-filter.hpp
../libdshowcapture/dshowcapture.hpp
)
set(obs-virtualcam-module_SOURCES
"${CMAKE_CURRENT_BINARY_DIR}/virtualcam-module.def"
sleepto.c
placeholder.cpp
virtualcam-module.cpp
virtualcam-filter.cpp
../shared-memory-queue.c
../tiny-nv12-scale.c
)
set(obs-virtualcam-module_HEADERS
sleepto.h
virtualcam-filter.hpp
../shared-memory-queue.h
../tiny-nv12-scale.h
)
if(MSVC)
add_compile_options("$<IF:$<CONFIG:Debug>,/MTd,/MT>")
endif()
include_directories(${CMAKE_SOURCE_DIR}/libobs/util)
source_group("libdshowcapture\\Source Files" FILES ${libdshowcapture_SOURCES})
source_group("libdshowcapture\\Header Files" FILES ${libdshowcapture_HEADERS})
set(CMAKE_MODULE_LINKER_FLAGS "${MAKE_MODULE_LINKER_FLAGS} /ignore:4104")
add_library(obs-virtualcam-module MODULE
${libdshowcapture_SOURCES}
${libdshowcapture_HEADERS}
${obs-virtualcam-module_SOURCES}
${obs-virtualcam-module_HEADERS})
target_link_libraries(obs-virtualcam-module
winmm
strmiids
gdiplus
)
set_target_properties(obs-virtualcam-module PROPERTIES FOLDER "plugins/win-dshow")
set_target_properties(obs-virtualcam-module
PROPERTIES
OUTPUT_NAME "obs-virtualcam-module${_output_suffix}")
install_obs_datatarget(obs-virtualcam-module "obs-plugins/win-dshow")
#include <windows.h>
#include <strsafe.h>
#include <gdiplus.h>
#include <stdint.h>
#include <vector>
using namespace Gdiplus;
extern HINSTANCE dll_inst;
static std::vector<uint8_t> placeholder;
/* XXX: optimize this later. or don't, it's only called once. */
static void convert_placeholder(const uint8_t *rgb_in, int width, int height)
{
size_t size = width * height * 3;
size_t linesize = width * 3;
std::vector<uint8_t> yuv_out;
yuv_out.resize(size);
const uint8_t *in = rgb_in;
const uint8_t *end = in + size;
uint8_t *out = &yuv_out[0];
while (in < end) {
const int16_t b = *(in++);
const int16_t g = *(in++);
const int16_t r = *(in++);
*(out++) = (uint8_t)(((66 * r + 129 * g + 25 * b + 128) >> 8) +
16);
*(out++) = (uint8_t)(((-38 * r - 74 * g + 112 * b + 128) >> 8) +
128);
*(out++) = (uint8_t)(((112 * r - 94 * g - 18 * b + 128) >> 8) +
128);
}
placeholder.resize(width * height * 3 / 2);
in = &yuv_out[0];
end = in + size;
out = &placeholder[0];
uint8_t *chroma = out + width * height;
while (in < end) {
const uint8_t *in2 = in + linesize;
const uint8_t *end2 = in2;
uint8_t *out2 = out + width;
while (in < end2) {
int16_t u;
int16_t v;
*(out++) = *(in++);
u = *(in++);
v = *(in++);
*(out++) = *(in++);
u += *(in++);
v += *(in++);
*(out2++) = *(in2++);
u += *(in2++);
v += *(in2++);
*(out2++) = *(in2++);
u += *(in2++);
v += *(in2++);
*(chroma++) = (uint8_t)(u / 4);
*(chroma++) = (uint8_t)(v / 4);
}
in = in2;
out = out2;
}
}
static bool load_placeholder_internal()
{
Status s;
wchar_t file[MAX_PATH];
if (!GetModuleFileNameW(dll_inst, file, MAX_PATH)) {
return false;
}
wchar_t *slash = wcsrchr(file, '\\');
if (!slash) {
return false;
}
slash[1] = 0;
StringCbCat(file, sizeof(file), L"placeholder.png");
Bitmap bmp(file);
if (bmp.GetLastStatus() != Status::Ok) {
return false;
}
BitmapData bmd = {};
Rect r(0, 0, bmp.GetWidth(), bmp.GetHeight());
s = bmp.LockBits(&r, ImageLockModeRead, PixelFormat24bppRGB, &bmd);
if (s != Status::Ok) {
return false;
}
convert_placeholder((const uint8_t *)bmd.Scan0, bmp.GetWidth(),
bmp.GetHeight());
bmp.UnlockBits(&bmd);
return true;
}
static bool load_placeholder()
{
GdiplusStartupInput si;
ULONG_PTR token;
GdiplusStartup(&token, &si, nullptr);
bool success = load_placeholder_internal();
GdiplusShutdown(token);
return success;
}
const uint8_t *get_placeholder()
{
static bool failed = false;
static bool initialized = false;
if (initialized) {
return placeholder.data();
} else if (failed) {
return nullptr;
}
initialized = load_placeholder();
failed = !initialized;
return initialized ? placeholder.data() : nullptr;
}
#include <windows.h>
#include <stdbool.h>
#include "sleepto.h"
static bool have_clockfreq = false;
static LARGE_INTEGER clock_freq;
static inline uint64_t get_clockfreq(void)
{
if (!have_clockfreq) {
QueryPerformanceFrequency(&clock_freq);
have_clockfreq = true;
}
return clock_freq.QuadPart;
}
uint64_t gettime_100ns(void)
{
LARGE_INTEGER current_time;
double time_val;
QueryPerformanceCounter(&current_time);
time_val = (double)current_time.QuadPart;
time_val *= 10000000.0;
time_val /= (double)get_clockfreq();
return (uint64_t)time_val;
}
bool sleepto_100ns(uint64_t time_target)
{
uint64_t t = gettime_100ns();
uint32_t milliseconds;
if (t >= time_target)
return false;
milliseconds = (uint32_t)((time_target - t) / 10000);
if (milliseconds > 1)
Sleep(milliseconds - 1);
for (;;) {
t = gettime_100ns();
if (t >= time_target)
return true;
Sleep(0);
}
}
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
extern uint64_t gettime_100ns(void);
extern bool sleepto_100ns(uint64_t time_target);
#ifdef __cplusplus
}
#endif
#include "virtualcam-filter.hpp"
#include "sleepto.h"
#include <shlobj_core.h>
#include <strsafe.h>
#include <inttypes.h>
using namespace DShow;
extern const uint8_t *get_placeholder();
/* ========================================================================= */
VCamFilter::VCamFilter()
: OutputFilter(VideoFormat::NV12, DEFAULT_CX, DEFAULT_CY,
DEFAULT_INTERVAL)
{
thread_start = CreateEvent(nullptr, true, false, nullptr);
thread_stop = CreateEvent(nullptr, true, false, nullptr);
AddVideoFormat(VideoFormat::I420, DEFAULT_CX, DEFAULT_CY,
DEFAULT_INTERVAL);
/* ---------------------------------------- */
/* load placeholder image */
placeholder = get_placeholder();
/* ---------------------------------------- */
/* detect if this filter is within obs */
wchar_t file[MAX_PATH];
if (!GetModuleFileNameW(nullptr, file, MAX_PATH)) {
file[0] = 0;
}
#ifdef _WIN64
const wchar_t *obs_process = L"obs64.exe";
#else
const wchar_t *obs_process = L"obs32.exe";
#endif
in_obs = !!wcsstr(file, obs_process);
/* ---------------------------------------- */
/* add last/current obs res/interval */
uint32_t new_cx = cx;
uint32_t new_cy = cy;
uint64_t new_interval = interval;
vq = video_queue_open();
if (vq) {
if (video_queue_state(vq) == SHARED_QUEUE_STATE_READY) {
video_queue_get_info(vq, &new_cx, &new_cy,
&new_interval);
}
/* don't keep it open until the filter actually starts */
video_queue_close(vq);
vq = nullptr;
} else {
wchar_t res_file[MAX_PATH];
SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr,
SHGFP_TYPE_CURRENT, res_file);
StringCbCat(res_file, sizeof(res_file),
L"\\obs-virtualcam.txt");
HANDLE file = CreateFileW(res_file, GENERIC_READ, 0, nullptr,
OPEN_EXISTING, 0, nullptr);
if (file) {
char res[128];
DWORD len = 0;
ReadFile(file, res, sizeof(res), &len, nullptr);
CloseHandle(file);
res[len] = 0;
int vals = sscanf(res,
"%" PRIu32 "x%" PRIu32 "x%" PRIu64,
&new_cx, &new_cy, &new_interval);
if (vals != 3) {
new_cx = cx;
new_cy = cy;
new_interval = interval;
}
}
}
if (new_cx != cx || new_cy != cy || new_interval != interval) {
AddVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
AddVideoFormat(VideoFormat::I420, new_cx, new_cy, new_interval);
SetVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
cx = new_cx;
cy = new_cy;
interval = new_interval;
}
nv12_scale_init(&scaler, false, cx, cy, cx, cy);
/* ---------------------------------------- */
th = std::thread([this] { Thread(); });
AddRef();
}
VCamFilter::~VCamFilter()
{
SetEvent(thread_stop);
th.join();
video_queue_close(vq);
}
const wchar_t *VCamFilter::FilterName() const
{
return L"VCamFilter";
}
STDMETHODIMP VCamFilter::Pause()
{
HRESULT hr;
hr = OutputFilter::Pause();
if (FAILED(hr)) {
return hr;
}
SetEvent(thread_start);
return S_OK;
}
inline uint64_t VCamFilter::GetTime()
{
if (!!clock) {
REFERENCE_TIME rt;
HRESULT hr = clock->GetTime(&rt);
if (SUCCEEDED(hr)) {
return (uint64_t)rt;
}
}
return gettime_100ns();
}
void VCamFilter::Thread()
{
HANDLE h[2] = {thread_start, thread_stop};
DWORD ret = WaitForMultipleObjects(2, h, false, INFINITE);
if (ret != WAIT_OBJECT_0)
return;
uint64_t cur_time = gettime_100ns();
uint64_t filter_time = GetTime();
cx = GetCX();
cy = GetCY();
interval = GetInterval();
nv12_scale_init(&scaler, false, GetCX(), GetCY(), cx, cy);
while (!stopped()) {
Frame(filter_time);
sleepto_100ns(cur_time += interval);
filter_time += interval;
}
}
void VCamFilter::Frame(uint64_t ts)
{
uint32_t new_cx = cx;
uint32_t new_cy = cy;
uint64_t new_interval = interval;
if (!vq) {
vq = video_queue_open();
}
enum queue_state state = video_queue_state(vq);
if (state != prev_state) {
if (state == SHARED_QUEUE_STATE_READY) {
video_queue_get_info(vq, &new_cx, &new_cy,
&new_interval);
} else if (state == SHARED_QUEUE_STATE_STOPPING) {
video_queue_close(vq);
vq = nullptr;
}
prev_state = state;
}
if (state != SHARED_QUEUE_STATE_READY) {
new_cx = DEFAULT_CX;
new_cy = DEFAULT_CY;
new_interval = DEFAULT_INTERVAL;
}
if (new_cx != cx || new_cy != cy || new_interval != interval) {
if (in_obs) {
SetVideoFormat(GetVideoFormat(), new_cx, new_cy,
new_interval);
}
nv12_scale_init(&scaler, false, GetCX(), GetCY(), new_cx,
new_cy);
cx = new_cx;
cy = new_cy;
interval = new_interval;
}
scaler.convert_to_i420 = GetVideoFormat() == VideoFormat::I420;
uint8_t *ptr;
if (LockSampleData(&ptr)) {
if (state == SHARED_QUEUE_STATE_READY)
ShowOBSFrame(ptr);
else
ShowDefaultFrame(ptr);
UnlockSampleData(ts, ts + interval);
}
}
void VCamFilter::ShowOBSFrame(uint8_t *ptr)
{
uint64_t temp;
if (!video_queue_read(vq, &scaler, ptr, &temp)) {
video_queue_close(vq);
vq = nullptr;
}
}
void VCamFilter::ShowDefaultFrame(uint8_t *ptr)
{
if (placeholder) {
nv12_do_scale(&scaler, ptr, placeholder);
} else {
memset(ptr, 127, GetCX() * GetCY() * 3 / 2);
}
}
#pragma once
#include <windows.h>
#include <cstdint>
#include <thread>
#include "../shared-memory-queue.h"
#include "../tiny-nv12-scale.h"
#include "../libdshowcapture/source/output-filter.hpp"
#include "../../../libobs/util/windows/WinHandle.hpp"
#define DEFAULT_CX 1920
#define DEFAULT_CY 1080
#define DEFAULT_INTERVAL 333333ULL
class VCamFilter : public DShow::OutputFilter {
std::thread th;
video_queue_t *vq = nullptr;
int queue_mode = 0;
bool in_obs = false;
enum queue_state prev_state = SHARED_QUEUE_STATE_INVALID;
const uint8_t *placeholder;
uint32_t cx = DEFAULT_CX;
uint32_t cy = DEFAULT_CY;
uint64_t interval = DEFAULT_INTERVAL;
WinHandle thread_start;
WinHandle thread_stop;
nv12_scale_t scaler = {};
inline bool stopped() const
{
return WaitForSingleObject(thread_stop, 0) != WAIT_TIMEOUT;
}
inline uint64_t GetTime();
void Thread();
void Frame(uint64_t ts);
void ShowOBSFrame(uint8_t *ptr);
void ShowDefaultFrame(uint8_t *ptr);
protected:
const wchar_t *FilterName() const override;
public:
VCamFilter();
~VCamFilter() override;
STDMETHODIMP Pause() override;
};
#include "virtualcam-filter.hpp"
#include "../virtualcam-guid.h"
/* ========================================================================= */
static const REGPINTYPES AMSMediaTypesV = {&MEDIATYPE_Video,
&MEDIASUBTYPE_NV12};
static const REGFILTERPINS AMSPinVideo = {L"Output", false, true,
false, false, &CLSID_NULL,
nullptr, 1, &AMSMediaTypesV};
HINSTANCE dll_inst = nullptr;
static volatile long locks = 0;
/* ========================================================================= */
class VCamFactory : public IClassFactory {
volatile long refs = 1;
CLSID cls;
public:
inline VCamFactory(CLSID cls_) : cls(cls_) {}
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **p_ptr);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IClassFactory
STDMETHODIMP CreateInstance(LPUNKNOWN parent, REFIID riid,
void **p_ptr);
STDMETHODIMP LockServer(BOOL lock);
};
STDMETHODIMP VCamFactory::QueryInterface(REFIID riid, void **p_ptr)
{
if (!p_ptr) {
return E_POINTER;
}
if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) {
AddRef();
*p_ptr = (void *)this;
return S_OK;
} else {
*p_ptr = nullptr;
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) VCamFactory::AddRef()
{
return InterlockedIncrement(&refs);
}
STDMETHODIMP_(ULONG) VCamFactory::Release()
{
long new_refs = InterlockedDecrement(&refs);
if (new_refs == 0) {
delete this;
return 0;
}
return (ULONG)new_refs;
}
STDMETHODIMP VCamFactory::CreateInstance(LPUNKNOWN parent, REFIID, void **p_ptr)
{
if (!p_ptr) {
return E_POINTER;
}
*p_ptr = nullptr;
/* don't bother supporting the "parent" functionality */
if (parent) {
return E_NOINTERFACE;
}
if (IsEqualCLSID(cls, CLSID_OBS_VirtualVideo)) {
*p_ptr = (void *)new VCamFilter();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP VCamFactory::LockServer(BOOL lock)
{
if (lock) {
InterlockedIncrement(&locks);
} else {
InterlockedDecrement(&locks);
}
return S_OK;
}
/* ========================================================================= */
static inline DWORD string_size(const wchar_t *str)
{
return (DWORD)(wcslen(str) + 1) * sizeof(wchar_t);
}
static bool RegServer(CLSID cls, const wchar_t *desc, const wchar_t *file,
const wchar_t *model = L"Both",
const wchar_t *type = L"InprocServer32")
{
wchar_t cls_str[CHARS_IN_GUID];
wchar_t temp[MAX_PATH];
HKEY key = nullptr;
HKEY subkey = nullptr;
bool success = false;
StringFromGUID2(cls, cls_str, CHARS_IN_GUID);
StringCbPrintf(temp, sizeof(temp), L"CLSID\\%s", cls_str);
if (RegCreateKey(HKEY_CLASSES_ROOT, temp, &key) != ERROR_SUCCESS) {
goto fail;
}
RegSetValueW(key, nullptr, REG_SZ, desc, string_size(desc));
if (RegCreateKey(key, type, &subkey) != ERROR_SUCCESS) {
goto fail;
}
RegSetValueW(subkey, nullptr, REG_SZ, file, string_size(file));
RegSetValueExW(subkey, L"ThreadingModel", 0, REG_SZ,
(const BYTE *)model, string_size(model));
success = true;
fail:
if (key) {
RegCloseKey(key);
}
if (key) {
RegCloseKey(subkey);
}
return success;
}
static bool UnregServer(CLSID cls)
{
wchar_t cls_str[CHARS_IN_GUID];
wchar_t temp[MAX_PATH];
StringFromGUID2(cls, cls_str, CHARS_IN_GUID);
StringCbPrintf(temp, sizeof(temp), L"CLSID\\%s", cls_str);
return RegDeleteTreeW(HKEY_CLASSES_ROOT, temp) == ERROR_SUCCESS;
}
static bool RegServers(bool reg)
{
wchar_t file[MAX_PATH];
if (!GetModuleFileNameW(dll_inst, file, MAX_PATH)) {
return false;
}
if (reg) {
return RegServer(CLSID_OBS_VirtualVideo, L"OBS Virtual Camera",
file);
} else {
return UnregServer(CLSID_OBS_VirtualVideo);
}
}
static bool RegFilters(bool reg)
{
ComPtr<IFilterMapper2> fm;
HRESULT hr;
hr = CoCreateInstance(CLSID_FilterMapper2, nullptr,
CLSCTX_INPROC_SERVER, IID_IFilterMapper2,
(void **)&fm);
if (FAILED(hr)) {
return false;
}
if (reg) {
ComPtr<IMoniker> moniker;
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 1;
rf2.rgPins = &AMSPinVideo;
hr = fm->RegisterFilter(CLSID_OBS_VirtualVideo,
L"OBS Video Output", &moniker,
&CLSID_VideoInputDeviceCategory,
nullptr, &rf2);
if (FAILED(hr)) {
return false;
}
} else {
hr = fm->UnregisterFilter(&CLSID_VideoInputDeviceCategory, 0,
CLSID_OBS_VirtualVideo);
if (FAILED(hr)) {
return false;
}
}
return true;
}
/* ========================================================================= */
STDAPI DllRegisterServer()
{
if (!RegServers(true)) {
RegServers(false);
return E_FAIL;
}
CoInitialize(0);
if (!RegFilters(true)) {
RegFilters(false);
RegServers(false);
CoUninitialize();
return E_FAIL;
}
CoUninitialize();
return S_OK;
}
STDAPI DllUnregisterServer()
{
CoInitialize(0);
RegFilters(false);
RegServers(false);
CoUninitialize();
return S_OK;
}
STDAPI DllInstall(BOOL install, LPCWSTR)
{
if (!install) {
return DllUnregisterServer();
} else {
return DllRegisterServer();
}
}
STDAPI DllCanUnloadNow()
{
return InterlockedOr(&locks, 0) == 0 ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(REFCLSID cls, REFIID riid, void **p_ptr)
{
if (!p_ptr) {
return E_POINTER;
}
*p_ptr = nullptr;
if (riid != IID_IClassFactory && riid != IID_IUnknown) {
return E_NOINTERFACE;
}
if (!IsEqualCLSID(cls, CLSID_OBS_VirtualVideo)) {
return E_INVALIDARG;
}
*p_ptr = (void *)new VCamFactory(cls);
return S_OK;
}
//#define ENABLE_LOGGING
#ifdef ENABLE_LOGGING
void logcallback(DShow::LogType, const wchar_t *msg, void *)
{
OutputDebugStringW(msg);
OutputDebugStringW(L"\n");
}
#endif
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(inst);
#ifdef ENABLE_LOGGING
DShow::SetLogCallback(logcallback, nullptr);
#endif
dll_inst = inst;
}
return true;
}
LIBRARY obs-virtualcam-module@_output_suffix@.dll
EXPORTS
DllMain PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllInstall PRIVATE
#include <obs-module.h>
#include <util/platform.h>
#include "shared-memory-queue.h"
struct virtualcam_data {
obs_output_t *output;
video_queue_t *vq;
};
static const char *virtualcam_name(void *unused)
{
UNUSED_PARAMETER(unused);
return "Virtual Camera Output";
}
static void virtualcam_destroy(void *data)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
video_queue_close(vcam->vq);
bfree(data);
}
static void *virtualcam_create(obs_data_t *settings, obs_output_t *output)
{
struct virtualcam_data *vcam =
(struct virtualcam_data *)bzalloc(sizeof(*vcam));
vcam->output = output;
UNUSED_PARAMETER(settings);
return vcam;
}
static bool virtualcam_start(void *data)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
uint32_t width = obs_output_get_width(vcam->output);
uint32_t height = obs_output_get_height(vcam->output);
struct obs_video_info ovi;
obs_get_video_info(&ovi);
uint64_t interval = ovi.fps_den * 10000000ULL / ovi.fps_num;
char res[64];
snprintf(res, sizeof(res), "%dx%dx%lld", (int)width, (int)height,
(long long)interval);
char *res_file = os_get_config_path_ptr("obs-virtualcam.txt");
os_quick_write_utf8_file_safe(res_file, res, strlen(res), false, "tmp",
NULL);
bfree(res_file);
vcam->vq = video_queue_create(width, height, interval);
if (!vcam->vq) {
blog(LOG_WARNING, "starting virtual-output failed");
return false;
}
struct video_scale_info vsi = {0};
vsi.format = VIDEO_FORMAT_NV12;
vsi.width = width;
vsi.height = height;
obs_output_set_video_conversion(vcam->output, &vsi);
blog(LOG_INFO, "Virtual output started");
obs_output_begin_data_capture(vcam->output, 0);
return true;
}
static void virtualcam_stop(void *data, uint64_t ts)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)data;
obs_output_end_data_capture(vcam->output);
video_queue_close(vcam->vq);
vcam->vq = NULL;
blog(LOG_INFO, "Virtual output stopped");
UNUSED_PARAMETER(ts);
}
static void virtual_video(void *param, struct video_data *frame)
{
struct virtualcam_data *vcam = (struct virtualcam_data *)param;
if (!vcam->vq)
return;
video_queue_write(vcam->vq, frame->data, frame->linesize,
frame->timestamp);
}
struct obs_output_info virtualcam_info = {
.id = "virtualcam_output",
.flags = OBS_OUTPUT_VIDEO,
.get_name = virtualcam_name,
.create = virtualcam_create,
.destroy = virtualcam_destroy,
.start = virtualcam_start,
.stop = virtualcam_stop,
.raw_video = virtual_video,
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册