+ double out_fps() const
+ {
+ auto out_framerate = muxer_->out_framerate();
+ auto fps = static_cast<double>(out_framerate.numerator()) / static_cast<double>(out_framerate.denominator());
+
+ return fps;
+ }
+
+ std::pair<core::draw_frame, uint32_t> render_frame()
+ {
+ frame_timer_.restart();
+ auto disable_logging = temporary_enable_quiet_logging_for_thread(thumbnail_mode_);
+
+ for (int n = 0; n < 16 && frame_buffer_.size() < 2; ++n)
+ try_decode_frame();
+
+ graph_->set_value("frame-time", frame_timer_.elapsed() * out_fps() *0.5);
+
+ if (frame_buffer_.empty())
+ {
+ if (input_.eof())
+ {
+ send_osc();
+ return std::make_pair(last_frame(), -1);
+ }
+ else if (resource_type_ == FFMPEG_Resource::FFMPEG_FILE)
+ {
+ graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow");
+ send_osc();
+ return std::make_pair(core::draw_frame::late(), -1);
+ }
+ else
+ {
+ send_osc();
+ return std::make_pair(last_frame(), -1);
+ }
+ }
+
+ auto frame = frame_buffer_.front();
+ frame_buffer_.pop();
+
+ ++frame_number_;
+ file_frame_number_ = frame.second;
+
+ graph_->set_text(print());
+
+ last_frame_ = frame.first;
+
+ send_osc();
+
+ return frame;
+ }
+
+ void send_osc()
+ {
+ double fps = static_cast<double>(framerate_.numerator()) / static_cast<double>(framerate_.denominator());
+
+ *monitor_subject_ << core::monitor::message("/profiler/time") % frame_timer_.elapsed() % (1.0/out_fps());
+
+ *monitor_subject_ << core::monitor::message("/file/time") % (file_frame_number()/fps)
+ % (file_nb_frames()/fps)
+ << core::monitor::message("/file/frame") % static_cast<int32_t>(file_frame_number())
+ % static_cast<int32_t>(file_nb_frames())
+ << core::monitor::message("/file/fps") % fps
+ << core::monitor::message("/file/path") % path_relative_to_media_
+ << core::monitor::message("/loop") % input_.loop();
+ }
+
+ core::draw_frame render_specific_frame(uint32_t file_position)
+ {
+ // Some trial and error and undeterministic stuff here
+ static const int NUM_RETRIES = 32;
+
+ if (file_position > 0) // Assume frames are requested in sequential order,
+ // therefore no seeking should be necessary for the first frame.
+ {
+ input_.seek(file_position > 1 ? file_position - 2: file_position).get();
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+ }
+
+ for (int i = 0; i < NUM_RETRIES; ++i)
+ {
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+
+ auto frame = render_frame();
+
+ if (frame.second == std::numeric_limits<uint32_t>::max())
+ {
+ // Retry
+ continue;
+ }
+ else if (frame.second == file_position + 1 || frame.second == file_position)
+ return frame.first;
+ else if (frame.second > file_position + 1)
+ {
+ CASPAR_LOG(trace) << print() << L" " << frame.second << L" received, wanted " << file_position + 1;
+ int64_t adjusted_seek = file_position - (frame.second - file_position + 1);
+
+ if (adjusted_seek > 1 && file_position > 0)
+ {
+ CASPAR_LOG(trace) << print() << L" adjusting to " << adjusted_seek;
+ input_.seek(static_cast<uint32_t>(adjusted_seek) - 1).get();
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(40));
+ }
+ else
+ return frame.first;
+ }
+ }
+
+ CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position;
+ return core::draw_frame::empty();
+ }
+
+ core::draw_frame create_thumbnail_frame()
+ {
+ auto total_frames = nb_frames();
+ auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
+
+ if (grid < 1)
+ {
+ CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1";
+ BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1"));
+ }
+
+ if (grid == 1)
+ {
+ return render_specific_frame(total_frames / 2);
+ }
+
+ auto num_snapshots = grid * grid;
+
+ std::vector<core::draw_frame> frames;
+
+ for (int i = 0; i < num_snapshots; ++i)
+ {
+ int x = i % grid;
+ int y = i / grid;
+ int desired_frame;
+
+ if (i == 0)
+ desired_frame = 0; // first
+ else if (i == num_snapshots - 1)
+ desired_frame = total_frames - 1; // last
+ else
+ // evenly distributed across the file.
+ desired_frame = total_frames * i / (num_snapshots - 1);
+
+ auto frame = render_specific_frame(desired_frame);
+ frame.transform().image_transform.fill_scale[0] = 1.0 / static_cast<double>(grid);
+ frame.transform().image_transform.fill_scale[1] = 1.0 / static_cast<double>(grid);
+ frame.transform().image_transform.fill_translation[0] = 1.0 / static_cast<double>(grid) * x;
+ frame.transform().image_transform.fill_translation[1] = 1.0 / static_cast<double>(grid) * y;
+
+ frames.push_back(frame);
+ }
+
+ return core::draw_frame(frames);
+ }
+
+ uint32_t file_frame_number() const
+ {
+ return video_decoder_ ? video_decoder_->file_frame_number() : 0;
+ }
+