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