]> git.sesse.net Git - casparcg/blobdiff - modules/screen/consumer/screen_consumer.cpp
[ffmpeg] Remove redundant av_frame_alloc()/av_frame_free() RAII pairs all over the...
[casparcg] / modules / screen / consumer / screen_consumer.cpp
index fc5a15763b176c0dbc730194af7882f2f6c93b4c..d40429d2492d0e6ab0ab3dcdfc23c87d31778ce2 100644 (file)
 #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 <ffmpeg/producer/filter/filter.h>
+#include <ffmpeg/producer/util/util.h>
 
 #include <core/video_format.h>
 #include <core/frame/frame.h>
 #include <core/consumer/frame_consumer.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/timer.hpp>
 #include <boost/circular_buffer.hpp>
 #include <boost/lexical_cast.hpp>
-#include <boost/foreach.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
 
 #include <tbb/atomic.h>
 #include <tbb/concurrent_queue.h>
 #include <tbb/parallel_for.h>
 
-#include <boost/assign.hpp>
-
 #include <asmlib.h>
 
 #include <algorithm>
@@ -63,7 +70,7 @@
 #pragma warning (push)
 #pragma warning (disable : 4244)
 #endif
-extern "C" 
+extern "C"
 {
        #define __STDC_CONSTANT_MACROS
        #define __STDC_LIMIT_MACROS
@@ -75,8 +82,8 @@ extern "C"
 #endif
 
 namespace caspar { namespace screen {
-               
-enum stretch
+
+enum class stretch
 {
        none,
        uniform,
@@ -86,125 +93,140 @@ enum stretch
 
 struct configuration
 {
-       enum aspect_ratio
+       enum class aspect_ratio
        {
                aspect_4_3 = 0,
                aspect_16_9,
                aspect_invalid,
        };
-               
-       std::wstring    name;
-       int                             screen_index;
-       stretch                 stretch;
-       bool                    windowed;
-       bool                    auto_deinterlace;
-       bool                    key_only;
-       aspect_ratio    aspect; 
-       bool                    vsync;
-       bool                    interactive;
-
-       configuration()
-               : name(L"ogl")
-               , screen_index(0)
-               , stretch(fill)
-               , windowed(true)
-               , auto_deinterlace(true)
-               , key_only(false)
-               , aspect(aspect_invalid)
-               , vsync(true)
-               , interactive(true)
-       {
-       }
+
+       std::wstring    name                            = L"Screen consumer";
+       int                             screen_index            = 0;
+       screen::stretch stretch                         = screen::stretch::fill;
+       bool                    windowed                        = true;
+       bool                    auto_deinterlace        = true;
+       bool                    key_only                        = false;
+       aspect_ratio    aspect                          = aspect_ratio::aspect_invalid;
+       bool                    vsync                           = false;
+       bool                    interactive                     = true;
+       bool                    borderless                      = false;
 };
 
 struct screen_consumer : boost::noncopyable
-{              
-       const configuration                                     config_;
-       core::video_format_desc                         format_desc_;
-       int                                                                     channel_index_;
-
-       GLuint                                                          texture_;
-       std::vector<GLuint>                                     pbos_;
-                       
-       float                                                           width_;
-       float                                                           height_;        
-       int                                                                     screen_x_;
-       int                                                                     screen_y_;
-       int                                                                     screen_width_;
-       int                                                                     screen_height_;
-       int                                                                     square_width_;
-       int                                                                     square_height_;                         
-       
-       sf::Window                                                      window_;
-       
-       spl::shared_ptr<diagnostics::graph>     graph_;
-       boost::timer                                            perf_timer_;
-       boost::timer                                            tick_timer_;
-
-       caspar::prec_timer                                      wait_timer_;
+{
+       const configuration                                                                     config_;
+       core::video_format_desc                                                         format_desc_;
+       int                                                                                                     channel_index_;
+
+       GLuint                                                                                          texture_                = 0;
+       std::vector<GLuint>                                                                     pbos_                   = std::vector<GLuint> { 0, 0 };
+
+       float                                                                                           width_;
+       float                                                                                           height_;
+       int                                                                                                     screen_x_;
+       int                                                                                                     screen_y_;
+       int                                                                                                     screen_width_   = format_desc_.width;
+       int                                                                                                     screen_height_  = format_desc_.height;
+       int                                                                                                     square_width_   = format_desc_.square_width;
+       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_;
+       caspar::timer                                                                           tick_timer_;
+
+       caspar::prec_timer                                                                      wait_timer_;
 
        tbb::concurrent_bounded_queue<core::const_frame>        frame_buffer_;
        core::interaction_sink*                                                         sink_;
 
-       boost::thread                                           thread_;
-       tbb::atomic<bool>                                       is_running_;
-       
-       ffmpeg::filter                                          filter_;
+       boost::thread                                                                           thread_;
+       tbb::atomic<bool>                                                                       is_running_;
+       tbb::atomic<int64_t>                                                            current_presentation_age_;
+
+       ffmpeg::filter                                                                          filter_;
 public:
-       screen_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index, core::interaction_sink* sink) 
+       screen_consumer(
+                       const configuration& config,
+                       const core::video_format_desc& format_desc,
+                       int channel_index,
+                       core::interaction_sink* sink)
                : config_(config)
                , format_desc_(format_desc)
                , channel_index_(channel_index)
-               , texture_(0)
-               , pbos_(2, 0)   
-               , screen_width_(format_desc.width)
-               , screen_height_(format_desc.height)
-               , square_width_(format_desc.square_width)
-               , square_height_(format_desc.square_height)
+               , pts_(0)
                , sink_(sink)
-               , filter_(format_desc.field_mode == core::field_mode::progressive || !config.auto_deinterlace ? L"" : L"YADIF=1:-1", boost::assign::list_of(PIX_FMT_BGRA))
-       {               
-               if(format_desc_.format == core::video_format::ntsc && config_.aspect == configuration::aspect_4_3)
+               , filter_([&]() -> ffmpeg::filter
+               {
+                       const auto sample_aspect_ratio =
+                               boost::rational<int>(
+                                       format_desc.square_width,
+                                       format_desc.square_height) /
+                               boost::rational<int>(
+                                       format_desc.width,
+                                       format_desc.height);
+
+                       return ffmpeg::filter(
+                               format_desc.width,
+                               format_desc.height,
+                               boost::rational<int>(format_desc.duration, format_desc.time_scale),
+                               boost::rational<int>(format_desc.time_scale, format_desc.duration),
+                               sample_aspect_ratio,
+                               AV_PIX_FMT_BGRA,
+                               { AV_PIX_FMT_BGRA },
+                               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)
                {
                        // Use default values which are 4:3.
                }
                else
                {
-                       if(config_.aspect == configuration::aspect_16_9)
+                       if (config_.aspect == configuration::aspect_ratio::aspect_16_9)
                                square_width_ = (format_desc.height*16)/9;
-                       else if(config_.aspect == configuration::aspect_4_3)
+                       else if (config_.aspect == configuration::aspect_ratio::aspect_4_3)
                                square_width_ = (format_desc.height*4)/3;
                }
 
                frame_buffer_.set_capacity(1);
-               
-               graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
+
+               graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
                graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
                graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
                graph_->set_text(print());
                diagnostics::register_graph(graph_);
-                                                                       
-               DISPLAY_DEVICE d_device = {sizeof(d_device), 0};                        
+
+               /*DISPLAY_DEVICE d_device = {sizeof(d_device), 0};
                std::vector<DISPLAY_DEVICE> displayDevices;
                for(int n = 0; EnumDisplayDevices(NULL, n, &d_device, NULL); ++n)
                        displayDevices.push_back(d_device);
 
                if(config_.screen_index >= displayDevices.size())
                        CASPAR_LOG(warning) << print() << L" Invalid screen-index: " << config_.screen_index;
-               
+
                DEVMODE devmode = {};
                if(!EnumDisplaySettings(displayDevices[config_.screen_index].DeviceName, ENUM_CURRENT_SETTINGS, &devmode))
                        CASPAR_LOG(warning) << print() << L" Could not find display settings for screen-index: " << config_.screen_index;
-               
+
                screen_x_               = devmode.dmPosition.x;
                screen_y_               = devmode.dmPosition.y;
                screen_width_   = config_.windowed ? square_width_ : devmode.dmPelsWidth;
-               screen_height_  = config_.windowed ? square_height_ : devmode.dmPelsHeight;
-               
+               screen_height_  = config_.windowed ? square_height_ : devmode.dmPelsHeight;*/
+               screen_x_               = 0;
+               screen_y_               = 0;
+               screen_width_   = square_width_;
+               screen_height_  = square_height_;
+
+               polling_event_ = false;
                is_running_ = true;
+               current_presentation_age_ = 0;
                thread_ = boost::thread([this]{run();});
        }
-       
+
        ~screen_consumer()
        {
                is_running_ = false;
@@ -214,12 +236,17 @@ public:
 
        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);
-               window_.ShowMouseCursor(config_.interactive);
-               window_.SetPosition(screen_x_, screen_y_);
-               window_.SetSize(screen_width_, screen_height_);
-               window_.SetActive();
-               
+               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_));
+               window_.setActive();
+
                if(!GLEW_VERSION_2_1 && glewInit() != GLEW_OK)
                        CASPAR_THROW_EXCEPTION(gl::ogl_exception() << msg_info("Failed to initialize GLEW."));
 
@@ -227,13 +254,13 @@ public:
                        CASPAR_THROW_EXCEPTION(not_supported() << msg_info("Missing OpenGL 2.1 support."));
 
                GL(glEnable(GL_TEXTURE_2D));
-               GL(glDisable(GL_DEPTH_TEST));           
+               GL(glDisable(GL_DEPTH_TEST));
                GL(glClearColor(0.0, 0.0, 0.0, 0.0));
                GL(glViewport(0, 0, format_desc_.width, format_desc_.height));
                GL(glLoadIdentity());
-                               
+
                calculate_aspect();
-                       
+
                GL(glGenTextures(1, &texture_));
                GL(glBindTexture(GL_TEXTURE_2D, texture_));
                GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
@@ -242,36 +269,29 @@ public:
                GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
                GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, format_desc_.width, format_desc_.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0));
                GL(glBindTexture(GL_TEXTURE_2D, 0));
-                                       
+
                GL(glGenBuffers(2, pbos_.data()));
-                       
+
                glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[0]);
                glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
                glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[1]);
                glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
                glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
-               
-               auto wglSwapIntervalEXT = reinterpret_cast<void(APIENTRY*)(int)>(wglGetProcAddress("wglSwapIntervalEXT"));
-               if(wglSwapIntervalEXT)
+
+               window_.setVerticalSyncEnabled(config_.vsync);
+
+               if (config_.vsync)
                {
-                       if(config_.vsync)
-                       {
-                               wglSwapIntervalEXT(1);
-                               CASPAR_LOG(info) << print() << " Enabled vsync.";
-                       }
-                       else
-                               wglSwapIntervalEXT(0);
+                       CASPAR_LOG(info) << print() << " Enabled vsync.";
                }
-
-               CASPAR_LOG(info) << print() << " Successfully Initialized.";
        }
 
        void uninit()
