]> git.sesse.net Git - casparcg/blob - core/producer/ffmpeg/input.cpp
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches...
[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 \r
7 #include <common/concurrency/executor.h>\r
8 \r
9 #include <tbb/concurrent_queue.h>\r
10 #include <tbb/queuing_mutex.h>\r
11 \r
12 #include <boost/exception/error_info.hpp>\r
13 #include <boost/thread/once.hpp>\r
14 \r
15 #include <errno.h>\r
16 #include <system_error>\r
17                 \r
18 #if defined(_MSC_VER)\r
19 #pragma warning (disable : 4244)\r
20 #endif\r
21 \r
22 \r
23 extern "C" \r
24 {\r
25         #define __STDC_CONSTANT_MACROS\r
26         #define __STDC_LIMIT_MACROS\r
27         #include <libavformat/avformat.h>\r
28 }\r
29 \r
30 namespace caspar { namespace core { namespace ffmpeg{\r
31                 \r
32 struct input::implementation : boost::noncopyable\r
33 {\r
34         static const size_t BUFFER_SIZE = 2 << 25;\r
35 \r
36         implementation(const std::wstring& filename) : video_s_index_(-1), audio_s_index_(-1), filename_(filename)\r
37         {               \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
40                 \r
41                 static boost::once_flag avcodec_init_flag = BOOST_ONCE_INIT;\r
42                 boost::call_once(avcodec_init, avcodec_init_flag);      \r
43 \r
44                 loop_ = false;  \r
45                 \r
46                 int errn;\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
55 \r
56                 format_context_.reset(weak_format_context_, av_close_input_file);\r
57                         \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
64 \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
68                 \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
72 \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
75                         \r
76                 executor_.start();\r
77                 executor_.begin_invoke([this]{read_file();});\r
78                 CASPAR_LOG(info) << print() << " started.";\r
79         }\r
80 \r
81         ~implementation()\r
82         {\r
83                 CASPAR_LOG(info) << print() << " ended.";\r
84         }\r
85                                                         \r
86         std::shared_ptr<AVCodecContext> open_stream(int codec_type, int& s_index)\r
87         {               \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
91                 \r
92                 if(stream == streams_end) \r
93                         return nullptr;\r
94 \r
95                 s_index = (*stream)->index;\r
96                 \r
97                 auto codec = avcodec_find_decoder((*stream)->codec->codec_id);                  \r
98                 if(codec == nullptr)\r
99                         return nullptr;\r
100                         \r
101                 if((-avcodec_open((*stream)->codec, codec)) > 0)                \r
102                         return nullptr;\r
103 \r
104                 return std::shared_ptr<AVCodecContext>((*stream)->codec, avcodec_close);\r
105         }\r
106                 \r
107         void read_file() // For every packet taken: read in a number of packets.\r
108         {               \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
110                 {\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
114 \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
116                         {\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
119                                 {\r
120                                         buffer_size_ += packet->size();\r
121                                         video_packet_buffer_.try_push(std::move(packet));                                               \r
122                                 }\r
123                                 else if(read_packet->stream_index == audio_s_index_)    \r
124                                 {\r
125                                         buffer_size_ += packet->size();\r
126                                         audio_packet_buffer_.try_push(std::move(packet));\r
127                                 }\r
128                         }\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
131                 }\r
132         }\r
133                 \r
134         aligned_buffer get_video_packet()\r
135         {\r
136                 return get_packet(video_packet_buffer_);\r
137         }\r
138 \r
139         aligned_buffer get_audio_packet()\r
140         {\r
141                 return get_packet(audio_packet_buffer_);\r
142         }\r
143         \r
144         aligned_buffer get_packet(tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>>& buffer)\r
145         {\r
146                 std::shared_ptr<aligned_buffer> packet;\r
147                 if(buffer.try_pop(packet))\r
148                 {\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
153                 }\r
154                 return aligned_buffer();\r
155         }\r
156 \r
157         bool is_eof() const\r
158         {\r
159                 return !executor_.is_running() && video_packet_buffer_.empty() && audio_packet_buffer_.empty();\r
160         }\r
161                 \r
162         // TODO: Not properly done.\r
163         bool seek(unsigned long long seek_target)\r
164         {\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
167                         return false;\r
168                 \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
176                 return true;\r
177         }\r
178 \r
179         std::wstring print() const\r
180         {\r
181                 return L"ffmpeg[" + boost::filesystem::wpath(filename_).filename() + L"] Buffer thread";\r
182         }\r
183                                 \r
184         std::shared_ptr<AVFormatContext>        format_context_;        // Destroy this last\r
185 \r
186         tbb::queuing_mutex                                      seek_mutex_;\r
187 \r
188         const std::wstring                                      filename_;\r
189 \r
190         std::shared_ptr<AVCodecContext>         video_codec_context_;\r
191 \r
192         std::shared_ptr<AVCodecContext>         audio_codex_context_;\r
193 \r
194         tbb::atomic<bool>                                       loop_;\r
195         int                                                                     video_s_index_;\r
196         int                                                                     audio_s_index_;\r
197         \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
200         \r
201         tbb::atomic<size_t>                     buffer_size_;\r
202 \r
203         executor executor_;\r
204 };\r
205 \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
214 }}}