]> git.sesse.net Git - casparcg/blob - core/producer/ffmpeg/input.cpp
816930cf76c84c79551ead2163dc1fdbc8c63c44
[casparcg] / core / producer / ffmpeg / input.cpp
1 #include "..\..\stdafx.h"\r
2 \r
3 #include "input.h"\r
4 \r
5 #include "../../frame/frame_format.h"\r
6 #include "../../../common/utility/memory.h"\r
7 #include "../../../common/utility/scope_exit.h"\r
8 \r
9 #include <tbb/concurrent_queue.h>\r
10 #include <tbb/queuing_mutex.h>\r
11 \r
12 #include <boost/thread.hpp>\r
13 \r
14 #include <errno.h>\r
15 #include <system_error>\r
16 \r
17 #pragma warning(disable : 4482)\r
18                 \r
19 namespace caspar { namespace core { namespace ffmpeg{\r
20                 \r
21 struct input::implementation : boost::noncopyable\r
22 {\r
23         implementation(const frame_format_desc& format_desc) \r
24                 : video_frame_rate_(25.0), video_s_index_(-1), audio_s_index_(-1), video_codec_(nullptr), audio_codec_(nullptr), format_desc_(format_desc)\r
25         {\r
26                 loop_ = false;\r
27                 //file_buffer_size_ = 0;                \r
28                 video_packet_buffer_.set_capacity(25);\r
29                 audio_packet_buffer_.set_capacity(25);\r
30         }\r
31 \r
32         ~implementation()\r
33         {               \r
34                 stop();\r
35         }\r
36         \r
37         void stop()\r
38         {\r
39                 is_running_ = false;\r
40                 audio_packet_buffer_.clear();\r
41                 video_packet_buffer_.clear();\r
42                 //file_buffer_size_ = 0;\r
43                 //file_buffer_size_cond_.notify_all();\r
44                 io_thread_.join();\r
45         }\r
46 \r
47         void load(const std::string& filename)\r
48         {       \r
49                 try\r
50                 {\r
51                         int errn;\r
52                         AVFormatContext* weak_format_context_;\r
53                         if((errn = -av_open_input_file(&weak_format_context_, filename.c_str(), nullptr, 0, nullptr)) > 0)\r
54                                 BOOST_THROW_EXCEPTION(file_read_error() << msg_info("No video or audio codec found."));\r
55                         format_context_.reset(weak_format_context_, av_close_input_file);\r
56                         \r
57                         if((errn = -av_find_stream_info(format_context_.get())) > 0)\r
58                                 throw std::runtime_error("File read error");\r
59 \r
60                         video_codec_context_ = open_video_stream();\r
61                         if(!video_codec_context_)\r
62                                 CASPAR_LOG(warning) << "No video stream found.";\r
63                 \r
64                         audio_codex_context_ = open_audio_stream();\r
65                         if(!audio_codex_context_)\r
66                                 CASPAR_LOG(warning) << "No audio stream found.";\r
67 \r
68                         if(!video_codec_context_ && !audio_codex_context_)\r
69                                 BOOST_THROW_EXCEPTION(file_read_error() << msg_info("No video or audio codec found."));\r
70                         \r
71                         video_frame_rate_ = static_cast<double>(video_codec_context_->time_base.den) / static_cast<double>(video_codec_context_->time_base.num);                        \r
72                 }\r
73                 catch(...)\r
74                 {\r
75                         video_codec_context_.reset();\r
76                         audio_codex_context_.reset();\r
77                         format_context_.reset();\r
78                         video_frame_rate_ = 25.0;\r
79                         video_s_index_ = -1;\r
80                         audio_s_index_ = -1;    \r
81                         throw;\r
82                 }\r
83                 filename_ = filename;\r
84         }\r
85 \r
86         void start()\r
87         {\r
88                 io_thread_ = boost::thread([=]{read_file();});\r
89         }\r
90                         \r
91         std::shared_ptr<AVCodecContext> open_video_stream()\r
92         {               \r
93                 AVStream** streams_end = format_context_->streams+format_context_->nb_streams;\r
94                 AVStream** video_stream = std::find_if(format_context_->streams, streams_end, \r
95                         [](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == CODEC_TYPE_VIDEO ;});\r
96 \r
97                 video_s_index_ = video_stream != streams_end ? (*video_stream)->index : -1;\r
98                 if(video_s_index_ == -1) \r
99                         return nullptr;\r
100                 \r
101                 video_codec_ = avcodec_find_decoder((*video_stream)->codec->codec_id);                  \r
102                 if(video_codec_ == nullptr)\r
103                         return nullptr;\r
104                         \r
105                 if((-avcodec_open((*video_stream)->codec, video_codec_)) > 0)           \r
106                         return nullptr;\r
107 \r
108                 return std::shared_ptr<AVCodecContext>((*video_stream)->codec, avcodec_close);\r
109         }\r
110 \r
111         std::shared_ptr<AVCodecContext> open_audio_stream()\r
112         {       \r
113                 AVStream** streams_end = format_context_->streams+format_context_->nb_streams;\r
114                 AVStream** audio_stream = std::find_if(format_context_->streams, streams_end, \r
115                         [](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == CODEC_TYPE_AUDIO;});\r
116 \r
117                 audio_s_index_ = audio_stream != streams_end ? (*audio_stream)->index : -1;\r
118                 if(audio_s_index_ == -1)\r
119                         return nullptr;\r
120                 \r
121                 audio_codec_ = avcodec_find_decoder((*audio_stream)->codec->codec_id);\r
122                 if(audio_codec_ == nullptr)\r
123                         return nullptr;\r
124 \r
125                 if((-avcodec_open((*audio_stream)->codec, audio_codec_)) > 0)           \r
126                         return nullptr;\r
127 \r
128                 return std::shared_ptr<AVCodecContext>((*audio_stream)->codec, avcodec_close);\r
129         }       \r
130 \r
131         void read_file()\r
132         {       \r
133                 CASPAR_LOG(info) << "Started ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
134                 win32_exception::install_handler();\r
135                 \r
136                 is_running_ = true;\r
137                 AVPacket tmp_packet;\r
138                 while(is_running_)\r
139                 {\r
140                         std::shared_ptr<AVPacket> packet(&tmp_packet, av_free_packet);  \r
141                         tbb::queuing_mutex::scoped_lock lock(seek_mutex_);      \r
142 \r
143                         if (av_read_frame(format_context_.get(), packet.get()) >= 0) // NOTE: Packet is only valid until next call of av_read_frame or av_close_input_file\r
144                         {\r
145                                 if(packet->stream_index == video_s_index_)              \r
146                                 {\r
147                                         video_packet_buffer_.push(std::make_shared<video_packet>(packet, format_desc_, video_codec_context_.get(), video_codec_)); // NOTE: video_packet makes a copy of AVPacket\r
148                                         //file_buffer_size_ += packet->size;\r
149                                 }\r
150                                 else if(packet->stream_index == audio_s_index_)         \r
151                                 {\r
152                                         audio_packet_buffer_.push(std::make_shared<audio_packet>(packet, audio_codex_context_.get(), audio_codec_, video_frame_rate_));         \r
153                                         //file_buffer_size_ += packet->size;\r
154                                 }\r
155                         }\r
156                         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
157                                 is_running_ = false;\r
158                         \r
159                         //if(is_running_)\r
160                         //{\r
161                         //      boost::unique_lock<boost::mutex> lock(file_buffer_size_mutex_);\r
162                         //      while(file_buffer_size_ > 32*1000000)\r
163                         //              file_buffer_size_cond_.wait(lock);      \r
164                         //}\r
165                 }\r
166                 \r
167                 is_running_ = false;\r
168                 \r
169                 CASPAR_LOG(info) << " Ended ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
170         }\r
171         \r
172         video_packet_ptr get_video_packet()\r
173         {\r
174                 video_packet_ptr video_packet;\r
175                 if(video_packet_buffer_.try_pop(video_packet))\r
176                 {\r
177                         //file_buffer_size_ -= video_packet->size;\r
178                         //file_buffer_size_cond_.notify_all();\r
179                 }\r
180                 return video_packet;\r
181         }\r
182 \r
183         audio_packet_ptr get_audio_packet()\r
184         {\r
185                 audio_packet_ptr audio_packet;\r
186                 if(audio_packet_buffer_.try_pop(audio_packet))\r
187                 {\r
188                         //file_buffer_size_ -= audio_packet->size;\r
189                         //file_buffer_size_cond_.notify_all();\r
190                 }\r
191                 return audio_packet;\r
192         }\r
193 \r
194         bool is_eof() const\r
195         {\r
196                 return !is_running_ && video_packet_buffer_.empty() && audio_packet_buffer_.empty();\r
197         }\r
198         \r
199         bool seek(unsigned long long seek_target)\r
200         {\r
201                 tbb::queuing_mutex::scoped_lock lock(seek_mutex_);\r
202                 if(av_seek_frame(format_context_.get(), -1, seek_target*AV_TIME_BASE, 0) >= 0)\r
203                 {\r
204                         video_packet_buffer_.clear();\r
205                         audio_packet_buffer_.clear();\r
206                         // TODO: Not sure its enough to jsut flush in input class\r
207                         if(video_codec_context_)\r
208                                 avcodec_flush_buffers(video_codec_context_.get());\r
209                         if(audio_codex_context_)\r
210                                 avcodec_flush_buffers(audio_codex_context_.get());\r
211                         return true;\r
212                 }\r
213                 \r
214                 return false;\r
215         }\r
216         \r
217         //int                                                                   file_buffer_max_size_;\r
218         //tbb::atomic<int>                                      file_buffer_size_;\r
219         //boost::condition_variable                     file_buffer_size_cond_;\r
220         //boost::mutex                                          file_buffer_size_mutex_;\r
221                 \r
222         tbb::queuing_mutex                                      seek_mutex_;\r
223 \r
224         std::string                                                     filename_;\r
225         std::shared_ptr<AVFormatContext>        format_context_;        // Destroy this last\r
226 \r
227         std::shared_ptr<AVCodecContext>         video_codec_context_;\r
228         AVCodec*                                                        video_codec_;\r
229 \r
230         std::shared_ptr<AVCodecContext>         audio_codex_context_;\r
231         AVCodec*                                                        audio_codec_;\r
232 \r
233         tbb::atomic<bool>                                       loop_;\r
234         int                                                                     video_s_index_;\r
235         int                                                                     audio_s_index_;\r
236         \r
237         tbb::concurrent_bounded_queue<video_packet_ptr> video_packet_buffer_;\r
238         tbb::concurrent_bounded_queue<audio_packet_ptr> audio_packet_buffer_;\r
239         boost::thread   io_thread_;\r
240         tbb::atomic<bool> is_running_;\r
241 \r
242         double video_frame_rate_;\r
243 \r
244         frame_format_desc format_desc_;\r
245 };\r
246 \r
247 input::input(const frame_format_desc& format_desc) : impl_(new implementation(format_desc)){}\r
248 void input::load(const std::string& filename){impl_->load(filename);}\r
249 void input::set_loop(bool value){impl_->loop_ = value;}\r
250 const std::shared_ptr<AVCodecContext>& input::get_video_codec_context() const{return impl_->video_codec_context_;}\r
251 const std::shared_ptr<AVCodecContext>& input::get_audio_codec_context() const{return impl_->audio_codex_context_;}\r
252 bool input::is_eof() const{return impl_->is_eof();}\r
253 video_packet_ptr input::get_video_packet(){return impl_->get_video_packet();}\r
254 audio_packet_ptr input::get_audio_packet(){return impl_->get_audio_packet();}\r
255 bool input::seek(unsigned long long frame){return impl_->seek(frame);}\r
256 void input::start(){impl_->start();}\r
257 }}}