-       {               
+       {
                if(texture_)
                        glDeleteTextures(1, &texture_);
 
-               BOOST_FOREACH(auto& pbo, pbos_)
+               for (auto& pbo : pbos_)
                {
                        if(pbo)
                                glDeleteBuffers(1, &pbo);
@@ -280,62 +300,85 @@ public:
 
        void run()
        {
+               ensure_gpf_handler_installed_for_thread("screen-consumer-thread");
+
                try
                {
                        init();
 
                        while(is_running_)
-                       {                       
+                       {
                                try
                                {
-                                       sf::Event e;            
-                                       while(window_.GetEvent(e))
+                                       auto poll_event = [this](sf::Event& e)
                                        {
-                                               if (e.Type == sf::Event::Resized)
+                                               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();
-                                               else if (e.Type == sf::Event::Closed)
+                                               else if (e.type == sf::Event::Closed)
                                                        is_running_ = false;
                                                else if (config_.interactive && sink_)
                                                {
-                                                       switch (e.Type)
+                                                       switch (e.type)
                                                        {
                                                        case sf::Event::MouseMoved:
                                                                {
-                                                                       auto& mouse_move = e.MouseMove;
+                                                                       auto& mouse_move = e.mouseMove;
                                                                        sink_->on_interaction(spl::make_shared<core::mouse_move_event>(
                                                                                        1,
-                                                                                       static_cast<double>(mouse_move.X) / screen_width_,
-                                                                                       static_cast<double>(mouse_move.Y) / screen_height_));
+                                                                                       static_cast<double>(mouse_move.x) / screen_width_,
+                                                                                       static_cast<double>(mouse_move.y) / screen_height_));
                                                                }
                                                                break;
                                                        case sf::Event::MouseButtonPressed:
                                                        case sf::Event::MouseButtonReleased:
                                                                {
-                                                                       auto& mouse_button = e.MouseButton;
+                                                                       auto& mouse_button = e.mouseButton;
                                                                        sink_->on_interaction(spl::make_shared<core::mouse_button_event>(
                                                                                        1,
-                                                                                       static_cast<double>(mouse_button.X) / screen_width_,
-                                                                                       static_cast<double>(mouse_button.Y) / screen_height_,
-                                                                                       static_cast<int>(mouse_button.Button),
-                                                                                       e.Type == sf::Event::MouseButtonPressed));
+                                                                                       static_cast<double>(mouse_button.x) / screen_width_,
+                                                                                       static_cast<double>(mouse_button.y) / screen_height_,
+                                                                                       static_cast<int>(mouse_button.button),
+                                                                                       e.type == sf::Event::MouseButtonPressed));
+                                                               }
+                                                               break;
+                                                       case sf::Event::MouseWheelMoved:
+                                                               {
+                                                                       auto& wheel_moved = e.mouseWheel;
+                                                                       sink_->on_interaction(spl::make_shared<core::mouse_wheel_event>(
+                                                                                       1,
+                                                                                       static_cast<double>(wheel_moved.x) / screen_width_,
+                                                                                       static_cast<double>(wheel_moved.y) / screen_height_,
+                                                                                       wheel_moved.delta));
                                                                }
                                                                break;
                                                        }
                                                }
                                        }
-                       
-                                       auto frame = core::const_frame::empty();
+
+                                       core::const_frame frame;
                                        frame_buffer_.pop(frame);
 
                                        render_and_draw_frame(frame);
-                                       
+
                                        /*perf_timer_.restart();
                                        render(frame);
-                                       graph_->set_value("frame-time", perf_timer_.elapsed()*format_desc_.fps*0.5);    
+                                       graph_->set_value("frame-time", perf_timer_.elapsed()*format_desc_.fps*0.5);
 
                                        window_.Display();*/
 
-                                       graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);     
+                                       current_presentation_age_ = frame.get_age_millis();
+                                       graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);
                                        tick_timer_.restart();
                                }
                                catch(...)
@@ -366,60 +409,61 @@ public:
        void wait_for_vblank_and_display()
        {
                try_sleep_almost_until_vblank();
-               window_.Display();
+               window_.display();
                // Make sure that the next tick measures the duration from this point in time.
                wait_timer_.tick(0.0);
        }
 
        spl::shared_ptr<AVFrame> get_av_frame()
-       {               
-               spl::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);      
-               avcodec_get_frame_defaults(av_frame.get());
-                                               
-               av_frame->linesize[0]           = format_desc_.width*4;                 
+       {
+               auto av_frame = ffmpeg::create_frame();
+
+               av_frame->linesize[0]           = format_desc_.width*4;
                av_frame->format                        = PIX_FMT_BGRA;
                av_frame->width                         = format_desc_.width;
                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)
                        return;
-                                       
+
                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);
 
