]> git.sesse.net Git - casparcg/blobdiff - modules/ffmpeg/producer/input/input.cpp
[ffmpeg] Copied flush logic when seeking from 2.0, as well as current frame in clip...
[casparcg] / modules / ffmpeg / producer / input / input.cpp
index 9a4befa0bd7f8f427c630e1d42cd4384092b926b..c51cf152d406c503b0807e55e139664eba10a045 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "../util/util.h"
 #include "../../ffmpeg_error.h"
+#include "../../ffmpeg.h"
 
 #include <common/diagnostics/graph.h>
 #include <common/executor.h>
@@ -59,6 +60,7 @@ extern "C"
 
 namespace caspar { namespace ffmpeg {
 
+static const int MAX_PUSH_WITHOUT_POP = 200;
 static const int MIN_FRAMES = 25;
 
 class stream
@@ -68,36 +70,53 @@ class stream
 
        typedef tbb::concurrent_bounded_queue<std::shared_ptr<AVPacket>>::size_type size_type;
 
-       int                                                                                                              index_;
-       tbb::concurrent_bounded_queue<std::shared_ptr<AVPacket>> packets_;
+       int                                                                                                                     index_;
+       tbb::concurrent_bounded_queue<std::shared_ptr<AVPacket>>        packets_;
+       tbb::atomic<int>                                                                                        push_since_pop_;
 public:
 
        stream(int index) 
                : index_(index)
        {
+               push_since_pop_ = 0;
        }
 
+       stream(stream&&) = default;
+
        bool is_available() const
        {
                return index_ >= 0;
        }
+
+       int index() const
+       {
+               return index_;
+       }
        
        void push(const std::shared_ptr<AVPacket>& packet)
        {
                if(packet && packet->data && packet->stream_index != index_)
                        return;
 
+               if (++push_since_pop_ > MAX_PUSH_WITHOUT_POP) // Out of memory protection for streams never being used.
+               {
+                       return;
+               }
+
                packets_.push(packet);
        }
 
        bool try_pop(std::shared_ptr<AVPacket>& packet)
        {
+               push_since_pop_ = 0;
+
                return packets_.try_pop(packet);
        }
 
        void clear()
        {
                std::shared_ptr<AVPacket> packet;
+               push_since_pop_ = 0;
                while(packets_.try_pop(packet));
        }
                
@@ -118,11 +137,12 @@ struct input::impl : boost::noncopyable
        tbb::atomic<uint32_t>                                           start_;         
        tbb::atomic<uint32_t>                                           length_;
        tbb::atomic<bool>                                                       loop_;
+       tbb::atomic<bool>                                                       eof_;
        double                                                                          fps_                                    = read_fps(*format_context_, 0.0);
        uint32_t                                                                        frame_number_                   = 0;
 
-       stream                                                                          video_stream_                   { av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0) };
-       stream                                                                          audio_stream_                   { av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) };
+       stream                                                                          video_stream_                   {                                                       av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0) };
+       std::vector<stream>                                                     audio_streams_;
 
        boost::optional<uint32_t>                                       seek_target_;
 
@@ -131,13 +151,20 @@ struct input::impl : boost::noncopyable
        boost::condition_variable                                       cond_;
        boost::thread                                                           thread_;
        
-       impl(const spl::shared_ptr<diagnostics::graph> graph, const std::wstring& filename, const bool loop, const uint32_t start, const uint32_t length) 
+       impl(
+                       const spl::shared_ptr<diagnostics::graph> graph,
+                       const std::wstring& filename,
+                       const bool loop,
+                       const uint32_t start,
+                       const uint32_t length,
+                       bool thumbnail_mode)
                : graph_(graph)
                , filename_(filename)
