});
}
-bool DeckLinkOutput::set_device(IDeckLink *decklink)
+DeckLinkOutput::~DeckLinkOutput()
{
+ if (output != nullptr) {
+ output->Release();
+ }
+}
+
+bool DeckLinkOutput::set_device(IDeckLink *decklink, IDeckLinkInput *input_arg)
+{
+ input = input_arg;
if (decklink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK) {
fprintf(stderr, "Warning: Card %u has no outputs\n", card_index);
return false;
return true;
}
-void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts)
+void DeckLinkOutput::start_output(uint32_t mode, int64_t base_pts, bool is_master_card_arg)
{
assert(output);
assert(!playback_initiated);
+ this->is_master_card = is_master_card_arg;
if (video_modes.empty()) {
fprintf(stderr, "ERROR: No matching output modes for %dx%d found\n", width, height);
- exit(1);
+ abort();
}
should_quit.unquit();
IDeckLinkConfiguration *config = nullptr;
if (output->QueryInterface(IID_IDeckLinkConfiguration, (void**)&config) != S_OK) {
fprintf(stderr, "Failed to get configuration interface for output card\n");
- exit(1);
+ abort();
}
if (config->SetFlag(bmdDeckLinkConfigLowLatencyVideoOutput, true) != S_OK) {
fprintf(stderr, "Failed to set low latency output\n");
- exit(1);
+ abort();
}
if (config->SetInt(bmdDeckLinkConfigVideoOutputConnection, video_connection) != S_OK) {
fprintf(stderr, "Failed to set video output connection for card %u\n", card_index);
- exit(1);
+ abort();
}
- if (config->SetFlag(bmdDeckLinkConfigUse1080pNotPsF, true) != S_OK) {
+ if (config->SetFlag(bmdDeckLinkConfigOutput1080pAsPsF, true) != S_OK) {
fprintf(stderr, "Failed to set PsF flag for card\n");
- exit(1);
+ abort();
}
if (config->SetFlag(bmdDeckLinkConfigSMPTELevelAOutput, true) != S_OK) {
// This affects at least some no-name SDI->HDMI converters.
BMDDisplayModeSupport support;
IDeckLinkDisplayMode *display_mode;
- BMDPixelFormat pixel_format = global_flags.ten_bit_output ? bmdFormat10BitYUV : bmdFormat8BitYUV;
+ BMDPixelFormat pixel_format = global_flags.bit_depth > 8 ? bmdFormat10BitYUV : bmdFormat8BitYUV;
if (output->DoesSupportVideoMode(mode, pixel_format, bmdVideoOutputFlagDefault,
&support, &display_mode) != S_OK) {
fprintf(stderr, "Couldn't ask for format support\n");
- exit(1);
+ abort();
}
if (support == bmdDisplayModeNotSupported) {
fprintf(stderr, "Requested display mode not supported\n");
- exit(1);
+ abort();
}
current_mode_flags = display_mode->GetFlags();
BMDTimeScale time_scale;
if (display_mode->GetFrameRate(&time_value, &time_scale) != S_OK) {
fprintf(stderr, "Couldn't get frame rate\n");
- exit(1);
+ abort();
}
metric_decklink_output_width_pixels = width;
display_mode->Release();
+ if (input != nullptr) {
+ if (input->DisableVideoInput() != S_OK) {
+ fprintf(stderr, "Warning: Failed to disable video input for card %d\n", card_index);
+ }
+ if (input->DisableAudioInput() != S_OK) {
+ fprintf(stderr, "Warning: Failed to disable audio input for card %d\n", card_index);
+ }
+ }
+
HRESULT result = output->EnableVideoOutput(mode, bmdVideoOutputFlagDefault);
if (result != S_OK) {
fprintf(stderr, "Couldn't enable output with error 0x%x\n", result);
- exit(1);
+ abort();
}
if (output->SetScheduledFrameCompletionCallback(this) != S_OK) {
fprintf(stderr, "Couldn't set callback\n");
- exit(1);
+ abort();
}
assert(OUTPUT_FREQUENCY == 48000);
if (output->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType32bitInteger, 2, bmdAudioOutputStreamTimestamped) != S_OK) {
fprintf(stderr, "Couldn't enable audio output\n");
- exit(1);
+ abort();
}
- if (output->BeginAudioPreroll() != S_OK) {
- fprintf(stderr, "Couldn't begin audio preroll\n");
- exit(1);
+ if (is_master_card) {
+ if (output->BeginAudioPreroll() != S_OK) {
+ fprintf(stderr, "Couldn't begin audio preroll\n");
+ abort();
+ }
+ } else {
+ playback_started = true;
}
present_thread = thread([this]{
if (!make_current(context, this->surface)) {
printf("display=%p surface=%p context=%p curr=%p err=%d\n", eglGetCurrentDisplay(), this->surface, context, eglGetCurrentContext(),
eglGetError());
- exit(1);
+ abort();
}
present_thread_func();
delete_context(context);
present_thread.join();
playback_initiated = false;
- output->StopScheduledPlayback(0, nullptr, 0);
+ if (is_master_card) {
+ output->StopScheduledPlayback(0, nullptr, 0);
+ }
output->DisableVideoOutput();
output->DisableAudioOutput();
// Wait until all frames are accounted for, and free them.
{
unique_lock<mutex> lock(frame_queue_mutex);
- while (!(frame_freelist.empty() && num_frames_in_flight == 0)) {
+ while (!(frame_freelist.empty() && scheduled_frames.empty())) {
frame_queues_changed.wait(lock, [this]{ return !frame_freelist.empty(); });
frame_freelist.pop();
}
}
unique_ptr<Frame> frame = get_frame();
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
chroma_subsampler->create_v210(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
} else {
chroma_subsampler->create_uyvy(y_tex, cbcr_tex, width, height, frame->uyvy_tex);
glBindBuffer(GL_PIXEL_PACK_BUFFER, frame->pbo);
check_error();
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
glBindTexture(GL_TEXTURE_2D, frame->uyvy_tex);
check_error();
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, BUFFER_OFFSET(0));
}
uint32_t frames_written;
- HRESULT result = output->ScheduleAudioSamples(int_samples.get(), samples.size() / 2,
- pts, TIMEBASE, &frames_written);
+ HRESULT result;
+ if (is_master_card) {
+ result = output->ScheduleAudioSamples(int_samples.get(), samples.size() / 2,
+ pts, TIMEBASE, &frames_written);
+ } else {
+ result = output->WriteAudioSamplesSync(int_samples.get(), samples.size() / 2,
+ &frames_written);
+ }
if (result != S_OK) {
- fprintf(stderr, "ScheduleAudioSamples(pts=%" PRId64 ") failed (result=0x%08x)\n", pts, result);
+ fprintf(stderr, "write audio to DeckLink (pts=%" PRId64 ") failed (result=0x%08x)\n", pts, result);
} else {
- if (frames_written != samples.size() / 2) {
- fprintf(stderr, "ScheduleAudioSamples() returned short write (%u/%zu)\n", frames_written, samples.size() / 2);
+ // Non-master card is not really synchronized on audio at all, so we don't warn on it.
+ if (frames_written != samples.size() / 2 && is_master_card) {
+ fprintf(stderr, "write audio to DeckLink returned short write (%u/%zu)\n", frames_written, samples.size() / 2);
}
}
metric_decklink_output_scheduled_samples += samples.size() / 2;
if (!playback_started) {
if (output->EndAudioPreroll() != S_OK) {
fprintf(stderr, "Could not end audio preroll\n");
- exit(1); // TODO
+ abort(); // TODO
}
if (output->StartScheduledPlayback(base_pts, TIMEBASE, 1.0) != S_OK) {
fprintf(stderr, "Could not start playback\n");
- exit(1); // TODO
+ abort(); // TODO
}
playback_started = true;
}
}
// Prioritize 59.94 > 60 > 29.97. If none of those are found, then pick the highest one.
- for (const pair<int, int> &desired : vector<pair<int, int>>{ { 60000, 1001 }, { 60, 0 }, { 30000, 1001 } }) {
+ for (const pair<int, int> &desired : vector<pair<int, int>>{ { 60000, 1001 }, { 60, 1 }, { 30000, 1001 } }) {
for (const auto &it : video_modes) {
if (it.second.frame_rate_num * desired.second == desired.first * it.second.frame_rate_den) {
return it.first;
++metric_decklink_output_completed_frames_late;
break;
case bmdOutputFrameDropped:
- fprintf(stderr, "Output frame was dropped (pts=%" PRId64 "ld)\n", frame->pts);
+ fprintf(stderr, "Output frame was dropped (pts=%" PRId64 ")\n", frame->pts);
fprintf(stderr, "Consider increasing --output-buffer-frames if this persists.\n");
++metric_decklink_output_completed_frames_dropped;
break;
case bmdOutputFrameFlushed:
- fprintf(stderr, "Output frame was flushed (pts=%" PRId64 "ld)\n", frame->pts);
+ fprintf(stderr, "Output frame was flushed (pts=%" PRId64 ")\n", frame->pts);
++metric_decklink_output_completed_frames_flushed;
break;
default:
{
lock_guard<mutex> lock(frame_queue_mutex);
frame_freelist.push(unique_ptr<Frame>(frame));
- --num_frames_in_flight;
+ assert(scheduled_frames.count(frame));
+ scheduled_frames.erase(frame);
--metric_decklink_output_inflight_frames;
}
unique_ptr<Frame> frame(new Frame);
size_t stride;
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
stride = v210Converter::get_v210_stride(width);
GLint v210_width = stride / sizeof(uint32_t);
frame->uyvy_tex = resource_pool->create_2d_texture(GL_RGB10_A2, v210_width, height);
for ( ;; ) {
unique_ptr<Frame> frame;
{
- unique_lock<mutex> lock(frame_queue_mutex);
- frame_queues_changed.wait(lock, [this]{
- return should_quit.should_quit() || !pending_video_frames.empty();
- });
- if (should_quit.should_quit()) {
+ unique_lock<mutex> lock(frame_queue_mutex);
+ frame_queues_changed.wait(lock, [this]{
+ return should_quit.should_quit() || !pending_video_frames.empty();
+ });
+ if (should_quit.should_quit()) {
return;
}
frame = move(pending_video_frames.front());
pending_video_frames.pop();
- ++num_frames_in_flight;
- ++metric_decklink_output_inflight_frames;
}
for ( ;; ) {
check_error();
frame->fence.reset();
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, v210Converter::get_v210_stride(width) * height);
} else {
memcpy(frame->uyvy_ptr_local.get(), frame->uyvy_ptr, width * height * 2);
// Release any input frames we needed to render this frame.
frame->input_frames.clear();
- BMDTimeValue pts = frame->pts;
- BMDTimeValue duration = frame->duration;
- HRESULT res = output->ScheduleVideoFrame(frame.get(), pts, duration, TIMEBASE);
- if (res == S_OK) {
- frame.release(); // Owned by the driver now.
- } else {
- fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
-
+ if (is_master_card) {
+ BMDTimeValue pts = frame->pts;
+ BMDTimeValue duration = frame->duration;
+ HRESULT res = output->ScheduleVideoFrame(frame.get(), pts, duration, TIMEBASE);
lock_guard<mutex> lock(frame_queue_mutex);
+ if (res == S_OK) {
+ scheduled_frames.insert(frame.release()); // Owned by the driver now.
+ ++metric_decklink_output_inflight_frames;
+ } else {
+ fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
+
+ frame_freelist.push(move(frame));
+ }
+ } else {
+ HRESULT res = output->DisplayVideoFrameSync(frame.get());
+ if (res != S_OK) {
+ fprintf(stderr, "Could not schedule video frame! (error=0x%08x)\n", res);
+ }
frame_freelist.push(move(frame));
- --num_frames_in_flight;
- --metric_decklink_output_inflight_frames;
}
}
}
long DeckLinkOutput::Frame::GetRowBytes()
{
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
return v210Converter::get_v210_stride(global_flags.width);
} else {
return global_flags.width * 2;
BMDPixelFormat DeckLinkOutput::Frame::GetPixelFormat()
{
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
return bmdFormat10BitYUV;
} else {
return bmdFormat8BitYUV;