]> git.sesse.net Git - casparcg/blob - modules/screen/consumer/screen_consumer.cpp
Merge branch '2.1.0' of https://github.com/CasparCG/Server into 2.1.0
[casparcg] / modules / screen / consumer / screen_consumer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "screen_consumer.h"
23
24 #include <GL/glew.h>
25 #include <SFML/Window.hpp>
26
27 #include <common/diagnostics/graph.h>
28 #include <common/gl/gl_check.h>
29 #include <common/log.h>
30 #include <common/memory.h>
31 #include <common/array.h>
32 #include <common/memshfl.h>
33 #include <common/utf.h>
34 #include <common/prec_timer.h>
35 #include <common/future.h>
36
37 #include <ffmpeg/producer/filter/filter.h>
38
39 #include <core/video_format.h>
40 #include <core/frame/frame.h>
41 #include <core/consumer/frame_consumer.h>
42 #include <core/consumer/frame_consumer.h>
43
44 #include <boost/timer.hpp>
45 #include <boost/circular_buffer.hpp>
46 #include <boost/lexical_cast.hpp>
47 #include <boost/foreach.hpp>
48 #include <boost/property_tree/ptree.hpp>
49 #include <boost/thread.hpp>
50
51 #include <tbb/atomic.h>
52 #include <tbb/concurrent_queue.h>
53 #include <tbb/parallel_for.h>
54
55 #include <boost/assign.hpp>
56
57 #include <asmlib.h>
58
59 #include <algorithm>
60 #include <vector>
61
62 #if defined(_MSC_VER)
63 #pragma warning (push)
64 #pragma warning (disable : 4244)
65 #endif
66 extern "C" 
67 {
68         #define __STDC_CONSTANT_MACROS
69         #define __STDC_LIMIT_MACROS
70         #include <libavcodec/avcodec.h>
71         #include <libavutil/imgutils.h>
72 }
73 #if defined(_MSC_VER)
74 #pragma warning (pop)
75 #endif
76
77 namespace caspar { namespace screen {
78                 
79 enum stretch
80 {
81         none,
82         uniform,
83         fill,
84         uniform_to_fill
85 };
86
87 struct configuration
88 {
89         enum aspect_ratio
90         {
91                 aspect_4_3 = 0,
92                 aspect_16_9,
93                 aspect_invalid,
94         };
95                 
96         std::wstring    name;
97         int                             screen_index;
98         stretch                 stretch;
99         bool                    windowed;
100         bool                    auto_deinterlace;
101         bool                    key_only;
102         aspect_ratio    aspect; 
103         bool                    vsync;
104         bool                    interactive;
105
106         configuration()
107                 : name(L"ogl")
108                 , screen_index(0)
109                 , stretch(fill)
110                 , windowed(true)
111                 , auto_deinterlace(true)
112                 , key_only(false)
113                 , aspect(aspect_invalid)
114                 , vsync(true)
115                 , interactive(true)
116         {
117         }
118 };
119
120 struct screen_consumer : boost::noncopyable
121 {               
122         const configuration                                     config_;
123         core::video_format_desc                         format_desc_;
124         int                                                                     channel_index_;
125
126         GLuint                                                          texture_;
127         std::vector<GLuint>                                     pbos_;
128                         
129         float                                                           width_;
130         float                                                           height_;        
131         int                                                                     screen_x_;
132         int                                                                     screen_y_;
133         int                                                                     screen_width_;
134         int                                                                     screen_height_;
135         int                                                                     square_width_;
136         int                                                                     square_height_;                         
137         
138         sf::Window                                                      window_;
139         
140         spl::shared_ptr<diagnostics::graph>     graph_;
141         boost::timer                                            perf_timer_;
142         boost::timer                                            tick_timer_;
143
144         caspar::prec_timer                                      wait_timer_;
145
146         tbb::concurrent_bounded_queue<core::const_frame>        frame_buffer_;
147         core::interaction_sink*                                                         sink_;
148
149         boost::thread                                           thread_;
150         tbb::atomic<bool>                                       is_running_;
151         
152         ffmpeg::filter                                          filter_;
153 public:
154         screen_consumer(const configuration& config, const core::video_format_desc& format_desc, int channel_index, core::interaction_sink* sink) 
155                 : config_(config)
156                 , format_desc_(format_desc)
157                 , channel_index_(channel_index)
158                 , texture_(0)
159                 , pbos_(2, 0)   
160                 , screen_width_(format_desc.width)
161                 , screen_height_(format_desc.height)
162                 , square_width_(format_desc.square_width)
163                 , square_height_(format_desc.square_height)
164                 , sink_(sink)
165                 , filter_(format_desc.field_mode == core::field_mode::progressive || !config.auto_deinterlace ? L"" : L"YADIF=1:-1", boost::assign::list_of(PIX_FMT_BGRA))
166         {               
167                 if(format_desc_.format == core::video_format::ntsc && config_.aspect == configuration::aspect_4_3)
168                 {
169                         // Use default values which are 4:3.
170                 }
171                 else
172                 {
173                         if(config_.aspect == configuration::aspect_16_9)
174                                 square_width_ = (format_desc.height*16)/9;
175                         else if(config_.aspect == configuration::aspect_4_3)
176                                 square_width_ = (format_desc.height*4)/3;
177                 }
178
179                 frame_buffer_.set_capacity(1);
180                 
181                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));   
182                 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
183                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
184                 graph_->set_text(print());
185                 diagnostics::register_graph(graph_);
186                                                                         
187                 DISPLAY_DEVICE d_device = {sizeof(d_device), 0};                        
188                 std::vector<DISPLAY_DEVICE> displayDevices;
189                 for(int n = 0; EnumDisplayDevices(NULL, n, &d_device, NULL); ++n)
190                         displayDevices.push_back(d_device);
191
192                 if(config_.screen_index >= displayDevices.size())
193                         CASPAR_LOG(warning) << print() << L" Invalid screen-index: " << config_.screen_index;
194                 
195                 DEVMODE devmode = {};
196                 if(!EnumDisplaySettings(displayDevices[config_.screen_index].DeviceName, ENUM_CURRENT_SETTINGS, &devmode))
197                         CASPAR_LOG(warning) << print() << L" Could not find display settings for screen-index: " << config_.screen_index;
198                 
199                 screen_x_               = devmode.dmPosition.x;
200                 screen_y_               = devmode.dmPosition.y;
201                 screen_width_   = config_.windowed ? square_width_ : devmode.dmPelsWidth;
202                 screen_height_  = config_.windowed ? square_height_ : devmode.dmPelsHeight;
203                 
204                 is_running_ = true;
205                 thread_ = boost::thread([this]{run();});
206         }
207         
208         ~screen_consumer()
209         {
210                 is_running_ = false;
211                 frame_buffer_.try_push(core::const_frame::empty());
212                 thread_.join();
213         }
214
215         void init()
216         {
217                 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);
218                 window_.ShowMouseCursor(config_.interactive);
219                 window_.SetPosition(screen_x_, screen_y_);
220                 window_.SetSize(screen_width_, screen_height_);
221                 window_.SetActive();
222                 
223                 if(!GLEW_VERSION_2_1 && glewInit() != GLEW_OK)
224                         CASPAR_THROW_EXCEPTION(gl::ogl_exception() << msg_info("Failed to initialize GLEW."));
225
226                 if(!GLEW_VERSION_2_1)
227                         CASPAR_THROW_EXCEPTION(not_supported() << msg_info("Missing OpenGL 2.1 support."));
228
229                 GL(glEnable(GL_TEXTURE_2D));
230                 GL(glDisable(GL_DEPTH_TEST));           
231                 GL(glClearColor(0.0, 0.0, 0.0, 0.0));
232                 GL(glViewport(0, 0, format_desc_.width, format_desc_.height));
233                 GL(glLoadIdentity());
234                                 
235                 calculate_aspect();
236                         
237                 GL(glGenTextures(1, &texture_));
238                 GL(glBindTexture(GL_TEXTURE_2D, texture_));
239                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
240                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
241                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
242                 GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
243                 GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, format_desc_.width, format_desc_.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0));
244                 GL(glBindTexture(GL_TEXTURE_2D, 0));
245                                         
246                 GL(glGenBuffers(2, pbos_.data()));
247                         
248                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[0]);
249                 glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
250                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, pbos_[1]);
251                 glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, format_desc_.size, 0, GL_STREAM_DRAW_ARB);
252                 glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
253                 
254                 auto wglSwapIntervalEXT = reinterpret_cast<void(APIENTRY*)(int)>(wglGetProcAddress("wglSwapIntervalEXT"));
255                 if(wglSwapIntervalEXT)
256                 {
257                         if(config_.vsync)
258                         {
259                                 wglSwapIntervalEXT(1);
260                                 CASPAR_LOG(info) << print() << " Enabled vsync.";
261                         }
262                         else
263                                 wglSwapIntervalEXT(0);
264                 }
265
266                 CASPAR_LOG(info) << print() << " Successfully Initialized.";
267         }
268
269         void uninit()
270         {               
271                 if(texture_)
272                         glDeleteTextures(1, &texture_);
273
274                 BOOST_FOREACH(auto& pbo, pbos_)
275                 {
276                         if(pbo)
277                                 glDeleteBuffers(1, &pbo);
278                 }
279         }
280
281         void run()
282         {
283                 try
284                 {
285                         init();
286
287                         while(is_running_)
288                         {                       
289                                 try
290                                 {
291                                         sf::Event e;            
292                                         while(window_.GetEvent(e))
293                                         {
294                                                 if (e.Type == sf::Event::Resized)
295                                                         calculate_aspect();
296                                                 else if (e.Type == sf::Event::Closed)
297                                                         is_running_ = false;
298                                                 else if (config_.interactive && sink_)
299                                                 {
300                                                         switch (e.Type)
301                                                         {
302                                                         case sf::Event::MouseMoved:
303                                                                 {
304                                                                         auto& mouse_move = e.MouseMove;
305                                                                         sink_->on_interaction(spl::make_shared<core::mouse_move_event>(
306                                                                                         1,
307                                                                                         static_cast<double>(mouse_move.X) / screen_width_,
308                                                                                         static_cast<double>(mouse_move.Y) / screen_height_));
309                                                                 }
310                                                                 break;
311                                                         case sf::Event::MouseButtonPressed:
312                                                         case sf::Event::MouseButtonReleased:
313                                                                 {
314                                                                         auto& mouse_button = e.MouseButton;
315                                                                         sink_->on_interaction(spl::make_shared<core::mouse_button_event>(
316                                                                                         1,
317                                                                                         static_cast<double>(mouse_button.X) / screen_width_,
318                                                                                         static_cast<double>(mouse_button.Y) / screen_height_,
319                                                                                         static_cast<int>(mouse_button.Button),
320                                                                                         e.Type == sf::Event::MouseButtonPressed));
321                                                                 }
322                                                                 break;
323                                                         }
324                                                 }
325                                         }
326                         
327                                         auto frame = core::const_frame::empty();
328                                         frame_buffer_.pop(frame);
329
330                                         render_and_draw_frame(frame);
331                                         
332                                         /*perf_timer_.restart();
333                                         render(frame);
334                                         graph_->set_value("frame-time", perf_timer_.elapsed()*format_desc_.fps*0.5);    
335
336                                         window_.Display();*/
337
338                                         graph_->set_value("tick-time", tick_timer_.elapsed()*format_desc_.fps*0.5);     
339                                         tick_timer_.restart();
340                                 }
341                                 catch(...)
342                                 {
343                                         CASPAR_LOG_CURRENT_EXCEPTION();
344                                         is_running_ = false;
345                                 }
346                         }
347
348                         uninit();
349                 }
350                 catch(...)
351                 {
352                         CASPAR_LOG_CURRENT_EXCEPTION();
353                 }
354         }
355
356         void try_sleep_almost_until_vblank()
357         {
358                 static const double THRESHOLD = 0.003;
359                 double threshold = config_.vsync ? THRESHOLD : 0.0;
360
361                 auto frame_time = 1.0 / (format_desc_.fps * format_desc_.field_count);
362
363                 wait_timer_.tick(frame_time - threshold);
364         }
365
366         void wait_for_vblank_and_display()
367         {
368                 try_sleep_almost_until_vblank();
369                 window_.Display();
370                 // Make sure that the next tick measures the duration from this point in time.
371                 wait_timer_.tick(0.0);
372         }
373
374         spl::shared_ptr<AVFrame> get_av_frame()
375         {               
376                 spl::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);      
377                 avcodec_get_frame_defaults(av_frame.get());
378                                                 
379                 av_frame->linesize[0]           = format_desc_.width*4;                 
380                 av_frame->format                        = PIX_FMT_BGRA;
381                 av_frame->width                         = format_desc_.width;
382                 av_frame->height                        = format_desc_.height;
383                 av_frame->interlaced_frame      = format_desc_.field_mode != core::field_mode::progressive;
384                 av_frame->top_field_first       = format_desc_.field_mode == core::field_mode::upper ? 1 : 0;
385
386                 return av_frame;
387         }
388
389         void render_and_draw_frame(core::const_frame frame)
390         {
391                 if(static_cast<size_t>(frame.image_data().size()) != format_desc_.size)
392                         return;
393
394                 if(screen_width_ == 0 && screen_height_ == 0)
395                         return;
396                                         
397                 perf_timer_.restart();
398                 auto av_frame = get_av_frame();
399                 av_frame->data[0] = const_cast<uint8_t*>(frame.image_data().begin());
400
401                 filter_.push(av_frame);
402                 auto frames = filter_.poll_all();
403
404                 if (frames.empty())
405                         return;
406
407                 if (frames.size() == 1)
408                 {
409                         render(frames[0]);
410                         graph_->set_value("frame-time", perf_timer_.elapsed() * format_desc_.fps * 0.5);
411
412                         wait_for_vblank_and_display(); // progressive frame
413                 }
414                 else if (frames.size() == 2)
415                 {
416                         render(frames[0]);
417                         double perf_elapsed = perf_timer_.elapsed();
418
419                         wait_for_vblank_and_display(); // field1
420
421                         perf_timer_.restart();
422                         render(frames[1]);
423                         perf_elapsed += perf_timer_.elapsed();
424                         graph_->set_value("frame-time", perf_elapsed * format_desc_.fps * 0.5);
425
426                         wait_for_vblank_and_display(); // field2
427                 }
428         }
429
430         void render(spl::shared_ptr<AVFrame> av_frame)
431         {
432                 GL(glBindTexture(GL_TEXTURE_2D, texture_));
433
434                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[0]));
435                 GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, format_desc_.width, format_desc_.height, GL_BGRA, GL_UNSIGNED_BYTE, 0));
436
437                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbos_[1]));
438                 GL(glBufferData(GL_PIXEL_UNPACK_BUFFER, format_desc_.size, 0, GL_STREAM_DRAW));
439
440                 auto ptr = reinterpret_cast<char*>(GL2(glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)));
441                 if(ptr)
442                 {
443                         if(config_.key_only)
444                         {
445                                 tbb::parallel_for(tbb::blocked_range<int>(0, format_desc_.height), [&](const tbb::blocked_range<int>& r)
446                                 {
447                                         for(int n = r.begin(); n != r.end(); ++n)
448                                                 aligned_memshfl(ptr+n*format_desc_.width*4, av_frame->data[0]+n*av_frame->linesize[0], format_desc_.width*4, 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);
449                                 });
450                         }
451                         else
452                         {       
453                                 tbb::parallel_for(tbb::blocked_range<int>(0, format_desc_.height), [&](const tbb::blocked_range<int>& r)
454                                 {
455                                         for(int n = r.begin(); n != r.end(); ++n)
456                                                 A_memcpy(ptr+n*format_desc_.width*4, av_frame->data[0]+n*av_frame->linesize[0], format_desc_.width*4);
457                                 });
458                         }
459                         
460                         GL(glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER)); // release the mapped buffer
461                 }
462
463                 GL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0));
464                                 
465                 GL(glClear(GL_COLOR_BUFFER_BIT));                       
466                 glBegin(GL_QUADS);
467                                 glTexCoord2f(0.0f,        1.0f);        glVertex2f(-width_, -height_);
468                                 glTexCoord2f(1.0f,        1.0f);        glVertex2f( width_, -height_);
469                                 glTexCoord2f(1.0f,        0.0f);        glVertex2f( width_,  height_);
470                                 glTexCoord2f(0.0f,        0.0f);        glVertex2f(-width_,  height_);
471                 glEnd();
472                 
473                 GL(glBindTexture(GL_TEXTURE_2D, 0));
474
475                 std::rotate(pbos_.begin(), pbos_.begin() + 1, pbos_.end());
476         }
477
478
479         boost::unique_future<bool> send(core::const_frame frame)
480         {
481                 if(!frame_buffer_.try_push(frame))
482                         graph_->set_tag("dropped-frame");
483
484                 return wrap_as_future(is_running_.load());
485         }
486
487         std::wstring channel_and_format() const
488         {
489                 return L"[" + boost::lexical_cast<std::wstring>(channel_index_) + L"|" + format_desc_.name + L"]";
490         }
491
492         std::wstring print() const
493         {       
494                 return config_.name + channel_and_format();
495         }
496         
497         void calculate_aspect()
498         {
499                 if(config_.windowed)
500                 {
501                         screen_height_ = window_.GetHeight();
502                         screen_width_ = window_.GetWidth();
503                 }
504                 
505                 GL(glViewport(0, 0, screen_width_, screen_height_));
506
507                 std::pair<float, float> target_ratio = None();
508                 if(config_.stretch == fill)
509                         target_ratio = Fill();
510                 else if(config_.stretch == uniform)
511                         target_ratio = Uniform();
512                 else if(config_.stretch == uniform_to_fill)
513                         target_ratio = UniformToFill();
514
515                 width_ = target_ratio.first;
516                 height_ = target_ratio.second;
517         }
518                 
519         std::pair<float, float> None()
520         {
521                 float width = static_cast<float>(square_width_)/static_cast<float>(screen_width_);
522                 float height = static_cast<float>(square_height_)/static_cast<float>(screen_height_);
523
524                 return std::make_pair(width, height);
525         }
526
527         std::pair<float, float> Uniform()
528         {
529                 float aspect = static_cast<float>(square_width_)/static_cast<float>(square_height_);
530                 float width = std::min(1.0f, static_cast<float>(screen_height_)*aspect/static_cast<float>(screen_width_));
531                 float height = static_cast<float>(screen_width_*width)/static_cast<float>(screen_height_*aspect);
532
533                 return std::make_pair(width, height);
534         }
535
536         std::pair<float, float> Fill()
537         {
538                 return std::make_pair(1.0f, 1.0f);
539         }
540
541         std::pair<float, float> UniformToFill()
542         {
543                 float wr = static_cast<float>(square_width_)/static_cast<float>(screen_width_);
544                 float hr = static_cast<float>(square_height_)/static_cast<float>(screen_height_);
545                 float r_inv = 1.0f/std::min(wr, hr);
546
547                 float width = wr*r_inv;
548                 float height = hr*r_inv;
549
550                 return std::make_pair(width, height);
551         }
552 };
553
554
555 struct screen_consumer_proxy : public core::frame_consumer
556 {
557         const configuration config_;
558         std::unique_ptr<screen_consumer> consumer_;
559         core::interaction_sink* sink_;
560
561 public:
562
563         screen_consumer_proxy(const configuration& config, core::interaction_sink* sink)
564                 : config_(config)
565                 , sink_(sink)
566         {
567         }
568         
569         // frame_consumer
570
571         void initialize(const core::video_format_desc& format_desc, int channel_index) override
572         {
573                 consumer_.reset();
574                 consumer_.reset(new screen_consumer(config_, format_desc, channel_index, sink_));
575         }
576         
577         boost::unique_future<bool> send(core::const_frame frame) override
578         {
579                 return consumer_->send(frame);
580         }
581         
582         std::wstring print() const override
583         {
584                 return consumer_ ? consumer_->print() : L"[screen_consumer]";
585         }
586
587         std::wstring name() const override
588         {
589                 return L"screen";
590         }
591
592         boost::property_tree::wptree info() const override
593         {
594                 boost::property_tree::wptree info;
595                 info.add(L"type", L"screen");
596                 info.add(L"key-only", config_.key_only);
597                 info.add(L"windowed", config_.windowed);
598                 info.add(L"auto-deinterlace", config_.auto_deinterlace);
599                 return info;
600         }
601
602         bool has_synchronization_clock() const override
603         {
604                 return false;
605         }
606         
607         int buffer_depth() const override
608         {
609                 return 1;
610         }
611
612         int index() const override
613         {
614                 return 600 + (config_.key_only ? 10 : 0) + config_.screen_index;
615         }
616
617         monitor::source& monitor_output()
618         {
619                 static monitor::subject monitor_subject(""); return monitor_subject;
620         }
621 };      
622
623 spl::shared_ptr<core::frame_consumer> create_consumer(const std::vector<std::wstring>& params)
624 {
625         if(params.size() < 1 || params[0] != L"SCREEN")
626                 return core::frame_consumer::empty();
627         
628         configuration config;
629                 
630         if(params.size() > 1)
631                 config.screen_index = boost::lexical_cast<int>(params[1]);
632                 
633         config.key_only = std::find(params.begin(), params.end(), L"WINDOWED") != params.end();
634         config.key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();
635
636         auto name_it    = std::find(params.begin(), params.end(), L"NAME");
637         if(name_it != params.end() && ++name_it != params.end())
638                 config.name = *name_it;
639
640         return spl::make_shared<screen_consumer_proxy>(config, nullptr);
641 }
642
643 spl::shared_ptr<core::frame_consumer> create_consumer(const boost::property_tree::wptree& ptree, core::interaction_sink* sink) 
644 {
645         configuration config;
646         config.name                             = ptree.get(L"name",     config.name);
647         config.screen_index             = ptree.get(L"device",   config.screen_index+1)-1;
648         config.windowed                 = ptree.get(L"windowed", config.windowed);
649         config.key_only                 = ptree.get(L"key-only", config.key_only);
650         config.auto_deinterlace = ptree.get(L"auto-deinterlace", config.auto_deinterlace);
651         config.vsync                    = ptree.get(L"vsync", config.vsync);
652         
653         auto stretch_str = ptree.get(L"stretch", L"default");
654         if(stretch_str == L"uniform")
655                 config.stretch = stretch::uniform;
656         else if(stretch_str == L"uniform_to_fill")
657                 config.stretch = stretch::uniform_to_fill;
658
659         auto aspect_str = ptree.get(L"aspect-ratio", L"default");
660         if(aspect_str == L"16:9")
661                 config.aspect = configuration::aspect_16_9;
662         else if(aspect_str == L"4:3")
663                 config.aspect = configuration::aspect_4_3;
664         
665         return spl::make_shared<screen_consumer_proxy>(config, sink);
666 }
667
668 }}