return begin_invoke(std::forward<Func>(func), prioriy).get();\r
}\r
\r
- void yield() // noexcept\r
+ bool yield() // noexcept\r
{\r
if(boost::this_thread::get_id() != thread_.get_id()) // Only yield when calling from execution thread.\r
- return;\r
+ return false;\r
\r
std::function<void()> func;\r
while(execution_queue_[high_priority].try_pop(func))\r
execution_queue_[normal_priority].try_pop(func);\r
if(func)\r
func();\r
- else\r
- boost::thread::yield();\r
}\r
+\r
+ return func != nullptr;\r
}\r
\r
function_queue::size_type capacity() const /*noexcept*/ { return execution_queue_[normal_priority].capacity(); }\r
<ClInclude Include="producer\playlist\playlist_producer.h">\r
<Filter>source\producer\playlist</Filter>\r
</ClInclude>\r
- <ClInclude Include="mixer\image\shader\image_shader.h">\r
- <Filter>source\mixer\image\shader</Filter>\r
- </ClInclude>\r
<ClInclude Include="mixer\image\blend_modes.h">\r
<Filter>source\mixer\image</Filter>\r
</ClInclude>\r
<ClInclude Include="mixer\data_frame.h">\r
<Filter>source\mixer</Filter>\r
</ClInclude>\r
+ <ClInclude Include="mixer\image\shader\image_shader.h">\r
+ <Filter>source\mixer\image\shader</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
<ItemGroup>\r
<ClCompile Include="producer\transition\transition_producer.cpp">\r
CASPAR_LOG_CURRENT_EXCEPTION();\r
}\r
}\r
- \r
- void bind()\r
- {\r
- GL(glBindTexture(GL_TEXTURE_2D, id_));\r
- }\r
};\r
\r
device_buffer::device_buffer(int width, int height, int stride) : impl_(new impl(width, height, stride)){}\r
#include <common/assert.h>\r
#include <boost/foreach.hpp>\r
\r
+#include <asmlib.h>\r
+\r
#include <gl/glew.h>\r
\r
namespace caspar { namespace core {\r
return make_safe_ptr(buffer);\r
}\r
\r
-safe_ptr<device_buffer> ogl_device::create_device_buffer(int width, int height, int stride)\r
+safe_ptr<device_buffer> ogl_device::create_device_buffer(int width, int height, int stride, bool zero)\r
{\r
CASPAR_VERIFY(stride > 0 && stride < 5);\r
CASPAR_VERIFY(width > 0 && height > 0);\r
auto& pool = device_pools_[stride-1][((width << 16) & 0xFFFF0000) | (height & 0x0000FFFF)];\r
std::shared_ptr<device_buffer> buffer;\r
- if(!pool->items.try_pop(buffer)) \r
- buffer = executor_.invoke([&]{return allocate_device_buffer(width, height, stride);}, high_priority); \r
+ \r
+ if(!pool->items.try_pop(buffer)) \r
+ buffer = executor_.invoke([&]{return allocate_device_buffer(width, height, stride);});\r
+\r
+ if(zero)\r
+ { \r
+ executor_.invoke([&]\r
+ {\r
+ scoped_state scope(*this);\r
+ attach(*buffer);\r
+ glClear(GL_COLOR_BUFFER_BIT);\r
+ }, high_priority); \r
+ } \r
\r
//++pool->usage_count;\r
\r
//}\r
}\r
\r
-void ogl_device::yield()\r
+bool ogl_device::yield()\r
{\r
- push_state();\r
- auto restore_state = make_scope_guard([&]\r
- {\r
- pop_state();\r
- });\r
- \r
- executor_.yield();\r
+ scoped_state scope(*this);\r
+ return executor_.yield();\r
}\r
\r
boost::unique_future<void> ogl_device::gc()\r
state_stack_.push(state_);\r
}\r
\r
-void ogl_device::pop_state()\r
+ogl_device::state ogl_device::pop_state()\r
{\r
- if(state_stack_.size() == 1)\r
+ if(state_stack_.size() <= 1)\r
BOOST_THROW_EXCEPTION(invalid_operation());\r
\r
+ auto prev_state = state_stack_.top();\r
state_stack_.pop();\r
auto new_state = state_stack_.top();\r
- viewport(new_state.viewport[0], new_state.viewport[1], new_state.viewport[2], new_state.viewport[3]);\r
- scissor(new_state.scissor[0], new_state.scissor[1], new_state.scissor[2], new_state.scissor[3]);\r
- stipple_pattern(new_state.pattern.data());\r
- blend_func(new_state.blend_func[0], new_state.blend_func[1], new_state.blend_func[2], new_state.blend_func[3]);\r
+ \r
+ viewport(new_state.viewport);\r
+ scissor(new_state.scissor);\r
+ stipple_pattern(new_state.pattern);\r
+ blend_func(new_state.blend_func);\r
attach(new_state.attached_texture);\r
use(new_state.active_shader);\r
for(int n = 0; n < 16; ++n)\r
bind(new_state.binded_textures[n], n);\r
+\r
+ return prev_state;\r
}\r
\r
void ogl_device::enable(GLenum cap)\r
}\r
}\r
\r
-void ogl_device::viewport(int x, int y, int width, int height)\r
+void ogl_device::viewport(const std::array<GLint, 4>& viewport)\r
{\r
- std::array<int, 4> viewport = {{x, y, width, height}};\r
if(viewport != state_.viewport)\r
{ \r
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);\r
}\r
}\r
\r
-void ogl_device::scissor(int x, int y, int width, int height)\r
+void ogl_device::viewport(int x, int y, int width, int height)\r
+{\r
+ std::array<int, 4> ar = {{x, y, width, height}};\r
+ viewport(ar);\r
+}\r
+\r
+void ogl_device::scissor(const std::array<GLint, 4>& scissor)\r
{\r
- std::array<int, 4> scissor = {{x, y, width, height}};\r
if(scissor != state_.scissor)\r
{ \r
- state def_state_;\r
- if(scissor == def_state_.scissor)\r
+ if(scissor == state().scissor)\r
{\r
disable(GL_SCISSOR_TEST);\r
}\r
}\r
}\r
\r
-void ogl_device::stipple_pattern(const GLubyte* pattern)\r
+void ogl_device::scissor(int x, int y, int width, int height)\r
{\r
- std::array<GLubyte, 32*32> pattern2;\r
- memcpy(pattern2.data(), pattern, 32*32);\r
+ std::array<int, 4> ar = {{x, y, width, height}};\r
+ scissor(ar);\r
+}\r
\r
- if(pattern2 != state_.pattern)\r
+void ogl_device::stipple_pattern(const std::array<GLubyte, 32*32>& pattern)\r
+{\r
+ if(pattern != state_.pattern)\r
{\r
- state def_state_;\r
- if(pattern2 == def_state_.pattern)\r
+ if(pattern == state().pattern)\r
disable(GL_POLYGON_STIPPLE);\r
else\r
{\r
enable(GL_POLYGON_STIPPLE);\r
- glPolygonStipple(pattern2.data());\r
- state_.pattern = pattern2;\r
+ glPolygonStipple(pattern.data());\r
+ state_.pattern = pattern;\r
}\r
}\r
}\r
}\r
}\r
\r
+void ogl_device::blend_func(const std::array<GLint, 4>& func)\r
+{\r
+ if(state_.blend_func != func)\r
+ {\r
+ state def_state_;\r
+ if(func == def_state_.blend_func)\r
+ disable(GL_BLEND);\r
+ else\r
+ {\r
+ enable(GL_BLEND);\r
+ GL(glBlendFuncSeparate(func[0], func[1], func[2], func[3]));\r
+ state_.blend_func = func;\r
+ }\r
+ }\r
+}\r
+\r
+void ogl_device::blend_func(int c1, int c2, int a1, int a2)\r
+{\r
+ std::array<int, 4> ar = {c1, c2, a1, a2};\r
+ blend_func(ar);\r
+}\r
+\r
+void ogl_device::blend_func(int c1, int c2)\r
+{\r
+ blend_func(c1, c2, c1, c2);\r
+}\r
+\r
void ogl_device::bind(const device_buffer& texture, int index)\r
{\r
//while(true)\r
bind(texture.id(), index);\r
}\r
\r
-void ogl_device::clear(device_buffer& texture)\r
-{ \r
- auto prev = state_.attached_texture;\r
- attach(texture);\r
- auto restore_attachement = make_scope_guard([&]\r
- {\r
- attach(prev);\r
- });\r
-\r
- GL(glClear(GL_COLOR_BUFFER_BIT));\r
-}\r
\r
void ogl_device::use(GLint id)\r
{\r
use(shader.id());\r
}\r
\r
-void ogl_device::blend_func(int c1, int c2, int a1, int a2)\r
-{\r
- std::array<int, 4> func = {c1, c2, a1, a2};\r
-\r
- if(state_.blend_func != func)\r
- {\r
- state def_state_;\r
- if(func == def_state_.blend_func)\r
- disable(GL_BLEND);\r
- else\r
- {\r
- enable(GL_BLEND);\r
- GL(glBlendFuncSeparate(c1, c2, a1, a2));\r
- state_.blend_func = func;\r
- }\r
- }\r
-}\r
-\r
-void ogl_device::blend_func(int c1, int c2)\r
-{\r
- blend_func(c1, c2, c1, c2);\r
-}\r
\r
boost::unique_future<safe_ptr<host_buffer>> ogl_device::transfer(const safe_ptr<device_buffer>& source)\r
{ \r
return begin_invoke([=]() -> safe_ptr<host_buffer>\r
{\r
- push_state();\r
- auto restore_state = make_scope_guard([&]\r
- {\r
- pop_state();\r
- });\r
-\r
auto dest = create_host_buffer(source->width()*source->height()*source->stride(), host_buffer::read_only);\r
\r
- attach(*source);\r
+ {\r
+ scoped_state scope(*this);\r
+\r
+ attach(*source);\r
\r
- dest->bind();\r
- GL(glReadPixels(0, 0, source->width(), source->height(), format(source->stride()), GL_UNSIGNED_BYTE, NULL));\r
- dest->unbind();\r
+ dest->bind();\r
+ GL(glReadPixels(0, 0, source->width(), source->height(), format(source->stride()), GL_UNSIGNED_BYTE, NULL));\r
+\r
+ dest->unbind();\r
+ }\r
\r
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);\r
\r
GL(glGetSynciv(sync, GL_SYNC_STATUS, 1, &length, values));\r
\r
if(values[0] != GL_SIGNALED) \r
- yield();\r
+ {\r
+ if(!yield())\r
+ Sleep(2);\r
+ }\r
else\r
break;\r
}\r
boost::unique_future<safe_ptr<device_buffer>> ogl_device::transfer(const safe_ptr<host_buffer>& source, int width, int height, int stride)\r
{ \r
return begin_invoke([=]() -> safe_ptr<device_buffer>\r
- {\r
- push_state();\r
- auto restore_state = make_scope_guard([&]\r
- {\r
- pop_state();\r
- });\r
- \r
+ { \r
auto dest = create_device_buffer(width, height, stride);\r
+ \r
+ scoped_state scope(*this);\r
\r
source->unmap();\r
source->bind();\r
GL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, dest->width(), dest->height(), format(dest->stride()), GL_UNSIGNED_BYTE, NULL));\r
\r
source->unbind();\r
-\r
+ \r
return dest;\r
});\r
}\r
\r
+ogl_device::state::state()\r
+ : attached_texture(0)\r
+ , active_shader(0)\r
+{\r
+ binded_textures.assign(0);\r
+ viewport.assign(std::numeric_limits<int>::max());\r
+ scissor.assign(std::numeric_limits<int>::max());\r
+ blend_func.assign(std::numeric_limits<int>::max());\r
+ pattern.assign(0xFF);\r
+} \r
+\r
+ogl_device::state::state(const state& other)\r
+{\r
+ A_memcpy(this, &other, sizeof(state));\r
+}\r
+\r
+ogl_device::state& ogl_device::state::operator=(const state& other)\r
+{\r
+ A_memcpy(this, &other, sizeof(state));\r
+}\r
+\r
}}\r
\r
#include <array>\r
#include <stack>\r
#include <unordered_map>\r
+#include <type_traits>\r
\r
FORWARD1(boost, template<typename> class unique_future);\r
\r
class ogl_device : public std::enable_shared_from_this<ogl_device>\r
, boost::noncopyable\r
{ \r
- struct state\r
+ __declspec(align(16)) struct state\r
{\r
- std::array<int, 4> viewport;\r
- std::array<int, 4> scissor;\r
std::array<GLubyte, 32*32> pattern;\r
- GLint attached_texture;\r
- GLint active_shader;\r
+ std::array<GLint, 4> viewport;\r
+ std::array<GLint, 4> scissor;\r
std::array<GLint, 4> blend_func;\r
std::array<GLint, 16> binded_textures;\r
+ GLint attached_texture;\r
+ GLint active_shader;\r
+ GLint padding[2];\r
\r
- state()\r
- : attached_texture(0)\r
- , active_shader(0)\r
- {\r
- binded_textures.assign(0);\r
- viewport.assign(std::numeric_limits<int>::max());\r
- scissor.assign(std::numeric_limits<int>::max());\r
- blend_func.assign(std::numeric_limits<int>::max());\r
- pattern.assign(0xFF);\r
- } \r
+ state(); \r
+ state(const state& other);\r
+ state& operator=(const state& other);\r
};\r
-\r
+ \r
state state_;\r
std::stack<state> state_stack_;\r
\r
void use(GLint id);\r
void attach(GLint id);\r
void bind(GLint id, int index); \r
- void push_state();\r
- void pop_state();\r
void flush();\r
-\r
+ \r
+ friend class scoped_state;\r
public: \r
+ void push_state();\r
+ state pop_state();\r
+ \r
static safe_ptr<ogl_device> create();\r
~ogl_device();\r
\r
-\r
// Not thread-safe, must be called inside of context\r
void viewport(int x, int y, int width, int height);\r
+ void viewport(const std::array<GLint, 4>& ar);\r
void scissor(int x, int y, int width, int height);\r
- void stipple_pattern(const GLubyte* pattern);\r
- \r
- void clear(device_buffer& texture);\r
- \r
+ void scissor(const std::array<GLint, 4>& ar);\r
+ void stipple_pattern(const std::array<GLubyte, 32*32>& pattern);\r
+ \r
+ void blend_func(const std::array<GLint, 4>& ar);\r
void blend_func(int c1, int c2, int a1, int a2);\r
void blend_func(int c1, int c2);\r
\r
return executor_.invoke(std::forward<Func>(func), priority);\r
}\r
\r
- safe_ptr<device_buffer> create_device_buffer(int width, int height, int stride);\r
+ safe_ptr<device_buffer> create_device_buffer(int width, int height, int stride, bool zero = false);\r
safe_ptr<host_buffer> create_host_buffer(int size, host_buffer::usage_t usage);\r
\r
boost::unique_future<safe_ptr<host_buffer>> transfer(const safe_ptr<device_buffer>& source);\r
boost::unique_future<safe_ptr<device_buffer>> transfer(const safe_ptr<host_buffer>& source, int width, int height, int stride);\r
\r
- void yield();\r
+ bool yield();\r
boost::unique_future<void> gc();\r
\r
std::wstring version();\r
safe_ptr<host_buffer> allocate_host_buffer(int size, host_buffer::usage_t usage);\r
};\r
\r
+class scoped_state\r
+{\r
+ ogl_device& context_;\r
+public:\r
+ scoped_state(ogl_device& context)\r
+ : context_(context)\r
+ {\r
+ context_.push_state();\r
+ }\r
+\r
+ ~scoped_state()\r
+ {\r
+ context_.pop_state();\r
+ }\r
+};\r
+\r
}}
\ No newline at end of file
\r
namespace caspar { namespace core {\r
\r
-GLubyte upper_pattern[] = {\r
+std::array<GLubyte, 32*32> upper_pattern = {{\r
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\r
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\r
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\r
- 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00};\r
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}};\r
\r
-GLubyte lower_pattern[] = {\r
+std::array<GLubyte, 32*32> lower_pattern = {{\r
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, \r
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,\r
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,\r
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff};\r
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}};\r
\r
struct image_kernel::impl : boost::noncopyable\r
{ \r
static const double epsilon = 0.001;\r
\r
ogl_->invoke([&]\r
- { \r
+ { \r
+ scoped_state scope(*ogl_);\r
+\r
CASPAR_ASSERT(params.pix_desc.planes.size() == params.textures.size());\r
\r
if(params.textures.empty() || !params.background)\r
// Set render target\r
\r
ogl_->attach(*params.background);\r
- \r
- // Sync background\r
-\r
- if(blend_modes_)\r
- {\r
- // http://www.opengl.org/registry/specs/NV/texture_barrier.txt\r
- // This allows us to use framebuffer (background) both as source and target while blending.\r
- glTextureBarrierNV(); \r
- }\r
-\r
+ \r
// Draw \r
\r
glBegin(GL_QUADS);\r
glMultiTexCoord2d(GL_TEXTURE0, 1.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, (f_p[0]+f_s[0]), (f_p[1]+f_s[1])); glVertex2d((f_p[0]+f_s[0])*2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
glMultiTexCoord2d(GL_TEXTURE0, 0.0, 1.0); glMultiTexCoord2d(GL_TEXTURE1, f_p[0] , (f_p[1]+f_s[1])); glVertex2d( f_p[0] *2.0-1.0, (f_p[1]+f_s[1])*2.0-1.0);\r
glEnd();\r
- \r
- // Cleanup\r
- \r
- params.textures.clear();\r
+\r
+ // Sync background\r
+\r
+ if(blend_modes_)\r
+ {\r
+ // http://www.opengl.org/registry/specs/NV/texture_barrier.txt\r
+ // This allows us to use framebuffer (background) both as source and target while blending.\r
+ glTextureBarrierNV(); \r
+ }\r
});\r
}\r
};\r
\r
boost::unique_future<safe_ptr<host_buffer>> operator()(std::vector<layer>&& layers, const video_format_desc& format_desc)\r
{ \r
- auto draw_buffer = create_mixer_buffer(4, format_desc);\r
-\r
+ auto draw_buffer = ogl_->create_device_buffer(format_desc.width, format_desc.height, 4, true);\r
+ \r
if(format_desc.field_mode != field_mode::progressive)\r
{\r
auto upper = layers;\r
{\r
draw(std::move(layers), draw_buffer, format_desc);\r
}\r
- \r
- return ogl_->transfer(draw_buffer); \r
+ \r
+ return ogl_->transfer(draw_buffer); \r
}\r
\r
private:\r
\r
if(layer.first != blend_mode::normal)\r
{\r
- auto layer_draw_buffer = create_mixer_buffer(4, format_desc);\r
+ auto layer_draw_buffer = ogl_->create_device_buffer(format_desc.width, format_desc.height, 4, true);\r
\r
BOOST_FOREACH(auto& item, layer.second)\r
draw_item(std::move(item), layer_draw_buffer, layer_key_buffer, local_key_buffer, local_mix_buffer, format_desc); \r
\r
if(item.transform.is_key)\r
{\r
- local_key_buffer = local_key_buffer ? local_key_buffer : create_mixer_buffer(1, format_desc);\r
+ local_key_buffer = local_key_buffer ? local_key_buffer : ogl_->create_device_buffer(format_desc.width, format_desc.height, 4, true);\r
\r
draw_params.background = local_key_buffer;\r
draw_params.local_key = nullptr;\r
}\r
else if(item.transform.is_mix)\r
{\r
- local_mix_buffer = local_mix_buffer ? local_mix_buffer : create_mixer_buffer(4, format_desc);\r
+ local_mix_buffer = local_mix_buffer ? local_mix_buffer : ogl_->create_device_buffer(format_desc.width, format_desc.height, 4, true);\r
\r
draw_params.background = local_mix_buffer;\r
draw_params.local_key = std::move(local_key_buffer);\r
\r
kernel_.draw(std::move(draw_params));\r
}\r
- \r
- safe_ptr<device_buffer> create_mixer_buffer(int stride, const video_format_desc& format_desc)\r
- {\r
- auto buffer = ogl_->create_device_buffer(format_desc.width, format_desc.height, stride);\r
- ogl_->clear(*buffer);\r
- return buffer;\r
- }\r
};\r
\r
struct image_mixer::impl : boost::noncopyable\r
</paths>\r
<log-level>trace</log-level>\r
<channel-grid>true</channel-grid>\r
- <blend-modes>true</blend-modes>\r
<channels>\r
<channel>\r
<video-mode>720p5000</video-mode>\r
<consumers>\r
- <screen>\r
- <device>1</device>\r
- </screen>\r
- <system-audio></system-audio>\r
+ </consumers>\r
+ </channel>\r
+ <channel>\r
+ <video-mode>720p5000</video-mode>\r
+ <consumers>\r
+ </consumers>\r
+ </channel>\r
+ <channel>\r
+ <video-mode>720p5000</video-mode>\r
+ <consumers>\r
</consumers>\r
</channel>\r
</channels>\r