aja-routing.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. #include "aja-card-manager.hpp"
  2. #include "aja-common.hpp"
  3. #include "aja-routing.hpp"
  4. #include "aja-widget-io.hpp"
  5. // Signal routing crosspoint and register setting tables for SDI/HDMI/etc.
  6. #include "routing/hdmi_rgb_capture.h"
  7. #include "routing/hdmi_rgb_display.h"
  8. #include "routing/hdmi_ycbcr_capture.h"
  9. #include "routing/hdmi_ycbcr_display.h"
  10. #include "routing/sdi_ycbcr_capture.h"
  11. #include "routing/sdi_ycbcr_display.h"
  12. #include "routing/sdi_rgb_capture.h"
  13. #include "routing/sdi_rgb_display.h"
  14. #include <ajabase/common/common.h>
  15. #include <ajantv2/includes/ntv2card.h>
  16. #include <ajantv2/includes/ntv2devicefeatures.h>
  17. #include <obs-module.h>
  18. RasterDefinition GetRasterDefinition(IOSelection io, NTV2VideoFormat vf,
  19. NTV2DeviceID deviceID)
  20. {
  21. RasterDefinition def = RasterDefinition::Unknown;
  22. if (NTV2_IS_SD_VIDEO_FORMAT(vf)) {
  23. def = RasterDefinition::SD;
  24. } else if (NTV2_IS_HD_VIDEO_FORMAT(vf)) {
  25. def = RasterDefinition::HD;
  26. } else if (NTV2_IS_QUAD_FRAME_FORMAT(vf)) {
  27. def = RasterDefinition::UHD_4K;
  28. /* NOTE(paulh): Special enum for Kona5 Retail & IO4K+ firmwares which route UHD/4K formats
  29. * over 1x 6G/12G SDI using an undocumented crosspoint config.
  30. */
  31. if (aja::IsSDIOneWireIOSelection(io) &&
  32. aja::IsRetailSDI12G(deviceID))
  33. def = RasterDefinition::UHD_4K_Retail_12G;
  34. } else if (NTV2_IS_QUAD_QUAD_FORMAT(vf)) {
  35. def = RasterDefinition::UHD2_8K;
  36. } else {
  37. def = RasterDefinition::Unknown;
  38. }
  39. return def;
  40. }
  41. #define NTV2UTILS_ENUM_CASE_RETURN_STR(enum_name) \
  42. case (enum_name): \
  43. return #enum_name
  44. std::string RasterDefinitionToString(RasterDefinition rd)
  45. {
  46. std::string str = "";
  47. switch (rd) {
  48. NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::SD);
  49. NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::HD);
  50. NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::UHD_4K);
  51. NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::UHD2_8K);
  52. NTV2UTILS_ENUM_CASE_RETURN_STR(RasterDefinition::Unknown);
  53. }
  54. return str;
  55. }
  56. /*
  57. * Parse the widget routing shorthand string into a map of input and output NTV2CrosspointIDs.
  58. * For example "sdi[0][0]->fb[0][0]" is shorthand for connecting the output crosspoint for
  59. * SDI1/Datastream1 (NTV2_XptSDIIn1) to the input crosspoint for Framestore1/Datastream1 (NTV2_XptFrameBuffer1Input).
  60. * These routing shorthand strings are found within the RoutingConfig structs in the "routing" sub-directory of the plugin.
  61. */
  62. bool Routing::ParseRouteString(const std::string &route,
  63. NTV2XptConnections &cnx)
  64. {
  65. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Input string: %s",
  66. route.c_str());
  67. std::string route_lower(route);
  68. route_lower = aja::lower(route_lower);
  69. const std::string &route_strip = aja::replace(route_lower, " ", "");
  70. if (route_strip.empty()) {
  71. blog(LOG_DEBUG,
  72. "Routing::ParseRouteString: input string is empty!");
  73. return false;
  74. }
  75. /* TODO(paulh): Tally up the lines and tokens and check that they are all parsed OK.
  76. * Right now we just return true if ANY tokens were parsed. This is OK _for now_ because
  77. * the route strings currently only come from a known set.
  78. */
  79. NTV2StringList lines;
  80. lines = aja::split(route_strip, ';');
  81. if (lines.empty())
  82. lines.push_back(route_strip);
  83. int32_t parse_ok = 0;
  84. for (const auto &l : lines) {
  85. if (l.empty()) {
  86. blog(LOG_DEBUG,
  87. "aja::Routing::ParseRouteString: Empty line!");
  88. continue;
  89. }
  90. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Line: %s",
  91. l.c_str());
  92. NTV2StringList tokens = aja::split(l, "->");
  93. if (tokens.empty() || tokens.size() != 2) {
  94. blog(LOG_DEBUG,
  95. "aja::Routing::ParseRouteString: Invalid token count!");
  96. continue;
  97. }
  98. const std::string &left = tokens[0]; // output crosspoint
  99. const std::string &right = tokens[1]; // input crosspoint
  100. if (left.empty() || left.length() > 64) {
  101. blog(LOG_DEBUG,
  102. "aja::Routing::ParseRouteString: Invalid Left token!");
  103. continue;
  104. }
  105. if (right.empty() || right.length() > 64) {
  106. blog(LOG_DEBUG,
  107. "aja::Routing::ParseRouteString: Invalid right token!");
  108. continue;
  109. }
  110. blog(LOG_DEBUG,
  111. "aja::Routing::ParseRouteString: Left Token: %s -> Right Token: %s",
  112. left.c_str(), right.c_str());
  113. // Parse Output Crosspoint from left token
  114. int32_t out_chan = 0;
  115. int32_t out_ds = 0;
  116. std::string out_name(64, ' ');
  117. if (std::sscanf(left.c_str(), "%[A-Za-z_0-9][%d][%d]",
  118. &out_name[0], &out_chan, &out_ds)) {
  119. out_name = aja::rstrip(out_name).substr(
  120. 0, out_name.find_first_of('\0'));
  121. WidgetOutputSocket widget_out;
  122. if (WidgetOutputSocket::Find(out_name,
  123. (NTV2Channel)out_chan,
  124. out_ds, widget_out)) {
  125. blog(LOG_DEBUG,
  126. "aja::Routing::ParseRouteString: Found NTV2OutputCrosspointID %s",
  127. NTV2OutputCrosspointIDToString(
  128. widget_out.id)
  129. .c_str());
  130. // Parse Input Crosspoint from right token
  131. int32_t inp_chan = 0;
  132. int32_t inp_ds = 0;
  133. std::string inp_name(64, ' ');
  134. if (std::sscanf(right.c_str(),
  135. "%[A-Za-z_0-9][%d][%d]",
  136. &inp_name[0], &inp_chan,
  137. &inp_ds)) {
  138. inp_name = aja::rstrip(inp_name).substr(
  139. 0,
  140. inp_name.find_first_of('\0'));
  141. WidgetInputSocket widget_inp;
  142. if (WidgetInputSocket::Find(
  143. inp_name,
  144. (NTV2Channel)inp_chan,
  145. inp_ds, widget_inp)) {
  146. blog(LOG_DEBUG,
  147. "aja::Routing::ParseRouteString: Found NTV2InputCrosspointID %s",
  148. NTV2InputCrosspointIDToString(
  149. widget_inp.id)
  150. .c_str());
  151. cnx[widget_inp.id] =
  152. widget_out.id;
  153. parse_ok++;
  154. } else {
  155. blog(LOG_DEBUG,
  156. "aja::Routing::ParseRouteString: NTV2InputCrosspointID not found!");
  157. }
  158. }
  159. } else {
  160. blog(LOG_DEBUG,
  161. "aja::Routing::ParseRouteString: NTV2OutputCrosspointID not found!");
  162. }
  163. }
  164. }
  165. return parse_ok > 0;
  166. }
  167. // Determine the appropriate SDIWireFormat based on the specified device ID and VPID specification.
  168. bool Routing::DetermineSDIWireFormat(NTV2DeviceID deviceID, VPIDSpec spec,
  169. SDIWireFormat &swf)
  170. {
  171. if (deviceID == DEVICE_ID_KONA5 || deviceID == DEVICE_ID_IO4KPLUS) {
  172. static const std::vector<VPIDStandard> kRetail6GVpidStandards = {
  173. VPIDStandard_2160_Single_6Gb,
  174. VPIDStandard_1080_Single_6Gb,
  175. VPIDStandard_1080_AFR_Single_6Gb,
  176. };
  177. static const std::vector<VPIDStandard> kRetail12GVpidStandards =
  178. {VPIDStandard_2160_Single_12Gb,
  179. VPIDStandard_1080_10_12_AFR_Single_12Gb};
  180. if (spec.first == RasterDefinition::UHD_4K &&
  181. aja::vec_contains<VPIDStandard>(kRetail6GVpidStandards,
  182. spec.second)) {
  183. swf = SDIWireFormat::
  184. UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus;
  185. return true;
  186. } else if (spec.first == RasterDefinition::UHD_4K &&
  187. aja::vec_contains<VPIDStandard>(
  188. kRetail12GVpidStandards, spec.second)) {
  189. swf = SDIWireFormat::
  190. UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus;
  191. return true;
  192. } else {
  193. if (kSDIWireFormats.find(spec) !=
  194. kSDIWireFormats.end()) {
  195. swf = kSDIWireFormats.at(spec);
  196. return true;
  197. }
  198. }
  199. } else {
  200. if (kSDIWireFormats.find(spec) != kSDIWireFormats.end()) {
  201. swf = kSDIWireFormats.at(spec);
  202. return true;
  203. }
  204. }
  205. return false;
  206. }
  207. // Lookup configuration for HDMI input/output in the routing table.
  208. bool Routing::FindRoutingConfigHDMI(HDMIWireFormat hwf, NTV2Mode mode,
  209. bool isRGB, NTV2DeviceID deviceID,
  210. RoutingConfig &routing)
  211. {
  212. if (isRGB) {
  213. if (mode == NTV2_MODE_CAPTURE) {
  214. if (kHDMIRGBCaptureConfigs.find(hwf) !=
  215. kHDMIRGBCaptureConfigs.end()) {
  216. routing = kHDMIRGBCaptureConfigs.at(hwf);
  217. return true;
  218. }
  219. } else {
  220. if (deviceID == DEVICE_ID_TTAP_PRO) {
  221. routing = kHDMIRGBDisplayConfigs.at(
  222. HDMIWireFormat::TTAP_PRO);
  223. return true;
  224. }
  225. if (kHDMIRGBDisplayConfigs.find(hwf) !=
  226. kHDMIRGBDisplayConfigs.end()) {
  227. routing = kHDMIRGBDisplayConfigs.at(hwf);
  228. return true;
  229. }
  230. }
  231. } else {
  232. if (mode == NTV2_MODE_CAPTURE) {
  233. if (kHDMIYCbCrCaptureConfigs.find(hwf) !=
  234. kHDMIYCbCrCaptureConfigs.end()) {
  235. routing = kHDMIYCbCrCaptureConfigs.at(hwf);
  236. return true;
  237. }
  238. } else {
  239. if (kHDMIYCbCrDisplayConfigs.find(hwf) !=
  240. kHDMIYCbCrDisplayConfigs.end()) {
  241. routing = kHDMIYCbCrDisplayConfigs.at(hwf);
  242. return true;
  243. }
  244. }
  245. }
  246. return false;
  247. }
  248. // Lookup configuration for SDI input/output in the routing table.
  249. bool Routing::FindRoutingConfigSDI(SDIWireFormat swf, NTV2Mode mode, bool isRGB,
  250. NTV2DeviceID deviceID,
  251. RoutingConfig &routing)
  252. {
  253. UNUSED_PARAMETER(deviceID);
  254. if (isRGB) {
  255. if (mode == NTV2_MODE_CAPTURE) {
  256. if (kSDIRGBCaptureConfigs.find(swf) !=
  257. kSDIRGBCaptureConfigs.end()) {
  258. routing = kSDIRGBCaptureConfigs.at(swf);
  259. return true;
  260. }
  261. } else if (mode == NTV2_MODE_DISPLAY) {
  262. if (kSDIRGBDisplayConfigs.find(swf) !=
  263. kSDIRGBDisplayConfigs.end()) {
  264. routing = kSDIRGBDisplayConfigs.at(swf);
  265. return true;
  266. }
  267. }
  268. } else {
  269. if (mode == NTV2_MODE_CAPTURE) {
  270. if (kSDIYCbCrCaptureConfigs.find(swf) !=
  271. kSDIYCbCrCaptureConfigs.end()) {
  272. routing = kSDIYCbCrCaptureConfigs.at(swf);
  273. return true;
  274. }
  275. } else if (mode == NTV2_MODE_DISPLAY) {
  276. if (kSDIYCbCrDisplayConfigs.find(swf) !=
  277. kSDIYCbCrDisplayConfigs.end()) {
  278. routing = kSDIYCbCrDisplayConfigs.at(swf);
  279. return true;
  280. }
  281. }
  282. }
  283. return false;
  284. }
  285. void Routing::StartSourceAudio(const SourceProps &props, CNTV2Card *card)
  286. {
  287. if (!card)
  288. return;
  289. auto inputSrc = props.inputSource;
  290. auto channel = props.Channel();
  291. auto audioSys = props.AudioSystem();
  292. card->WriteAudioSource(0, channel);
  293. card->SetAudioSystemInputSource(
  294. audioSys, NTV2InputSourceToAudioSource(inputSrc),
  295. NTV2InputSourceToEmbeddedAudioInput(inputSrc));
  296. card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
  297. card->SetAudioRate(props.AudioRate(), audioSys);
  298. card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
  299. // Fix for AJA NTV2 internal bug #11467
  300. ULWord magicAudioBits = 0;
  301. if (NTV2_INPUT_SOURCE_IS_HDMI(inputSrc)) {
  302. switch (inputSrc) {
  303. default:
  304. case NTV2_INPUTSOURCE_HDMI1:
  305. magicAudioBits = 0x00100000;
  306. break;
  307. case NTV2_INPUTSOURCE_HDMI2:
  308. magicAudioBits = 0x00110000;
  309. break;
  310. case NTV2_INPUTSOURCE_HDMI3:
  311. magicAudioBits = 0x00900000;
  312. break;
  313. case NTV2_INPUTSOURCE_HDMI4:
  314. magicAudioBits = 0x00910000;
  315. break;
  316. }
  317. } else if (NTV2_INPUT_SOURCE_IS_ANALOG(inputSrc)) {
  318. magicAudioBits = 0x00000990;
  319. } else { // SDI
  320. magicAudioBits = 0x00000320;
  321. }
  322. // TODO(paulh): Ask aja-seanl about these deprecated calls and if they are still needed
  323. ULWord oldValue = 0;
  324. if (card->ReadAudioSource(oldValue, channel)) {
  325. card->WriteAudioSource(oldValue | magicAudioBits, channel);
  326. }
  327. for (int a = 0; a < NTV2DeviceGetNumAudioSystems(card->GetDeviceID());
  328. a++) {
  329. card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF,
  330. NTV2AudioSystem(a));
  331. }
  332. card->StartAudioInput(audioSys);
  333. card->SetAudioCaptureEnable(audioSys, true);
  334. }
  335. void Routing::StopSourceAudio(const SourceProps &props, CNTV2Card *card)
  336. {
  337. if (card) {
  338. auto audioSys = props.AudioSystem();
  339. card->SetAudioCaptureEnable(audioSys, false);
  340. card->StopAudioInput(audioSys);
  341. }
  342. }
  343. // Guess an SDIWireFormat based on specified Video Format, IOSelection, 4K Transport and device ID.
  344. SDIWireFormat GuessSDIWireFormat(NTV2VideoFormat vf, IOSelection io,
  345. SDI4KTransport t4k,
  346. NTV2DeviceID device_id = DEVICE_ID_NOTFOUND)
  347. {
  348. auto rd = GetRasterDefinition(io, vf, device_id);
  349. auto fg = GetNTV2FrameGeometryFromVideoFormat(vf);
  350. SDIWireFormat swf = SDIWireFormat::Unknown;
  351. if (rd == RasterDefinition::SD) {
  352. swf = SDIWireFormat::SD_ST352;
  353. } else if (rd == RasterDefinition::HD) {
  354. if (fg == NTV2_FG_1280x720) {
  355. swf = SDIWireFormat::HD_720p_ST292;
  356. } else if (fg == NTV2_FG_1920x1080 || fg == NTV2_FG_2048x1080) {
  357. swf = SDIWireFormat::HD_1080_ST292;
  358. }
  359. } else if (rd == RasterDefinition::UHD_4K) {
  360. if (t4k == SDI4KTransport::Squares) {
  361. if (aja::IsSDIFourWireIOSelection(io)) {
  362. swf = SDIWireFormat::UHD4K_ST292_Quad_1_5_Squares;
  363. } else if (aja::IsSDITwoWireIOSelection(io)) {
  364. swf = SDIWireFormat::UHD4K_ST292_Dual_1_5_Squares;
  365. }
  366. } else if (t4k == SDI4KTransport::TwoSampleInterleave) {
  367. if (aja::IsSDIOneWireIOSelection(io)) {
  368. if (NTV2_IS_4K_HFR_VIDEO_FORMAT(vf)) {
  369. if (aja::IsRetailSDI12G(device_id)) {
  370. swf = SDIWireFormat::
  371. UHD4K_ST2018_12G_Squares_2SI_Kona5_io4KPlus;
  372. } else {
  373. swf = SDIWireFormat::
  374. UHD4K_ST2018_12G_Squares_2SI;
  375. }
  376. } else {
  377. if (aja::IsRetailSDI12G(device_id)) {
  378. swf = SDIWireFormat::
  379. UHD4K_ST2018_6G_Squares_2SI_Kona5_io4KPlus;
  380. } else {
  381. swf = SDIWireFormat::
  382. UHD4K_ST2018_6G_Squares_2SI;
  383. }
  384. }
  385. } else if (aja::IsSDITwoWireIOSelection(io)) {
  386. swf = SDIWireFormat::UHD4K_ST425_Dual_3Gb_2SI;
  387. } else if (aja::IsSDIFourWireIOSelection(io)) {
  388. swf = SDIWireFormat::UHD4K_ST425_Quad_3Gb_2SI;
  389. }
  390. }
  391. }
  392. return swf;
  393. }
  394. bool Routing::ConfigureSourceRoute(const SourceProps &props, NTV2Mode mode,
  395. CNTV2Card *card)
  396. {
  397. if (!card)
  398. return false;
  399. auto deviceID = props.deviceID;
  400. NTV2VideoFormat vf = props.videoFormat;
  401. if (NTV2_VIDEO_FORMAT_IS_B(props.videoFormat)) {
  402. vf = aja::GetLevelAFormatForLevelBFormat(props.videoFormat);
  403. }
  404. NTV2InputSourceSet inputSources;
  405. aja::IOSelectionToInputSources(props.ioSelect, inputSources);
  406. if (inputSources.empty()) {
  407. blog(LOG_DEBUG,
  408. "No Input Sources specified to configure routing!");
  409. return false;
  410. }
  411. auto init_src = *inputSources.begin();
  412. auto init_channel = NTV2InputSourceToChannel(init_src);
  413. RoutingConfig rc;
  414. if (NTV2_INPUT_SOURCE_IS_SDI(init_src)) {
  415. SDIWireFormat swf = SDIWireFormat::Unknown;
  416. auto standard = VPIDStandard_Unknown;
  417. auto vpidList = props.vpids;
  418. if (vpidList.size() > 0)
  419. standard = vpidList.at(0).Standard();
  420. if (standard != VPIDStandard_Unknown) {
  421. // Determine SDI format based on raster "definition" and VPID byte 1 value (AKA SMPTE standard)
  422. auto rasterDef = GetRasterDefinition(props.ioSelect, vf,
  423. props.deviceID);
  424. VPIDSpec vpidSpec = std::make_pair(rasterDef, standard);
  425. DetermineSDIWireFormat(deviceID, vpidSpec, swf);
  426. } else {
  427. // Best guess SDI format from incoming video format if no VPIDs detected
  428. swf = GuessSDIWireFormat(vf, props.ioSelect,
  429. props.sdi4kTransport,
  430. props.deviceID);
  431. }
  432. if (swf == SDIWireFormat::Unknown) {
  433. blog(LOG_DEBUG, "Could not determine SDI Wire Format!");
  434. return false;
  435. }
  436. if (!FindRoutingConfigSDI(swf, mode,
  437. NTV2_IS_FBF_RGB(props.pixelFormat),
  438. props.deviceID, rc)) {
  439. blog(LOG_DEBUG,
  440. "Could not find RoutingConfig for SDI Wire Format!");
  441. return false;
  442. }
  443. } else if (NTV2_INPUT_SOURCE_IS_HDMI(init_src)) {
  444. HDMIWireFormat hwf = HDMIWireFormat::Unknown;
  445. if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
  446. if (NTV2_IS_HD_VIDEO_FORMAT(vf))
  447. hwf = HDMIWireFormat::HD_RGB_LFR;
  448. } else {
  449. if (NTV2_IS_HD_VIDEO_FORMAT(vf))
  450. hwf = HDMIWireFormat::HD_YCBCR_LFR;
  451. else if (NTV2_IS_4K_VIDEO_FORMAT(vf))
  452. hwf = HDMIWireFormat::UHD_4K_YCBCR_LFR;
  453. }
  454. if (!FindRoutingConfigHDMI(hwf, mode,
  455. NTV2_IS_FBF_RGB(props.pixelFormat),
  456. props.deviceID, rc)) {
  457. blog(LOG_DEBUG,
  458. "Could not find RoutingConfig for HDMI Wire Format!");
  459. return false;
  460. }
  461. }
  462. // Substitute channel placeholders for actual indices
  463. std::string route_string = rc.route_string;
  464. ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
  465. for (ULWord c = 0; c < 8; c++) {
  466. std::string channel_placeholder =
  467. std::string("{ch" + aja::to_string(c + 1) + "}");
  468. route_string =
  469. aja::replace(route_string, channel_placeholder,
  470. aja::to_string(start_channel_index++));
  471. }
  472. NTV2XptConnections cnx;
  473. ParseRouteString(route_string, cnx);
  474. card->ApplySignalRoute(cnx, false);
  475. // Apply SDI widget settings
  476. start_channel_index = GetIndexForNTV2Channel(init_channel);
  477. for (uint32_t i = (uint32_t)start_channel_index;
  478. i < (start_channel_index + rc.num_wires); i++) {
  479. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  480. if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
  481. card->SetSDITransmitEnable(channel,
  482. mode == NTV2_MODE_DISPLAY);
  483. }
  484. card->SetSDIOut3GEnable(channel, rc.enable_3g_out);
  485. card->SetSDIOut3GbEnable(channel, rc.enable_3gb_out);
  486. card->SetSDIOut6GEnable(channel, rc.enable_6g_out);
  487. card->SetSDIOut12GEnable(channel, rc.enable_12g_out);
  488. card->SetSDIInLevelBtoLevelAConversion((UWord)i,
  489. rc.convert_3g_in);
  490. card->SetSDIOutLevelAtoLevelBConversion((UWord)i,
  491. rc.convert_3g_out);
  492. card->SetSDIOutRGBLevelAConversion((UWord)i,
  493. rc.enable_rgb_3ga_convert);
  494. }
  495. // Apply Framestore settings
  496. for (uint32_t i = (uint32_t)start_channel_index;
  497. i < (start_channel_index + rc.num_framestores); i++) {
  498. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  499. card->EnableChannel(channel);
  500. card->SetMode(channel, mode);
  501. card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
  502. card->SetVideoFormat(vf, false, false, channel);
  503. card->SetFrameBufferFormat(channel, props.pixelFormat);
  504. card->SetTsiFrameEnable(rc.enable_tsi, channel);
  505. card->Set4kSquaresEnable(rc.enable_4k_squares, channel);
  506. card->SetQuadQuadSquaresEnable(rc.enable_8k_squares, channel);
  507. }
  508. return true;
  509. }
  510. bool Routing::ConfigureOutputRoute(const OutputProps &props, NTV2Mode mode,
  511. CNTV2Card *card)
  512. {
  513. if (!card)
  514. return false;
  515. auto deviceID = props.deviceID;
  516. NTV2OutputDestinations outputDests;
  517. aja::IOSelectionToOutputDests(props.ioSelect, outputDests);
  518. if (outputDests.empty()) {
  519. blog(LOG_DEBUG,
  520. "No Output Destinations specified to configure routing!");
  521. return false;
  522. }
  523. auto init_dest = *outputDests.begin();
  524. auto init_channel = NTV2OutputDestinationToChannel(init_dest);
  525. RoutingConfig rc;
  526. if (NTV2_OUTPUT_DEST_IS_SDI(init_dest)) {
  527. SDIWireFormat swf = GuessSDIWireFormat(props.videoFormat,
  528. props.ioSelect,
  529. props.sdi4kTransport,
  530. props.deviceID);
  531. if (swf == SDIWireFormat::Unknown) {
  532. blog(LOG_DEBUG, "Could not determine SDI Wire Format!");
  533. return false;
  534. }
  535. if (!FindRoutingConfigSDI(swf, mode,
  536. NTV2_IS_FBF_RGB(props.pixelFormat),
  537. props.deviceID, rc)) {
  538. blog(LOG_DEBUG,
  539. "Could not find RoutingConfig for SDI Wire Format!");
  540. return false;
  541. }
  542. } else if (NTV2_OUTPUT_DEST_IS_HDMI(init_dest)) {
  543. HDMIWireFormat hwf = HDMIWireFormat::Unknown;
  544. // special case devices...
  545. if (props.deviceID == DEVICE_ID_TTAP_PRO) {
  546. hwf = HDMIWireFormat::TTAP_PRO;
  547. } else {
  548. // ...all other devices.
  549. if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
  550. if (NTV2_IS_HD_VIDEO_FORMAT(props.videoFormat))
  551. hwf = HDMIWireFormat::HD_RGB_LFR;
  552. } else {
  553. if (NTV2_IS_HD_VIDEO_FORMAT(
  554. props.videoFormat)) {
  555. hwf = HDMIWireFormat::HD_YCBCR_LFR;
  556. } else if (NTV2_IS_4K_VIDEO_FORMAT(
  557. props.videoFormat)) {
  558. hwf = HDMIWireFormat::UHD_4K_YCBCR_LFR;
  559. }
  560. }
  561. }
  562. if (!FindRoutingConfigHDMI(hwf, mode,
  563. NTV2_IS_FBF_RGB(props.pixelFormat),
  564. props.deviceID, rc)) {
  565. blog(LOG_DEBUG,
  566. "Could not find RoutingConfig for HDMI Wire Format!");
  567. return false;
  568. }
  569. }
  570. std::string route_string = rc.route_string;
  571. // Replace framestore channel placeholders
  572. ULWord start_framestore_index = initial_framestore_output_index(
  573. deviceID, props.ioSelect, init_channel);
  574. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  575. std::string fs_channel_placeholder =
  576. std::string("fb[{ch" + aja::to_string(c + 1) + "}]");
  577. route_string = aja::replace(
  578. route_string, fs_channel_placeholder,
  579. "fb[" + aja::to_string(start_framestore_index++) + "]");
  580. }
  581. // Replace other channel placeholders
  582. ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
  583. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  584. std::string channel_placeholder =
  585. std::string("{ch" + aja::to_string(c + 1) + "}");
  586. route_string =
  587. aja::replace(route_string, channel_placeholder,
  588. aja::to_string(start_channel_index++));
  589. }
  590. NTV2XptConnections cnx;
  591. ParseRouteString(route_string, cnx);
  592. card->ApplySignalRoute(cnx, false);
  593. // Apply SDI widget settings
  594. if (props.ioSelect != IOSelection::HDMIMonitorOut) {
  595. start_channel_index = GetIndexForNTV2Channel(init_channel);
  596. for (uint32_t i = (uint32_t)start_channel_index;
  597. i < (start_channel_index + rc.num_wires); i++) {
  598. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  599. if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
  600. card->SetSDITransmitEnable(
  601. channel, mode == NTV2_MODE_DISPLAY);
  602. }
  603. card->SetSDIOut3GEnable(channel, rc.enable_3g_out);
  604. card->SetSDIOut3GbEnable(channel, rc.enable_3gb_out);
  605. card->SetSDIOut6GEnable(channel, rc.enable_6g_out);
  606. card->SetSDIOut12GEnable(channel, rc.enable_12g_out);
  607. card->SetSDIInLevelBtoLevelAConversion(
  608. (UWord)i, rc.convert_3g_in);
  609. card->SetSDIOutLevelAtoLevelBConversion(
  610. (UWord)i, rc.convert_3g_out);
  611. card->SetSDIOutRGBLevelAConversion(
  612. (UWord)i, rc.enable_rgb_3ga_convert);
  613. }
  614. }
  615. // Apply Framestore settings
  616. start_framestore_index = initial_framestore_output_index(
  617. deviceID, props.ioSelect, init_channel);
  618. for (uint32_t i = (uint32_t)start_framestore_index;
  619. i < (start_framestore_index + rc.num_framestores); i++) {
  620. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  621. card->EnableChannel(channel);
  622. card->SetMode(channel, mode);
  623. card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
  624. card->SetVideoFormat(props.videoFormat, false, false, channel);
  625. card->SetFrameBufferFormat(channel, props.pixelFormat);
  626. card->SetTsiFrameEnable(rc.enable_tsi, channel);
  627. card->Set4kSquaresEnable(rc.enable_4k_squares, channel);
  628. card->SetQuadQuadSquaresEnable(rc.enable_8k_squares, channel);
  629. }
  630. return true;
  631. }
  632. ULWord Routing::initial_framestore_output_index(NTV2DeviceID deviceID,
  633. IOSelection io,
  634. NTV2Channel init_channel)
  635. {
  636. if (deviceID == DEVICE_ID_TTAP_PRO) {
  637. return 0;
  638. } else if (deviceID == DEVICE_ID_KONA1) {
  639. return 1;
  640. } else if (deviceID == DEVICE_ID_IO4K ||
  641. deviceID == DEVICE_ID_IO4KPLUS) {
  642. // SDI Monitor output uses framestore 4
  643. if (io == IOSelection::SDI5)
  644. return 3;
  645. }
  646. // HDMI Monitor output uses framestore 4
  647. if (io == IOSelection::HDMIMonitorOut) {
  648. return 3;
  649. }
  650. return GetIndexForNTV2Channel(init_channel);
  651. }
  652. // Output Routing
  653. void Routing::ConfigureOutputAudio(const OutputProps &props, CNTV2Card *card)
  654. {
  655. if (!card)
  656. return;
  657. auto deviceID = card->GetDeviceID();
  658. auto audioSys = props.AudioSystem();
  659. auto channel = props.Channel();
  660. card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
  661. card->SetAudioRate(props.AudioRate(), audioSys);
  662. card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
  663. card->SetAudioOutputDelay(audioSys, 0);
  664. card->SetSDIOutputAudioSystem(channel, audioSys);
  665. card->SetSDIOutputDS2AudioSystem(channel, audioSys);
  666. /* NOTE(paulh):
  667. * The SDK has a specifies an SDI audio system by Channel rather than by SDI output
  668. * and certain devices require setting the SDI audio system to NTV2_CHANNEL1.
  669. * i.e.
  670. * SDI 1 = NTV2_CHANNEL1
  671. * SDI 2 = NTV2_CHANNEL2
  672. * ...
  673. * SDI 5/Monitor = NTV2_CHANNEL5
  674. * etc...
  675. *
  676. * This fixes AJA internal bugs: 10730, 10986, 16274
  677. */
  678. if (deviceID == DEVICE_ID_IOXT || deviceID == DEVICE_ID_IO4KUFC ||
  679. deviceID == DEVICE_ID_IO4KPLUS || deviceID == DEVICE_ID_KONA1 ||
  680. deviceID == DEVICE_ID_KONA3G || deviceID == DEVICE_ID_KONA4UFC ||
  681. deviceID == DEVICE_ID_KONA5 || deviceID == DEVICE_ID_KONA5_2X4K) {
  682. // Make sure SDI out 1 (aka Channel 1) is set to the correct sub-system
  683. card->SetSDIOutputAudioSystem(NTV2_CHANNEL1, audioSys);
  684. card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL1, audioSys);
  685. }
  686. // make sure that audio is setup for the SDI monitor output on devices that support it
  687. if (NTV2DeviceCanDoWidget(deviceID, NTV2_WgtSDIMonOut1)) {
  688. card->SetSDIOutputAudioSystem(NTV2_CHANNEL5, audioSys);
  689. card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL5, audioSys);
  690. }
  691. card->SetHDMIOutAudioRate(props.AudioRate());
  692. card->SetHDMIOutAudioFormat(NTV2_AUDIO_FORMAT_LPCM);
  693. card->SetAudioOutputMonitorSource(NTV2_AudioChannel1_2, channel);
  694. card->SetAESOutputSource(NTV2_AudioChannel1_4, audioSys,
  695. NTV2_AudioChannel1_4);
  696. card->SetAESOutputSource(NTV2_AudioChannel5_8, audioSys,
  697. NTV2_AudioChannel5_8);
  698. card->SetAESOutputSource(NTV2_AudioChannel9_12, audioSys,
  699. NTV2_AudioChannel9_12);
  700. card->SetAESOutputSource(NTV2_AudioChannel13_16, audioSys,
  701. NTV2_AudioChannel13_16);
  702. // make sure that audio is setup for HDMI output on devices that support it
  703. if (NTV2DeviceGetNumHDMIVideoOutputs(deviceID) > 0) {
  704. if (NTV2DeviceCanDoAudioMixer(deviceID)) {
  705. card->SetAudioMixerInputAudioSystem(
  706. NTV2_AudioMixerInputMain, audioSys);
  707. card->SetAudioMixerInputChannelSelect(
  708. NTV2_AudioMixerInputMain, NTV2_AudioChannel1_2);
  709. card->SetAudioMixerInputChannelsMute(
  710. NTV2_AudioMixerInputAux1,
  711. NTV2AudioChannelsMuteAll);
  712. card->SetAudioMixerInputChannelsMute(
  713. NTV2_AudioMixerInputAux2,
  714. NTV2AudioChannelsMuteAll);
  715. }
  716. card->SetHDMIOutAudioChannels(NTV2_HDMIAudio8Channels);
  717. card->SetHDMIOutAudioSource2Channel(NTV2_AudioChannel1_2,
  718. audioSys);
  719. card->SetHDMIOutAudioSource8Channel(NTV2_AudioChannel1_8,
  720. audioSys);
  721. }
  722. card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF, audioSys);
  723. card->StopAudioOutput(audioSys);
  724. }