]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/producer/input.cpp
2.0.1: ffmpeg: Replaced TBB implementation with better Concurrency Runtime based...
[casparcg] / modules / ffmpeg / producer / input.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This file is part of CasparCG.\r
5 *\r
6 *    CasparCG is free software: you can redistribute it and/or modify\r
7 *    it under the terms of the GNU General Public License as published by\r
8 *    the Free Software Foundation, either version 3 of the License, or\r
9 *    (at your option) any later version.\r
10 *\r
11 *    CasparCG is distributed in the hope that it will be useful,\r
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14 *    GNU General Public License for more details.\r
15 \r
16 *    You should have received a copy of the GNU General Public License\r
17 *    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
18 *\r
19 */\r
20 #if defined(_MSC_VER)\r
21 #pragma warning (disable : 4244)\r
22 #endif\r
23 \r
24 #include "../stdafx.h"\r
25 \r
26 #include "input.h"\r
27 #include "util.h"\r
28 #include "../ffmpeg_error.h"\r
29 #include "../tbb_avcodec.h"\r
30 \r
31 #include <core/video_format.h>\r
32 \r
33 #include <common/concrt/scoped_oversubscription_token.h>\r
34 #include <common/diagnostics/graph.h>\r
35 #include <common/exception/exceptions.h>\r
36 #include <common/exception/win32_exception.h>\r
37 \r
38 #include <tbb/atomic.h>\r
39 \r
40 #include <boost/range/algorithm.hpp>\r
41 \r
42 #include <agents.h>\r
43 \r
44 #if defined(_MSC_VER)\r
45 #pragma warning (push)\r
46 #pragma warning (disable : 4244)\r
47 #endif\r
48 extern "C" \r
49 {\r
50         #define __STDC_CONSTANT_MACROS\r
51         #define __STDC_LIMIT_MACROS\r
52         #include <libavformat/avformat.h>\r
53 }\r
54 #if defined(_MSC_VER)\r
55 #pragma warning (pop)\r
56 #endif\r
57 \r
58 using namespace Concurrency;\r
59 \r
60 namespace caspar { namespace ffmpeg {\r
61 \r
62 static const size_t MAX_BUFFER_COUNT = 100;\r
63 static const size_t MIN_BUFFER_COUNT = 4;\r
64 static const size_t MAX_BUFFER_SIZE  = 16 * 1000000;\r
65         \r
66 struct input::implementation : public Concurrency::agent, boost::noncopyable\r
67 {               \r
68         std::shared_ptr<AVFormatContext>                                                        format_context_; // Destroy this last\r
69         int                                                                                                                     default_stream_index_;\r
70 \r
71         safe_ptr<diagnostics::graph>                                                            graph_;\r
72                 \r
73         const std::wstring                                                                                      filename_;\r
74         const bool                                                                                                      loop_;\r
75         const size_t                                                                                            start_;         \r
76         const size_t                                                                                            length_;\r
77         size_t                                                                                                          frame_number_;\r
78         \r
79         input::token_t&                                                                                         active_token_;\r
80         input::target_t&                                                                                        video_target_;\r
81         input::target_t&                                                                                        audio_target_;\r
82                 \r
83         tbb::atomic<size_t>                                                                                     nb_frames_;\r
84         tbb::atomic<size_t>                                                                                     nb_loops_;\r
85 \r
86         int                                                                                                                     video_index_;\r
87         int                                                                                                                     audio_index_;\r
88 \r
89 public:\r
90         explicit implementation(input::token_t& active_token,\r
91                                                         input::target_t& video_target,\r
92                                                         input::target_t& audio_target,\r
93                                                         const safe_ptr<diagnostics::graph>& graph, \r
94                                                         const std::wstring& filename, \r
95                                                         bool loop, \r
96                                                         size_t start,\r
97                                                         size_t length)\r
98                 : active_token_(active_token)\r
99                 , video_target_(video_target)\r
100                 , audio_target_(audio_target)\r
101                 , graph_(graph)\r
102                 , loop_(loop)\r
103                 , filename_(filename)\r
104                 , start_(start)\r
105                 , length_(length)\r
106                 , frame_number_(0)\r
107         {                       \r
108                 nb_frames_      = 0;\r
109                 nb_loops_       = 0;\r
110                 \r
111                 AVFormatContext* weak_format_context_ = nullptr;\r
112                 THROW_ON_ERROR2(avformat_open_input(&weak_format_context_, narrow(filename).c_str(), nullptr, nullptr), print());\r
113 \r
114                 format_context_.reset(weak_format_context_, av_close_input_file);\r
115 \r
116                 av_dump_format(weak_format_context_, 0, narrow(filename).c_str(), 0);\r
117                         \r
118                 THROW_ON_ERROR2(avformat_find_stream_info(format_context_.get(), nullptr), print());\r
119                 \r
120                 default_stream_index_ = THROW_ON_ERROR2(av_find_default_stream_index(format_context_.get()), print());\r
121                 video_index_ = av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);\r
122                 audio_index_ = av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);\r
123                 \r
124                 if(start_ > 0)                  \r
125                         seek_frame(start_);\r
126                 \r
127                 for(int n = 0; n < 16; ++n)\r
128                         read_next_packet();\r
129                                                 \r
130                 graph_->set_color("seek", diagnostics::color(1.0f, 0.5f, 0.0f));        \r
131 \r
132                 agent::start();\r
133         }\r
134 \r
135         ~implementation()\r
136         {\r
137                 agent::wait(this);\r
138         }\r
139         \r
140         virtual void run()\r
141         {\r
142                 try\r
143                 {\r
144                         while(Concurrency::receive(active_token_))\r
145                         {\r
146                                 if(!read_next_packet())\r
147                                 {\r
148                                         Concurrency::send(video_target_, eof_packet());\r
149                                         Concurrency::send(audio_target_, eof_packet());\r
150                                         break;\r
151                                 }\r
152                         }                               \r
153                 }\r
154                 catch(...)\r
155                 {\r
156                         CASPAR_LOG_CURRENT_EXCEPTION();\r
157                 }               \r
158                                                 \r
159                 done();\r
160         }\r
161 \r
162         bool read_next_packet()\r
163         {               \r
164                 int ret = 0;\r
165 \r
166                 auto read_packet = create_packet();\r
167 \r
168                 {\r
169                         Concurrency::scoped_oversubcription_token oversubscribe;\r
170                         ret = av_read_frame(format_context_.get(), read_packet.get()); // read_packet is only valid until next call of av_read_frame. Use av_dup_packet to extend its life.     \r
171                 }\r
172 \r
173                 if(is_eof(ret))                                                                                                              \r
174                 {\r
175                         ++nb_loops_;\r
176                         frame_number_ = 0;\r
177 \r
178                         if(loop_)\r
179                         {\r
180                                 int flags = AVSEEK_FLAG_BACKWARD;\r
181 \r
182                                 int vid_stream_index = av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);\r
183                                 if(vid_stream_index >= 0)\r
184                                 {\r
185                                         auto codec_id = format_context_->streams[vid_stream_index]->codec->codec_id;\r
186                                         if(codec_id == CODEC_ID_VP6A || codec_id == CODEC_ID_VP6F || codec_id == CODEC_ID_VP6)\r
187                                                 flags |= AVSEEK_FLAG_BYTE;\r
188                                 }\r
189 \r
190                                 seek_frame(start_, flags);\r
191                                 graph_->add_tag("seek");                \r
192                                 CASPAR_LOG(trace) << print() << " Looping.";                    \r
193                         }       \r
194                         else\r
195                         {\r
196                                 CASPAR_LOG(trace) << print() << " Stopping.";\r
197                                 return false;\r
198                         }\r
199                 }\r
200                 else if(read_packet->stream_index == video_index_ || read_packet->stream_index == audio_index_)\r
201                 {               \r
202                         THROW_ON_ERROR(ret, print(), "av_read_frame");\r
203 \r
204                         if(read_packet->stream_index == default_stream_index_)\r
205                         {\r
206                                 if(nb_loops_ == 0)\r
207                                         ++nb_frames_;\r
208                                 ++frame_number_;\r
209                         }\r
210 \r
211                         THROW_ON_ERROR2(av_dup_packet(read_packet.get()), print());\r
212                                 \r
213                         // Make sure that the packet is correctly deallocated even if size and data is modified during decoding.\r
214                         auto size = read_packet->size;\r
215                         auto data = read_packet->data;\r
216 \r
217                         read_packet = std::shared_ptr<AVPacket>(read_packet.get(), [=](AVPacket*)\r
218                         {\r
219                                 read_packet->size = size;\r
220                                 read_packet->data = data;\r
221                         });\r
222         \r
223                         if(read_packet->stream_index == video_index_)\r
224                                 Concurrency::send(video_target_, read_packet);\r
225                         else if(read_packet->stream_index == audio_index_)\r
226                                 Concurrency::send(audio_target_, read_packet);\r
227                 }       \r
228 \r
229                 return true;\r
230         }\r
231 \r
232         void seek_frame(int64_t frame, int flags = 0)\r
233         {                                                       \r
234                 THROW_ON_ERROR2(av_seek_frame(format_context_.get(), default_stream_index_, frame, flags), print());    \r
235                 auto packet = create_packet();\r
236                 packet->size = 0;\r
237                 Concurrency::send(video_target_, loop_packet());                \r
238                 Concurrency::send(audio_target_, loop_packet());\r
239         }               \r
240 \r
241         bool is_eof(int ret)\r
242         {\r
243                 if(ret == AVERROR(EIO))\r
244                         CASPAR_LOG(trace) << print() << " Received EIO, assuming EOF. " << nb_frames_;\r
245                 if(ret == AVERROR_EOF)\r
246                         CASPAR_LOG(trace) << print() << " Received EOF. " << nb_frames_;\r
247 \r
248                 return ret == AVERROR_EOF || ret == AVERROR(EIO) || frame_number_ >= length_; // av_read_frame doesn't always correctly return AVERROR_EOF;\r
249         }\r
250         \r
251         std::wstring print() const\r
252         {\r
253                 return L"ffmpeg_input[" + filename_ + L")]";\r
254         }\r
255 };\r
256 \r
257 input::input(token_t& active_token,  \r
258                          target_t& video_target,  \r
259                          target_t& audio_target,  \r
260                      const safe_ptr<diagnostics::graph>& graph, \r
261                          const std::wstring& filename, \r
262                          bool loop, \r
263                          size_t start, \r
264                          size_t length)\r
265         : impl_(new implementation(active_token, video_target, audio_target, graph, filename, loop, start, length))\r
266 {\r
267 }\r
268 \r
269 safe_ptr<AVFormatContext> input::context()\r
270 {\r
271         return safe_ptr<AVFormatContext>(impl_->format_context_);\r
272 }\r
273 \r
274 size_t input::nb_frames() const\r
275 {\r
276         return impl_->nb_frames_;\r
277 }\r
278 \r
279 size_t input::nb_loops() const \r
280 {\r
281         return impl_->nb_loops_;\r
282 }\r
283 \r
284 }}