]> git.sesse.net Git - casparcg/blob - core/producer/ffmpeg/input.cpp
6cf6f5b7f7e89583c8896bb2dbd3ae52b3882c66
[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/image/image.h"\r
7 #include "../../../common/utility/scope_exit.h"\r
8 \r
9 #include <tbb/concurrent_queue.h>\r
10 \r
11 #include <boost/thread.hpp>\r
12 \r
13 #include <errno.h>\r
14 #include <system_error>\r
15 \r
16 #pragma warning(disable : 4482)\r
17                 \r
18 namespace caspar{ namespace ffmpeg{\r
19                 \r
20 struct input::implementation : boost::noncopyable\r
21 {\r
22         implementation(const frame_format_desc& format_desc) \r
23                 : video_frame_rate_(25.0), video_s_index_(-1), audio_s_index_(-1), video_codec_(nullptr), audio_codec_a(nullptr), format_desc_(format_desc)\r
24         {\r
25                 loop_ = false;\r
26                 video_packet_buffer_.set_capacity(28);\r
27                 audio_packet_buffer_.set_capacity(28);\r
28         }\r
29 \r
30         ~implementation()\r
31         {               \r
32                 is_running_ = false;\r
33                 audio_packet_buffer_.clear();\r
34                 video_packet_buffer_.clear();\r
35                 io_thread_.join();\r
36         }\r
37         \r
38         void load(const std::string& filename)\r
39         {       \r
40                 try\r
41                 {\r
42                         int errn;\r
43                         AVFormatContext* weak_format_context;\r
44                         if((errn = -av_open_input_file(&weak_format_context, filename.c_str(), nullptr, 0, nullptr)) > 0)\r
45                                 BOOST_THROW_EXCEPTION(ffmpeg_error() << boost::errinfo_errno(errn));\r
46                         format_context.reset(weak_format_context, av_close_input_file);\r
47                         \r
48                         if((errn = -av_find_stream_info(format_context.get())) > 0)\r
49                                 BOOST_THROW_EXCEPTION(ffmpeg_error() << boost::errinfo_errno(errn));\r
50 \r
51                         video_codec_context_ = open_video_stream();\r
52                         if(!video_codec_context_)\r
53                                 CASPAR_LOG(info) << "No video stream found.";\r
54                 \r
55                         audio_codex_context = open_audio_stream();\r
56                         if(!audio_codex_context)\r
57                                 CASPAR_LOG(info) << "No audio stream found.";\r
58 \r
59                         if(!video_codec_context_ && !audio_codex_context)\r
60                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("No video or audio codec found."));\r
61                         \r
62                         video_frame_rate_ = static_cast<double>(video_codec_context_->time_base.den) / static_cast<double>(video_codec_context_->time_base.num);\r
63 \r
64                         is_running_ = true;\r
65                         io_thread_ = boost::thread([=]{this->read_file();});\r
66                 }\r
67                 catch(...)\r
68                 {\r
69                         video_codec_context_.reset();\r
70                         audio_codex_context.reset();\r
71                         format_context.reset();\r
72                         video_frame_rate_ = 25.0;\r
73                         video_s_index_ = -1;\r
74                         audio_s_index_ = -1;    \r
75                         throw;\r
76                 }\r
77                 filename_ = filename;\r
78         }\r
79                         \r
80         std::shared_ptr<AVCodecContext> open_video_stream()\r
81         {\r
82                 bool succeeded = false;\r
83 \r
84                 CASPAR_SCOPE_EXIT([&]\r
85                 {\r
86                         if(!succeeded)\r
87                         {\r
88                                 video_s_index_ = -1;\r
89                                 video_codec_ = nullptr;\r
90                         }\r
91                 });\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                 succeeded = true;\r
109 \r
110                 return std::shared_ptr<AVCodecContext>((*video_stream)->codec, avcodec_close);\r
111         }\r
112 \r
113         std::shared_ptr<AVCodecContext> open_audio_stream()\r
114         {       \r
115                 bool succeeded = false;\r
116 \r
117                 CASPAR_SCOPE_EXIT([&]\r
118                 {\r
119                         if(!succeeded)\r
120                         {\r
121                                 audio_s_index_ = -1;\r
122                                 audio_codec_a = nullptr;\r
123                         }\r
124                 });\r
125 \r
126                 AVStream** streams_end = format_context->streams+format_context->nb_streams;\r
127                 AVStream** audio_stream = std::find_if(format_context->streams, streams_end, \r
128                         [](AVStream* stream) { return stream != nullptr && stream->codec->codec_type == CODEC_TYPE_AUDIO;});\r
129 \r
130                 audio_s_index_ = audio_stream != streams_end ? (*audio_stream)->index : -1;\r
131                 if(audio_s_index_ == -1)\r
132                         return nullptr;\r
133                 \r
134                 audio_codec_a = avcodec_find_decoder((*audio_stream)->codec->codec_id);\r
135                 if(audio_codec_a == nullptr)\r
136                         return nullptr;\r
137 \r
138                 if((-avcodec_open((*audio_stream)->codec, audio_codec_a)) > 0)          \r
139                         return nullptr;\r
140 \r
141                 succeeded = true;\r
142 \r
143                 return std::shared_ptr<AVCodecContext>((*audio_stream)->codec, avcodec_close);\r
144         }       \r
145 \r
146         void read_file()\r
147         {       \r
148                 CASPAR_LOG(info) << "Started ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
149                 win32_exception::install_handler();\r
150 \r
151                 try\r
152                 {\r
153                         AVPacket tmp_packet;\r
154                         while(is_running_)\r
155                         {\r
156                                 std::shared_ptr<AVPacket> packet(&tmp_packet, av_free_packet);\r
157 \r
158                                 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
159                                 {\r
160                                         if(packet->stream_index == video_s_index_)                              \r
161                                                 video_packet_buffer_.push(std::make_shared<video_packet>(packet, nullptr, format_desc_, video_codec_context_.get(), video_codec_));              // NOTE: video_packet makes a copy of AVPacket\r
162                                         else if(packet->stream_index == audio_s_index_)         \r
163                                                 audio_packet_buffer_.push(std::make_shared<audio_packet>(packet, audio_codex_context.get(), audio_codec_a, video_frame_rate_));                 \r
164                                 }\r
165                                 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
166                                         is_running_ = false;\r
167                         }\r
168                 }\r
169                 catch(...)\r
170                 {\r
171                         CASPAR_LOG_CURRENT_EXCEPTION();\r
172                 }\r
173 \r
174                 is_running_ = false;\r
175                 \r
176                 CASPAR_LOG(info) << " Ended ffmpeg_producer::read_file Thread for " << filename_.c_str();\r
177         }\r
178         \r
179         video_packet_ptr get_video_packet()\r
180         {\r
181                 video_packet_ptr video_packet;\r
182                 video_packet_buffer_.try_pop(video_packet);\r
183                 return video_packet;\r
184         }\r
185 \r
186         audio_packet_ptr get_audio_packet()\r
187         {\r
188                 audio_packet_ptr audio_packet;\r
189                 audio_packet_buffer_.try_pop(audio_packet);\r
190                 return audio_packet;\r
191         }\r
192 \r
193         bool is_eof() const\r
194         {\r
195                 return !is_running_ && video_packet_buffer_.empty() && audio_packet_buffer_.empty();\r
196         }\r
197                                 \r
198         std::string                                                     filename_;\r
199         std::shared_ptr<AVFormatContext>        format_context; // Destroy this last\r
200 \r
201         std::shared_ptr<AVCodecContext>         video_codec_context_;\r
202         AVCodec*                                                        video_codec_;\r
203 \r
204         std::shared_ptr<AVCodecContext>         audio_codex_context;\r
205         AVCodec*                                                        audio_codec_a;\r
206 \r
207         tbb::atomic<bool>                                       loop_;\r
208         int                                                                     video_s_index_;\r
209         int                                                                     audio_s_index_;\r
210 \r
211         frame_format_desc format_desc_;\r
212 \r
213         tbb::concurrent_bounded_queue<video_packet_ptr> video_packet_buffer_;\r
214         tbb::concurrent_bounded_queue<audio_packet_ptr> audio_packet_buffer_;\r
215         boost::thread   io_thread_;\r
216         tbb::atomic<bool> is_running_;\r
217 \r
218         double video_frame_rate_;\r
219 };\r
220 \r
221 input::input(const frame_format_desc& format_desc) : impl_(new implementation(format_desc)){}\r
222 void input::load(const std::string& filename){impl_->load(filename);}\r
223 void input::set_loop(bool value){impl_->loop_ = value;}\r
224 const std::shared_ptr<AVCodecContext>& input::get_video_codec_context() const{return impl_->video_codec_context_;}\r
225 const std::shared_ptr<AVCodecContext>& input::get_audio_codec_context() const{return impl_->audio_codex_context;}\r
226 bool input::is_eof() const{return impl_->is_eof();}\r
227 video_packet_ptr input::get_video_packet(){return impl_->get_video_packet();}\r
228 audio_packet_ptr input::get_audio_packet(){return impl_->get_audio_packet();}\r
229 }}