aja-card-manager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. #include "aja-card-manager.hpp"
  2. #include "aja-common.hpp"
  3. #include "aja-output.hpp"
  4. #include "aja-source.hpp"
  5. #include "obs-properties.h"
  6. #include <util/base.h>
  7. #include <ajantv2/includes/ntv2card.h>
  8. #include <ajantv2/includes/ntv2devicescanner.h>
  9. #include <ajantv2/includes/ntv2devicefeatures.h>
  10. #include <ajantv2/includes/ntv2utils.h>
  11. #include <ajabase/system/process.h>
  12. #include <ajabase/system/systemtime.h>
  13. static const uint32_t kStreamingAppID = NTV2_FOURCC('O', 'B', 'S', ' ');
  14. namespace aja {
  15. CardManager &CardManager::Instance()
  16. {
  17. static CardManager instance;
  18. return instance;
  19. }
  20. CardEntry::CardEntry(uint32_t cardIndex, const std::string &cardID)
  21. : mCardIndex{cardIndex},
  22. mCardID{cardID},
  23. mCard{std::make_unique<CNTV2Card>((UWord)cardIndex)},
  24. mChannelPwnz{},
  25. mMutex{}
  26. {
  27. }
  28. CardEntry::~CardEntry()
  29. {
  30. if (mCard) {
  31. mCard->Close();
  32. mCard.reset();
  33. }
  34. }
  35. CNTV2Card *CardEntry::GetCard()
  36. {
  37. return mCard.get();
  38. }
  39. bool CardEntry::Initialize()
  40. {
  41. if (!mCard) {
  42. blog(LOG_ERROR, "Invalid card instance %s!", mCardID.c_str());
  43. return false;
  44. }
  45. const NTV2DeviceID deviceID = mCard->GetDeviceID();
  46. // Briefly enter Standard Tasks mode to reset card via AJA Daemon/Agent.
  47. auto taskMode = NTV2_STANDARD_TASKS;
  48. mCard->GetEveryFrameServices(taskMode);
  49. if (taskMode != NTV2_STANDARD_TASKS) {
  50. mCard->SetEveryFrameServices(NTV2_STANDARD_TASKS);
  51. AJATime::Sleep(100);
  52. }
  53. mCard->SetEveryFrameServices(NTV2_OEM_TASKS);
  54. const int32_t obsPid = (int32_t)AJAProcess::GetPid();
  55. mCard->AcquireStreamForApplicationWithReference((ULWord)kStreamingAppID,
  56. obsPid);
  57. mCard->SetSuspendHostAudio(true);
  58. mCard->ClearRouting();
  59. if (NTV2DeviceCanDoMultiFormat(deviceID)) {
  60. mCard->SetMultiFormatMode(true);
  61. }
  62. mCard->SetReference(NTV2_REFERENCE_FREERUN);
  63. for (UWord i = 0; i < aja::CardNumAudioSystems(deviceID); i++) {
  64. mCard->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF,
  65. static_cast<NTV2AudioSystem>(i));
  66. }
  67. auto numFramestores = aja::CardNumFramestores(deviceID);
  68. for (UWord i = 0; i < NTV2DeviceGetNumVideoInputs(deviceID); i++) {
  69. mCard->SetInputFrame(static_cast<NTV2Channel>(i), 0xff);
  70. // Disable 3G Level B converter by default
  71. if (NTV2DeviceCanDo3GLevelConversion(deviceID)) {
  72. mCard->SetSDIInLevelBtoLevelAConversion(
  73. static_cast<NTV2Channel>(i), false);
  74. }
  75. }
  76. // SDI Outputs Default State
  77. for (UWord i = 0; i < NTV2DeviceGetNumVideoOutputs(deviceID); i++) {
  78. auto channel = GetNTV2ChannelForIndex(i);
  79. if (NTV2DeviceCanDo3GOut(deviceID, i)) {
  80. mCard->SetSDIOut3GEnable(channel, true);
  81. mCard->SetSDIOut3GbEnable(channel, false);
  82. }
  83. if (NTV2DeviceCanDo12GOut(deviceID, i)) {
  84. mCard->SetSDIOut6GEnable(channel, false);
  85. mCard->SetSDIOut12GEnable(channel, false);
  86. }
  87. if (NTV2DeviceCanDo3GLevelConversion(deviceID)) {
  88. mCard->SetSDIOutLevelAtoLevelBConversion(i, false);
  89. mCard->SetSDIOutRGBLevelAConversion(i, false);
  90. }
  91. }
  92. for (UWord i = 0; i < numFramestores; i++) {
  93. auto channel = GetNTV2ChannelForIndex(i);
  94. if (isAutoCirculateRunning(channel)) {
  95. mCard->AutoCirculateStop(channel, true);
  96. }
  97. mCard->SetVideoFormat(NTV2_FORMAT_1080p_5994_A, false, false,
  98. channel);
  99. mCard->SetFrameBufferFormat(channel, NTV2_FBF_8BIT_YCBCR);
  100. mCard->DisableChannel(channel);
  101. }
  102. blog(LOG_DEBUG, "NTV2 Card Initialized: %s", mCardID.c_str());
  103. return true;
  104. }
  105. uint32_t CardEntry::GetCardIndex() const
  106. {
  107. return mCardIndex;
  108. }
  109. std::string CardEntry::GetCardID() const
  110. {
  111. return mCardID;
  112. }
  113. std::string CardEntry::GetDisplayName() const
  114. {
  115. if (mCard) {
  116. std::ostringstream oss;
  117. oss << mCard->GetIndexNumber() << " - "
  118. << mCard->GetModelName();
  119. const std::string &serial = GetSerial();
  120. if (!serial.empty())
  121. oss << " (" << serial << ")";
  122. return oss.str();
  123. }
  124. // very bad if we get here...
  125. return "Unknown";
  126. }
  127. std::string CardEntry::GetSerial() const
  128. {
  129. std::string serial;
  130. if (mCard)
  131. mCard->GetSerialNumberString(serial);
  132. return serial;
  133. }
  134. NTV2DeviceID CardEntry::GetDeviceID() const
  135. {
  136. NTV2DeviceID id = DEVICE_ID_NOTFOUND;
  137. if (mCard)
  138. id = mCard->GetDeviceID();
  139. return id;
  140. }
  141. bool CardEntry::ChannelReady(NTV2Channel chan, const std::string &owner) const
  142. {
  143. const std::lock_guard<std::mutex> lock(mMutex);
  144. for (const auto &pwn : mChannelPwnz) {
  145. if (pwn.second & (1 << static_cast<int32_t>(chan))) {
  146. return pwn.first == owner;
  147. }
  148. }
  149. return true;
  150. }
  151. bool CardEntry::AcquireChannel(NTV2Channel chan, NTV2Mode mode,
  152. const std::string &owner)
  153. {
  154. bool acquired = false;
  155. if (ChannelReady(chan, owner)) {
  156. const std::lock_guard<std::mutex> lock(mMutex);
  157. if (mChannelPwnz.find(owner) != mChannelPwnz.end()) {
  158. if (mChannelPwnz[owner] &
  159. (1 << static_cast<int32_t>(chan))) {
  160. acquired = true;
  161. } else {
  162. mChannelPwnz[owner] |=
  163. (1 << static_cast<int32_t>(chan));
  164. acquired = true;
  165. }
  166. } else {
  167. mChannelPwnz[owner] |=
  168. (1 << static_cast<int32_t>(chan));
  169. acquired = true;
  170. }
  171. // Acquire interrupt handles
  172. if (acquired && mCard) {
  173. if (mode == NTV2_MODE_CAPTURE) {
  174. mCard->EnableInputInterrupt(chan);
  175. mCard->SubscribeInputVerticalEvent(chan);
  176. } else if (mode == NTV2_MODE_DISPLAY) {
  177. mCard->EnableOutputInterrupt(chan);
  178. mCard->SubscribeOutputVerticalEvent(chan);
  179. }
  180. }
  181. }
  182. return acquired;
  183. }
  184. bool CardEntry::ReleaseChannel(NTV2Channel chan, NTV2Mode mode,
  185. const std::string &owner)
  186. {
  187. const std::lock_guard<std::mutex> lock(mMutex);
  188. for (const auto &pwn : mChannelPwnz) {
  189. if (pwn.first == owner) {
  190. if (mChannelPwnz[owner] &
  191. (1 << static_cast<int32_t>(chan))) {
  192. mChannelPwnz[owner] ^=
  193. (1 << static_cast<int32_t>(chan));
  194. // Release interrupt handles
  195. if (mCard) {
  196. if (mode == NTV2_MODE_CAPTURE) {
  197. mCard->DisableInputInterrupt(
  198. chan);
  199. mCard->UnsubscribeInputVerticalEvent(
  200. chan);
  201. } else if (mode == NTV2_MODE_DISPLAY) {
  202. mCard->DisableOutputInterrupt(
  203. chan);
  204. mCard->UnsubscribeOutputVerticalEvent(
  205. chan);
  206. }
  207. }
  208. return true;
  209. }
  210. }
  211. }
  212. return false;
  213. }
  214. bool CardEntry::InputSelectionReady(IOSelection io, NTV2DeviceID id,
  215. const std::string &owner) const
  216. {
  217. UNUSED_PARAMETER(id);
  218. NTV2InputSourceSet inputSources;
  219. aja::IOSelectionToInputSources(io, inputSources);
  220. if (inputSources.size() > 0) {
  221. size_t channelsReady = 0;
  222. for (auto &&src : inputSources) {
  223. auto channel = NTV2InputSourceToChannel(src);
  224. if (ChannelReady(channel, owner))
  225. channelsReady++;
  226. }
  227. if (channelsReady == inputSources.size())
  228. return true;
  229. }
  230. return false;
  231. }
  232. bool CardEntry::OutputSelectionReady(IOSelection io, NTV2DeviceID id,
  233. const std::string &owner) const
  234. {
  235. /* Handle checking special case outputs before all other outputs.
  236. * 1. HDMI Monitor uses framestore 4
  237. * 2. SDI Monitor on Io 4K/Io 4K Plus, etc. uses framestore 4.
  238. * 3. Everything else...
  239. */
  240. if (aja::CardCanDoHDMIMonitorOutput(id) &&
  241. io == IOSelection::HDMIMonitorOut) {
  242. NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
  243. return ChannelReady(hdmiMonChannel, owner);
  244. } else if (aja::CardCanDoSDIMonitorOutput(id) &&
  245. io == IOSelection::SDI5) {
  246. NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
  247. return ChannelReady(sdiMonChannel, owner);
  248. } else {
  249. NTV2OutputDestinations outputDests;
  250. aja::IOSelectionToOutputDests(io, outputDests);
  251. if (outputDests.size() > 0) {
  252. size_t channelsReady = 0;
  253. for (auto &&dst : outputDests) {
  254. auto channel =
  255. NTV2OutputDestinationToChannel(dst);
  256. if (ChannelReady(channel, owner))
  257. channelsReady++;
  258. }
  259. if (channelsReady == outputDests.size())
  260. return true;
  261. }
  262. }
  263. return false;
  264. }
  265. bool CardEntry::AcquireInputSelection(IOSelection io, NTV2DeviceID id,
  266. const std::string &owner)
  267. {
  268. UNUSED_PARAMETER(id);
  269. NTV2InputSourceSet inputSources;
  270. aja::IOSelectionToInputSources(io, inputSources);
  271. std::vector<NTV2Channel> acquiredChannels;
  272. for (auto &&src : inputSources) {
  273. auto acqChan = NTV2InputSourceToChannel(src);
  274. if (AcquireChannel(acqChan, NTV2_MODE_CAPTURE, owner)) {
  275. blog(LOG_DEBUG, "Source %s acquired channel %s",
  276. owner.c_str(),
  277. NTV2ChannelToString(acqChan).c_str());
  278. acquiredChannels.push_back(acqChan);
  279. } else {
  280. blog(LOG_DEBUG,
  281. "Source %s could not acquire channel %s",
  282. owner.c_str(),
  283. NTV2ChannelToString(acqChan).c_str());
  284. }
  285. }
  286. // Release channels if we couldn't acquire all required channels.
  287. if (acquiredChannels.size() != inputSources.size()) {
  288. for (auto &&ac : acquiredChannels) {
  289. ReleaseChannel(ac, NTV2_MODE_CAPTURE, owner);
  290. }
  291. }
  292. return acquiredChannels.size() == inputSources.size();
  293. }
  294. bool CardEntry::ReleaseInputSelection(IOSelection io, NTV2DeviceID id,
  295. const std::string &owner)
  296. {
  297. UNUSED_PARAMETER(id);
  298. NTV2InputSourceSet currentInputSources;
  299. aja::IOSelectionToInputSources(io, currentInputSources);
  300. uint32_t releasedCount = 0;
  301. for (auto &&src : currentInputSources) {
  302. auto relChan = NTV2InputSourceToChannel(src);
  303. if (ReleaseChannel(relChan, NTV2_MODE_CAPTURE, owner)) {
  304. blog(LOG_DEBUG, "Released Channel %s",
  305. NTV2ChannelToString(relChan).c_str());
  306. releasedCount++;
  307. }
  308. }
  309. return releasedCount == currentInputSources.size();
  310. }
  311. bool CardEntry::AcquireOutputSelection(IOSelection io, NTV2DeviceID id,
  312. const std::string &owner)
  313. {
  314. std::vector<NTV2Channel> acquiredChannels;
  315. NTV2OutputDestinations outputDests;
  316. aja::IOSelectionToOutputDests(io, outputDests);
  317. // Handle acquiring special case outputs --
  318. // HDMI Monitor uses framestore 4
  319. if (aja::CardCanDoHDMIMonitorOutput(id) &&
  320. io == IOSelection::HDMIMonitorOut) {
  321. NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
  322. if (AcquireChannel(hdmiMonChannel, NTV2_MODE_DISPLAY, owner)) {
  323. blog(LOG_DEBUG, "Output %s acquired channel %s",
  324. owner.c_str(),
  325. NTV2ChannelToString(hdmiMonChannel).c_str());
  326. acquiredChannels.push_back(hdmiMonChannel);
  327. } else {
  328. blog(LOG_DEBUG,
  329. "Output %s could not acquire channel %s",
  330. owner.c_str(),
  331. NTV2ChannelToString(hdmiMonChannel).c_str());
  332. }
  333. } else if (aja::CardCanDoSDIMonitorOutput(id) &&
  334. io == IOSelection::SDI5) {
  335. // SDI Monitor on io4K/io4K+/etc. uses framestore 4
  336. NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
  337. if (AcquireChannel(sdiMonChannel, NTV2_MODE_DISPLAY, owner)) {
  338. blog(LOG_DEBUG, "Output %s acquired channel %s",
  339. owner.c_str(),
  340. NTV2ChannelToString(sdiMonChannel).c_str());
  341. acquiredChannels.push_back(sdiMonChannel);
  342. } else {
  343. blog(LOG_DEBUG,
  344. "Output %s could not acquire channel %s",
  345. owner.c_str(),
  346. NTV2ChannelToString(sdiMonChannel).c_str());
  347. }
  348. } else {
  349. // Handle acquiring all other channels
  350. for (auto &&dst : outputDests) {
  351. auto acqChan = NTV2OutputDestinationToChannel(dst);
  352. if (AcquireChannel(acqChan, NTV2_MODE_DISPLAY, owner)) {
  353. acquiredChannels.push_back(acqChan);
  354. blog(LOG_DEBUG, "Output %s acquired channel %s",
  355. owner.c_str(),
  356. NTV2ChannelToString(acqChan).c_str());
  357. } else {
  358. blog(LOG_DEBUG,
  359. "Output %s could not acquire channel %s",
  360. owner.c_str(),
  361. NTV2ChannelToString(acqChan).c_str());
  362. }
  363. }
  364. // Release channels if we couldn't acquire all required channels.
  365. if (acquiredChannels.size() != outputDests.size()) {
  366. for (auto &&ac : acquiredChannels) {
  367. ReleaseChannel(ac, NTV2_MODE_DISPLAY, owner);
  368. }
  369. }
  370. }
  371. return acquiredChannels.size() == outputDests.size();
  372. }
  373. bool CardEntry::ReleaseOutputSelection(IOSelection io, NTV2DeviceID id,
  374. const std::string &owner)
  375. {
  376. NTV2OutputDestinations currentOutputDests;
  377. aja::IOSelectionToOutputDests(io, currentOutputDests);
  378. uint32_t releasedCount = 0;
  379. // Handle releasing special case outputs --
  380. // HDMI Monitor uses framestore 4
  381. if (aja::CardCanDoHDMIMonitorOutput(id) &&
  382. io == IOSelection::HDMIMonitorOut) {
  383. NTV2Channel hdmiMonChannel = NTV2_CHANNEL4;
  384. if (ReleaseChannel(hdmiMonChannel, NTV2_MODE_DISPLAY, owner)) {
  385. blog(LOG_DEBUG, "Released Channel %s",
  386. NTV2ChannelToString(hdmiMonChannel).c_str());
  387. releasedCount++;
  388. }
  389. } else if (aja::CardCanDoSDIMonitorOutput(id) &&
  390. io == IOSelection::SDI5) {
  391. // SDI Monitor on io4K/io4K+/etc. uses framestore 4
  392. NTV2Channel sdiMonChannel = NTV2_CHANNEL4;
  393. if (ReleaseChannel(sdiMonChannel, NTV2_MODE_DISPLAY, owner)) {
  394. blog(LOG_DEBUG, "Released Channel %s",
  395. NTV2ChannelToString(sdiMonChannel).c_str());
  396. releasedCount++;
  397. }
  398. } else {
  399. // Release all other channels
  400. for (auto &&dst : currentOutputDests) {
  401. auto relChan = NTV2OutputDestinationToChannel(dst);
  402. if (ReleaseChannel(relChan, NTV2_MODE_DISPLAY, owner)) {
  403. blog(LOG_DEBUG, "Released Channel %s",
  404. NTV2ChannelToString(relChan).c_str());
  405. releasedCount++;
  406. }
  407. }
  408. }
  409. return releasedCount == currentOutputDests.size();
  410. }
  411. bool CardEntry::UpdateChannelOwnerName(const std::string &oldName,
  412. const std::string &newName)
  413. {
  414. const std::lock_guard<std::mutex> lock(mMutex);
  415. for (const auto &pwn : mChannelPwnz) {
  416. if (pwn.first == oldName) {
  417. mChannelPwnz.insert(std::pair<std::string, int32_t>{
  418. newName, pwn.second});
  419. mChannelPwnz.erase(oldName);
  420. return true;
  421. }
  422. }
  423. return false;
  424. }
  425. bool CardEntry::isAutoCirculateRunning(NTV2Channel chan)
  426. {
  427. if (!mCard)
  428. return false;
  429. AUTOCIRCULATE_STATUS acStatus;
  430. if (mCard->AutoCirculateGetStatus(chan, acStatus)) {
  431. if (acStatus.acState != NTV2_AUTOCIRCULATE_RUNNING &&
  432. acStatus.acState != NTV2_AUTOCIRCULATE_STARTING &&
  433. acStatus.acState != NTV2_AUTOCIRCULATE_PAUSED &&
  434. acStatus.acState != NTV2_AUTOCIRCULATE_INIT) {
  435. return false;
  436. }
  437. }
  438. return true;
  439. }
  440. void CardManager::ClearCardEntries()
  441. {
  442. const std::lock_guard<std::mutex> lock(mMutex);
  443. for (auto &&entry : mCardEntries) {
  444. CNTV2Card *card = entry.second->GetCard();
  445. if (card) {
  446. card->SetSuspendHostAudio(false);
  447. card->SetEveryFrameServices(NTV2_STANDARD_TASKS);
  448. // Workaround for AJA internal bug #11378
  449. // Set HDMI output back to Audio System 1 on card release
  450. if (NTV2DeviceGetNumHDMIVideoOutputs(
  451. card->GetDeviceID()) > 0) {
  452. card->SetHDMIOutAudioSource8Channel(
  453. NTV2_AudioChannel1_8,
  454. NTV2_AUDIOSYSTEM_1);
  455. }
  456. int32_t pid = (int32_t)AJAProcess::GetPid();
  457. card->ReleaseStreamForApplicationWithReference(
  458. (ULWord)kStreamingAppID, pid);
  459. }
  460. }
  461. mCardEntries.clear();
  462. }
  463. void CardManager::EnumerateCards()
  464. {
  465. const std::lock_guard<std::mutex> lock(mMutex);
  466. CNTV2DeviceScanner scanner;
  467. for (const auto &iter : scanner.GetDeviceInfoList()) {
  468. CNTV2Card card((UWord)iter.deviceIndex);
  469. const std::string &cardID = aja::MakeCardID(card);
  470. // New Card Entry
  471. if (mCardEntries.find(cardID) == mCardEntries.end()) {
  472. CardEntryPtr cardEntry = std::make_shared<CardEntry>(
  473. iter.deviceIndex, cardID);
  474. if (cardEntry && cardEntry->Initialize())
  475. mCardEntries.emplace(cardID, cardEntry);
  476. } else {
  477. // Card fell off of the bus and came back with a new physical index?
  478. auto currEntry = mCardEntries[cardID];
  479. if (currEntry) {
  480. if (currEntry->GetCardIndex() !=
  481. iter.deviceIndex) {
  482. mCardEntries.erase(cardID);
  483. CardEntryPtr cardEntry =
  484. std::make_shared<CardEntry>(
  485. iter.deviceIndex,
  486. cardID);
  487. if (cardEntry &&
  488. cardEntry->Initialize())
  489. mCardEntries.emplace(cardID,
  490. cardEntry);
  491. }
  492. }
  493. }
  494. }
  495. }
  496. size_t CardManager::NumCardEntries() const
  497. {
  498. const std::lock_guard<std::mutex> lock(mMutex);
  499. return mCardEntries.size();
  500. }
  501. CNTV2Card *CardManager::GetCard(const std::string &cardID)
  502. {
  503. auto entry = GetCardEntry(cardID);
  504. if (entry)
  505. return entry->GetCard();
  506. return nullptr;
  507. }
  508. const CardEntryPtr CardManager::GetCardEntry(const std::string &cardID) const
  509. {
  510. const std::lock_guard<std::mutex> lock(mMutex);
  511. for (const auto &entry : mCardEntries) {
  512. if (entry.second && entry.second->GetCardID() == cardID)
  513. return entry.second;
  514. }
  515. return nullptr;
  516. }
  517. const CardEntries &CardManager::GetCardEntries() const
  518. {
  519. const std::lock_guard<std::mutex> lock(mMutex);
  520. return mCardEntries;
  521. }
  522. const CardEntries::iterator CardManager::begin()
  523. {
  524. const std::lock_guard<std::mutex> lock(mMutex);
  525. return mCardEntries.begin();
  526. }
  527. const CardEntries::iterator CardManager::end()
  528. {
  529. const std::lock_guard<std::mutex> lock(mMutex);
  530. return mCardEntries.end();
  531. }
  532. } // aja