]> git.sesse.net Git - casparcg/blobdiff - modules/ffmpeg/producer/ffmpeg_producer.cpp
* Ported thread local disabling of ffmpeg producer logging from 2.0 and move some...
[casparcg] / modules / ffmpeg / producer / ffmpeg_producer.cpp
index 0dffaaea4c1127dc96207af5ee3de9fbe33250a4..8dcf6fbd888b7f6806f45f2291d66e0b4e8c3d18 100644 (file)
 * Author: Robert Nagy, ronag89@gmail.com
 */
 
-#include "../stdafx.h"
+#include "../StdAfx.h"
 
 #include "ffmpeg_producer.h"
 
 #include "../ffmpeg_error.h"
+#include "../ffmpeg.h"
 
 #include "muxer/frame_muxer.h"
 #include "input/input.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 <boost/algorithm/string.hpp>
-#include <common/assert.h>
-#include <boost/assign.hpp>
-#include <boost/timer.hpp>
-#include <boost/foreach.hpp>
 #include <boost/filesystem.hpp>
-#include <boost/range/algorithm/find_if.hpp>
-#include <boost/range/algorithm/find.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/regex.hpp>
 #include <boost/thread/future.hpp>
 #include <queue>
 
 namespace caspar { namespace ffmpeg {
-                               
+
+struct seek_out_of_range : virtual user_error {};
+
+std::wstring get_relative_or_original(
+               const std::wstring& filename,
+               const boost::filesystem::path& relative_to)
+{
+       boost::filesystem::path file(filename);
+       auto result = file.filename().wstring();
+
+       boost::filesystem::path current_path = file;
+
+       while (true)
+       {
+               current_path = current_path.parent_path();
+
+               if (boost::filesystem::equivalent(current_path, relative_to))
+                       break;
+
+               if (current_path.empty())
+                       return filename;
+
+               result = current_path.filename().wstring() + L"/" + result;
+       }
+
+       return result;
+}
+
 struct ffmpeg_producer : public core::frame_producer_base
 {
-       monitor::subject                                                                monitor_subject_;
+       spl::shared_ptr<core::monitor::subject>                 monitor_subject_;
        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_;
                                        
@@ -76,34 +104,34 @@ struct ffmpeg_producer : public core::frame_producer_base
 
        input                                                                                   input_; 
 
-       const double                                                                    fps_;
+       const double                                                                    fps_                                    = read_fps(input_.context(), format_desc_.fps);
        const uint32_t                                                                  start_;
                
        std::unique_ptr<video_decoder>                                  video_decoder_;
        std::unique_ptr<audio_decoder>                                  audio_decoder_; 
-       frame_muxer                                                                             muxer_;
+       std::unique_ptr<frame_muxer>                                    muxer_;
        core::constraints                                                               constraints_;
        
-       core::draw_frame                                                                last_frame_;
+       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& filename, 
-                                                        const std::wstring& filter, 
-                                                        bool loop, 
-                                                        uint32_t start, 
-                                                        uint32_t length) 
+       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)
                : filename_(filename)
                , frame_factory_(frame_factory)         
                , format_desc_(format_desc)
                , input_(graph_, filename_, loop, start, length)
                , fps_(read_fps(input_.context(), format_desc_.fps))
-               , muxer_(fps_, frame_factory, format_desc_, filter)
                , start_(start)
-               , last_frame_(core::draw_frame::empty())
        {
                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));   
@@ -113,11 +141,12 @@ public:
                try
                {
                        video_decoder_.reset(new video_decoder(input_));
-                       video_decoder_->monitor_output().link_target(&monitor_subject_);
+                       video_decoder_->monitor_output().attach_parent(monitor_subject_);
                        constraints_.width.set(video_decoder_->width());
                        constraints_.height.set(video_decoder_->height());
                        
-                       CASPAR_LOG(info) << print() << L" " << video_decoder_->print();
+                       if (!is_logging_disabled_for_thread())
+                               CASPAR_LOG(info) << print() << L" " << video_decoder_->print();
                }
                catch(averror_stream_not_found&)
                {
@@ -129,10 +158,14 @@ public:
                        CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video.";        
                }
 
