]> 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/scope_exit.h"\r
7 \r
8 #include <tbb/concurrent_queue.h>\r
9 #include <tbb/queuing_mutex.h>\r
10 \r
11 #include <boost/thread.hpp>\r
12 #include <boost/exception/error_info.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                 video_packet_buffer_.set_capacity(50);\r
39                 audio_packet_buffer_.set_capacity(50);\r
40         }\r
41 \r
42         ~implementation()\r
43         {               \r
44                 is_running_ = false;\r
45                 std::shared_ptr<aligned_buffer> buffer;\r
46                 audio_packet_buffer_.try_pop(buffer);\r
47                 video_packet_buffer_.try_pop(buffer);\r
48                 io_thread_.join();\r
49         }\r
50         \r
51         void load(const std::string& filename)\r
52         {       \r
53                 try\r
54                 {\r
55                         int errn;\r
56                         AVFormatContext* weak_format_context_;\r
57                         if((errn = -av_open_input_file(&weak_format_context_, filename.c_str(), nullptr, 0, nullptr)) > 0)\r
58                                 BOOST_THROW_EXCEPTION(\r
59                                         file_read_error() << \r
60                                         msg_info("No format context found.") << \r
61                                         boost::errinfo_api_function("av_open_input_file") <<\r
62                                         boost::errinfo_errno(errn) <<\r
63                                         boost::errinfo_file_name(filename));\r
64 \r
65                         format_context_.reset(weak_format_context_, av_close_input_file);\r
66                         \r
67                         if((errn = -av_find_stream_info(format_context_.get())) > 0)\r
68                                 BOOST_THROW_EXCEPTION(\r
69                                         file_read_error() << \r
70                                         boost::errinfo_api_function("av_find_stream_info") <<\r
71                                         msg_info("No stream found.") << \r
72                                         boost::errinfo_errno(errn));\r
73 \r
74                         video_codec_context_ = open_video_stream();\r
75                         if(!video_codec_context_)\r
76                                 CASPAR_LOG(warning) << "No video stream found.";\r
77                 \r
78                         audio_codex_context_ = open_audio_stream();\r
79                         if(!audio_codex_context_)\r
80                                 CASPAR_LOG(warning) << "No audio stream found.";\r
81 \r
82                         if(!video_codec_context_ && !audio_codex_context_)\r
83                                 BOOST_THROW_EXCEPTION(file_read_error() << msg_info("No video or audio codec context found."));         \r
84                 }\r
85                 catch(...)\r
86                 {\r
87                         video_codec_context_.reset();\r
88                         audio_codex_context_.reset();\r
89                         format_context_.reset();\r
90                         video_s_index_ = -1;\r
91                         audio_s_index_ = -1;    \r
92                         throw;\r
93                 }\r
94                 filename_ = filename;\r
95                 io_thread_ = boost::thread([=]{read_file();});\r
96         }\r
97                                 \r
98         std::shared_ptr<AVCodecContext> open_video_stream()\r
99         {               \r
100                 AVStream** streams_end = format_context_->streams+format_context_->nb_streams;\r
101                 AVStream** video_stream = std::find_if(format_context_->streams, streams_end, \r
102                         [](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == CODEC_TYPE_VIDEO ;});\r
103 \r
104                 video_s_index_ = video_stream != streams_end ? (*video_stream)->index : -1;\r
105                 if(video_s_index_ == -1) \r
106                         return nullptr;\r
107                 \r
108                 video_codec_ = avcodec_find_decoder((*video_stream)->codec->codec_id);                  \r
109                 if(video_codec_ == nullptr)\r
110                         return nullptr;\r
111                         \r
112                 if((-avcodec_open((*video_stream)->codec, video_codec_)) > 0)           \r
113                         return nullptr;\r
114 \r
115                 return std::shared_ptr<AVCodecContext>((*video_stream)->codec, avcodec_close);\r
116         }\r
117 \r
118         std::shared_ptr<AVCodecContext> open_audio_stream()\r
119         {       \r
120                 AVStream** streams_end = format_context_->streams+format_context_->nb_streams;\r
121                 AVStream** audio_stream = std::find_if(format_context_->streams, streams_end, \r
122                         [](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == CODEC_TYPE_AUDIO;});\r
123 \r
124                 audio_s_index_ = audio_stream != streams_end ? (*audio_stream)->index : -1;\r
125                 if(audio_s_index_ == -1)\r
126                         return nullptr;\r
127                 \r
128                 audio_codec_ = avcodec_find_decoder((*audio_stream)->codec->codec_id);\r
129                 if(audio_codec_ == nullptr)\r
130                         return nullptr;\r
131 \r
132                 if((-avcodec_open((*audio_stream)->codec, audio_codec_)) > 0)           \r
133                         return nullptr;\r
134 \r
135                 return std::shared_ptr<AVCodecContext>((*audio_stream)->codec, avcodec_close);\r
136         }       \r
137 \r
138         void read_file()\r
139         {       \r
140                 CASPAR_LOG(info) << "Started ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
141                 win32_exception::install_handler();\r
142                 \r
143                 is_running_ = true;\r
144                 AVPacket tmp_packet;\r
145                 while(is_running_)\r
146                 {\r
147                         std::shared_ptr<AVPacket> packet(&tmp_packet, av_free_packet);  \r
148                         tbb::queuing_mutex::scoped_lock lock(seek_mutex_);      \r
149 \r
150                         if (av_read_frame(format_context_.get(), packet.get()) >= 0) // NOTE: Packet is only valid until next call of av_safe_ptr<read_frame> or av_close_input_file\r
151                         {\r
152                                 auto buffer = std::make_shared<aligned_buffer>(packet->data, packet->data + packet->size);\r
153                                 if(packet->stream_index == video_s_index_)                                              \r
154                                         video_packet_buffer_.push(buffer);                                              \r
155                                 else if(packet->stream_index == audio_s_index_)         \r
156                                         audio_packet_buffer_.push(buffer);              \r
157                         }\r
158                         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
159                                 is_running_ = false;\r
160                 }\r
161                 \r
162                 is_running_ = false;\r
163                 \r
164                 CASPAR_LOG(info) << " Ended ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
165         }\r
166         \r
167         aligned_buffer get_video_packet()\r
168         {\r
169                 std::shared_ptr<aligned_buffer> video_packet;\r
170                 if(video_packet_buffer_.try_pop(video_packet))          \r
171                         return std::move(*video_packet);                \r
172                 return aligned_buffer();\r
173         }\r
174 \r
175         aligned_buffer get_audio_packet()\r
176         {\r
177                 std::shared_ptr<aligned_buffer> audio_packet;\r
178                 if(audio_packet_buffer_.try_pop(audio_packet))\r
179                         return std::move(*audio_packet);\r
180                 return aligned_buffer();\r
181         }\r
182 \r
183         bool is_eof() const\r
184         {\r
185                 return !is_running_ && video_packet_buffer_.empty() && audio_packet_buffer_.empty();\r
186         }\r
187                 \r
188         bool seek(unsigned long long seek_target)\r
189         {\r
190                 tbb::queuing_mutex::scoped_lock lock(seek_mutex_);\r
191                 if(av_seek_frame(format_context_.get(), -1, seek_target*AV_TIME_BASE, 0) < 0)\r
192                         return false;\r
193                 \r
194                 video_packet_buffer_.clear();\r
195                 audio_packet_buffer_.clear();\r
196                 // TODO: Not sure its enough to jsut flush in input class\r
197                 if(video_codec_context_)\r
198                         avcodec_flush_buffers(video_codec_context_.get());\r
199                 if(audio_codex_context_)\r
200                         avcodec_flush_buffers(audio_codex_context_.get());\r
201                 return true;\r
202         }\r
203                                 \r
204         std::shared_ptr<AVFormatContext>        format_context_;        // Destroy this last\r
205 \r
206         tbb::queuing_mutex                                      seek_mutex_;\r
207 \r
208         std::string                                                     filename_;\r
209 \r
210         std::shared_ptr<AVCodecContext>         video_codec_context_;\r
211         AVCodec*                                                        video_codec_;\r
212 \r
213         std::shared_ptr<AVCodecContext>         audio_codex_context_;\r
214         AVCodec*                                                        audio_codec_;\r
215 \r
216         tbb::atomic<bool>                                       loop_;\r
217         int                                                                     video_s_index_;\r
218         int                                                                     audio_s_index_;\r
219         \r
220         tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> video_packet_buffer_;\r
221         tbb::concurrent_bounded_queue<std::shared_ptr<aligned_buffer>> audio_packet_buffer_;\r
222         boost::thread   io_thread_;\r
223         tbb::atomic<bool> is_running_;\r
224 };\r
225 \r
226 input::input() : impl_(new implementation()){}\r
227 void input::load(const std::string& filename){impl_->load(filename);}\r
228 void input::set_loop(bool value){impl_->loop_ = value;}\r
229 const std::shared_ptr<AVCodecContext>& input::get_video_codec_context() const{return impl_->video_codec_context_;}\r
230 const std::shared_ptr<AVCodecContext>& input::get_audio_codec_context() const{return impl_->audio_codex_context_;}\r
231 bool input::is_eof() const{return impl_->is_eof();}\r
232 aligned_buffer input::get_video_packet(){return impl_->get_video_packet();}\r
233 aligned_buffer input::get_audio_packet(){return impl_->get_audio_packet();}\r
234 bool input::seek(unsigned long long frame){return impl_->seek(frame);}\r
235 }}}