This is for if you want just a monitor output without synchronizing
your entire stream chain to the output card (ie., you want to keep
some other camera as the master). Sound support is untested, and is
probably going to crackle a fair bit.
There's no GUI support for changing this currently.
-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);
{
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);
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();
}
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 = thread([this]{
present_thread.join();
playback_initiated = false;
present_thread.join();
playback_initiated = false;
- output->StopScheduledPlayback(0, nullptr, 0);
+ if (is_master_card) {
+ output->StopScheduledPlayback(0, nullptr, 0);
+ }
output->DisableVideoOutput();
output->DisableAudioOutput();
output->DisableVideoOutput();
output->DisableAudioOutput();
}
uint32_t frames_written;
}
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);
+ }
- 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);
- 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;
}
}
metric_decklink_output_scheduled_samples += samples.size() / 2;
// Release any input frames we needed to render this frame.
frame->input_frames.clear();
// 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));
}
}
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);
// 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);
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;
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;
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_YCBCR_COEFFICIENTS,
OPTION_OUTPUT_BUFFER_FRAMES,
OPTION_OUTPUT_SLOP_FRAMES,
+ OPTION_OUTPUT_CARD_UNSYNCHRONIZED,
OPTION_TIMECODE_STREAM,
OPTION_TIMECODE_STDOUT,
OPTION_QUICK_CUT_KEYS,
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-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");
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-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 },
{ "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_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;
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;
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.
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);
// 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) {
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));
}
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);
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->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();
}
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);
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;
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 {
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
{
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;
// 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;
}
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::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::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;
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,
// 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,