+               auto channel_layout = core::audio_channel_layout::invalid();
+
                try
                {
-                       audio_decoder_ .reset(new audio_decoder(input_, format_desc_));
-                       audio_decoder_->monitor_output().link_target(&monitor_subject_);
+                       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();
                }
@@ -144,11 +177,17 @@ public:
                {
                        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();
 
-               CASPAR_LOG(info) << print() << L" Initialized";
+               if (!is_logging_disabled_for_thread())
+                       CASPAR_LOG(info) << print() << L" Initialized";
        }
 
        // frame_producer
@@ -157,30 +196,29 @@ public:
        {                               
                auto frame = core::draw_frame::late();          
                
-               boost::timer frame_timer;
+               caspar::timer frame_timer;
                
                end_seek();
                                
                decode_next_frame();
                
-               if(!muxer_.empty())
+               if(!muxer_->empty())
                {
-                       last_frame_ = frame = std::move(muxer_.front());
-                       muxer_.pop();   
+                       last_frame_ = frame = std::move(muxer_->front());
+                       muxer_->pop();
                }
-               else                            
-                       graph_->set_tag("underflow");
-                                                                       
+               else if (!input_.eof())
+                       graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow");
+
                graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5);
-               monitor_subject_        << monitor::message("/profiler/time") % frame_timer.elapsed() % (1.0/format_desc_.fps);                 
-                                                               
-               monitor_subject_        << monitor::message("/file/time")                       % (file_frame_number()/fps_) 
-                                                                                                                               % (file_nb_frames()/fps_)
-                                                       << monitor::message("/file/frame")                      % static_cast<int32_t>(file_frame_number())
-                                                                                                                               % static_cast<int32_t>(file_nb_frames())
-                                                       << monitor::message("/file/fps")                        % fps_
-                                                       << monitor::message("/file/path")                       % filename_
-                                                       << monitor::message("/loop")                            % input_.loop();
+               *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/path")         % path_relative_to_media_
+                               << core::monitor::message("/loop")                      % input_.loop();
                                                
                return frame;
        }
@@ -204,7 +242,7 @@ public:
                uint32_t nb_frames = file_nb_frames();
 
                nb_frames = std::min(input_.length(), nb_frames);
-               nb_frames = muxer_.calc_nb_frames(nb_frames);
+               nb_frames = muxer_->calc_nb_frames(nb_frames);
                
                return nb_frames > start_ ? nb_frames - start_ : 0;
        }
@@ -222,12 +260,12 @@ public:
                return video_decoder_ ? video_decoder_->file_frame_number() : 0;
        }
                
-       boost::unique_future<std::wstring> call(const std::vector<std::wstring>& params) override
+       std::future<std::wstring> call(const std::vector<std::wstring>& params) override
        {
-               static const boost::wregex loop_exp(L"LOOP\\s*(?<VALUE>\\d?)?", boost::regex::icase);
-               static const boost::wregex seek_exp(L"SEEK\\s+(?<VALUE>\\d+)", boost::regex::icase);
-               static const boost::wregex length_exp(L"LENGTH\\s+(?<VALUE>\\d+)?", boost::regex::icase);
-               static const boost::wregex start_exp(L"START\\s+(?<VALUE>\\d+)?", boost::regex::icase);
+               static const boost::wregex loop_exp(LR"(LOOP\s*(?<VALUE>\d?)?)", boost::regex::icase);
+               static const boost::wregex seek_exp(LR"(SEEK\s+(?<VALUE>\d+))", boost::regex::icase);
+               static const boost::wregex length_exp(LR"(LENGTH\s+(?<VALUE>\d+)?)", boost::regex::icase);
+               static const boost::wregex start_exp(LR"(START\\s+(?<VALUE>\\d+)?)", boost::regex::icase);
 
                auto param = boost::algorithm::join(params, L" ");
                
@@ -263,7 +301,7 @@ public:
                else
                        CASPAR_THROW_EXCEPTION(invalid_argument());
 
-               return async(launch::deferred, [=]{return result;});
+               return make_ready_future(std::move(result));
        }
                                
        std::wstring print() const override
