]> git.sesse.net Git - casparcg/commitdiff
2.0.0.2: Added ffmpeg file consumer, both video and audio. No alpha support, limited...
authorronag <ronag@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Sun, 16 Jan 2011 10:52:32 +0000 (10:52 +0000)
committerronag <ronag@362d55ac-95cf-4e76-9f9a-cbaa9c17b72d>
Sun, 16 Jan 2011 10:52:32 +0000 (10:52 +0000)
git-svn-id: https://casparcg.svn.sourceforge.net/svnroot/casparcg/server/branches/2.0.0.2@370 362d55ac-95cf-4e76-9f9a-cbaa9c17b72d

core/consumer/ffmpeg/ffmpeg_consumer.cpp [new file with mode: 0644]
core/consumer/ffmpeg/ffmpeg_consumer.h [new file with mode: 0644]
core/core.vcxproj
shell/boostrapper.cpp
shell/caspar.config

diff --git a/core/consumer/ffmpeg/ffmpeg_consumer.cpp b/core/consumer/ffmpeg/ffmpeg_consumer.cpp
new file mode 100644 (file)
index 0000000..5f9083e
--- /dev/null
@@ -0,0 +1,382 @@
+/*\r
+* copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
+*\r
+*  This ffmpeg is part of CasparCG.\r
+*\r
+*    CasparCG is free software: you can redistribute it and/or modify\r
+*    it under the terms of the GNU General Public License as published by\r
+*    the Free Software Foundation, either version 3 of the License, or\r
+*    (at your option) any later version.\r
+*\r
+*    CasparCG is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+*    GNU General Public License for more details.\r
+\r
+*    You should have received a copy of the GNU General Public License\r
+*    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+*/\r
\r
+#include "../../StdAfx.h"\r
+\r
+#include "ffmpeg_consumer.h"\r
+\r
+#include "../../processor/read_frame.h"\r
+\r
+#include <common/concurrency/executor.h>\r
+#include <common/utility/string_convert.h>\r
+\r
+#include <boost/thread/once.hpp>\r
+\r
+#include <tbb/cache_aligned_allocator.h>\r
+#include <tbb/parallel_invoke.h>\r
+\r
+#include <cstdio>\r
+\r
+#if defined(_MSC_VER)\r
+#pragma warning (push)\r
+#pragma warning (disable : 4244)\r
+#endif\r
+extern "C" \r
+{\r
+       #define __STDC_CONSTANT_MACROS\r
+       #define __STDC_LIMIT_MACROS\r
+       #include <libavformat/avformat.h>\r
+       #include <libswscale/swscale.h>\r
+}\r
+#if defined(_MSC_VER)\r
+#pragma warning (pop)\r
+#endif\r
+\r
+namespace caspar { namespace core { namespace ffmpeg {\r
+       \r
+struct consumer::implementation : boost::noncopyable\r
+{              \r
+       executor executor_;\r
+       const std::string filename_;\r
+\r
+       // Audio\r
+       AVStream* audio_st_;\r
+       std::vector<unsigned char, tbb::cache_aligned_allocator<unsigned char>> audio_outbuf_;\r
+\r
+       // Video\r
+       AVStream* video_st_;\r
+       AVFrame* picture_;\r
+       std::vector<uint8_t, tbb::cache_aligned_allocator<unsigned char>> picture_buf_;\r
+\r
+       std::vector<unsigned char, tbb::cache_aligned_allocator<unsigned char>> video_outbuf_;\r
+       SwsContext* img_convert_ctx_;\r
+       \r
+       AVOutputFormat* fmt_;\r
+       std::shared_ptr<AVFormatContext> oc_;\r
+       const video_format_desc format_desc_;\r
+\r
+       std::vector<short, tbb::cache_aligned_allocator<short>> audio_input_buffer_;\r
+\r
+       implementation(const video_format_desc& format_desc, const std::string& filename)\r
+               : filename_(filename)\r
+               , format_desc_(format_desc)\r
+               , audio_st_(0)\r
+               , video_st_(0)\r
+               , picture_(0)\r
+               , fmt_(0)\r
+               , img_convert_ctx_(0)\r
+               , video_outbuf_(format_desc.size)\r
+               , audio_outbuf_(48000)\r
+       {\r
+               executor_.set_capacity(8);\r
+               executor_.start();\r
+\r
+               static boost::once_flag av_register_all_flag = BOOST_ONCE_INIT;\r
+               boost::call_once(av_register_all, av_register_all_flag);        \r
+               \r
+               static boost::once_flag avcodec_init_flag = BOOST_ONCE_INIT;\r
+               boost::call_once(avcodec_init, avcodec_init_flag);      \r
+\r
+               fmt_ = av_guess_format(nullptr, filename.c_str(), nullptr);\r
+               if (!fmt_) \r
+               {\r
+                       CASPAR_LOG(warning) << "Could not deduce output format from ffmpeg extension: using MPEG.";\r
+                       fmt_ = av_guess_format("mpeg", nullptr, nullptr);\r
+               }\r
+               if (!fmt_)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Could not find suitable output format"));\r
+               \r
+               oc_.reset(avformat_alloc_context(), av_free);\r
+               if (!oc_)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Memory error"));\r
+               std::copy_n(filename.c_str(), filename.size(), oc_->filename);\r
+\r
+               oc_->oformat = fmt_;\r
+               // To avoid mpeg buffer underflow (http://www.mail-archive.com/libav-user@mplayerhq.hu/msg00194.html)\r
+               oc_->preload = static_cast<int>(0.5*AV_TIME_BASE);\r
+               oc_->max_delay = static_cast<int>(0.7*AV_TIME_BASE);\r
+                       \r
+               //  Add the audio and video streams using the default format codecs     and initialize the codecs .\r
+               if (fmt_->video_codec != CODEC_ID_NONE)         \r
+                       video_st_ = add_video_stream(fmt_->video_codec);\r
+               \r
+               if (fmt_->audio_codec != CODEC_ID_NONE) \r
+                       audio_st_ = add_audio_stream(fmt_->audio_codec);        \r
+\r
+               // Set the output parameters (must be done even if no parameters).\r
+               \r
+               int errn = 0;\r
+               if ((errn = -av_set_parameters(oc_.get(), nullptr)) > 0)\r
+                       BOOST_THROW_EXCEPTION(\r
+                               file_read_error() << \r
+                               msg_info("Invalid output format parameters") <<\r
+                               boost::errinfo_api_function("avcodec_open") <<\r
+                               boost::errinfo_errno(errn) <<\r
+                               boost::errinfo_file_name(filename_));\r
+               \r
+               dump_format(oc_.get(), 0, filename.c_str(), 1);\r
+\r
+               // Now that all the parameters are set, we can open the audio and\r
+               // video codecs and allocate the necessary encode buffers.\r
+               if (video_st_)\r
+                       open_video(video_st_);\r
+               \r
+               try\r
+               {\r
+                       if (audio_st_)\r
+                               open_audio(audio_st_);\r
+               }\r
+               catch(...)\r
+               {\r
+                       CASPAR_LOG_CURRENT_EXCEPTION();\r
+                       audio_st_ = nullptr;\r
+               }\r
\r
+               // Open the output ffmpeg, if needed.\r
+               if (!(fmt_->flags & AVFMT_NOFILE)) \r
+               {\r
+                       int errn = 0;\r
+                       if ((errn = -url_fopen(&oc_->pb, filename.c_str(), URL_WRONLY)) > 0) \r
+                               BOOST_THROW_EXCEPTION(\r
+                                       file_not_found() << \r
+                                       msg_info("Could not open file") <<\r
+                                       boost::errinfo_api_function("url_fopen") <<\r
+                                       boost::errinfo_errno(errn) <<\r
+                                       boost::errinfo_file_name(filename_));\r
+               }\r
+               \r
+               av_write_header(oc_.get()); // write the stream header, if any \r
+       }\r
+\r
+       ~implementation()\r
+       {    \r
+               av_write_trailer(oc_.get());\r
+\r
+               // Close each codec.\r
+               if (video_st_)          \r
+                       avcodec_close(video_st_->codec);\r
+               \r
+               if (audio_st_)\r
+                       avcodec_close(audio_st_->codec);\r
+                               \r
+               if(picture_)\r
+                       av_free(picture_);              \r
+\r
+               // Free the streams.\r
+               for(size_t i = 0; i < oc_->nb_streams; ++i) \r
+               {\r
+                       av_freep(&oc_->streams[i]->codec);\r
+                       av_freep(&oc_->streams[i]);\r
+               }\r
+\r
+               if (!(fmt_->flags & AVFMT_NOFILE)) \r
+                       url_fclose(oc_->pb); // Close the output ffmpeg.\r
+       }\r
+\r
+       AVStream* add_video_stream(enum CodecID codec_id)\r
+       { \r
+               auto st = av_new_stream(oc_.get(), 0);\r
+               if (!st) \r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Could not alloc stream"));\r
\r
+               auto c = st->codec;\r
+               c->codec_id = codec_id;\r
+               c->codec_type = AVMEDIA_TYPE_VIDEO;\r
\r
+               // Put sample parameters.\r
+               c->bit_rate = (format_desc_.size*static_cast<int>(format_desc_.fps))/2;\r
+               c->width = format_desc_.width;\r
+               c->height = format_desc_.height;\r
+               c->time_base.den = static_cast<int>(format_desc_.fps);\r
+               c->time_base.num = 1;\r
+               c->gop_size = 12; // Emit one intra frame every twelve frames at most.\r
+               c->pix_fmt = PIX_FMT_YUV420P;\r
+\r
+               // Some formats want stream headers to be separate.\r
+               if(oc_->oformat->flags & AVFMT_GLOBALHEADER)\r
+                       c->flags |= CODEC_FLAG_GLOBAL_HEADER;\r
\r
+               return st;\r
+       }\r
+        \r
+       void open_video(AVStream* st)\r
+       { \r
+               auto c = st->codec;\r
\r
+               auto codec = avcodec_find_encoder(c->codec_id);\r
+               if (!codec)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found"));\r
+               \r
+               int errn = 0;\r
+               if ((errn = -avcodec_open(c, codec)) > 0)\r
+                       BOOST_THROW_EXCEPTION(\r
+                               file_read_error() << \r
+                               msg_info("Could not open video codec.") <<\r
+                               boost::errinfo_api_function("avcodec_open") <<\r
+                               boost::errinfo_errno(errn) <<\r
+                               boost::errinfo_file_name(filename_));            \r
+       }\r
+  \r
+       void encode_video_frame(const safe_ptr<const read_frame>& frame)\r
+       { \r
+               AVCodecContext* c = video_st_->codec;\r
\r
+               if (img_convert_ctx_ == nullptr) \r
+               {\r
+                       img_convert_ctx_ = sws_getContext(format_desc_.width, format_desc_.height, PIX_FMT_BGRA, c->width, c->height, c->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr);\r
+                       if (img_convert_ctx_ == nullptr) \r
+                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Cannot initialize the conversion context"));\r
+               }\r
+\r
+               std::shared_ptr<AVFrame> av_frame(avcodec_alloc_frame(), av_free);\r
+               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
+                               \r
+               AVFrame local_av_frame;\r
+               avcodec_get_frame_defaults(&local_av_frame);\r
+               picture_buf_.resize(avpicture_get_size(c->pix_fmt, format_desc_.width, format_desc_.height));\r
+               avpicture_fill(reinterpret_cast<AVPicture*>(&local_av_frame), picture_buf_.data(), c->pix_fmt, format_desc_.width, format_desc_.height);\r
+\r
+               sws_scale(img_convert_ctx_, av_frame->data, av_frame->linesize, 0, c->height, local_av_frame.data, local_av_frame.linesize);\r
+                               \r
+               auto out_size = avcodec_encode_video(c, video_outbuf_.data(), video_outbuf_.size(), &local_av_frame);\r
+\r
+               AVPacket pkt;\r
+               av_init_packet(&pkt);\r
+               pkt.size = out_size;\r
+\r
+               // If zero size, it means the image was buffered.\r
+               if (out_size > 0) \r
+               { \r
+                       if (c->coded_frame->pts != AV_NOPTS_VALUE)\r
+                               pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, video_st_->time_base);\r
+                       if(c->coded_frame->key_frame)\r
+                               pkt.flags |= AV_PKT_FLAG_KEY;\r
+\r
+                       pkt.stream_index = video_st_->index;\r
+                       pkt.data = video_outbuf_.data();\r
+               } \r
+               \r
+               if (av_interleaved_write_frame(oc_.get(), &pkt) != 0)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Error while writing video frame"));\r
+       }\r
+\r
+       AVStream* add_audio_stream(enum CodecID codec_id)\r
+       {\r
+               audio_st_ = av_new_stream(oc_.get(), 1);\r
+               if (!audio_st_)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Could not alloc stream"));\r
+\r
+               auto c = audio_st_->codec;\r
+               c->codec_id = codec_id;\r
+               c->codec_type = AVMEDIA_TYPE_AUDIO;\r
+\r
+               // Put sample parameters.\r
+               c->bit_rate = 192000;\r
+               c->sample_rate = 48000;\r
+               c->channels = 2;\r
+\r
+               // Some formats want stream headers to be separate.\r
+               if(oc_->oformat->flags & AVFMT_GLOBALHEADER)\r
+                       c->flags |= CODEC_FLAG_GLOBAL_HEADER;\r
+\r
+               return audio_st_;\r
+       }\r
+\r
+       void open_audio(AVStream* st)\r
+       {\r
+               auto c = st->codec;\r
+\r
+               // Find the audio encoder.\r
+               auto codec = avcodec_find_encoder(c->codec_id);\r
+               if (!codec) \r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found"));\r
+               \r
+               // Open it.\r
+               int errn = 0;\r
+               if ((errn = -avcodec_open(c, codec)) > 0)\r
+                       BOOST_THROW_EXCEPTION(\r
+                               file_read_error() << \r
+                               msg_info("Could not open audio codec") <<\r
+                               boost::errinfo_api_function("avcodec_open") <<\r
+                               boost::errinfo_errno(errn) <<\r
+                               boost::errinfo_file_name(filename_));\r
+       }\r
+       \r
+       void encode_audio_frame(const safe_ptr<const read_frame>& frame)\r
+       {       \r
+               if(!frame->audio_data().empty())\r
+                       audio_input_buffer_.insert(audio_input_buffer_.end(), frame->audio_data().begin(), frame->audio_data().end());\r
+               else\r
+                       audio_input_buffer_.insert(audio_input_buffer_.end(), 1920, 0);\r
+\r
+               while(encode_audio_packet()){}\r
+\r
+               CASPAR_LOG(trace) << audio_input_buffer_.size();\r
+       }\r
+\r
+       bool encode_audio_packet()\r
+       {                               \r
+               auto c = audio_st_->codec;\r
+\r
+               auto frame_bytes = c->frame_size * 2 * 2; // samples per frame * 2 channels * 2 bytes per sample\r
+               if(static_cast<int>(audio_input_buffer_.size()) < frame_bytes/2)\r
+                       return false;\r
+\r
+               AVPacket pkt;\r
+               av_init_packet(&pkt);\r
+               \r
+               pkt.size = avcodec_encode_audio(c, audio_outbuf_.data(), audio_outbuf_.size(), audio_input_buffer_.data());\r
+\r
+               audio_input_buffer_ = std::vector<short, tbb::cache_aligned_allocator<short>>(audio_input_buffer_.begin() + frame_bytes/2, audio_input_buffer_.end());\r
+\r
+               if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE)\r
+                       pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st_->time_base);\r
+               pkt.flags |= AV_PKT_FLAG_KEY;\r
+               pkt.stream_index = audio_st_->index;\r
+               pkt.data = audio_outbuf_.data();\r
+               \r
+               if (av_interleaved_write_frame(oc_.get(), &pkt) != 0)\r
+                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Error while writing audio frame"));\r
+\r
+               return true;\r
+       }\r
+        \r
+       void send(const safe_ptr<const read_frame>& frame)\r
+       {\r
+               if(frame->audio_data().empty())\r
+                       return;\r
+\r
+               executor_.begin_invoke([=]\r
+               {                               \r
+                       auto my_frame = frame;\r
+                       encode_video_frame(my_frame);\r
+                       encode_audio_frame(my_frame);\r
+               });\r
+       }\r
+\r
+       size_t buffer_depth() const { return 1; }\r
+};\r
+\r
+consumer::consumer(const video_format_desc& format_desc, const std::wstring& filename) : impl_(new implementation(format_desc, narrow(filename))){}\r
+consumer::consumer(consumer&& other) : impl_(std::move(other.impl_)){}\r
+void consumer::send(const safe_ptr<const read_frame>& frame){impl_->send(frame);}\r
+size_t consumer::buffer_depth() const{return impl_->buffer_depth();}\r
+\r
+}}}\r
diff --git a/core/consumer/ffmpeg/ffmpeg_consumer.h b/core/consumer/ffmpeg/ffmpeg_consumer.h
new file mode 100644 (file)
index 0000000..3dc70c3
--- /dev/null
@@ -0,0 +1,40 @@
+/*\r
+* copyright (c) 2010 Sveriges Television AB <info@casparcg.com>\r
+*\r
+*  This file is part of CasparCG.\r
+*\r
+*    CasparCG is free software: you can redistribute it and/or modify\r
+*    it under the terms of the GNU General Public License as published by\r
+*    the Free Software Foundation, either version 3 of the License, or\r
+*    (at your option) any later version.\r
+*\r
+*    CasparCG is distributed in the hope that it will be useful,\r
+*    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+*    GNU General Public License for more details.\r
+\r
+*    You should have received a copy of the GNU General Public License\r
+*    along with CasparCG.  If not, see <http://www.gnu.org/licenses/>.\r
+*\r
+*/\r
+#pragma once\r
+\r
+#include "../../video_format.h"\r
+#include "../../consumer/frame_consumer.h"\r
+\r
+namespace caspar { namespace core { namespace ffmpeg {\r
+       \r
+class consumer : public frame_consumer\r
+{\r
+public:        \r
+       explicit consumer(const video_format_desc& format_desc, const std::wstring& filename);\r
+       consumer(consumer&& other);\r
+       \r
+       virtual void send(const safe_ptr<const read_frame>&);\r
+       virtual size_t buffer_depth() const;\r
+private:\r
+       struct implementation;\r
+       std::shared_ptr<implementation> impl_;\r
+};\r
+\r
+}}}
\ No newline at end of file
index 95c3b78aac389661651ea2eb7bcb408d09b8fcfe..a14df05d0effcc924a9e42a79d91c5a230ac92d0 100644 (file)
     <ClCompile Include="consumer\ffmpeg\ffmpeg_consumer.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../../StdAfx.h</PrecompiledHeaderFile>\r
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>\r
-      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>\r
     </ClCompile>\r
     <ClCompile Include="consumer\frame_consumer_device.cpp">\r
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../StdAfx.h</PrecompiledHeaderFile>\r
index 838fd03324efdce216251d69cb77885929e0a959..e94533365acc72c02552c637629182c3a1f322e7 100644 (file)
@@ -6,6 +6,8 @@
 #include <core/consumer/bluefish/bluefish_consumer.h>\r
 #include <core/consumer/decklink/decklink_consumer.h>\r
 #include <core/consumer/ogl/ogl_consumer.h>\r
+#include <core/consumer/ffmpeg/ffmpeg_consumer.h>\r
+\r
 #include <core/producer/flash/FlashAxContainer.h>\r
 \r
 #include <protocol/amcp/AMCPProtocolStrategy.h>\r
@@ -92,7 +94,7 @@ struct bootstrapper::implementation : boost::noncopyable
                                        CASPAR_LOG_CURRENT_EXCEPTION();\r
                                }\r
                        }\r
-                       \r
+                                                       \r
                        channels_.push_back(channel(format_desc, consumers));\r
                }\r
        }\r
index c762927a660db21e04a9593c2f8c537ca22a80a5..b2c2f77cc7acd9dca9a015719179a8da1addee7e 100644 (file)
@@ -12,7 +12,7 @@
       <videomode>PAL</videomode>\r
       <consumers>\r
         <ogl>\r
-          <device>0</device>\r
+          <device>1</device>\r
           <stretch>uniform</stretch>\r
           <windowed>true</windowed>\r
         </ogl>\r