-       {               
+       {
                start_                  = start;
                length_                 = length;
                loop_                   = loop;
+               eof_                    = false;
                is_running_             = true;
 
                if(start_ != 0)
@@ -145,8 +172,13 @@ struct input::impl : boost::noncopyable
                                                                                                                
                graph_->set_color("seek", diagnostics::color(1.0f, 0.5f, 0.0f));
 
-               if (audio_stream_.is_available())
-                       graph_->set_color("audio-buffer", diagnostics::color(0.7f, 0.4f, 0.4f));
+               if (!thumbnail_mode)
+                       for (unsigned i = 0; i < format_context_->nb_streams; ++i)
+                               if (format_context_->streams[i]->codec->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO)
+                                       audio_streams_.emplace_back(i);
+
+               for (int i = 0; i < audio_streams_.size(); ++i)
+                       graph_->set_color("audio-buffer" + boost::lexical_cast<std::string>(i + 1), diagnostics::color(0.7f, 0.4f, 0.4f));
 
                if (video_stream_.is_available())
                        graph_->set_color("video-buffer", diagnostics::color(1.0f, 1.0f, 0.0f));
@@ -154,7 +186,7 @@ struct input::impl : boost::noncopyable
                for(int n = 0; n < 8; ++n)
                        tick();
 
-               thread_ = boost::thread([this]{run();});
+               thread_ = boost::thread([this, thumbnail_mode]{run(thumbnail_mode);});
        }
 
        ~impl()
@@ -170,24 +202,27 @@ struct input::impl : boost::noncopyable
                        return false;
 
                bool result = video_stream_.try_pop(packet);
+
                if(result)
                        cond_.notify_one();
                
-               graph_->set_value("video-buffer", std::min(1.0, static_cast<double>(video_stream_.size()/MIN_FRAMES)));
+               graph_->set_value("video-buffer", std::min(1.0, static_cast<double>(video_stream_.size())/MIN_FRAMES));
                                
                return result;
        }
        
-       bool try_pop_audio(std::shared_ptr<AVPacket>& packet)
+       bool try_pop_audio(std::shared_ptr<AVPacket>& packet, int audio_stream_index)
        {
-               if (!audio_stream_.is_available())
+               if (audio_streams_.size() < audio_stream_index + 1)
                        return false;
 
-               bool result = audio_stream_.try_pop(packet);
+               auto& audio_stream = audio_streams_.at(audio_stream_index);
+               bool result = audio_stream.try_pop(packet);
                if(result)
                        cond_.notify_one();
-                               
-               graph_->set_value("audio-buffer", std::min(1.0, static_cast<double>(audio_stream_.size()/MIN_FRAMES)));
+
+               auto buffer_nr = boost::lexical_cast<std::string>(audio_stream_index + 1);
+               graph_->set_value("audio-buffer" + buffer_nr, std::min(1.0, static_cast<double>(audio_stream.size())/MIN_FRAMES));
 
                return result;
        }
@@ -199,11 +234,21 @@ struct input::impl : boost::noncopyable
 
                        seek_target_ = target;
                        video_stream_.clear();
-                       audio_stream_.clear();
+
+                       for (auto& audio_stream : audio_streams_)
+                               audio_stream.clear();
                }
-               
+
                cond_.notify_one();
        }
+
+       int get_actual_audio_stream_index(int audio_stream_index) const
+       {
+               if (audio_stream_index + 1 > audio_streams_.size())
+                       CASPAR_THROW_EXCEPTION(averror_stream_not_found());
+
+               return audio_streams_.at(audio_stream_index).index();
+       }
                
        std::wstring print() const
        {
@@ -213,9 +258,13 @@ struct input::impl : boost::noncopyable
 private:
        void internal_seek(uint32_t target)
        {
+               eof_ = false;
                graph_->set_tag(diagnostics::tag_severity::INFO, "seek");
 
-               CASPAR_LOG(debug) << print() << " Seeking: " << target;
+               if (is_logging_quiet_for_thread())
+                       CASPAR_LOG(trace) << print() << " Seeking: " << target;
+               else
+                       CASPAR_LOG(debug) << print() << " Seeking: " << target;
 
                int flags = AVSEEK_FLAG_FRAME;
                if(target == 0)
@@ -230,19 +279,27 @@ private:
                        }
                }
                
-               auto stream     = format_context_->streams[default_stream_index_];
-               auto fps        = read_fps(*format_context_, 0.0);
+               auto stream                             = format_context_->streams[default_stream_index_];
+               auto fps                                = read_fps(*format_context_, 0.0);
+               auto target_timestamp   = static_cast<int64_t>((target / fps * stream->time_base.den) / stream->time_base.num);
                
                THROW_ON_ERROR2(avformat_seek_file(
                                format_context_.get(),
                                default_stream_index_,
                                std::numeric_limits<int64_t>::min(),
-                               static_cast<int64_t>((target / fps * stream->time_base.den) / stream->time_base.num),
+                               target_timestamp,
                                std::numeric_limits<int64_t>::max(),
                                0), print());
