UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
aja.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2018-2024 CESNET
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37/*
38 * Internal structure heavily inspired by and much code taken from NTV2Player Demo.
39 * The use of ping-pong buffer technique is based on NTV2LLBurn.
40 */
41
42#include "config_msvc.h"
43
44#include "debug.h"
45#undef UNUSED // collides with same-named macro defined by libajantv2
46
47#ifdef __GNUC__
48#pragma GCC diagnostic push
49#pragma GCC diagnostic ignored "-Wcast-qual"
50#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
51#pragma GCC diagnostic ignored "-Woverloaded-virtual"
52#endif // defined __GNUC__
53#include <ntv2devicescanner.h>
54#include <ajantv2/includes/ntv2utils.h>
55#include <ajabase/system/debug.h>
56#ifdef __GNUC__
57#pragma GCC diagnostic pop
58#endif // defined __GNUC__
59#include <ajatypes.h>
60#include <ntv2debug.h>
61
62#include <algorithm> // for copy, max, min
63#include <cassert>
64#include <chrono>
65#include <cmath> // for fab
66#include <condition_variable>
67#include <cstdint>
68#include <cstdio>
69#include <cstdlib>
70#include <cstring>
71#include <functional>
72#include <iostream>
73#include <memory>
74#include <mutex>
75#include <queue>
76#include <sstream>
77#include <stdexcept>
78#include <string>
79#include <thread>
80#include <unordered_map>
81#include <utility> // for pair
82#include <vector>
83
84#include "audio/types.h"
85#include "host.h"
86#include "lib_common.h"
87#include "utils/color_out.h" // for col, SBOLD, SRED
88#include "video_display.h"
89#include "video.h"
90
91#include "aja_common.hpp" // should be included last (overrides log_msg etc.)
92
93#define DEFAULT_MAX_FRAME_QUEUE_LEN 1
94#define MOD_NAME "[AJA display] "
102#define BPS 4
103#define SAMPLE_RATE 48000
104
105#ifdef _MSC_VER
106#define LINK_SPEC extern "C" __declspec(dllexport)
107#else
108#define LINK_SPEC static
109#endif
110
111using std::cerr;
112using std::chrono::duration;
113using std::chrono::steady_clock;
114using std::condition_variable;
115using std::cout;
116using std::endl;
117using std::hash;
118using std::lock_guard;
119using std::min;
120using std::mutex;
121using std::ostringstream;
122using std::queue;
123using std::runtime_error;
124using std::string;
125using std::thread;
126using std::unique_lock;
127using std::unique_ptr;
128using std::unordered_map;
129using std::vector;
130
131#define CHECK_EX(cmd, msg, action_failed) do { bool ret = cmd; if (!ret) {\
132 LOG(LOG_LEVEL_WARNING) << MOD_NAME << (msg) << "\n";\
133 action_failed;\
134}\
135} while(0)
136#define NOOP ((void)0)
137#define CHECK(cmd) CHECK_EX(cmd, #cmd " failed", NOOP)
138
139namespace ultragrid {
140namespace aja {
141
142struct display {
143 const struct configuration {
144 string deviceId{"0"};
145 NTV2OutputDestination outputDestination = NTV2_OUTPUTDESTINATION_SDI1;
146 NTV2Channel outputChannel = NTV2_CHANNEL_INVALID;
147 bool withAudio = false;
148 bool novsync = false;
150 bool clearRouting = false;
151 int doMultiChannel = -1; /* -1/0/1 */
153 bool setupAll = true;
154 bool setupRoute = true;
155 bool smpteRange = true;
157
158 queue<struct video_frame *> frames;
161 condition_variable frame_ready;
162 thread worker;
163 void join();
164 void process_frames();
165
166 bool mOutIsRGB = false;
167 static const ULWord app = AJA_FOURCC ('U','L','G','R');
168
169 CNTV2Card mDevice;
170 NTV2DeviceID mDeviceID;
171 struct video_desc desc{};
173 NTV2EveryFrameTaskMode mSavedTaskMode = NTV2_TASK_MODE_INVALID;
174 NTV2Channel mOutputChannel;
175 NTV2VideoFormat mVideoFormat = NTV2_FORMAT_UNKNOWN;
176 NTV2FrameBufferFormat mPixelFormat = NTV2_FBF_INVALID;
177 bool mDoLevelConversion = false;
178 bool mEnableVanc = false;
179
180 unique_ptr<char[]> mAudioBuffer {new char[NTV2_AUDIOSIZE_MAX]};
181 size_t mAudioLen = 0;
182 NTV2AudioSystem mAudioSystem = NTV2_AUDIOSYSTEM_1;
184 bool mAudioIsReset = false;
185 uint32_t mAudioOutWrapAddress = 0u;
186 uint32_t mAudioOutLastAddress = 0u;
187
189 uint32_t mFramesProcessed = 0u;
190 uint32_t mFramesDropped = 0u;
191 uint32_t mFramesDiscarded = 0u;
192
193 steady_clock::time_point mT0 = steady_clock::now();
194 int mFrames = 0;
195
196public:
198 ~display();
199 void Init();
200 AJAStatus SetUpVideo();
201 AJAStatus SetUpAudio();
202 void RouteOutputSignal();
203 bool Putf(struct video_frame *frame, long long flags);
204
205 static NTV2FrameRate getFrameRate(double fps);
206 void print_stats();
207 static void show_help();
208};
209
211 : mConf(conf), max_frame_queue_len(conf.bufLen), mOutputChannel(conf.outputChannel)
212{
213 if (!CNTV2DeviceScanner::GetFirstDeviceFromArgument(conf.deviceId, mDevice)) {
214 throw runtime_error(string("Device '") + conf.deviceId + "' not found!");
215 }
216
217 if (!mDevice.IsDeviceReady(false)) {
218 throw runtime_error(string("Device '") + conf.deviceId + "' not ready!");
219 }
220
221 if (conf.clearRouting) {
222 CHECK(mDevice.ClearRouting());
223 }
224
225 mDeviceID = mDevice.GetDeviceID(); // Keep this ID handy -- it's used frequently
226
227 bool canDoMultiChannel = NTV2DeviceCanDoMultiFormat(mDeviceID);
228 if (!canDoMultiChannel) {
229 if (conf.doMultiChannel != 0) {
230 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Device " << conf.deviceId << " cannot simultaneously handle different video formats.\n";
231 }
232 mDoMultiChannel = false;
233 } else {
235 }
236 if (!mDoMultiChannel) {
237 if (!mDevice.AcquireStreamForApplication(app, static_cast <uint32_t> (getpid()))) {
238 throw runtime_error("Device busy!\n"); // Device is in use by another app -- fail
239 }
240 CHECK(mDevice.GetEveryFrameServices (mSavedTaskMode)); // Save the current service level
241 }
242
243 CHECK(mDevice.SetEveryFrameServices(NTV2_OEM_TASKS));
244
245 if (::NTV2DeviceCanDoMultiFormat(mDeviceID) && mDoMultiChannel) {
246 CHECK(mDevice.SetMultiFormatMode(true));
247 } else if (::NTV2DeviceCanDoMultiFormat(mDeviceID)) {
248 CHECK(mDevice.SetMultiFormatMode (false));
249 }
250
251 if (mOutputChannel == NTV2_CHANNEL_INVALID) {
252 if (!NTV2_OUTPUT_DEST_IS_SDI(mConf.outputDestination)) {
253 LOG(LOG_LEVEL_NOTICE) << MOD_NAME "Non-SDI destination detected - we will use "
254 "probably channel 1. Consider passing \"channel\" option (see help).\n";
255 }
256
257 mOutputChannel = ::NTV2OutputDestinationToChannel(mConf.outputDestination);
258
259 // Beware -- some devices (e.g. Corvid1) can only output from FrameStore 2...
260 if ((mOutputChannel == NTV2_CHANNEL1) && (!::NTV2DeviceCanDoFrameStore1Display (mDeviceID))) {
261 mOutputChannel = NTV2_CHANNEL2;
262 }
263 }
264 if (UWord (mOutputChannel) >= ::NTV2DeviceGetNumFrameStores (mDeviceID)) {
265 ostringstream oss;
266 oss << "## ERROR: Cannot use channel '" << mOutputChannel+1 << "' -- device only supports channel 1"
267 << (::NTV2DeviceGetNumFrameStores (mDeviceID) > 1 ? string (" thru ") + string (1, uint8_t (::NTV2DeviceGetNumFrameStores (mDeviceID)+'0')) : "");
268 throw runtime_error(oss.str());
269 }
270
272}
273
275 CHECK(mDevice.UnsubscribeOutputVerticalEvent (mOutputChannel));
276
277 if (!mDoMultiChannel) {
278 CHECK_EX(mDevice.SetEveryFrameServices (mSavedTaskMode), "Restore Service Mode", NOOP); // Restore the previously saved service level
279 CHECK(mDevice.ReleaseStreamForApplication (app, static_cast <uint32_t>(getpid()))); // Release the device
280 }
281
282 // Don't leave the audio system active after we exit
283 CHECK_EX(mDevice.StopAudioOutput(mAudioSystem), "Restore Audio", NOOP);
284}
285
287{
288 AJAStatus status;
289 status = SetUpVideo();
290 if (AJA_FAILURE(status)) {
291 ostringstream oss;
292 oss << "Unable to initialize video: " << ::AJAStatusToString(status);
293 throw runtime_error(oss.str());
294 }
296
297 // Before the main loop starts, ping-pong the buffers so the hardware will use
298 // different buffers than the ones it was using while idling...
299 for (unsigned int i = 0; i < desc.tile_count; ++i) {
300 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
301 mCurrentOutFrame ^= 1;
302 CHECK(mDevice.SetOutputFrame(chan, mCurrentOutFrame + 2 * i));
303
304 mCurrentOutFrame ^= 1;
305 CHECK(mDevice.SetOutputFrame(chan, mCurrentOutFrame + 2 * i));
306 }
307}
308
310{
311 if (!mConf.setupAll) {
312 return AJA_STATUS_SUCCESS;
313 }
314
315 if (!::NTV2DeviceCanDoVideoFormat (mDeviceID, mVideoFormat)) {
316 cerr << "## ERROR: This device cannot handle '" << ::NTV2VideoFormatToString (mVideoFormat) << "'" << endl;
317 return AJA_STATUS_UNSUPPORTED;
318 }
319
320 // Configure the device to handle the requested video format...
321 for (unsigned int i = 0; i < desc.tile_count; ++i) {
322 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
323 if (!mDevice.SetVideoFormat (mVideoFormat, false, false, chan)) {
324 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Cannot set format "
325 << NTV2VideoFormatToString(mVideoFormat)
326 << " for output " << chan << "\n";
327 return AJA_STATUS_FAIL;
328 }
329 }
330
331 mOutIsRGB = !mConf.forceOutputColorSpace ? (NTV2_OUTPUT_DEST_IS_HDMI(mConf.outputDestination) ? true : ::IsRGBFormat(mPixelFormat)) : mConf.forceOutputColorSpace == RGB;
332 // If device has no RGB conversion capability for the desired channel, use FBF instead
333 for (unsigned int i = 0; i < desc.tile_count; ++i) {
334 if (UWord (mOutputChannel) + UWord(i) > ::NTV2DeviceGetNumCSCs (mDeviceID)) {
335 if (mConf.forceOutputColorSpace && mOutIsRGB != ::IsRGBFormat(mPixelFormat)) {
336 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Not enough CSCs found, found " << ::NTV2DeviceGetNumCSCs (mDeviceID) << " CSCs, "
337 "overriding output color spec preference.\n";
338 }
339 mOutIsRGB = ::IsRGBFormat(mPixelFormat);
340 }
341 }
342
343 if (!::NTV2DeviceCanDo3GLevelConversion (mDeviceID) && mDoLevelConversion && ::IsVideoFormatA (mVideoFormat)) {
344 mDoLevelConversion = false;
345 }
346 if (mDoLevelConversion) {
347 CHECK(mDevice.SetSDIOutLevelAtoLevelBConversion (mOutputChannel, mDoLevelConversion));
348 }
349
350 // Set the frame buffer pixel format for all the channels on the device.
351 // If the device doesn't support it, fall back to 8-bit YCbCr...
352 if (!::NTV2DeviceCanDoFrameBufferFormat (mDeviceID, mPixelFormat))
353 {
354 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Device cannot handle '" << ::NTV2FrameBufferFormatString (mPixelFormat) << "'\n";
355 return AJA_STATUS_FAIL;
356 }
357
358 for (unsigned int i = 0; i < desc.tile_count; ++i) {
359 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
360 if (!mDevice.SetFrameBufferFormat (chan, mPixelFormat)) {
361 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Cannot set format "
362 << NTV2FrameBufferFormatString(mPixelFormat)
363 << " for output " << mOutputChannel + i << "\n";
364 return AJA_STATUS_FAIL;
365 }
366 }
367 if(mDeviceID == DEVICE_ID_KONAIP_1RX_1TX_2110 ||
368 mDeviceID == DEVICE_ID_KONAIP_2110) {
369 CHECK_EX(mDevice.SetReference(NTV2_REFERENCE_SFP1_PTP), "Set Reference SFP1_PTP", NOOP);
370 } else {
371 CHECK_EX(mDevice.SetReference (NTV2_REFERENCE_FREERUN), "Set Reference FREERUN", NOOP);
372 }
373 for (unsigned int i = 0; i < desc.tile_count; ++i) {
374 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
375 if (!mDevice.EnableChannel(chan)) {
376 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Cannot enable channel "
377 << chan << "\n";
378 return AJA_STATUS_FAIL;
379 }
380 }
381
382 if (mEnableVanc && !::IsRGBFormat (mPixelFormat) && NTV2_IS_HD_VIDEO_FORMAT (mVideoFormat))
383 {
384 // Try enabling VANC...
385 CHECK_EX(mDevice.SetEnableVANCData(true), "SetEnableVANCData true", NOOP); // Enable VANC for non-SD formats, to pass thru captions, etc.
386 if (::Is8BitFrameBufferFormat (mPixelFormat)) {
387 // 8-bit FBFs require VANC bit shift...
388 CHECK_EX(mDevice.SetVANCShiftMode(mOutputChannel, NTV2_VANCDATA_8BITSHIFT_ENABLE), "SetVANCShiftMode", NOOP);
389 }
390 } else {
391 CHECK_EX(mDevice.SetEnableVANCData(false), "SetEnableVANCData false", NOOP);; // No VANC with RGB pixel formats (for now)
392 }
393
394 if (NTV2_OUTPUT_DEST_IS_HDMI(mConf.outputDestination)) {
395 // convert all to RGB SMPTE range
396 if (IsRGBFormat (mPixelFormat) && mOutIsRGB && mConf.smpteRange) { // set LUT
397 NTV2LutType lutType = NTV2_LUTRGBRangeFull_SMPTE;
398 const int bank = 1; // Bank 0 (RGB->YUV, SMPTE->Full), Bank 1 (YUV->RGB, Full->SMPTE)
399 CHECK_EX(mDevice.SetColorCorrectionOutputBank(mOutputChannel, bank), "LUT Bank", NOOP);
400 CHECK_EX(mDevice.WriteRegister(kVRegLUTType, (ULWord) lutType), "LUT Bank Reg", NOOP);
401 NTV2DoubleArray table(1024);
402 CHECK_EX(mDevice.GenerateGammaTable(lutType, bank, table), "Generate Gamma", NOOP);
403 CHECK_EX(mDevice.DownloadLUTToHW(table, table, table, mOutputChannel, bank), "Download LUT", NOOP);
404 }
405 if (IsRGBFormat (mPixelFormat) != mOutIsRGB) { // set CSC
406 CHECK(mDevice.SetColorSpaceMatrixSelect(NTV2_Rec709Matrix, mOutputChannel));
407 CHECK(mDevice.SetColorSpaceRGBBlackRange(NTV2_CSC_RGB_RANGE_SMPTE, mOutputChannel));
408 CHECK(mDevice.SetColorSpaceMethod(NTV2_CSC_Method_Original, mOutputChannel));
409 CHECK(mDevice.SetColorSpaceMakeAlphaFromKey(false, mOutputChannel));
410 CHECK_EX(mDevice.SetColorSpaceRGBBlackRange(NTV2_CSC_RGB_RANGE_SMPTE, mOutputChannel), "CSC RGB Range", NOOP);
411 }
412
413 CHECK_EX(mDevice.SetHDMIOutProtocol(NTV2_HDMIProtocolHDMI), "HDMI Output Protocol", NOOP);
414 CHECK_EX(mDevice.SetHDMIOutSampleStructure(mOutIsRGB ? NTV2_HDMI_RGB : NTV2_HDMI_YC422),
415 "HDMI Sample Struct", NOOP);
416 CHECK_EX(mDevice.SetHDMIOutRange(NTV2_HDMIRangeSMPTE), "HDMI Range", NOOP);
417 CHECK_EX(mDevice.SetHDMIOutColorSpace(mOutIsRGB ? NTV2_HDMIColorSpaceRGB : NTV2_HDMIColorSpaceYCbCr),
418 "HDMI Color Space", NOOP);
419 //CHECK_EX(mDevice.SetHDMIOutForceConfig(true), "HDMI Force Config", NOOP);
420 //CHECK(mDevice.SetHDMIOutPrefer420(false));
421 }
422
423 for (unsigned int i = 0; i < desc.tile_count; ++i) {
424 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
425 // Enable and subscribe to the output interrupts (though it's enabled by default)...
426 CHECK(mDevice.EnableOutputInterrupt(chan));
427 CHECK(mDevice.SubscribeOutputVerticalEvent(chan));
428 // Set the Frame Store modes
429 CHECK(mDevice.SetMode(chan, NTV2_MODE_DISPLAY));
430 }
431
432 // Subscribe the output interrupt -- it's enabled by default...
433
434 return AJA_STATUS_SUCCESS;
435
436} // SetUpVideo
437
439{
440 if (!mConf.setupAll) {
441 return AJA_STATUS_SUCCESS;
442 }
443
444 mAudioSystem = NTV2ChannelToAudioSystem(mOutputChannel);
445 CHECK(mDevice.StartAudioOutput(mAudioSystem));
446
447 // It's best to use all available audio channels...
448 CHECK_EX(mDevice.SetNumberAudioChannels(::NTV2DeviceGetMaxAudioChannels(mDeviceID), mAudioSystem), "Unable to set audio channels!", return AJA_STATUS_FAIL);
449
450 // Assume 48kHz PCM...
451 CHECK(mDevice.SetAudioRate (NTV2_AUDIO_48K, mAudioSystem));
452
453 // 4MB device audio buffers work best...
454 CHECK(mDevice.SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, mAudioSystem));
455
456 // Set the SDI output audio embedders to embed audio samples from the output of mAudioSystem...
457 if (NTV2_OUTPUT_DEST_IS_SDI(mConf.outputDestination)) {
458 CHECK_EX(mDevice.SetSDIOutputAudioSystem(NTV2OutputDestinationToChannel(mConf.outputDestination), mAudioSystem), "Unable to set SDI output audio system!", return AJA_STATUS_FAIL);
459 CHECK_EX(mDevice.SetSDIOutputDS2AudioSystem(NTV2OutputDestinationToChannel(mConf.outputDestination), mAudioSystem), "Unable to set SDI output audio system!", return AJA_STATUS_FAIL);
460 } else {
461 CHECK(mDevice.SetHDMIOutAudioChannels(NTV2_HDMIAudio8Channels));
462 CHECK_EX(mDevice.SetHDMIOutAudioSource8Channel(NTV2_AudioChannel1_8, mAudioSystem), "Unable to set HDMI output audio system!", return AJA_STATUS_FAIL);
463 }
464
465 //
466 // Loopback mode plays whatever audio appears in the input signal when it's
467 // connected directly to an output (i.e., "end-to-end" mode). If loopback is
468 // left enabled, the video will lag the audio as video frames get briefly delayed
469 // in our ring buffer. Audio, therefore, needs to come out of the (buffered) frame
470 // data being played, so loopback must be turned off...
471 //
472 CHECK_EX(mDevice.SetAudioLoopBack (NTV2_AUDIO_LOOPBACK_OFF, mAudioSystem), "Disable Audio Loopback", NOOP);
473
474 CHECK_EX(mDevice.SetAudioOutputEraseMode(mAudioSystem, true), "Set erase mode", return AJA_STATUS_FAIL);
475
476 // Reset both the input and output sides of the audio system so that the buffer
477 // pointers are reset to zero and inhibited from advancing.
478 CHECK(mDevice.StopAudioOutput(mAudioSystem));
479 mAudioIsReset = true;
480
481 CHECK(mDevice.GetAudioWrapAddress(mAudioOutWrapAddress, mAudioSystem));
483
484 return AJA_STATUS_SUCCESS;
485
486} // SetupAudio
487
488const unordered_map<NTV2Channel, NTV2OutputCrosspointID, hash<int>> chanToLutSrc = {
489 {NTV2_CHANNEL1, NTV2_XptLUT1RGB},
490 {NTV2_CHANNEL2, NTV2_XptLUT2RGB},
491 {NTV2_CHANNEL3, NTV2_XptLUT3Out},
492 {NTV2_CHANNEL4, NTV2_XptLUT4Out},
493 {NTV2_CHANNEL5, NTV2_XptLUT5Out},
494 {NTV2_CHANNEL6, NTV2_XptLUT6Out},
495 {NTV2_CHANNEL7, NTV2_XptLUT7Out},
496 {NTV2_CHANNEL8, NTV2_XptLUT8Out}
497};
498
500{
501 if (!mConf.setupRoute) {
502 return;
503 }
504
505 const NTV2Standard outputStandard (::GetNTV2StandardFromVideoFormat (mVideoFormat));
506 const UWord numSDIOutputs (::NTV2DeviceGetNumVideoOutputs (mDeviceID));
507 bool fbIsRGB (::IsRGBFormat (mPixelFormat));
508
509 if (mDoMultiChannel) {
510 // Multiformat --- We may be sharing the device with other processes, so route only one SDI output...
511 for (unsigned int i = 0; i < desc.tile_count; ++i) {
512 // Multiformat --- route the one output to the CSC video output (RGB) or FrameStore output (YUV)...
513 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
514 NTV2OutputCrosspointID cscVidOutXpt (::GetCSCOutputXptFromChannel (chan, false/*isKey*/, !fbIsRGB/*isRGB*/));
515 NTV2OutputCrosspointID fsVidOutXpt (::GetFrameBufferOutputXptFromChannel (chan, fbIsRGB/*isRGB*/, false/*is425*/));
516 if (fbIsRGB != mOutIsRGB) {
517 CHECK_EX(mDevice.Connect(::GetCSCInputXptFromChannel (chan, false/*isKeyInput*/), fsVidOutXpt),
518 "Connnect to CSC", NOOP);
519 }
520
521 if (NTV2_OUTPUT_DEST_IS_SDI(mConf.outputDestination)) {
522 if (::NTV2DeviceHasBiDirectionalSDI (mDeviceID)) {
523 CHECK(mDevice.SetSDITransmitEnable(chan, true));
524 }
525
526 CHECK(mDevice.SetSDIOutputStandard(chan, outputStandard));
527 //mDevice.Connect (::GetSDIOutputInputXpt (chan, false/*isDS2*/), fbIsRGB ? cscVidOutXpt : fsVidOutXpt);
528 if (mOutIsRGB) {
529 CHECK(mDevice.Connect (::GetDLOutInputXptFromChannel(chan), fbIsRGB ? ::GetFrameBufferOutputXptFromChannel(chan, NTV2_IS_FBF_RGB(mPixelFormat)) : cscVidOutXpt)); // DLOut <== FBRGB/CSC
530 CHECK(mDevice.Connect (::GetOutputDestInputXpt(mConf.outputDestination, false), ::GetDLOutOutputXptFromChannel(chan, false))); // SDIOut <== DLOut
531 CHECK(mDevice.Connect (::GetOutputDestInputXpt(mConf.outputDestination, true), ::GetDLOutOutputXptFromChannel(chan, true))); // SDIOutDS <== DLOutDS
532 } else {
533 CHECK_EX(mDevice.Connect(::GetOutputDestInputXpt(mConf.outputDestination), fbIsRGB ? cscVidOutXpt : fsVidOutXpt),
534 "Connect to CSC", NOOP);
535 }
536 } else if (NTV2_OUTPUT_DEST_IS_HDMI(mConf.outputDestination)) {
537 // convert all to RGB SMPTE range
538 if (fbIsRGB && mOutIsRGB) { // connect to LUT to convert full->SMPTE range
539 if (mConf.smpteRange) {
540 auto lutInXpt = (NTV2InputCrosspointID) ((unsigned int) NTV2_XptLUT1Input + (unsigned int) chan);
541 CHECK_EX(mDevice.Connect(lutInXpt, fsVidOutXpt), "Connect to LUT", NOOP);
542 CHECK_EX(mDevice.Connect(::GetOutputDestInputXpt(mConf.outputDestination), chanToLutSrc.at(chan)),
543 "Connect from LUT", NOOP);
544 } else {
545 CHECK_EX(mDevice.Connect(::GetOutputDestInputXpt(mConf.outputDestination), fsVidOutXpt), "Connect to FS to HDMI", NOOP);
546 }
547 } else { // connect output to FB or CSC
548 CHECK_EX(mDevice.Connect(::GetOutputDestInputXpt(mConf.outputDestination), fbIsRGB != mOutIsRGB ? cscVidOutXpt : fsVidOutXpt),
549 "Connect from CSC", NOOP);
550 }
551 } else {
554 "Routing for %s may be incorrect. ",
555 NTV2OutputDestinationToString(
557 .c_str());
558 CHECK_EX(mDevice.Connect(::GetOutputDestInputXpt(mConf.outputDestination), fbIsRGB ? cscVidOutXpt : fsVidOutXpt),
559 "Connect from CSC or frame store", NOOP);
560 }
561 }
562 } else { // Not multiformat: We own the whole device, so connect all possible SDI outputs...
563 // Route all possible SDI outputs to CSC video output (RGB) or FrameStore output (YUV)...
564 const NTV2OutputCrosspointID cscVidOutXpt(::GetCSCOutputXptFromChannel (mOutputChannel, false/*isKey*/, !fbIsRGB/*isRGB*/));
565 const NTV2OutputCrosspointID fsVidOutXpt(::GetFrameBufferOutputXptFromChannel (mOutputChannel, fbIsRGB/*isRGB*/, false/*is425*/));
566 const UWord numFrameStores(::NTV2DeviceGetNumFrameStores(mDeviceID));
567 CHECK(mDevice.ClearRouting()); // Start with clean slate
568
569 CHECK(mDevice.Connect (::GetCSCInputXptFromChannel (mOutputChannel, false/*isKeyInput*/), fsVidOutXpt));
570
571 for (NTV2Channel chan(NTV2_CHANNEL1); ULWord(chan) < numSDIOutputs; chan = NTV2Channel(chan+1))
572 {
573 if (chan != mOutputChannel && chan < numFrameStores)
574 CHECK(mDevice.DisableChannel(chan)); // Disable unused FrameStore
575 if (::NTV2DeviceHasBiDirectionalSDI (mDeviceID))
576 CHECK(mDevice.SetSDITransmitEnable (chan, true)); // Make it an output
577
578 CHECK(mDevice.Connect (::GetSDIOutputInputXpt (chan, false/*isDS2*/), fbIsRGB ? cscVidOutXpt : fsVidOutXpt));
579 CHECK(mDevice.SetSDIOutputStandard (chan, outputStandard));
580 } // for each SDI output spigot
581
582 // And connect analog video output, if the device has one...
583 if (::NTV2DeviceCanDoWidget (mDeviceID, NTV2_WgtAnalogOut1))
584 CHECK(mDevice.Connect (::GetOutputDestInputXpt (NTV2_OUTPUTDESTINATION_ANALOG), fbIsRGB ? cscVidOutXpt : fsVidOutXpt));
585
586 // And connect HDMI video output, if the device has one...
587 if (::NTV2DeviceCanDoWidget (mDeviceID, NTV2_WgtHDMIOut1)
588 || ::NTV2DeviceCanDoWidget (mDeviceID, NTV2_WgtHDMIOut1v2)
589 || ::NTV2DeviceCanDoWidget (mDeviceID, NTV2_WgtHDMIOut1v3)
590 || ::NTV2DeviceCanDoWidget (mDeviceID, NTV2_WgtHDMIOut1v4))
591 CHECK(mDevice.Connect (::GetOutputDestInputXpt (NTV2_OUTPUTDESTINATION_HDMI), fbIsRGB ? fsVidOutXpt : cscVidOutXpt));
592
593 // connect eventual additional tiles
594 for (unsigned int i = 1; i < desc.tile_count; ++i) {
595 NTV2Channel chan = (NTV2Channel) ((int) mOutputChannel + i);
596 const NTV2OutputCrosspointID fsVidOutXpt(::GetFrameBufferOutputXptFromChannel (chan, fbIsRGB/*isRGB*/, false/*is425*/));
597 if (fbIsRGB)
598 CHECK(mDevice.Connect (::GetCSCInputXptFromChannel (chan, false/*isKeyInput*/), fsVidOutXpt)); // CSC
599 CHECK(mDevice.Connect (::GetSDIOutputInputXpt (chan, false/*isDS2*/), fbIsRGB ? cscVidOutXpt : fsVidOutXpt));
600 }
601 }
602}
603
605{
606 unique_lock<mutex> lk(frames_lock);
607 frames.push(nullptr); // poison pill
608 lk.unlock();
609 frame_ready.notify_one();
610 worker.join();
611}
612
614{
615 for (unsigned int i = 0; i < desc.tile_count; ++i) {
616 NTV2Channel chan = (NTV2Channel)((unsigned int) mOutputChannel + i);
617 CHECK(mDevice.SubscribeOutputVerticalEvent(chan));
618 }
619
620 while (true) {
621 unique_lock<mutex> lk(frames_lock);
622 frame_ready.wait(lk, [this]{ return frames.size() > 0; });
623 struct video_frame *frame = frames.front();
624 frames.pop();
625 lk.unlock();
626 if (!frame) { // poison pill
627 break;
628 }
629
630 CHECK(mDevice.WaitForOutputVerticalInterrupt(mOutputChannel));
631 // Flip sense of the buffers again to refer to the buffers that the hardware isn't using (i.e. the off-screen buffers)...
632 mCurrentOutFrame ^= 1;
633 for (unsigned int i = 0; i < frame->tile_count; ++i) {
634 CHECK(mDevice.DMAWriteFrame(mCurrentOutFrame + 2 * i, reinterpret_cast<ULWord*>(frame->tiles[i].data), frame->tiles[i].data_len));
635 }
636
637 for (unsigned int i = 0; i < frame->tile_count; ++i) {
638 // Check for dropped frames by ensuring the hardware has not started to process
639 // the buffers that were just filled....
640 uint32_t readBackOut;
641 CHECK(mDevice.GetOutputFrame((NTV2Channel)((unsigned int) mOutputChannel + i), readBackOut));
642
643 if (readBackOut == mCurrentOutFrame + 2 * i) {
644 LOG(LOG_LEVEL_WARNING) << "## WARNING: Drop detected: current out " << mCurrentOutFrame + 2 * i << ", readback out " << readBackOut << endl;
646 } else {
648 }
649 // Tell the hardware which buffers to start using at the beginning of the next frame...
650 CHECK(mDevice.SetOutputFrame((NTV2Channel)((unsigned int) mOutputChannel + i), mCurrentOutFrame + 2 * i));
651 }
652
653 if (mConf.withAudio) {
654 lock_guard<mutex> lk(mAudioLock);
655 if (mAudioIsReset && mAudioLen > 0) {
656 // Now that the audio system has some samples to play, playback can be taken out of reset...
657 CHECK(mDevice.StartAudioOutput(mAudioSystem));
658 mAudioIsReset = false;
659 }
660
661 if (mAudioLen > 0) {
662 uint32_t val;
663 CHECK(mDevice.ReadAudioLastOut(val, NTV2AudioSystem(mOutputChannel)));
664 int channels = ::NTV2DeviceGetMaxAudioChannels (mDeviceID);
666 if (latency_ms > 135) {
667 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Buffer length: " << latency_ms << " ms, possible wrap-around.\n";
668 mAudioOutLastAddress = ((val + (SAMPLE_RATE / 1000) * 70 /* ms */ * BPS * channels) % mAudioOutWrapAddress) / 128 * 128;
669 } else {
670 LOG(LOG_LEVEL_DEBUG) << MOD_NAME "Audio latency: " << latency_ms << "\n";
671 }
672
673 int len = min<int>(mAudioLen, mAudioOutWrapAddress - mAudioOutLastAddress); // length to the wrap-around
674 CHECK(mDevice.DMAWriteAudio(mAudioSystem, reinterpret_cast<ULWord*>(mAudioBuffer.get()), mAudioOutLastAddress, len));
675 if (mAudioLen - len > 0) {
676 CHECK(mDevice.DMAWriteAudio(mAudioSystem, reinterpret_cast<ULWord*>(mAudioBuffer.get() + len), 0, mAudioLen - len));
678 } else {
680 }
681 mAudioLen = 0;
682 }
683 }
684
685 mFrames += 1;
686 print_stats();
687
688 vf_free(frame);
689 }
690}
691
692bool display::Putf(struct video_frame *frame, long long flags) {
693 if (frame == nullptr) {
694 return true;
695 }
696
697 if (flags == PUTF_DISCARD) {
698 vf_free(frame);
699 return true;
700 }
701
702 unique_lock<mutex> lk(frames_lock);
703 // PUTF_BLOCKING is not currently honored
704 if (frames.size() > max_frame_queue_len) {
705 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Frame dropped!\n";
706 if (++mFramesDiscarded % 20 == 0) {
707 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << mFramesDiscarded << " frames discarded - try to increase buffer count or set \"novsync\" (see \"-d aja:help\" for details).\n";
708 }
709 vf_free(frame);
710 return false;
711 }
712 frames.push(frame);
713 lk.unlock();
714 frame_ready.notify_one();
715
716 return true;
717}
718
720 auto now = steady_clock::now();
721 double seconds = duration<double>(now - mT0).count();
722 if (seconds > 5) {
723 LOG(LOG_LEVEL_INFO) << MOD_NAME << mFrames << " frames in "
724 << seconds << " seconds = " << mFrames / seconds << " FPS\n";
725 mFrames = 0;
726 mT0 = now;
727 }
728}
729
731 col() << "Usage:\n"
732 "\t"
733 << SBOLD(SRED("-d aja")
734 << "[[:buffers=<b>][:channel=<ch>][:clear-routing][:"
735 "connection=<c>][:device=<d>][:[no-]multi-channel][:"
736 "novsync][:no-setup[-route]][:RGB|:YUV][:{smpte|"
737 "full}-range]|:help] [-r embedded]")
738 << "\nwhere\n";
739
740 col() << SBOLD("\tbuffers\n") <<
741 "\t\tuse <b> output buffers (default is " << DEFAULT_MAX_FRAME_QUEUE_LEN << ") - higher values increase stability\n"
742 "\t\tbut may also increase latency (when VBlank is enabled)\n";
743
744 col() << SBOLD("\tclear-routing\n") <<
745 "\t\tremove all existing signal paths for device\n";
746
747 col() << SBOLD("\tconnection\n") <<
748 "\t\tone of: ";
749 NTV2OutputDestination dest = NTV2OutputDestination();
750 while (dest != NTV2_OUTPUTDESTINATION_INVALID) {
751 if (dest > 0) {
752 col() << ", ";
753 }
754 col() << NTV2OutputDestinationToString(dest, true);
755 // should be this, but GetNTV2InputSourceForIndex knows only SDIs
756 //source = ::GetNTV2InputSourceForIndex(::GetIndexForNTV2InputSource(source) + 1);
757 dest = (NTV2OutputDestination) ((int) dest + 1);
758 }
759 col() << "\n";
760
761 col() << SBOLD("\tchannel\n") <<
762 "\t\tchannel number to use (indexed from 1). Doesn't need to be set for SDI, useful for HDMI (capture and display should have different channel numbers if both used, also other than 1 if SDI1 is in use, see \"-t aja:help\" to see number of available channels).\n";
763
764 col() << SBOLD("\tdevice\n") <<
765 "\t\tdevice identifier (number or name)\n";
766
767 col() << SBOLD("\t[no-]multi-channel\n") <<
768 "\t\tdo (not) treat the device as a multi-channel\n";
769
770 col() << SBOLD("\tno-setup[-route]\n") <<
771 "\t\tdo not setup anything/routing (user must set it eg. in Cables app)\n";
772
773 col() << SBOLD("\tnovsync\n") <<
774 "\t\tdisable sync on VBlank (may improve latency at the expense of tearing)\n";
775
776 col() << SBOLD("\tRGB|YUV\n") <<
777 "\t\tforce SDI output to be RGB or YCbCr, otherwise UG keeps the colorspace\n";
778
779 col() << SBOLD("\tsmpte-|full-range\n") <<
780 "\t\tuse either SMPTE (default) or full RGB range\n";
781
782 col() << SBOLD("\t-r embedded\n") <<
783 "\t\treceive also audio and embed it to SDI\n";
784
785 col() << "\n";
786 col() << "Available devices:\n";
787
788 CNTV2DeviceScanner deviceScanner;
789 for (unsigned int i = 0; i < deviceScanner.GetNumDevices (); i++) {
790 CNTV2Card device;
791 if (!CNTV2DeviceScanner::GetDeviceAtIndex(i, device)) {
792 MSG(WARNING, "Cannot get device at index #%u!\n", i);
793 continue;
794 }
795
796 col() << "\t" << SBOLD(i) << ") "
797 << SBOLD(device.GetDisplayName());
799 col() << "\n";
800 }
801 if (deviceScanner.GetNumDevices() == 0) {
802 col() << SRED("\tno devices found\n");
803 }
804 col() << "\n";
805}
806
807NTV2FrameRate aja::display::getFrameRate(double fps)
808{
809 NTV2FrameRate result = NTV2_FRAMERATE_UNKNOWN;
810 double best = 1000.0;
811
812 for(int i = 0; i < NTV2_NUM_FRAMERATES; i++)
813 {
814 auto candidate = static_cast<NTV2FrameRate>(i);
815 double distance = fabs(GetFramesPerSecond(candidate) - fps);
816 if (distance < best)
817 {
818 result = candidate;
819 best = distance;
820 }
821 }
822
823 return result;
824}
825
826} // end of namespace aja
827} // end of namespace ultragrid
828
829namespace ugaja = ultragrid::aja;
830
831LINK_SPEC void display_aja_probe(struct device_info **available_cards, int *count, void (**deleter)(void *))
832{
833 *deleter = free;
834 CNTV2DeviceScanner deviceScanner;
835 *count = deviceScanner.GetNumDevices();
836 *available_cards = static_cast<struct device_info *>(calloc(deviceScanner.GetNumDevices(), sizeof(struct device_info)));
837 for (unsigned int i = 0; i < deviceScanner.GetNumDevices (); i++) {
838 snprintf((*available_cards)[i].dev, sizeof (*available_cards)[i].dev, ":device=%d", i);
839 snprintf((*available_cards)[i].extra, sizeof (*available_cards)[i].extra, "\"embeddedAudioAvailable\":\"t\"");
840 CNTV2Card device;
841 if (!CNTV2DeviceScanner::GetDeviceAtIndex(i, device)) {
842 MSG(WARNING, "Cannot get device at index #%u!\n", i);
843 continue;
844 }
845 strncpy((*available_cards)[i].name,
846 device.GetDisplayName().c_str(),
847 sizeof(*available_cards)[0].name - 1);
848 (*available_cards)[i].repeatable = false;
849 }
850}
851
855static bool display_aja_is_quad_format(NTV2VideoFormat fmt) {
856 if ((fmt >= NTV2_FORMAT_FIRST_4K_DEF_FORMAT && fmt < NTV2_FORMAT_END_4K_DEF_FORMATS)
857 || (fmt >= NTV2_FORMAT_FIRST_4K_DEF_FORMAT2 && fmt < NTV2_FORMAT_END_4K_DEF_FORMATS2)
858 || (fmt >= NTV2_FORMAT_FIRST_UHD2_DEF_FORMAT && fmt < NTV2_FORMAT_END_UHD2_DEF_FORMATS)
859 || (fmt >= NTV2_FORMAT_FIRST_UHD2_FULL_DEF_FORMAT && fmt < NTV2_FORMAT_END_UHD2_FULL_DEF_FORMATS)) {
860 return true;
861 }
862 return false;
863}
864
865/*
866 * modified GetFirstMatchingVideoFormat
867 */
868static NTV2VideoFormat display_aja_get_first_matching_video_format(const NTV2FrameRate inFrameRate, const UWord inHeightLines, const UWord inWidthPixels, const bool inIsInterlaced, const bool inIsLevelB, bool skipQuadFormats)
869{
870 for (NTV2VideoFormat fmt(NTV2_FORMAT_FIRST_HIGH_DEF_FORMAT); fmt < NTV2_MAX_NUM_VIDEO_FORMATS; fmt = NTV2VideoFormat(fmt + 1)) {
871 if (display_aja_is_quad_format(fmt) && skipQuadFormats) {
872 continue; // skip NTV2_FORMAT_4x formats
873 }
874 if (inFrameRate == ::GetNTV2FrameRateFromVideoFormat(fmt))
875 if (inHeightLines == ::GetDisplayHeight(fmt))
876 if (inWidthPixels == ::GetDisplayWidth(fmt))
877 if (inIsInterlaced == !::IsProgressiveTransport(fmt) && !IsPSF(fmt))
878 if (NTV2_VIDEO_FORMAT_IS_B(fmt) == inIsLevelB)
879 return fmt;
880 }
881 return NTV2_FORMAT_UNKNOWN;
882}
883
884
886{
887 auto *s = static_cast<struct ugaja::display *>(state);
888 if (s->desc.color_spec != VIDEO_CODEC_NONE) {
889 s->join();
890 s->desc = {};
891 }
892
893 if (desc.tile_count != 1 && desc.tile_count != 4) {
894 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Unsupported tile count: " << desc.tile_count << "\n";
895 return false;
896 }
897
898 bool interlaced = desc.interlacing == INTERLACED_MERGED;
899 // first try to skip quad split formats (aka quad link)
900 s->mVideoFormat = display_aja_get_first_matching_video_format(ugaja::display::getFrameRate(desc.fps),
901 desc.height, desc.width, interlaced, false, false);
902 // if not found or supported, include quad formats
903 if (s->mVideoFormat == NTV2_FORMAT_UNKNOWN || !::NTV2DeviceCanDoVideoFormat (s->mDeviceID, s->mVideoFormat)) {
904 s->mVideoFormat = display_aja_get_first_matching_video_format(ugaja::display::getFrameRate(desc.fps),
905 desc.height, desc.width, interlaced, false, true);
906 }
907 if (s->mVideoFormat == NTV2_FORMAT_UNKNOWN) {
908 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Unsupported resolution"
909#ifndef _MSC_VER
910 ": " << desc
911#endif
912 << "\n";
913 return false;
914 }
915 s->mPixelFormat = NTV2_FBF_INVALID;
916 for (auto & c : ugaja::codec_map) {
917 if (c.second == desc.color_spec) {
918 s->mPixelFormat = c.first;
919 }
920 }
921 assert(s->mPixelFormat != NTV2_FBF_INVALID);
922
923 // deinit?
924 s->desc = desc;
925 try {
926 s->Init();
927 } catch (runtime_error &e) {
928 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Reconfiguration failed: " << e.what() << "\n";
929 s->desc = {};
930 return false;
931 }
932
933 s->worker = thread(&ugaja::display::process_frames, s);
934
935 return true;
936}
937
938LINK_SPEC void *display_aja_init(struct module * /* parent */, const char *fmt, unsigned int flags)
939{
940 struct ugaja::display::configuration conf;
941 auto tmp = static_cast<char *>(alloca(strlen(fmt) + 1));
942 strcpy(tmp, fmt);
943
944 char *item;
945 while ((item = strtok(tmp, ":")) != nullptr) {
946 if (strcmp("help", item) == 0) {
947 ugaja::display::show_help();
948 return INIT_NOERR;
949 } else if (strstr(item, "buffers=") == item) {
950 conf.bufLen = atoi(item + strlen("buffers="));
951 if (conf.bufLen <= 0) {
952 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Buffers must be positive!\n";
953 return nullptr;
954 }
955 } else if (strcmp("clear-routing", item) == 0) {
956 conf.clearRouting = true;
957 } else if (strstr(item, "connection=") != nullptr) {
958 string connection = item + strlen("connection=");
959 NTV2OutputDestination dest = NTV2OutputDestination();
960 while (dest != NTV2_OUTPUTDESTINATION_INVALID) {
961 if (NTV2OutputDestinationToString(dest, true) == connection) {
962 conf.outputDestination = dest;
963 break;
964 }
965 // should be this, but GetNTV2InputSourceForIndex knows only SDIs
966 //source = ::GetNTV2InputSourceForIndex(::GetIndexForNTV2InputSource(source) + 1);
967 dest = (NTV2OutputDestination) ((int) dest + 1);
968 }
969 if (dest == NTV2_OUTPUTDESTINATION_INVALID) {
970 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Unknown destination: " << connection << "!\n";
971 return nullptr;
972 }
973 } else if (strstr(item, "channel=") != nullptr) {
974 conf.outputChannel = (NTV2Channel) (atoi(item + strlen("channel=")) - 1);
975 } else if (strstr(item, "device=") != nullptr) {
976 conf.deviceId = item + strlen("device=");
977 } else if (strstr(item, "multi-channel") != nullptr) {
978 conf.doMultiChannel = strstr(item, "no-") == nullptr;
979 } else if (strstr(item, "no-setup") == item) {
980 conf.setupRoute = false;
981 if (strcmp(item, "no-setup") == 0) {
982 conf.setupAll = false;
983 }
984 } else if (strstr(item, "novsync") == item) {
985 conf.novsync = true;
986 } else if (strcasecmp(item, "RGB") == 0 || strcasecmp(item, "YUV") == 0) {
987 conf.forceOutputColorSpace = strcasecmp(item, "RGB") == 0 ? RGB : UYVY;
988 } else if (strcasecmp(item, "smpte-range") == 0 || strcasecmp(item, "full-range") == 0) {
989 conf.smpteRange = strcasecmp(item, "smpte-range") == 0;
990 } else {
991 LOG(LOG_LEVEL_ERROR) << MOD_NAME "Unknown option: " << item << "\n";
992 return nullptr;
993 }
994 tmp = nullptr;
995 }
996
997 try {
998 conf.withAudio = (flags & DISPLAY_FLAG_AUDIO_ANY) != 0u;
999 auto *s = new ugaja::display(conf);
1000 return s;
1001 } catch (runtime_error &e) {
1002 LOG(LOG_LEVEL_ERROR) << MOD_NAME << e.what() << "\n";
1003 }
1004
1005 return nullptr;
1006}
1007
1009{
1010 auto *s = static_cast<struct ugaja::display *>(state);
1011
1012 if (s->desc.color_spec != VIDEO_CODEC_NONE) {
1013 s->join();
1014 s->desc = {};
1015 }
1016
1017 delete s;
1018}
1019
1021{
1022 auto *s = static_cast<struct ugaja::display *>(state);
1023
1024 return vf_alloc_desc_data(s->desc);
1025}
1026
1028{
1029 auto *s = static_cast<struct ugaja::display *>(state);
1030
1031 if (frame && frame->color_spec == R12L) {
1032 char *tmp = (char *) malloc(frame->tiles[0].data_len);
1033 vc_copylineR12LtoR12A((unsigned char *) tmp, (unsigned char *) frame->tiles[0].data, frame->tiles[0].data_len, 0, 0, 0);
1034 free(frame->tiles[0].data);
1035 frame->tiles[0].data = tmp;
1036 }
1037
1038 return s->Putf(frame, nonblock);
1039}
1040
1041LINK_SPEC bool display_aja_get_property(void *state, int property, void *val, size_t *len)
1042{
1043 auto *s = static_cast<struct ugaja::display *>(state);
1044
1045 vector<codec_t> codecs(ugaja::codec_map.size());
1046 int count = 0;
1047
1048 for (auto & c : ugaja::codec_map) {
1049 if (::NTV2DeviceCanDoFrameBufferFormat(s->mDeviceID, c.first)) {
1050 codecs[count++] = c.second;
1051 }
1052 }
1053
1054 switch (property) {
1056 if (count * sizeof(codec_t) <= *len) {
1057 *len = count * sizeof(codec_t);
1058 memcpy(val, codecs.data(), *len);
1059 } else {
1060 return false;
1061 }
1062 break;
1064 {
1065 assert(*len >= sizeof(struct audio_desc));
1066 auto desc = static_cast<struct audio_desc *>(val);
1067 desc->sample_rate = SAMPLE_RATE;
1068 desc->ch_count = ::NTV2DeviceGetMaxAudioChannels (s->mDeviceID);
1069 desc->codec = AC_PCM;
1070 desc->bps = BPS;
1071 *len = sizeof *desc;
1072 }
1073 break;
1075 if (*len >= sizeof(int)) {
1077 *len = sizeof(int);
1078 } else {
1079 return false;
1080 }
1081 break;
1082 default:
1083 return false;
1084 }
1085 return true;
1086}
1087
1089{
1090 auto *s = static_cast<struct ugaja::display *>(state);
1091
1092 lock_guard<mutex> lk(s->mAudioLock);
1093 int len = NTV2_AUDIOSIZE_MAX - s->mAudioLen;
1094 if (frame->data_len > len) {
1095 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Audio buffer overrun!\n";
1096 } else {
1097 len = frame->data_len;
1098 }
1099 memcpy(s->mAudioBuffer.get() + s->mAudioLen, frame->data, len);
1100 s->mAudioLen += len;
1101}
1102
1104 int sample_rate)
1105{
1106 auto *s = static_cast<struct ugaja::display *>(state);
1107 assert(quant_samples == BPS * 8 && sample_rate == SAMPLE_RATE && ::NTV2DeviceGetMaxAudioChannels (s->mDeviceID) == channels);
1108
1109 AJAStatus status = s->SetUpAudio();
1110 if (AJA_FAILURE(status)) {
1111 ostringstream oss;
1112 oss << "Unable to initialize audio: " << AJAStatusToString(status);
1113 return false;
1114 }
1115
1116 return true;
1117}
1118
1119#ifndef _MSC_VER
1133
1135#endif
1136
1137/* vim: set expandtab sw=8 cino=N-8: */
void print_aja_device_details(CNTV2Card *device)
void vc_copylineR12LtoR12A(unsigned char *__restrict dst, const unsigned char *__restrict src, int dstlen, int rshift, int gshift, int bshift)
#define NTV2_AUDIOSIZE_MAX
void ** state
struct video_frame long long nonblock
int void * val
struct video_desc desc
int int int sample_rate
int int channels
struct video_frame * frame
const char * fmt
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
int quant_samples
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
This file contains definition of some common audio types.
@ AC_PCM
Definition types.h:53
#define MOD_NAME
Definition audio.cpp:110
#define SRED(x)
Definition color_out.h:85
#define SBOLD(x)
Definition color_out.h:80
#define NOOP
Definition coreaudio.m:191
void bug_msg(int level, const char *format,...)
prints the error with the advice to report the bug
Definition debug.cpp:379
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define MSG(l, fmt,...)
Definition debug.h:96
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
steady_clock::duration duration
Definition echo.cpp:67
#define PUTF_DISCARD
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_FLAG_AUDIO_ANY
#define DISPLAY_NO_GENERIC_FPS_INDICATOR
@ DISPLAY_PROPERTY_VIDEO_MODE
requested video mode - int (one of display_prop_vid_mode)
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
@ DISPLAY_PROPERTY_AUDIO_FORMAT
@ DISPLAY_PROPERTY_VIDEO_SEPARATE_TILES
framebuffer consists of separate tiles
int count
Definition hd-rum.c:44
#define INIT_NOERR
Definition host.h:73
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define SAMPLE_RATE
Definition mixer.cpp:63
#define BPS
Definition mixer.cpp:64
const unordered_map< NTV2Channel, NTV2OutputCrosspointID, hash< int > > chanToLutSrc
Definition aja.cpp:488
static const std::map< NTV2FrameBufferFormat, codec_t > codec_map
#define CHECK(cmd)
Definition wasapi.cpp:374
#define min(a, b)
Definition rtp.c:99
const char * name
Definition settings.cpp:193
struct used to comunicate with audio capture/playback device
Definition types.h:99
Definition hd-rum.c:27
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
NTV2OutputDestination outputDestination
Definition aja.cpp:145
NTV2Channel outputChannel
if invalid, select according to output destination
Definition aja.cpp:146
codec_t forceOutputColorSpace
force output color space for SDI - RGB or UYVY, VIDEO_CODEC_NONE for default
Definition aja.cpp:152
unique_ptr< char[]> mAudioBuffer
Definition aja.cpp:180
NTV2AudioSystem mAudioSystem
Definition aja.cpp:182
unsigned int max_frame_queue_len
Definition aja.cpp:159
AJAStatus SetUpAudio()
Definition aja.cpp:438
static NTV2FrameRate getFrameRate(double fps)
Definition aja.cpp:807
const struct ultragrid::aja::display::configuration mConf
uint32_t mAudioOutWrapAddress
Definition aja.cpp:185
NTV2DeviceID mDeviceID
Definition aja.cpp:170
static const ULWord app
Definition aja.cpp:167
uint32_t mCurrentOutFrame
Definition aja.cpp:188
queue< struct video_frame * > frames
Definition aja.cpp:158
NTV2VideoFormat mVideoFormat
Definition aja.cpp:175
bool Putf(struct video_frame *frame, long long flags)
Definition aja.cpp:692
display(struct configuration &configuration)
Definition aja.cpp:210
steady_clock::time_point mT0
Definition aja.cpp:193
uint32_t mFramesProcessed
Definition aja.cpp:189
NTV2Channel mOutputChannel
Definition aja.cpp:174
NTV2EveryFrameTaskMode mSavedTaskMode
Used to restore the prior task mode.
Definition aja.cpp:173
AJAStatus SetUpVideo()
Definition aja.cpp:309
static void show_help()
Definition aja.cpp:730
uint32_t mFramesDropped
Definition aja.cpp:190
NTV2FrameBufferFormat mPixelFormat
Definition aja.cpp:176
condition_variable frame_ready
Definition aja.cpp:161
uint32_t mFramesDiscarded
Definition aja.cpp:191
uint32_t mAudioOutLastAddress
Definition aja.cpp:186
bool mDoMultiChannel
Use multi-format.
Definition aja.cpp:172
unsigned int tile_count
Definition types.h:185
codec_t color_spec
Definition types.h:182
enum interlacing_t interlacing
Definition types.h:184
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
unsigned int tile_count
Definition types.h:344
codec_t color_spec
Definition types.h:306
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
Definition types.h:167
codec_t
Definition types.h:65
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ VIDEO_CODEC_NONE
dummy color spec
Definition types.h:66
@ R12L
RGB 12-bit packed, little-endian.
Definition types.h:74
This is an umbrella header for video functions.
static struct @76 codecs[]
LINK_SPEC bool display_aja_reconfigure(void *state, struct video_desc desc)
Definition aja.cpp:885
static bool display_aja_is_quad_format(NTV2VideoFormat fmt)
Definition aja.cpp:855
LINK_SPEC bool display_aja_putf(void *state, struct video_frame *frame, long long nonblock)
Definition aja.cpp:1027
LINK_SPEC void display_aja_done(void *state)
Definition aja.cpp:1008
#define DEFAULT_MAX_FRAME_QUEUE_LEN
Definition aja.cpp:93
#define SAMPLE_RATE
Definition aja.cpp:103
static NTV2VideoFormat display_aja_get_first_matching_video_format(const NTV2FrameRate inFrameRate, const UWord inHeightLines, const UWord inWidthPixels, const bool inIsInterlaced, const bool inIsLevelB, bool skipQuadFormats)
Definition aja.cpp:868
LINK_SPEC bool display_aja_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate)
Definition aja.cpp:1103
static const struct video_display_info display_aja_info
Definition aja.cpp:1120
#define LINK_SPEC
Definition aja.cpp:108
#define MOD_NAME
Definition aja.cpp:94
LINK_SPEC void display_aja_put_audio_frame(void *state, const struct audio_frame *frame)
Definition aja.cpp:1088
#define BPS
Definition aja.cpp:102
LINK_SPEC void * display_aja_init(struct module *, const char *fmt, unsigned int flags)
Definition aja.cpp:938
LINK_SPEC struct video_frame * display_aja_getf(void *state)
Definition aja.cpp:1020
LINK_SPEC bool display_aja_get_property(void *state, int property, void *val, size_t *len)
Definition aja.cpp:1041
#define CHECK_EX(cmd, msg, action_failed)
Definition aja.cpp:131
LINK_SPEC void display_aja_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition aja.cpp:831