aja-routing.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. #include "aja-card-manager.hpp"
  2. #include "aja-common.hpp"
  3. #include "aja-routing.hpp"
  4. #include "aja-widget-io.hpp"
  5. #include <ajabase/common/common.h>
  6. #include <ajantv2/includes/ntv2card.h>
  7. #include <ajantv2/includes/ntv2devicefeatures.h>
  8. #include <ajantv2/includes/ntv2utils.h>
  9. #include <obs-module.h>
  10. namespace aja {
  11. /*
  12. * Parse the widget routing shorthand string into a map of input and output NTV2CrosspointIDs.
  13. * For example "sdi[0][0]->fb[0][0]" is shorthand for connecting the output crosspoint for
  14. * SDI1/Datastream1 (NTV2_XptSDIIn1) to the input crosspoint for Framestore1/Datastream1 (NTV2_XptFrameBuffer1Input).
  15. * These routing shorthand strings are found within the RoutingConfig structs in the "routing" sub-directory of the plugin.
  16. */
  17. bool Routing::ParseRouteString(const std::string &route, NTV2XptConnections &cnx)
  18. {
  19. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Input string: %s", route.c_str());
  20. std::string route_strip(route);
  21. aja::lower(route_strip);
  22. aja::replace(route_strip, " ", "");
  23. if (route_strip.empty()) {
  24. blog(LOG_DEBUG, "Routing::ParseRouteString: input string is empty!");
  25. return false;
  26. }
  27. /* TODO(paulh): Tally up the lines and tokens and check that they are all parsed OK.
  28. * Right now we just return true if ANY tokens were parsed. This is OK _for now_ because
  29. * the route strings currently only come from a known set.
  30. */
  31. NTV2StringList lines;
  32. lines = aja::split(route_strip, ';');
  33. if (lines.empty())
  34. lines.push_back(route_strip);
  35. int32_t parse_ok = 0;
  36. for (const auto &l : lines) {
  37. if (l.empty()) {
  38. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Empty line!");
  39. continue;
  40. }
  41. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Line: %s", l.c_str());
  42. NTV2StringList tokens = aja::split(l, "->");
  43. if (tokens.empty() || tokens.size() != 2) {
  44. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Invalid token count!");
  45. continue;
  46. }
  47. const std::string &left = tokens[0]; // output crosspoint
  48. const std::string &right = tokens[1]; // input crosspoint
  49. if (left.empty() || left.length() > 64) {
  50. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Invalid Left token!");
  51. continue;
  52. }
  53. if (right.empty() || right.length() > 64) {
  54. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Invalid right token!");
  55. continue;
  56. }
  57. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Left Token: %s -> Right Token: %s", left.c_str(),
  58. right.c_str());
  59. // Parse Output Crosspoint from left token
  60. int32_t out_chan = 0;
  61. int32_t out_ds = 0;
  62. std::string out_name(64, ' ');
  63. if (std::sscanf(left.c_str(), "%[A-Za-z_0-9][%d][%d]", &out_name[0], &out_chan, &out_ds)) {
  64. out_name = aja::rstrip(out_name).substr(0, out_name.find_first_of('\0'));
  65. WidgetOutputSocket widget_out;
  66. if (WidgetOutputSocket::Find(out_name, (NTV2Channel)out_chan, out_ds, widget_out)) {
  67. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: Found NTV2OutputCrosspointID %s",
  68. NTV2OutputCrosspointIDToString(widget_out.id).c_str());
  69. // Parse Input Crosspoint from right token
  70. int32_t inp_chan = 0;
  71. int32_t inp_ds = 0;
  72. std::string inp_name(64, ' ');
  73. if (std::sscanf(right.c_str(), "%[A-Za-z_0-9][%d][%d]", &inp_name[0], &inp_chan,
  74. &inp_ds)) {
  75. inp_name = aja::rstrip(inp_name).substr(0, inp_name.find_first_of('\0'));
  76. WidgetInputSocket widget_inp;
  77. if (WidgetInputSocket::Find(inp_name, (NTV2Channel)inp_chan, inp_ds,
  78. widget_inp)) {
  79. blog(LOG_DEBUG,
  80. "aja::Routing::ParseRouteString: Found NTV2InputCrosspointID %s",
  81. NTV2InputCrosspointIDToString(widget_inp.id).c_str());
  82. cnx[widget_inp.id] = widget_out.id;
  83. parse_ok++;
  84. } else {
  85. blog(LOG_DEBUG,
  86. "aja::Routing::ParseRouteString: NTV2InputCrosspointID not found!");
  87. }
  88. }
  89. } else {
  90. blog(LOG_DEBUG, "aja::Routing::ParseRouteString: NTV2OutputCrosspointID not found!");
  91. }
  92. }
  93. }
  94. return parse_ok > 0;
  95. }
  96. void Routing::StartSourceAudio(const SourceProps &props, CNTV2Card *card)
  97. {
  98. if (!card)
  99. return;
  100. auto inputSrc = props.InitialInputSource();
  101. auto channel = props.Channel();
  102. auto audioSys = props.AudioSystem();
  103. card->WriteAudioSource(0, channel);
  104. card->SetAudioSystemInputSource(audioSys, NTV2InputSourceToAudioSource(inputSrc),
  105. NTV2InputSourceToEmbeddedAudioInput(inputSrc));
  106. card->SetNumberAudioChannels(kDefaultAudioChannels, audioSys);
  107. card->SetAudioRate(props.AudioRate(), audioSys);
  108. card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
  109. // Fix for AJA NTV2 internal bug #11467
  110. ULWord magicAudioBits = 0;
  111. if (NTV2_INPUT_SOURCE_IS_HDMI(inputSrc)) {
  112. switch (inputSrc) {
  113. default:
  114. case NTV2_INPUTSOURCE_HDMI1:
  115. magicAudioBits = 0x00100000;
  116. break;
  117. case NTV2_INPUTSOURCE_HDMI2:
  118. magicAudioBits = 0x00110000;
  119. break;
  120. case NTV2_INPUTSOURCE_HDMI3:
  121. magicAudioBits = 0x00900000;
  122. break;
  123. case NTV2_INPUTSOURCE_HDMI4:
  124. magicAudioBits = 0x00910000;
  125. break;
  126. }
  127. } else if (NTV2_INPUT_SOURCE_IS_ANALOG(inputSrc)) {
  128. magicAudioBits = 0x00000990;
  129. } else { // SDI
  130. magicAudioBits = 0x00000320;
  131. }
  132. // TODO(paulh): Ask aja-seanl about these deprecated calls and if they are still needed
  133. ULWord oldValue = 0;
  134. if (card->ReadAudioSource(oldValue, channel)) {
  135. card->WriteAudioSource(oldValue | magicAudioBits, channel);
  136. }
  137. for (int a = 0; a < NTV2DeviceGetNumAudioSystems(card->GetDeviceID()); a++) {
  138. card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_ON, NTV2AudioSystem(a));
  139. }
  140. card->StartAudioInput(audioSys);
  141. card->SetAudioCaptureEnable(audioSys, true);
  142. }
  143. void Routing::StopSourceAudio(const SourceProps &props, CNTV2Card *card)
  144. {
  145. if (card) {
  146. auto audioSys = props.AudioSystem();
  147. card->SetAudioCaptureEnable(audioSys, false);
  148. card->StopAudioInput(audioSys);
  149. }
  150. }
  151. bool Routing::ConfigureSourceRoute(const SourceProps &props, NTV2Mode mode, CNTV2Card *card, NTV2XptConnections &cnx)
  152. {
  153. if (!card)
  154. return false;
  155. auto deviceID = props.deviceID;
  156. NTV2VideoFormat vf = props.videoFormat;
  157. auto init_src = props.InitialInputSource();
  158. auto init_channel = props.Channel();
  159. RoutingConfigurator rc;
  160. RoutingPreset rp;
  161. ConnectionKind kind = ConnectionKind::Unknown;
  162. VPIDStandard vpidStandard = VPIDStandard_Unknown;
  163. HDMIWireFormat hwf = HDMIWireFormat::Unknown;
  164. if (NTV2_INPUT_SOURCE_IS_SDI(init_src)) {
  165. kind = ConnectionKind::SDI;
  166. if (props.autoDetect) {
  167. auto vpidList = props.vpids;
  168. if (vpidList.size() > 0)
  169. vpidStandard = vpidList.at(0).Standard();
  170. }
  171. if (vpidStandard == VPIDStandard_Unknown) {
  172. vpidStandard = DetermineVPIDStandard(props.ioSelect, props.videoFormat, props.pixelFormat,
  173. props.sdiTransport, props.sdi4kTransport);
  174. }
  175. } else if (NTV2_INPUT_SOURCE_IS_HDMI(init_src)) {
  176. kind = ConnectionKind::HDMI;
  177. if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
  178. if (NTV2_IS_HD_VIDEO_FORMAT(vf)) {
  179. hwf = HDMIWireFormat::SD_HD_RGB;
  180. } else if (NTV2_IS_4K_VIDEO_FORMAT(vf)) {
  181. hwf = HDMIWireFormat::UHD_4K_RGB;
  182. }
  183. } else {
  184. if (NTV2_IS_HD_VIDEO_FORMAT(vf)) {
  185. hwf = HDMIWireFormat::SD_HD_YCBCR;
  186. } else if (NTV2_IS_4K_VIDEO_FORMAT(vf)) {
  187. hwf = HDMIWireFormat::UHD_4K_YCBCR;
  188. }
  189. }
  190. } else {
  191. blog(LOG_WARNING, "Unsupported connection kind. SDI and HDMI only!");
  192. return false;
  193. }
  194. if (!rc.FindFirstPreset(kind, props.deviceID, NTV2_MODE_CAPTURE, vf, props.pixelFormat, vpidStandard, hwf,
  195. rp)) {
  196. blog(LOG_WARNING, "No SDI capture routing preset found!");
  197. return false;
  198. }
  199. LogRoutingPreset(rp);
  200. // Substitute channel placeholders for actual indices
  201. std::string route_string = rp.route_string;
  202. // Channel-substitution for widgets associated with framestore channel(s)
  203. ULWord start_framestore_index = GetIndexForNTV2Channel(props.Framestore());
  204. const std::vector<std::string> fs_associated = {"fb", "tsi", "dli"};
  205. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  206. for (const auto &name : fs_associated) {
  207. std::string placeholder = std::string(name + "[{ch" + aja::to_string(c + 1) + "}]");
  208. aja::replace(route_string, placeholder,
  209. name + "[" + aja::to_string(start_framestore_index) + "]");
  210. }
  211. start_framestore_index++;
  212. }
  213. // Replace other channel placeholders
  214. ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
  215. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  216. std::string channel_placeholder = std::string("{ch" + aja::to_string(c + 1) + "}");
  217. aja::replace(route_string, channel_placeholder, aja::to_string(start_channel_index++));
  218. }
  219. if (!ParseRouteString(route_string, cnx))
  220. return false;
  221. card->ApplySignalRoute(cnx, false);
  222. // Apply SDI widget settings
  223. start_channel_index = GetIndexForNTV2Channel(init_channel);
  224. for (uint32_t i = (uint32_t)start_channel_index; i < (start_channel_index + rp.num_channels); i++) {
  225. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  226. if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
  227. card->SetSDITransmitEnable(channel, mode == NTV2_MODE_DISPLAY);
  228. }
  229. card->SetSDIOut3GEnable(channel, rp.flags & kEnable3GOut);
  230. card->SetSDIOut3GbEnable(channel, rp.flags & kEnable3GbOut);
  231. card->SetSDIOut6GEnable(channel, rp.flags & kEnable6GOut);
  232. card->SetSDIOut12GEnable(channel, rp.flags & kEnable12GOut);
  233. card->SetSDIInLevelBtoLevelAConversion((UWord)i, rp.flags & kConvert3GIn);
  234. card->SetSDIOutLevelAtoLevelBConversion((UWord)i, rp.flags & kConvert3GOut);
  235. card->SetSDIOutRGBLevelAConversion((UWord)i, rp.flags & kConvert3GaRGBOut);
  236. }
  237. // Apply HDMI settings
  238. if (aja::IsIOSelectionHDMI(props.ioSelect)) {
  239. if (NTV2_IS_4K_VIDEO_FORMAT(props.videoFormat))
  240. card->SetHDMIV2Mode(NTV2_HDMI_V2_4K_CAPTURE);
  241. else
  242. card->SetHDMIV2Mode(NTV2_HDMI_V2_HDSD_BIDIRECTIONAL);
  243. }
  244. // Apply Framestore settings
  245. start_framestore_index = GetIndexForNTV2Channel(props.Framestore());
  246. for (uint32_t i = (uint32_t)start_framestore_index; i < (start_framestore_index + rp.num_framestores); i++) {
  247. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  248. card->EnableChannel(channel);
  249. card->SetMode(channel, mode);
  250. card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
  251. card->SetVideoFormat(vf, false, false, channel);
  252. card->SetFrameBufferFormat(channel, props.pixelFormat);
  253. card->SetTsiFrameEnable(rp.flags & kEnable4KTSI, channel);
  254. card->Set4kSquaresEnable(rp.flags & kEnable4KSquares, channel);
  255. card->SetQuadQuadSquaresEnable(rp.flags & kEnable8KSquares, channel);
  256. }
  257. return true;
  258. }
  259. bool Routing::ConfigureOutputRoute(const OutputProps &props, NTV2Mode mode, CNTV2Card *card, NTV2XptConnections &cnx)
  260. {
  261. if (!card)
  262. return false;
  263. auto deviceID = props.deviceID;
  264. NTV2OutputDestinations outputDests;
  265. aja::IOSelectionToOutputDests(props.ioSelect, outputDests);
  266. if (outputDests.empty()) {
  267. blog(LOG_DEBUG, "No Output Destinations specified to configure routing!");
  268. return false;
  269. }
  270. auto init_dest = *outputDests.begin();
  271. RoutingConfigurator rc;
  272. RoutingPreset rp;
  273. ConnectionKind kind = ConnectionKind::Unknown;
  274. VPIDStandard vpidStandard = VPIDStandard_Unknown;
  275. HDMIWireFormat hwf = HDMIWireFormat::Unknown;
  276. if (NTV2_OUTPUT_DEST_IS_SDI(init_dest)) {
  277. kind = ConnectionKind::SDI;
  278. vpidStandard = DetermineVPIDStandard(props.ioSelect, props.videoFormat, props.pixelFormat,
  279. props.sdiTransport, props.sdi4kTransport);
  280. } else if (NTV2_OUTPUT_DEST_IS_HDMI(init_dest)) {
  281. kind = ConnectionKind::HDMI;
  282. hwf = HDMIWireFormat::Unknown;
  283. if (NTV2_IS_FBF_RGB(props.pixelFormat)) {
  284. if (NTV2_IS_HD_VIDEO_FORMAT(props.videoFormat)) {
  285. hwf = HDMIWireFormat::SD_HD_RGB;
  286. } else if (NTV2_IS_4K_VIDEO_FORMAT(props.videoFormat)) {
  287. hwf = HDMIWireFormat::UHD_4K_RGB;
  288. }
  289. } else {
  290. if (NTV2_IS_HD_VIDEO_FORMAT(props.videoFormat)) {
  291. hwf = HDMIWireFormat::SD_HD_YCBCR;
  292. } else if (NTV2_IS_4K_VIDEO_FORMAT(props.videoFormat)) {
  293. hwf = HDMIWireFormat::UHD_4K_YCBCR;
  294. }
  295. }
  296. } else {
  297. blog(LOG_WARNING, "Unsupported connection kind. SDI and HDMI only!");
  298. return false;
  299. }
  300. if (!rc.FindFirstPreset(kind, props.deviceID, NTV2_MODE_DISPLAY, props.videoFormat, props.pixelFormat,
  301. vpidStandard, hwf, rp)) {
  302. blog(LOG_WARNING, "No HDMI output routing preset found!");
  303. return false;
  304. }
  305. LogRoutingPreset(rp);
  306. std::string route_string = rp.route_string;
  307. // Replace framestore channel placeholders
  308. auto init_channel = NTV2OutputDestinationToChannel(init_dest);
  309. ULWord start_framestore_index = GetIndexForNTV2Channel(props.Framestore());
  310. if (rp.verbatim) {
  311. // Presets marked "verbatim" must only be routed on the specified channels
  312. start_framestore_index = 0;
  313. init_channel = NTV2_CHANNEL1;
  314. }
  315. // Channel-substitution for widgets associated with framestore channel(s)
  316. const std::vector<std::string> fs_associated = {"fb", "tsi", "dlo"};
  317. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  318. for (const auto &name : fs_associated) {
  319. std::string placeholder = std::string(name + "[{ch" + aja::to_string(c + 1) + "}]");
  320. aja::replace(route_string, placeholder,
  321. name + "[" + aja::to_string(start_framestore_index) + "]");
  322. }
  323. start_framestore_index++;
  324. }
  325. // Replace other channel placeholders
  326. ULWord start_channel_index = GetIndexForNTV2Channel(init_channel);
  327. for (ULWord c = 0; c < NTV2_MAX_NUM_CHANNELS; c++) {
  328. std::string channel_placeholder = std::string("{ch" + aja::to_string(c + 1) + "}");
  329. aja::replace(route_string, channel_placeholder, aja::to_string(start_channel_index++));
  330. }
  331. if (!ParseRouteString(route_string, cnx))
  332. return false;
  333. card->ApplySignalRoute(cnx, false);
  334. // Apply SDI widget settings
  335. if (props.ioSelect != IOSelection::HDMIMonitorOut || props.deviceID == DEVICE_ID_TTAP_PRO) {
  336. start_channel_index = GetIndexForNTV2Channel(init_channel);
  337. for (uint32_t i = (uint32_t)start_channel_index; i < (start_channel_index + rp.num_channels); i++) {
  338. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  339. if (::NTV2DeviceHasBiDirectionalSDI(deviceID)) {
  340. card->SetSDITransmitEnable(channel, mode == NTV2_MODE_DISPLAY);
  341. }
  342. card->SetSDIOut3GEnable(channel, rp.flags & kEnable3GOut);
  343. card->SetSDIOut3GbEnable(channel, rp.flags & kEnable3GbOut);
  344. card->SetSDIOut6GEnable(channel, rp.flags & kEnable6GOut);
  345. card->SetSDIOut12GEnable(channel, rp.flags & kEnable12GOut);
  346. card->SetSDIInLevelBtoLevelAConversion((UWord)i, rp.flags & kConvert3GIn);
  347. card->SetSDIOutLevelAtoLevelBConversion((UWord)i, rp.flags & kConvert3GOut);
  348. card->SetSDIOutRGBLevelAConversion((UWord)i, rp.flags & kConvert3GaRGBOut);
  349. }
  350. }
  351. // Apply Framestore settings
  352. start_framestore_index = GetIndexForNTV2Channel(props.Framestore());
  353. if (rp.verbatim) {
  354. start_framestore_index = 0;
  355. }
  356. for (uint32_t i = (uint32_t)start_framestore_index; i < (start_framestore_index + rp.num_framestores); i++) {
  357. NTV2Channel channel = GetNTV2ChannelForIndex(i);
  358. card->EnableChannel(channel);
  359. card->SetMode(channel, mode);
  360. card->SetVANCMode(NTV2_VANCMODE_OFF, channel);
  361. card->SetVideoFormat(props.videoFormat, false, false, channel);
  362. card->SetFrameBufferFormat(channel, props.pixelFormat);
  363. card->SetTsiFrameEnable(rp.flags & kEnable4KTSI, channel);
  364. card->Set4kSquaresEnable(rp.flags & kEnable4KSquares, channel);
  365. card->SetQuadQuadSquaresEnable(rp.flags & kEnable8KSquares, channel);
  366. }
  367. return true;
  368. }
  369. void Routing::ConfigureOutputAudio(const OutputProps &props, CNTV2Card *card)
  370. {
  371. if (!card)
  372. return;
  373. auto deviceID = card->GetDeviceID();
  374. auto audioSys = props.AudioSystem();
  375. auto channel = props.Channel();
  376. card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
  377. card->SetAudioRate(props.AudioRate(), audioSys);
  378. card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
  379. card->SetAudioOutputDelay(audioSys, 0);
  380. card->SetSDIOutputAudioSystem(channel, audioSys);
  381. card->SetSDIOutputDS2AudioSystem(channel, audioSys);
  382. /* NOTE(paulh):
  383. * The SDK has a specifies an SDI audio system by Channel rather than by SDI output
  384. * and certain devices require setting the SDI audio system to NTV2_CHANNEL1.
  385. * i.e.
  386. * SDI 1 = NTV2_CHANNEL1
  387. * SDI 2 = NTV2_CHANNEL2
  388. * ...
  389. * SDI 5/Monitor = NTV2_CHANNEL5
  390. * etc...
  391. *
  392. * This fixes AJA internal bugs: 10730, 10986, 16274
  393. */
  394. if (deviceID == DEVICE_ID_IOXT || deviceID == DEVICE_ID_IO4KUFC || deviceID == DEVICE_ID_IO4KPLUS ||
  395. deviceID == DEVICE_ID_KONA1 || deviceID == DEVICE_ID_KONA3G || deviceID == DEVICE_ID_KONA4UFC ||
  396. deviceID == DEVICE_ID_KONA5 || deviceID == DEVICE_ID_KONA5_2X4K) {
  397. // Make sure SDI out 1 (aka Channel 1) is set to the correct sub-system
  398. card->SetSDIOutputAudioSystem(NTV2_CHANNEL1, audioSys);
  399. card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL1, audioSys);
  400. }
  401. // make sure that audio is setup for the SDI monitor output on devices that support it
  402. if (NTV2DeviceCanDoWidget(deviceID, NTV2_WgtSDIMonOut1)) {
  403. card->SetSDIOutputAudioSystem(NTV2_CHANNEL5, audioSys);
  404. card->SetSDIOutputDS2AudioSystem(NTV2_CHANNEL5, audioSys);
  405. }
  406. card->SetHDMIOutAudioRate(props.AudioRate());
  407. card->SetHDMIOutAudioFormat(NTV2_AUDIO_FORMAT_LPCM);
  408. card->SetAudioOutputMonitorSource(NTV2_AudioChannel1_2, channel);
  409. card->SetAESOutputSource(NTV2_AudioChannel1_4, audioSys, NTV2_AudioChannel1_4);
  410. card->SetAESOutputSource(NTV2_AudioChannel5_8, audioSys, NTV2_AudioChannel5_8);
  411. card->SetAESOutputSource(NTV2_AudioChannel9_12, audioSys, NTV2_AudioChannel9_12);
  412. card->SetAESOutputSource(NTV2_AudioChannel13_16, audioSys, NTV2_AudioChannel13_16);
  413. // make sure that audio is setup for HDMI output on devices that support it
  414. if (NTV2DeviceGetNumHDMIVideoOutputs(deviceID) > 0) {
  415. if (NTV2DeviceCanDoAudioMixer(deviceID)) {
  416. card->SetAudioMixerInputAudioSystem(NTV2_AudioMixerInputMain, audioSys);
  417. card->SetAudioMixerInputChannelSelect(NTV2_AudioMixerInputMain, NTV2_AudioChannel1_2);
  418. card->SetAudioMixerInputChannelsMute(NTV2_AudioMixerInputAux1, NTV2AudioChannelsMuteAll);
  419. card->SetAudioMixerInputChannelsMute(NTV2_AudioMixerInputAux2, NTV2AudioChannelsMuteAll);
  420. }
  421. card->SetHDMIOutAudioChannels(NTV2_HDMIAudio8Channels);
  422. card->SetHDMIOutAudioSource2Channel(NTV2_AudioChannel1_2, audioSys);
  423. card->SetHDMIOutAudioSource8Channel(NTV2_AudioChannel1_8, audioSys);
  424. }
  425. card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF, audioSys);
  426. card->StopAudioOutput(audioSys);
  427. }
  428. void Routing::LogRoutingPreset(const RoutingPreset &rp)
  429. {
  430. auto hexStr = [&](uint8_t val) -> std::string {
  431. std::stringstream ss;
  432. ss << std::setfill('0') << std::setw(sizeof(uint8_t) * 2) << std::hex << (val | 0);
  433. return ss.str();
  434. };
  435. std::stringstream ss;
  436. ss << "\nPreset: " << rp.name;
  437. if (rp.kind == ConnectionKind::SDI) {
  438. ss << "\nVPID Standard: 0x" << hexStr(static_cast<uint8_t>(rp.vpid_standard));
  439. }
  440. ss << "\nMode: " << NTV2ModeToString(rp.mode) << "\nChannels: " << rp.num_channels
  441. << "\nFramestores: " << rp.num_framestores;
  442. blog(LOG_INFO, "[ AJA Crosspoint Routing Preset ]%s", ss.str().c_str());
  443. if (rp.device_ids.size() > 0) {
  444. ss.clear();
  445. for (auto id : rp.device_ids) {
  446. ss << " - " << NTV2DeviceIDToString(id) << "\n";
  447. }
  448. blog(LOG_INFO, "\nCompatible Device IDs: \n%s", ss.str().c_str());
  449. }
  450. }
  451. } // namespace aja