@@ -429,6 +473,7 @@ public:
 
        void render(spl::shared_ptr<AVFrame> av_frame)
        {
+               CASPAR_LOG_CALL(trace) << "screen_consumer::render() <- " << print();
                GL(glBindTexture(GL_TEXTURE_2D, texture_));
 
                GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[0]));
@@ -449,39 +494,35 @@ public:
                                });
                        }
                        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
                }
 
                GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
-                               
-               GL(glClear(GL_COLOR_BUFFER_BIT));                       
+
+               GL(glClear(GL_COLOR_BUFFER_BIT));
                glBegin(GL_QUADS);
                                glTexCoord2f(0.0f,        1.0f);        glVertex2f(-width_, -height_);
                                glTexCoord2f(1.0f,        1.0f);        glVertex2f( width_, -height_);
                                glTexCoord2f(1.0f,        0.0f);        glVertex2f( width_,  height_);
                                glTexCoord2f(0.0f,        0.0f);        glVertex2f(-width_,  height_);
                glEnd();
-               
+
                GL(glBindTexture(GL_TEXTURE_2D, 0));
 
                std::rotate(pbos_.begin(), pbos_.begin() + 1, pbos_.end());
        }
 
 
-       boost::unique_future<bool> send(core::const_frame frame)
+       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 wrap_as_future(is_running_.load());
+               return make_ready_future(is_running_.load());
        }
 
        std::wstring channel_and_format() const
