2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
4 * This file is part of CasparCG (www.casparcg.com).
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.
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.
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/>.
19 * Author: Robert Nagy, ronag89@gmail.com
22 #include "../StdAfx.h"
24 #include "ffmpeg_producer.h"
26 #include "../ffmpeg_pipeline.h"
27 #include "../ffmpeg.h"
28 #include "util/util.h"
30 #include <common/param.h>
31 #include <common/diagnostics/graph.h>
32 #include <common/future.h>
34 #include <core/frame/draw_frame.h>
35 #include <core/help/help_repository.h>
36 #include <core/help/help_sink.h>
37 #include <core/producer/media_info/media_info.h>
38 #include <core/producer/framerate/framerate_producer.h>
42 namespace caspar { namespace ffmpeg {
44 struct seek_out_of_range : virtual user_error {};
46 std::wstring get_relative_or_original(
47 const std::wstring& filename,
48 const boost::filesystem::path& relative_to)
50 boost::filesystem::path file(filename);
51 auto result = file.filename().wstring();
53 boost::filesystem::path current_path = file;
57 current_path = current_path.parent_path();
59 if (boost::filesystem::equivalent(current_path, relative_to))
62 if (current_path.empty())
65 result = current_path.filename().wstring() + L"/" + result;
71 struct ffmpeg_producer : public core::frame_producer_base
73 spl::shared_ptr<core::monitor::subject> monitor_subject_;
74 ffmpeg_pipeline pipeline_;
75 const std::wstring filename_;
76 const std::wstring path_relative_to_media_ = get_relative_or_original(filename_, env::media_folder());
78 const spl::shared_ptr<diagnostics::graph> graph_;
80 const core::video_format_desc format_desc_;
82 core::constraints constraints_;
84 core::draw_frame first_frame_ = core::draw_frame::empty();
85 core::draw_frame last_frame_ = core::draw_frame::empty();
87 boost::optional<uint32_t> seek_target_;
90 explicit ffmpeg_producer(
91 ffmpeg_pipeline pipeline,
92 const core::video_format_desc& format_desc)
93 : pipeline_(std::move(pipeline))
94 , filename_(u16(pipeline_.source_filename()))
95 , format_desc_(format_desc)
97 graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f));
98 graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f));
99 diagnostics::register_graph(graph_);
101 pipeline_.graph(graph_);
104 while ((first_frame_ = pipeline_.try_pop_frame()) == core::draw_frame::late())
105 boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
107 constraints_.width.set(pipeline_.width());
108 constraints_.height.set(pipeline_.height());
110 if (is_logging_quiet_for_thread())
111 CASPAR_LOG(debug) << print() << L" Initialized";
113 CASPAR_LOG(info) << print() << L" Initialized";
118 core::draw_frame receive_impl() override
120 auto frame = core::draw_frame::late();
122 caspar::timer frame_timer;
124 auto decoded_frame = first_frame_;
126 if (decoded_frame == core::draw_frame::empty())
127 decoded_frame = pipeline_.try_pop_frame();
129 first_frame_ = core::draw_frame::empty();
131 if (decoded_frame == core::draw_frame::empty())
132 frame = core::draw_frame::still(last_frame_);
133 else if (decoded_frame != core::draw_frame::late())
134 last_frame_ = frame = core::draw_frame(std::move(decoded_frame));
135 else if (pipeline_.started())
136 graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow");
138 graph_->set_text(print());
140 graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5);
142 << core::monitor::message("/profiler/time") % frame_timer.elapsed() % (1.0/format_desc_.fps);
144 << core::monitor::message("/file/frame") % static_cast<int32_t>(pipeline_.last_frame())
145 % static_cast<int32_t>(pipeline_.length())
146 << core::monitor::message("/file/fps") % boost::rational_cast<double>(pipeline_.framerate())
147 << core::monitor::message("/file/path") % path_relative_to_media_
148 << core::monitor::message("/loop") % pipeline_.loop();
153 core::draw_frame last_frame() override
155 return core::draw_frame::still(last_frame_);
158 core::constraints& pixel_constraints() override
163 uint32_t nb_frames() const override
165 if (pipeline_.loop())
166 return std::numeric_limits<uint32_t>::max();
168 return pipeline_.length();
171 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
173 static const boost::wregex loop_exp(LR"(LOOP\s*(?<VALUE>\d?)?)", boost::regex::icase);
174 static const boost::wregex seek_exp(LR"(SEEK\s+(?<VALUE>\d+))", boost::regex::icase);
175 static const boost::wregex length_exp(LR"(LENGTH\s+(?<VALUE>\d+)?)", boost::regex::icase);
176 static const boost::wregex start_exp(LR"(START\\s+(?<VALUE>\\d+)?)", boost::regex::icase);
178 auto param = boost::algorithm::join(params, L" ");
183 if(boost::regex_match(param, what, loop_exp))
185 auto value = what["VALUE"].str();
187 pipeline_.loop(boost::lexical_cast<bool>(value));
188 result = boost::lexical_cast<std::wstring>(pipeline_.loop());
190 else if(boost::regex_match(param, what, seek_exp))
192 auto value = what["VALUE"].str();
193 pipeline_.seek(boost::lexical_cast<uint32_t>(value));
195 else if(boost::regex_match(param, what, length_exp))
197 auto value = what["VALUE"].str();
199 pipeline_.length(boost::lexical_cast<uint32_t>(value));
200 result = boost::lexical_cast<std::wstring>(pipeline_.length());
202 else if(boost::regex_match(param, what, start_exp))
204 auto value = what["VALUE"].str();
206 pipeline_.start_frame(boost::lexical_cast<uint32_t>(value));
207 result = boost::lexical_cast<std::wstring>(pipeline_.start_frame());
210 CASPAR_THROW_EXCEPTION(invalid_argument());
212 return make_ready_future(std::move(result));
215 std::wstring print() const override
217 return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|"
218 + print_mode() + L"|"
219 + boost::lexical_cast<std::wstring>(pipeline_.last_frame()) + L"/" + boost::lexical_cast<std::wstring>(pipeline_.length()) + L"]";
222 std::wstring name() const override
227 boost::property_tree::wptree info() const override
229 boost::property_tree::wptree info;
230 info.add(L"type", L"ffmpeg");
231 info.add(L"filename", filename_);
232 info.add(L"width", pipeline_.width());
233 info.add(L"height", pipeline_.height());
234 info.add(L"progressive", pipeline_.progressive());
235 info.add(L"fps", boost::rational_cast<double>(pipeline_.framerate()));
236 info.add(L"loop", pipeline_.loop());
237 info.add(L"frame-number", frame_number());
238 info.add(L"nb-frames", nb_frames());
239 info.add(L"file-frame-number", pipeline_.last_frame());
240 info.add(L"file-nb-frames", pipeline_.length());
244 core::monitor::subject& monitor_output()
246 return *monitor_subject_;
251 std::wstring print_mode() const
253 return ffmpeg::print_mode(
256 boost::rational_cast<double>(pipeline_.framerate()),
257 !pipeline_.progressive());
261 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
263 sink.short_description(L"A producer for playing media files supported by FFmpeg.");
264 sink.syntax(L"[clip:string] {[loop:LOOP]} {START,SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}");
266 ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ")
267 ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ")
268 ->text(L"H.264, FLV, WMV and several audio codecs as well as uncompressed audio.");
270 ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.")
271 ->item(L"loop", L"Will cause the media file to loop between start and start + length")
272 ->item(L"start", L"Optionally sets the start frame. 0 by default. If loop is specified this will be the frame where it starts over again.")
273 ->item(L"length", L"Optionally sets the length of the clip. If not specified the clip will be played to the end. If loop is specified the file will jump to start position once this number of frames has been played.")
274 ->item(L"filter", L"If specified, will be used as an FFmpeg video filter.")
275 ->item(L"channel_layout",
276 L"Optionally override the automatically deduced audio channel layout. "
277 L"Either a named layout as specified in casparcg.config or in the format [type:string]:[channel_order:string] for a custom layout.");
278 sink.para()->text(L"Examples:");
279 sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame.");
280 sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame.");
281 sink.example(L">> PLAY 1-10 folder/clip LOOP START 10", L"to loop a clip between frame 10 and the last frame.");
282 sink.example(L">> PLAY 1-10 folder/clip LOOP START 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
283 sink.example(L">> PLAY 1-10 folder/clip START 10 LENGTH 50", L"to play frames 10-60 in a clip and stop.");
284 sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video.");
285 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.");
286 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.");
287 sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":");
288 sink.example(L">> CALL 1-10 LOOP 1");
289 sink.example(L">> CALL 1-10 START 10");
290 sink.example(L">> CALL 1-10 LENGTH 50");
291 core::describe_framerate_producer(sink);
294 spl::shared_ptr<core::frame_producer> create_producer(
295 const core::frame_producer_dependencies& dependencies,
296 const std::vector<std::wstring>& params,
297 const spl::shared_ptr<core::media_info_repository>& info_repo)
299 auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), false);
302 return core::frame_producer::empty();
304 auto pipeline = ffmpeg_pipeline()
305 .from_file(u8(filename))
306 .loop(contains_param(L"LOOP", params))
307 .start_frame(get_param(L"START", params, get_param(L"SEEK", params, static_cast<uint32_t>(0))))
308 .length(get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max()))
309 .vfilter(u8(get_param(L"FILTER", params, L"")))
310 .to_memory(dependencies.frame_factory, dependencies.format_desc);
312 auto producer = create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
314 dependencies.format_desc)));
316 if (pipeline.framerate() == -1) // Audio only.
319 auto source_framerate = pipeline.framerate();
320 auto target_framerate = boost::rational<int>(
321 dependencies.format_desc.time_scale,
322 dependencies.format_desc.duration);
324 return core::create_framerate_producer(
328 dependencies.format_desc.field_mode,
329 dependencies.format_desc.audio_cadence);
332 core::draw_frame create_thumbnail_frame(
333 const core::frame_producer_dependencies& dependencies,
334 const std::wstring& media_file,
335 const spl::shared_ptr<core::media_info_repository>& info_repo)
337 auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
338 auto filename = probe_stem(env::media_folder() + L"/" + media_file, true);
340 if (filename.empty())
341 return core::draw_frame::empty();
343 auto render_specific_frame = [&](std::int64_t frame_num)
345 auto pipeline = ffmpeg_pipeline()
346 .from_file(u8(filename))
347 .start_frame(static_cast<uint32_t>(frame_num))
348 .to_memory(dependencies.frame_factory, dependencies.format_desc);
351 auto frame = core::draw_frame::empty();
352 while ((frame = pipeline.try_pop_frame()) == core::draw_frame::late())
353 boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
357 auto info = info_repo->get(filename);
360 return core::draw_frame::empty();
362 auto total_frames = info->duration;
363 auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
367 CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1";
368 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1"));
373 return render_specific_frame(total_frames / 2);
376 auto num_snapshots = grid * grid;
378 std::vector<core::draw_frame> frames;
380 for (int i = 0; i < num_snapshots; ++i)
384 std::int64_t desired_frame;
387 desired_frame = 0; // first
388 else if (i == num_snapshots - 1)
389 desired_frame = total_frames - 2; // last
391 // evenly distributed across the file.
392 desired_frame = total_frames * i / (num_snapshots - 1);
394 auto frame = render_specific_frame(desired_frame);
395 frame.transform().image_transform.fill_scale[0] = 1.0 / static_cast<double>(grid);
396 frame.transform().image_transform.fill_scale[1] = 1.0 / static_cast<double>(grid);
397 frame.transform().image_transform.fill_translation[0] = 1.0 / static_cast<double>(grid) * x;
398 frame.transform().image_transform.fill_translation[1] = 1.0 / static_cast<double>(grid) * y;
400 frames.push_back(frame);
403 return core::draw_frame(frames);