aja-source.cpp 34 KB

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