@@ -490,32 +531,32 @@ public:
        }
 
        std::wstring print() const
-       {       
-               return config_.name + channel_and_format();
+       {
+               return config_.name + L" " + channel_and_format();
        }
-       
+
        void calculate_aspect()
        {
                if(config_.windowed)
                {
-                       screen_height_ = window_.GetHeight();
-                       screen_width_ = window_.GetWidth();
+                       screen_height_ = window_.getSize().y;
+                       screen_width_ = window_.getSize().x;
                }
-               
+
                GL(glViewport(0, 0, screen_width_, screen_height_));
 
                std::pair<float, float> target_ratio = None();
-               if(config_.stretch == fill)
+               if (config_.stretch == screen::stretch::fill)
                        target_ratio = Fill();
-               else if(config_.stretch == uniform)
+               else if (config_.stretch == screen::stretch::uniform)
                        target_ratio = Uniform();
-               else if(config_.stretch == uniform_to_fill)
+               else if (config_.stretch == screen::stretch::uniform_to_fill)
                        target_ratio = UniformToFill();
 
                width_ = target_ratio.first;
                height_ = target_ratio.second;
        }
-               
+
        std::pair<float, float> None()
        {
                float width = static_cast<float>(square_width_)/static_cast<float>(screen_width_);
@@ -554,9 +595,10 @@ public:
 
 struct screen_consumer_proxy : public core::frame_consumer
 {
-       const configuration config_;
-       std::unique_ptr<screen_consumer> consumer_;
-       core::interaction_sink* sink_;
+       core::monitor::subject                          monitor_subject_;
+       const configuration                                     config_;
+       std::unique_ptr<screen_consumer>        consumer_;
+       core::interaction_sink*                         sink_;
 
 public:
 
@@ -565,20 +607,25 @@ public:
                , sink_(sink)
        {
        }
-       
+
        // 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_));
        }
-       
-       boost::unique_future<bool> send(core::const_frame frame) override
+
+       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);
        }
