1 #include "..\..\stdafx.h"
\r
5 #include "../../format/video_format.h"
\r
7 #include <common/concurrency/executor.h>
\r
9 #include <tbb/concurrent_queue.h>
\r
10 #include <tbb/queuing_mutex.h>
\r
12 #include <boost/exception/error_info.hpp>
\r
13 #include <boost/thread/once.hpp>
\r
16 #include <system_error>
\r
18 #if defined(_MSC_VER)
\r
19 #pragma warning (disable : 4244)
\r
25 #define __STDC_CONSTANT_MACROS
\r
26 #define __STDC_LIMIT_MACROS
\r
27 #include <libavformat/avformat.h>
\r
30 namespace caspar { namespace core { namespace ffmpeg{
\r
32 struct input::implementation : boost::noncopyable
\r
34 static const size_t BUFFER_SIZE = 2 << 25;
\r
36 implementation(const std::wstring& filename) : video_s_index_(-1), audio_s_index_(-1), filename_(filename)
\r
38 static boost::once_flag av_register_all_flag = BOOST_ONCE_INIT;
\r
39 boost::call_once(av_register_all, av_register_all_flag);
\r
41 static boost::once_flag avcodec_init_flag = BOOST_ONCE_INIT;
\r
42 boost::call_once(avcodec_init, avcodec_init_flag);
\r
47 AVFormatContext* weak_format_context_;
\r
48 if((errn = -av_open_input_file(&weak_format_context_, narrow(filename).c_str(), nullptr, 0, nullptr)) > 0)
\r
49 BOOST_THROW_EXCEPTION(
\r
50 file_read_error() <<
\r
51 msg_info("No format context found.") <<
\r
52 boost::errinfo_api_function("av_open_input_file") <<
\r
53 boost::errinfo_errno(errn) <<
\r
54 boost::errinfo_file_name(narrow(filename)));
\r
56 format_context_.reset(weak_format_context_, av_close_input_file);
\r
58 if((errn = -av_find_stream_info(format_context_.get())) > 0)
\r
59 BOOST_THROW_EXCEPTION(
\r
60 file_read_error() <<
\r
61 boost::errinfo_api_function("av_find_stream_info") <<
\r
62 msg_info("No stream found.") <<
\r
63 boost::errinfo_errno(errn));
\r
65 video_codec_context_ = open_stream(CODEC_TYPE_VIDEO, video_s_index_);
\r
66 if(!video_codec_context_)
\r
67 CASPAR_LOG(warning) << "Could not open any video stream.";
\r
69 audio_codex_context_ = open_stream(CODEC_TYPE_AUDIO, audio_s_index_);
\r
70 if(!audio_codex_context_)
\r
71 CASPAR_LOG(warning) << "Could not open any audio stream.";
\r
73 if(!video_codec_context_ && !audio_codex_context_)
\r
74 BOOST_THROW_EXCEPTION(file_read_error() << msg_info("No video or audio codec context found."));
\r
77 executor_.begin_invoke([this]{read_file();});
\r
78 CASPAR_LOG(info) << print() << " started.";
\r
83 CASPAR_LOG(info) << print() << " ended.";
\r
86 std::shared_ptr<AVCodecContext> open_stream(int codec_type, int& s_index)
\r
88 AVStream** streams_end = format_context_->streams+format_context_->nb_streams;
\r
89 AVStream** stream = std::find_if(format_context_->streams, streams_end,
\r
90 [&](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == codec_type ;});
\r
92 if(stream == streams_end)
\r
95 s_index = (*stream)->index;
\r
97 auto codec = avcodec_find_decoder((*stream)->codec->codec_id);
\r
98 if(codec == nullptr)
\r
101 if((-avcodec_open((*stream)->codec, codec)) > 0)
\r
104 return std::shared_ptr<AVCodecContext>((*stream)->codec, avcodec_close);
\r
107 void read_file() // For every packet taken: read in a number of packets.
\r
109 for(size_t n = 0; buffer_size_ < BUFFER_SIZE && (n < 3 || video_packet_buffer_.size() < 3 || audio_packet_buffer_.size() < 3) && executor_.is_running(); ++n)
\r
111 AVPacket tmp_packet;
\r
112 safe_ptr<AVPacket> read_packet(&tmp_packet, av_free_packet);
\r
113 tbb::queuing_mutex::scoped_lock lock(seek_mutex_);
\r
115 if (av_read_frame(format_context_.get(), read_packet.get()) >= 0) // NOTE: read_packet is only valid until next call of av_safe_ptr<read_frame> or av_close_input_file
\r
117 auto packet = std::make_shared<aligned_buffer>(read_packet->data, read_packet->data + read_packet->size);
\r
118 if(read_packet->stream_index == video_s_index_)
\r
120 buffer_size_ += packet->size();
\r
121 video_packet_buffer_.try_push(std::move(packet));
\r
123 else if(read_packet->stream_index == audio_s_index_)
\r
125 buffer_size_ += packet->size();
\r
126 audio_packet_buffer_.try_push(std::move(packet));
\r
129 else if(!loop_ || av_seek_frame(format_context_.get(), -1, 0, AVSEEK_FLAG_BACKWARD) < 0) // TODO: av_seek_frame does not work for all formats
\r
130 executor_.stop(executor::no_wait);
\r
134 aligned_buffer get_video_packet()
\r
136 return get_packet(video_packet_buffer_);
\r
139 aligned_buffer get_audio_packet()
\r
141 return get_packet(audio_packet_buffer_);
\r
144 aligned_buffer get_packet(tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>>& buffer)
\r
146 std::shared_ptr<aligned_buffer> packet;
\r
147 if(buffer.try_pop(packet))
\r
149 buffer_size_ -= packet->size();
\r
150 if(executor_.size() < 4)
\r
151 executor_.begin_invoke([this]{read_file();});
\r
152 return std::move(*packet);
\r
154 return aligned_buffer();
\r
157 bool is_eof() const
\r
159 return !executor_.is_running() && video_packet_buffer_.empty() && audio_packet_buffer_.empty();
\r
162 // TODO: Not properly done.
\r
163 bool seek(unsigned long long seek_target)
\r
165 tbb::queuing_mutex::scoped_lock lock(seek_mutex_);
\r
166 if(av_seek_frame(format_context_.get(), -1, seek_target*AV_TIME_BASE, 0) < 0)
\r
169 video_packet_buffer_.clear();
\r
170 audio_packet_buffer_.clear();
\r
171 // TODO: Not sure its enough to just flush in input class
\r
172 if(video_codec_context_)
\r
173 avcodec_flush_buffers(video_codec_context_.get());
\r
174 if(audio_codex_context_)
\r
175 avcodec_flush_buffers(audio_codex_context_.get());
\r
179 std::wstring print() const
\r
181 return L"ffmpeg[" + boost::filesystem::wpath(filename_).filename() + L"] Buffer thread";
\r
184 std::shared_ptr<AVFormatContext> format_context_; // Destroy this last
\r
186 tbb::queuing_mutex seek_mutex_;
\r
188 const std::wstring filename_;
\r
190 std::shared_ptr<AVCodecContext> video_codec_context_;
\r
192 std::shared_ptr<AVCodecContext> audio_codex_context_;
\r
194 tbb::atomic<bool> loop_;
\r
195 int video_s_index_;
\r
196 int audio_s_index_;
\r
198 tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> video_packet_buffer_;
\r
199 tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> audio_packet_buffer_;
\r
201 tbb::atomic<size_t> buffer_size_;
\r
203 executor executor_;
\r
206 input::input(const std::wstring& filename) : impl_(new implementation(filename)){}
\r
207 void input::set_loop(bool value){impl_->loop_ = value;}
\r
208 const std::shared_ptr<AVCodecContext>& input::get_video_codec_context() const{return impl_->video_codec_context_;}
\r
209 const std::shared_ptr<AVCodecContext>& input::get_audio_codec_context() const{return impl_->audio_codex_context_;}
\r
210 bool input::is_eof() const{return impl_->is_eof();}
\r
211 aligned_buffer input::get_video_packet(){return impl_->get_video_packet();}
\r
212 aligned_buffer input::get_audio_packet(){return impl_->get_audio_packet();}
\r
213 bool input::seek(unsigned long long frame){return impl_->seek(frame);}
\r