]> git.sesse.net Git - casparcg/blob - modules/ffmpeg/consumer/ffmpeg_consumer.cpp
2.0.2: ffmpeg_consumer: Re-enabled, in alpha state. Test it!
[casparcg] / modules / ffmpeg / consumer / ffmpeg_consumer.cpp
1 /*\r
2 * copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
3 *\r
4 *  This ffmpeg 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  \r
21 #include "../StdAfx.h"\r
22 \r
23 #include "../ffmpeg_error.h"\r
24 #include "../producer/tbb_avcodec.h"\r
25 \r
26 #include "ffmpeg_consumer.h"\r
27 \r
28 #include <core/mixer/read_frame.h>\r
29 #include <core/mixer/audio/audio_util.h>\r
30 #include <core/consumer/frame_consumer.h>\r
31 #include <core/video_format.h>\r
32 \r
33 #include <common/concurrency/executor.h>\r
34 #include <common/utility/string.h>\r
35 #include <common/env.h>\r
36 \r
37 #include <boost/thread/once.hpp>\r
38 \r
39 #include <tbb/cache_aligned_allocator.h>\r
40 #include <tbb/parallel_invoke.h>\r
41 \r
42 #include <cstdio>\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         #include <libswscale/swscale.h>\r
54 }\r
55 #if defined(_MSC_VER)\r
56 #pragma warning (pop)\r
57 #endif\r
58 \r
59 namespace caspar { namespace ffmpeg {\r
60         \r
61 struct ffmpeg_consumer : boost::noncopyable\r
62 {               \r
63         const std::string                                               filename_;\r
64                 \r
65         const std::shared_ptr<AVFormatContext>  oc_;\r
66         const core::video_format_desc                   format_desc_;\r
67         \r
68         executor                                                                executor_;\r
69 \r
70         // Audio\r
71         std::shared_ptr<AVStream>                               audio_st_;\r
72         std::vector<uint8_t>                                    audio_outbuf_;\r
73 \r
74         std::vector<int16_t>                                    audio_input_buffer_;\r
75 \r
76         // Video\r
77         std::shared_ptr<AVStream>                               video_st_;\r
78         std::vector<uint8_t>                                    video_outbuf_;\r
79 \r
80         std::vector<uint8_t>                                    picture_buf_;\r
81         std::shared_ptr<SwsContext>                             img_convert_ctx_;\r
82         \r
83 public:\r
84         ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc)\r
85                 : filename_(filename + ".mov")\r
86                 , video_outbuf_(1920*1080*8)\r
87                 , audio_outbuf_(48000)\r
88                 , oc_(avformat_alloc_context(), av_free)\r
89                 , format_desc_(format_desc)\r
90                 , executor_(print())\r
91         {\r
92                 executor_.set_capacity(25);\r
93                 \r
94                 oc_->oformat = av_guess_format(nullptr, filename_.c_str(), nullptr);\r
95                 if (!oc_->oformat)\r
96                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Could not find suitable output format."));\r
97                 \r
98                 THROW_ON_ERROR2(av_set_parameters(oc_.get(), nullptr), "[ffmpeg_consumer]");\r
99 \r
100                 strcpy_s(oc_->filename, filename_.c_str());\r
101                 \r
102                 //  Add the audio and video streams using the default format codecs     and initialize the codecs .\r
103                 video_st_ = add_video_stream(oc_->oformat->video_codec);\r
104                 audio_st_ = add_audio_stream(oc_->oformat->audio_codec);\r
105                                 \r
106                 dump_format(oc_.get(), 0, filename_.c_str(), 1);\r
107                  \r
108                 // Open the output ffmpeg, if needed.\r
109                 if (!(oc_->oformat->flags & AVFMT_NOFILE)) \r
110                         THROW_ON_ERROR2(avio_open(&oc_->pb, filename_.c_str(), URL_WRONLY), "[ffmpeg_consumer]");\r
111                                 \r
112                 THROW_ON_ERROR2(av_write_header(oc_.get()), "[ffmpeg_consumer]");\r
113 \r
114                 CASPAR_LOG(info) << print() << L" Successfully initialized.";   \r
115         }\r
116 \r
117         ~ffmpeg_consumer()\r
118         {    \r
119                 executor_.stop();\r
120                 executor_.join();\r
121                 \r
122                 try\r
123                 {\r
124                         THROW_ON_ERROR2(av_write_trailer(oc_.get()), "[ffmpeg_consumer]");\r
125                 \r
126                         audio_st_.reset();\r
127                         video_st_.reset();\r
128                           \r
129                         for(size_t i = 0; i < oc_->nb_streams; i++) \r
130                         {\r
131                                 av_freep(&oc_->streams[i]->codec);\r
132                                 av_freep(&oc_->streams[i]);\r
133                         }\r
134 \r
135                         if (!(oc_->oformat->flags & AVFMT_NOFILE)) \r
136                                 THROW_ON_ERROR2(avio_close(oc_->pb), "[ffmpeg_consumer]"); // Close the output ffmpeg.\r
137                 }\r
138                 catch(...)\r
139                 {\r
140                         CASPAR_LOG_CURRENT_EXCEPTION();\r
141                 }\r
142 \r
143         }\r
144                         \r
145         std::wstring print() const\r
146         {\r
147                 return L"ffmpeg[" + widen(filename_) + L"]";\r
148         }\r
149 \r
150         std::shared_ptr<AVStream> add_video_stream(enum CodecID codec_id)\r
151         { \r
152                 auto st = av_new_stream(oc_.get(), 0);\r
153                 if (!st) \r
154                 {\r
155                         BOOST_THROW_EXCEPTION(caspar_exception() \r
156                                 << msg_info("Could not alloc video-stream")                             \r
157                                 << boost::errinfo_api_function("av_new_stream"));\r
158                 }\r
159 \r
160                 st->codec->codec_id                     = CODEC_ID_DNXHD;\r
161                 st->codec->codec_type           = AVMEDIA_TYPE_VIDEO;\r
162                 st->codec->bit_rate                     = 145*1000000;\r
163                 st->codec->width                        = std::min<size_t>(1280, format_desc_.width);\r
164                 st->codec->height                       = std::min<size_t>(720, format_desc_.height);\r
165                 st->codec->time_base.den        = format_desc_.time_scale;\r
166                 st->codec->time_base.num        = format_desc_.duration;\r
167                 st->codec->pix_fmt                      = st->codec->pix_fmt == -1 ? PIX_FMT_YUV422P : st->codec->pix_fmt;\r
168                 
169                 if(oc_->oformat->flags & AVFMT_GLOBALHEADER)
170                         st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;\r
171 \r
172                 auto codec = avcodec_find_encoder(st->codec->codec_id);\r
173                 if (!codec)\r
174                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found"));\r
175 \r
176                 THROW_ON_ERROR2(tbb_avcodec_open(st->codec, codec), "[ffmpeg_consumer]");\r
177 \r
178                 return std::shared_ptr<AVStream>(st, [](AVStream* st)\r
179                 {\r
180                         tbb_avcodec_close(st->codec);\r
181                 });\r
182         }\r
183         \r
184         std::shared_ptr<AVStream> add_audio_stream(enum CodecID codec_id)\r
185         {\r
186                 auto st = av_new_stream(oc_.get(), 1);\r
187                 if (!st) \r
188                 {\r
189                         BOOST_THROW_EXCEPTION(caspar_exception() \r
190                                 << msg_info("Could not alloc audio-stream")                             \r
191                                 << boost::errinfo_api_function("av_new_stream"));\r
192                 }\r
193 \r
194                 st->codec->codec_id             = CODEC_ID_PCM_S16LE;\r
195                 st->codec->codec_type   = AVMEDIA_TYPE_AUDIO;\r
196                 st->codec->sample_rate  = 48000;\r
197                 st->codec->channels             = 2;\r
198                 st->codec->sample_fmt   = SAMPLE_FMT_S16;\r
199                 
200                 if(oc_->oformat->flags & AVFMT_GLOBALHEADER)
201                         st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;\r
202                 \r
203                 auto codec = avcodec_find_encoder(st->codec->codec_id);\r
204                 if (!codec)\r
205                         BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found"));\r
206 \r
207                 THROW_ON_ERROR2(avcodec_open(st->codec, codec), "[ffmpeg_consumer]");\r
208 \r
209                 return std::shared_ptr<AVStream>(st, [](AVStream* st)\r
210                 {\r
211                         avcodec_close(st->codec);\r
212                 });\r
213         }\r
214   \r
215         void encode_video_frame(const safe_ptr<core::read_frame>& frame)\r
216         { \r
217                 auto c = video_st_->codec;\r
218  \r
219                 if(!img_convert_ctx_) \r
220                 {\r
221                         img_convert_ctx_.reset(sws_getContext(format_desc_.width, format_desc_.height, PIX_FMT_BGRA, c->width, c->height, c->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr), sws_freeContext);\r
222                         if (img_convert_ctx_ == nullptr) \r
223                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Cannot initialize the conversion context"));\r
224                 }\r
225 \r
226                 std::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);\r
227                 avpicture_fill(reinterpret_cast<AVPicture*>(av_frame.get()), const_cast<uint8_t*>(frame->image_data().begin()), PIX_FMT_BGRA, format_desc_.width, format_desc_.height);\r
228                                 \r
229                 std::shared_ptr<AVFrame> local_av_frame(avcodec_alloc_frame(), av_free);\r
230                 local_av_frame->interlaced_frame = format_desc_.field_mode != core::field_mode::progressive;\r
231                 local_av_frame->top_field_first  = format_desc_.field_mode == core::field_mode::upper;\r
232 \r
233                 picture_buf_.resize(avpicture_get_size(c->pix_fmt, format_desc_.width, format_desc_.height));\r
234                 avpicture_fill(reinterpret_cast<AVPicture*>(local_av_frame.get()), picture_buf_.data(), c->pix_fmt, format_desc_.width, format_desc_.height);\r
235 \r
236                 sws_scale(img_convert_ctx_.get(), av_frame->data, av_frame->linesize, 0, c->height, local_av_frame->data, local_av_frame->linesize);\r
237                                 \r
238                 int out_size = THROW_ON_ERROR2(avcodec_encode_video(c, video_outbuf_.data(), video_outbuf_.size(), local_av_frame.get()), "[ffmpeg_consumer]");\r
239                 if(out_size > 0)\r
240                 {\r
241                         AVPacket pkt;\r
242                         av_init_packet(&pkt);\r
243  \r
244                         if (c->coded_frame->pts != AV_NOPTS_VALUE)\r
245                                 pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, video_st_->time_base);\r
246 \r
247                         if(c->coded_frame->key_frame)\r
248                                 pkt.flags |= AV_PKT_FLAG_KEY;\r
249 \r
250                         pkt.stream_index        = video_st_->index;\r
251                         pkt.data                        = video_outbuf_.data();\r
252                         pkt.size                        = out_size;\r
253  \r
254                         THROW_ON_ERROR2(av_interleaved_write_frame(oc_.get(), &pkt), L"[ffmpeg_consumer]");\r
255                 }       \r
256         }\r
257                 \r
258         void encode_audio_frame(const safe_ptr<core::read_frame>& frame)\r
259         {                       \r
260                 auto c = audio_st_->codec;\r
261 \r
262                 auto audio_data = core::audio_32_to_16(frame->audio_data());\r
263 \r
264                 AVPacket pkt;\r
265                 av_init_packet(&pkt);\r
266                 \r
267                 if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE)\r
268                         pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st_->time_base);\r
269 \r
270                 pkt.flags                |= AV_PKT_FLAG_KEY;\r
271                 pkt.stream_index = audio_st_->index;\r
272                 pkt.size                 = audio_data.size()*2;\r
273                 pkt.data                 = reinterpret_cast<uint8_t*>(audio_data.data());\r
274                 \r
275                 THROW_ON_ERROR2(av_interleaved_write_frame(oc_.get(), &pkt), L"[ffmpeg_consumer]");\r
276         }\r
277                  \r
278         void send(const safe_ptr<core::read_frame>& frame)\r
279         {\r
280                 executor_.begin_invoke([=]\r
281                 {                               \r
282                         encode_video_frame(frame);\r
283                         encode_audio_frame(frame);\r
284                 });\r
285         }\r
286 };\r
287 \r
288 struct ffmpeg_consumer_proxy : public core::frame_consumer\r
289 {\r
290         const std::wstring filename_;\r
291         const bool key_only_;\r
292 \r
293         std::unique_ptr<ffmpeg_consumer> consumer_;\r
294 \r
295 public:\r
296 \r
297         ffmpeg_consumer_proxy(const std::wstring& filename, bool key_only)\r
298                 : filename_(filename)\r
299                 , key_only_(key_only){}\r
300         \r
301         virtual void initialize(const core::video_format_desc& format_desc, int, int)\r
302         {\r
303                 consumer_.reset();\r
304                 consumer_.reset(new ffmpeg_consumer(narrow(filename_), format_desc));\r
305         }\r
306         \r
307         virtual bool send(const safe_ptr<core::read_frame>& frame) override\r
308         {\r
309                 consumer_->send(frame);\r
310                 return true;\r
311         }\r
312         \r
313         virtual std::wstring print() const override\r
314         {\r
315                 return consumer_ ? consumer_->print() : L"[ffmpeg_consumer]";\r
316         }\r
317                 \r
318         virtual bool has_synchronization_clock() const override\r
319         {\r
320                 return false;\r
321         }\r
322 \r
323         virtual size_t buffer_depth() const override\r
324         {\r
325                 return 1;\r
326         }\r
327 };      \r
328 \r
329 safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const std::vector<std::wstring>& params)\r
330 {\r
331         if(params.size() < 2 || params[0] != L"FILE")\r
332                 return core::frame_consumer::empty();\r
333         \r
334         // TODO: Ask stakeholders about case where file already exists.\r
335         boost::filesystem::remove(boost::filesystem::wpath(env::media_folder() + params[1])); // Delete the file if it exists\r
336         bool key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
337 \r
338         return make_safe<ffmpeg_consumer_proxy>(env::media_folder() + params[1], key_only);\r
339 }\r
340 \r
341 safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const boost::property_tree::ptree& ptree)\r
342 {\r
343         std::string filename = ptree.get<std::string>("path");\r
344         bool key_only            = ptree.get("key-only", false);\r
345         \r
346         return make_safe<ffmpeg_consumer_proxy>(env::media_folder() + widen(filename), key_only);\r
347 }\r
348 \r
349 }}\r