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);
fprintf(stderr, "Couldn't enable audio output\n");
abort();
}
- if (output->BeginAudioPreroll() != S_OK) {
- fprintf(stderr, "Couldn't begin audio preroll\n");
- abort();
+ 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]{
present_thread.join();
playback_initiated = false;
- output->StopScheduledPlayback(0, nullptr, 0);
+ if (is_master_card) {
+ output->StopScheduledPlayback(0, nullptr, 0);
+ }
output->DisableVideoOutput();
output->DisableAudioOutput();
}
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;
// 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);
- 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);
+ 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));
}
}
// where you get a freeze if querying an IDeckLinkInput interface
// on an already-started card.
bool set_device(IDeckLink *decklink, IDeckLinkInput *input_arg);
- void start_output(uint32_t mode, int64_t base_pts); // Mode comes from get_available_video_modes().
+ void start_output(uint32_t mode, int64_t base_pts, bool is_master_card); // Mode comes from get_available_video_modes().
void end_output();
void send_frame(GLuint y_tex, GLuint cbcr_tex, movit::YCbCrLumaCoefficients ycbcr_coefficients, const std::vector<RefCountedFrame> &input_frames, int64_t pts, int64_t duration);
std::unordered_set<Frame *> scheduled_frames; // Owned by the driver, so no unique_ptr. Under <frame_queue_mutex>.
std::condition_variable frame_queues_changed;
- bool playback_initiated = false, playback_started = false;
+ bool playback_initiated = false, playback_started = false, is_master_card = false;
int64_t base_pts, frame_duration;
BMDDisplayModeFlags current_mode_flags = 0;
bool last_frame_had_mode_mismatch = false;
OPTION_OUTPUT_YCBCR_COEFFICIENTS,
OPTION_OUTPUT_BUFFER_FRAMES,
OPTION_OUTPUT_SLOP_FRAMES,
+ OPTION_OUTPUT_CARD_UNSYNCHRONIZED,
OPTION_TIMECODE_STREAM,
OPTION_TIMECODE_STDOUT,
OPTION_QUICK_CUT_KEYS,
fprintf(stderr, " --output-slop-frames=NUM if more less than this number of frames behind for\n");
fprintf(stderr, " --output-card, try to submit anyway instead of\n");
fprintf(stderr, " dropping the frame (default 0.5)\n");
+ fprintf(stderr, " --output-card-unsynchronized if --output-card is in use, do _not_ use it as\n");
+ fprintf(stderr, " master clock (may give jittery output and audio breakups)\n");
fprintf(stderr, " --timecode-stream show timestamp and timecode in stream\n");
fprintf(stderr, " --timecode-stdout show timestamp and timecode on standard output\n");
fprintf(stderr, " --quick-cut-keys enable direct cutting by Q, W, E, ... keys\n");
{ "output-ycbcr-coefficients", required_argument, 0, OPTION_OUTPUT_YCBCR_COEFFICIENTS },
{ "output-buffer-frames", required_argument, 0, OPTION_OUTPUT_BUFFER_FRAMES },
{ "output-slop-frames", required_argument, 0, OPTION_OUTPUT_SLOP_FRAMES },
+ { "output-card-unsynchronized", no_argument, 0, OPTION_OUTPUT_CARD_UNSYNCHRONIZED },
{ "timecode-stream", no_argument, 0, OPTION_TIMECODE_STREAM },
{ "timecode-stdout", no_argument, 0, OPTION_TIMECODE_STDOUT },
{ "quick-cut-keys", no_argument, 0, OPTION_QUICK_CUT_KEYS },
case OPTION_OUTPUT_SLOP_FRAMES:
global_flags.output_slop_frames = atof(optarg);
break;
+ case OPTION_OUTPUT_CARD_UNSYNCHRONIZED:
+ global_flags.output_card_is_master = false;
+ break;
case OPTION_TIMECODE_STREAM:
global_flags.display_timecode_in_stream = true;
break;
int output_card = -1;
double output_buffer_frames = 6.0;
double output_slop_frames = 0.5;
+ bool output_card_is_master = true;
int max_input_queue_frames = 6;
int http_port = DEFAULT_HTTPD_PORT;
int srt_port = DEFAULT_SRT_PORT; // -1 for none.
// And a master clock selector.
QAction *master_clock_action = new QAction("Use as master clock", &menu);
master_clock_action->setCheckable(true);
- if (global_mixer->get_output_card_index() != -1) {
+ if (global_mixer->get_output_card_index() != -1 && global_mixer->get_output_card_is_master()) {
master_clock_action->setChecked(false);
master_clock_action->setEnabled(false);
} else if (global_mixer->get_master_clock() == signal_num) {
if (global_flags.enable_alsa_output) {
alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
}
+ output_card_is_master = global_flags.output_card_is_master;
if (global_flags.output_card != -1) {
desired_output_card_index = global_flags.output_card;
set_output_card_internal(global_flags.output_card);
card->jitter_history.clear();
card->capture->start_bm_capture();
desired_output_video_mode = output_video_mode = card->output->pick_video_mode(desired_output_video_mode);
- card->output->start_output(desired_output_video_mode, pts_int);
+ card->output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
}
output_card_index = card_index;
output_jitter_history.clear();
DeckLinkOutput *output = cards[output_card_index].output.get();
output->end_output();
desired_output_video_mode = output_video_mode = output->pick_video_mode(desired_output_video_mode);
- output->start_output(desired_output_video_mode, pts_int);
+ output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
}
{
bool master_card_is_output;
unsigned master_card_index;
- if (output_card_index != -1) {
+ if (output_card_index != -1 && output_card_is_master) {
master_card_is_output = true;
master_card_index = output_card_index;
} else {
bool Mixer::input_card_is_master_clock(unsigned card_index, unsigned master_card_index) const
{
- if (output_card_index != -1) {
+ if (output_card_index != -1 && output_card_is_master) {
// The output card (ie., cards[output_card_index].output) is the master clock,
// so no input card (ie., cards[card_index].capture) is.
return false;
desired_output_card_index = card_index;
}
+ bool get_output_card_is_master() const {
+ return output_card_is_master;
+ }
+
std::map<uint32_t, bmusb::VideoMode> get_available_output_video_modes() const;
uint32_t get_output_video_mode() const {
std::unique_ptr<movit::ResourcePool> resource_pool;
std::unique_ptr<Theme> theme;
std::atomic<unsigned> audio_source_channel{0};
- std::atomic<int> master_clock_channel{0}; // Gets overridden by <output_card_index> if set.
+ std::atomic<int> master_clock_channel{0}; // Gets overridden by <output_card_index> if output_card_is_master == true.
int output_card_index = -1; // -1 for none.
uint32_t output_video_mode = -1;
+ bool output_card_is_master = false; // Only relevant if output_card_index != -1.
// The mechanics of changing the output card and modes are so intricately connected
// with the work the mixer thread is doing. Thus, we don't change it directly,