+
+               auto flush_packet       = create_packet();
+               flush_packet->data      = nullptr;
+               flush_packet->size      = 0;
+               flush_packet->pos       = target;
                
-               video_stream_.push(nullptr);
-               audio_stream_.push(nullptr);
+               video_stream_.push(flush_packet);
+
+               for (auto& audio_stream : audio_streams_)
+                       audio_stream.push(flush_packet);
        }
 
        void tick()
@@ -263,8 +320,7 @@ private:
                                internal_seek(start_);
                        else
                        {
-                               audio_stream_.push(packet);
-                               video_stream_.push(packet);
+                               eof_ = true;
                        }
                }
                else
@@ -292,25 +348,39 @@ private:
                        if(packet_frame_number >= start_ && packet_frame_number < length_)
                        {
                                video_stream_.push(packet);
-                               audio_stream_.push(packet);
+
+                               for (auto& audio_stream : audio_streams_)
+                                       audio_stream.push(packet);
                        }
                }       
 
                if (video_stream_.is_available())
-                       graph_->set_value("video-buffer", std::min(1.0, static_cast<double>(video_stream_.size()/MIN_FRAMES)));
+                       graph_->set_value("video-buffer", std::min(1.0, static_cast<double>(video_stream_.size())/MIN_FRAMES));
 
-               if (audio_stream_.is_available())
-                       graph_->set_value("audio-buffer", std::min(1.0, static_cast<double>(audio_stream_.size()/MIN_FRAMES)));
+               for (int i = 0; i < audio_streams_.size(); ++i)
+                       graph_->set_value(
+                                       "audio-buffer" + boost::lexical_cast<std::string>(i + 1),
+                                       std::min(1.0, static_cast<double>(audio_streams_[i].size())/MIN_FRAMES));
        }
                        
        bool full() const
        {
-               return video_stream_.size() >= MIN_FRAMES && audio_stream_.size() >= MIN_FRAMES;
+               bool video_full = video_stream_.size() >= MIN_FRAMES;
+
+               if (!video_full)
+                       return false;
+
+               for (auto& audio_stream : audio_streams_)
+                       if (audio_stream.size() < MIN_FRAMES)
+                               return false;
+
+               return true;
        }
 
-       void run()
+       void run(bool thumbnail_mode)
        {
                ensure_gpf_handler_installed_for_thread(u8(print()).c_str());
+               auto quiet_logging = temporary_enable_quiet_logging_for_thread(thumbnail_mode);
 
                while(is_running_)
                {
@@ -320,7 +390,7 @@ private:
                                {
                                        boost::unique_lock<boost::mutex> lock(mutex_);
 
-                                       while(full() && !seek_target_ && is_running_)
+                                       while((eof_ || full()) && !seek_target_ && is_running_)
                                                cond_.wait(lock);
                                        
                                        tick();
@@ -341,10 +411,12 @@ private:
        }
 };
 
-input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length) 
-       : impl_(new impl(graph, filename, loop, start, length)){}
+input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode)
+       : impl_(new impl(graph, filename, loop, start, length, thumbnail_mode)){}
+int input::get_actual_audio_stream_index(int audio_stream_index) const { return impl_->get_actual_audio_stream_index(audio_stream_index); };
+int input::num_audio_streams() const { return static_cast<int>(impl_->audio_streams_.size()); }
 bool input::try_pop_video(std::shared_ptr<AVPacket>& packet){return impl_->try_pop_video(packet);}
-bool input::try_pop_audio(std::shared_ptr<AVPacket>& packet){return impl_->try_pop_audio(packet);}
+bool input::try_pop_audio(std::shared_ptr<AVPacket>& packet, int audio_stream_index){return impl_->try_pop_audio(packet, audio_stream_index);}
 AVFormatContext& input::context(){return *impl_->format_context_;}
 void input::loop(bool value){impl_->loop_ = value;}
 bool input::loop() const{return impl_->loop_;}
@@ -353,4 +425,5 @@ void input::start(uint32_t value){impl_->start_ = value;}
 uint32_t input::start() const{return impl_->start_;}
 void input::length(uint32_t value){impl_->length_ = value;}
 uint32_t input::length() const{return impl_->length_;}
+bool input::eof() const { return impl_->eof_; }
 }}