1 /**
2  * @file
3  * @brief Source file for DecklinkWriter class
4  * @author Jonathan Thomas <jonathan@openshot.org>
5  *
6  * @ref License
7  */
8 
9 /* LICENSE
10  *
11  * Copyright (c) 2008-2019 OpenShot Studios, LLC
12  * <http://www.openshotstudios.com/>. This file is part of
13  * OpenShot Library (libopenshot), an open-source project dedicated to
14  * delivering high quality video editing and animation solutions to the
15  * world. For more information visit <http://www.openshot.org/>.
16  *
17  * OpenShot Library (libopenshot) is free software: you can redistribute it
18  * and/or modify it under the terms of the GNU Lesser General Public License
19  * as published by the Free Software Foundation, either version 3 of the
20  * License, or (at your option) any later version.
21  *
22  * OpenShot Library (libopenshot) is distributed in the hope that it will be
23  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25  * GNU Lesser General Public License for more details.
26  *
27  * You should have received a copy of the GNU Lesser General Public License
28  * along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29  */
30 
31 #include "DecklinkWriter.h"
32 
33 using namespace openshot;
34 
DecklinkWriter(int device,int video_mode,int pixel_format,int channels,int sample_depth)35 DecklinkWriter::DecklinkWriter(int device, int video_mode, int pixel_format, int channels, int sample_depth)
36 	: device(device), is_open(false), g_videoModeIndex(video_mode), g_audioChannels(channels), g_audioSampleDepth(sample_depth)
37 {
38 	// Init decklink variables
39 	inputFlags = 0;
40 	selectedDisplayMode = bmdModeNTSC;
41 	pixelFormat = bmdFormat8BitYUV;
42 	displayModeCount = 0;
43 	exitStatus = 1;
44 	foundDisplayMode = false;
45 	pthread_mutex_init(&sleepMutex, NULL);
46 	pthread_cond_init(&sleepCond, NULL);
47 
48 	switch(pixel_format)
49 	{
50 		case 0: pixelFormat = bmdFormat8BitYUV; break;
51 		case 1: pixelFormat = bmdFormat10BitYUV; break;
52 		case 2: pixelFormat = bmdFormat10BitRGB; break;
53 		case 3: pixelFormat = bmdFormat8BitARGB; break;
54 		default:
55 			throw DecklinkError("Pixel format is not valid (must be 0,1,2,3).");
56 	}
57 }
58 
59 // Open decklink writer
Open()60 void DecklinkWriter::Open()
61 {
62 	// Open reader if not already open
63 	if (!is_open)
64 	{
65 		// Attempt to open blackmagic card
66 		deckLinkIterator = CreateDeckLinkIteratorInstance();
67 
68 		if (!deckLinkIterator)
69 			throw DecklinkError("This application requires the DeckLink drivers installed.");
70 
71 		/* Connect to a DeckLink instance */
72 		for (int device_count = 0; device_count <= device; device_count++)
73 		{
74 			// Check for requested device
75 			result = deckLinkIterator->Next(&deckLink);
76 			if (result != S_OK)
77 				throw DecklinkError("No DeckLink PCI cards found.");
78 
79 			if (device_count == device)
80 				break;
81 		}
82 
83 		if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&deckLinkOutput) != S_OK)
84 			throw DecklinkError("DeckLink QueryInterface Failed.");
85 
86 		// Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
87 		result = deckLinkOutput->GetDisplayModeIterator(&displayModeIterator);
88 		if (result != S_OK)
89 			throw DecklinkError("Could not obtain the video output display mode iterator.");
90 
91 		if (g_videoModeIndex < 0)
92 			throw DecklinkError("No video mode specified.");
93 
94 		// Loop through all available display modes, until a match is found (if any)
95 		const char *displayModeName;
96 		BMDTimeValue frameRateDuration, frameRateScale;
97 
98 		while (displayModeIterator->Next(&displayMode) == S_OK)
99 		{
100 			if (g_videoModeIndex == displayModeCount)
101 			{
102 				BMDDisplayModeSupport result;
103 
104 				foundDisplayMode = true;
105 				displayMode->GetName(&displayModeName);
106 				selectedDisplayMode = displayMode->GetDisplayMode();
107 				//deckLinkOutput->DoesSupportVideoMode(selectedDisplayMode, pixelFormat, bmdVideoOutputFlagDefault, &result, NULL);
108 
109 				// Get framerate
110 		        displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
111 
112 				//if (result == bmdDisplayModeNotSupported)
113 				//{
114 				//	cout << "The display mode does not support the selected pixel format." << endl;
115 				//	throw DecklinkError("The display mode does not support the selected pixel format.");
116 				//}
117 
118 				break;
119 			}
120 			displayModeCount++;
121 		}
122 
123 		if (!foundDisplayMode)
124 			throw DecklinkError("Invalid video mode.  No matching ones found.");
125 
126 		// Calculate FPS
127 		unsigned long m_framesPerSecond = (unsigned long)((frameRateScale + (frameRateDuration-1))  /  frameRateDuration);
128 
129 		// Create Delegate & Pass in pointers to the output and converters
130 		delegate = new DeckLinkOutputDelegate(displayMode, deckLinkOutput);
131 
132 		// Provide this class as a delegate to the audio and video output interfaces
133 		deckLinkOutput->SetScheduledFrameCompletionCallback(delegate);
134 		//deckLinkOutput->SetAudioCallback(delegate);
135 
136 		// Check for video input
137 		if (deckLinkOutput->EnableVideoOutput(displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) != S_OK)
138 	    	throw DecklinkError("Failed to enable video output. Is another application using the card?");
139 
140 	    // Check for audio input
141 		//if (deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, g_audioSampleDepth, g_audioChannels, bmdAudioOutputStreamContinuous) != S_OK)
142 	    //	throw DecklinkError("Failed to enable audio output. Is another application using the card?");
143 
144 		// Begin video preroll by scheduling a second of frames in hardware
145 		//auto f = std::make_shared<Frame>(1, displayMode->GetWidth(), displayMode->GetHeight(), "Blue");
146 		//f->AddColor(displayMode->GetWidth(), displayMode->GetHeight(), "Blue");
147 
148 		// Preroll 1 second of video
149 		//for (unsigned i = 0; i < 16; i++)
150 		//{
151 		//	// Write 30 blank frames (for preroll)
152 		//	delegate->WriteFrame(f);
153 		//	delegate->ScheduleNextFrame(true);
154 		//}
155 
156 		//deckLinkOutput->StartScheduledPlayback(0, 100, 1.0);
157 		//if (deckLinkOutput->BeginAudioPreroll() != S_OK)
158 		//	throw DecklinkError("Failed to begin audio preroll.");
159 
160 
161 		// Update image properties
162 		info.has_audio = true;
163 		info.has_video = true;
164 		info.vcodec = displayModeName;
165 		info.width = displayMode->GetWidth();
166 		info.height = displayMode->GetHeight();
167 		info.file_size = info.width * info.height * sizeof(char) * 4;
168 		info.pixel_ratio.num = 1;
169 		info.pixel_ratio.den = 1;
170 		info.duration = 60 * 60 * 24; // 24 hour duration... since we're capturing a live stream
171 		info.fps.num = frameRateScale;
172 		info.fps.den = frameRateDuration;
173 		info.video_timebase.num = frameRateDuration;
174 		info.video_timebase.den = frameRateScale;
175 		info.video_length = round(info.duration * info.fps.ToDouble());
176 
177 		// Calculate the DAR (display aspect ratio)
178 		Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den);
179 
180 		// Reduce size fraction
181 		size.Reduce();
182 
183 		// Set the ratio based on the reduced fraction
184 		info.display_ratio.num = size.num;
185 		info.display_ratio.den = size.den;
186 
187 		// Mark as "open"
188 		is_open = true;
189 	}
190 }
191 
192 // Close device and video stream
Close()193 void DecklinkWriter::Close()
194 {
195 	// Close all objects, if reader is 'open'
196 	if (is_open)
197 	{
198 		// Stop the audio and video output streams immediately
199 		deckLinkOutput->StopScheduledPlayback(0, NULL, 0);
200 		deckLinkOutput->DisableAudioOutput();
201 		deckLinkOutput->DisableVideoOutput();
202 
203 		// Release DisplayMode
204 		displayMode->Release();
205 
206 		if (displayModeIterator != NULL)
207 		{
208 			displayModeIterator->Release();
209 			displayModeIterator = NULL;
210 		}
211 
212 	    if (deckLinkOutput != NULL)
213 	    {
214 	    	deckLinkOutput->Release();
215 	    	deckLinkOutput = NULL;
216 	    }
217 
218 	    if (deckLink != NULL)
219 	    {
220 	        deckLink->Release();
221 	        deckLink = NULL;
222 	    }
223 
224 		if (deckLinkIterator != NULL)
225 			deckLinkIterator->Release();
226 
227 		// Mark as "closed"
228 		is_open = false;
229 	}
230 }
231 
232 // This method is required for all derived classes of WriterBase.  Write a Frame to the video file.
WriteFrame(std::shared_ptr<Frame> frame)233 void DecklinkWriter::WriteFrame(std::shared_ptr<Frame> frame)
234 {
235 	// Check for open reader (or throw exception)
236 	if (!is_open)
237 		throw WriterClosed("The DecklinkWriter is closed.  Call Open() before calling this method.");
238 
239 	delegate->WriteFrame(frame);
240 }
241 
242 // This method is required for all derived classes of WriterBase.  Write a block of frames from a reader.
WriteFrame(ReaderBase * reader,int start,int length)243 void DecklinkWriter::WriteFrame(ReaderBase* reader, int start, int length)
244 {
245 	// Loop through each frame (and encoded it)
246 	for (int number = start; number <= length; number++)
247 	{
248 		// Get the frame
249 		std::shared_ptr<Frame> f = reader->GetFrame(number);
250 
251 		// Encode frame
252 		WriteFrame(f);
253 	}
254 }
255