| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- #include "virtualcam-filter.hpp"
- #include "sleepto.h"
- #include <shlobj_core.h>
- #include <strsafe.h>
- #include <inttypes.h>
- using namespace DShow;
- extern bool initialize_placeholder();
- extern const uint8_t *get_placeholder_ptr();
- extern const bool get_placeholder_size(int *out_cx, int *out_cy);
- /* ========================================================================= */
- 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);
- AddVideoFormat(VideoFormat::YUY2, DEFAULT_CX, DEFAULT_CY,
- DEFAULT_INTERVAL);
- format = VideoFormat::NV12;
- placeholder.scaled_data = nullptr;
- /* ---------------------------------------- */
- /* 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;
- if (ReadFile(file, res, sizeof(res) - 1, &len,
- nullptr)) {
- 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;
- }
- }
- CloseHandle(file);
- }
- }
- 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);
- AddVideoFormat(VideoFormat::YUY2, new_cx, new_cy, new_interval);
- SetVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
- cx = new_cx;
- cy = new_cy;
- interval = new_interval;
- }
- /* ---------------------------------------- */
- th = std::thread([this] { Thread(); });
- AddRef();
- }
- VCamFilter::~VCamFilter()
- {
- SetEvent(thread_stop);
- th.join();
- video_queue_close(vq);
- if (placeholder.scaled_data)
- free(placeholder.scaled_data);
- }
- 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();
- /* ---------------------------------------- */
- /* load placeholder image */
- if (initialize_placeholder()) {
- placeholder.source_data = get_placeholder_ptr();
- get_placeholder_size(&placeholder.cx, &placeholder.cy);
- } else {
- placeholder.source_data = nullptr;
- }
- /* Created dynamically based on output resolution changes */
- placeholder.scaled_data = nullptr;
- nv12_scale_init(&scaler, TARGET_FORMAT_NV12, cx, cy, cx, cy);
- nv12_scale_init(&placeholder.scaler, TARGET_FORMAT_NV12, cx, cy,
- placeholder.cx, placeholder.cy);
- UpdatePlaceholder();
- 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;
- /* cx, cy and interval are the resolution and frame rate of the
- virtual camera _source_, ie OBS' output. Do not confuse cx / cy
- with GetCX() / GetCY() / GetInterval() which return the virtualcam
- filter output! */
- if (!vq) {
- vq = video_queue_open();
- }
- enum queue_state state = video_queue_state(vq);
- if (state != prev_state) {
- if (state == SHARED_QUEUE_STATE_READY) {
- /* The virtualcam output from OBS has started, get
- the actual cx / cy of the data stream */
- 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) {
- /* Virtualcam output not yet started, assume it's
- the same resolution as the filter output */
- new_cx = GetCX();
- new_cy = GetCY();
- new_interval = GetInterval();
- }
- if (new_cx != cx || new_cy != cy || new_interval != interval) {
- /* The res / FPS of the video coming from OBS has
- changed, update parameters as needed */
- if (in_obs) {
- /* If the vcam is being used inside obs, adjust
- the format we present to match */
- SetVideoFormat(GetVideoFormat(), new_cx, new_cy,
- new_interval);
- }
- /* Re-initialize the main scaler to use the new resolution */
- nv12_scale_init(&scaler, scaler.format, GetCX(), GetCY(),
- new_cx, new_cy);
- cx = new_cx;
- cy = new_cy;
- interval = new_interval;
- UpdatePlaceholder();
- }
- VideoFormat current_format = GetVideoFormat();
- if (current_format != format) {
- /* The output format changed, update the scalers */
- if (current_format == VideoFormat::I420)
- scaler.format = placeholder.scaler.format =
- TARGET_FORMAT_I420;
- else if (current_format == VideoFormat::YUY2)
- scaler.format = placeholder.scaler.format =
- TARGET_FORMAT_YUY2;
- else
- scaler.format = placeholder.scaler.format =
- TARGET_FORMAT_NV12;
- format = current_format;
- UpdatePlaceholder();
- }
- /* Actual output */
- 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.scaled_data) {
- memcpy(ptr, placeholder.scaled_data, GetOutputBufferSize());
- } else {
- memset(ptr, 127, GetOutputBufferSize());
- }
- }
- /* Called when the output resolution or format has changed to re-scale
- the placeholder graphic into the placeholder.scaled_data buffer. */
- void VCamFilter::UpdatePlaceholder(void)
- {
- if (!placeholder.source_data)
- return;
- if (placeholder.scaled_data)
- free(placeholder.scaled_data);
- placeholder.scaled_data = (uint8_t *)malloc(GetOutputBufferSize());
- if (!placeholder.scaled_data)
- return;
- if (placeholder.cx == GetCX() && placeholder.cy == GetCY() &&
- placeholder.scaler.format == TARGET_FORMAT_NV12) {
- /* No scaling necessary if it matches exactly */
- memcpy(placeholder.scaled_data, placeholder.source_data,
- GetOutputBufferSize());
- } else {
- nv12_scale_init(&placeholder.scaler, placeholder.scaler.format,
- GetCX(), GetCY(), placeholder.cx,
- placeholder.cy);
- nv12_do_scale(&placeholder.scaler, placeholder.scaled_data,
- placeholder.source_data);
- }
- }
- /* Calculate the size of the output buffer based on the filter's
- resolution and format */
- const int VCamFilter::GetOutputBufferSize(void)
- {
- int bits = VFormatBits(format);
- return GetCX() * GetCY() * bits / 8;
- }
|