<ItemGroup>\r
<ClInclude Include="compiler\vs\disable_silly_warnings.h" />\r
<ClInclude Include="concurrency\executor.h" />\r
+ <ClInclude Include="diagnostics\context.h" />\r
<ClInclude Include="diagnostics\graph.h" />\r
+ <ClInclude Include="diagnostics\sink_backend.h" />\r
<ClInclude Include="exception\exceptions.h" />\r
<ClInclude Include="exception\win32_exception.h" />\r
<ClInclude Include="gl\gl_check.h" />\r
<ClInclude Include="utility\timer.h" />\r
</ItemGroup>\r
<ItemGroup>\r
+ <ClCompile Include="diagnostics\context.cpp">\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ </ClCompile>\r
<ClCompile Include="diagnostics\graph.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
</ClCompile>\r
+ <ClCompile Include="diagnostics\sink_backend.cpp">\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
+ </ClCompile>\r
<ClCompile Include="exception\win32_exception.cpp">\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
<ClCompile Include="diagnostics\graph.cpp">\r
<Filter>Source\diagnostics</Filter>\r
</ClCompile>\r
+ <ClCompile Include="diagnostics\context.cpp">\r
+ <Filter>Source\diagnostics</Filter>\r
+ </ClCompile>\r
+ <ClCompile Include="diagnostics\sink_backend.cpp">\r
+ <Filter>Source\log</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="exception\exceptions.h">\r
<ClInclude Include="diagnostics\graph.h">\r
<Filter>Source\diagnostics</Filter>\r
</ClInclude>\r
+ <ClInclude Include="diagnostics\context.h">\r
+ <Filter>Source\diagnostics</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="diagnostics\sink_backend.h">\r
+ <Filter>Source\log</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
</Project>
\ No newline at end of file
\r
namespace caspar {\r
\r
+namespace detail {\r
+\r
+typedef struct tagTHREADNAME_INFO\r
+{\r
+ DWORD dwType; // must be 0x1000\r
+ LPCSTR szName; // pointer to name (in user addr space)\r
+ DWORD dwThreadID; // thread ID (-1=caller thread)\r
+ DWORD dwFlags; // reserved for future use, must be zero\r
+} THREADNAME_INFO;\r
+\r
+inline void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)\r
+{\r
+ THREADNAME_INFO info;\r
+ {\r
+ info.dwType = 0x1000;\r
+ info.szName = szThreadName;\r
+ info.dwThreadID = dwThreadID;\r
+ info.dwFlags = 0;\r
+ }\r
+ __try\r
+ {\r
+ RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );\r
+ }\r
+ __except (EXCEPTION_CONTINUE_EXECUTION)\r
+ {\r
+ } \r
+}\r
+\r
+}\r
+\r
class executor : boost::noncopyable\r
{\r
public:\r
\r
- explicit executor(const std::function<void()>& f = nullptr)\r
+ explicit executor(const std::wstring& name = L"executor") : name_(narrow(name))\r
{\r
is_running_ = false;\r
- f_ = f != nullptr ? f : [this]{run();};\r
}\r
-\r
+ \r
virtual ~executor()\r
{\r
stop();\r
+ clear();\r
if(boost::this_thread::get_id() != thread_.get_id())\r
thread_.join();\r
}\r
if(is_running_.fetch_and_store(true))\r
return;\r
clear();\r
- thread_ = boost::thread(f_);\r
+ thread_ = boost::thread([this]{run();});\r
}\r
\r
void stop() // noexcept\r
\r
void run() // noexcept\r
{\r
- win32_exception::install_handler();\r
+ win32_exception::install_handler(); \r
+ detail::SetThreadName(GetCurrentThreadId(), name_.c_str());\r
while(is_running_)\r
execute();\r
}\r
\r
- std::function<void()> f_;\r
+ std::string name_;\r
boost::thread thread_;\r
tbb::atomic<bool> is_running_;\r
tbb::concurrent_bounded_queue<std::function<void()>> execution_queue_;\r
#include "../stdafx.h"\r
\r
#include "graph.h"\r
+#include "context.h"\r
\r
#pragma warning (disable : 4244)\r
\r
\r
namespace caspar { namespace diagnostics {\r
\r
-struct drawable : public sf::Drawable\r
-{\r
- virtual ~drawable(){}\r
- virtual void render(sf::RenderTarget& target) = 0;\r
- virtual void Render(sf::RenderTarget& target) const { const_cast<drawable*>(this)->render(target);}\r
-};\r
-\r
-class context : public drawable\r
-{ \r
- timer timer_;\r
- sf::RenderWindow window_;\r
- \r
- std::list<std::shared_ptr<drawable>> drawables_;\r
- std::map<size_t, sf::Font> fonts_;\r
- \r
- executor executor_;\r
-public: \r
-\r
- template<typename Func>\r
- static auto begin_invoke(Func&& func) -> boost::unique_future<decltype(func())> // noexcept\r
- { \r
- return get_instance().executor_.begin_invoke(std::forward<Func>(func)); \r
- }\r
-\r
- static void register_drawable(const std::shared_ptr<drawable>& drawable)\r
- {\r
- begin_invoke([=]\r
- {\r
- get_instance().drawables_.push_back(drawable);\r
- });\r
- }\r
- \r
-private:\r
-\r
- void tick()\r
- {\r
- sf::Event e;\r
- while(window_.GetEvent(e)){} \r
- glClear(GL_COLOR_BUFFER_BIT);\r
- window_.Draw(*this);\r
- window_.Display();\r
- timer_.tick(1.0/50.0);\r
- executor_.begin_invoke([this]{tick();});\r
- }\r
-\r
- void render(sf::RenderTarget& target)\r
- {\r
- auto count = std::max<size_t>(5, drawables_.size());\r
-\r
- int n = 0;\r
- for(auto it = drawables_.begin(); it != drawables_.end(); ++n)\r
- {\r
- auto& drawable = *it;\r
- if(!drawable.unique())\r
- {\r
- drawable->SetScale(window_.GetWidth(), window_.GetHeight()/count);\r
- drawable->SetPosition(0.0f, n* window_.GetHeight()/count);\r
- target.Draw(*drawable); \r
- ++it; \r
- }\r
- else \r
- it = drawables_.erase(it); \r
- } \r
- } \r
- \r
- static context& get_instance()\r
- {\r
- static context impl;\r
- return impl;\r
- }\r
-\r
- context()\r
- {\r
- executor_.start();\r
- executor_.begin_invoke([this]\r
- {\r
- window_.Create(sf::VideoMode(600, 1000), "CasparCG Diagnostics");\r
- window_.SetPosition(0, 0);\r
- window_.SetActive();\r
- glEnable(GL_BLEND);\r
- glEnable(GL_LINE_SMOOTH);\r
- glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);\r
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\r
- tick();\r
- });\r
- }\r
-};\r
- \r
class guide : public drawable\r
{\r
float value_;\r
class line : public drawable\r
{\r
boost::optional<diagnostics::guide> guide_;\r
- boost::circular_buffer<float> line_data_;\r
- std::vector<float> tick_data_;\r
+ boost::circular_buffer<std::pair<float, bool>> line_data_;\r
+\r
+ std::vector<float> tick_data_;\r
+ bool tick_tag_;\r
color c_;\r
public:\r
line(size_t res = 600)\r
: line_data_(res)\r
- , c_(1.0f, 1.0f, 1.0f){}\r
+ , tick_tag_(false)\r
+ , c_(1.0f, 1.0f, 1.0f)\r
+ {\r
+ line_data_.push_back(std::make_pair(-1.0f, false));\r
+ }\r
\r
void update(float value)\r
{\r
tick_data_.push_back(value);\r
}\r
+ \r
+ void tag()\r
+ {\r
+ tick_tag_ = true;\r
+ }\r
\r
void guide(const guide& guide)\r
{\r
if(!tick_data_.empty())\r
{\r
float sum = std::accumulate(tick_data_.begin(), tick_data_.end(), 0.0) + std::numeric_limits<float>::min();\r
- line_data_.push_back(static_cast<float>(sum)/static_cast<float>(tick_data_.size()));\r
+ line_data_.push_back(std::make_pair(static_cast<float>(sum)/static_cast<float>(tick_data_.size()), tick_tag_));\r
tick_data_.clear();\r
}\r
else if(!line_data_.empty())\r
{\r
- line_data_.push_back(line_data_.back());\r
+ line_data_.push_back(std::make_pair(line_data_.back().first, tick_tag_));\r
}\r
+ tick_tag_ = false;\r
\r
if(guide_)\r
target.Draw(*guide_);\r
\r
glBegin(GL_LINE_STRIP);\r
glColor4f(c_.red, c_.green, c_.blue, 1.0f); \r
- for(size_t n = 0; n < line_data_.size(); ++n) \r
- glVertex3f(x+n*dx, std::max(0.05f, std::min(0.95f, (1.0f-line_data_[n])*0.8f + 0.1f)), 0.0f); \r
+ for(size_t n = 0; n < line_data_.size(); ++n) \r
+ if(line_data_[n].first > -0.5)\r
+ glVertex3f(x+n*dx, std::max(0.05f, std::min(0.95f, (1.0f-line_data_[n].first)*0.8f + 0.1f)), 0.0f); \r
glEnd();\r
+ \r
+ glEnable(GL_LINE_STIPPLE);\r
+ glLineStipple(3, 0xAAAA);\r
+ for(size_t n = 0; n < line_data_.size(); ++n) \r
+ {\r
+ if(line_data_[n].second)\r
+ {\r
+ glBegin(GL_LINE_STRIP);\r
+ glColor4f(c_.red, c_.green, c_.blue, c_.alpha); \r
+ glVertex3f(x+n*dx, 0.0f, 0.0f); \r
+ glVertex3f(x+n*dx, 1.0f, 0.0f); \r
+ glEnd();\r
+ }\r
+ }\r
+ glDisable(GL_LINE_STIPPLE);\r
}\r
};\r
\r
});\r
}\r
\r
+ void tag(const std::string& name)\r
+ {\r
+ context::begin_invoke([=]\r
+ {\r
+ lines_[name].tag();\r
+ });\r
+ }\r
+\r
void set_color(const std::string& name, color c)\r
{\r
context::begin_invoke([=]\r
{\r
context::register_drawable(impl_);\r
}\r
+\r
void graph::update(const std::string& name, float value){impl_->update(name, value);}\r
+void graph::tag(const std::string& name){impl_->tag(name);}\r
void graph::guide(const std::string& name, float value){impl_->guide(name, value);}\r
void graph::set_color(const std::string& name, color c){impl_->set_color(name, c);}\r
\r
graph(const std::string& name);\r
public:\r
void update(const std::string& name, float value);\r
+ void tag(const std::string& name);\r
void guide(const std::string& name, float value);\r
void set_color(const std::string& name, color c);\r
private:\r
#include "log.h"\r
\r
#include "../exception/exceptions.h"\r
+#include "../diagnostics/sink_backend.h"\r
\r
#include <ios>\r
#include <string>\r
\r
typedef boost::log::sinks::synchronous_sink<boost::log::sinks::wtext_file_backend> file_sink_type;\r
\r
- typedef boost::log::sinks::asynchronous_sink<boost::log::sinks::wtext_ostream_backend> stream_sink_type;\r
-\r
- auto stream_backend = boost::make_shared<boost::log::sinks::wtext_ostream_backend>();\r
- stream_backend->add_stream(boost::shared_ptr<std::wostream>(&std::wcout, boost::log::empty_deleter()));\r
- stream_backend->auto_flush(true);\r
+ typedef boost::log::sinks::asynchronous_sink<diagnostics::sink_backend> stream_sink_type;\r
\r
+ auto stream_backend = boost::make_shared<diagnostics::sink_backend>();\r
auto stream_sink = boost::make_shared<stream_sink_type>(stream_backend);\r
\r
stream_sink->locked_backend()->set_formatter(\r
BOOST_THROW_EXCEPTION(directory_not_found());\r
\r
auto file_sink = boost::make_shared<file_sink_type>(\r
- boost::log::keywords::file_name = (folder + L"caspar_%Y-%m-%d_%H-%M-%S.%N.log"),\r
+ boost::log::keywords::file_name = (folder + L"%Y-%m-%d_%H-%M-%S.%N.log"),\r
boost::log::keywords::time_based_rotation = boost::log::sinks::file::rotation_at_time_point(0, 0, 0),\r
boost::log::keywords::auto_flush = true\r
);\r
executor executor_;\r
public:\r
implementation::implementation(unsigned int device_index, bool embed_audio) \r
- : graph_(diagnostics::create_graph("bluefish[" + boost::lexical_cast<std::string>(device_index) + "]"))\r
+ : graph_(diagnostics::create_graph("blue[" + boost::lexical_cast<std::string>(device_index) + "]"))\r
, device_index_(device_index) \r
, mem_fmt_(MEM_FMT_ARGB_PC)\r
, upd_fmt_(UPD_FMT_FRAME)\r
namespace caspar { namespace core {\r
\r
struct frame_consumer_device::implementation\r
-{\r
- safe_ptr<diagnostics::graph> graph_;\r
- timer perf_timer_;\r
- \r
+{ \r
timer clock_;\r
\r
boost::circular_buffer<safe_ptr<const read_frame>> buffer_;\r
executor executor_; \r
public:\r
implementation(const video_format_desc& format_desc) \r
- : graph_(diagnostics::create_graph("output"))\r
- , format_desc_(format_desc)\r
+ : format_desc_(format_desc)\r
+ , executor_(L"frame_consumer_device")\r
{ \r
- graph_->guide("frame-time", 0.5f); \r
- graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f));\r
executor_.set_capacity(2);\r
executor_.start();\r
}\r
}\r
\r
clock_.tick(1.0/format_desc_.fps);\r
- \r
- graph_->update("frame-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval*0.5));\r
- perf_timer_.reset();\r
});\r
}\r
};\r
video_format_desc format_desc_;\r
public:\r
implementation() \r
- : graph_(diagnostics::create_graph("audio"))\r
+ : graph_(diagnostics::create_graph("oal"))\r
, container_(5)\r
{\r
graph_->guide("frame-time", 0.5);\r
, screen_x_(0)\r
, screen_y_(0)\r
, screen_index_(screen_index)\r
- , graph_(diagnostics::create_graph("screen[" + boost::lexical_cast<std::string>(screen_index) + "]"))\r
+ , graph_(diagnostics::create_graph("ogl[" + boost::lexical_cast<std::string>(screen_index) + "]"))\r
{ \r
graph_->guide("frame-time", 0.5);\r
graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f));\r
\r
#include <boost/exception/error_info.hpp>\r
#include <boost/thread/once.hpp>\r
+#include <boost/thread/thread.hpp>\r
\r
#include <errno.h>\r
#include <system_error>\r
\r
struct input::implementation : boost::noncopyable\r
{ \r
- static const size_t BUFFER_SIZE = 2 << 24;\r
+ static const size_t PACKET_BUFFER_COUNT = 50;\r
\r
safe_ptr<diagnostics::graph> graph_;\r
\r
bool loop_;\r
int video_s_index_;\r
int audio_s_index_;\r
-\r
- tbb::atomic<size_t> buffer_size_;\r
- \r
+ \r
tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> video_packet_buffer_;\r
tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> audio_packet_buffer_;\r
+\r
+ boost::condition_variable cond_;\r
+ boost::mutex mutex_;\r
\r
executor executor_;\r
public:\r
, video_s_index_(-1)\r
, audio_s_index_(-1)\r
, filename_(filename)\r
+ , executor_(print())\r
{ \r
- graph_->set_color("input-buffer", diagnostics::color(1.0f, 1.0f, 0.0f)); \r
+ graph_->set_color("input-buffer", diagnostics::color(1.0f, 1.0f, 0.0f));\r
+ graph_->set_color("stop", diagnostics::color(1.0f, 0.5f, 0.5f));\r
+ graph_->set_color("seek", diagnostics::color(0.5f, 1.0f, 0.5f)); \r
\r
int errn;\r
AVFormatContext* weak_format_context_;\r
\r
~implementation()\r
{\r
+ video_packet_buffer_.clear();\r
+ audio_packet_buffer_.clear();\r
+ cond_.notify_all();\r
executor_.clear();\r
CASPAR_LOG(info) << print() << " ended.";\r
}\r
}\r
\r
void read_file() // For every packet taken: read in a number of packets.\r
- { \r
- for(size_t n = 0; buffer_size_ < BUFFER_SIZE && (n < 3 || video_packet_buffer_.size() < 3 || audio_packet_buffer_.size() < 3) && executor_.is_running(); ++n)\r
+ { \r
+ AVPacket tmp_packet;\r
+ safe_ptr<AVPacket> read_packet(&tmp_packet, av_free_packet); \r
+\r
+ if (av_read_frame(format_context_.get(), read_packet.get()) >= 0) // NOTE: read_packet is only valid until next call of av_safe_ptr<read_frame> or av_close_input_file\r
{\r
- AVPacket tmp_packet;\r
- safe_ptr<AVPacket> read_packet(&tmp_packet, av_free_packet); \r
-\r
- if (av_read_frame(format_context_.get(), read_packet.get()) >= 0) // NOTE: read_packet is only valid until next call of av_safe_ptr<read_frame> or av_close_input_file\r
- {\r
- auto packet = std::make_shared<aligned_buffer>(read_packet->data, read_packet->data + read_packet->size);\r
- if(read_packet->stream_index == video_s_index_) \r
- {\r
- buffer_size_ += packet->size();\r
- video_packet_buffer_.try_push(std::move(packet)); \r
- }\r
- else if(read_packet->stream_index == audio_s_index_) \r
- {\r
- buffer_size_ += packet->size();\r
- audio_packet_buffer_.try_push(std::move(packet));\r
- }\r
- }\r
- else if(!loop_ || !seek_frame(0, AVSEEK_FLAG_BACKWARD)) // TODO: av_seek_frame does not work for all formats\r
- executor_.stop();\r
- graph_->update("input-buffer", static_cast<float>(buffer_size_)/static_cast<float>(BUFFER_SIZE));\r
+ auto packet = std::make_shared<aligned_buffer>(read_packet->data, read_packet->data + read_packet->size);\r
+ if(read_packet->stream_index == video_s_index_) \r
+ video_packet_buffer_.try_push(std::move(packet)); \r
+ else if(read_packet->stream_index == audio_s_index_) \r
+ audio_packet_buffer_.try_push(std::move(packet)); \r
}\r
+ else if(!loop_ || !seek_frame(0, AVSEEK_FLAG_BACKWARD)) // TODO: av_seek_frame does not work for all formats\r
+ { \r
+ graph_->tag("stop");\r
+ executor_.stop();\r
+ }\r
+ else\r
+ graph_->tag("seek");\r
+\r
+ boost::this_thread::yield();\r
+ \r
+ graph_->update("input-buffer", static_cast<float>(video_packet_buffer_.size())/static_cast<float>(PACKET_BUFFER_COUNT)); \r
+ \r
+ executor_.begin_invoke([this]{read_file();}); \r
+ boost::unique_lock<boost::mutex> lock(mutex_);\r
+ while(executor_.is_running() && audio_packet_buffer_.size() > PACKET_BUFFER_COUNT && video_packet_buffer_.size() > PACKET_BUFFER_COUNT)\r
+ cond_.wait(lock); \r
}\r
\r
bool seek_frame(int64_t seek_target, int flags = 0)\r
\r
aligned_buffer get_packet(tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>>& buffer)\r
{\r
+ cond_.notify_all();\r
std::shared_ptr<aligned_buffer> packet;\r
- if(buffer.try_pop(packet))\r
- {\r
- buffer_size_ -= packet->size();\r
- if(executor_.size() < 4)\r
- executor_.begin_invoke([this]{read_file();});\r
- return std::move(*packet);\r
- }\r
- return aligned_buffer();\r
+ return buffer.try_pop(packet) ? std::move(*packet) : aligned_buffer();\r
}\r
\r
bool is_eof() const\r
, head_(draw_frame::empty())\r
{\r
graph_->guide("frame-time", 0.5f);\r
- graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f)); \r
+ graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f)); \r
+ graph_->set_color("param", diagnostics::color(1.0f, 0.5f, 0.0f)); \r
CASPAR_LOG(info) << print() << L" Started";\r
\r
if(FAILED(CComObject<caspar::flash::FlashAxContainer>::CreateInstance(&ax_)))\r
{ \r
if(!ax_->FlashCall(param))\r
CASPAR_LOG(warning) << "Flash Function Call Failed. Param: " << param;\r
+ graph_->tag("param");\r
}\r
\r
safe_ptr<draw_frame> render_frame(bool has_underflow)\r
: filename_(filename)\r
, graph_(diagnostics::create_graph(narrow(print())))\r
, tail_(draw_frame::empty()) \r
+ , executor_(print())\r
{ \r
if(!boost::filesystem::exists(filename))\r
BOOST_THROW_EXCEPTION(file_not_found() << boost::errinfo_file_name(narrow(filename))); \r
{ \r
safe_ptr<diagnostics::graph> graph_;\r
timer perf_timer_;\r
+ timer wait_perf_timer_;\r
\r
const video_format_desc format_desc_;\r
\r
, format_desc_(format_desc)\r
, image_mixer_(format_desc)\r
, output_(output)\r
+ , executor_(L"frame_mixer_device")\r
{\r
graph_->guide("frame-time", 0.5f); \r
graph_->set_color("frame-time", diagnostics::color(1.0f, 0.0f, 0.0f));\r
+ graph_->set_color("frame-wait", diagnostics::color(0.1f, 0.7f, 0.8f));\r
graph_->set_color("output-buffer", diagnostics::color( 0.0f, 1.0f, 0.0f)); \r
executor_.start();\r
executor_.set_capacity(2);\r
audio_mixer_.end_pass();\r
graph_->update("frame-time", static_cast<float>(perf_timer_.elapsed()/format_desc_.interval*0.5));\r
\r
+ wait_perf_timer_.reset();\r
output_(make_safe<const read_frame>(std::move(image.get()), std::move(audio)));\r
+ graph_->update("frame-wait", static_cast<float>(wait_perf_timer_.elapsed()/format_desc_.interval*0.5));\r
});\r
graph_->update("output-buffer", static_cast<float>(executor_.size())/static_cast<float>(executor_.capacity()));\r
}\r
</ogl>\r
<audio/>\r
<bluefish>\r
+ <embedded-audio>true</embedded-audio>\r
<device>1</device>\r
</bluefish>\r
<!--decklink>\r