-       
+
        std::wstring print() const override
        {
                return consumer_ ? consumer_->print() : L"[screen_consumer]";
@@ -596,6 +643,7 @@ public:
                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;
        }
 
@@ -603,7 +651,7 @@ public:
        {
                return false;
        }
-       
+
        int buffer_depth() const override
        {
                return 1;
@@ -614,55 +662,87 @@ public:
                return 600 + (config_.key_only ? 10 : 0) + config_.screen_index;
        }
 
-       monitor::source& monitor_output()
+       core::monitor::subject& monitor_output()
        {
-               static monitor::subject monitor_subject(""); return monitor_subject;
+               return monitor_subject_;
        }
-};     
+};
+
+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)
+spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params, core::interaction_sink* sink)
 {
-       if(params.size() < 1 || params[0] != L"SCREEN")
+       if (params.size() < 1 || !boost::iequals(params.at(0), L"SCREEN"))
                return core::frame_consumer::empty();
-       
+
        configuration config;
-               
-       if(params.size() > 1)
-               config.screen_index = boost::lexical_cast<int>(params[1]);
-               
-       config.key_only = std::find(params.begin(), params.end(), L"WINDOWED") != params.end();
-       config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
-
-       auto name_it    = std::find(params.begin(), params.end(), L"NAME");
-       if(name_it != params.end() && ++name_it != params.end())
-               config.name = *name_it;
-
-       return spl::make_shared<screen_consumer_proxy>(config, nullptr);
+
+       if (params.size() > 1)
+               config.screen_index = boost::lexical_cast<int>(params.at(1));
+
+       config.windowed                 = !contains_param(L"FULLSCREEN", params);
+       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);
+
+       return spl::make_shared<screen_consumer_proxy>(config, sink);
 }
 
-spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree, core::interaction_sink* sink) 
+spl::shared_ptr<core::frame_consumer> create_preconfigured_consumer(const boost::property_tree::wptree& ptree, core::interaction_sink* sink)
 {
        configuration config;
-       config.name                             = ptree.get(L"name",     config.name);
-       config.screen_index             = ptree.get(L"device",   config.screen_index+1)-1;
-       config.windowed                 = ptree.get(L"windowed", config.windowed);
-       config.key_only                 = ptree.get(L"key-only", config.key_only);
-       config.auto_deinterlace = ptree.get(L"auto-deinterlace", config.auto_deinterlace);
-       config.vsync                    = ptree.get(L"vsync", config.vsync);
-       
+       config.name                             = ptree.get(L"name",                            config.name);
+       config.screen_index             = ptree.get(L"device",                          config.screen_index + 1) - 1;
+       config.windowed                 = ptree.get(L"windowed",                        config.windowed);
+       config.key_only                 = ptree.get(L"key-only",                        config.key_only);
+       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")
-               config.stretch = stretch::uniform;
+               config.stretch = screen::stretch::uniform;
        else if(stretch_str == L"uniform_to_fill")
-               config.stretch = stretch::uniform_to_fill;
+               config.stretch = screen::stretch::uniform_to_fill;
 
        auto aspect_str = ptree.get(L"aspect-ratio", L"default");
        if(aspect_str == L"16:9")
-               config.aspect = configuration::aspect_16_9;
+               config.aspect = configuration::aspect_ratio::aspect_16_9;
        else if(aspect_str == L"4:3")
-               config.aspect = configuration::aspect_4_3;
-       
+               config.aspect = configuration::aspect_ratio::aspect_4_3;
+
        return spl::make_shared<screen_consumer_proxy>(config, sink);
 }
 
-}}
\ No newline at end of file
+}}