1
0

av-capture.mm 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203
  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")
  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 OBSAVCaptureDelegate
  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. OBSAVCaptureDelegate *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 = (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] = (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] = (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 = (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] = (uint8_t *) list->mBuffers[i].mData;
  488. audio->frames = (uint32_t) CMSampleBufferGetNumSamples(sample_buffer);
  489. CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(sample_buffer);
  490. const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(desc);
  491. audio->samples_per_sec = (uint32_t) asbd->mSampleRate;
  492. audio->speakers = (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 OBSAVCaptureDelegate
  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 = [[OBSAVCaptureDelegate 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 ((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 = (FourCharCode) input_format;
  812. SCOPE_EXIT
  813. {
  814. bool refresh = false;
  815. if (input_format != actual_format) {
  816. refresh = obs_data_get_autoselect_int(settings, "input_format") != actual_format;
  817. obs_data_set_autoselect_int(settings, "input_format", actual_format);
  818. } else {
  819. refresh = obs_data_has_autoselect_value(settings, "input_format");
  820. obs_data_unset_autoselect_value(settings, "input_format");
  821. }
  822. if (refresh)
  823. obs_source_update_properties(capture->source);
  824. };
  825. capture->requested_colorspace = (int) obs_data_get_int(settings, "color_space");
  826. if (!color_space_valid(capture->requested_colorspace)) {
  827. AVLOG(LOG_WARNING, "Unsupported color space: %d", capture->requested_colorspace);
  828. return false;
  829. }
  830. capture->requested_video_range = (int) obs_data_get_int(settings, "video_range");
  831. if (!video_range_valid(capture->requested_video_range)) {
  832. AVLOG(LOG_WARNING, "Unsupported color range: %d", capture->requested_video_range);
  833. return false;
  834. }
  835. CMVideoDimensions dims {};
  836. if (!get_resolution(settings, dims)) {
  837. AVLOG(LOG_WARNING, "Could not load resolution");
  838. return false;
  839. }
  840. media_frames_per_second fps {};
  841. if (!obs_data_get_frames_per_second(settings, "frame_rate", &fps, nullptr)) {
  842. AVLOG(LOG_WARNING, "Could not load frame rate");
  843. return false;
  844. }
  845. AVCaptureDeviceFormat *format = nullptr;
  846. find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format_) {
  847. auto desc = format_.formatDescription;
  848. auto fourcc = CMFormatDescriptionGetMediaSubType(desc);
  849. if (input_format != INPUT_FORMAT_AUTO && fourcc != input_format)
  850. return false;
  851. actual_format = fourcc;
  852. format = format_;
  853. return true;
  854. });
  855. if (!format) {
  856. AVLOG(LOG_WARNING,
  857. "Frame rate is not supported: %g FPS "
  858. "(%u/%u)",
  859. media_frames_per_second_to_fps(fps), fps.numerator, fps.denominator);
  860. return false;
  861. }
  862. if (!lock_device(capture, dev))
  863. return false;
  864. const char *if_name = input_format == INPUT_FORMAT_AUTO ? "Auto" : fourcc_subtype_name((FourCharCode) input_format);
  865. #define IF_AUTO(x) (input_format != INPUT_FORMAT_AUTO ? "" : x)
  866. AVLOG(LOG_INFO,
  867. "Capturing '%s' (%s):\n"
  868. " Resolution: %ux%u\n"
  869. " FPS: %g (%" PRIu32 "/%" PRIu32 ")\n"
  870. " Frame interval: %g" NBSP "s\n"
  871. " Input format: %s%s%s (%s)%s\n"
  872. " Requested color space: %s (%d)\n"
  873. " Requested video range: %s (%d)\n"
  874. " Using format: %s",
  875. dev.localizedName.UTF8String, dev.uniqueID.UTF8String, dims.width, dims.height,
  876. media_frames_per_second_to_fps(fps), fps.numerator, fps.denominator,
  877. media_frames_per_second_to_frame_interval(fps), if_name, IF_AUTO(" (actual: "),
  878. IF_AUTO(fourcc_subtype_name(actual_format)), AV_FOURCC_STR(actual_format), IF_AUTO(")"),
  879. color_space_name(capture->requested_colorspace), capture->requested_colorspace,
  880. video_range_name(capture->requested_video_range), capture->requested_video_range,
  881. format.description.UTF8String);
  882. #undef IF_AUTO
  883. dev.activeFormat = format;
  884. dev.activeVideoMinFrameDuration = convert(fps);
  885. dev.activeVideoMaxFrameDuration = convert(fps);
  886. capture->video_info.update([&](av_video_info &vi) {
  887. vi.video_params_valid = false;
  888. });
  889. return true;
  890. }
  891. static inline void av_capture_set_audio_active(av_capture *capture, bool active)
  892. {
  893. obs_source_set_audio_active(capture->source, active);
  894. capture->enable_audio = active;
  895. }
  896. static void capture_device(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  897. {
  898. const char *name = dev.localizedName.UTF8String;
  899. obs_data_set_string(settings, "device_name", name);
  900. obs_data_set_string(settings, "device", dev.uniqueID.UTF8String);
  901. AVLOG(LOG_INFO, "Selected device '%s'", name);
  902. if (@available(macOS 12.0, *)) {
  903. if ([dev isPortraitEffectActive])
  904. AVLOG(LOG_WARNING, "Portrait effect is active on selected device");
  905. }
  906. if (@available(macOS 12.3, *)) {
  907. if ([dev isCenterStageActive])
  908. AVLOG(LOG_WARNING, "Center Stage effect is active on selected device");
  909. }
  910. if (@available(macOS 13.0, *)) {
  911. if ([dev isStudioLightActive])
  912. AVLOG(LOG_WARNING, "Studio Light effect is active on selected device");
  913. }
  914. if ((capture->use_preset = obs_data_get_bool(settings, "use_preset"))) {
  915. if (!init_preset(capture, dev, settings))
  916. return;
  917. } else {
  918. if (!init_manual(capture, dev, settings))
  919. return;
  920. }
  921. av_capture_set_audio_active(capture,
  922. obs_data_get_bool(settings, "enable_audio") &&
  923. ([dev hasMediaType:AVMediaTypeAudio] || [dev hasMediaType:AVMediaTypeMuxed]));
  924. if (!init_device_input(capture, dev))
  925. return;
  926. AVCaptureInputPort *port = capture->device_input.ports[0];
  927. capture->has_clock = [port respondsToSelector:@selector(clock)];
  928. capture->device = dev;
  929. start_capture(capture);
  930. return;
  931. }
  932. static inline void handle_disconnect_capture(av_capture *capture, AVCaptureDevice *dev)
  933. {
  934. if (![dev.uniqueID isEqualTo:capture->uid])
  935. return;
  936. if (!capture->device) {
  937. AVLOG(LOG_INFO, "Received disconnect for inactive device '%s'", capture->uid.UTF8String);
  938. return;
  939. }
  940. AVLOG(LOG_WARNING, "Device with unique ID '%s' disconnected", dev.uniqueID.UTF8String);
  941. remove_device(capture);
  942. }
  943. static inline void handle_disconnect(av_capture *capture, AVCaptureDevice *dev)
  944. {
  945. if (!dev)
  946. return;
  947. handle_disconnect_capture(capture, dev);
  948. obs_source_update_properties(capture->source);
  949. }
  950. static inline void handle_connect_capture(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  951. {
  952. if (![dev.uniqueID isEqualTo:capture->uid])
  953. return;
  954. if (capture->device) {
  955. AVLOG(LOG_ERROR, "Received connect for in-use device '%s'", capture->uid.UTF8String);
  956. return;
  957. }
  958. AVLOG(LOG_INFO,
  959. "Device with unique ID '%s' connected, "
  960. "resuming capture",
  961. dev.uniqueID.UTF8String);
  962. capture_device(capture, dev, settings);
  963. }
  964. static inline void handle_connect(av_capture *capture, AVCaptureDevice *dev, obs_data_t *settings)
  965. {
  966. if (!dev)
  967. return;
  968. handle_connect_capture(capture, dev, settings);
  969. obs_source_update_properties(capture->source);
  970. }
  971. static bool av_capture_init(av_capture *capture, obs_data_t *settings)
  972. {
  973. if (!init_session(capture))
  974. return false;
  975. capture->uid = get_string(settings, "device");
  976. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  977. capture->disconnect_observer.reset([nc addObserverForName:AVCaptureDeviceWasDisconnectedNotification object:nil
  978. queue:[NSOperationQueue mainQueue]
  979. usingBlock:^(NSNotification *note) {
  980. handle_disconnect(capture, note.object);
  981. }]);
  982. capture->connect_observer.reset([nc addObserverForName:AVCaptureDeviceWasConnectedNotification object:nil
  983. queue:[NSOperationQueue mainQueue]
  984. usingBlock:^(NSNotification *note) {
  985. handle_connect(capture, note.object, settings);
  986. }]);
  987. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:capture->uid];
  988. if (!dev) {
  989. if (capture->uid.length < 1)
  990. AVLOG(LOG_INFO, "No device selected");
  991. else
  992. AVLOG(LOG_WARNING,
  993. "Could not initialize device "
  994. "with unique ID '%s'",
  995. capture->uid.UTF8String);
  996. return true;
  997. }
  998. capture_device(capture, dev, settings);
  999. return true;
  1000. }
  1001. static void *av_capture_create(obs_data_t *settings, obs_source_t *source)
  1002. {
  1003. unique_ptr<av_capture> capture;
  1004. try {
  1005. capture.reset(new av_capture());
  1006. } catch (...) {
  1007. return capture.release();
  1008. }
  1009. capture->source = source;
  1010. if (!av_capture_init(capture.get(), settings)) {
  1011. AVLOG(LOG_ERROR, "av_capture_init failed");
  1012. return nullptr;
  1013. }
  1014. av_capture_enable_buffering(capture.get(), obs_data_get_bool(settings, "buffering"));
  1015. return capture.release();
  1016. }
  1017. static NSArray *presets(void)
  1018. {
  1019. return @[
  1020. //AVCaptureSessionPresetiFrame1280x720,
  1021. //AVCaptureSessionPresetiFrame960x540,
  1022. AVCaptureSessionPreset3840x2160, AVCaptureSessionPreset1920x1080, AVCaptureSessionPreset1280x720,
  1023. AVCaptureSessionPreset960x540, AVCaptureSessionPreset640x480, AVCaptureSessionPreset352x288,
  1024. AVCaptureSessionPreset320x240, AVCaptureSessionPresetHigh,
  1025. //AVCaptureSessionPresetMedium,
  1026. //AVCaptureSessionPresetLow,
  1027. //AVCaptureSessionPresetPhoto,
  1028. ];
  1029. }
  1030. static NSString *preset_names(NSString *preset)
  1031. {
  1032. NSDictionary *preset_names = nil;
  1033. preset_names = @ {
  1034. AVCaptureSessionPresetLow: @"Low",
  1035. AVCaptureSessionPresetMedium: @"Medium",
  1036. AVCaptureSessionPresetHigh: @"High",
  1037. AVCaptureSessionPreset320x240: @"320x240",
  1038. AVCaptureSessionPreset352x288: @"352x288",
  1039. AVCaptureSessionPreset640x480: @"640x480",
  1040. AVCaptureSessionPreset960x540: @"960x540",
  1041. AVCaptureSessionPreset1280x720: @"1280x720",
  1042. AVCaptureSessionPreset1920x1080: @"1920x1080",
  1043. AVCaptureSessionPreset3840x2160: @"3840x2160",
  1044. AVCaptureSessionPresetHigh: @"High",
  1045. };
  1046. NSString *name = preset_names[preset];
  1047. if (name)
  1048. return name;
  1049. return [NSString stringWithFormat:@"Unknown (%@)", preset];
  1050. }
  1051. inline static void av_capture_defaults(obs_data_t *settings, bool enable_audio_and_high_preset)
  1052. {
  1053. obs_data_set_default_string(settings, "uid", "");
  1054. obs_data_set_default_bool(settings, "use_preset", true);
  1055. obs_data_set_default_string(settings, "preset",
  1056. enable_audio_and_high_preset ? AVCaptureSessionPresetHigh.UTF8String
  1057. : AVCaptureSessionPreset1280x720.UTF8String);
  1058. obs_data_set_default_int(settings, "input_format", INPUT_FORMAT_AUTO);
  1059. obs_data_set_default_int(settings, "color_space", COLOR_SPACE_AUTO);
  1060. obs_data_set_default_int(settings, "video_range", VIDEO_RANGE_AUTO);
  1061. obs_data_set_default_bool(settings, "enable_audio", enable_audio_and_high_preset);
  1062. }
  1063. static void av_capture_defaults_v1(obs_data_t *settings)
  1064. {
  1065. av_capture_defaults(settings, false);
  1066. }
  1067. static void av_capture_defaults_v2(obs_data_t *settings)
  1068. {
  1069. av_capture_defaults(settings, true);
  1070. }
  1071. static bool update_device_list(obs_property_t *list, NSString *uid, NSString *name, bool disconnected)
  1072. {
  1073. bool dev_found = false;
  1074. bool list_modified = false;
  1075. size_t size = obs_property_list_item_count(list);
  1076. for (size_t i = 0; i < size;) {
  1077. const char *uid_ = obs_property_list_item_string(list, i);
  1078. bool found = [uid isEqualToString:@(uid_ ? uid_ : "")];
  1079. bool disabled = obs_property_list_item_disabled(list, i);
  1080. if (!found && !disabled) {
  1081. i += 1;
  1082. continue;
  1083. }
  1084. if (disabled && !found) {
  1085. list_modified = true;
  1086. obs_property_list_item_remove(list, i);
  1087. continue;
  1088. }
  1089. if (disabled != disconnected)
  1090. list_modified = true;
  1091. dev_found = true;
  1092. obs_property_list_item_disable(list, i, disconnected);
  1093. i += 1;
  1094. }
  1095. if (dev_found)
  1096. return list_modified;
  1097. size_t idx = obs_property_list_add_string(list, name.UTF8String, uid.UTF8String);
  1098. obs_property_list_item_disable(list, idx, disconnected);
  1099. return true;
  1100. }
  1101. static void fill_presets(AVCaptureDevice *dev, obs_property_t *list, NSString *current_preset)
  1102. {
  1103. obs_property_list_clear(list);
  1104. bool preset_found = false;
  1105. for (NSString *preset in presets()) {
  1106. bool is_current = [preset isEqualToString:current_preset];
  1107. bool supported = dev && [dev supportsAVCaptureSessionPreset:preset];
  1108. if (is_current)
  1109. preset_found = true;
  1110. if (!supported && !is_current)
  1111. continue;
  1112. size_t idx = obs_property_list_add_string(list, preset_names(preset).UTF8String, preset.UTF8String);
  1113. obs_property_list_item_disable(list, idx, !supported);
  1114. }
  1115. if (preset_found)
  1116. return;
  1117. size_t idx = obs_property_list_add_string(list, preset_names(current_preset).UTF8String, current_preset.UTF8String);
  1118. obs_property_list_item_disable(list, idx, true);
  1119. }
  1120. static bool check_preset(AVCaptureDevice *dev, obs_property_t *list, obs_data_t *settings)
  1121. {
  1122. NSString *current_preset = get_string(settings, "preset");
  1123. size_t size = obs_property_list_item_count(list);
  1124. NSMutableSet *listed = [NSMutableSet setWithCapacity:size];
  1125. for (size_t i = 0; i < size; i++)
  1126. [listed addObject:@(obs_property_list_item_string(list, i))];
  1127. bool presets_changed = false;
  1128. for (NSString *preset in presets()) {
  1129. bool is_listed = [listed member:preset] != nil;
  1130. bool supported = dev && [dev supportsAVCaptureSessionPreset:preset];
  1131. if (supported == is_listed)
  1132. continue;
  1133. presets_changed = true;
  1134. }
  1135. if (!presets_changed && [listed member:current_preset] != nil)
  1136. return false;
  1137. fill_presets(dev, list, current_preset);
  1138. return true;
  1139. }
  1140. static bool autoselect_preset(AVCaptureDevice *dev, obs_data_t *settings)
  1141. {
  1142. NSString *preset = get_string(settings, "preset");
  1143. if (!dev || [dev supportsAVCaptureSessionPreset:preset]) {
  1144. if (obs_data_has_autoselect_value(settings, "preset")) {
  1145. obs_data_unset_autoselect_value(settings, "preset");
  1146. return true;
  1147. }
  1148. } else {
  1149. preset = select_preset(dev, preset);
  1150. const char *autoselect = obs_data_get_autoselect_string(settings, "preset");
  1151. if (![preset isEqualToString:@(autoselect)]) {
  1152. obs_data_set_autoselect_string(settings, "preset", preset.UTF8String);
  1153. return true;
  1154. }
  1155. }
  1156. return false;
  1157. }
  1158. static CMVideoDimensions get_dimensions(AVCaptureDeviceFormat *format)
  1159. {
  1160. auto desc = format.formatDescription;
  1161. return CMVideoFormatDescriptionGetDimensions(desc);
  1162. }
  1163. using resolutions_t = vector<CMVideoDimensions>;
  1164. static resolutions_t enumerate_resolutions(AVCaptureDevice *dev)
  1165. {
  1166. resolutions_t res;
  1167. if (!dev)
  1168. return res;
  1169. res.reserve(dev.formats.count + 1);
  1170. for (AVCaptureDeviceFormat *format in dev.formats) {
  1171. auto dims = get_dimensions(format);
  1172. if (find(begin(res), end(res), dims) == end(res))
  1173. res.push_back(dims);
  1174. }
  1175. return res;
  1176. }
  1177. static void sort_resolutions(vector<CMVideoDimensions> &resolutions)
  1178. {
  1179. auto cmp = [](const CMVideoDimensions &a, const CMVideoDimensions &b) {
  1180. return a.width * a.height > b.width * b.height;
  1181. };
  1182. sort(begin(resolutions), end(resolutions), cmp);
  1183. }
  1184. static void data_set_resolution(obs_data_t *data, const CMVideoDimensions &dims)
  1185. {
  1186. obs_data_set_int(data, "width", dims.width);
  1187. obs_data_set_int(data, "height", dims.height);
  1188. }
  1189. static void data_set_resolution(const unique_ptr<obs_data_t> &data, const CMVideoDimensions &dims)
  1190. {
  1191. data_set_resolution(data.get(), dims);
  1192. }
  1193. static bool add_resolution_to_list(vector<CMVideoDimensions> &res, const CMVideoDimensions &dims)
  1194. {
  1195. if (find(begin(res), end(res), dims) != end(res))
  1196. return false;
  1197. res.push_back(dims);
  1198. return true;
  1199. }
  1200. static const char *obs_data_get_json(const unique_ptr<obs_data_t> &data)
  1201. {
  1202. return obs_data_get_json(data.get());
  1203. }
  1204. static bool operator==(const CMVideoDimensions &a, const CMVideoDimensions &b)
  1205. {
  1206. return a.width == b.width && a.height == b.height;
  1207. }
  1208. static bool resolution_property_needs_update(obs_property_t *p, const resolutions_t &resolutions)
  1209. {
  1210. vector<bool> res_found(resolutions.size());
  1211. auto num = obs_property_list_item_count(p);
  1212. for (size_t i = 1; i < num; i++) { // skip empty entry
  1213. const char *json = obs_property_list_item_string(p, i);
  1214. unique_ptr<obs_data_t> buffer {obs_data_create_from_json(json)};
  1215. CMVideoDimensions dims {};
  1216. if (!get_resolution(buffer.get(), dims))
  1217. return true;
  1218. auto pos = find(begin(resolutions), end(resolutions), dims);
  1219. if (pos == end(resolutions))
  1220. return true;
  1221. res_found[pos - begin(resolutions)] = true;
  1222. }
  1223. return any_of(begin(res_found), end(res_found), [](bool b) {
  1224. return !b;
  1225. });
  1226. }
  1227. static bool update_resolution_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1228. {
  1229. if (!p)
  1230. p = obs_properties_get(props, "resolution");
  1231. if (!p)
  1232. return false;
  1233. auto valid_dims = conf.dims();
  1234. auto resolutions = enumerate_resolutions(conf.dev());
  1235. bool unsupported = true;
  1236. if (valid_dims)
  1237. unsupported = add_resolution_to_list(resolutions, *valid_dims);
  1238. bool was_enabled = obs_property_enabled(p);
  1239. obs_property_set_enabled(p, !!conf.dev());
  1240. if (!resolution_property_needs_update(p, resolutions))
  1241. return was_enabled != obs_property_enabled(p);
  1242. sort_resolutions(resolutions);
  1243. obs_property_list_clear(p);
  1244. obs_property_list_add_string(p, "", "{}");
  1245. DStr name;
  1246. unique_ptr<obs_data_t> buffer {obs_data_create()};
  1247. for (const CMVideoDimensions &dims : resolutions) {
  1248. data_set_resolution(buffer, dims);
  1249. auto json = obs_data_get_json(buffer);
  1250. dstr_printf(name, "%dx%d", dims.width, dims.height);
  1251. size_t idx = obs_property_list_add_string(p, name->array, json);
  1252. if (unsupported && valid_dims && dims == *valid_dims)
  1253. obs_property_list_item_disable(p, idx, true);
  1254. }
  1255. return true;
  1256. }
  1257. static media_frames_per_second convert(CMTime time_)
  1258. {
  1259. media_frames_per_second res {};
  1260. clamp(res.numerator, time_.timescale);
  1261. clamp(res.denominator, time_.value);
  1262. return res;
  1263. }
  1264. using frame_rates_t = vector<pair<media_frames_per_second, media_frames_per_second>>;
  1265. static frame_rates_t enumerate_frame_rates(AVCaptureDevice *dev, const CMVideoDimensions *dims = nullptr)
  1266. {
  1267. frame_rates_t res;
  1268. if (!dev || !dims)
  1269. return res;
  1270. auto add_unique_frame_rate_range = [&](AVFrameRateRange *range) {
  1271. auto min = convert(range.maxFrameDuration);
  1272. auto max = convert(range.minFrameDuration);
  1273. auto pair = make_pair(min, max);
  1274. if (find(begin(res), end(res), pair) != end(res))
  1275. return;
  1276. res.push_back(pair);
  1277. };
  1278. for (AVCaptureDeviceFormat *format in dev.formats) {
  1279. if (!(get_dimensions(format) == *dims))
  1280. continue;
  1281. for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
  1282. add_unique_frame_rate_range(range);
  1283. if (CMTimeCompare(range.minFrameDuration, range.maxFrameDuration) != 0) {
  1284. blog(LOG_WARNING,
  1285. "Got actual frame rate range:"
  1286. " %g - %g "
  1287. "({%lld, %d} - {%lld, %d})",
  1288. range.minFrameRate, range.maxFrameRate, range.maxFrameDuration.value,
  1289. range.maxFrameDuration.timescale, range.minFrameDuration.value, range.minFrameDuration.timescale);
  1290. }
  1291. }
  1292. }
  1293. return res;
  1294. }
  1295. static bool operator==(const media_frames_per_second &a, const media_frames_per_second &b)
  1296. {
  1297. return a.numerator == b.numerator && a.denominator == b.denominator;
  1298. }
  1299. static bool operator!=(const media_frames_per_second &a, const media_frames_per_second &b)
  1300. {
  1301. return !(a == b);
  1302. }
  1303. static bool frame_rate_property_needs_update(obs_property_t *p, const frame_rates_t &frame_rates)
  1304. {
  1305. auto fps_num = frame_rates.size();
  1306. auto num = obs_property_frame_rate_fps_ranges_count(p);
  1307. if (fps_num != num)
  1308. return true;
  1309. vector<bool> fps_found(fps_num);
  1310. for (size_t i = 0; i < num; i++) {
  1311. auto min_ = obs_property_frame_rate_fps_range_min(p, i);
  1312. auto max_ = obs_property_frame_rate_fps_range_max(p, i);
  1313. auto it = find(begin(frame_rates), end(frame_rates), make_pair(min_, max_));
  1314. if (it == end(frame_rates))
  1315. return true;
  1316. fps_found[it - begin(frame_rates)] = true;
  1317. }
  1318. return any_of(begin(fps_found), end(fps_found), [](bool b) {
  1319. return !b;
  1320. });
  1321. }
  1322. static bool update_frame_rate_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1323. {
  1324. if (!p)
  1325. p = obs_properties_get(props, "frame_rate");
  1326. if (!p)
  1327. return false;
  1328. auto valid_dims = conf.dims();
  1329. auto frame_rates = enumerate_frame_rates(conf.dev(), valid_dims);
  1330. bool was_enabled = obs_property_enabled(p);
  1331. obs_property_set_enabled(p, !frame_rates.empty());
  1332. if (!frame_rate_property_needs_update(p, frame_rates))
  1333. return was_enabled != obs_property_enabled(p);
  1334. obs_property_frame_rate_fps_ranges_clear(p);
  1335. for (auto &pair : frame_rates)
  1336. obs_property_frame_rate_fps_range_add(p, pair.first, pair.second);
  1337. return true;
  1338. }
  1339. static vector<AVCaptureDeviceFormat *> enumerate_formats(AVCaptureDevice *dev, const CMVideoDimensions &dims,
  1340. const media_frames_per_second &fps)
  1341. {
  1342. vector<AVCaptureDeviceFormat *> result;
  1343. find_formats(fps, dev, &dims, [&](AVCaptureDeviceFormat *format) {
  1344. result.push_back(format);
  1345. return false;
  1346. });
  1347. return result;
  1348. }
  1349. static bool input_format_property_needs_update(obs_property_t *p, const vector<AVCaptureDeviceFormat *> &formats,
  1350. const FourCharCode *fourcc_)
  1351. {
  1352. bool fourcc_found = !fourcc_;
  1353. vector<bool> if_found(formats.size());
  1354. auto num = obs_property_list_item_count(p);
  1355. for (size_t i = 1; i < num; i++) { // skip auto entry
  1356. FourCharCode fourcc = (FourCharCode) obs_property_list_item_int(p, i);
  1357. fourcc_found = fourcc_found || fourcc == *fourcc_;
  1358. auto pos = find_if(begin(formats), end(formats), [&](AVCaptureDeviceFormat *format) {
  1359. FourCharCode fourcc_ = 0;
  1360. format_description_subtype_name(format.formatDescription, &fourcc_);
  1361. return fourcc_ == fourcc;
  1362. });
  1363. if (pos == end(formats))
  1364. return true;
  1365. if_found[pos - begin(formats)] = true;
  1366. }
  1367. return fourcc_found || any_of(begin(if_found), end(if_found), [](bool b) {
  1368. return !b;
  1369. });
  1370. }
  1371. static bool update_input_format_property(obs_properties_t *props, const config_helper &conf,
  1372. obs_property_t *p = nullptr)
  1373. {
  1374. if (!p)
  1375. p = obs_properties_get(props, "input_format");
  1376. if (!p)
  1377. return false;
  1378. auto update_enabled = [&](bool enabled) {
  1379. bool was_enabled = obs_property_enabled(p);
  1380. obs_property_set_enabled(p, enabled);
  1381. return was_enabled != enabled;
  1382. };
  1383. auto valid_dims = conf.dims();
  1384. auto valid_fps = conf.fps();
  1385. auto valid_if = conf.input_format();
  1386. if (!valid_dims || !valid_fps)
  1387. return update_enabled(false);
  1388. auto formats = enumerate_formats(conf.dev(), *valid_dims, *valid_fps);
  1389. if (!input_format_property_needs_update(p, formats, valid_if))
  1390. return update_enabled(!formats.empty());
  1391. while (obs_property_list_item_count(p) > 1)
  1392. obs_property_list_item_remove(p, 1);
  1393. bool fourcc_found = !valid_if || *valid_if == INPUT_FORMAT_AUTO;
  1394. for (auto &format : formats) {
  1395. FourCharCode fourcc = 0;
  1396. const char *name = format_description_subtype_name(format.formatDescription, &fourcc);
  1397. obs_property_list_add_int(p, name, fourcc);
  1398. fourcc_found = fourcc_found || fourcc == *valid_if;
  1399. }
  1400. if (!fourcc_found) {
  1401. const char *name = fourcc_subtype_name(*valid_if);
  1402. obs_property_list_add_int(p, name, *valid_if);
  1403. }
  1404. return update_enabled(!formats.empty());
  1405. }
  1406. static bool update_int_list_property(obs_property_t *p, const int *val, const size_t count,
  1407. const char *localization_name)
  1408. {
  1409. size_t num = obs_property_list_item_count(p);
  1410. if (num > count) {
  1411. if (!val || obs_property_list_item_int(p, count) != *val) {
  1412. obs_property_list_item_remove(p, count);
  1413. if (!val)
  1414. return true;
  1415. } else {
  1416. return false;
  1417. }
  1418. }
  1419. if (!val)
  1420. return false;
  1421. DStr buf, label;
  1422. dstr_printf(buf, "%d", *val);
  1423. dstr_init_copy(label, obs_module_text(localization_name));
  1424. dstr_replace(label, "%1", buf->array);
  1425. size_t idx = obs_property_list_add_int(p, label->array, *val);
  1426. obs_property_list_item_disable(p, idx, true);
  1427. return true;
  1428. }
  1429. template<typename Func>
  1430. static bool update_int_list_property(const char *prop_name, const char *localization_name, size_t count, int auto_val,
  1431. bool (*valid_func)(int), obs_properties_t *props, const config_helper &conf,
  1432. obs_property_t *p, Func get_val)
  1433. {
  1434. auto ref = get_ref(props);
  1435. if (!p)
  1436. p = obs_properties_get(props, prop_name);
  1437. int val = (int) obs_data_get_int(conf.settings, prop_name);
  1438. av_video_info vi;
  1439. if (ref)
  1440. vi = ref->video_info.read();
  1441. bool params_valid = vi.video_params_valid;
  1442. bool enabled = obs_property_enabled(p);
  1443. bool should_enable = false;
  1444. bool has_autoselect = obs_data_has_autoselect_value(conf.settings, prop_name);
  1445. if ((params_valid && format_is_yuv(ref->frame.format)) || !valid_func(val))
  1446. should_enable = true;
  1447. obs_property_set_enabled(p, should_enable);
  1448. bool updated = enabled != should_enable;
  1449. updated = update_int_list_property(p, valid_func(val) ? nullptr : &val, count, localization_name) || updated;
  1450. if (!should_enable) {
  1451. if (has_autoselect)
  1452. obs_data_unset_autoselect_value(conf.settings, prop_name);
  1453. return updated || has_autoselect;
  1454. }
  1455. bool use_autoselect = ref && val == auto_val;
  1456. if (!use_autoselect) {
  1457. if (has_autoselect)
  1458. obs_data_unset_autoselect_value(conf.settings, prop_name);
  1459. return updated || has_autoselect;
  1460. }
  1461. if (params_valid && get_val(vi) != obs_data_get_autoselect_int(conf.settings, prop_name)) {
  1462. obs_data_set_autoselect_int(conf.settings, prop_name, get_val(vi));
  1463. return true;
  1464. }
  1465. return updated;
  1466. }
  1467. static bool update_color_space_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1468. {
  1469. return update_int_list_property("color_space", TEXT_COLOR_UNKNOWN_NAME, 4, COLOR_SPACE_AUTO, color_space_valid,
  1470. props, conf, p, [](av_video_info vi) {
  1471. return vi.colorspace;
  1472. });
  1473. }
  1474. static bool update_video_range_property(obs_properties_t *props, const config_helper &conf, obs_property_t *p = nullptr)
  1475. {
  1476. return update_int_list_property("video_range", TEXT_RANGE_UNKNOWN_NAME, 5, VIDEO_RANGE_AUTO, video_range_valid,
  1477. props, conf, p, [](av_video_info vi) {
  1478. return vi.video_range;
  1479. });
  1480. }
  1481. static bool properties_device_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1482. {
  1483. NSString *uid = get_string(settings, "device");
  1484. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1485. NSString *name = get_string(settings, "device_name");
  1486. bool dev_list_updated = update_device_list(p, uid, name, !dev && uid.length);
  1487. p = obs_properties_get(props, "preset");
  1488. bool preset_list_changed = check_preset(dev, p, settings);
  1489. bool autoselect_changed = autoselect_preset(dev, settings);
  1490. config_helper conf {settings};
  1491. bool res_changed = update_resolution_property(props, conf);
  1492. bool fps_changed = update_frame_rate_property(props, conf);
  1493. bool if_changed = update_input_format_property(props, conf);
  1494. return preset_list_changed || autoselect_changed || dev_list_updated || res_changed || fps_changed || if_changed;
  1495. }
  1496. static bool properties_use_preset_changed(obs_properties_t *props, obs_property_t *, obs_data_t *settings)
  1497. {
  1498. auto use_preset = obs_data_get_bool(settings, "use_preset");
  1499. config_helper conf {settings};
  1500. bool updated = false;
  1501. bool visible = false;
  1502. obs_property_t *p = nullptr;
  1503. auto noop = [](obs_properties_t *, const config_helper &, obs_property_t *) {
  1504. return false;
  1505. };
  1506. #define UPDATE_PROPERTY(prop, uses_preset, func) \
  1507. p = obs_properties_get(props, prop); \
  1508. visible = use_preset == uses_preset; \
  1509. updated = obs_property_visible(p) != visible || updated; \
  1510. obs_property_set_visible(p, visible); \
  1511. updated = func(props, conf, p) || updated;
  1512. UPDATE_PROPERTY("preset", true, noop);
  1513. UPDATE_PROPERTY("resolution", false, update_resolution_property);
  1514. UPDATE_PROPERTY("frame_rate", false, update_frame_rate_property);
  1515. UPDATE_PROPERTY("input_format", false, update_input_format_property);
  1516. UPDATE_PROPERTY("color_space", false, update_color_space_property);
  1517. UPDATE_PROPERTY("video_range", false, update_video_range_property);
  1518. return updated;
  1519. }
  1520. static bool properties_preset_changed(obs_properties_t *, obs_property_t *p, obs_data_t *settings)
  1521. {
  1522. NSString *uid = get_string(settings, "device");
  1523. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1524. bool preset_list_changed = check_preset(dev, p, settings);
  1525. bool autoselect_changed = autoselect_preset(dev, settings);
  1526. return preset_list_changed || autoselect_changed;
  1527. }
  1528. static bool properties_resolution_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1529. {
  1530. config_helper conf {settings};
  1531. bool res_updated = update_resolution_property(props, conf, p);
  1532. bool fps_updated = update_frame_rate_property(props, conf);
  1533. bool if_updated = update_input_format_property(props, conf);
  1534. bool cs_updated = update_color_space_property(props, conf);
  1535. bool cr_updated = update_video_range_property(props, conf);
  1536. return res_updated || fps_updated || if_updated || cs_updated || cr_updated;
  1537. }
  1538. static bool properties_frame_rate_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1539. {
  1540. config_helper conf {settings};
  1541. bool fps_updated = update_frame_rate_property(props, conf, p);
  1542. bool if_updated = update_input_format_property(props, conf);
  1543. bool cs_updated = update_color_space_property(props, conf);
  1544. bool cr_updated = update_video_range_property(props, conf);
  1545. return fps_updated || if_updated || cs_updated || cr_updated;
  1546. }
  1547. static bool properties_input_format_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1548. {
  1549. config_helper conf {settings};
  1550. bool if_updated = update_input_format_property(props, conf, p);
  1551. bool cs_updated = update_color_space_property(props, conf);
  1552. bool cr_updated = update_video_range_property(props, conf);
  1553. return if_updated || cs_updated || cr_updated;
  1554. }
  1555. static bool properties_color_space_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1556. {
  1557. config_helper conf {settings};
  1558. return update_color_space_property(props, conf, p);
  1559. }
  1560. static bool properties_video_range_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1561. {
  1562. config_helper conf {settings};
  1563. return update_video_range_property(props, conf, p);
  1564. }
  1565. static void add_properties_param(obs_properties_t *props, av_capture *capture)
  1566. {
  1567. auto param = unique_ptr<properties_param>(new properties_param(capture));
  1568. obs_properties_set_param(props, param.release(), [](void *param) {
  1569. delete static_cast<properties_param *>(param);
  1570. });
  1571. }
  1572. static void add_preset_properties(obs_properties_t *props)
  1573. {
  1574. obs_property_t *preset_list =
  1575. obs_properties_add_list(props, "preset", TEXT_PRESET, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1576. for (NSString *preset in presets())
  1577. obs_property_list_add_string(preset_list, preset_names(preset).UTF8String, preset.UTF8String);
  1578. obs_property_set_modified_callback(preset_list, properties_preset_changed);
  1579. }
  1580. static void add_manual_properties(obs_properties_t *props)
  1581. {
  1582. obs_property_t *resolutions =
  1583. obs_properties_add_list(props, "resolution", TEXT_RESOLUTION, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1584. obs_property_set_enabled(resolutions, false);
  1585. obs_property_set_modified_callback(resolutions, properties_resolution_changed);
  1586. obs_property_t *frame_rates = obs_properties_add_frame_rate(props, "frame_rate", TEXT_FRAME_RATE);
  1587. /*obs_property_frame_rate_option_add(frame_rates, "match obs",
  1588. TEXT_MATCH_OBS);*/
  1589. obs_property_set_enabled(frame_rates, false);
  1590. obs_property_set_modified_callback(frame_rates, properties_frame_rate_changed);
  1591. obs_property_t *input_format =
  1592. obs_properties_add_list(props, "input_format", TEXT_INPUT_FORMAT, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1593. obs_property_list_add_int(input_format, TEXT_AUTO, INPUT_FORMAT_AUTO);
  1594. obs_property_set_enabled(input_format, false);
  1595. obs_property_set_modified_callback(input_format, properties_input_format_changed);
  1596. obs_property_t *color_space =
  1597. obs_properties_add_list(props, "color_space", TEXT_COLOR_SPACE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1598. obs_property_list_add_int(color_space, TEXT_AUTO, COLOR_SPACE_AUTO);
  1599. obs_property_list_add_int(color_space, "Rec. 601", VIDEO_CS_601);
  1600. obs_property_list_add_int(color_space, "Rec. 709", VIDEO_CS_709);
  1601. obs_property_set_enabled(color_space, false);
  1602. obs_property_set_modified_callback(color_space, properties_color_space_changed);
  1603. #define ADD_RANGE(x) obs_property_list_add_int(video_range, TEXT_##x, VIDEO_##x)
  1604. obs_property_t *video_range =
  1605. obs_properties_add_list(props, "video_range", TEXT_VIDEO_RANGE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1606. obs_property_list_add_int(video_range, TEXT_AUTO, VIDEO_RANGE_AUTO);
  1607. ADD_RANGE(RANGE_PARTIAL);
  1608. ADD_RANGE(RANGE_FULL);
  1609. obs_property_set_enabled(video_range, false);
  1610. obs_property_set_modified_callback(video_range, properties_video_range_changed);
  1611. #undef ADD_RANGE
  1612. }
  1613. static obs_properties_t *av_capture_properties(void *data)
  1614. {
  1615. obs_properties_t *props = obs_properties_create();
  1616. obs_property_t *dev_list =
  1617. obs_properties_add_list(props, "device", TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1618. obs_property_list_add_string(dev_list, "", "");
  1619. NSMutableArray *device_types = [NSMutableArray
  1620. arrayWithObjects:AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown, nil];
  1621. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
  1622. if (__builtin_available(macOS 13.0, *)) {
  1623. [device_types addObject:AVCaptureDeviceTypeDeskViewCamera];
  1624. }
  1625. #endif
  1626. AVCaptureDeviceDiscoverySession *video_discovery =
  1627. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:device_types mediaType:AVMediaTypeVideo
  1628. position:AVCaptureDevicePositionUnspecified];
  1629. for (AVCaptureDevice *dev in [video_discovery devices]) {
  1630. obs_property_list_add_string(dev_list, dev.localizedName.UTF8String, dev.uniqueID.UTF8String);
  1631. }
  1632. AVCaptureDeviceDiscoverySession *muxed_discovery =
  1633. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:device_types mediaType:AVMediaTypeMuxed
  1634. position:AVCaptureDevicePositionUnspecified];
  1635. for (AVCaptureDevice *dev in [muxed_discovery devices]) {
  1636. obs_property_list_add_string(dev_list, dev.localizedName.UTF8String, dev.uniqueID.UTF8String);
  1637. }
  1638. obs_property_set_modified_callback(dev_list, properties_device_changed);
  1639. obs_property_t *use_preset = obs_properties_add_bool(props, "use_preset", TEXT_USE_PRESET);
  1640. obs_property_set_modified_callback(use_preset, properties_use_preset_changed);
  1641. add_preset_properties(props);
  1642. add_manual_properties(props);
  1643. obs_properties_add_bool(props, "buffering", obs_module_text("Buffering"));
  1644. if (data) {
  1645. struct av_capture *capture = static_cast<av_capture *>(data);
  1646. add_properties_param(props, capture);
  1647. OBSDataAutoRelease current_settings = obs_source_get_settings(capture->source);
  1648. if (!obs_data_get_bool(current_settings, "enable_audio")) {
  1649. auto cb = [](obs_properties_t *, obs_property_t *prop, void *data) {
  1650. struct av_capture *capture = static_cast<av_capture *>(data);
  1651. OBSDataAutoRelease settings = obs_data_create();
  1652. obs_data_set_bool(settings, "enable_audio", true);
  1653. obs_source_update(capture->source, settings);
  1654. // Enable all audio tracks
  1655. obs_source_set_audio_mixers(capture->source, 0x3F);
  1656. obs_property_set_visible(prop, false);
  1657. return true;
  1658. };
  1659. obs_properties_add_button2(props, "enable_audio_button", obs_module_text("EnableAudio"), cb, capture);
  1660. }
  1661. }
  1662. return props;
  1663. }
  1664. static void switch_device(av_capture *capture, NSString *uid, obs_data_t *settings)
  1665. {
  1666. if (!uid)
  1667. return;
  1668. if (capture->device)
  1669. remove_device(capture);
  1670. capture->uid = uid;
  1671. if (!uid.length) {
  1672. AVLOG(LOG_INFO, "No device selected, stopping capture");
  1673. return;
  1674. }
  1675. AVCaptureDevice *dev = [AVCaptureDevice deviceWithUniqueID:uid];
  1676. if (!dev) {
  1677. AVLOG(LOG_WARNING, "Device with unique id '%s' not found", uid.UTF8String);
  1678. return;
  1679. }
  1680. capture_device(capture, dev, settings);
  1681. }
  1682. static void update_preset(av_capture *capture, obs_data_t *settings)
  1683. {
  1684. unlock_device(capture);
  1685. NSString *preset = get_string(settings, "preset");
  1686. if (![capture->device supportsAVCaptureSessionPreset:preset]) {
  1687. AVLOG(LOG_WARNING, "Preset %s not available", preset.UTF8String);
  1688. preset = select_preset(capture->device, preset);
  1689. }
  1690. capture->session.sessionPreset = preset;
  1691. AVLOG(LOG_INFO, "Selected preset %s", preset.UTF8String);
  1692. start_capture(capture);
  1693. }
  1694. static void update_manual(av_capture *capture, obs_data_t *settings)
  1695. {
  1696. if (init_manual(capture, capture->device, settings))
  1697. start_capture(capture);
  1698. }
  1699. static void av_capture_update(void *data, obs_data_t *settings)
  1700. {
  1701. auto capture = static_cast<av_capture *>(data);
  1702. NSString *uid = get_string(settings, "device");
  1703. if (!capture->device || ![capture->device.uniqueID isEqualToString:uid])
  1704. return switch_device(capture, uid, settings);
  1705. if ((capture->use_preset = obs_data_get_bool(settings, "use_preset"))) {
  1706. update_preset(capture, settings);
  1707. } else {
  1708. update_manual(capture, settings);
  1709. }
  1710. av_capture_enable_buffering(capture, obs_data_get_bool(settings, "buffering"));
  1711. av_capture_set_audio_active(capture, obs_data_get_bool(settings, "enable_audio") &&
  1712. ([capture->device hasMediaType:AVMediaTypeAudio] ||
  1713. [capture->device hasMediaType:AVMediaTypeMuxed]));
  1714. }
  1715. OBS_DECLARE_MODULE()
  1716. OBS_MODULE_USE_DEFAULT_LOCALE("mac-avcapture", "en-US")
  1717. MODULE_EXPORT const char *obs_module_description(void)
  1718. {
  1719. return "MacOS AVFoundation Capture source";
  1720. }
  1721. bool obs_module_load(void)
  1722. {
  1723. // Enable iOS device to show up as AVCapture devices
  1724. // From WWDC video 2014 #508 at 5:34
  1725. // https://developer.apple.com/videos/wwdc/2014/#508
  1726. CMIOObjectPropertyAddress prop = {kCMIOHardwarePropertyAllowScreenCaptureDevices, kCMIOObjectPropertyScopeGlobal,
  1727. kCMIOObjectPropertyElementMaster};
  1728. UInt32 allow = 1;
  1729. CMIOObjectSetPropertyData(kCMIOObjectSystemObject, &prop, 0, NULL, sizeof(allow), &allow);
  1730. obs_source_info av_capture_info = {
  1731. .id = "av_capture_input",
  1732. .type = OBS_SOURCE_TYPE_INPUT,
  1733. .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  1734. OBS_SOURCE_CAP_OBSOLETE,
  1735. .get_name = av_capture_getname,
  1736. .create = av_capture_create,
  1737. .destroy = av_capture_destroy,
  1738. .get_defaults = av_capture_defaults_v1,
  1739. .get_properties = av_capture_properties,
  1740. .update = av_capture_update,
  1741. .icon_type = OBS_ICON_TYPE_CAMERA,
  1742. };
  1743. obs_register_source(&av_capture_info);
  1744. av_capture_info.version = 2;
  1745. av_capture_info.output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
  1746. av_capture_info.get_defaults = av_capture_defaults_v2;
  1747. obs_register_source(&av_capture_info);
  1748. return true;
  1749. }