av-capture.mm 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150
  1. #import <AVFoundation/AVFoundation.h>
  2. #import <CoreFoundation/CoreFoundation.h>
  3. #import <CoreMedia/CoreMedia.h>
  4. #import <CoreVideo/CoreVideo.h>
  5. #import <CoreMediaIO/CMIOHardware.h>
  6. #include <AvailabilityMacros.h>
  7. #include <obs-module.h>
  8. #include <obs.hpp>
  9. #include <media-io/video-io.h>
  10. #include <util/dstr.hpp>
  11. #include <algorithm>
  12. #include <initializer_list>
  13. #include <cinttypes>
  14. #include <limits>
  15. #include <memory>
  16. #include <vector>
  17. #include "left-right.hpp"
  18. #include "scope-guard.hpp"
  19. #define NBSP "\xC2\xA0"
  20. using namespace std;
  21. namespace std {
  22. template<> struct default_delete<obs_data_t> {
  23. void operator()(obs_data_t *data)
  24. {
  25. obs_data_release(data);
  26. }
  27. };
  28. template<> struct default_delete<obs_data_item_t> {
  29. void operator()(obs_data_item_t *item)
  30. {
  31. obs_data_item_release(&item);
  32. }
  33. };
  34. } // namespace std
  35. #define TEXT_AVCAPTURE obs_module_text("AVCapture_Legacy")
  36. #define TEXT_DEVICE obs_module_text("Device")
  37. #define TEXT_USE_PRESET obs_module_text("UsePreset")
  38. #define TEXT_PRESET obs_module_text("Preset")
  39. #define TEXT_RESOLUTION obs_module_text("Resolution")
  40. #define TEXT_FRAME_RATE obs_module_text("FrameRate")
  41. #define TEXT_MATCH_OBS obs_module_text("MatchOBS")
  42. #define TEXT_INPUT_FORMAT obs_module_text("InputFormat")
  43. #define TEXT_COLOR_SPACE obs_module_text("ColorSpace")
  44. #define TEXT_VIDEO_RANGE obs_module_text("VideoRange")
  45. #define TEXT_RANGE_PARTIAL obs_module_text("VideoRange.Partial")
  46. #define TEXT_RANGE_FULL obs_module_text("VideoRange.Full")
  47. #define TEXT_AUTO obs_module_text("Auto")
  48. #define TEXT_COLOR_UNKNOWN_NAME "Unknown"
  49. #define TEXT_RANGE_UNKNOWN_NAME "Unknown"
  50. static const FourCharCode INPUT_FORMAT_AUTO = -1;
  51. static const int COLOR_SPACE_AUTO = -1;
  52. static const int VIDEO_RANGE_AUTO = -1;
  53. #define MILLI_TIMESCALE 1000
  54. #define MICRO_TIMESCALE (MILLI_TIMESCALE * 1000)
  55. #define NANO_TIMESCALE (MICRO_TIMESCALE * 1000)
  56. #define AV_FOURCC_STR(code) \
  57. (char[5]) \
  58. { \
  59. static_cast<char>((code >> 24) & 0xFF), static_cast<char>((code >> 16) & 0xFF), \
  60. static_cast<char>((code >> 8) & 0xFF), static_cast<char>(code & 0xFF), 0 \
  61. }
  62. struct av_capture;
  63. #define AVLOG(level, format, ...) blog(level, "%s: " format, obs_source_get_name(capture->source), ##__VA_ARGS__)
  64. @interface OBSLegacyAVCaptureDelegate
  65. : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate> {
  66. @public
  67. struct av_capture *capture;
  68. }
  69. - (void)captureOutput:(AVCaptureOutput *)out
  70. didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
  71. fromConnection:(AVCaptureConnection *)connection;
  72. - (void)captureOutput:(AVCaptureOutput *)captureOutput
  73. didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
  74. fromConnection:(AVCaptureConnection *)connection;
  75. @end
  76. namespace {
  77. static auto remove_observer = [](id observer) {
  78. [[NSNotificationCenter defaultCenter] removeObserver:observer];
  79. };
  80. struct observer_handle : unique_ptr<remove_pointer<id>::type, decltype(remove_observer)> {
  81. using base = unique_ptr<remove_pointer<id>::type, decltype(remove_observer)>;
  82. explicit observer_handle(id observer = nullptr) : base(observer, remove_observer) {}
  83. };
  84. struct av_video_info {
  85. video_colorspace colorspace;
  86. video_range_type video_range;
  87. bool video_params_valid = false;
  88. };
  89. } // namespace
  90. struct av_capture {
  91. OBSLegacyAVCaptureDelegate *delegate;
  92. dispatch_queue_t queue;
  93. dispatch_queue_t audioQueue;
  94. bool has_clock;
  95. bool device_locked;
  96. left_right::left_right<av_video_info> video_info;
  97. AVCaptureVideoDataOutput *out;
  98. AVCaptureAudioDataOutput *audioOut;
  99. AVCaptureDevice *device;
  100. AVCaptureDeviceInput *device_input;
  101. AVCaptureSession *session;
  102. NSString *uid;
  103. observer_handle connect_observer;
  104. observer_handle disconnect_observer;
  105. FourCharCode fourcc;
  106. video_format video_format;
  107. bool use_preset = false;
  108. int requested_colorspace = COLOR_SPACE_AUTO;
  109. int requested_video_range = VIDEO_RANGE_AUTO;
  110. bool enable_audio;
  111. obs_source_t *source;
  112. obs_source_frame frame;
  113. obs_source_audio audio;
  114. };
  115. static NSString *get_string(obs_data_t *data, char const *name)
  116. {
  117. return @(obs_data_get_string(data, name));
  118. }
  119. static AVCaptureDevice *get_device(obs_data_t *settings)
  120. {
  121. auto uid = get_string(settings, "device");
  122. return [AVCaptureDevice deviceWithUniqueID:uid];
  123. }
  124. template<typename T, typename U>
  125. static void clamp(T &store, U val, T low = numeric_limits<T>::min(), T high = numeric_limits<T>::max())
  126. {
  127. store = static_cast<intmax_t>(val) < static_cast<intmax_t>(low)
  128. ? low
  129. : (static_cast<intmax_t>(val) > static_cast<intmax_t>(high) ? high : static_cast<T>(val));
  130. }
  131. static bool get_resolution(obs_data_t *settings, CMVideoDimensions &dims)
  132. {
  133. using item_ptr = unique_ptr<obs_data_item_t>;
  134. item_ptr item {obs_data_item_byname(settings, "resolution")};
  135. if (!item)
  136. return false;
  137. auto res_str = obs_data_item_get_string(item.get());
  138. unique_ptr<obs_data_t> res {obs_data_create_from_json(res_str)};
  139. if (!res)
  140. return false;
  141. item_ptr width {obs_data_item_byname(res.get(), "width")};
  142. item_ptr height {obs_data_item_byname(res.get(), "height")};
  143. if (!width || !height)
  144. return false;
  145. clamp(dims.width, obs_data_item_get_int(width.get()), 0);
  146. clamp(dims.height, obs_data_item_get_int(height.get()), 0);
  147. if (!dims.width || !dims.height)
  148. return false;
  149. return true;
  150. }
  151. static bool get_input_format(obs_data_t *settings, FourCharCode &fourcc)
  152. {
  153. auto item = unique_ptr<obs_data_item_t> {obs_data_item_byname(settings, "input_format")};
  154. if (!item)
  155. return false;
  156. fourcc = static_cast<FourCharCode>(obs_data_item_get_int(item.get()));
  157. return true;
  158. }
  159. namespace {
  160. struct config_helper {
  161. obs_data_t *settings = nullptr;
  162. AVCaptureDevice *dev_ = nullptr;
  163. bool dims_valid : 1;
  164. bool fr_valid : 1;
  165. bool fps_valid : 1;
  166. bool if_valid : 1;
  167. CMVideoDimensions dims_ {};
  168. const char *frame_rate_ = nullptr;
  169. media_frames_per_second fps_ {};
  170. FourCharCode input_format_ = INPUT_FORMAT_AUTO;
  171. explicit config_helper(obs_data_t *settings) : settings(settings)
  172. {
  173. dev_ = get_device(settings);
  174. dims_valid = get_resolution(settings, dims_);
  175. fr_valid = obs_data_get_frames_per_second(settings, "frame_rate", nullptr, &frame_rate_);
  176. fps_valid = obs_data_get_frames_per_second(settings, "frame_rate", &fps_, nullptr);
  177. if_valid = get_input_format(settings, input_format_);
  178. }
  179. AVCaptureDevice *dev() const
  180. {
  181. return dev_;
  182. }
  183. const CMVideoDimensions *dims() const
  184. {
  185. return dims_valid ? &dims_ : nullptr;
  186. }
  187. const char *frame_rate() const
  188. {
  189. return fr_valid ? frame_rate_ : nullptr;
  190. }
  191. const media_frames_per_second *fps() const
  192. {
  193. return fps_valid ? &fps_ : nullptr;
  194. }
  195. const FourCharCode *input_format() const
  196. {
  197. return if_valid ? &input_format_ : nullptr;
  198. }
  199. };
  200. struct av_capture_ref {
  201. av_capture *capture = nullptr;
  202. OBSSource source;
  203. av_capture_ref() = default;
  204. av_capture_ref(av_capture *capture_, obs_weak_source_t *weak_source) : source(OBSGetStrongRef(weak_source))
  205. {
  206. if (!source)
  207. return;
  208. capture = capture_;
  209. }
  210. operator av_capture *()
  211. {
  212. return capture;
  213. }
  214. av_capture *operator->()
  215. {
  216. return capture;
  217. }
  218. };
  219. struct properties_param {
  220. av_capture *capture = nullptr;
  221. OBSWeakSource weak_source;
  222. properties_param(av_capture *capture) : capture(capture)
  223. {
  224. if (!capture)
  225. return;
  226. weak_source = OBSGetWeakRef(capture->source);
  227. }
  228. av_capture_ref get_ref()
  229. {
  230. return {capture, weak_source};
  231. }
  232. };
  233. } // namespace
  234. static av_capture_ref get_ref(obs_properties_t *props)
  235. {
  236. void *param = obs_properties_get_param(props);
  237. if (!param)
  238. return {};
  239. return static_cast<properties_param *>(param)->get_ref();
  240. }
  241. static inline video_format format_from_subtype(FourCharCode subtype)
  242. {
  243. //TODO: uncomment VIDEO_FORMAT_NV12 and VIDEO_FORMAT_ARGB once libobs
  244. // gains matching GPU conversions or a CPU fallback is implemented
  245. switch (subtype) {
  246. case kCVPixelFormatType_422YpCbCr8:
  247. return VIDEO_FORMAT_UYVY;
  248. case kCVPixelFormatType_422YpCbCr8_yuvs:
  249. return VIDEO_FORMAT_YUY2;
  250. /* case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
  251. * case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
  252. * return VIDEO_FORMAT_NV12;
  253. * case kCVPixelFormatType_32ARGB:
  254. * return VIDEO_FORMAT_ARGB;
  255. */
  256. case kCVPixelFormatType_32BGRA:
  257. return VIDEO_FORMAT_BGRA;
  258. default:
  259. return VIDEO_FORMAT_NONE;
  260. }
  261. }
  262. static const char *fourcc_subtype_name(FourCharCode fourcc);
  263. static const char *format_description_subtype_name(CMFormatDescriptionRef desc, FourCharCode *fourcc_ = nullptr)
  264. {
  265. FourCharCode fourcc = CMFormatDescriptionGetMediaSubType(desc);
  266. if (fourcc_)
  267. *fourcc_ = fourcc;
  268. return fourcc_subtype_name(fourcc);
  269. }
  270. static const char *fourcc_subtype_name(FourCharCode fourcc)
  271. {
  272. switch (fourcc) {
  273. case kCVPixelFormatType_422YpCbCr8:
  274. return "UYVY - 422YpCbCr8"; //VIDEO_FORMAT_UYVY;
  275. case kCVPixelFormatType_422YpCbCr8_yuvs:
  276. return "YUY2 - 422YpCbCr8_yuvs"; //VIDEO_FORMAT_YUY2;
  277. case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
  278. case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
  279. return "NV12 - 420YpCbCr8BiPlanar"; //VIDEO_FORMAT_NV12;
  280. case kCVPixelFormatType_32ARGB:
  281. return "ARGB - 32ARGB"; //VIDEO_FORMAT_ARGB;*/
  282. case kCVPixelFormatType_32BGRA:
  283. return "BGRA - 32BGRA"; //VIDEO_FORMAT_BGRA;
  284. case kCMVideoCodecType_Animation:
  285. return "Apple Animation";
  286. case kCMVideoCodecType_Cinepak:
  287. return "Cinepak";
  288. case kCMVideoCodecType_JPEG:
  289. return "JPEG";
  290. case kCMVideoCodecType_JPEG_OpenDML:
  291. return "MJPEG - JPEG OpenDML";
  292. case kCMVideoCodecType_SorensonVideo:
  293. return "Sorenson Video";
  294. case kCMVideoCodecType_SorensonVideo3:
  295. return "Sorenson Video 3";
  296. case kCMVideoCodecType_H263:
  297. return "H.263";
  298. case kCMVideoCodecType_H264:
  299. return "H.264";
  300. case kCMVideoCodecType_MPEG4Video:
  301. return "MPEG-4";
  302. case kCMVideoCodecType_MPEG2Video:
  303. return "MPEG-2";
  304. case kCMVideoCodecType_MPEG1Video:
  305. return "MPEG-1";
  306. case kCMVideoCodecType_DVCNTSC:
  307. return "DV NTSC";
  308. case kCMVideoCodecType_DVCPAL:
  309. return "DV PAL";
  310. case kCMVideoCodecType_DVCProPAL:
  311. return "Panasonic DVCPro PAL";
  312. case kCMVideoCodecType_DVCPro50NTSC:
  313. return "Panasonic DVCPro-50 NTSC";
  314. case kCMVideoCodecType_DVCPro50PAL:
  315. return "Panasonic DVCPro-50 PAL";
  316. case kCMVideoCodecType_DVCPROHD720p60:
  317. return "Panasonic DVCPro-HD 720p60";
  318. case kCMVideoCodecType_DVCPROHD720p50:
  319. return "Panasonic DVCPro-HD 720p50";
  320. case kCMVideoCodecType_DVCPROHD1080i60:
  321. return "Panasonic DVCPro-HD 1080i60";
  322. case kCMVideoCodecType_DVCPROHD1080i50:
  323. return "Panasonic DVCPro-HD 1080i50";
  324. case kCMVideoCodecType_DVCPROHD1080p30:
  325. return "Panasonic DVCPro-HD 1080p30";
  326. case kCMVideoCodecType_DVCPROHD1080p25:
  327. return "Panasonic DVCPro-HD 1080p25";
  328. case kCMVideoCodecType_AppleProRes4444:
  329. return "Apple ProRes 4444";
  330. case kCMVideoCodecType_AppleProRes422HQ:
  331. return "Apple ProRes 422 HQ";
  332. case kCMVideoCodecType_AppleProRes422:
  333. return "Apple ProRes 422";
  334. case kCMVideoCodecType_AppleProRes422LT:
  335. return "Apple ProRes 422 LT";
  336. case kCMVideoCodecType_AppleProRes422Proxy:
  337. return "Apple ProRes 422 Proxy";
  338. default:
  339. blog(LOG_INFO, "Unknown format %s", AV_FOURCC_STR(fourcc));
  340. return "unknown";
  341. }
  342. }
  343. static inline bool is_fullrange_yuv(FourCharCode pixel_format)
  344. {
  345. switch (pixel_format) {
  346. case kCVPixelFormatType_420YpCbCr8PlanarFullRange:
  347. case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
  348. case kCVPixelFormatType_422YpCbCr8FullRange:
  349. return true;
  350. default:
  351. return false;
  352. }
  353. }
  354. static inline video_colorspace get_colorspace(CMFormatDescriptionRef desc)
  355. {
  356. CFPropertyListRef matrix = CMFormatDescriptionGetExtension(desc, kCMFormatDescriptionExtension_YCbCrMatrix);
  357. if (!matrix)
  358. return VIDEO_CS_DEFAULT;
  359. if (CFStringCompare(static_cast<CFStringRef>(matrix), kCVImageBufferYCbCrMatrix_ITU_R_601_4, 0) ==
  360. kCFCompareEqualTo)
  361. return VIDEO_CS_601;
  362. return VIDEO_CS_709;
  363. }
  364. static inline bool update_colorspace(av_capture *capture, obs_source_frame *frame, CMFormatDescriptionRef desc,
  365. bool full_range, av_video_info &vi)
  366. {
  367. auto cs_auto = capture->use_preset || capture->requested_colorspace == COLOR_SPACE_AUTO;
  368. auto vr_auto = capture->use_preset || capture->requested_video_range == VIDEO_RANGE_AUTO;
  369. video_colorspace colorspace = get_colorspace(desc);
  370. video_range_type range = full_range ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL;
  371. bool cs_matches = false;
  372. if (cs_auto) {
  373. cs_matches = colorspace == vi.colorspace;
  374. } else {
  375. colorspace = static_cast<video_colorspace>(capture->requested_colorspace);
  376. cs_matches = colorspace == vi.colorspace;
  377. }
  378. bool vr_matches = false;
  379. if (vr_auto) {
  380. vr_matches = range == vi.video_range;
  381. } else {
  382. range = static_cast<video_range_type>(capture->requested_video_range);
  383. vr_matches = range == vi.video_range;
  384. full_range = range == VIDEO_RANGE_FULL;
  385. }
  386. if (cs_matches && vr_matches) {
  387. if (!vi.video_params_valid)
  388. capture->video_info.update([&](av_video_info &vi_) {
  389. vi_.video_params_valid = vi.video_params_valid = true;
  390. });
  391. return true;
  392. }
  393. frame->full_range = full_range;
  394. if (!video_format_get_parameters_for_format(colorspace, range, frame->format, frame->color_matrix,
  395. frame->color_range_min, frame->color_range_max)) {
  396. AVLOG(LOG_ERROR,
  397. "Failed to get colorspace parameters for "
  398. "colorspace %u range %u",
  399. colorspace, range);
  400. if (vi.video_params_valid)
  401. capture->video_info.update([&](av_video_info &vi_) {
  402. vi_.video_params_valid = vi.video_params_valid = false;
  403. });
  404. return false;
  405. }
  406. capture->video_info.update([&](av_video_info &vi_) {
  407. vi_.colorspace = colorspace;
  408. vi_.video_range = range;
  409. vi_.video_params_valid = vi.video_params_valid = true;
  410. });
  411. return true;
  412. }
  413. static inline bool update_frame(av_capture *capture, obs_source_frame *frame, CMSampleBufferRef sample_buffer)
  414. {
  415. CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(sample_buffer);
  416. FourCharCode fourcc = CMFormatDescriptionGetMediaSubType(desc);
  417. video_format format = format_from_subtype(fourcc);
  418. CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(desc);
  419. CVImageBufferRef img = CMSampleBufferGetImageBuffer(sample_buffer);
  420. auto vi = capture->video_info.read();
  421. bool video_params_were_valid = vi.video_params_valid;
  422. SCOPE_EXIT
  423. {
  424. if (video_params_were_valid != vi.video_params_valid)
  425. obs_source_update_properties(capture->source);
  426. };
  427. if (format == VIDEO_FORMAT_NONE) {
  428. if (capture->fourcc == fourcc)
  429. return false;
  430. capture->fourcc = fourcc;
  431. AVLOG(LOG_ERROR, "Unhandled fourcc: %s (0x%x) (%zu planes)", AV_FOURCC_STR(fourcc), fourcc,
  432. CVPixelBufferGetPlaneCount(img));
  433. return false;
  434. }
  435. if (frame->format != format)
  436. AVLOG(LOG_DEBUG,
  437. "Switching fourcc: "
  438. "'%s' (0x%x) -> '%s' (0x%x)",
  439. AV_FOURCC_STR(capture->fourcc), capture->fourcc, AV_FOURCC_STR(fourcc), fourcc);
  440. bool was_yuv = format_is_yuv(frame->format);
  441. capture->fourcc = fourcc;
  442. frame->format = format;
  443. frame->width = dims.width;
  444. frame->height = dims.height;
  445. if (format_is_yuv(format) && !update_colorspace(capture, frame, desc, is_fullrange_yuv(fourcc), vi)) {
  446. return false;
  447. } else if (was_yuv == format_is_yuv(format)) {
  448. capture->video_info.update([&](av_video_info &vi_) {
  449. vi_.video_params_valid = vi.video_params_valid = true;
  450. });
  451. }
  452. CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly);
  453. if (!CVPixelBufferIsPlanar(img)) {
  454. frame->linesize[0] = static_cast<uint32_t>(CVPixelBufferGetBytesPerRow(img));
  455. frame->data[0] = static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(img));
  456. return true;
  457. }
  458. size_t count = CVPixelBufferGetPlaneCount(img);
  459. for (size_t i = 0; i < count; i++) {
  460. frame->linesize[i] = static_cast<uint32_t>(CVPixelBufferGetBytesPerRowOfPlane(img, i));
  461. frame->data[i] = static_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(img, i));
  462. }
  463. return true;
  464. }
  465. static inline bool update_audio(obs_source_audio *audio, CMSampleBufferRef sample_buffer)
  466. {
  467. size_t requiredSize;
  468. OSStatus status = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
  469. sample_buffer, &requiredSize, nullptr, 0, nullptr, nullptr,
  470. kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, nullptr);
  471. if (status != noErr) {
  472. blog(LOG_WARNING, "[mac-avcapture]: Error while getting size of sample buffer");
  473. return false;
  474. }
  475. AudioBufferList *list = static_cast<AudioBufferList *>(bmalloc(requiredSize));
  476. CMBlockBufferRef buffer;
  477. status = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
  478. sample_buffer, nullptr, list, requiredSize, nullptr, nullptr,
  479. kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &buffer);
  480. if (status != noErr || !list) {
  481. if (list)
  482. bfree(list);
  483. blog(LOG_WARNING, "[mac-avcapture]: Error while copying sample buffer to audio buffer list");
  484. return false;
  485. }
  486. for (size_t i = 0; i < list->mNumberBuffers; i++)
  487. audio->data[i] = static_cast<uint8_t *>(list->mBuffers[i].mData);
  488. audio->frames = static_cast<uint32_t>(CMSampleBufferGetNumSamples(sample_buffer));
  489. CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(sample_buffer);
  490. const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(desc);
  491. audio->samples_per_sec = static_cast<uint32_t>(asbd->mSampleRate);
  492. audio->speakers = static_cast<enum speaker_layout>(asbd->mChannelsPerFrame);
  493. switch (asbd->mBitsPerChannel) {
  494. case 8:
  495. audio->format = AUDIO_FORMAT_U8BIT;
  496. break;
  497. case 16:
  498. audio->format = AUDIO_FORMAT_16BIT;
  499. break;
  500. case 32:
  501. audio->format = AUDIO_FORMAT_32BIT;
  502. break;
  503. default:
  504. audio->format = AUDIO_FORMAT_UNKNOWN;
  505. }
  506. if (buffer)
  507. CFRelease(buffer);
  508. bfree(list);
  509. return true;
  510. }
  511. @implementation OBSLegacyAVCaptureDelegate
  512. - (void)captureOutput:(AVCaptureOutput *)out
  513. didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer
  514. fromConnection:(AVCaptureConnection *)connection
  515. {
  516. UNUSED_PARAMETER(out);
  517. UNUSED_PARAMETER(sampleBuffer);
  518. UNUSED_PARAMETER(connection);
  519. }
  520. - (void)captureOutput:(AVCaptureOutput *)captureOutput
  521. didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
  522. fromConnection:(AVCaptureConnection *)connection
  523. {
  524. UNUSED_PARAMETER(captureOutput);
  525. UNUSED_PARAMETER(connection);
  526. CMItemCount count = CMSampleBufferGetNumSamples(sampleBuffer);
  527. if (count < 1 || !capture)
  528. return;
  529. CMTime target_pts = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
  530. CMTime target_pts_nano = CMTimeConvertScale(target_pts, NANO_TIMESCALE, kCMTimeRoundingMethod_Default);
  531. CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(sampleBuffer);
  532. CMMediaType type = CMFormatDescriptionGetMediaType(desc);
  533. if (type == kCMMediaType_Video) {
  534. obs_source_frame *frame = &capture->frame;
  535. frame->timestamp = target_pts_nano.value;
  536. if (!update_frame(capture, frame, sampleBuffer)) {
  537. obs_source_output_video(capture->source, nullptr);
  538. return;
  539. }
  540. obs_source_output_video(capture->source, frame);
  541. CVImageBufferRef img = CMSampleBufferGetImageBuffer(sampleBuffer);
  542. CVPixelBufferUnlockBaseAddress(img, kCVPixelBufferLock_ReadOnly);
  543. } else if (type == kCMMediaType_Audio) {
  544. if (!capture->enable_audio)
  545. return;
  546. obs_source_audio *audio = &capture->audio;
  547. audio->timestamp = target_pts_nano.value;
  548. if (!update_audio(audio, sampleBuffer)) {
  549. obs_source_output_audio(capture->source, nullptr);
  550. return;
  551. }
  552. obs_source_output_audio(capture->source, audio);
  553. }
  554. }
  555. @end
  556. static void av_capture_enable_buffering(av_capture *capture, bool enabled)
  557. {
  558. obs_source_set_async_unbuffered(capture->source, !enabled);
  559. }
  560. static const char *av_capture_getname(void *)
  561. {
  562. return TEXT_AVCAPTURE;
  563. }
  564. static void unlock_device(av_capture *capture, AVCaptureDevice *dev = nullptr)
  565. {
  566. if (!dev)
  567. dev = capture->device;
  568. if (dev && capture->device_locked)
  569. [dev unlockForConfiguration];
  570. capture->device_locked = false;
  571. }
  572. static void start_capture(av_capture *capture)
  573. {
  574. if (capture->session && !capture->session.running)
  575. [capture->session startRunning];
  576. }
  577. static void clear_capture(av_capture *capture)
  578. {
  579. if (capture->session && capture->session.running)
  580. [capture->session stopRunning];
  581. obs_source_output_video(capture->source, nullptr);
  582. obs_source_output_audio(capture->source, nullptr);
  583. }
  584. static void remove_device(av_capture *capture)
  585. {
  586. clear_capture(capture);
  587. [capture->session removeInput:capture->device_input];
  588. unlock_device(capture);
  589. capture->device_input = nullptr;
  590. capture->device = nullptr;
  591. }
  592. static void av_capture_destroy(void *data)
  593. {
  594. auto capture = static_cast<av_capture *>(data);
  595. delete capture;
  596. }
  597. static bool init_session(av_capture *capture)
  598. {
  599. auto session = [[AVCaptureSession alloc] init];
  600. if (!session) {
  601. AVLOG(LOG_ERROR, "Could not create AVCaptureSession");
  602. return false;
  603. }
  604. auto delegate = [[OBSLegacyAVCaptureDelegate alloc] init];
  605. if (!delegate) {
  606. AVLOG(LOG_ERROR, "Could not create OBSAVCaptureDelegate");
  607. return false;
  608. }
  609. delegate->capture = capture;
  610. auto out = [[AVCaptureVideoDataOutput alloc] init];
  611. if (!out) {
  612. AVLOG(LOG_ERROR, "Could not create AVCaptureVideoDataOutput");
  613. return false;
  614. }
  615. auto audioOut = [[AVCaptureAudioDataOutput alloc] init];
  616. if (!audioOut) {
  617. AVLOG(LOG_ERROR, "Could not create AVCaptureAudioDataOutput");
  618. return false;
  619. }
  620. auto queue = dispatch_queue_create(NULL, NULL);
  621. if (!queue) {
  622. AVLOG(LOG_ERROR, "Could not create dispatch queue");
  623. return false;
  624. }
  625. auto audioQueue = dispatch_queue_create(NULL, NULL);
  626. if (!queue) {
  627. AVLOG(LOG_ERROR, "Could not create dispatch queue for audio");
  628. return false;
  629. }
  630. capture->session = session;
  631. capture->delegate = delegate;
  632. capture->out = out;
  633. capture->audioOut = audioOut;
  634. capture->queue = queue;
  635. capture->audioQueue = audioQueue;
  636. [capture->session addOutput:capture->out];
  637. [capture->out setSampleBufferDelegate:capture->delegate queue:capture->queue];
  638. [capture->session addOutput:capture->audioOut];
  639. [capture->audioOut setSampleBufferDelegate:capture->delegate queue:capture->audioQueue];
  640. return true;
  641. }
  642. static bool init_format(av_capture *capture, AVCaptureDevice *dev);
  643. static bool init_device_input(av_capture *capture, AVCaptureDevice *dev)
  644. {
  645. NSError *err = nil;
  646. AVCaptureDeviceInput *device_input = [AVCaptureDeviceInput deviceInputWithDevice:dev error:&err];
  647. if (!device_input) {
  648. AVLOG(LOG_ERROR, "Error while initializing device input: %s", err.localizedFailureReason.UTF8String);
  649. return false;
  650. }
  651. [capture->session addInput:device_input];
  652. if (!init_format(capture, dev)) {
  653. [capture->session removeInput:device_input];
  654. return false;
  655. }
  656. capture->device_input = device_input;
  657. return true;
  658. }
  659. static uint32_t uint_from_dict(NSDictionary *dict, CFStringRef key)
  660. {
  661. return static_cast<NSNumber *>(dict[(__bridge NSString *) key]).unsignedIntValue;
  662. }
  663. static bool init_format(av_capture *capture, AVCaptureDevice *dev)
  664. {
  665. AVCaptureDeviceFormat *format = dev.activeFormat;
  666. CMMediaType mtype = CMFormatDescriptionGetMediaType(format.formatDescription);
  667. // TODO: support other media types
  668. if (mtype != kCMMediaType_Video && mtype != kCMMediaType_Muxed) {
  669. AVLOG(LOG_ERROR, "CMMediaType '%s' is unsupported", AV_FOURCC_STR(mtype));
  670. return false;
  671. }
  672. capture->out.videoSettings = nil;
  673. FourCharCode subtype = uint_from_dict(capture->out.videoSettings, kCVPixelBufferPixelFormatTypeKey);
  674. if (format_from_subtype(subtype) != VIDEO_FORMAT_NONE) {
  675. AVLOG(LOG_DEBUG, "Using native fourcc '%s'", AV_FOURCC_STR(subtype));
  676. return true;
  677. }
  678. AVLOG(LOG_DEBUG, "Using fallback fourcc '%s' ('%s' 0x%08x unsupported)", AV_FOURCC_STR(kCVPixelFormatType_32BGRA),
  679. AV_FOURCC_STR(subtype), subtype);
  680. capture->out.videoSettings =
  681. @{(__bridge NSString *) kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
  682. return true;
  683. }
  684. static NSArray *presets(void);
  685. static NSString *preset_names(NSString *preset);
  686. static NSString *select_preset(AVCaptureDevice *dev, NSString *cur_preset)
  687. {
  688. NSString *new_preset = nil;
  689. bool found_previous_preset = false;
  690. for (NSString *preset in presets().reverseObjectEnumerator) {
  691. if (!found_previous_preset)
  692. found_previous_preset = [cur_preset isEqualToString:preset];
  693. if (![dev supportsAVCaptureSessionPreset:preset])
  694. continue;
  695. if (!new_preset || !found_previous_preset)
  696. new_preset = preset;
  697. }
  698. return new_preset;
  699. }
  700. static bool init_preset(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  701. {
  702. clear_capture(capture);
  703. unlock_device(capture, dev);
  704. NSString *preset = get_string(settings, "preset");
  705. if (![dev supportsAVCaptureSessionPreset:preset]) {
  706. AVLOG(LOG_WARNING, "Preset %s not available", preset_names(preset).UTF8String);
  707. preset = select_preset(dev, preset);
  708. }
  709. if (!preset) {
  710. AVLOG(LOG_WARNING, "Could not select a preset, "
  711. "initialization failed");
  712. return false;
  713. }
  714. capture->session.sessionPreset = preset;
  715. AVLOG(LOG_INFO, "Using preset %s", preset_names(preset).UTF8String);
  716. return true;
  717. }
  718. static bool operator==(const CMVideoDimensions &a, const CMVideoDimensions &b);
  719. static CMVideoDimensions get_dimensions(AVCaptureDeviceFormat *format);
  720. static CMTime convert(media_frames_per_second fps)
  721. {
  722. CMTime time {};
  723. time.value = fps.denominator;
  724. time.timescale = fps.numerator;
  725. time.flags = 1;
  726. return time;
  727. }
  728. static bool lock_device(av_capture *capture, AVCaptureDevice *dev)
  729. {
  730. if (!dev)
  731. dev = capture->device;
  732. NSError *err;
  733. if (![dev lockForConfiguration:&err]) {
  734. AVLOG(LOG_WARNING,
  735. "Could not lock device for configuration: "
  736. "%s",
  737. err.localizedDescription.UTF8String);
  738. return false;
  739. }
  740. capture->device_locked = true;
  741. return true;
  742. }
  743. template<typename Func>
  744. static void find_formats(media_frames_per_second fps, AVCaptureDevice *dev, const CMVideoDimensions *dims, Func &&f)
  745. {
  746. auto time = convert(fps);
  747. for (AVCaptureDeviceFormat *format in dev.formats) {
  748. if (!(get_dimensions(format) == *dims))
  749. continue;
  750. for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
  751. if (CMTimeCompare(range.maxFrameDuration, time) >= 0 && CMTimeCompare(range.minFrameDuration, time) <= 0)
  752. if (f(format))
  753. return;
  754. }
  755. }
  756. }
  757. static bool color_space_valid(int color_space)
  758. {
  759. switch (color_space) {
  760. case COLOR_SPACE_AUTO:
  761. case VIDEO_CS_DEFAULT:
  762. case VIDEO_CS_601:
  763. case VIDEO_CS_709:
  764. return true;
  765. }
  766. return false;
  767. }
  768. static const char *color_space_name(int color_space)
  769. {
  770. switch (color_space) {
  771. case COLOR_SPACE_AUTO:
  772. return "Auto";
  773. case VIDEO_CS_DEFAULT:
  774. return "Default";
  775. case VIDEO_CS_601:
  776. return "CS 601";
  777. case VIDEO_CS_709:
  778. return "CS 709";
  779. }
  780. return "Unknown";
  781. }
  782. static bool video_range_valid(int video_range)
  783. {
  784. switch (video_range) {
  785. case VIDEO_RANGE_AUTO:
  786. case VIDEO_RANGE_DEFAULT:
  787. case VIDEO_RANGE_PARTIAL:
  788. case VIDEO_RANGE_FULL:
  789. return true;
  790. }
  791. return false;
  792. }
  793. static const char *video_range_name(int video_range)
  794. {
  795. switch (video_range) {
  796. case VIDEO_RANGE_AUTO:
  797. return "Auto";
  798. case VIDEO_RANGE_DEFAULT:
  799. return "Default";
  800. case VIDEO_RANGE_PARTIAL:
  801. return "Partial";
  802. case VIDEO_RANGE_FULL:
  803. return "Full";
  804. }
  805. return "Unknown";
  806. }
  807. static bool init_manual(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  808. {
  809. clear_capture(capture);
  810. auto input_format = obs_data_get_int(settings, "input_format");
  811. FourCharCode actual_format = static_cast<FourCharCode>(input_format);
  812. capture->requested_colorspace = static_cast<int>(obs_data_get_int(settings, "color_space"));
  813. if (!color_space_valid(capture->requested_colorspace)) {
  814. AVLOG(LOG_WARNING, "Unsupported color space: %d", capture->requested_colorspace);
  815. return false;
  816. }
  817. capture->requested_video_range = static_cast<int>(obs_data_get_int(settings, "video_range"));
  818. if (!video_range_valid(capture->requested_video_range)) {
  819. AVLOG(LOG_WARNING, "Unsupported color range: %d", capture->requested_video_range);
  820. return false;
  821. }
  822. CMVideoDimensions dims {};
  823. if (!get_resolution(settings, dims)) {
  824. AVLOG(LOG_WARNING, "Could not load resolution");
  825. return false;
  826. }
  827. media_frames_per_second fps {};
  828. if (!obs_data_get_frames_per_second(settings, "frame_rate", &fps, nullptr)) {
  829. AVLOG(LOG_WARNING, "Could not load frame rate");
  830. return false;
  831. }
  832. AVCaptureDeviceFormat *format = nullptr;
  833. find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format_) {
  834. auto desc = format_.formatDescription;
  835. auto fourcc = CMFormatDescriptionGetMediaSubType(desc);
  836. if (input_format != INPUT_FORMAT_AUTO && fourcc != input_format)
  837. return false;
  838. actual_format = fourcc;
  839. format = format_;
  840. return true;
  841. });
  842. if (!format) {
  843. AVLOG(LOG_WARNING,
  844. "Frame rate is not supported: %g FPS "
  845. "(%u/%u)",
  846. media_frames_per_second_to_fps(fps), fps.numerator, fps.denominator);
  847. return false;
  848. }
  849. if (!lock_device(capture, dev))
  850. return false;
  851. const char *if_name =
  852. input_format == INPUT_FORMAT_AUTO ? "Auto" : fourcc_subtype_name(static_cast<FourCharCode>(input_format));
  853. #define IF_AUTO(x) (input_format != INPUT_FORMAT_AUTO ? "" : x)
  854. AVLOG(LOG_INFO,
  855. "Capturing '%s' (%s):\n"
  856. " Resolution: %ux%u\n"
  857. " FPS: %g (%" PRIu32 "/%" PRIu32 ")\n"
  858. " Frame interval: %g" NBSP "s\n"
  859. " Input format: %s%s%s (%s)%s\n"
  860. " Requested color space: %s (%d)\n"
  861. " Requested video range: %s (%d)\n"
  862. " Using format: %s",
  863. dev.localizedName.UTF8String, dev.uniqueID.UTF8String, dims.width, dims.height,
  864. media_frames_per_second_to_fps(fps), fps.numerator, fps.denominator,
  865. media_frames_per_second_to_frame_interval(fps), if_name, IF_AUTO(" (actual: "),
  866. IF_AUTO(fourcc_subtype_name(actual_format)), AV_FOURCC_STR(actual_format), IF_AUTO(")"),
  867. color_space_name(capture->requested_colorspace), capture->requested_colorspace,
  868. video_range_name(capture->requested_video_range), capture->requested_video_range,
  869. format.description.UTF8String);
  870. #undef IF_AUTO
  871. dev.activeFormat = format;
  872. dev.activeVideoMinFrameDuration = convert(fps);
  873. dev.activeVideoMaxFrameDuration = convert(fps);
  874. capture->video_info.update([&](av_video_info &vi) {
  875. vi.video_params_valid = false;
  876. });
  877. return true;
  878. }
  879. static inline void av_capture_set_audio_active(av_capture *capture, bool active)
  880. {
  881. obs_source_set_audio_active(capture->source, active);
  882. capture->enable_audio = active;
  883. }
  884. static void capture_device(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  885. {
  886. const char *name = dev.localizedName.UTF8String;
  887. obs_data_set_string(settings, "device_name", name);
  888. obs_data_set_string(settings, "device", dev.uniqueID.UTF8String);
  889. AVLOG(LOG_INFO, "Selected device '%s'", name);
  890. if (@available(macOS 12.0, *)) {
  891. if ([dev isPortraitEffectActive])
  892. AVLOG(LOG_WARNING, "Portrait effect is active on selected device");
  893. }
  894. if (@available(macOS 12.3, *)) {
  895. if ([dev isCenterStageActive])
  896. AVLOG(LOG_WARNING, "Center Stage effect is active on selected device");
  897. }
  898. if (@available(macOS 13.0, *)) {
  899. if ([dev isStudioLightActive])
  900. AVLOG(LOG_WARNING, "Studio Light effect is active on selected device");
  901. }
  902. if ((capture->use_preset = obs_data_get_bool(settings, "use_preset"))) {
  903. if (!init_preset(capture, dev, settings))
  904. return;
  905. } else {
  906. if (!init_manual(capture, dev, settings))
  907. return;
  908. }
  909. av_capture_set_audio_active(capture,
  910. obs_data_get_bool(settings, "enable_audio") &&
  911. ([dev hasMediaType:AVMediaTypeAudio] || [dev hasMediaType:AVMediaTypeMuxed]));
  912. if (!init_device_input(capture, dev))
  913. return;
  914. AVCaptureInputPort *port = capture->device_input.ports[0];
  915. capture->has_clock = [port respondsToSelector:@selector(clock)];
  916. capture->device = dev;
  917. start_capture(capture);
  918. return;
  919. }
  920. static inline void handle_disconnect_capture(av_capture *capture, AVCaptureDevice *dev)
  921. {
  922. if (![dev.uniqueID isEqualTo:capture->uid])
  923. return;
  924. if (!capture->device) {
  925. AVLOG(LOG_INFO, "Received disconnect for inactive device '%s'", capture->uid.UTF8String);
  926. return;
  927. }
  928. AVLOG(LOG_WARNING, "Device with unique ID '%s' disconnected", dev.uniqueID.UTF8String);
  929. remove_device(capture);
  930. }
  931. static inline void handle_disconnect(av_capture *capture, AVCaptureDevice *dev)
  932. {
  933. if (!dev)
  934. return;
  935. handle_disconnect_capture(capture, dev);
  936. obs_source_update_properties(capture->source);
  937. }
  938. static inline void handle_connect_capture(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  939. {
  940. if (![dev.uniqueID isEqualTo:capture->uid])
  941. return;
  942. if (capture->device) {
  943. AVLOG(LOG_ERROR, "Received connect for in-use device '%s'", capture->uid.UTF8String);
  944. return;
  945. }
  946. AVLOG(LOG_INFO,
  947. "Device with unique ID '%s' connected, "
  948. "resuming capture",
  949. dev.uniqueID.UTF8String);
  950. capture_device(capture, dev, settings);
  951. }
  952. static inline void handle_connect(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  953. {
  954. if (!dev)
  955. return;
  956. handle_connect_capture(capture, dev, settings);
  957. obs_source_update_properties(capture->source);
  958. }
  959. static bool av_capture_init(av_capture *capture, obs_data_t *settings)
  960. {
  961. if (!init_session(capture))
  962. return false;
  963. capture->uid = get_string(settings, "device");
  964. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  965. capture->disconnect_observer.reset([nc addObserverForName:AVCaptureDeviceWasDisconnectedNotification object:nil
  966. queue:[NSOperationQueue mainQueue]
  967. usingBlock:^(NSNotification *note) {
  968. handle_disconnect(capture, note.object);
  969. }]);
  970. capture->connect_observer.reset([nc addObserverForName:AVCaptureDeviceWasConnectedNotification object:nil
  971. queue:[NSOperationQueue mainQueue]
  972. usingBlock:^(NSNotification *note) {
  973. handle_connect(capture, note.object, settings);
  974. }]);
  975. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:capture->uid];
  976. if (!dev) {
  977. if (capture->uid.length < 1)
  978. AVLOG(LOG_INFO, "No device selected");
  979. else
  980. AVLOG(LOG_WARNING,
  981. "Could not initialize device "
  982. "with unique ID '%s'",
  983. capture->uid.UTF8String);
  984. return true;
  985. }
  986. capture_device(capture, dev, settings);
  987. return true;
  988. }
  989. static void *av_capture_create(obs_data_t *settings, obs_source_t *source)
  990. {
  991. unique_ptr<av_capture> capture;
  992. try {
  993. capture.reset(new av_capture());
  994. } catch (...) {
  995. return capture.release();
  996. }
  997. capture->source = source;
  998. if (!av_capture_init(capture.get(), settings)) {
  999. AVLOG(LOG_ERROR, "av_capture_init failed");
  1000. return nullptr;
  1001. }
  1002. av_capture_enable_buffering(capture.get(), obs_data_get_bool(settings, "buffering"));
  1003. return capture.release();
  1004. }
  1005. static NSArray *presets(void)
  1006. {
  1007. return @[
  1008. //AVCaptureSessionPresetiFrame1280x720,
  1009. //AVCaptureSessionPresetiFrame960x540,
  1010. AVCaptureSessionPreset3840x2160, AVCaptureSessionPreset1920x1080, AVCaptureSessionPreset1280x720,
  1011. AVCaptureSessionPreset960x540, AVCaptureSessionPreset640x480, AVCaptureSessionPreset352x288,
  1012. AVCaptureSessionPreset320x240, AVCaptureSessionPresetHigh,
  1013. //AVCaptureSessionPresetMedium,
  1014. //AVCaptureSessionPresetLow,
  1015. //AVCaptureSessionPresetPhoto,
  1016. ];
  1017. }
  1018. static NSString *preset_names(NSString *preset)
  1019. {
  1020. NSDictionary *preset_names = nil;
  1021. preset_names = @ {
  1022. AVCaptureSessionPresetLow: @"Low",
  1023. AVCaptureSessionPresetMedium: @"Medium",
  1024. AVCaptureSessionPresetHigh: @"High",
  1025. AVCaptureSessionPreset320x240: @"320x240",
  1026. AVCaptureSessionPreset352x288: @"352x288",
  1027. AVCaptureSessionPreset640x480: @"640x480",
  1028. AVCaptureSessionPreset960x540: @"960x540",
  1029. AVCaptureSessionPreset1280x720: @"1280x720",
  1030. AVCaptureSessionPreset1920x1080: @"1920x1080",
  1031. AVCaptureSessionPreset3840x2160: @"3840x2160",
  1032. AVCaptureSessionPresetHigh: @"High",
  1033. };
  1034. NSString *name = preset_names[preset];
  1035. if (name)
  1036. return name;
  1037. return [NSString stringWithFormat:@"Unknown (%@)", preset];
  1038. }
  1039. inline static void av_capture_defaults(obs_data_t *settings, bool enable_audio_and_high_preset)
  1040. {
  1041. obs_data_set_default_string(settings, "uid", "");
  1042. obs_data_set_default_bool(settings, "use_preset", true);
  1043. obs_data_set_default_string(settings, "preset",
  1044. enable_audio_and_high_preset ? AVCaptureSessionPresetHigh.UTF8String
  1045. : AVCaptureSessionPreset1280x720.UTF8String);
  1046. obs_data_set_default_int(settings, "input_format", INPUT_FORMAT_AUTO);
  1047. obs_data_set_default_int(settings, "color_space", COLOR_SPACE_AUTO);
  1048. obs_data_set_default_int(settings, "video_range", VIDEO_RANGE_AUTO);
  1049. obs_data_set_default_bool(settings, "enable_audio", enable_audio_and_high_preset);
  1050. }
  1051. static void av_capture_defaults_v1(obs_data_t *settings)
  1052. {
  1053. av_capture_defaults(settings, false);
  1054. }
  1055. static void av_capture_defaults_v2(obs_data_t *settings)
  1056. {
  1057. av_capture_defaults(settings, true);
  1058. }
  1059. static bool update_device_list(obs_property_t *list, NSString *uid, NSString *name, bool disconnected)
  1060. {
  1061. bool dev_found = false;
  1062. bool list_modified = false;
  1063. size_t size = obs_property_list_item_count(list);
  1064. for (size_t i = 0; i < size;) {
  1065. const char *uid_ = obs_property_list_item_string(list, i);
  1066. bool found = [uid isEqualToString:@(uid_ ? uid_ : "")];
  1067. bool disabled = obs_property_list_item_disabled(list, i);
  1068. if (!found && !disabled) {
  1069. i += 1;
  1070. continue;
  1071. }
  1072. if (disabled && !found) {
  1073. list_modified = true;
  1074. obs_property_list_item_remove(list, i);
  1075. continue;
  1076. }
  1077. if (disabled != disconnected)
  1078. list_modified = true;
  1079. dev_found = true;
  1080. obs_property_list_item_disable(list, i, disconnected);
  1081. i += 1;
  1082. }
  1083. if (dev_found)
  1084. return list_modified;
  1085. size_t idx = obs_property_list_add_string(list, name.UTF8String, uid.UTF8String);
  1086. obs_property_list_item_disable(list, idx, disconnected);
  1087. return true;
  1088. }
  1089. static void fill_presets(AVCaptureDevice *dev, obs_property_t *list, NSString *current_preset)
  1090. {
  1091. obs_property_list_clear(list);
  1092. bool preset_found = false;
  1093. for (NSString *preset in presets()) {
  1094. bool is_current = [preset isEqualToString:current_preset];
  1095. bool supported = dev && [dev supportsAVCaptureSessionPreset:preset];
  1096. if (is_current)
  1097. preset_found = true;
  1098. if (!supported && !is_current)
  1099. continue;
  1100. size_t idx = obs_property_list_add_string(list, preset_names(preset).UTF8String, preset.UTF8String);
  1101. obs_property_list_item_disable(list, idx, !supported);
  1102. }
  1103. if (preset_found)
  1104. return;
  1105. size_t idx = obs_property_list_add_string(list, preset_names(current_preset).UTF8String, current_preset.UTF8String);
  1106. obs_property_list_item_disable(list, idx, true);
  1107. }
  1108. static bool check_preset(AVCaptureDevice *dev, obs_property_t *list, obs_data_t *settings)
  1109. {
  1110. NSString *current_preset = get_string(settings, "preset");
  1111. size_t size = obs_property_list_item_count(list);
  1112. NSMutableSet *listed = [NSMutableSet setWithCapacity:size];
  1113. for (size_t i = 0; i < size; i++)
  1114. [listed addObject:@(obs_property_list_item_string(list, i))];
  1115. bool presets_changed = false;
  1116. for (NSString *preset in presets()) {
  1117. bool is_listed = [listed member:preset] != nil;
  1118. bool supported = dev && [dev supportsAVCaptureSessionPreset:preset];
  1119. if (supported == is_listed)
  1120. continue;
  1121. presets_changed = true;
  1122. }
  1123. if (!presets_changed && [listed member:current_preset] != nil)
  1124. return false;
  1125. fill_presets(dev, list, current_preset);
  1126. return true;
  1127. }
  1128. static CMVideoDimensions get_dimensions(AVCaptureDeviceFormat *format)
  1129. {
  1130. auto desc = format.formatDescription;
  1131. return CMVideoFormatDescriptionGetDimensions(desc);
  1132. }
  1133. using resolutions_t = vector<CMVideoDimensions>;
  1134. static resolutions_t enumerate_resolutions(AVCaptureDevice *dev)
  1135. {
  1136. resolutions_t res;
  1137. if (!dev)
  1138. return res;
  1139. res.reserve(dev.formats.count + 1);
  1140. for (AVCaptureDeviceFormat *format in dev.formats) {
  1141. auto dims = get_dimensions(format);
  1142. if (find(begin(res), end(res), dims) == end(res))
  1143. res.push_back(dims);
  1144. }
  1145. return res;
  1146. }
  1147. static void sort_resolutions(vector<CMVideoDimensions> &resolutions)
  1148. {
  1149. auto cmp = [](const CMVideoDimensions &a, const CMVideoDimensions &b) {
  1150. return a.width * a.height > b.width * b.height;
  1151. };
  1152. sort(begin(resolutions), end(resolutions), cmp);
  1153. }
  1154. static void data_set_resolution(obs_data_t *data, const CMVideoDimensions &dims)
  1155. {
  1156. obs_data_set_int(data, "width", dims.width);
  1157. obs_data_set_int(data, "height", dims.height);
  1158. }
  1159. static void data_set_resolution(const unique_ptr<obs_data_t> &data, const CMVideoDimensions &dims)
  1160. {
  1161. data_set_resolution(data.get(), dims);
  1162. }
  1163. static bool add_resolution_to_list(vector<CMVideoDimensions> &res, const CMVideoDimensions &dims)
  1164. {
  1165. if (find(begin(res), end(res), dims) != end(res))
  1166. return false;
  1167. res.push_back(dims);
  1168. return true;
  1169. }
  1170. static const char *obs_data_get_json(const unique_ptr<obs_data_t> &data)
  1171. {
  1172. return obs_data_get_json(data.get());
  1173. }
  1174. static bool operator==(const CMVideoDimensions &a, const CMVideoDimensions &b)
  1175. {
  1176. return a.width == b.width && a.height == b.height;
  1177. }
  1178. static bool resolution_property_needs_update(obs_property_t *p, const resolutions_t &resolutions)
  1179. {
  1180. vector<bool> res_found(resolutions.size());
  1181. auto num = obs_property_list_item_count(p);
  1182. for (size_t i = 1; i < num; i++) { // skip empty entry
  1183. const char *json = obs_property_list_item_string(p, i);
  1184. unique_ptr<obs_data_t> buffer {obs_data_create_from_json(json)};
  1185. CMVideoDimensions dims {};
  1186. if (!get_resolution(buffer.get(), dims))
  1187. return true;
  1188. auto pos = find(begin(resolutions), end(resolutions), dims);
  1189. if (pos == end(resolutions))
  1190. return true;
  1191. res_found[pos - begin(resolutions)] = true;
  1192. }
  1193. return any_of(begin(res_found), end(res_found), [](bool b) {
  1194. return !b;
  1195. });
  1196. }
  1197. static bool update_resolution_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1198. {
  1199. if (!p)
  1200. p = obs_properties_get(props, "resolution");
  1201. if (!p)
  1202. return false;
  1203. auto valid_dims = conf.dims();
  1204. auto resolutions = enumerate_resolutions(conf.dev());
  1205. bool unsupported = true;
  1206. if (valid_dims)
  1207. unsupported = add_resolution_to_list(resolutions, *valid_dims);
  1208. bool was_enabled = obs_property_enabled(p);
  1209. obs_property_set_enabled(p, !!conf.dev());
  1210. if (!resolution_property_needs_update(p, resolutions))
  1211. return was_enabled != obs_property_enabled(p);
  1212. sort_resolutions(resolutions);
  1213. obs_property_list_clear(p);
  1214. obs_property_list_add_string(p, "", "{}");
  1215. DStr name;
  1216. unique_ptr<obs_data_t> buffer {obs_data_create()};
  1217. for (const CMVideoDimensions &dims : resolutions) {
  1218. data_set_resolution(buffer, dims);
  1219. auto json = obs_data_get_json(buffer);
  1220. dstr_printf(name, "%dx%d", dims.width, dims.height);
  1221. size_t idx = obs_property_list_add_string(p, name->array, json);
  1222. if (unsupported && valid_dims && dims == *valid_dims)
  1223. obs_property_list_item_disable(p, idx, true);
  1224. }
  1225. return true;
  1226. }
  1227. static media_frames_per_second convert(CMTime time_)
  1228. {
  1229. media_frames_per_second res {};
  1230. clamp(res.numerator, time_.timescale);
  1231. clamp(res.denominator, time_.value);
  1232. return res;
  1233. }
  1234. using frame_rates_t = vector<pair<media_frames_per_second, media_frames_per_second>>;
  1235. static frame_rates_t enumerate_frame_rates(AVCaptureDevice *dev, const CMVideoDimensions *dims = nullptr)
  1236. {
  1237. frame_rates_t res;
  1238. if (!dev || !dims)
  1239. return res;
  1240. auto add_unique_frame_rate_range = [&](AVFrameRateRange *range) {
  1241. auto min = convert(range.maxFrameDuration);
  1242. auto max = convert(range.minFrameDuration);
  1243. auto pair = make_pair(min, max);
  1244. if (find(begin(res), end(res), pair) != end(res))
  1245. return;
  1246. res.push_back(pair);
  1247. };
  1248. for (AVCaptureDeviceFormat *format in dev.formats) {
  1249. if (!(get_dimensions(format) == *dims))
  1250. continue;
  1251. for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
  1252. add_unique_frame_rate_range(range);
  1253. if (CMTimeCompare(range.minFrameDuration, range.maxFrameDuration) != 0) {
  1254. blog(LOG_WARNING,
  1255. "Got actual frame rate range:"
  1256. " %g - %g "
  1257. "({%lld, %d} - {%lld, %d})",
  1258. range.minFrameRate, range.maxFrameRate, range.maxFrameDuration.value,
  1259. range.maxFrameDuration.timescale, range.minFrameDuration.value, range.minFrameDuration.timescale);
  1260. }
  1261. }
  1262. }
  1263. return res;
  1264. }
  1265. static bool operator==(const media_frames_per_second &a, const media_frames_per_second &b)
  1266. {
  1267. return a.numerator == b.numerator && a.denominator == b.denominator;
  1268. }
  1269. static bool operator!=(const media_frames_per_second &a, const media_frames_per_second &b)
  1270. {
  1271. return !(a == b);
  1272. }
  1273. static bool frame_rate_property_needs_update(obs_property_t *p, const frame_rates_t &frame_rates)
  1274. {
  1275. auto fps_num = frame_rates.size();
  1276. auto num = obs_property_frame_rate_fps_ranges_count(p);
  1277. if (fps_num != num)
  1278. return true;
  1279. vector<bool> fps_found(fps_num);
  1280. for (size_t i = 0; i < num; i++) {
  1281. auto min_ = obs_property_frame_rate_fps_range_min(p, i);
  1282. auto max_ = obs_property_frame_rate_fps_range_max(p, i);
  1283. auto it = find(begin(frame_rates), end(frame_rates), make_pair(min_, max_));
  1284. if (it == end(frame_rates))
  1285. return true;
  1286. fps_found[it - begin(frame_rates)] = true;
  1287. }
  1288. return any_of(begin(fps_found), end(fps_found), [](bool b) {
  1289. return !b;
  1290. });
  1291. }
  1292. static bool update_frame_rate_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1293. {
  1294. if (!p)
  1295. p = obs_properties_get(props, "frame_rate");
  1296. if (!p)
  1297. return false;
  1298. auto valid_dims = conf.dims();
  1299. auto frame_rates = enumerate_frame_rates(conf.dev(), valid_dims);
  1300. bool was_enabled = obs_property_enabled(p);
  1301. obs_property_set_enabled(p, !frame_rates.empty());
  1302. if (!frame_rate_property_needs_update(p, frame_rates))
  1303. return was_enabled != obs_property_enabled(p);
  1304. obs_property_frame_rate_fps_ranges_clear(p);
  1305. for (auto &pair : frame_rates)
  1306. obs_property_frame_rate_fps_range_add(p, pair.first, pair.second);
  1307. return true;
  1308. }
  1309. static vector<AVCaptureDeviceFormat *> enumerate_formats(AVCaptureDevice *dev, const CMVideoDimensions &dims,
  1310. const media_frames_per_second &fps)
  1311. {
  1312. vector<AVCaptureDeviceFormat *> result;
  1313. find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format) {
  1314. result.push_back(format);
  1315. return false;
  1316. });
  1317. return result;
  1318. }
  1319. static bool input_format_property_needs_update(obs_property_t *p, const vector<AVCaptureDeviceFormat *> &formats,
  1320. const FourCharCode *fourcc_)
  1321. {
  1322. bool fourcc_found = !fourcc_;
  1323. vector<bool> if_found(formats.size());
  1324. auto num = obs_property_list_item_count(p);
  1325. for (size_t i = 1; i < num; i++) { // skip auto entry
  1326. FourCharCode fourcc = static_cast<FourCharCode>(obs_property_list_item_int(p, i));
  1327. fourcc_found = fourcc_found || fourcc == *fourcc_;
  1328. auto pos = find_if(begin(formats), end(formats), [&](AVCaptureDeviceFormat *format) {
  1329. FourCharCode fourcc_ = 0;
  1330. format_description_subtype_name(format.formatDescription, &fourcc_);
  1331. return fourcc_ == fourcc;
  1332. });
  1333. if (pos == end(formats))
  1334. return true;
  1335. if_found[pos - begin(formats)] = true;
  1336. }
  1337. return fourcc_found || any_of(begin(if_found), end(if_found), [](bool b) {
  1338. return !b;
  1339. });
  1340. }
  1341. static bool update_input_format_property(obs_properties_t *props, const config_helper &conf,
  1342. obs_property_t *p = nullptr)
  1343. {
  1344. if (!p)
  1345. p = obs_properties_get(props, "input_format");
  1346. if (!p)
  1347. return false;
  1348. auto update_enabled = [&](bool enabled) {
  1349. bool was_enabled = obs_property_enabled(p);
  1350. obs_property_set_enabled(p, enabled);
  1351. return was_enabled != enabled;
  1352. };
  1353. auto valid_dims = conf.dims();
  1354. auto valid_fps = conf.fps();
  1355. auto valid_if = conf.input_format();
  1356. if (!valid_dims || !valid_fps)
  1357. return update_enabled(false);
  1358. auto formats = enumerate_formats(conf.dev(), *valid_dims, *valid_fps);
  1359. if (!input_format_property_needs_update(p, formats, valid_if))
  1360. return update_enabled(!formats.empty());
  1361. while (obs_property_list_item_count(p) > 1)
  1362. obs_property_list_item_remove(p, 1);
  1363. bool fourcc_found = !valid_if || *valid_if == INPUT_FORMAT_AUTO;
  1364. for (auto &format : formats) {
  1365. FourCharCode fourcc = 0;
  1366. const char *name = format_description_subtype_name(format.formatDescription, &fourcc);
  1367. obs_property_list_add_int(p, name, fourcc);
  1368. fourcc_found = fourcc_found || fourcc == *valid_if;
  1369. }
  1370. if (!fourcc_found) {
  1371. const char *name = fourcc_subtype_name(*valid_if);
  1372. obs_property_list_add_int(p, name, *valid_if);
  1373. }
  1374. return update_enabled(!formats.empty());
  1375. }
  1376. static bool update_int_list_property(obs_property_t *p, const int *val, const size_t count,
  1377. const char *localization_name)
  1378. {
  1379. size_t num = obs_property_list_item_count(p);
  1380. if (num > count) {
  1381. if (!val || obs_property_list_item_int(p, count) != *val) {
  1382. obs_property_list_item_remove(p, count);
  1383. if (!val)
  1384. return true;
  1385. } else {
  1386. return false;
  1387. }
  1388. }
  1389. if (!val)
  1390. return false;
  1391. DStr buf, label;
  1392. dstr_printf(buf, "%d", *val);
  1393. dstr_init_copy(label, obs_module_text(localization_name));
  1394. dstr_replace(label, "%1", buf->array);
  1395. size_t idx = obs_property_list_add_int(p, label->array, *val);
  1396. obs_property_list_item_disable(p, idx, true);
  1397. return true;
  1398. }
  1399. template<typename Func>
  1400. static bool update_int_list_property(const char *prop_name, const char *localization_name, size_t count, int,
  1401. bool (*valid_func)(int), obs_properties_t *props, const config_helper &conf,
  1402. obs_property_t *p, Func)
  1403. {
  1404. auto ref = get_ref(props);
  1405. if (!p)
  1406. p = obs_properties_get(props, prop_name);
  1407. int val = static_cast<int>(obs_data_get_int(conf.settings, prop_name));
  1408. av_video_info vi;
  1409. if (ref)
  1410. vi = ref->video_info.read();
  1411. bool params_valid = vi.video_params_valid;
  1412. bool enabled = obs_property_enabled(p);
  1413. bool should_enable = false;
  1414. if ((params_valid && format_is_yuv(ref->frame.format)) || !valid_func(val))
  1415. should_enable = true;
  1416. obs_property_set_enabled(p, should_enable);
  1417. bool updated = enabled != should_enable;
  1418. updated = update_int_list_property(p, valid_func(val) ? nullptr : &val, count, localization_name) || updated;
  1419. if (!should_enable) {
  1420. return updated;
  1421. }
  1422. return updated;
  1423. }
  1424. static bool update_color_space_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1425. {
  1426. return update_int_list_property("color_space", TEXT_COLOR_UNKNOWN_NAME, 4, COLOR_SPACE_AUTO, color_space_valid,
  1427. props, conf, p, [](av_video_info vi) {
  1428. return vi.colorspace;
  1429. });
  1430. }
  1431. static bool update_video_range_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1432. {
  1433. return update_int_list_property("video_range", TEXT_RANGE_UNKNOWN_NAME, 5, VIDEO_RANGE_AUTO, video_range_valid,
  1434. props, conf, p, [](av_video_info vi) {
  1435. return vi.video_range;
  1436. });
  1437. }
  1438. static bool properties_device_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1439. {
  1440. NSString *uid = get_string(settings, "device");
  1441. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1442. NSString *name = get_string(settings, "device_name");
  1443. bool dev_list_updated = update_device_list(p, uid, name, !dev && uid.length);
  1444. p = obs_properties_get(props, "preset");
  1445. bool preset_list_changed = check_preset(dev, p, settings);
  1446. config_helper conf {settings};
  1447. bool res_changed = update_resolution_property(props, conf);
  1448. bool fps_changed = update_frame_rate_property(props, conf);
  1449. bool if_changed = update_input_format_property(props, conf);
  1450. return preset_list_changed || dev_list_updated || res_changed || fps_changed || if_changed;
  1451. }
  1452. static bool properties_use_preset_changed(obs_properties_t *props, obs_property_t *, obs_data_t *settings)
  1453. {
  1454. auto use_preset = obs_data_get_bool(settings, "use_preset");
  1455. config_helper conf {settings};
  1456. bool updated = false;
  1457. bool visible = false;
  1458. obs_property_t *p = nullptr;
  1459. auto noop = [](obs_properties_t *, const config_helper &, obs_property_t *) {
  1460. return false;
  1461. };
  1462. #define UPDATE_PROPERTY(prop, uses_preset, func) \
  1463. p = obs_properties_get(props, prop); \
  1464. visible = use_preset == uses_preset; \
  1465. updated = obs_property_visible(p) != visible || updated; \
  1466. obs_property_set_visible(p, visible); \
  1467. updated = func(props, conf, p) || updated;
  1468. UPDATE_PROPERTY("preset", true, noop);
  1469. UPDATE_PROPERTY("resolution", false, update_resolution_property);
  1470. UPDATE_PROPERTY("frame_rate", false, update_frame_rate_property);
  1471. UPDATE_PROPERTY("input_format", false, update_input_format_property);
  1472. UPDATE_PROPERTY("color_space", false, update_color_space_property);
  1473. UPDATE_PROPERTY("video_range", false, update_video_range_property);
  1474. return updated;
  1475. }
  1476. static bool properties_preset_changed(obs_properties_t *, obs_property_t *p, obs_data_t *settings)
  1477. {
  1478. NSString *uid = get_string(settings, "device");
  1479. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1480. bool preset_list_changed = check_preset(dev, p, settings);
  1481. return preset_list_changed;
  1482. }
  1483. static bool properties_resolution_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1484. {
  1485. config_helper conf {settings};
  1486. bool res_updated = update_resolution_property(props, conf, p);
  1487. bool fps_updated = update_frame_rate_property(props, conf);
  1488. bool if_updated = update_input_format_property(props, conf);
  1489. bool cs_updated = update_color_space_property(props, conf);
  1490. bool cr_updated = update_video_range_property(props, conf);
  1491. return res_updated || fps_updated || if_updated || cs_updated || cr_updated;
  1492. }
  1493. static bool properties_frame_rate_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1494. {
  1495. config_helper conf {settings};
  1496. bool fps_updated = update_frame_rate_property(props, conf, p);
  1497. bool if_updated = update_input_format_property(props, conf);
  1498. bool cs_updated = update_color_space_property(props, conf);
  1499. bool cr_updated = update_video_range_property(props, conf);
  1500. return fps_updated || if_updated || cs_updated || cr_updated;
  1501. }
  1502. static bool properties_input_format_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1503. {
  1504. config_helper conf {settings};
  1505. bool if_updated = update_input_format_property(props, conf, p);
  1506. bool cs_updated = update_color_space_property(props, conf);
  1507. bool cr_updated = update_video_range_property(props, conf);
  1508. return if_updated || cs_updated || cr_updated;
  1509. }
  1510. static bool properties_color_space_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1511. {
  1512. config_helper conf {settings};
  1513. return update_color_space_property(props, conf, p);
  1514. }
  1515. static bool properties_video_range_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1516. {
  1517. config_helper conf {settings};
  1518. return update_video_range_property(props, conf, p);
  1519. }
  1520. static void add_properties_param(obs_properties_t *props, av_capture *capture)
  1521. {
  1522. auto param = unique_ptr<properties_param>(new properties_param(capture));
  1523. obs_properties_set_param(props, param.release(), [](void *param) {
  1524. delete static_cast<properties_param *>(param);
  1525. });
  1526. }
  1527. static void add_preset_properties(obs_properties_t *props)
  1528. {
  1529. obs_property_t *preset_list =
  1530. obs_properties_add_list(props, "preset", TEXT_PRESET, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1531. for (NSString *preset in presets())
  1532. obs_property_list_add_string(preset_list, preset_names(preset).UTF8String, preset.UTF8String);
  1533. obs_property_set_modified_callback(preset_list, properties_preset_changed);
  1534. }
  1535. static void add_manual_properties(obs_properties_t *props)
  1536. {
  1537. obs_property_t *resolutions =
  1538. obs_properties_add_list(props, "resolution", TEXT_RESOLUTION, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1539. obs_property_set_enabled(resolutions, false);
  1540. obs_property_set_modified_callback(resolutions, properties_resolution_changed);
  1541. obs_property_t *frame_rates = obs_properties_add_frame_rate(props, "frame_rate", TEXT_FRAME_RATE);
  1542. /*obs_property_frame_rate_option_add(frame_rates, "match obs",
  1543. TEXT_MATCH_OBS);*/
  1544. obs_property_set_enabled(frame_rates, false);
  1545. obs_property_set_modified_callback(frame_rates, properties_frame_rate_changed);
  1546. obs_property_t *input_format =
  1547. obs_properties_add_list(props, "input_format", TEXT_INPUT_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1548. obs_property_list_add_int(input_format, TEXT_AUTO, INPUT_FORMAT_AUTO);
  1549. obs_property_set_enabled(input_format, false);
  1550. obs_property_set_modified_callback(input_format, properties_input_format_changed);
  1551. obs_property_t *color_space =
  1552. obs_properties_add_list(props, "color_space", TEXT_COLOR_SPACE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1553. obs_property_list_add_int(color_space, TEXT_AUTO, COLOR_SPACE_AUTO);
  1554. obs_property_list_add_int(color_space, "Rec. 601", VIDEO_CS_601);
  1555. obs_property_list_add_int(color_space, "Rec. 709", VIDEO_CS_709);
  1556. obs_property_set_enabled(color_space, false);
  1557. obs_property_set_modified_callback(color_space, properties_color_space_changed);
  1558. #define ADD_RANGE(x) obs_property_list_add_int(video_range, TEXT_##x, VIDEO_##x)
  1559. obs_property_t *video_range =
  1560. obs_properties_add_list(props, "video_range", TEXT_VIDEO_RANGE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1561. obs_property_list_add_int(video_range, TEXT_AUTO, VIDEO_RANGE_AUTO);
  1562. ADD_RANGE(RANGE_PARTIAL);
  1563. ADD_RANGE(RANGE_FULL);
  1564. obs_property_set_enabled(video_range, false);
  1565. obs_property_set_modified_callback(video_range, properties_video_range_changed);
  1566. #undef ADD_RANGE
  1567. }
  1568. static obs_properties_t *av_capture_properties(void *data)
  1569. {
  1570. obs_properties_t *props = obs_properties_create();
  1571. obs_property_t *dev_list =
  1572. obs_properties_add_list(props, "device", TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1573. obs_property_list_add_string(dev_list, "", "");
  1574. NSMutableArray *device_types = [NSMutableArray
  1575. arrayWithObjects:AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown, nil];
  1576. if (__builtin_available(macOS 13.0, *)) {
  1577. [device_types addObject:AVCaptureDeviceTypeDeskViewCamera];
  1578. }
  1579. AVCaptureDeviceDiscoverySession *video_discovery =
  1580. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:device_types mediaType:AVMediaTypeVideo
  1581. position:AVCaptureDevicePositionUnspecified];
  1582. for (AVCaptureDevice *dev in [video_discovery devices]) {
  1583. obs_property_list_add_string(dev_list, dev.localizedName.UTF8String, dev.uniqueID.UTF8String);
  1584. }
  1585. AVCaptureDeviceDiscoverySession *muxed_discovery =
  1586. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:device_types mediaType:AVMediaTypeMuxed
  1587. position:AVCaptureDevicePositionUnspecified];
  1588. for (AVCaptureDevice *dev in [muxed_discovery devices]) {
  1589. obs_property_list_add_string(dev_list, dev.localizedName.UTF8String, dev.uniqueID.UTF8String);
  1590. }
  1591. obs_property_set_modified_callback(dev_list, properties_device_changed);
  1592. obs_property_t *use_preset = obs_properties_add_bool(props, "use_preset", TEXT_USE_PRESET);
  1593. obs_property_set_modified_callback(use_preset, properties_use_preset_changed);
  1594. add_preset_properties(props);
  1595. add_manual_properties(props);
  1596. obs_properties_add_bool(props, "buffering", obs_module_text("Buffering"));
  1597. if (data) {
  1598. struct av_capture *capture = static_cast<av_capture *>(data);
  1599. add_properties_param(props, capture);
  1600. OBSDataAutoRelease current_settings = obs_source_get_settings(capture->source);
  1601. if (!obs_data_get_bool(current_settings, "enable_audio")) {
  1602. auto cb = [](obs_properties_t *, obs_property_t *prop, void *data) {
  1603. struct av_capture *capture = static_cast<av_capture *>(data);
  1604. OBSDataAutoRelease settings = obs_data_create();
  1605. obs_data_set_bool(settings, "enable_audio", true);
  1606. obs_source_update(capture->source, settings);
  1607. // Enable all audio tracks
  1608. obs_source_set_audio_mixers(capture->source, 0x3F);
  1609. obs_property_set_visible(prop, false);
  1610. return true;
  1611. };
  1612. obs_properties_add_button2(props, "enable_audio_button", obs_module_text("EnableAudio"), cb, capture);
  1613. }
  1614. }
  1615. return props;
  1616. }
  1617. static void switch_device(av_capture *capture, NSString *uid, obs_data_t *settings)
  1618. {
  1619. if (!uid)
  1620. return;
  1621. if (capture->device)
  1622. remove_device(capture);
  1623. capture->uid = uid;
  1624. if (!uid.length) {
  1625. AVLOG(LOG_INFO, "No device selected, stopping capture");
  1626. return;
  1627. }
  1628. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1629. if (!dev) {
  1630. AVLOG(LOG_WARNING, "Device with unique id '%s' not found", uid.UTF8String);
  1631. return;
  1632. }
  1633. capture_device(capture, dev, settings);
  1634. }
  1635. static void update_preset(av_capture *capture, obs_data_t *settings)
  1636. {
  1637. unlock_device(capture);
  1638. NSString *preset = get_string(settings, "preset");
  1639. if (![capture->device supportsAVCaptureSessionPreset:preset]) {
  1640. AVLOG(LOG_WARNING, "Preset %s not available", preset.UTF8String);
  1641. preset = select_preset(capture->device, preset);
  1642. }
  1643. capture->session.sessionPreset = preset;
  1644. AVLOG(LOG_INFO, "Selected preset %s", preset.UTF8String);
  1645. start_capture(capture);
  1646. }
  1647. static void update_manual(av_capture *capture, obs_data_t *settings)
  1648. {
  1649. if (init_manual(capture, capture->device, settings))
  1650. start_capture(capture);
  1651. }
  1652. static void av_capture_update(void *data, obs_data_t *settings)
  1653. {
  1654. auto capture = static_cast<av_capture *>(data);
  1655. NSString *uid = get_string(settings, "device");
  1656. if (!capture->device || ![capture->device.uniqueID isEqualToString:uid])
  1657. return switch_device(capture, uid, settings);
  1658. if ((capture->use_preset = obs_data_get_bool(settings, "use_preset"))) {
  1659. update_preset(capture, settings);
  1660. } else {
  1661. update_manual(capture, settings);
  1662. }
  1663. av_capture_enable_buffering(capture, obs_data_get_bool(settings, "buffering"));
  1664. av_capture_set_audio_active(capture, obs_data_get_bool(settings, "enable_audio") &&
  1665. ([capture->device hasMediaType:AVMediaTypeAudio] ||
  1666. [capture->device hasMediaType:AVMediaTypeMuxed]));
  1667. }
  1668. OBS_DECLARE_MODULE()
  1669. OBS_MODULE_USE_DEFAULT_LOCALE("mac-avcapture", "en-US")
  1670. MODULE_EXPORT const char *obs_module_description(void)
  1671. {
  1672. return "MacOS AVFoundation Capture source";
  1673. }
  1674. bool obs_module_load(void)
  1675. {
  1676. // Enable iOS device to show up as AVCapture devices
  1677. // From WWDC video 2014 #508 at 5:34
  1678. // https://developer.apple.com/videos/wwdc/2014/#508
  1679. CMIOObjectPropertyAddress prop = {kCMIOHardwarePropertyAllowScreenCaptureDevices, kCMIOObjectPropertyScopeGlobal,
  1680. kCMIOObjectPropertyElementMain};
  1681. UInt32 allow = 1;
  1682. CMIOObjectSetPropertyData(kCMIOObjectSystemObject, &prop, 0, NULL, sizeof(allow), &allow);
  1683. obs_source_info av_capture_info = {
  1684. .id = "av_capture_input",
  1685. .type = OBS_SOURCE_TYPE_INPUT,
  1686. .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  1687. OBS_SOURCE_CAP_OBSOLETE | OBS_SOURCE_DEPRECATED,
  1688. .get_name = av_capture_getname,
  1689. .create = av_capture_create,
  1690. .destroy = av_capture_destroy,
  1691. .get_defaults = av_capture_defaults_v1,
  1692. .get_properties = av_capture_properties,
  1693. .update = av_capture_update,
  1694. .icon_type = OBS_ICON_TYPE_CAMERA,
  1695. };
  1696. obs_register_source(&av_capture_info);
  1697. av_capture_info.version = 2;
  1698. av_capture_info.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  1699. OBS_SOURCE_DEPRECATED;
  1700. av_capture_info.get_defaults = av_capture_defaults_v2;
  1701. obs_register_source(&av_capture_info);
  1702. return true;
  1703. }