aja-card-manager.cpp 16 KB

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