@@ -296,9 +334,9 @@ public:
                return info;
        }
        
-       monitor::source& monitor_output()
+       core::monitor::subject& monitor_output()
        {
-               return monitor_subject_;
+               return *monitor_subject_;
        }
 
        // ffmpeg_producer
@@ -308,9 +346,9 @@ public:
                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())
+                       if(!muxer_->empty())
                        {
-                               last_frame_ = muxer_.front();
+                               last_frame_ = muxer_->front();
                                seek_target_.reset();
                        }
                }
@@ -348,10 +386,13 @@ public:
 
        void seek(uint32_t target)
        {               
-               seek_target_ = std::min(target, file_nb_frames());
+               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();
+               muxer_->clear();
        }
 
        std::wstring print_mode() const
@@ -364,30 +405,72 @@ public:
                        
        void decode_next_frame()
        {
-               for(int n = 0; n < 8 && muxer_.empty(); ++n)
-               {                               
-                       if(!muxer_.video_ready())
-                               muxer_.push_video(video_decoder_ ? (*video_decoder_)() : create_frame());
-                       if(!muxer_.audio_ready())
-                               muxer_.push_audio(audio_decoder_ ? (*audio_decoder_)() : create_frame());
+               for(int n = 0; n < 32 && muxer_->empty(); ++n)
+               {
+                       if(!muxer_->video_ready())
+                               muxer_->push_video(video_decoder_ ? (*video_decoder_)() : create_frame());
+                       if(!muxer_->audio_ready())
+                               muxer_->push_audio(audio_decoder_ ? (*audio_decoder_)() : create_frame());
                }
+
                graph_->set_text(print());
        }
 };
 
-spl::shared_ptr<core::frame_producer> create_producer(const spl::shared_ptr<core::frame_factory>& frame_factory, const core::video_format_desc& format_desc, const std::vector<std::wstring>& params)
+void describe_producer(core::help_sink& sink, const core::help_repository& repo)
+{
+       sink.short_description(L"A producer for playing media files supported by FFmpeg.");
+       sink.syntax(L"[clip:string] {[loop:LOOP]} {START,SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}");
+       sink.para()
+               ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ")
+               ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ")
+               ->text(L"H.264, FLV, WMV and several audio codecs as well as uncompressed audio.");
+       sink.definitions()
+               ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.")
+               ->item(L"loop", L"Will cause the media file to loop between start and start + length")
+               ->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.")
+               ->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.")
+               ->item(L"filter", L"If specified, will be used as an FFmpeg video filter.")
+               ->item(L"channel_layout",
+                               L"Optionally override the automatically deduced audio channel layout. "
+                               L"Either a named layout as specified in casparcg.config or in the format [type:string]:[channel_order:string] for a custom layout.");
+       sink.para()->text(L"Examples:");
+       sink.example(L">> PLAY 1-10 folder/clip", L"to play all frames in a clip and stop at the last frame.");
+       sink.example(L">> PLAY 1-10 folder/clip LOOP", L"to loop a clip between the first frame and the last frame.");
+       sink.example(L">> PLAY 1-10 folder/clip LOOP START 10", L"to loop a clip between frame 10 and the last frame.");
+       sink.example(L">> PLAY 1-10 folder/clip LOOP START 10 LENGTH 50", L"to loop a clip between frame 10 and frame 60.");
+       sink.example(L">> PLAY 1-10 folder/clip START 10 LENGTH 50", L"to play frames 10-60 in a clip and stop.");
+       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.example(L">> CALL 1-10 LOOP 1");
+       sink.example(L">> CALL 1-10 START 10");
+       sink.example(L">> CALL 1-10 LENGTH 50");
+}
+
+spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
 {              
-       auto filename = probe_stem(env::media_folder() + L"\\" + params.at(0));
+       auto filename = probe_stem(env::media_folder() + L"/" + params.at(0));
 
        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"");    
-       
-       return create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(frame_factory, format_desc, filename, filter_str, loop, start, length)));
+       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"");
+
+       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)));
 }
 
-}}
\ No newline at end of file
+}}