]> git.sesse.net Git - nageru/blob - decklink_output.cpp
Update the queue length metric after trimming, not before.
[nageru] / decklink_output.cpp
1 #include <movit/effect_util.h>
2 #include <movit/util.h>
3 #include <movit/resource_pool.h>  // Must be above the Xlib includes.
4 #include <pthread.h>
5
6 #include <epoxy/egl.h>
7
8 #include "chroma_subsampler.h"
9 #include "decklink_output.h"
10 #include "decklink_util.h"
11 #include "flags.h"
12 #include "print_latency.h"
13 #include "timebase.h"
14 #include "v210_converter.h"
15
16 using namespace movit;
17 using namespace std;
18 using namespace std::chrono;
19
20 namespace {
21
22 // This class can be deleted during regular use, so make all the metrics static.
23 bool metrics_inited = false;
24 LatencyHistogram latency_histogram;
25 atomic<int64_t> metric_decklink_output_width_pixels{-1};
26 atomic<int64_t> metric_decklink_output_height_pixels{-1};
27 atomic<int64_t> metric_decklink_output_frame_rate_den{-1};
28 atomic<int64_t> metric_decklink_output_frame_rate_nom{-1};
29 atomic<int64_t> metric_decklink_output_inflight_frames{0};
30 atomic<int64_t> metric_decklink_output_color_mismatch_frames{0};
31
32 atomic<int64_t> metric_decklink_output_scheduled_frames_dropped{0};
33 atomic<int64_t> metric_decklink_output_scheduled_frames_late{0};
34 atomic<int64_t> metric_decklink_output_scheduled_frames_normal{0};
35 atomic<int64_t> metric_decklink_output_scheduled_frames_preroll{0};
36
37 atomic<int64_t> metric_decklink_output_completed_frames_completed{0};
38 atomic<int64_t> metric_decklink_output_completed_frames_dropped{0};
39 atomic<int64_t> metric_decklink_output_completed_frames_flushed{0};
40 atomic<int64_t> metric_decklink_output_completed_frames_late{0};
41 atomic<int64_t> metric_decklink_output_completed_frames_unknown{0};
42
43 atomic<int64_t> metric_decklink_output_scheduled_samples{0};
44
45 }  // namespace
46
47 DeckLinkOutput::DeckLinkOutput(ResourcePool *resource_pool, QSurface *surface, unsigned width, unsigned height, unsigned card_index)
48         : resource_pool(resource_pool), surface(surface), width(width), height(height), card_index(card_index)
49 {
50         chroma_subsampler.reset(new ChromaSubsampler(resource_pool));
51
52         if (!metrics_inited) {
53                 latency_histogram.init("decklink_output");
54                 global_metrics.add("decklink_output_width_pixels", &metric_decklink_output_width_pixels, Metrics::TYPE_GAUGE);
55                 global_metrics.add("decklink_output_height_pixels", &metric_decklink_output_height_pixels, Metrics::TYPE_GAUGE);
56                 global_metrics.add("decklink_output_frame_rate_den", &metric_decklink_output_frame_rate_den, Metrics::TYPE_GAUGE);
57                 global_metrics.add("decklink_output_frame_rate_nom", &metric_decklink_output_frame_rate_nom, Metrics::TYPE_GAUGE);
58                 global_metrics.add("decklink_output_inflight_frames", &metric_decklink_output_inflight_frames, Metrics::TYPE_GAUGE);
59                 global_metrics.add("decklink_output_color_mismatch_frames", &metric_decklink_output_color_mismatch_frames);
60
61                 global_metrics.add("decklink_output_scheduled_frames", {{ "status", "dropped" }}, &metric_decklink_output_scheduled_frames_dropped);
62                 global_metrics.add("decklink_output_scheduled_frames", {{ "status", "late" }}, &metric_decklink_output_scheduled_frames_late);
63                 global_metrics.add("decklink_output_scheduled_frames", {{ "status", "normal" }}, &metric_decklink_output_scheduled_frames_normal);
64                 global_metrics.add("decklink_output_scheduled_frames", {{ "status", "preroll" }}, &metric_decklink_output_scheduled_frames_preroll);
65
66                 global_metrics.add("decklink_output_completed_frames", {{ "status", "completed" }}, &metric_decklink_output_completed_frames_completed);
67                 global_metrics.add("decklink_output_completed_frames", {{ "status", "dropped" }}, &metric_decklink_output_completed_frames_dropped);
68                 global_metrics.add("decklink_output_completed_frames", {{ "status", "flushed" }}, &metric_decklink_output_completed_frames_flushed);
69                 global_metrics.add("decklink_output_completed_frames", {{ "status", "late" }}, &metric_decklink_output_completed_frames_late);
70                 global_metrics.add("decklink_output_completed_frames", {{ "status", "unknown" }}, &metric_decklink_output_completed_frames_unknown);
71
72                 global_metrics.add("decklink_output_scheduled_samples", &metric_decklink_output_scheduled_samples);
73
74                 metrics_inited = true;
75         }
76 }
77
78 void DeckLinkOutput::set_device(IDeckLink *decklink)
79 {
80         if (decklink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK) {
81                 fprintf(stderr, "Card %u has no outputs\n", card_index);
82                 exit(1);
83         }
84
85         IDeckLinkDisplayModeIterator *mode_it;
86         if (output->GetDisplayModeIterator(&mode_it) != S_OK) {
87                 fprintf(stderr, "Failed to enumerate output display modes for card %u\n", card_index);
88                 exit(1);
89         }
90
91         video_modes.clear();
92
93         for (const auto &it : summarize_video_modes(mode_it, card_index)) {
94                 if (it.second.width != width || it.second.height != height) {
95                         continue;
96                 }
97
98                 // We could support interlaced modes, but let's stay out of it for now,
99                 // since we don't have interlaced stream output.
100                 if (it.second.interlaced) {
101                         continue;
102                 }
103
104                 video_modes.insert(it);
105         }
106
107         mode_it->Release();
108
109         // HDMI or SDI generally mean “both HDMI and SDI at the same time” on DeckLink cards
110         // that support both; pick_default_video_connection() will generally pick one of those
111         // if they exist. We're not very likely to need analog outputs, so we don't need a way
112         // to change beyond that.
113         video_connection = pick_default_video_connection(decklink, BMDDeckLinkVideoOutputConnections, card_index);
114 }
115
116 void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
117 {
118         assert(output);
119         assert(!playback_initiated);
120
121         if (video_modes.empty()) {
122                 fprintf(stderr, "ERROR: No matching output modes for %dx%d found\n", width, height);
123                 exit(1);
124         }
125
126         should_quit.unquit();
127         playback_initiated = true;
128         playback_started = false;
129         this->base_pts = base_pts;
130
131         IDeckLinkConfiguration *config = nullptr;
132         if (output->QueryInterface(IID_IDeckLinkConfiguration, (void**)&config) != S_OK) {
133                 fprintf(stderr, "Failed to get configuration interface for output card\n");
134                 exit(1);
135         }
136         if (config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true) != S_OK) {
137                 fprintf(stderr, "Failed to set low latency output\n");
138                 exit(1);
139         }
140         if (config->SetInt(bmdDeckLinkConfigVideoOutputConnection, video_connection) != S_OK) {
141                 fprintf(stderr, "Failed to set video output connection for card %u\n", card_index);
142                 exit(1);
143         }
144         if (config->SetFlag(bmdDeckLinkConfigUse1080pNotPsF, true) != S_OK) {
145                 fprintf(stderr, "Failed to set PsF flag for card\n");
146                 exit(1);
147         }
148         if (config->SetFlag(bmdDeckLinkConfigSMPTELevelAOutput, true) != S_OK) {
149                 // This affects at least some no-name SDI->HDMI converters.
150                 // Warn, but don't die.
151                 fprintf(stderr, "WARNING: Failed to enable SMTPE Level A; resolutions like 1080p60 might have issues.\n");
152         }
153
154         BMDDisplayModeSupport support;
155         IDeckLinkDisplayMode *display_mode;
156         BMDPixelFormat pixel_format = global_flags.ten_bit_output ? bmdFormat10BitYUV : bmdFormat8BitYUV;
157         if (output->DoesSupportVideoMode(mode, pixel_format, bmdVideoOutputFlagDefault,
158                                          &support, &display_mode) != S_OK) {
159                 fprintf(stderr, "Couldn't ask for format support\n");
160                 exit(1);
161         }
162
163         if (support == bmdDisplayModeNotSupported) {
164                 fprintf(stderr, "Requested display mode not supported\n");
165                 exit(1);
166         }
167
168         current_mode_flags = display_mode->GetFlags();
169
170         BMDTimeValue time_value;
171         BMDTimeScale time_scale;
172         if (display_mode->GetFrameRate(&time_value, &time_scale) != S_OK) {
173                 fprintf(stderr, "Couldn't get frame rate\n");
174                 exit(1);
175         }
176
177         metric_decklink_output_width_pixels = width;
178         metric_decklink_output_height_pixels = height;
179         metric_decklink_output_frame_rate_nom = time_value;
180         metric_decklink_output_frame_rate_den = time_scale;
181
182         frame_duration = time_value * TIMEBASE / time_scale;
183
184         display_mode->Release();
185
186         HRESULT result = output->EnableVideoOutput(mode, bmdVideoOutputFlagDefault);
187         if (result != S_OK) {
188                 fprintf(stderr, "Couldn't enable output with error 0x%x\n", result);
189                 exit(1);
190         }
191         if (output->SetScheduledFrameCompletionCallback(this) != S_OK) {
192                 fprintf(stderr, "Couldn't set callback\n");
193                 exit(1);
194         }
195         assert(OUTPUT_FREQUENCY == 48000);
196         if (output->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped) != S_OK) {
197                 fprintf(stderr, "Couldn't enable audio output\n");
198                 exit(1);
199         }
200         if (output->BeginAudioPreroll() != S_OK) {
201                 fprintf(stderr, "Couldn't begin audio preroll\n");
202                 exit(1);
203         }
204
205         present_thread = thread([this]{
206                 QOpenGLContext *context = create_context(this->surface);
207                 eglBindAPI(EGL_OPENGL_API);
208                 if (!make_current(context, this->surface)) {
209                         printf("display=%p surface=%p context=%p curr=%p err=%d\n", eglGetCurrentDisplay(), this->surface, context, eglGetCurrentContext(),
210                                 eglGetError());
211                         exit(1);
212                 }
213                 present_thread_func();
214                 delete_context(context);
215         });
216 }
217
218 void DeckLinkOutput::end_output()
219 {
220         if (!playback_initiated) {
221                 return;
222         }
223
224         should_quit.quit();
225         frame_queues_changed.notify_all();
226         present_thread.join();
227         playback_initiated = false;
228
229         output->StopScheduledPlayback(0, nullptr, 0);
230         output->DisableVideoOutput();
231         output->DisableAudioOutput();
232
233         // Wait until all frames are accounted for, and free them.
234         {
235                 unique_lock<mutex> lock(frame_queue_mutex);
236                 while (!(frame_freelist.empty() && num_frames_in_flight == 0)) {
237                         frame_queues_changed.wait(lock, [this]{ return !frame_freelist.empty(); });
238                         frame_freelist.pop();
239                 }
240         }
241 }
242
243 void DeckLinkOutput::send_frame(GLuint y_tex, GLuint cbcr_tex, YCbCrLumaCoefficients output_ycbcr_coefficients, const vector<RefCountedFrame> &input_frames, int64_t pts, int64_t duration)
244 {
245         assert(!should_quit.should_quit());
246
247         if ((current_mode_flags & bmdDisplayModeColorspaceRec601) && output_ycbcr_coefficients == YCBCR_REC_709) {
248                 if (!last_frame_had_mode_mismatch) {
249                         fprintf(stderr, "WARNING: Chosen output mode expects Rec. 601 Y'CbCr coefficients.\n");
250                         fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec601 (or =auto).\n");
251                 }
252                 last_frame_had_mode_mismatch = true;
253                 ++metric_decklink_output_color_mismatch_frames;
254         } else if ((current_mode_flags & bmdDisplayModeColorspaceRec709) && output_ycbcr_coefficients == YCBCR_REC_601) {
255                 if (!last_frame_had_mode_mismatch) {
256                         fprintf(stderr, "WARNING: Chosen output mode expects Rec. 709 Y'CbCr coefficients.\n");
257                         fprintf(stderr, "         Consider --output-ycbcr-coefficients=rec709 (or =auto).\n");
258                 }
259                 last_frame_had_mode_mismatch = true;
260                 ++metric_decklink_output_color_mismatch_frames;
261         } else {
262                 last_frame_had_mode_mismatch = false;
263         }
264
265         unique_ptr<Frame> frame = move(get_frame());
266         if (global_flags.ten_bit_output) {
267                 chroma_subsampler->create_v210(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
268         } else {
269                 chroma_subsampler->create_uyvy(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
270         }
271
272         // Download the UYVY texture to the PBO.
273         glPixelStorei(GL_PACK_ROW_LENGTH, 0);
274         check_error();
275
276         glBindBuffer(GL_PIXEL_PACK_BUFFER, frame->pbo);
277         check_error();
278
279         if (global_flags.ten_bit_output) {
280                 glBindTexture(GL_TEXTURE_2D, frame->uyvy_tex);
281                 check_error();
282                 glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, BUFFER_OFFSET(0));
283                 check_error();
284         } else {
285                 glBindTexture(GL_TEXTURE_2D, frame->uyvy_tex);
286                 check_error();
287                 glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
288                 check_error();
289         }
290
291         glBindTexture(GL_TEXTURE_2D, 0);
292         check_error();
293         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
294         check_error();
295
296         glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT | GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
297         check_error();
298
299         frame->fence = RefCountedGLsync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
300         check_error();
301         glFlush();  // Make the DeckLink thread see the fence as soon as possible.
302         check_error();
303
304         frame->input_frames = input_frames;
305         frame->received_ts = find_received_timestamp(input_frames);
306         frame->pts = pts;
307         frame->duration = duration;
308
309         {
310                 unique_lock<mutex> lock(frame_queue_mutex);
311                 pending_video_frames.push(move(frame));
312         }
313         frame_queues_changed.notify_all();
314 }
315
316 void DeckLinkOutput::send_audio(int64_t pts, const std::vector<float> &samples)
317 {
318         unique_ptr<int32_t[]> int_samples(new int32_t[samples.size()]);
319         for (size_t i = 0; i < samples.size(); ++i) {
320                 int_samples[i] = lrintf(samples[i] * 2147483648.0f);
321         }
322
323         uint32_t frames_written;
324         HRESULT result = output->ScheduleAudioSamples(int_samples.get(), samples.size() / 2,
325                 pts, TIMEBASE, &frames_written);
326         if (result != S_OK) {
327                 fprintf(stderr, "ScheduleAudioSamples(pts=%ld) failed (result=0x%08x)\n", pts, result);
328         } else {
329                 if (frames_written != samples.size() / 2) {
330                         fprintf(stderr, "ScheduleAudioSamples() returned short write (%u/%ld)\n", frames_written, samples.size() / 2);
331                 }
332         }
333         metric_decklink_output_scheduled_samples += samples.size() / 2;
334 }
335
336 void DeckLinkOutput::wait_for_frame(int64_t pts, int *dropped_frames, int64_t *frame_duration, bool *is_preroll, steady_clock::time_point *frame_timestamp)
337 {
338         assert(!should_quit.should_quit());
339
340         *dropped_frames = 0;
341         *frame_duration = this->frame_duration;
342
343         const BMDTimeValue buffer = lrint(*frame_duration * global_flags.output_buffer_frames);
344         const BMDTimeValue max_overshoot = lrint(*frame_duration * global_flags.output_slop_frames);
345         BMDTimeValue target_time = pts - buffer;
346
347         // While prerolling, we send out frames as quickly as we can.
348         if (target_time < base_pts) {
349                 *is_preroll = true;
350                 ++metric_decklink_output_scheduled_frames_preroll;
351                 return;
352         }
353
354         *is_preroll = !playback_started;
355
356         if (!playback_started) {
357                 if (output->EndAudioPreroll() != S_OK) {
358                         fprintf(stderr, "Could not end audio preroll\n");
359                         exit(1);  // TODO
360                 }
361                 if (output->StartScheduledPlayback(base_pts, TIMEBASE, 1.0) != S_OK) {
362                         fprintf(stderr, "Could not start playback\n");
363                         exit(1);  // TODO
364                 }
365                 playback_started = true;
366         }
367
368         BMDTimeValue stream_frame_time;
369         double playback_speed;
370         output->GetScheduledStreamTime(TIMEBASE, &stream_frame_time, &playback_speed);
371
372         *frame_timestamp = steady_clock::now() +
373                 nanoseconds((target_time - stream_frame_time) * 1000000000 / TIMEBASE);
374
375         // If we're ahead of time, wait for the frame to (approximately) start.
376         if (stream_frame_time < target_time) {
377                 should_quit.sleep_until(*frame_timestamp);
378                 ++metric_decklink_output_scheduled_frames_normal;
379                 return;
380         }
381
382         // If we overshot the previous frame by just a little,
383         // fire off one immediately.
384         if (stream_frame_time < target_time + max_overshoot) {
385                 fprintf(stderr, "Warning: Frame was %ld ms late (but not skipping it due to --output-slop-frames).\n",
386                         lrint((stream_frame_time - target_time) * 1000.0 / TIMEBASE));
387                 ++metric_decklink_output_scheduled_frames_late;
388                 return;
389         }
390
391         // Oops, we missed by more than one frame. Return immediately,
392         // but drop so that we catch up.
393         *dropped_frames = (stream_frame_time - target_time + *frame_duration - 1) / *frame_duration;
394         const int64_t ns_per_frame = this->frame_duration * 1000000000 / TIMEBASE;
395         *frame_timestamp += nanoseconds(*dropped_frames * ns_per_frame);
396         fprintf(stderr, "Dropped %d output frames; skipping.\n", *dropped_frames);
397         metric_decklink_output_scheduled_frames_dropped += *dropped_frames;
398         ++metric_decklink_output_scheduled_frames_normal;
399 }
400
401 uint32_t DeckLinkOutput::pick_video_mode(uint32_t mode) const
402 {
403         if (video_modes.count(mode)) {
404                 return mode;
405         }
406
407         // Prioritize 59.94 > 60 > 29.97. If none of those are found, then pick the highest one.
408         for (const pair<int, int> &desired : vector<pair<int, int>>{ { 60000, 1001 }, { 60, 0 }, { 30000, 1001 } }) {
409                 for (const auto &it : video_modes) {
410                         if (it.second.frame_rate_num * desired.second == desired.first * it.second.frame_rate_den) {
411                                 return it.first;
412                         }
413                 }
414         }
415
416         uint32_t best_mode = 0;
417         double best_fps = 0.0;
418         for (const auto &it : video_modes) {
419                 double fps = double(it.second.frame_rate_num) / it.second.frame_rate_den;
420                 if (fps > best_fps) {
421                         best_mode = it.first;
422                         best_fps = fps;
423                 }
424         }
425         return best_mode;
426 }
427
428 YCbCrLumaCoefficients DeckLinkOutput::preferred_ycbcr_coefficients() const
429 {
430         if (current_mode_flags & bmdDisplayModeColorspaceRec601) {
431                 return YCBCR_REC_601;
432         } else {
433                 // Don't bother checking bmdDisplayModeColorspaceRec709;
434                 // if none is set, 709 is a good default anyway.
435                 return YCBCR_REC_709;
436         }
437 }
438
439 HRESULT DeckLinkOutput::ScheduledFrameCompleted(/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result)
440 {
441         Frame *frame = static_cast<Frame *>(completedFrame);
442         switch (result) {
443         case bmdOutputFrameCompleted:
444                 ++metric_decklink_output_completed_frames_completed;
445                 break;
446         case bmdOutputFrameDisplayedLate:
447                 fprintf(stderr, "Output frame displayed late (pts=%ld)\n", frame->pts);
448                 fprintf(stderr, "Consider increasing --output-buffer-frames if this persists.\n");
449                 ++metric_decklink_output_completed_frames_late;
450                 break;
451         case bmdOutputFrameDropped:
452                 fprintf(stderr, "Output frame was dropped (pts=%ld)\n", frame->pts);
453                 fprintf(stderr, "Consider increasing --output-buffer-frames if this persists.\n");
454                 ++metric_decklink_output_completed_frames_dropped;
455                 break;
456         case bmdOutputFrameFlushed:
457                 fprintf(stderr, "Output frame was flushed (pts=%ld)\n", frame->pts);
458                 ++metric_decklink_output_completed_frames_flushed;
459                 break;
460         default:
461                 fprintf(stderr, "Output frame completed with unknown status %d\n", result);
462                 ++metric_decklink_output_completed_frames_unknown;
463                 break;
464         }
465
466         static int frameno = 0;
467         print_latency("DeckLink output latency (frame received → output on HDMI):", frame->received_ts, false, &frameno, &latency_histogram);
468
469         {
470                 lock_guard<mutex> lock(frame_queue_mutex);
471                 frame_freelist.push(unique_ptr<Frame>(frame));
472                 --num_frames_in_flight;
473                 --metric_decklink_output_inflight_frames;
474         }
475
476         return S_OK;
477 }
478
479 HRESULT DeckLinkOutput::ScheduledPlaybackHasStopped()
480 {
481         printf("playback stopped!\n");
482         return S_OK;
483 }
484
485 unique_ptr<DeckLinkOutput::Frame> DeckLinkOutput::get_frame()
486 {
487         lock_guard<mutex> lock(frame_queue_mutex);
488
489         if (!frame_freelist.empty()) {
490                 unique_ptr<Frame> frame = move(frame_freelist.front());
491                 frame_freelist.pop();
492                 return frame;
493         }
494
495         unique_ptr<Frame> frame(new Frame);
496
497         size_t stride;
498         if (global_flags.ten_bit_output) {
499                 stride = v210Converter::get_v210_stride(width);
500                 GLint v210_width = stride / sizeof(uint32_t);
501                 frame->uyvy_tex = resource_pool->create_2d_texture(GL_RGB10_A2, v210_width, height);
502
503                 // We need valid texture state, or NVIDIA won't allow us to write to the texture.
504                 glBindTexture(GL_TEXTURE_2D, frame->uyvy_tex);
505                 check_error();
506                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
507                 check_error();
508         } else {
509                 stride = width * 2;
510                 frame->uyvy_tex = resource_pool->create_2d_texture(GL_RGBA8, width / 2, height);
511         }
512
513         glGenBuffers(1, &frame->pbo);
514         check_error();
515         glBindBuffer(GL_PIXEL_PACK_BUFFER, frame->pbo);
516         check_error();
517         glBufferStorage(GL_PIXEL_PACK_BUFFER, stride * height, NULL, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT);
518         check_error();
519         frame->uyvy_ptr = (uint8_t *)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, stride * height, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT);
520         check_error();
521         frame->uyvy_ptr_local.reset(new uint8_t[stride * height]);
522         frame->resource_pool = resource_pool;
523
524         return frame;
525 }
526
527 void DeckLinkOutput::present_thread_func()
528 {
529         pthread_setname_np(pthread_self(), "DeckLinkOutput");
530         for ( ;; ) {
531                 unique_ptr<Frame> frame;
532                 {
533                         unique_lock<mutex> lock(frame_queue_mutex);
534                         frame_queues_changed.wait(lock, [this]{
535                                 return should_quit.should_quit() || !pending_video_frames.empty();
536                         });
537                         if (should_quit.should_quit()) {
538                                 return;
539                         }
540                         frame = move(pending_video_frames.front());
541                         pending_video_frames.pop();
542                         ++num_frames_in_flight;
543                         ++metric_decklink_output_inflight_frames;
544                 }
545
546                 glClientWaitSync(frame->fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
547                 check_error();
548                 frame->fence.reset();
549
550                 if (global_flags.ten_bit_output) {
551                         memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, v210Converter::get_v210_stride(width) * height);
552                 } else {
553                         memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, width * height * 2);
554                 }
555
556                 // Release any input frames we needed to render this frame.
557                 frame->input_frames.clear();
558
559                 BMDTimeValue pts = frame->pts;
560                 BMDTimeValue duration = frame->duration;
561                 HRESULT res = output->ScheduleVideoFrame(frame.get(), pts, duration, TIMEBASE);
562                 if (res == S_OK) {
563                         frame.release();  // Owned by the driver now.
564                 } else {
565                         fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
566
567                         lock_guard<mutex> lock(frame_queue_mutex);
568                         frame_freelist.push(move(frame));
569                         --num_frames_in_flight;
570                         --metric_decklink_output_inflight_frames;
571                 }
572         }
573 }
574
575 HRESULT STDMETHODCALLTYPE DeckLinkOutput::QueryInterface(REFIID, LPVOID *)
576 {
577         return E_NOINTERFACE;
578 }
579
580 ULONG STDMETHODCALLTYPE DeckLinkOutput::AddRef()
581 {
582         return refcount.fetch_add(1) + 1;
583 }
584
585 ULONG STDMETHODCALLTYPE DeckLinkOutput::Release()
586 {
587         int new_ref = refcount.fetch_sub(1) - 1;
588         if (new_ref == 0)
589                 delete this;
590         return new_ref;
591 }
592
593 DeckLinkOutput::Frame::~Frame()
594 {
595         glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
596         check_error();
597         glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
598         check_error();
599         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
600         check_error();
601         glDeleteBuffers(1, &pbo);
602         check_error();
603         resource_pool->release_2d_texture(uyvy_tex);
604         check_error();
605 }
606
607 HRESULT STDMETHODCALLTYPE DeckLinkOutput::Frame::QueryInterface(REFIID, LPVOID *)
608 {
609         return E_NOINTERFACE;
610 }
611
612 ULONG STDMETHODCALLTYPE DeckLinkOutput::Frame::AddRef()
613 {
614         return refcount.fetch_add(1) + 1;
615 }
616
617 ULONG STDMETHODCALLTYPE DeckLinkOutput::Frame::Release()
618 {
619         int new_ref = refcount.fetch_sub(1) - 1;
620         if (new_ref == 0)
621                 delete this;
622         return new_ref;
623 }
624
625 long DeckLinkOutput::Frame::GetWidth()
626 {
627         return global_flags.width;
628 }
629
630 long DeckLinkOutput::Frame::GetHeight()
631 {
632         return global_flags.height;
633 }
634
635 long DeckLinkOutput::Frame::GetRowBytes()
636 {
637         if (global_flags.ten_bit_output) {
638                 return v210Converter::get_v210_stride(global_flags.width);
639         } else {
640                 return global_flags.width * 2;
641         }
642 }
643
644 BMDPixelFormat DeckLinkOutput::Frame::GetPixelFormat()
645 {
646         if (global_flags.ten_bit_output) {
647                 return bmdFormat10BitYUV;
648         } else {
649                 return bmdFormat8BitYUV;
650         }
651 }
652
653 BMDFrameFlags DeckLinkOutput::Frame::GetFlags()
654 {
655         return bmdFrameFlagDefault;
656 }
657
658 HRESULT DeckLinkOutput::Frame::GetBytes(/* out */ void **buffer)
659 {
660         *buffer = uyvy_ptr_local.get();
661         return S_OK;
662 }
663
664 HRESULT DeckLinkOutput::Frame::GetTimecode(/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode **timecode)
665 {
666         fprintf(stderr, "STUB: GetTimecode()\n");
667         return E_NOTIMPL;
668 }
669
670 HRESULT DeckLinkOutput::Frame::GetAncillaryData(/* out */ IDeckLinkVideoFrameAncillary **ancillary)
671 {
672         fprintf(stderr, "STUB: GetAncillaryData()\n");
673         return E_NOTIMPL;
674 }