#include "ffmpeg_producer.h"
-#include "../ffmpeg_error.h"
+#include "../ffmpeg_pipeline.h"
#include "../ffmpeg.h"
-
-#include "muxer/frame_muxer.h"
-#include "input/input.h"
#include "util/util.h"
-#include "audio/audio_decoder.h"
-#include "video/video_decoder.h"
-#include <common/env.h>
-#include <common/log.h>
#include <common/param.h>
#include <common/diagnostics/graph.h>
#include <common/future.h>
-#include <common/timer.h>
-#include <common/assert.h>
-#include <core/video_format.h>
-#include <core/producer/frame_producer.h>
-#include <core/frame/audio_channel_layout.h>
-#include <core/frame/frame_factory.h>
#include <core/frame/draw_frame.h>
-#include <core/frame/frame_transform.h>
-#include <core/monitor/monitor.h>
#include <core/help/help_repository.h>
#include <core/help/help_sink.h>
-#include <core/producer/media_info/media_info_repository.h>
#include <core/producer/media_info/media_info.h>
+#include <core/producer/framerate/framerate_producer.h>
-#include <boost/algorithm/string.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/property_tree/ptree.hpp>
-#include <boost/regex.hpp>
-#include <boost/thread/future.hpp>
-
-#include <tbb/parallel_invoke.h>
-
-#include <limits>
-#include <memory>
-#include <queue>
+#include <future>
namespace caspar { namespace ffmpeg {
struct ffmpeg_producer : public core::frame_producer_base
{
spl::shared_ptr<core::monitor::subject> monitor_subject_;
+ ffmpeg_pipeline pipeline_;
const std::wstring filename_;
const std::wstring path_relative_to_media_ = get_relative_or_original(filename_, env::media_folder());
const spl::shared_ptr<diagnostics::graph> graph_;
- const spl::shared_ptr<core::frame_factory> frame_factory_;
const core::video_format_desc format_desc_;
- input input_;
-
- const double fps_ = read_fps(input_.context(), format_desc_.fps);
- const uint32_t start_;
- const bool thumbnail_mode_;
- const boost::optional<core::media_info> info_;
-
- std::unique_ptr<video_decoder> video_decoder_;
- std::unique_ptr<audio_decoder> audio_decoder_;
- std::unique_ptr<frame_muxer> muxer_;
core::constraints constraints_;
+ core::draw_frame first_frame_ = core::draw_frame::empty();
core::draw_frame last_frame_ = core::draw_frame::empty();
boost::optional<uint32_t> seek_target_;
public:
explicit ffmpeg_producer(
- const spl::shared_ptr<core::frame_factory>& frame_factory,
- const core::video_format_desc& format_desc,
- const std::wstring& channel_layout_spec,
- const std::wstring& filename,
- const std::wstring& filter,
- bool loop,
- uint32_t start,
- uint32_t length,
- bool thumbnail_mode,
- boost::optional<core::media_info> info)
- : filename_(filename)
- , frame_factory_(frame_factory)
+ ffmpeg_pipeline pipeline,
+ const core::video_format_desc& format_desc)
+ : pipeline_(std::move(pipeline))
+ , filename_(u16(pipeline_.source_filename()))
, format_desc_(format_desc)
- , input_(graph_, filename_, loop, start, length, thumbnail_mode)
- , start_(start)
- , thumbnail_mode_(thumbnail_mode)
- , info_(info)
{
graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f));
diagnostics::register_graph(graph_);
-
- try
- {
- video_decoder_.reset(new video_decoder(input_, thumbnail_mode));
- video_decoder_->monitor_output().attach_parent(monitor_subject_);
- constraints_.width.set(video_decoder_->width());
- constraints_.height.set(video_decoder_->height());
-
- if (is_logging_quiet_for_thread())
- CASPAR_LOG(debug) << print() << L" " << video_decoder_->print();
- else
- CASPAR_LOG(info) << print() << L" " << video_decoder_->print();
- }
- catch(averror_stream_not_found&)
- {
- CASPAR_LOG(debug) << print() << " No video-stream found. Running without video.";
- }
- catch(...)
- {
- CASPAR_LOG_CURRENT_EXCEPTION();
- CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video.";
- }
+ pipeline_.graph(graph_);
+ pipeline_.start();
- auto channel_layout = core::audio_channel_layout::invalid();
+ while ((first_frame_ = pipeline_.try_pop_frame()) == core::draw_frame::late())
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
- if (!thumbnail_mode)
- {
- try
- {
- audio_decoder_.reset(new audio_decoder(input_, format_desc_, channel_layout_spec));
- audio_decoder_->monitor_output().attach_parent(monitor_subject_);
-
- channel_layout = audio_decoder_->channel_layout();
-
- CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();
- }
- catch (averror_stream_not_found&)
- {
- CASPAR_LOG(debug) << print() << " No audio-stream found. Running without audio.";
- }
- catch (...)
- {
- CASPAR_LOG_CURRENT_EXCEPTION();
- CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";
- }
- }
-
- if (start_ > file_nb_frames())
- CASPAR_THROW_EXCEPTION(seek_out_of_range() << msg_info("SEEK out of range"));
-
- muxer_.reset(new frame_muxer(fps_, frame_factory, format_desc_, channel_layout, filter));
-
- decode_next_frame();
+ constraints_.width.set(pipeline_.width());
+ constraints_.height.set(pipeline_.height());
if (is_logging_quiet_for_thread())
CASPAR_LOG(debug) << print() << L" Initialized";
core::draw_frame receive_impl() override
{
- auto frame = core::draw_frame::late();
+ auto frame = core::draw_frame::late();
caspar::timer frame_timer;
- end_seek();
-
- decode_next_frame();
-
- if(!muxer_->empty())
- {
- last_frame_ = frame = std::move(muxer_->front());
- muxer_->pop();
- }
- else if (!input_.eof())
+ auto decoded_frame = first_frame_;
+
+ if (decoded_frame == core::draw_frame::empty())
+ decoded_frame = pipeline_.try_pop_frame();
+ else
+ first_frame_ = core::draw_frame::empty();
+
+ if (decoded_frame == core::draw_frame::empty())
+ frame = core::draw_frame::still(last_frame_);
+ else if (decoded_frame != core::draw_frame::late())
+ last_frame_ = frame = core::draw_frame(std::move(decoded_frame));
+ else if (pipeline_.started())
graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow");
+ graph_->set_text(print());
+
graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5);
*monitor_subject_
<< core::monitor::message("/profiler/time") % frame_timer.elapsed() % (1.0/format_desc_.fps);
*monitor_subject_
- << 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/frame") % static_cast<int32_t>(pipeline_.last_frame())
+ % static_cast<int32_t>(pipeline_.length())
+ << core::monitor::message("/file/fps") % boost::rational_cast<double>(pipeline_.framerate())
<< core::monitor::message("/file/path") % path_relative_to_media_
- << core::monitor::message("/loop") % input_.loop();
-
+ << core::monitor::message("/loop") % pipeline_.loop();
+
return frame;
}
core::draw_frame last_frame() override
{
- end_seek();
return core::draw_frame::still(last_frame_);
}
uint32_t nb_frames() const override
{
- if(input_.loop())
+ if (pipeline_.loop())
return std::numeric_limits<uint32_t>::max();
- uint32_t nb_frames = file_nb_frames();
-
- nb_frames = std::min(input_.length(), nb_frames);
- nb_frames = muxer_->calc_nb_frames(nb_frames);
-
- return nb_frames > start_ ? nb_frames - start_ : 0;
- }
-
- uint32_t file_nb_frames() const
- {
- uint32_t file_nb_frames = 0;
-
- if (info_)
- file_nb_frames = static_cast<uint32_t>(info_->duration);
-
- file_nb_frames = std::max(file_nb_frames, video_decoder_ ? video_decoder_->nb_frames() : 0);
- file_nb_frames = std::max(file_nb_frames, audio_decoder_ ? audio_decoder_->nb_frames() : 0);
- return file_nb_frames;
- }
-
- uint32_t file_frame_number() const
- {
- return video_decoder_ ? video_decoder_->file_frame_number() : 0;
+ return pipeline_.length();
}
std::future<std::wstring> call(const std::vector<std::wstring>& params) override
{
auto value = what["VALUE"].str();
if(!value.empty())
- input_.loop(boost::lexical_cast<bool>(value));
- result = boost::lexical_cast<std::wstring>(loop());
+ pipeline_.loop(boost::lexical_cast<bool>(value));
+ result = boost::lexical_cast<std::wstring>(pipeline_.loop());
}
else if(boost::regex_match(param, what, seek_exp))
{
auto value = what["VALUE"].str();
- seek(boost::lexical_cast<uint32_t>(value));
+ pipeline_.seek(boost::lexical_cast<uint32_t>(value));
}
else if(boost::regex_match(param, what, length_exp))
{
auto value = what["VALUE"].str();
if(!value.empty())
- length(boost::lexical_cast<uint32_t>(value));
- result = boost::lexical_cast<std::wstring>(length());
+ pipeline_.length(boost::lexical_cast<uint32_t>(value));
+ result = boost::lexical_cast<std::wstring>(pipeline_.length());
}
else if(boost::regex_match(param, what, start_exp))
{
auto value = what["VALUE"].str();
if(!value.empty())
- start(boost::lexical_cast<uint32_t>(value));
- result = boost::lexical_cast<std::wstring>(start());
+ pipeline_.start_frame(boost::lexical_cast<uint32_t>(value));
+ result = boost::lexical_cast<std::wstring>(pipeline_.start_frame());
}
else
CASPAR_THROW_EXCEPTION(invalid_argument());
{
return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|"
+ print_mode() + L"|"
- + boost::lexical_cast<std::wstring>(file_frame_number()) + L"/" + boost::lexical_cast<std::wstring>(file_nb_frames()) + L"]";
+ + boost::lexical_cast<std::wstring>(pipeline_.last_frame()) + L"/" + boost::lexical_cast<std::wstring>(pipeline_.length()) + L"]";
}
std::wstring name() const override
boost::property_tree::wptree info;
info.add(L"type", L"ffmpeg");
info.add(L"filename", filename_);
- info.add(L"width", video_decoder_ ? video_decoder_->width() : 0);
- info.add(L"height", video_decoder_ ? video_decoder_->height() : 0);
- info.add(L"progressive", video_decoder_ ? video_decoder_->is_progressive() : 0);
- info.add(L"fps", fps_);
- info.add(L"loop", input_.loop());
+ info.add(L"width", pipeline_.width());
+ info.add(L"height", pipeline_.height());
+ info.add(L"progressive", pipeline_.progressive());
+ info.add(L"fps", boost::rational_cast<double>(pipeline_.framerate()));
+ info.add(L"loop", pipeline_.loop());
info.add(L"frame-number", frame_number());
- auto nb_frames2 = nb_frames();
- info.add(L"nb-frames", nb_frames2 == std::numeric_limits<int64_t>::max() ? -1 : nb_frames2);
- info.add(L"file-frame-number", file_frame_number());
- info.add(L"file-nb-frames", file_nb_frames());
+ info.add(L"nb-frames", nb_frames());
+ info.add(L"file-frame-number", pipeline_.last_frame());
+ info.add(L"file-nb-frames", pipeline_.length());
return info;
}
}
// ffmpeg_producer
-
- void end_seek()
- {
- for(int n = 0; n < 8 && (last_frame_ == core::draw_frame::empty() || (seek_target_ && file_frame_number() != *seek_target_+2)); ++n)
- {
- decode_next_frame();
- if(!muxer_->empty())
- {
- last_frame_ = muxer_->front();
- seek_target_.reset();
- }
- }
- }
-
- void loop(bool value)
- {
- input_.loop(value);
- }
-
- bool loop() const
- {
- return input_.loop();
- }
-
- void length(uint32_t value)
- {
- input_.length(value);
- }
-
- uint32_t length()
- {
- return input_.length();
- }
-
- void start(uint32_t value)
- {
- input_.start(value);
- }
-
- uint32_t start()
- {
- return input_.start();
- }
-
- void seek(uint32_t target)
- {
- if (target > file_nb_frames())
- CASPAR_THROW_EXCEPTION(seek_out_of_range() << msg_info("SEEK out of range"));
-
- seek_target_ = target;
-
- input_.seek(*seek_target_);
- muxer_->clear();
- }
std::wstring print_mode() const
{
- return ffmpeg::print_mode(video_decoder_ ? video_decoder_->width() : 0,
- video_decoder_ ? video_decoder_->height() : 0,
- fps_,
- video_decoder_ ? !video_decoder_->is_progressive() : false);
- }
-
- void decode_next_frame()
- {
- for(int n = 0; n < 32 && muxer_->empty(); ++n)
- {
- std::shared_ptr<AVFrame> video;
- std::shared_ptr<AVFrame> audio;
- bool needs_video = !muxer_->video_ready();
- bool needs_audio = !muxer_->audio_ready();
-
- tbb::parallel_invoke(
- [&]
- {
- if (needs_video)
- video = video_decoder_ ? (*video_decoder_)() : create_frame();
- },
- [&]
- {
- if (needs_audio)
- audio = audio_decoder_ ? (*audio_decoder_)() : create_frame();
- });
-
- muxer_->push_video(video);
- muxer_->push_audio(audio);
- }
-
- graph_->set_text(print());
+ return ffmpeg::print_mode(
+ pipeline_.width(),
+ pipeline_.height(),
+ boost::rational_cast<double>(pipeline_.framerate()),
+ !pipeline_.progressive());
}
};
sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video.");
sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT film", L"given the defaults in casparcg.config this will specifies that the clip has 6 audio channels of the type 5.1 and that they are in the order FL FC FR BL BR LFE regardless of what ffmpeg says.");
sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT \"5.1:LFE FL FC FR BL BR\"", L"specifies that the clip has 6 audio channels of the type 5.1 and that they are in the specified order regardless of what ffmpeg says.");
- sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via CALL:");
+ sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":");
sink.example(L">> CALL 1-10 LOOP 1");
sink.example(L">> CALL 1-10 START 10");
sink.example(L">> CALL 1-10 LENGTH 50");
+ core::describe_framerate_producer(sink);
}
spl::shared_ptr<core::frame_producer> create_producer(
if(filename.empty())
return core::frame_producer::empty();
- bool loop = contains_param(L"LOOP", params);
- auto start = get_param(L"START", params, get_param(L"SEEK", params, static_cast<uint32_t>(0)));
- auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max());
- auto filter_str = get_param(L"FILTER", params, L"");
- auto channel_layout = get_param(L"CHANNEL_LAYOUT", params, L"");
- bool thumbnail_mode = false;
- auto info = info_repo->get(filename);
-
- return create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
- dependencies.frame_factory,
- dependencies.format_desc,
- channel_layout,
- filename,
- filter_str,
- loop,
- start,
- length,
- thumbnail_mode,
- info)));
+ auto pipeline = ffmpeg_pipeline()
+ .from_file(u8(filename))
+ .loop(contains_param(L"LOOP", params))
+ .start_frame(get_param(L"START", params, get_param(L"SEEK", params, static_cast<uint32_t>(0))))
+ .length(get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max()))
+ .vfilter(u8(get_param(L"FILTER", params, L"")))
+ .to_memory(dependencies.frame_factory, dependencies.format_desc);
+
+ auto producer = create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
+ pipeline,
+ dependencies.format_desc)));
+
+ if (pipeline.framerate() == -1) // Audio only.
+ return producer;
+
+ auto source_framerate = pipeline.framerate();
+ auto target_framerate = boost::rational<int>(
+ dependencies.format_desc.time_scale,
+ dependencies.format_desc.duration);
+
+ return core::create_framerate_producer(
+ producer,
+ source_framerate,
+ target_framerate,
+ dependencies.format_desc.field_mode,
+ dependencies.format_desc.audio_cadence);
}
core::draw_frame create_thumbnail_frame(
- const core::frame_producer_dependencies& dependencies,
- const std::wstring& media_file,
- const spl::shared_ptr<core::media_info_repository>& info_repo)
+ const core::frame_producer_dependencies& dependencies,
+ const std::wstring& media_file,
+ const spl::shared_ptr<core::media_info_repository>& info_repo)
{
auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
auto filename = probe_stem(env::media_folder() + L"/" + media_file, true);
auto render_specific_frame = [&](std::int64_t frame_num)
{
- spl::shared_ptr<core::frame_producer> producer = spl::make_shared<ffmpeg_producer>(
- dependencies.frame_factory,
- dependencies.format_desc,
- L"",
- filename,
- L"",
- false,
- static_cast<uint32_t>(frame_num),
- std::numeric_limits<uint32_t>::max(),
- true,
- info_repo->get(filename));
- return producer->receive();
+ auto pipeline = ffmpeg_pipeline()
+ .from_file(u8(filename))
+ .start_frame(static_cast<uint32_t>(frame_num))
+ .to_memory(dependencies.frame_factory, dependencies.format_desc);
+ pipeline.start();
+
+ auto frame = core::draw_frame::empty();
+ while ((frame = pipeline.try_pop_frame()) == core::draw_frame::late())
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
+ return frame;
};
auto info = info_repo->get(filename);
if (i == 0)
desired_frame = 0; // first
else if (i == num_snapshots - 1)
- desired_frame = total_frames - 30; // last
+ desired_frame = total_frames - 2; // last
else
// evenly distributed across the file.
desired_frame = total_frames * i / (num_snapshots - 1);