aja-source.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196
  1. #include "aja-card-manager.hpp"
  2. #include "aja-common.hpp"
  3. #include "aja-ui-props.hpp"
  4. #include "aja-source.hpp"
  5. #include "aja-routing.hpp"
  6. #include <util/threading.h>
  7. #include <util/platform.h>
  8. #include <util/dstr.h>
  9. #include <util/util_uint64.h>
  10. #include <obs-module.h>
  11. #include <ajantv2/includes/ntv2card.h>
  12. #include <ajantv2/includes/ntv2utils.h>
  13. #define NSEC_PER_SEC 1000000000LL
  14. #define NTV2_AUDIOSIZE_MAX (401 * 1024)
  15. AJASource::AJASource(obs_source_t *source)
  16. : mVideoBuffer{},
  17. mAudioBuffer{},
  18. mCard{nullptr},
  19. mSourceName{""},
  20. mCardID{""},
  21. mDeviceIndex{0},
  22. mBuffering{false},
  23. mIsCapturing{false},
  24. mSourceProps{},
  25. mTestPattern{},
  26. mCaptureThread{nullptr},
  27. mMutex{},
  28. mSource{source},
  29. mCrosspoints{}
  30. {
  31. }
  32. AJASource::~AJASource()
  33. {
  34. Deactivate();
  35. mTestPattern.clear();
  36. mVideoBuffer.Deallocate();
  37. mAudioBuffer.Deallocate();
  38. mVideoBuffer = 0;
  39. mAudioBuffer = 0;
  40. }
  41. void AJASource::SetCard(CNTV2Card *card)
  42. {
  43. mCard = card;
  44. }
  45. CNTV2Card *AJASource::GetCard()
  46. {
  47. return mCard;
  48. }
  49. void AJASource::SetOBSSource(obs_source_t *source)
  50. {
  51. mSource = source;
  52. }
  53. obs_source_t *AJASource::GetOBSSource(void) const
  54. {
  55. return mSource;
  56. }
  57. void AJASource::SetName(const std::string &name)
  58. {
  59. mSourceName = name;
  60. }
  61. std::string AJASource::GetName() const
  62. {
  63. return mSourceName;
  64. }
  65. void populate_source_device_list(obs_property_t *list)
  66. {
  67. obs_property_list_clear(list);
  68. auto &cardManager = aja::CardManager::Instance();
  69. cardManager.EnumerateCards();
  70. for (const auto &iter : cardManager.GetCardEntries()) {
  71. if (iter.second) {
  72. CNTV2Card *card = iter.second->GetCard();
  73. if (!card)
  74. continue;
  75. if (aja::IsOutputOnlyDevice(iter.second->GetDeviceID()))
  76. continue;
  77. obs_property_list_add_string(
  78. list, iter.second->GetDisplayName().c_str(),
  79. iter.second->GetCardID().c_str());
  80. }
  81. }
  82. }
  83. //
  84. // Capture Thread stuff
  85. //
  86. struct AudioOffsets {
  87. ULWord currentAddress = 0;
  88. ULWord lastAddress = 0;
  89. ULWord readOffset = 0;
  90. ULWord wrapAddress = 0;
  91. ULWord bytesRead = 0;
  92. };
  93. static void ResetAudioBufferOffsets(CNTV2Card *card,
  94. NTV2AudioSystem audioSystem,
  95. AudioOffsets &offsets)
  96. {
  97. if (!card)
  98. return;
  99. offsets.currentAddress = 0;
  100. offsets.lastAddress = 0;
  101. offsets.readOffset = 0;
  102. offsets.wrapAddress = 0;
  103. offsets.bytesRead = 0;
  104. card->GetAudioReadOffset(offsets.readOffset, audioSystem);
  105. card->GetAudioWrapAddress(offsets.wrapAddress, audioSystem);
  106. offsets.wrapAddress += offsets.readOffset;
  107. offsets.lastAddress = offsets.readOffset;
  108. }
  109. void AJASource::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
  110. NTV2TestPatternSelect ps)
  111. {
  112. NTV2VideoFormat vid_fmt = vf;
  113. NTV2PixelFormat pix_fmt = pf;
  114. if (vid_fmt == NTV2_FORMAT_UNKNOWN)
  115. vid_fmt = NTV2_FORMAT_720p_5994;
  116. if (pix_fmt == NTV2_FBF_INVALID)
  117. pix_fmt = kDefaultAJAPixelFormat;
  118. NTV2FormatDesc fd(vid_fmt, pix_fmt, NTV2_VANCMODE_OFF);
  119. auto bufSize = fd.GetTotalRasterBytes();
  120. if (bufSize != mTestPattern.size()) {
  121. mTestPattern.clear();
  122. mTestPattern.resize(bufSize);
  123. NTV2TestPatternGen gen;
  124. gen.DrawTestPattern(ps, fd.GetRasterWidth(),
  125. fd.GetRasterHeight(), pix_fmt,
  126. mTestPattern);
  127. }
  128. if (mTestPattern.size() == 0) {
  129. blog(LOG_DEBUG,
  130. "AJASource::GenerateTestPattern: Error generating test pattern!");
  131. return;
  132. }
  133. const enum video_format obs_vid_fmt =
  134. aja::AJAPixelFormatToOBSVideoFormat(pix_fmt);
  135. struct obs_source_frame2 obsFrame;
  136. obsFrame.flip = false;
  137. obsFrame.timestamp = os_gettime_ns();
  138. obsFrame.width = fd.GetRasterWidth();
  139. obsFrame.height = fd.GetRasterHeight();
  140. obsFrame.format = obs_vid_fmt;
  141. obsFrame.data[0] = mTestPattern.data();
  142. obsFrame.linesize[0] = fd.GetBytesPerRow();
  143. video_colorspace colorspace = VIDEO_CS_709;
  144. if (NTV2_IS_SD_VIDEO_FORMAT(vid_fmt))
  145. colorspace = VIDEO_CS_601;
  146. video_format_get_parameters_for_format(colorspace, VIDEO_RANGE_PARTIAL,
  147. obs_vid_fmt,
  148. obsFrame.color_matrix,
  149. obsFrame.color_range_min,
  150. obsFrame.color_range_max);
  151. obs_source_output_video2(mSource, &obsFrame);
  152. blog(LOG_DEBUG, "AJASource::GenerateTestPattern: Black");
  153. }
  154. static inline uint64_t samples_to_ns(size_t frames, uint_fast32_t rate)
  155. {
  156. return util_mul_div64(frames, NSEC_PER_SEC, rate);
  157. }
  158. static inline uint64_t get_sample_time(size_t frames, uint_fast32_t rate)
  159. {
  160. return os_gettime_ns() - samples_to_ns(frames, rate);
  161. }
  162. static bool CheckTransferAudioDMA(CNTV2Card *card,
  163. const NTV2AudioSystem audioSystem,
  164. void *buffer, ULWord bufferSize,
  165. ULWord dmaOffset, ULWord dmaSize,
  166. const char *log_id)
  167. {
  168. if (dmaSize > bufferSize) {
  169. blog(LOG_DEBUG,
  170. "AJASource::CaptureThread: Audio overrun %s! Buffer Size: %u, Bytes Captured: %u",
  171. log_id, bufferSize, dmaSize);
  172. return false;
  173. }
  174. card->DMAReadAudio(audioSystem, (ULWord *)buffer, dmaOffset, dmaSize);
  175. return true;
  176. }
  177. static inline bool TransferAudio(CNTV2Card *card,
  178. const NTV2AudioSystem audioSystem,
  179. NTV2_POINTER &audioBuffer,
  180. AudioOffsets &offsets)
  181. {
  182. card->ReadAudioLastIn(offsets.currentAddress, audioSystem);
  183. offsets.currentAddress += 1;
  184. offsets.currentAddress += offsets.readOffset;
  185. if (offsets.currentAddress < offsets.lastAddress) {
  186. offsets.bytesRead = offsets.wrapAddress - offsets.lastAddress;
  187. if (!CheckTransferAudioDMA(card, audioSystem, audioBuffer,
  188. audioBuffer.GetByteCount(),
  189. offsets.lastAddress,
  190. offsets.bytesRead, "(1)"))
  191. return false;
  192. if (!CheckTransferAudioDMA(
  193. card, audioSystem,
  194. audioBuffer.GetHostAddress(offsets.bytesRead),
  195. audioBuffer.GetByteCount() - offsets.bytesRead,
  196. offsets.readOffset,
  197. offsets.currentAddress - offsets.readOffset, "(2)"))
  198. return false;
  199. offsets.bytesRead +=
  200. offsets.currentAddress - offsets.readOffset;
  201. } else {
  202. offsets.bytesRead =
  203. offsets.currentAddress - offsets.lastAddress;
  204. if (!CheckTransferAudioDMA(card, audioSystem, audioBuffer,
  205. audioBuffer.GetByteCount(),
  206. offsets.lastAddress,
  207. offsets.bytesRead, "(3)"))
  208. return false;
  209. }
  210. return true;
  211. }
  212. void AJASource::CaptureThread(AJAThread *thread, void *data)
  213. {
  214. UNUSED_PARAMETER(thread);
  215. auto ajaSource = (AJASource *)data;
  216. if (!ajaSource) {
  217. blog(LOG_WARNING,
  218. "AJASource::CaptureThread: Plugin instance is null!");
  219. return;
  220. }
  221. blog(LOG_INFO,
  222. "AJASource::CaptureThread: Starting capture thread for AJA source %s",
  223. ajaSource->GetName().c_str());
  224. auto card = ajaSource->GetCard();
  225. if (!card) {
  226. blog(LOG_ERROR,
  227. "AJASource::CaptureThread: Card instance is null!");
  228. return;
  229. }
  230. auto sourceProps = ajaSource->GetSourceProps();
  231. ajaSource->ResetVideoBuffer(sourceProps.videoFormat,
  232. sourceProps.pixelFormat);
  233. auto inputSource = sourceProps.InitialInputSource();
  234. auto channel = sourceProps.Channel();
  235. auto framestore = sourceProps.Framestore();
  236. auto audioSystem = sourceProps.AudioSystem();
  237. // Current "on-air" frame on the card. The capture thread "Ping-pongs" between
  238. // two frames, starting at an index corresponding to the framestore channel.
  239. // For example:
  240. // Channel 1 (index 0) = frames 0/1
  241. // Channel 2 (index 1) = frames 2/3
  242. // Channel 3 (index 2) = frames 4/5
  243. // Channel 4 (index 3) = frames 6/7
  244. // etc...
  245. ULWord currentCardFrame = GetIndexForNTV2Channel(framestore) * 2;
  246. card->WaitForInputFieldID(NTV2_FIELD0, channel);
  247. currentCardFrame ^= 1;
  248. card->SetInputFrame(framestore, currentCardFrame);
  249. AudioOffsets offsets;
  250. ResetAudioBufferOffsets(card, audioSystem, offsets);
  251. obs_data_t *settings = obs_source_get_settings(ajaSource->mSource);
  252. while (ajaSource->IsCapturing()) {
  253. if (card->GetModelName() == "(Not Found)") {
  254. os_sleep_ms(250);
  255. obs_source_update(ajaSource->mSource, settings);
  256. break;
  257. }
  258. auto videoFormat = sourceProps.videoFormat;
  259. auto pixelFormat = sourceProps.pixelFormat;
  260. auto ioSelection = sourceProps.ioSelect;
  261. card->WaitForInputFieldID(NTV2_FIELD0, channel);
  262. currentCardFrame ^= 1;
  263. // Card format detection -- restarts capture thread via aja_source_update callback
  264. auto newVideoFormat = card->GetInputVideoFormat(
  265. inputSource, aja::Is3GLevelB(card, channel));
  266. if (newVideoFormat == NTV2_FORMAT_UNKNOWN) {
  267. blog(LOG_DEBUG,
  268. "AJASource::CaptureThread: Video format unknown!");
  269. ajaSource->GenerateTestPattern(videoFormat, pixelFormat,
  270. NTV2_TestPatt_Black);
  271. os_sleep_ms(250);
  272. continue;
  273. }
  274. newVideoFormat = aja::HandleSpecialCaseFormats(
  275. ioSelection, newVideoFormat, sourceProps.deviceID);
  276. if (sourceProps.autoDetect && (videoFormat != newVideoFormat)) {
  277. blog(LOG_INFO,
  278. "AJASource::CaptureThread: New Video Format detected! Triggering 'aja_source_update' callback and returning...");
  279. blog(LOG_INFO,
  280. "AJASource::CaptureThread: Current Video Format: %s, | Want Video Format: %s",
  281. NTV2VideoFormatToString(videoFormat, true).c_str(),
  282. NTV2VideoFormatToString(newVideoFormat, true)
  283. .c_str());
  284. os_sleep_ms(250);
  285. obs_source_update(ajaSource->mSource, settings);
  286. break;
  287. }
  288. if (!TransferAudio(card, audioSystem, ajaSource->mAudioBuffer,
  289. offsets)) {
  290. ResetAudioBufferOffsets(card, audioSystem, offsets);
  291. } else {
  292. offsets.lastAddress = offsets.currentAddress;
  293. obs_source_audio audioPacket;
  294. audioPacket.samples_per_sec = 48000;
  295. audioPacket.format = AUDIO_FORMAT_32BIT;
  296. audioPacket.speakers = SPEAKERS_7POINT1;
  297. audioPacket.frames = offsets.bytesRead / 32;
  298. audioPacket.timestamp =
  299. get_sample_time(audioPacket.frames, 48000);
  300. audioPacket.data[0] = (uint8_t *)ajaSource->mAudioBuffer
  301. .GetHostPointer();
  302. obs_source_output_audio(ajaSource->mSource,
  303. &audioPacket);
  304. }
  305. if (ajaSource->mVideoBuffer.GetByteCount() == 0) {
  306. blog(LOG_DEBUG,
  307. "AJASource::CaptureThread: 0 bytes in video buffer! Something went wrong!");
  308. continue;
  309. }
  310. card->DMAReadFrame(currentCardFrame, ajaSource->mVideoBuffer,
  311. ajaSource->mVideoBuffer.GetByteCount());
  312. auto actualVideoFormat = videoFormat;
  313. if (aja::Is3GLevelB(card, channel))
  314. actualVideoFormat = aja::GetLevelAFormatForLevelBFormat(
  315. videoFormat);
  316. const enum video_format obs_vid_fmt =
  317. aja::AJAPixelFormatToOBSVideoFormat(
  318. sourceProps.pixelFormat);
  319. NTV2FormatDesc fd(actualVideoFormat, pixelFormat);
  320. struct obs_source_frame2 obsFrame;
  321. obsFrame.flip = false;
  322. obsFrame.timestamp = os_gettime_ns();
  323. obsFrame.width = fd.GetRasterWidth();
  324. obsFrame.height = fd.GetRasterHeight();
  325. obsFrame.format = obs_vid_fmt;
  326. obsFrame.data[0] = reinterpret_cast<uint8_t *>(
  327. (ULWord *)ajaSource->mVideoBuffer.GetHostPointer());
  328. obsFrame.linesize[0] = fd.GetBytesPerRow();
  329. video_colorspace colorspace = VIDEO_CS_709;
  330. if (NTV2_IS_SD_VIDEO_FORMAT(actualVideoFormat))
  331. colorspace = VIDEO_CS_601;
  332. video_format_get_parameters_for_format(
  333. colorspace, VIDEO_RANGE_PARTIAL, obs_vid_fmt,
  334. obsFrame.color_matrix, obsFrame.color_range_min,
  335. obsFrame.color_range_max);
  336. obs_source_output_video2(ajaSource->mSource, &obsFrame);
  337. card->SetInputFrame(framestore, currentCardFrame);
  338. }
  339. blog(LOG_INFO, "AJASource::Capturethread: Thread loop stopped");
  340. ajaSource->GenerateTestPattern(sourceProps.videoFormat,
  341. sourceProps.pixelFormat,
  342. NTV2_TestPatt_Black);
  343. obs_data_release(settings);
  344. }
  345. void AJASource::Deactivate()
  346. {
  347. SetCapturing(false);
  348. if (mCaptureThread) {
  349. if (mCaptureThread->Active()) {
  350. mCaptureThread->Stop();
  351. blog(LOG_INFO, "AJASource::CaptureThread: Stopped!");
  352. }
  353. delete mCaptureThread;
  354. mCaptureThread = nullptr;
  355. blog(LOG_INFO, "AJASource::CaptureThread: Destroyed!");
  356. }
  357. }
  358. void AJASource::Activate(bool enable)
  359. {
  360. if (mCaptureThread == nullptr) {
  361. mCaptureThread = new AJAThread();
  362. mCaptureThread->Attach(AJASource::CaptureThread, this);
  363. mCaptureThread->SetPriority(AJA_ThreadPriority_High);
  364. blog(LOG_INFO, "AJASource::CaptureThread: Created!");
  365. }
  366. if (enable) {
  367. SetCapturing(true);
  368. if (!mCaptureThread->Active()) {
  369. mCaptureThread->Start();
  370. blog(LOG_INFO, "AJASource::CaptureThread: Started!");
  371. }
  372. }
  373. }
  374. bool AJASource::IsCapturing() const
  375. {
  376. return mIsCapturing;
  377. }
  378. void AJASource::SetCapturing(bool capturing)
  379. {
  380. std::lock_guard<std::mutex> lock(mMutex);
  381. mIsCapturing = capturing;
  382. }
  383. //
  384. // CardEntry/Device stuff
  385. //
  386. std::string AJASource::CardID() const
  387. {
  388. return mCardID;
  389. }
  390. void AJASource::SetCardID(const std::string &cardID)
  391. {
  392. mCardID = cardID;
  393. }
  394. uint32_t AJASource::DeviceIndex() const
  395. {
  396. return static_cast<uint32_t>(mDeviceIndex);
  397. }
  398. void AJASource::SetDeviceIndex(uint32_t index)
  399. {
  400. mDeviceIndex = static_cast<UWord>(index);
  401. }
  402. //
  403. // AJASource Properties stuff
  404. //
  405. void AJASource::SetSourceProps(const SourceProps &props)
  406. {
  407. mSourceProps = props;
  408. }
  409. SourceProps AJASource::GetSourceProps() const
  410. {
  411. return mSourceProps;
  412. }
  413. void AJASource::CacheConnections(const NTV2XptConnections &cnx)
  414. {
  415. mCrosspoints.clear();
  416. mCrosspoints = cnx;
  417. }
  418. void AJASource::ClearConnections()
  419. {
  420. for (auto &&xpt : mCrosspoints) {
  421. mCard->Connect(xpt.first, NTV2_XptBlack);
  422. }
  423. mCrosspoints.clear();
  424. }
  425. bool AJASource::ReadChannelVPIDs(NTV2Channel channel, VPIDData &vpids)
  426. {
  427. ULWord vpid_a = 0;
  428. ULWord vpid_b = 0;
  429. bool read_ok = mCard->ReadSDIInVPID(channel, vpid_a, vpid_b);
  430. vpids.SetA(vpid_a);
  431. vpids.SetB(vpid_b);
  432. vpids.Parse();
  433. return read_ok;
  434. }
  435. bool AJASource::ReadWireFormats(NTV2DeviceID device_id, IOSelection io_select,
  436. NTV2VideoFormat &vf, NTV2PixelFormat &pf,
  437. VPIDDataList &vpids)
  438. {
  439. NTV2InputSourceSet input_srcs;
  440. aja::IOSelectionToInputSources(io_select, input_srcs);
  441. if (input_srcs.empty()) {
  442. blog(LOG_INFO,
  443. "AJASource::ReadWireFormats: No NTV2InputSources found for IOSelection %s",
  444. aja::IOSelectionToString(io_select).c_str());
  445. return false;
  446. }
  447. NTV2InputSource initial_src = *input_srcs.begin();
  448. for (auto &&src : input_srcs) {
  449. auto channel = NTV2InputSourceToChannel(src);
  450. mCard->EnableChannel(channel);
  451. if (NTV2_INPUT_SOURCE_IS_SDI(src)) {
  452. if (NTV2DeviceHasBiDirectionalSDI(device_id)) {
  453. mCard->SetSDITransmitEnable(channel, false);
  454. }
  455. mCard->WaitForInputVerticalInterrupt(channel);
  456. VPIDData vpid_data;
  457. if (ReadChannelVPIDs(channel, vpid_data))
  458. vpids.push_back(vpid_data);
  459. } else if (NTV2_INPUT_SOURCE_IS_HDMI(src)) {
  460. mCard->WaitForInputVerticalInterrupt(channel);
  461. ULWord hdmi_version =
  462. NTV2DeviceGetHDMIVersion(device_id);
  463. // HDMIv1 handles its own RGB->YCbCr color space conversion
  464. if (hdmi_version == 1) {
  465. pf = kDefaultAJAPixelFormat;
  466. } else {
  467. NTV2LHIHDMIColorSpace hdmiInputColor;
  468. mCard->GetHDMIInputColor(hdmiInputColor,
  469. channel);
  470. if (hdmiInputColor ==
  471. NTV2_LHIHDMIColorSpaceYCbCr) {
  472. pf = kDefaultAJAPixelFormat;
  473. } else if (hdmiInputColor ==
  474. NTV2_LHIHDMIColorSpaceRGB) {
  475. pf = NTV2_FBF_24BIT_BGR;
  476. }
  477. }
  478. }
  479. }
  480. NTV2Channel initial_channel = NTV2InputSourceToChannel(initial_src);
  481. mCard->WaitForInputVerticalInterrupt(initial_channel);
  482. vf = mCard->GetInputVideoFormat(
  483. initial_src, aja::Is3GLevelB(mCard, initial_channel));
  484. if (NTV2_INPUT_SOURCE_IS_SDI(initial_src)) {
  485. if (vpids.size() > 0) {
  486. auto vpid = *vpids.begin();
  487. if (vpid.Sampling() == VPIDSampling_YUV_422) {
  488. pf = NTV2_FBF_8BIT_YCBCR;
  489. blog(LOG_INFO,
  490. "AJASource::ReadWireFormats - Detected pixel format %s",
  491. NTV2FrameBufferFormatToString(pf, true)
  492. .c_str());
  493. } else if (vpid.Sampling() == VPIDSampling_GBR_444) {
  494. pf = NTV2_FBF_24BIT_BGR;
  495. blog(LOG_INFO,
  496. "AJASource::ReadWireFormats - Detected pixel format %s",
  497. NTV2FrameBufferFormatToString(pf, true)
  498. .c_str());
  499. }
  500. }
  501. }
  502. vf = aja::HandleSpecialCaseFormats(io_select, vf, device_id);
  503. blog(LOG_INFO, "AJASource::ReadWireFormats - Detected video format %s",
  504. NTV2VideoFormatToString(vf).c_str());
  505. return true;
  506. }
  507. void AJASource::ResetVideoBuffer(NTV2VideoFormat vf, NTV2PixelFormat pf)
  508. {
  509. if (vf != NTV2_FORMAT_UNKNOWN) {
  510. auto videoBufferSize = GetVideoWriteSize(vf, pf);
  511. if (mVideoBuffer)
  512. mVideoBuffer.Deallocate();
  513. mVideoBuffer.Allocate(videoBufferSize, true);
  514. blog(LOG_INFO,
  515. "AJASource::ResetVideoBuffer: Video Format: %s | Pixel Format: %s | Buffer Size: %d",
  516. NTV2VideoFormatToString(vf, false).c_str(),
  517. NTV2FrameBufferFormatToString(pf, true).c_str(),
  518. videoBufferSize);
  519. }
  520. }
  521. void AJASource::ResetAudioBuffer(size_t size)
  522. {
  523. if (mAudioBuffer)
  524. mAudioBuffer.Deallocate();
  525. mAudioBuffer.Allocate(size, true);
  526. }
  527. static const char *aja_source_get_name(void *);
  528. static void *aja_source_create(obs_data_t *, obs_source_t *);
  529. static void aja_source_destroy(void *);
  530. static void aja_source_activate(void *);
  531. static void aja_source_deactivate(void *);
  532. static void aja_source_update(void *, obs_data_t *);
  533. const char *aja_source_get_name(void *unused)
  534. {
  535. UNUSED_PARAMETER(unused);
  536. return obs_module_text(kUIPropCaptureModule.text);
  537. }
  538. bool aja_source_device_changed(void *data, obs_properties_t *props,
  539. obs_property_t *list, obs_data_t *settings)
  540. {
  541. UNUSED_PARAMETER(list);
  542. blog(LOG_DEBUG, "AJA Source Device Changed");
  543. auto *ajaSource = (AJASource *)data;
  544. if (!ajaSource) {
  545. blog(LOG_DEBUG,
  546. "aja_source_device_changed: AJA Source instance is null!");
  547. return false;
  548. }
  549. const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
  550. if (!cardID || !cardID[0])
  551. return false;
  552. auto &cardManager = aja::CardManager::Instance();
  553. auto cardEntry = cardManager.GetCardEntry(cardID);
  554. if (!cardEntry) {
  555. blog(LOG_DEBUG,
  556. "aja_source_device_changed: Card Entry not found for %s",
  557. cardID);
  558. return false;
  559. }
  560. blog(LOG_DEBUG, "Found CardEntry for %s", cardID);
  561. CNTV2Card *card = cardEntry->GetCard();
  562. if (!card) {
  563. blog(LOG_DEBUG,
  564. "aja_source_device_changed: Card instance is null!");
  565. return false;
  566. }
  567. const NTV2DeviceID deviceID = card->GetDeviceID();
  568. /* If Channel 1 is actively in use, filter the video format list to only
  569. * show video formats within the same framerate family. If Channel 1 is
  570. * not active we just go ahead and try to set all framestores to the same video format.
  571. * This is because Channel 1's clock rate will govern the card's Free Run clock.
  572. */
  573. NTV2VideoFormat videoFormatChannel1 = NTV2_FORMAT_UNKNOWN;
  574. if (!cardEntry->ChannelReady(NTV2_CHANNEL1, ajaSource->GetName())) {
  575. card->GetVideoFormat(videoFormatChannel1, NTV2_CHANNEL1);
  576. }
  577. obs_property_t *devices_list =
  578. obs_properties_get(props, kUIPropDevice.id);
  579. obs_property_t *io_select_list =
  580. obs_properties_get(props, kUIPropInput.id);
  581. obs_property_t *vid_fmt_list =
  582. obs_properties_get(props, kUIPropVideoFormatSelect.id);
  583. obs_property_t *pix_fmt_list =
  584. obs_properties_get(props, kUIPropPixelFormatSelect.id);
  585. obs_property_t *sdi_trx_list =
  586. obs_properties_get(props, kUIPropSDITransport.id);
  587. obs_property_t *sdi_4k_list =
  588. obs_properties_get(props, kUIPropSDITransport4K.id);
  589. obs_property_list_clear(vid_fmt_list);
  590. obs_property_list_add_int(vid_fmt_list, obs_module_text("Auto"),
  591. kAutoDetect);
  592. populate_video_format_list(deviceID, vid_fmt_list, videoFormatChannel1,
  593. true);
  594. obs_property_list_clear(pix_fmt_list);
  595. obs_property_list_add_int(pix_fmt_list, obs_module_text("Auto"),
  596. kAutoDetect);
  597. populate_pixel_format_list(deviceID, pix_fmt_list);
  598. IOSelection io_select = static_cast<IOSelection>(
  599. obs_data_get_int(settings, kUIPropInput.id));
  600. obs_property_list_clear(sdi_trx_list);
  601. populate_sdi_transport_list(sdi_trx_list, deviceID, true);
  602. obs_property_list_clear(sdi_4k_list);
  603. populate_sdi_4k_transport_list(sdi_4k_list);
  604. populate_io_selection_input_list(cardID, ajaSource->GetName(), deviceID,
  605. io_select_list);
  606. auto curr_vf = static_cast<NTV2VideoFormat>(
  607. obs_data_get_int(settings, kUIPropVideoFormatSelect.id));
  608. bool have_cards = cardManager.NumCardEntries() > 0;
  609. obs_property_set_visible(devices_list, have_cards);
  610. obs_property_set_visible(io_select_list, have_cards);
  611. obs_property_set_visible(vid_fmt_list, have_cards);
  612. obs_property_set_visible(pix_fmt_list, have_cards);
  613. obs_property_set_visible(
  614. sdi_trx_list, have_cards && aja::IsIOSelectionSDI(io_select));
  615. obs_property_set_visible(
  616. sdi_4k_list, have_cards && NTV2_IS_4K_VIDEO_FORMAT(curr_vf));
  617. return true;
  618. }
  619. bool aja_io_selection_changed(void *data, obs_properties_t *props,
  620. obs_property_t *list, obs_data_t *settings)
  621. {
  622. UNUSED_PARAMETER(list);
  623. AJASource *ajaSource = (AJASource *)data;
  624. if (!ajaSource) {
  625. blog(LOG_DEBUG,
  626. "aja_io_selection_changed: AJA Source instance is null!");
  627. return false;
  628. }
  629. const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
  630. if (!cardID || !cardID[0])
  631. return false;
  632. auto &cardManager = aja::CardManager::Instance();
  633. auto cardEntry = cardManager.GetCardEntry(cardID);
  634. if (!cardEntry) {
  635. blog(LOG_DEBUG,
  636. "aja_io_selection_changed: Card Entry not found for %s",
  637. cardID);
  638. return false;
  639. }
  640. filter_io_selection_input_list(cardID, ajaSource->GetName(),
  641. obs_properties_get(props,
  642. kUIPropInput.id));
  643. obs_property_set_visible(
  644. obs_properties_get(props, kUIPropSDITransport.id),
  645. aja::IsIOSelectionSDI(static_cast<IOSelection>(
  646. obs_data_get_int(settings, kUIPropInput.id))));
  647. return true;
  648. }
  649. bool aja_sdi_mode_list_changed(obs_properties_t *props, obs_property_t *list,
  650. obs_data_t *settings)
  651. {
  652. UNUSED_PARAMETER(props);
  653. UNUSED_PARAMETER(list);
  654. UNUSED_PARAMETER(settings);
  655. return true;
  656. }
  657. void *aja_source_create(obs_data_t *settings, obs_source_t *source)
  658. {
  659. blog(LOG_DEBUG, "AJA Source Create");
  660. auto ajaSource = new AJASource(source);
  661. ajaSource->SetName(obs_source_get_name(source));
  662. obs_source_set_async_decoupled(source, true);
  663. ajaSource->SetOBSSource(source);
  664. ajaSource->ResetAudioBuffer(NTV2_AUDIOSIZE_MAX);
  665. ajaSource->Activate(false);
  666. obs_source_update(source, settings);
  667. return ajaSource;
  668. }
  669. void aja_source_destroy(void *data)
  670. {
  671. blog(LOG_DEBUG, "AJA Source Destroy");
  672. auto ajaSource = (AJASource *)data;
  673. if (!ajaSource) {
  674. blog(LOG_ERROR, "aja_source_destroy: Plugin instance is null!");
  675. return;
  676. }
  677. ajaSource->Deactivate();
  678. NTV2DeviceID deviceID = DEVICE_ID_NOTFOUND;
  679. CNTV2Card *card = ajaSource->GetCard();
  680. if (card) {
  681. deviceID = card->GetDeviceID();
  682. aja::Routing::StopSourceAudio(ajaSource->GetSourceProps(),
  683. card);
  684. }
  685. ajaSource->mVideoBuffer.Deallocate();
  686. ajaSource->mAudioBuffer.Deallocate();
  687. ajaSource->mVideoBuffer = 0;
  688. ajaSource->mAudioBuffer = 0;
  689. auto &cardManager = aja::CardManager::Instance();
  690. const auto &cardID = ajaSource->CardID();
  691. auto cardEntry = cardManager.GetCardEntry(cardID);
  692. if (!cardEntry) {
  693. blog(LOG_DEBUG,
  694. "aja_source_destroy: Card Entry not found for %s",
  695. cardID.c_str());
  696. return;
  697. }
  698. auto ioSelect = ajaSource->GetSourceProps().ioSelect;
  699. if (!cardEntry->ReleaseInputSelection(ioSelect, deviceID,
  700. ajaSource->GetName())) {
  701. blog(LOG_WARNING,
  702. "aja_source_destroy: Error releasing Input Selection!");
  703. }
  704. delete ajaSource;
  705. ajaSource = nullptr;
  706. }
  707. static void aja_source_show(void *data)
  708. {
  709. auto ajaSource = (AJASource *)data;
  710. if (!ajaSource) {
  711. blog(LOG_ERROR,
  712. "aja_source_show: AJA Source instance is null!");
  713. return;
  714. }
  715. bool deactivateWhileNotShowing =
  716. ajaSource->GetSourceProps().deactivateWhileNotShowing;
  717. bool showing = obs_source_showing(ajaSource->GetOBSSource());
  718. blog(LOG_DEBUG,
  719. "aja_source_show: deactivateWhileNotShowing = %s, showing = %s",
  720. deactivateWhileNotShowing ? "true" : "false",
  721. showing ? "true" : "false");
  722. if (deactivateWhileNotShowing && showing && !ajaSource->IsCapturing()) {
  723. ajaSource->Activate(true);
  724. blog(LOG_DEBUG, "aja_source_show: activated capture thread!");
  725. }
  726. }
  727. static void aja_source_hide(void *data)
  728. {
  729. auto ajaSource = (AJASource *)data;
  730. if (!ajaSource)
  731. return;
  732. bool deactivateWhileNotShowing =
  733. ajaSource->GetSourceProps().deactivateWhileNotShowing;
  734. bool showing = obs_source_showing(ajaSource->GetOBSSource());
  735. blog(LOG_DEBUG,
  736. "aja_source_hide: deactivateWhileNotShowing = %s, showing = %s",
  737. deactivateWhileNotShowing ? "true" : "false",
  738. showing ? "true" : "false");
  739. if (deactivateWhileNotShowing && !showing && ajaSource->IsCapturing()) {
  740. ajaSource->Deactivate();
  741. blog(LOG_DEBUG, "aja_source_hide: deactivated capture thread!");
  742. }
  743. }
  744. static void aja_source_activate(void *data)
  745. {
  746. UNUSED_PARAMETER(data);
  747. }
  748. static void aja_source_deactivate(void *data)
  749. {
  750. UNUSED_PARAMETER(data);
  751. }
  752. static void aja_source_update(void *data, obs_data_t *settings)
  753. {
  754. static bool initialized = false;
  755. auto ajaSource = (AJASource *)data;
  756. if (!ajaSource) {
  757. blog(LOG_WARNING,
  758. "aja_source_update: Plugin instance is null!");
  759. return;
  760. }
  761. auto io_select = static_cast<IOSelection>(
  762. obs_data_get_int(settings, kUIPropInput.id));
  763. auto vf_select = static_cast<NTV2VideoFormat>(
  764. obs_data_get_int(settings, kUIPropVideoFormatSelect.id));
  765. auto pf_select = static_cast<NTV2PixelFormat>(
  766. obs_data_get_int(settings, kUIPropPixelFormatSelect.id));
  767. auto sdi_trx_select = static_cast<SDITransport>(
  768. obs_data_get_int(settings, kUIPropSDITransport.id));
  769. auto sdi_t4k_select = static_cast<SDITransport4K>(
  770. obs_data_get_int(settings, kUIPropSDITransport4K.id));
  771. bool deactivateWhileNotShowing =
  772. obs_data_get_bool(settings, kUIPropDeactivateWhenNotShowing.id);
  773. const std::string &wantCardID =
  774. obs_data_get_string(settings, kUIPropDevice.id);
  775. obs_source_set_async_unbuffered(
  776. ajaSource->GetOBSSource(),
  777. !obs_data_get_bool(settings, kUIPropBuffering.id));
  778. const std::string &currentCardID = ajaSource->CardID();
  779. if (wantCardID != currentCardID) {
  780. initialized = false;
  781. ajaSource->Deactivate();
  782. }
  783. auto &cardManager = aja::CardManager::Instance();
  784. cardManager.EnumerateCards();
  785. auto cardEntry = cardManager.GetCardEntry(wantCardID);
  786. if (!cardEntry) {
  787. blog(LOG_DEBUG,
  788. "aja_source_update: Card Entry not found for %s",
  789. wantCardID.c_str());
  790. return;
  791. }
  792. CNTV2Card *card = cardEntry->GetCard();
  793. if (!card || !card->IsOpen()) {
  794. blog(LOG_ERROR, "aja_source_update: AJA device %s not open!",
  795. wantCardID.c_str());
  796. return;
  797. }
  798. if (card->GetModelName() == "(Not Found)") {
  799. blog(LOG_ERROR,
  800. "aja_source_update: AJA device %s disconnected?",
  801. wantCardID.c_str());
  802. return;
  803. }
  804. ajaSource->SetCard(cardEntry->GetCard());
  805. SourceProps curr_props = ajaSource->GetSourceProps();
  806. // Release Channels from previous card if card ID changes
  807. if (wantCardID != currentCardID) {
  808. auto prevCardEntry = cardManager.GetCardEntry(currentCardID);
  809. if (prevCardEntry) {
  810. const std::string &ioSelectStr =
  811. aja::IOSelectionToString(curr_props.ioSelect);
  812. if (!prevCardEntry->ReleaseInputSelection(
  813. curr_props.ioSelect, curr_props.deviceID,
  814. ajaSource->GetName())) {
  815. blog(LOG_WARNING,
  816. "aja_source_update: Error releasing IOSelection %s for card ID %s",
  817. ioSelectStr.c_str(),
  818. currentCardID.c_str());
  819. } else {
  820. blog(LOG_INFO,
  821. "aja_source_update: Released IOSelection %s for card ID %s",
  822. ioSelectStr.c_str(),
  823. currentCardID.c_str());
  824. ajaSource->SetCardID(wantCardID);
  825. io_select = IOSelection::Invalid;
  826. }
  827. }
  828. }
  829. if (io_select == IOSelection::Invalid) {
  830. blog(LOG_DEBUG, "aja_source_update: Invalid IOSelection");
  831. return;
  832. }
  833. SourceProps want_props;
  834. want_props.deviceID = card->GetDeviceID();
  835. want_props.ioSelect = io_select;
  836. want_props.videoFormat =
  837. ((int32_t)vf_select == kAutoDetect)
  838. ? NTV2_FORMAT_UNKNOWN
  839. : static_cast<NTV2VideoFormat>(vf_select);
  840. want_props.pixelFormat =
  841. ((int32_t)pf_select == kAutoDetect)
  842. ? NTV2_FBF_INVALID
  843. : static_cast<NTV2PixelFormat>(pf_select);
  844. want_props.sdiTransport =
  845. ((int32_t)sdi_trx_select == kAutoDetect)
  846. ? SDITransport::Unknown
  847. : static_cast<SDITransport>(sdi_trx_select);
  848. want_props.sdi4kTransport = sdi_t4k_select;
  849. want_props.vpids.clear();
  850. want_props.deactivateWhileNotShowing = deactivateWhileNotShowing;
  851. if (aja::IsIOSelectionSDI(io_select)) {
  852. want_props.autoDetect = (int)sdi_trx_select == kAutoDetect;
  853. } else {
  854. want_props.autoDetect = ((int)vf_select == kAutoDetect ||
  855. (int)pf_select == kAutoDetect);
  856. }
  857. ajaSource->SetCardID(wantCardID);
  858. ajaSource->SetDeviceIndex((UWord)cardEntry->GetCardIndex());
  859. // Release Channels if IOSelection changes
  860. if (want_props.ioSelect != curr_props.ioSelect) {
  861. const std::string &ioSelectStr =
  862. aja::IOSelectionToString(curr_props.ioSelect);
  863. if (!cardEntry->ReleaseInputSelection(curr_props.ioSelect,
  864. curr_props.deviceID,
  865. ajaSource->GetName())) {
  866. blog(LOG_WARNING,
  867. "aja_source_update: Error releasing IOSelection %s for card ID %s",
  868. ioSelectStr.c_str(), currentCardID.c_str());
  869. } else {
  870. blog(LOG_INFO,
  871. "aja_source_update: Released IOSelection %s for card ID %s",
  872. ioSelectStr.c_str(), currentCardID.c_str());
  873. }
  874. }
  875. // Acquire Channels for current IOSelection
  876. if (!cardEntry->AcquireInputSelection(want_props.ioSelect,
  877. want_props.deviceID,
  878. ajaSource->GetName())) {
  879. blog(LOG_ERROR,
  880. "aja_source_update: Could not acquire IOSelection %s",
  881. aja::IOSelectionToString(want_props.ioSelect).c_str());
  882. return;
  883. }
  884. // Read SDI video payload IDs (VPID) used for helping to determine the wire format
  885. NTV2VideoFormat new_vf = want_props.videoFormat;
  886. NTV2PixelFormat new_pf = want_props.pixelFormat;
  887. if (!ajaSource->ReadWireFormats(want_props.deviceID,
  888. want_props.ioSelect, new_vf, new_pf,
  889. want_props.vpids)) {
  890. blog(LOG_ERROR, "aja_source_update: ReadWireFormats failed!");
  891. cardEntry->ReleaseInputSelection(want_props.ioSelect,
  892. curr_props.deviceID,
  893. ajaSource->GetName());
  894. return;
  895. }
  896. // Set auto-detected formats
  897. if ((int32_t)vf_select == kAutoDetect)
  898. want_props.videoFormat = new_vf;
  899. if ((int32_t)pf_select == kAutoDetect)
  900. want_props.pixelFormat = new_pf;
  901. if (want_props.videoFormat == NTV2_FORMAT_UNKNOWN ||
  902. want_props.pixelFormat == NTV2_FBF_INVALID) {
  903. blog(LOG_ERROR,
  904. "aja_source_update: Unknown video/pixel format(s): %s / %s",
  905. NTV2VideoFormatToString(want_props.videoFormat).c_str(),
  906. NTV2FrameBufferFormatToString(want_props.pixelFormat)
  907. .c_str());
  908. cardEntry->ReleaseInputSelection(want_props.ioSelect,
  909. curr_props.deviceID,
  910. ajaSource->GetName());
  911. return;
  912. }
  913. // Change capture format and restart capture thread
  914. if (!initialized || want_props != ajaSource->GetSourceProps()) {
  915. ajaSource->ClearConnections();
  916. NTV2XptConnections xpt_cnx;
  917. aja::Routing::ConfigureSourceRoute(
  918. want_props, NTV2_MODE_CAPTURE, card, xpt_cnx);
  919. ajaSource->CacheConnections(xpt_cnx);
  920. ajaSource->Deactivate();
  921. initialized = true;
  922. }
  923. ajaSource->SetSourceProps(want_props);
  924. aja::Routing::StartSourceAudio(want_props, card);
  925. card->SetReference(NTV2_REFERENCE_FREERUN);
  926. ajaSource->Activate(true);
  927. }
  928. static obs_properties_t *aja_source_get_properties(void *data)
  929. {
  930. obs_properties_t *props = obs_properties_create();
  931. obs_property_t *device_list = obs_properties_add_list(
  932. props, kUIPropDevice.id, obs_module_text(kUIPropDevice.text),
  933. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  934. populate_source_device_list(device_list);
  935. obs_property_t *io_select_list = obs_properties_add_list(
  936. props, kUIPropInput.id, obs_module_text(kUIPropInput.text),
  937. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  938. obs_property_t *vid_fmt_list = obs_properties_add_list(
  939. props, kUIPropVideoFormatSelect.id,
  940. obs_module_text(kUIPropVideoFormatSelect.text),
  941. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  942. obs_properties_add_list(props, kUIPropPixelFormatSelect.id,
  943. obs_module_text(kUIPropPixelFormatSelect.text),
  944. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  945. obs_properties_add_list(props, kUIPropSDITransport.id,
  946. obs_module_text(kUIPropSDITransport.text),
  947. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  948. obs_properties_add_list(props, kUIPropSDITransport4K.id,
  949. obs_module_text(kUIPropSDITransport4K.text),
  950. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  951. obs_properties_add_bool(
  952. props, kUIPropDeactivateWhenNotShowing.id,
  953. obs_module_text(kUIPropDeactivateWhenNotShowing.text));
  954. obs_properties_add_bool(props, kUIPropBuffering.id,
  955. obs_module_text(kUIPropBuffering.text));
  956. obs_property_set_modified_callback(vid_fmt_list,
  957. aja_video_format_changed);
  958. obs_property_set_modified_callback2(device_list,
  959. aja_source_device_changed, data);
  960. obs_property_set_modified_callback2(io_select_list,
  961. aja_io_selection_changed, data);
  962. return props;
  963. }
  964. void aja_source_get_defaults(obs_data_t *settings)
  965. {
  966. obs_data_set_default_int(settings, kUIPropInput.id,
  967. static_cast<long long>(IOSelection::Invalid));
  968. obs_data_set_default_int(settings, kUIPropVideoFormatSelect.id,
  969. static_cast<long long>(kAutoDetect));
  970. obs_data_set_default_int(settings, kUIPropPixelFormatSelect.id,
  971. static_cast<long long>(kAutoDetect));
  972. obs_data_set_default_int(settings, kUIPropSDITransport.id,
  973. static_cast<long long>(kAutoDetect));
  974. obs_data_set_default_int(
  975. settings, kUIPropSDITransport4K.id,
  976. static_cast<long long>(SDITransport4K::TwoSampleInterleave));
  977. obs_data_set_default_bool(settings, kUIPropDeactivateWhenNotShowing.id,
  978. false);
  979. }
  980. static void aja_source_get_defaults_v1(obs_data_t *settings)
  981. {
  982. aja_source_get_defaults(settings);
  983. obs_data_set_default_bool(settings, kUIPropBuffering.id, true);
  984. }
  985. void aja_source_save(void *data, obs_data_t *settings)
  986. {
  987. AJASource *ajaSource = (AJASource *)data;
  988. if (!ajaSource) {
  989. blog(LOG_ERROR,
  990. "aja_source_save: AJA Source instance is null!");
  991. return;
  992. }
  993. const char *cardID = obs_data_get_string(settings, kUIPropDevice.id);
  994. if (!cardID || !cardID[0])
  995. return;
  996. auto &cardManager = aja::CardManager::Instance();
  997. auto cardEntry = cardManager.GetCardEntry(cardID);
  998. if (!cardEntry) {
  999. blog(LOG_DEBUG, "aja_source_save: Card Entry not found for %s",
  1000. cardID);
  1001. return;
  1002. }
  1003. auto oldName = ajaSource->GetName();
  1004. auto newName = obs_source_get_name(ajaSource->GetOBSSource());
  1005. if (oldName != newName &&
  1006. cardEntry->UpdateChannelOwnerName(oldName, newName)) {
  1007. ajaSource->SetName(newName);
  1008. blog(LOG_DEBUG, "aja_source_save: Renamed \"%s\" to \"%s\"",
  1009. oldName.c_str(), newName);
  1010. }
  1011. }
  1012. void register_aja_source_info()
  1013. {
  1014. struct obs_source_info aja_source_info = {};
  1015. aja_source_info.id = kUIPropCaptureModule.id;
  1016. aja_source_info.type = OBS_SOURCE_TYPE_INPUT;
  1017. aja_source_info.output_flags =
  1018. OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO |
  1019. OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_CAP_OBSOLETE;
  1020. aja_source_info.get_name = aja_source_get_name;
  1021. aja_source_info.create = aja_source_create;
  1022. aja_source_info.destroy = aja_source_destroy;
  1023. aja_source_info.update = aja_source_update;
  1024. aja_source_info.show = aja_source_show;
  1025. aja_source_info.hide = aja_source_hide;
  1026. aja_source_info.activate = aja_source_activate;
  1027. aja_source_info.deactivate = aja_source_deactivate;
  1028. aja_source_info.get_properties = aja_source_get_properties;
  1029. aja_source_info.get_defaults = aja_source_get_defaults_v1;
  1030. aja_source_info.save = aja_source_save;
  1031. aja_source_info.icon_type = OBS_ICON_TYPE_CAMERA;
  1032. obs_register_source(&aja_source_info);
  1033. aja_source_info.version = 2;
  1034. aja_source_info.output_flags &= ~OBS_SOURCE_CAP_OBSOLETE;
  1035. aja_source_info.get_defaults = aja_source_get_defaults;
  1036. obs_register_source(&aja_source_info);
  1037. }