\r
virtual void send(const safe_ptr<const read_frame>& frame) = 0;\r
virtual size_t buffer_depth() const {return 1;}\r
+ virtual bool key_only() const{ return false;}\r
virtual void initialize(const video_format_desc& format_desc) = 0;\r
virtual std::wstring print() const = 0;\r
\r
#include "frame_consumer_device.h"\r
\r
#include "../video_format.h"\r
+#include "../mixer/gpu/ogl_device.h"\r
+#include "../mixer/read_frame.h"\r
\r
#include <common/concurrency/executor.h>\r
#include <common/diagnostics/graph.h>\r
#include <common/utility/assert.h>\r
+#include <common/memory/memshfl.h>\r
\r
#include <boost/range/algorithm_ext/erase.hpp>\r
#include <boost/range/algorithm.hpp>\r
#include <boost/circular_buffer.hpp>\r
#include <boost/timer.hpp>\r
+#include <boost/range/algorithm.hpp>\r
\r
namespace caspar { namespace core {\r
\r
struct frame_consumer_device::implementation\r
{ \r
- boost::circular_buffer<safe_ptr<const read_frame>> buffer_;\r
+ boost::circular_buffer<std::pair<safe_ptr<const read_frame>,safe_ptr<const read_frame>>> buffer_;\r
\r
std::map<int, std::shared_ptr<frame_consumer>> consumers_; // Valid iterators after erase\r
\r
{\r
diag_->set_value("input-buffer", static_cast<float>(executor_.size())/static_cast<float>(executor_.capacity()));\r
frame_timer_.restart();\r
+ \r
+ auto key_frame = read_frame::empty();\r
+\r
+ if(boost::range::find_if(consumers_, [](const decltype(*consumers_.begin())& p){return p.second->key_only();}) != consumers_.end())\r
+ {\r
+ // Currently do key_only transform on cpu. Unsure if the extra 400MB/s (1080p50) overhead is worth it to do it on gpu.\r
+ auto key_data = ogl_device::create_host_buffer(frame->image_data().size(), host_buffer::write_only); \r
+ fast_memsfhl(key_data->data(), frame->image_data().begin(), frame->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
+ std::vector<short> audio_data(frame->audio_data().begin(), frame->audio_data().end());\r
+ key_frame = make_safe<const read_frame>(std::move(key_data), std::move(audio_data));\r
+ }\r
\r
- buffer_.push_back(std::move(frame));\r
+ buffer_.push_back(std::make_pair(std::move(frame), std::move(key_frame)));\r
\r
if(!buffer_.full())\r
return;\r
+\r
\r
auto it = consumers_.begin();\r
while(it != consumers_.end())\r
{\r
try\r
{\r
- it->second->send(buffer_[it->second->buffer_depth()-1]);\r
+ auto p = buffer_[it->second->buffer_depth()-1];\r
+ it->second->send(it->second->key_only() ? p.second : p.first);\r
++it;\r
}\r
catch(...)\r
std::unique_ptr<bluefish_consumer> consumer_;\r
const size_t device_index_;\r
const bool embedded_audio_;\r
+ bool key_only_;\r
public:\r
\r
- bluefish_consumer_proxy(size_t device_index, bool embedded_audio)\r
+ bluefish_consumer_proxy(size_t device_index, bool embedded_audio, bool key_only)\r
: device_index_(device_index)\r
- , embedded_audio_(embedded_audio){}\r
+ , embedded_audio_(embedded_audio)\r
+ , key_only_(key_only){}\r
\r
virtual void initialize(const core::video_format_desc& format_desc)\r
{\r
{\r
return consumer_->print();\r
}\r
+\r
+ virtual bool key_only() const\r
+ {\r
+ return key_only_;\r
+ }\r
}; \r
\r
std::wstring get_bluefish_version()\r
device_index = lexical_cast_or_default<int>(params[1], 1);\r
\r
bool embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
+ bool key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
\r
- return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio);\r
+ return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio, key_only);\r
}\r
\r
safe_ptr<core::frame_consumer> create_bluefish_consumer(const boost::property_tree::ptree& ptree) \r
{ \r
auto device_index = ptree.get("device", 0);\r
auto embedded_audio = ptree.get("embedded-audio", false);\r
+ bool key_only = (ptree.get("output", "fill_and_key") == "key_only");\r
\r
- return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio);\r
+ return make_safe<bluefish_consumer_proxy>(device_index, embedded_audio, key_only);\r
}\r
\r
}
\ No newline at end of file
STDMETHOD(GetAncillaryData(IDeckLinkVideoFrameAncillary** ancillary)) {return S_FALSE;}\r
};\r
\r
-std::shared_ptr<IDeckLinkVideoFrame> make_alpha_only_frame(const CComQIPtr<IDeckLinkOutput>& decklink, const safe_ptr<const core::read_frame>& frame, const core::video_format_desc& format_desc)\r
-{\r
- if(static_cast<size_t>(frame->image_data().size()) != format_desc.size)\r
- return std::make_shared<decklink_frame_adapter>(frame, format_desc);\r
-\r
- IDeckLinkMutableVideoFrame* result;\r
-\r
- if(FAILED(decklink->CreateVideoFrame(format_desc.width, format_desc.height, format_desc.size/format_desc.height, bmdFormat8BitBGRA, bmdFrameFlagDefault, &result)))\r
- BOOST_THROW_EXCEPTION(caspar_exception());\r
-\r
- void* bytes = nullptr;\r
- if(FAILED(result->GetBytes(&bytes)))\r
- BOOST_THROW_EXCEPTION(caspar_exception());\r
- \r
- fast_memsfhl(reinterpret_cast<unsigned char*>(bytes), frame->image_data().begin(), frame->image_data().size(), 0x0F0F0F0F, 0x0B0B0B0B, 0x07070707, 0x03030303);\r
-\r
- return std::shared_ptr<IDeckLinkVideoFrame>(result, [](IDeckLinkMutableVideoFrame* p) {p->Release();});\r
-}\r
-\r
struct decklink_consumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback, boost::noncopyable\r
{ \r
const configuration config_;\r
\r
void schedule_next_video(const safe_ptr<const core::read_frame>& frame)\r
{\r
- std::shared_ptr<IDeckLinkVideoFrame> deck_frame;\r
- if(config_.output == key_only)\r
- deck_frame = make_alpha_only_frame(output_, frame, format_desc_);\r
- else \r
- deck_frame = std::make_shared<decklink_frame_adapter>(frame, format_desc_);\r
-\r
- frame_container_.push_back(deck_frame);\r
+ frame_container_.push_back(std::make_shared<decklink_frame_adapter>(frame, format_desc_));\r
if(FAILED(output_->ScheduleVideoFrame(frame_container_.back().get(), (frames_scheduled_++) * format_desc_.duration, format_desc_.duration, format_desc_.time_scale)))\r
CASPAR_LOG(error) << print() << L" Failed to schedule video.";\r
\r
{\r
return context_->print();\r
}\r
+\r
+ virtual bool key_only() const\r
+ {\r
+ return (config_.output == caspar::key_only);\r
+ }\r
}; \r
\r
safe_ptr<core::frame_consumer> create_decklink_consumer(const std::vector<std::wstring>& params) \r
config.latency = normal_latency;\r
\r
config.embedded_audio = std::find(params.begin(), params.end(), L"EMBEDDED_AUDIO") != params.end();\r
+ config.output = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end() ? key_only : fill_and_key;\r
\r
return make_safe<decklink_consumer_proxy>(config);\r
}\r
\r
\r
/* File created by MIDL compiler version 7.00.0555 */\r
-/* at Wed May 18 20:18:19 2011\r
+/* at Wed May 18 23:30:51 2011\r
*/\r
/* Compiler settings for interop\DeckLinkAPI.idl:\r
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 7.00.0555 \r
\r
\r
/* File created by MIDL compiler version 7.00.0555 */\r
-/* at Wed May 18 20:18:19 2011\r
+/* at Wed May 18 23:30:51 2011\r
*/\r
/* Compiler settings for interop\DeckLinkAPI.idl:\r
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 7.00.0555 \r
\r
namespace caspar { \r
\r
-struct ffmpeg_consumer::implementation : boost::noncopyable\r
+struct ffmpeg_consumer : boost::noncopyable\r
{ \r
std::string filename_;\r
\r
\r
executor executor_;\r
public:\r
- implementation(const std::string& filename)\r
+ ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc)\r
: filename_(filename)\r
, audio_st_(nullptr)\r
, video_st_(nullptr)\r
, img_convert_ctx_(nullptr)\r
, video_outbuf_(1920*1080*4)\r
, audio_outbuf_(48000)\r
- , executor_(L"ffmpeg_consumer")\r
- {}\r
-\r
- ~implementation()\r
- { \r
- executor_.invoke([]{});\r
- executor_.stop();\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
- // 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
- void initialize(const core::video_format_desc& format_desc)\r
+ , format_desc_(format_desc)\r
+ , executor_(L"ffmpeg_consumer", true)\r
{\r
- format_desc_ = format_desc;\r
- executor_.start();\r
active_ = executor_.begin_invoke([]{});\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
+ CASPAR_LOG(info) << "Could not deduce output format from ffmpeg extension: using MPEG.";\r
fmt_ = av_guess_format("mpeg", nullptr, nullptr);\r
filename_ = filename_ + ".avi"; \r
}\r
\r
av_write_header(oc_.get()); // write the stream header, if any \r
\r
- CASPAR_LOG(info) << print() << L" Successfully initialized.";\r
+ CASPAR_LOG(info) << print() << L" Successfully initialized."; \r
}\r
- \r
+\r
+ ~ffmpeg_consumer()\r
+ { \r
+ executor_.invoke([]{});\r
+ executor_.stop();\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
+ // 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
std::wstring print() const\r
{\r
return L"ffmpeg[" + widen(filename_) + L"]";\r
\r
void encode_video_frame(const safe_ptr<const core::read_frame>& frame)\r
{ \r
+ if(!video_st_)\r
+ return;\r
+\r
AVCodecContext* c = video_st_->codec;\r
\r
if (img_convert_ctx_ == nullptr) \r
}\r
\r
bool encode_audio_packet()\r
- { \r
+ { \r
+ if(!audio_st_)\r
+ return false;\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
size_t buffer_depth() const { return 1; }\r
};\r
\r
-ffmpeg_consumer::ffmpeg_consumer(const std::wstring& filename) : impl_(new implementation(narrow(filename))){}\r
-ffmpeg_consumer::ffmpeg_consumer(ffmpeg_consumer&& other) : impl_(std::move(other.impl_)){}\r
-void ffmpeg_consumer::send(const safe_ptr<const core::read_frame>& frame){impl_->send(frame);}\r
-size_t ffmpeg_consumer::buffer_depth() const{return impl_->buffer_depth();}\r
-void ffmpeg_consumer::initialize(const core::video_format_desc& format_desc)\r
+struct ffmpeg_consumer_proxy : public core::frame_consumer\r
{\r
- // TODO: Ugly\r
- impl_.reset(new implementation(impl_->filename_));\r
- impl_->initialize(format_desc);\r
-}\r
-std::wstring ffmpeg_consumer::print() const {return impl_->print();}\r
+ const std::wstring filename_;\r
+ const bool key_only_;\r
+\r
+ std::unique_ptr<ffmpeg_consumer> consumer_;\r
+\r
+public:\r
+\r
+ ffmpeg_consumer_proxy(const std::wstring& filename, bool key_only)\r
+ : filename_(filename)\r
+ , key_only_(key_only){}\r
+ \r
+ virtual void initialize(const core::video_format_desc& format_desc)\r
+ {\r
+ consumer_.reset(new ffmpeg_consumer(narrow(filename_), format_desc));\r
+ }\r
+ \r
+ virtual void send(const safe_ptr<const core::read_frame>& frame)\r
+ {\r
+ consumer_->send(frame);\r
+ }\r
+ \r
+ virtual std::wstring print() const\r
+ {\r
+ return consumer_->print();\r
+ }\r
+\r
+ virtual bool key_only() const\r
+ {\r
+ return key_only_;\r
+ }\r
+}; \r
\r
safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const std::vector<std::wstring>& params)\r
{\r
\r
// TODO: Ask stakeholders about case where file already exists.\r
boost::filesystem::remove(boost::filesystem::wpath(env::media_folder() + params[1])); // Delete the file if it exists\r
- return make_safe<ffmpeg_consumer>(env::media_folder() + params[1]);\r
+ bool key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
+\r
+ return make_safe<ffmpeg_consumer_proxy>(env::media_folder() + params[1], key_only);\r
+}\r
+\r
+safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const boost::property_tree::ptree& ptree)\r
+{\r
+ std::string filename = ptree.get<std::string>("filename");\r
+ bool key_only = (ptree.get("output", "fill_and_key") == "key_only");\r
+ \r
+ return make_safe<ffmpeg_consumer_proxy>(env::media_folder() + widen(filename), key_only);\r
}\r
\r
}\r
*/\r
#pragma once\r
\r
-#include <core/video_format.h>\r
#include <core/consumer/frame_consumer.h>\r
+#include <core/video_format.h>\r
+\r
+#include <boost/property_tree/ptree.hpp>\r
+\r
+#include <string>\r
+#include <vector>\r
\r
namespace caspar { \r
\r
-class ffmpeg_consumer : public core::frame_consumer\r
-{\r
-public: \r
- explicit ffmpeg_consumer(const std::wstring& filename);\r
- ffmpeg_consumer(ffmpeg_consumer&& other);\r
- \r
- virtual void initialize(const core::video_format_desc& format_desc);\r
- virtual void send(const safe_ptr<const core::read_frame>&);\r
- virtual size_t buffer_depth() const;\r
- virtual std::wstring print() const;\r
-private:\r
- struct implementation;\r
- std::shared_ptr<implementation> impl_;\r
-};\r
-\r
safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const std::vector<std::wstring>& params);\r
+safe_ptr<core::frame_consumer> create_ffmpeg_consumer(const boost::property_tree::ptree& ptree);\r
\r
}
\ No newline at end of file
av_register_all();\r
avcodec_init();\r
\r
- core::register_consumer_factory(create_ffmpeg_consumer);\r
+ core::register_consumer_factory([](const std::vector<std::wstring>& params){return create_ffmpeg_consumer(params);});\r
core::register_producer_factory(create_ffmpeg_producer);\r
}\r
\r
boost::errinfo_api_function("av_find_stream_info") <<\r
boost::errinfo_errno(AVUNERROR(errn)));\r
}\r
-\r
- video_codec_context_ = open_stream(AVMEDIA_TYPE_VIDEO, video_s_index_);\r
+ \r
+ video_codec_context_ = open_stream(AVMEDIA_TYPE_VIDEO, video_s_index_, 4);\r
if(!video_codec_context_)\r
CASPAR_LOG(warning) << print() << " Could not open any video stream.";\r
else\r
CASPAR_LOG(warning) << print() << " Could not open any audio stream.";\r
else\r
fix_time_base(audio_codex_context_.get());\r
-\r
+ \r
if(!video_codec_context_ && !audio_codex_context_)\r
{ \r
BOOST_THROW_EXCEPTION(\r
source_info(narrow(print())) << \r
msg_info("No video or audio codec context found.")); \r
}\r
-\r
+ \r
if(start_ != 0) \r
seek_frame(start_);\r
\r
context->time_base.num = static_cast<int>(std::pow(10.0, static_cast<int>(std::log10(static_cast<float>(context->time_base.den)))-1));\r
}\r
\r
- std::shared_ptr<AVCodecContext> open_stream(int codec_type, int& s_index) const\r
+ std::shared_ptr<AVCodecContext> open_stream(int codec_type, int& s_index, int thread_count = -1) const\r
{ \r
const auto streams = boost::iterator_range<AVStream**>(format_context_->streams, format_context_->streams+format_context_->nb_streams);\r
const auto stream = boost::find_if(streams, [&](AVStream* stream) \r
#include <array>\r
\r
namespace caspar {\r
+ \r
+enum stretch\r
+{\r
+ none,\r
+ uniform,\r
+ fill,\r
+ uniform_to_fill\r
+};\r
\r
struct ogl_consumer : boost::noncopyable\r
{ \r
size_t screen_index_;\r
caspar::stretch stretch_;\r
bool windowed_;\r
+ bool key_only_;\r
\r
std::unique_ptr<ogl_consumer> consumer_;\r
\r
public:\r
\r
- ogl_consumer_proxy(size_t screen_index, stretch stretch, bool windowed)\r
+ ogl_consumer_proxy(size_t screen_index, stretch stretch, bool windowed, bool key_only)\r
: screen_index_(screen_index)\r
, stretch_(stretch)\r
- , windowed_(windowed){}\r
+ , windowed_(windowed)\r
+ , key_only_(key_only){}\r
\r
virtual void initialize(const core::video_format_desc& format_desc)\r
{\r
{\r
return consumer_->print();\r
}\r
+\r
+ virtual bool key_only() const\r
+ {\r
+ return key_only_;\r
+ }\r
}; \r
\r
safe_ptr<core::frame_consumer> create_ogl_consumer(const std::vector<std::wstring>& params)\r
if(params.size() > 2) \r
windowed = lexical_cast_or_default<bool>(params[3], windowed);\r
\r
- return make_safe<ogl_consumer_proxy>(screen_index, stretch, windowed);\r
+ bool key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end();\r
+\r
+ return make_safe<ogl_consumer_proxy>(screen_index, stretch, windowed, key_only);\r
}\r
\r
safe_ptr<core::frame_consumer> create_ogl_consumer(const boost::property_tree::ptree& ptree) \r
else if(key_str == "uniform_to_fill")\r
stretch = stretch::uniform_to_fill;\r
\r
- return make_safe<ogl_consumer_proxy>(screen_index, stretch, windowed);\r
+ bool key_only = (ptree.get("output", "fill_and_key") == "key_only");\r
+\r
+ return make_safe<ogl_consumer_proxy>(screen_index, stretch, windowed, key_only);\r
}\r
\r
}
\ No newline at end of file
#include <boost/property_tree/ptree.hpp>\r
\r
namespace caspar {\r
- \r
-enum stretch\r
-{\r
- none,\r
- uniform,\r
- fill,\r
- uniform_to_fill\r
-};\r
\r
safe_ptr<core::frame_consumer> create_ogl_consumer(const std::vector<std::wstring>& params);\r
safe_ptr<core::frame_consumer> create_ogl_consumer(const boost::property_tree::ptree& ptree);\r
</diagnostics>\r
<channels>\r
<channel>\r
- <videomode>PAL</videomode>\r
+ <videomode>1080i5000</videomode>\r
<consumers>\r
<decklink>\r
<device>1</device>\r
<device>1</device>\r
<stretch>uniform</stretch>\r
<windowed>true</windowed>\r
+ <output>key_only</output>\r
</ogl>\r
+ <file>\r
+ <filename>TESTING</filename>\r
+ </file>\r
<!--<audio/>-->\r
<!--<bluefish>\r
<device>1</device>\r
else if(name == "bluefish") \r
channels_.back()->consumer()->add(index++, create_bluefish_consumer(xml_consumer.second)); \r
else if(name == "decklink") \r
- channels_.back()->consumer()->add(index++, create_decklink_consumer(xml_consumer.second)); \r
+ channels_.back()->consumer()->add(index++, create_decklink_consumer(xml_consumer.second)); \r
+ else if(name == "file") \r
+ channels_.back()->consumer()->add(index++, create_ffmpeg_consumer(xml_consumer.second)); \r
else if(name == "audio")\r
channels_.back()->consumer()->add(index++, oal_consumer()); \r
- else\r
- CASPAR_LOG(warning) << "Invalid consumer: " << widen(name); \r
+ else if(name != "<xmlcomment>")\r
+ CASPAR_LOG(warning) << "Invalid consumer: " << widen(name); \r
}\r
catch(...)\r
{\r