--- /dev/null
+/*\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