]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/producer/ffmpeg_producer.cpp
b2d4b0e8b7ceed1db9b443f6a4859f56ff3a38ad
[casparcg] / modules / ffmpeg / producer / ffmpeg_producer.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "../StdAfx.h"
23
24 #include "ffmpeg_producer.h"
25
26 #include "../ffmpeg_pipeline.h"
27 #include "../ffmpeg.h"
28 #include "util/util.h"
29
30 #include <common/param.h>
31 #include <common/diagnostics/graph.h>
32 #include <common/future.h>
33
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>
39
40 #include <future>
41
42 namespace caspar { namespace ffmpeg {
43
44 struct seek_out_of_range : virtual user_error {};
45
46 std::wstring get_relative_or_original(
47                 const std::wstring& filename,
48                 const boost::filesystem::path& relative_to)
49 {
50         boost::filesystem::path file(filename);
51         auto result = file.filename().wstring();
52
53         boost::filesystem::path current_path = file;
54
55         while (true)
56         {
57                 current_path = current_path.parent_path();
58
59                 if (boost::filesystem::equivalent(current_path, relative_to))
60                         break;
61
62                 if (current_path.empty())
63                         return filename;
64
65                 result = current_path.filename().wstring() + L"/" + result;
66         }
67
68         return result;
69 }
70
71 struct ffmpeg_producer : public core::frame_producer_base
72 {
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());
77         
78         const spl::shared_ptr<diagnostics::graph>               graph_;
79                                         
80         const core::video_format_desc                                   format_desc_;
81
82         core::constraints                                                               constraints_;
83         
84         core::draw_frame                                                                first_frame_                    = core::draw_frame::empty();
85         core::draw_frame                                                                last_frame_                             = core::draw_frame::empty();
86
87         boost::optional<uint32_t>                                               seek_target_;
88         
89 public:
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)
96         {
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_);
100
101                 pipeline_.graph(graph_);
102                 pipeline_.start();
103
104                 while ((first_frame_ = pipeline_.try_pop_frame()) == core::draw_frame::late())
105                         boost::this_thread::sleep_for(boost::chrono::milliseconds(1));
106
107                 constraints_.width.set(pipeline_.width());
108                 constraints_.height.set(pipeline_.height());
109
110                 if (is_logging_quiet_for_thread())
111                         CASPAR_LOG(debug) << print() << L" Initialized";
112                 else
113                         CASPAR_LOG(info) << print() << L" Initialized";
114         }
115
116         // frame_producer
117         
118         core::draw_frame receive_impl() override
119         {                               
120                 auto frame = core::draw_frame::late();
121                 
122                 caspar::timer frame_timer;
123                 
124                 auto decoded_frame = first_frame_;
125
126                 if (decoded_frame == core::draw_frame::empty())
127                         decoded_frame = pipeline_.try_pop_frame();
128                 else
129                         first_frame_ = core::draw_frame::empty();
130
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");
137
138                 graph_->set_text(print());
139
140                 graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5);
141                 *monitor_subject_
142                                 << core::monitor::message("/profiler/time")     % frame_timer.elapsed() % (1.0/format_desc_.fps);                       
143                 *monitor_subject_
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();
149
150                 return frame;
151         }
152
153         core::draw_frame last_frame() override
154         {
155                 return core::draw_frame::still(last_frame_);
156         }
157
158         core::constraints& pixel_constraints() override
159         {
160                 return constraints_;
161         }
162
163         uint32_t nb_frames() const override
164         {
165                 if (pipeline_.loop())
166                         return std::numeric_limits<uint32_t>::max();
167
168                 return pipeline_.length();
169         }
170                 
171         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
172         {
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);
177
178                 auto param = boost::algorithm::join(params, L" ");
179                 
180                 std::wstring result;
181                         
182                 boost::wsmatch what;
183                 if(boost::regex_match(param, what, loop_exp))
184                 {
185                         auto value = what["VALUE"].str();
186                         if(!value.empty())
187                                 pipeline_.loop(boost::lexical_cast<bool>(value));
188                         result = boost::lexical_cast<std::wstring>(pipeline_.loop());
189                 }
190                 else if(boost::regex_match(param, what, seek_exp))
191                 {
192                         auto value = what["VALUE"].str();
193                         pipeline_.seek(boost::lexical_cast<uint32_t>(value));
194                 }
195                 else if(boost::regex_match(param, what, length_exp))
196                 {
197                         auto value = what["VALUE"].str();
198                         if(!value.empty())
199                                 pipeline_.length(boost::lexical_cast<uint32_t>(value));                 
200                         result = boost::lexical_cast<std::wstring>(pipeline_.length());
201                 }
202                 else if(boost::regex_match(param, what, start_exp))
203                 {
204                         auto value = what["VALUE"].str();
205                         if(!value.empty())
206                                 pipeline_.start_frame(boost::lexical_cast<uint32_t>(value));
207                         result = boost::lexical_cast<std::wstring>(pipeline_.start_frame());
208                 }
209                 else
210                         CASPAR_THROW_EXCEPTION(invalid_argument());
211
212                 return make_ready_future(std::move(result));
213         }
214                                 
215         std::wstring print() const override
216         {
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"]";
220         }
221
222         std::wstring name() const override
223         {
224                 return L"ffmpeg";
225         }
226
227         boost::property_tree::wptree info() const override
228         {
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());
241                 return info;
242         }
243         
244         core::monitor::subject& monitor_output()
245         {
246                 return *monitor_subject_;
247         }
248
249         // ffmpeg_producer
250
251         std::wstring print_mode() const
252         {
253                 return ffmpeg::print_mode(
254                                 pipeline_.width(),
255                                 pipeline_.height(),
256                                 boost::rational_cast<double>(pipeline_.framerate()), 
257                                 !pipeline_.progressive());
258         }
259 };
260
261 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
262 {
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]}");
265         sink.para()
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.");
269         sink.definitions()
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);
292 }
293
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)
298 {
299         auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), false);
300
301         if(filename.empty())
302                 return core::frame_producer::empty();
303         
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);
311
312         auto producer = create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
313                         pipeline,
314                         dependencies.format_desc)));
315
316         if (pipeline.framerate() == -1) // Audio only.
317                 return producer;
318
319         auto source_framerate = pipeline.framerate();
320         auto target_framerate = boost::rational<int>(
321                         dependencies.format_desc.time_scale,
322                         dependencies.format_desc.duration);
323
324         return core::create_framerate_producer(
325                         producer,
326                         source_framerate,
327                         target_framerate,
328                         dependencies.format_desc.field_mode,
329                         dependencies.format_desc.audio_cadence);
330 }
331
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)
336 {
337         auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
338         auto filename = probe_stem(env::media_folder() + L"/" + media_file, true);
339
340         if (filename.empty())
341                 return core::draw_frame::empty();
342
343         auto render_specific_frame = [&](std::int64_t frame_num)
344         {
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);
349                 pipeline.start();
350
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));
354                 return frame;
355         };
356
357         auto info = info_repo->get(filename);
358
359         if (!info)
360                 return core::draw_frame::empty();
361
362         auto total_frames = info->duration;
363         auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
364
365         if (grid < 1)
366         {
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"));
369         }
370
371         if (grid == 1)
372         {
373                 return render_specific_frame(total_frames / 2);
374         }
375
376         auto num_snapshots = grid * grid;
377
378         std::vector<core::draw_frame> frames;
379
380         for (int i = 0; i < num_snapshots; ++i)
381         {
382                 int x = i % grid;
383                 int y = i / grid;
384                 std::int64_t desired_frame;
385
386                 if (i == 0)
387                         desired_frame = 0; // first
388                 else if (i == num_snapshots - 1)
389                         desired_frame = total_frames - 2; // last
390                 else
391                         // evenly distributed across the file.
392                         desired_frame = total_frames * i / (num_snapshots - 1);
393
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;
399
400                 frames.push_back(frame);
401         }
402
403         return core::draw_frame(frames);
404 }
405
406 }}