virtualcam-filter.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #include "virtualcam-filter.hpp"
  2. #include "sleepto.h"
  3. #include <shlobj_core.h>
  4. #include <strsafe.h>
  5. #include <inttypes.h>
  6. using namespace DShow;
  7. extern bool initialize_placeholder();
  8. extern const uint8_t *get_placeholder_ptr();
  9. extern const bool get_placeholder_size(int *out_cx, int *out_cy);
  10. extern volatile long locks;
  11. /* ========================================================================= */
  12. VCamFilter::VCamFilter()
  13. : OutputFilter(VideoFormat::NV12, DEFAULT_CX, DEFAULT_CY,
  14. DEFAULT_INTERVAL)
  15. {
  16. thread_start = CreateEvent(nullptr, true, false, nullptr);
  17. thread_stop = CreateEvent(nullptr, true, false, nullptr);
  18. AddVideoFormat(VideoFormat::I420, DEFAULT_CX, DEFAULT_CY,
  19. DEFAULT_INTERVAL);
  20. AddVideoFormat(VideoFormat::YUY2, DEFAULT_CX, DEFAULT_CY,
  21. DEFAULT_INTERVAL);
  22. format = VideoFormat::NV12;
  23. placeholder.scaled_data = nullptr;
  24. /* ---------------------------------------- */
  25. /* detect if this filter is within obs */
  26. wchar_t file[MAX_PATH];
  27. if (!GetModuleFileNameW(nullptr, file, MAX_PATH)) {
  28. file[0] = 0;
  29. }
  30. #ifdef _WIN64
  31. const wchar_t *obs_process = L"obs64.exe";
  32. #else
  33. const wchar_t *obs_process = L"obs32.exe";
  34. #endif
  35. in_obs = !!wcsstr(file, obs_process);
  36. /* ---------------------------------------- */
  37. /* add last/current obs res/interval */
  38. uint32_t new_cx = cx;
  39. uint32_t new_cy = cy;
  40. uint64_t new_interval = interval;
  41. vq = video_queue_open();
  42. if (vq) {
  43. if (video_queue_state(vq) == SHARED_QUEUE_STATE_READY) {
  44. video_queue_get_info(vq, &new_cx, &new_cy,
  45. &new_interval);
  46. }
  47. /* don't keep it open until the filter actually starts */
  48. video_queue_close(vq);
  49. vq = nullptr;
  50. } else {
  51. wchar_t res_file[MAX_PATH];
  52. SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr,
  53. SHGFP_TYPE_CURRENT, res_file);
  54. StringCbCat(res_file, sizeof(res_file),
  55. L"\\obs-virtualcam.txt");
  56. HANDLE file = CreateFileW(res_file, GENERIC_READ, 0, nullptr,
  57. OPEN_EXISTING, 0, nullptr);
  58. if (file) {
  59. char res[128];
  60. DWORD len = 0;
  61. if (ReadFile(file, res, sizeof(res) - 1, &len,
  62. nullptr)) {
  63. res[len] = 0;
  64. int vals = sscanf(
  65. res, "%" PRIu32 "x%" PRIu32 "x%" PRIu64,
  66. &new_cx, &new_cy, &new_interval);
  67. if (vals != 3) {
  68. new_cx = cx;
  69. new_cy = cy;
  70. new_interval = interval;
  71. }
  72. }
  73. CloseHandle(file);
  74. }
  75. }
  76. if (new_cx != cx || new_cy != cy || new_interval != interval) {
  77. AddVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
  78. AddVideoFormat(VideoFormat::I420, new_cx, new_cy, new_interval);
  79. AddVideoFormat(VideoFormat::YUY2, new_cx, new_cy, new_interval);
  80. SetVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
  81. cx = new_cx;
  82. cy = new_cy;
  83. interval = new_interval;
  84. }
  85. /* ---------------------------------------- */
  86. th = std::thread([this] { Thread(); });
  87. AddRef();
  88. os_atomic_inc_long(&locks);
  89. }
  90. VCamFilter::~VCamFilter()
  91. {
  92. SetEvent(thread_stop);
  93. if (th.joinable())
  94. th.join();
  95. video_queue_close(vq);
  96. if (placeholder.scaled_data)
  97. free(placeholder.scaled_data);
  98. os_atomic_dec_long(&locks);
  99. }
  100. const wchar_t *VCamFilter::FilterName() const
  101. {
  102. return L"VCamFilter";
  103. }
  104. STDMETHODIMP VCamFilter::Pause()
  105. {
  106. HRESULT hr;
  107. hr = OutputFilter::Pause();
  108. if (FAILED(hr)) {
  109. return hr;
  110. }
  111. os_atomic_set_bool(&active, true);
  112. SetEvent(thread_start);
  113. return S_OK;
  114. }
  115. STDMETHODIMP VCamFilter::Run(REFERENCE_TIME tStart)
  116. {
  117. os_atomic_set_bool(&active, true);
  118. return OutputFilter::Run(tStart);
  119. }
  120. STDMETHODIMP VCamFilter::Stop()
  121. {
  122. os_atomic_set_bool(&active, false);
  123. return OutputFilter::Stop();
  124. }
  125. inline uint64_t VCamFilter::GetTime()
  126. {
  127. if (!!clock) {
  128. REFERENCE_TIME rt;
  129. HRESULT hr = clock->GetTime(&rt);
  130. if (SUCCEEDED(hr)) {
  131. return (uint64_t)rt;
  132. }
  133. }
  134. return gettime_100ns();
  135. }
  136. void VCamFilter::Thread()
  137. {
  138. HANDLE h[2] = {thread_start, thread_stop};
  139. DWORD ret = WaitForMultipleObjects(2, h, false, INFINITE);
  140. if (ret != WAIT_OBJECT_0)
  141. return;
  142. uint64_t cur_time = gettime_100ns();
  143. uint64_t filter_time = GetTime();
  144. cx = GetCX();
  145. cy = GetCY();
  146. interval = GetInterval();
  147. /* ---------------------------------------- */
  148. /* load placeholder image */
  149. if (initialize_placeholder()) {
  150. placeholder.source_data = get_placeholder_ptr();
  151. get_placeholder_size(&placeholder.cx, &placeholder.cy);
  152. } else {
  153. placeholder.source_data = nullptr;
  154. }
  155. /* Created dynamically based on output resolution changes */
  156. placeholder.scaled_data = nullptr;
  157. nv12_scale_init(&scaler, TARGET_FORMAT_NV12, cx, cy, cx, cy);
  158. nv12_scale_init(&placeholder.scaler, TARGET_FORMAT_NV12, cx, cy,
  159. placeholder.cx, placeholder.cy);
  160. UpdatePlaceholder();
  161. while (!stopped()) {
  162. if (os_atomic_load_bool(&active))
  163. Frame(filter_time);
  164. sleepto_100ns(cur_time += interval);
  165. filter_time += interval;
  166. }
  167. }
  168. void VCamFilter::Frame(uint64_t ts)
  169. {
  170. uint32_t new_cx = cx;
  171. uint32_t new_cy = cy;
  172. uint64_t new_interval = interval;
  173. /* cx, cy and interval are the resolution and frame rate of the
  174. virtual camera _source_, ie OBS' output. Do not confuse cx / cy
  175. with GetCX() / GetCY() / GetInterval() which return the virtualcam
  176. filter output! */
  177. if (!vq) {
  178. vq = video_queue_open();
  179. }
  180. enum queue_state state = video_queue_state(vq);
  181. if (state != prev_state) {
  182. if (state == SHARED_QUEUE_STATE_READY) {
  183. /* The virtualcam output from OBS has started, get
  184. the actual cx / cy of the data stream */
  185. video_queue_get_info(vq, &new_cx, &new_cy,
  186. &new_interval);
  187. } else if (state == SHARED_QUEUE_STATE_STOPPING) {
  188. video_queue_close(vq);
  189. vq = nullptr;
  190. }
  191. prev_state = state;
  192. }
  193. if (state != SHARED_QUEUE_STATE_READY) {
  194. /* Virtualcam output not yet started, assume it's
  195. the same resolution as the filter output */
  196. new_cx = GetCX();
  197. new_cy = GetCY();
  198. new_interval = GetInterval();
  199. }
  200. if (new_cx != cx || new_cy != cy || new_interval != interval) {
  201. /* The res / FPS of the video coming from OBS has
  202. changed, update parameters as needed */
  203. if (in_obs) {
  204. /* If the vcam is being used inside obs, adjust
  205. the format we present to match */
  206. SetVideoFormat(GetVideoFormat(), new_cx, new_cy,
  207. new_interval);
  208. }
  209. /* Re-initialize the main scaler to use the new resolution */
  210. nv12_scale_init(&scaler, scaler.format, GetCX(), GetCY(),
  211. new_cx, new_cy);
  212. cx = new_cx;
  213. cy = new_cy;
  214. interval = new_interval;
  215. UpdatePlaceholder();
  216. }
  217. VideoFormat current_format = GetVideoFormat();
  218. if (current_format != format) {
  219. /* The output format changed, update the scalers */
  220. if (current_format == VideoFormat::I420)
  221. scaler.format = placeholder.scaler.format =
  222. TARGET_FORMAT_I420;
  223. else if (current_format == VideoFormat::YUY2)
  224. scaler.format = placeholder.scaler.format =
  225. TARGET_FORMAT_YUY2;
  226. else
  227. scaler.format = placeholder.scaler.format =
  228. TARGET_FORMAT_NV12;
  229. format = current_format;
  230. UpdatePlaceholder();
  231. }
  232. /* Actual output */
  233. uint8_t *ptr;
  234. if (LockSampleData(&ptr)) {
  235. if (state == SHARED_QUEUE_STATE_READY)
  236. ShowOBSFrame(ptr);
  237. else
  238. ShowDefaultFrame(ptr);
  239. UnlockSampleData(ts, ts + interval);
  240. }
  241. }
  242. void VCamFilter::ShowOBSFrame(uint8_t *ptr)
  243. {
  244. uint64_t temp;
  245. if (!video_queue_read(vq, &scaler, ptr, &temp)) {
  246. video_queue_close(vq);
  247. vq = nullptr;
  248. }
  249. }
  250. void VCamFilter::ShowDefaultFrame(uint8_t *ptr)
  251. {
  252. if (placeholder.scaled_data) {
  253. memcpy(ptr, placeholder.scaled_data, GetOutputBufferSize());
  254. } else {
  255. memset(ptr, 127, GetOutputBufferSize());
  256. }
  257. }
  258. /* Called when the output resolution or format has changed to re-scale
  259. the placeholder graphic into the placeholder.scaled_data buffer. */
  260. void VCamFilter::UpdatePlaceholder(void)
  261. {
  262. if (!placeholder.source_data)
  263. return;
  264. if (placeholder.scaled_data)
  265. free(placeholder.scaled_data);
  266. placeholder.scaled_data = (uint8_t *)malloc(GetOutputBufferSize());
  267. if (!placeholder.scaled_data)
  268. return;
  269. if (placeholder.cx == GetCX() && placeholder.cy == GetCY() &&
  270. placeholder.scaler.format == TARGET_FORMAT_NV12) {
  271. /* No scaling necessary if it matches exactly */
  272. memcpy(placeholder.scaled_data, placeholder.source_data,
  273. GetOutputBufferSize());
  274. } else {
  275. nv12_scale_init(&placeholder.scaler, placeholder.scaler.format,
  276. GetCX(), GetCY(), placeholder.cx,
  277. placeholder.cy);
  278. nv12_do_scale(&placeholder.scaler, placeholder.scaled_data,
  279. placeholder.source_data);
  280. }
  281. }
  282. /* Calculate the size of the output buffer based on the filter's
  283. resolution and format */
  284. const int VCamFilter::GetOutputBufferSize(void)
  285. {
  286. int bits = VFormatBits(format);
  287. return GetCX() * GetCY() * bits / 8;
  288. }