virtualcam-filter.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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 const uint8_t *get_placeholder();
  8. /* ========================================================================= */
  9. VCamFilter::VCamFilter()
  10. : OutputFilter(VideoFormat::NV12, DEFAULT_CX, DEFAULT_CY,
  11. DEFAULT_INTERVAL)
  12. {
  13. thread_start = CreateEvent(nullptr, true, false, nullptr);
  14. thread_stop = CreateEvent(nullptr, true, false, nullptr);
  15. AddVideoFormat(VideoFormat::I420, DEFAULT_CX, DEFAULT_CY,
  16. DEFAULT_INTERVAL);
  17. /* ---------------------------------------- */
  18. /* load placeholder image */
  19. placeholder = get_placeholder();
  20. /* ---------------------------------------- */
  21. /* detect if this filter is within obs */
  22. wchar_t file[MAX_PATH];
  23. if (!GetModuleFileNameW(nullptr, file, MAX_PATH)) {
  24. file[0] = 0;
  25. }
  26. #ifdef _WIN64
  27. const wchar_t *obs_process = L"obs64.exe";
  28. #else
  29. const wchar_t *obs_process = L"obs32.exe";
  30. #endif
  31. in_obs = !!wcsstr(file, obs_process);
  32. /* ---------------------------------------- */
  33. /* add last/current obs res/interval */
  34. uint32_t new_cx = cx;
  35. uint32_t new_cy = cy;
  36. uint64_t new_interval = interval;
  37. vq = video_queue_open();
  38. if (vq) {
  39. if (video_queue_state(vq) == SHARED_QUEUE_STATE_READY) {
  40. video_queue_get_info(vq, &new_cx, &new_cy,
  41. &new_interval);
  42. }
  43. /* don't keep it open until the filter actually starts */
  44. video_queue_close(vq);
  45. vq = nullptr;
  46. } else {
  47. wchar_t res_file[MAX_PATH];
  48. SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr,
  49. SHGFP_TYPE_CURRENT, res_file);
  50. StringCbCat(res_file, sizeof(res_file),
  51. L"\\obs-virtualcam.txt");
  52. HANDLE file = CreateFileW(res_file, GENERIC_READ, 0, nullptr,
  53. OPEN_EXISTING, 0, nullptr);
  54. if (file) {
  55. char res[128];
  56. DWORD len = 0;
  57. ReadFile(file, res, sizeof(res), &len, nullptr);
  58. CloseHandle(file);
  59. res[len] = 0;
  60. int vals = sscanf(res,
  61. "%" PRIu32 "x%" PRIu32 "x%" PRIu64,
  62. &new_cx, &new_cy, &new_interval);
  63. if (vals != 3) {
  64. new_cx = cx;
  65. new_cy = cy;
  66. new_interval = interval;
  67. }
  68. }
  69. }
  70. if (new_cx != cx || new_cy != cy || new_interval != interval) {
  71. AddVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
  72. AddVideoFormat(VideoFormat::I420, new_cx, new_cy, new_interval);
  73. SetVideoFormat(VideoFormat::NV12, new_cx, new_cy, new_interval);
  74. cx = new_cx;
  75. cy = new_cy;
  76. interval = new_interval;
  77. }
  78. nv12_scale_init(&scaler, false, cx, cy, cx, cy);
  79. /* ---------------------------------------- */
  80. th = std::thread([this] { Thread(); });
  81. AddRef();
  82. }
  83. VCamFilter::~VCamFilter()
  84. {
  85. SetEvent(thread_stop);
  86. th.join();
  87. video_queue_close(vq);
  88. }
  89. const wchar_t *VCamFilter::FilterName() const
  90. {
  91. return L"VCamFilter";
  92. }
  93. STDMETHODIMP VCamFilter::Pause()
  94. {
  95. HRESULT hr;
  96. hr = OutputFilter::Pause();
  97. if (FAILED(hr)) {
  98. return hr;
  99. }
  100. SetEvent(thread_start);
  101. return S_OK;
  102. }
  103. inline uint64_t VCamFilter::GetTime()
  104. {
  105. if (!!clock) {
  106. REFERENCE_TIME rt;
  107. HRESULT hr = clock->GetTime(&rt);
  108. if (SUCCEEDED(hr)) {
  109. return (uint64_t)rt;
  110. }
  111. }
  112. return gettime_100ns();
  113. }
  114. void VCamFilter::Thread()
  115. {
  116. HANDLE h[2] = {thread_start, thread_stop};
  117. DWORD ret = WaitForMultipleObjects(2, h, false, INFINITE);
  118. if (ret != WAIT_OBJECT_0)
  119. return;
  120. uint64_t cur_time = gettime_100ns();
  121. uint64_t filter_time = GetTime();
  122. cx = GetCX();
  123. cy = GetCY();
  124. interval = GetInterval();
  125. nv12_scale_init(&scaler, false, GetCX(), GetCY(), cx, cy);
  126. while (!stopped()) {
  127. Frame(filter_time);
  128. sleepto_100ns(cur_time += interval);
  129. filter_time += interval;
  130. }
  131. }
  132. void VCamFilter::Frame(uint64_t ts)
  133. {
  134. uint32_t new_cx = cx;
  135. uint32_t new_cy = cy;
  136. uint64_t new_interval = interval;
  137. if (!vq) {
  138. vq = video_queue_open();
  139. }
  140. enum queue_state state = video_queue_state(vq);
  141. if (state != prev_state) {
  142. if (state == SHARED_QUEUE_STATE_READY) {
  143. video_queue_get_info(vq, &new_cx, &new_cy,
  144. &new_interval);
  145. } else if (state == SHARED_QUEUE_STATE_STOPPING) {
  146. video_queue_close(vq);
  147. vq = nullptr;
  148. }
  149. prev_state = state;
  150. }
  151. if (state != SHARED_QUEUE_STATE_READY) {
  152. new_cx = DEFAULT_CX;
  153. new_cy = DEFAULT_CY;
  154. new_interval = DEFAULT_INTERVAL;
  155. }
  156. if (new_cx != cx || new_cy != cy || new_interval != interval) {
  157. if (in_obs) {
  158. SetVideoFormat(GetVideoFormat(), new_cx, new_cy,
  159. new_interval);
  160. }
  161. nv12_scale_init(&scaler, false, GetCX(), GetCY(), new_cx,
  162. new_cy);
  163. cx = new_cx;
  164. cy = new_cy;
  165. interval = new_interval;
  166. }
  167. scaler.convert_to_i420 = GetVideoFormat() == VideoFormat::I420;
  168. uint8_t *ptr;
  169. if (LockSampleData(&ptr)) {
  170. if (state == SHARED_QUEUE_STATE_READY)
  171. ShowOBSFrame(ptr);
  172. else
  173. ShowDefaultFrame(ptr);
  174. UnlockSampleData(ts, ts + interval);
  175. }
  176. }
  177. void VCamFilter::ShowOBSFrame(uint8_t *ptr)
  178. {
  179. uint64_t temp;
  180. if (!video_queue_read(vq, &scaler, ptr, &temp)) {
  181. video_queue_close(vq);
  182. vq = nullptr;
  183. }
  184. }
  185. void VCamFilter::ShowDefaultFrame(uint8_t *ptr)
  186. {
  187. if (placeholder) {
  188. nv12_do_scale(&scaler, ptr, placeholder);
  189. } else {
  190. memset(ptr, 127, GetCX() * GetCY() * 3 / 2);
  191. }
  192. }