#include <common/memory.h>
#include <common/array.h>
#include <common/memshfl.h>
+#include <common/memcpy.h>
#include <common/utf.h>
#include <common/prec_timer.h>
#include <common/future.h>
#include <common/timer.h>
#include <common/param.h>
+#include <common/os/general_protection_fault.h>
+#include <common/scope_exit.h>
//#include <windows.h>
#include <core/frame/frame.h>
#include <core/consumer/frame_consumer.h>
#include <core/interaction/interaction_sink.h>
+#include <core/help/help_sink.h>
+#include <core/help/help_repository.h>
#include <boost/circular_buffer.hpp>
#include <boost/lexical_cast.hpp>
aspect_invalid,
};
- std::wstring name = L"ogl";
+ std::wstring name = L"Screen consumer";
int screen_index = 0;
screen::stretch stretch = screen::stretch::fill;
bool windowed = true;
aspect_ratio aspect = aspect_ratio::aspect_invalid;
bool vsync = true;
bool interactive = true;
+ bool borderless = false;
};
struct screen_consumer : boost::noncopyable
int square_height_ = format_desc_.square_height;
sf::Window window_;
+ tbb::atomic<bool> polling_event_;
+ std::int64_t pts_;
spl::shared_ptr<diagnostics::graph> graph_;
caspar::timer perf_timer_;
boost::thread thread_;
tbb::atomic<bool> is_running_;
+ tbb::atomic<int64_t> current_presentation_age_;
ffmpeg::filter filter_;
public:
: config_(config)
, format_desc_(format_desc)
, channel_index_(channel_index)
+ , pts_(0)
, sink_(sink)
, filter_([&]() -> ffmpeg::filter
{
sample_aspect_ratio,
AV_PIX_FMT_BGRA,
{ AV_PIX_FMT_BGRA },
- format_desc.field_mode == core::field_mode::progressive || !config.auto_deinterlace ? "" : "YADIF=1:-1");
+ format_desc.field_mode == core::field_mode::progressive || !config.auto_deinterlace ? "" : "format=pix_fmts=gbrp,YADIF=1:-1");
}())
{
if (format_desc_.format == core::video_format::ntsc && config_.aspect == configuration::aspect_ratio::aspect_4_3)
screen_width_ = square_width_;
screen_height_ = square_height_;
+ polling_event_ = false;
is_running_ = true;
+ current_presentation_age_ = 0;
thread_ = boost::thread([this]{run();});
}
void init()
{
- window_.create(sf::VideoMode(screen_width_, screen_height_, 32), u8(L"Screen consumer " + channel_and_format()), config_.windowed ? sf::Style::Resize | sf::Style::Close : sf::Style::Fullscreen);
+ auto window_style = config_.borderless
+ ? sf::Style::None
+ : (config_.windowed
+ ? sf::Style::Resize | sf::Style::Close
+ : sf::Style::Fullscreen);
+ window_.create(sf::VideoMode(screen_width_, screen_height_, 32), u8(print()), window_style);
window_.setMouseCursorVisible(config_.interactive);
window_.setPosition(sf::Vector2i(screen_x_, screen_y_));
window_.setSize(sf::Vector2u(screen_width_, screen_height_));
else
wglSwapIntervalEXT(0);
}*/
-
- CASPAR_LOG(info) << print() << " Successfully Initialized.";
}
void uninit()
void run()
{
+ ensure_gpf_handler_installed_for_thread("screen-consumer-thread");
+
try
{
init();
{
try
{
- sf::Event e;
- while(window_.pollEvent(e))
+ auto poll_event = [this](sf::Event& e)
+ {
+ polling_event_ = true;
+ CASPAR_SCOPE_EXIT
+ {
+ polling_event_ = false;
+ };
+ return window_.pollEvent(e);
+ };
+
+ sf::Event e;
+ while(poll_event(e))
{
if (e.type == sf::Event::Resized)
calculate_aspect();
}
}
- auto frame = core::const_frame::empty();
+ core::const_frame frame;
frame_buffer_.pop(frame);
render_and_draw_frame(frame);
window_.Display();*/
+ current_presentation_age_ = frame.get_age_millis();
graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
tick_timer_.restart();
}
spl::shared_ptr<AVFrame> get_av_frame()
{
- spl::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);
+ spl::shared_ptr<AVFrame> av_frame(av_frame_alloc(), [](AVFrame* p) { av_frame_free(&p); });
avcodec_get_frame_defaults(av_frame.get());
av_frame->linesize[0] = format_desc_.width*4;
av_frame->height = format_desc_.height;
av_frame->interlaced_frame = format_desc_.field_mode != core::field_mode::progressive;
av_frame->top_field_first = format_desc_.field_mode == core::field_mode::upper ? 1 : 0;
+ av_frame->pts = pts_++;
return av_frame;
}
- void render_and_draw_frame(core::const_frame frame)
+ void render_and_draw_frame(core::const_frame input_frame)
{
- if(static_cast<size_t>(frame.image_data().size()) != format_desc_.size)
+ if(static_cast<size_t>(input_frame.image_data().size()) != format_desc_.size)
return;
if(screen_width_ == 0 && screen_height_ == 0)
perf_timer_.restart();
auto av_frame = get_av_frame();
- av_frame->data[0] = const_cast<uint8_t*>(frame.image_data().begin());
+ av_frame->data[0] = const_cast<uint8_t*>(input_frame.image_data().begin());
filter_.push(av_frame);
- auto frames = filter_.poll_all();
+ auto frame = filter_.poll();
- if (frames.empty())
+ if (!frame)
return;
- if (frames.size() == 1)
+ if (!filter_.is_double_rate())
{
- render(frames[0]);
+ render(spl::make_shared_ptr(frame));
graph_->set_value("frame-time", perf_timer_.elapsed() * format_desc_.fps * 0.5);
wait_for_vblank_and_display(); // progressive frame
}
- else if (frames.size() == 2)
+ else
{
- render(frames[0]);
+ render(spl::make_shared_ptr(frame));
double perf_elapsed = perf_timer_.elapsed();
wait_for_vblank_and_display(); // field1
perf_timer_.restart();
- render(frames[1]);
+ frame = filter_.poll();
+ render(spl::make_shared_ptr(frame));
perf_elapsed += perf_timer_.elapsed();
graph_->set_value("frame-time", perf_elapsed * format_desc_.fps * 0.5);
});
}
else
- {
- tbb::parallel_for(tbb::blocked_range<int>(0, format_desc_.height), [&](const tbb::blocked_range<int>& r)
- {
- for(int n = r.begin(); n != r.end(); ++n)
- A_memcpy(ptr+n*format_desc_.width*4, av_frame->data[0]+n*av_frame->linesize[0], format_desc_.width*4);
- });
+ {
+ fast_memcpy(ptr, av_frame->data[0], format_desc_.size);
}
GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); // release the mapped buffer
std::future<bool> send(core::const_frame frame)
{
- if(!frame_buffer_.try_push(frame))
- graph_->set_tag("dropped-frame");
+ if(!frame_buffer_.try_push(frame) && !polling_event_)
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
return make_ready_future(is_running_.load());
}
std::wstring print() const
{
- return config_.name + channel_and_format();
+ return config_.name + L" " + channel_and_format();
}
void calculate_aspect()
// frame_consumer
- void initialize(const core::video_format_desc& format_desc, int channel_index) override
+ void initialize(const core::video_format_desc& format_desc, const core::audio_channel_layout&, int channel_index) override
{
consumer_.reset();
consumer_.reset(new screen_consumer(config_, format_desc, channel_index, sink_));
}
-
+
+ int64_t presentation_frame_age_millis() const override
+ {
+ return consumer_ ? static_cast<int64_t>(consumer_->current_presentation_age_) : 0;
+ }
+
std::future<bool> send(core::const_frame frame) override
{
return consumer_->send(frame);
info.add(L"key-only", config_.key_only);
info.add(L"windowed", config_.windowed);
info.add(L"auto-deinterlace", config_.auto_deinterlace);
+ info.add(L"vsync", config_.vsync);
return info;
}
}
};
+void describe_consumer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Displays the contents of a channel on screen using OpenGL.");
+ sink.syntax(
+ L"SCREEN "
+ L"{[screen_index:int]|1} "
+ L"{[fullscreen:FULLSCREEN]} "
+ L"{[borderless:BORDERLESS]} "
+ L"{[key_only:KEY_ONLY]} "
+ L"{[non_interactive:NON_INTERACTIVE]} "
+ L"{[no_auto_deinterlace:NO_AUTO_DEINTERLACE]} "
+ L"{NAME [name:string]}");
+ sink.para()->text(L"Displays the contents of a channel on screen using OpenGL.");
+ sink.definitions()
+ ->item(L"screen_index", L"Determines which screen the channel should be displayed on. Defaults to 1.")
+ ->item(L"fullscreen", L"If specified opens the window in fullscreen.")
+ ->item(L"borderless", L"Makes the window appear without any window decorations.")
+ ->item(L"key_only", L"Only displays the alpha channel of the video channel if specified.")
+ ->item(L"non_interactive", L"If specified does not send mouse input to producers on the video channel.")
+ ->item(L"no_auto_deinterlace", L"If the video mode of the channel is an interlaced mode, specifying this will turn of deinterlacing.")
+ ->item(L"name", L"Optionally specifies a name of the window to show.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> ADD 1 SCREEN", L"opens a screen consumer on the default screen.");
+ sink.example(L">> ADD 1 SCREEN 2", L"opens a screen consumer on the screen 2.");
+ sink.example(L">> ADD 1 SCREEN 1 FULLSCREEN", L"opens a screen consumer in fullscreen on screen 1.");
+ sink.example(L">> ADD 1 SCREEN 1 BORDERLESS", L"opens a screen consumer without borders/window decorations on screen 1.");
+}
+
spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params, core::interaction_sink* sink)
{
if (params.size() < 1 || !boost::iequals(params.at(0), L"SCREEN"))
config.key_only = contains_param(L"KEY_ONLY", params);
config.interactive = !contains_param(L"NON_INTERACTIVE", params);
config.auto_deinterlace = !contains_param(L"NO_AUTO_DEINTERLACE", params);
+ config.borderless = contains_param(L"BORDERLESS", params);
if (contains_param(L"NAME", params))
config.name = get_param(L"NAME", params);
config.auto_deinterlace = ptree.get(L"auto-deinterlace", config.auto_deinterlace);
config.vsync = ptree.get(L"vsync", config.vsync);
config.interactive = ptree.get(L"interactive", config.interactive);
+ config.borderless = ptree.get(L"borderless", config.borderless);
auto stretch_str = ptree.get(L"stretch", L"default");
if(stretch_str == L"uniform")