X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fffmpeg%2Fproducer%2Fffmpeg_producer.cpp;h=919011fc208e9eb5bfb4f9a0a4cec86a188a0e0c;hb=c10279387895a1a0e18ea1d089ccd9f2a4bbd44c;hp=0331840b275257df958a3ddb23c9e90d6ea04d0d;hpb=4a51c1cf40508e1a145662eae3b22ba6372cf073;p=casparcg diff --git a/modules/ffmpeg/producer/ffmpeg_producer.cpp b/modules/ffmpeg/producer/ffmpeg_producer.cpp index 0331840b2..919011fc2 100644 --- a/modules/ffmpeg/producer/ffmpeg_producer.cpp +++ b/modules/ffmpeg/producer/ffmpeg_producer.cpp @@ -1,241 +1,347 @@ /* -* copyright (c) 2010 Sveriges Television AB +* Copyright (c) 2011 Sveriges Television AB * -* This file is part of CasparCG. +* This file is part of CasparCG (www.casparcg.com). * -* CasparCG is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. +* CasparCG is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. * -* CasparCG is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. - -* You should have received a copy of the GNU General Public License -* along with CasparCG. If not, see . +* CasparCG is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with CasparCG. If not, see . * +* Author: Robert Nagy, ronag89@gmail.com */ + #include "../stdafx.h" #include "ffmpeg_producer.h" -#include "frame_muxer.h" -#include "input.h" -#include "util.h" +#include "../ffmpeg_error.h" + +#include "muxer/frame_muxer.h" +#include "input/input.h" +#include "util/util.h" #include "audio/audio_decoder.h" #include "video/video_decoder.h" -#include "../ffmpeg_error.h" #include -#include +#include +#include #include #include #include -#include -#include +#include +#include +#include +#include #include +#include #include #include #include #include #include #include +#include +#include +#include -#include -#include -#include -#include +#include -using namespace Concurrency; +#include +#include +#include namespace caspar { namespace ffmpeg { - + struct ffmpeg_producer : public core::frame_producer -{ - const std::wstring filename_; - const int start_; - const bool loop_; - const size_t length_; +{ + monitor::basic_subject event_subject_; + const std::wstring filename_; - unbounded_buffer> packets_; - unbounded_buffer> video_; - unbounded_buffer> audio_; - call> throw_away_; - bounded_buffer> frames_; - - const safe_ptr graph_; + const spl::shared_ptr graph_; - std::shared_ptr audio_decoder_; - std::shared_ptr video_decoder_; - std::unique_ptr muxer_; - input input_; + const spl::shared_ptr frame_factory_; + const core::video_format_desc format_desc_; + + input input_; + std::unique_ptr video_decoder_; + std::unique_ptr audio_decoder_; + std::unique_ptr muxer_; - safe_ptr last_frame_; + const double fps_; + const uint32_t start_; + const uint32_t length_; + + int64_t frame_number_; public: - explicit ffmpeg_producer(const safe_ptr& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, int start, size_t length) + explicit ffmpeg_producer(const spl::shared_ptr& frame_factory, const std::wstring& filename, const std::wstring& filter, bool loop, uint32_t start, uint32_t length) : filename_(filename) + , frame_factory_(frame_factory) + , format_desc_(frame_factory->video_format_desc()) + , input_(graph_, filename_, loop, start, length) + , fps_(read_fps(*input_.context(), format_desc_.fps)) , start_(start) - , loop_(loop) , length_(length) - , throw_away_([](const safe_ptr&){}) - , frames_(2) - , graph_(diagnostics::create_graph("", false)) - , input_(packets_, graph_, filename_, loop, start, length) - , last_frame_(core::basic_frame::empty()) + , frame_number_(0) { - frame_muxer2::video_source_t* video_source = nullptr; - frame_muxer2::audio_source_t* audio_source = nullptr; - + graph_->set_color("frame-time", diagnostics::color(0.1f, 1.0f, 0.1f)); + graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f)); + diagnostics::register_graph(graph_); + try { - video_decoder_.reset(new video_decoder(packets_, video_, *input_.context())); - video_source = &video_; + video_decoder_.reset(new video_decoder(input_.context())); + video_decoder_->subscribe(event_subject_); + CASPAR_LOG(info) << print() << L" " << video_decoder_->print(); } - catch(ffmpeg_stream_not_found&) + catch(averror_stream_not_found&) { - CASPAR_LOG(warning) << "No video-stream found. Running without video."; + //CASPAR_LOG(warning) << print() << " No video-stream found. Running without video."; } catch(...) { CASPAR_LOG_CURRENT_EXCEPTION(); - CASPAR_LOG(warning) << "Failed to open video-stream. Running without video."; + CASPAR_LOG(warning) << print() << "Failed to open video-stream. Running without video."; } try { - audio_decoder_.reset(new audio_decoder(packets_, audio_, *input_.context(), frame_factory->get_video_format_desc())); - audio_source = &audio_; + audio_decoder_.reset(new audio_decoder(input_.context(), frame_factory->video_format_desc())); + audio_decoder_->subscribe(event_subject_); + CASPAR_LOG(info) << print() << L" " << audio_decoder_->print(); } - catch(ffmpeg_stream_not_found&) + catch(averror_stream_not_found&) { - CASPAR_LOG(warning) << "No audio-stream found. Running without video."; + //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio."; } catch(...) { CASPAR_LOG_CURRENT_EXCEPTION(); - CASPAR_LOG(warning) << "Failed to open audio-stream. Running without audio."; - } - - packets_.link_target(&throw_away_); - - CASPAR_VERIFY(video_decoder_ || audio_decoder_, ffmpeg_error()); + CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio."; + } - muxer_.reset(new frame_muxer2(video_source, audio_source, frames_, video_decoder_ ? video_decoder_->fps() : frame_factory->get_video_format_desc().fps, frame_factory, filter)); - - graph_->set_color("underflow", diagnostics::color(0.6f, 0.3f, 0.9f)); - graph_->start(); + if(!video_decoder_ && !audio_decoder_) + BOOST_THROW_EXCEPTION(averror_stream_not_found() << msg_info("No streams found")); - input_.start(); + muxer_.reset(new frame_muxer(fps_, frame_factory, filter)); } - ~ffmpeg_producer() - { - input_.stop(); - while(Concurrency::receive(frames_) != core::basic_frame::eof()) + // frame_producer + + virtual spl::shared_ptr receive(int flags) override + { + boost::timer frame_timer; + + std::shared_ptr frame = try_decode_frame(flags); + + graph_->set_value("frame-time", frame_timer.elapsed()*format_desc_.fps*0.5); + + event_subject_ << monitor::event("profiler/time") % frame_timer.elapsed() % (1.0/format_desc_.fps) + << monitor::event("file/time") % monitor::duration(file_frame_number()/fps_) + % monitor::duration(file_nb_frames()/fps_) + << monitor::event("file/frame") % static_cast(file_frame_number()) + % static_cast(file_nb_frames()) + << monitor::event("file/fps") % fps_ + << monitor::event("filename") % u8(filename_) + << monitor::event("loop") % input_.loop(); + + if(!frame) { + if(!input_.eof()) + graph_->set_tag("underflow"); + return core::draw_frame::late(); } + + ++frame_number_; + + graph_->set_text(print()); + + return spl::make_shared_ptr(frame); } - - virtual safe_ptr receive(int hints) + + virtual uint32_t nb_frames() const override { - auto frame = core::basic_frame::late(); + if(input_.loop()) + return std::numeric_limits::max(); + + uint32_t nb_frames = file_nb_frames(); + + nb_frames = std::min(length_, nb_frames); + nb_frames = muxer_->calc_nb_frames(nb_frames); - try - { - frame = last_frame_ = Concurrency::receive(frames_, 5); - graph_->update_text(narrow(print())); - } - catch(operation_timed_out&) - { - graph_->add_tag("underflow"); - } + return nb_frames > start_ ? nb_frames - start_ : 0; + } - return frame; + uint32_t file_nb_frames() const + { + uint32_t file_nb_frames = 0; + file_nb_frames = std::max(file_nb_frames, video_decoder_ ? video_decoder_->nb_frames() : 0); + file_nb_frames = std::max(file_nb_frames, audio_decoder_ ? audio_decoder_->nb_frames() : 0); + return file_nb_frames; } - virtual safe_ptr last_frame() const + uint32_t file_frame_number() const { - return disable_audio(last_frame_); + return video_decoder_ ? video_decoder_->file_frame_number() : 0; } - virtual int64_t nb_frames() const + virtual boost::unique_future call(const std::wstring& param) override { - if(loop_) - return std::numeric_limits::max(); - - // This function estimates nb_frames until input has read all packets for one loop, at which point the count should be accurate. + boost::promise promise; + promise.set_value(do_call(param)); + return promise.get_future(); + } + + virtual std::wstring print() const override + { + return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|" + + print_mode() + L"|" + + boost::lexical_cast(file_frame_number()) + L"/" + boost::lexical_cast(file_nb_frames()) + L"]"; + } - int64_t nb_frames = input_.nb_frames(); - if(input_.nb_loops() < 1) // input still hasn't counted all frames - { - int64_t video_nb_frames = video_decoder_->nb_frames(); - int64_t audio_nb_frames = audio_decoder_->nb_frames(); + virtual std::wstring name() const override + { + return L"ffmpeg"; + } - nb_frames = std::min(static_cast(length_), std::max(nb_frames, std::max(video_nb_frames, audio_nb_frames))); - } + boost::property_tree::wptree info() const override + { + boost::property_tree::wptree info; + info.add(L"type", L"ffmpeg"); + info.add(L"filename", filename_); + info.add(L"width", video_decoder_ ? video_decoder_->width() : 0); + info.add(L"height", video_decoder_ ? video_decoder_->height() : 0); + info.add(L"progressive", video_decoder_ ? video_decoder_->is_progressive() : false); + info.add(L"fps", fps_); + info.add(L"loop", input_.loop()); + info.add(L"frame-number", frame_number_); + auto nb_frames2 = nb_frames(); + info.add(L"nb-frames", nb_frames2 == std::numeric_limits::max() ? -1 : nb_frames2); + info.add(L"file-frame-number", file_frame_number()); + info.add(L"file-nb-frames", file_nb_frames()); + return info; + } + + virtual void subscribe(const monitor::observable::observer_ptr& o) override + { + event_subject_.subscribe(o); + } - nb_frames = muxer_->calc_nb_frames(nb_frames); + virtual void unsubscribe(const monitor::observable::observer_ptr& o) override + { + event_subject_.unsubscribe(o); + } - // TODO: Might need to scale nb_frames av frame_muxer transformations. + // ffmpeg_producer - return nb_frames - start_; + std::wstring print_mode() const + { + return video_decoder_ ? ffmpeg::print_mode(video_decoder_->width(), video_decoder_->height(), fps_, !video_decoder_->is_progressive()) : L"n/a"; } - - virtual std::wstring print() const + + std::wstring do_call(const std::wstring& param) { - if(video_decoder_) + static const boost::wregex loop_exp(L"LOOP\\s*(?\\d?)?", boost::regex::icase); + static const boost::wregex seek_exp(L"SEEK\\s+(?\\d+)", boost::regex::icase); + + boost::wsmatch what; + if(boost::regex_match(param, what, loop_exp)) { - return L"ffmpeg[" + boost::filesystem::wpath(filename_).filename() + L"|" - + boost::lexical_cast(video_decoder_->width()) + L"x" + boost::lexical_cast(video_decoder_->height()) - + (video_decoder_->is_progressive() ? L"p" : L"i") + boost::lexical_cast(video_decoder_->is_progressive() ? video_decoder_->fps() : 2.0 * video_decoder_->fps()) - + L"]"; + if(!what["VALUE"].str().empty()) + input_.loop(boost::lexical_cast(what["VALUE"].str())); + return boost::lexical_cast(input_.loop()); } + if(boost::regex_match(param, what, seek_exp)) + { + input_.seek(boost::lexical_cast(what["VALUE"].str())); + return L""; + } + + BOOST_THROW_EXCEPTION(invalid_argument()); + } + + std::shared_ptr try_decode_frame(int flags) + { + std::shared_ptr result = muxer_->poll(); + + for(int n = 0; n < 32 && !result; ++n, result = muxer_->poll()) + { + std::shared_ptr pkt; + + for(int n = 0; n < 32 && ((video_decoder_ && !video_decoder_->ready()) || (audio_decoder_ && !audio_decoder_->ready())) && input_.try_pop(pkt); ++n) + { + if(video_decoder_) + video_decoder_->push(pkt); + if(audio_decoder_) + audio_decoder_->push(pkt); + } - return L"ffmpeg[" + boost::filesystem::wpath(filename_).filename() + L"]"; + std::shared_ptr video; + std::shared_ptr audio; + + tbb::parallel_invoke( + [&] + { + if(!muxer_->video_ready() && video_decoder_) + video = video_decoder_->poll(); + }, + [&] + { + if(!muxer_->audio_ready() && audio_decoder_) + audio = audio_decoder_->poll(); + }); + + muxer_->push(video, flags); + muxer_->push(audio); + + if(!audio_decoder_) + { + if(video == flush_video()) + muxer_->push(flush_audio()); + else if(!muxer_->audio_ready()) + muxer_->push(empty_audio()); + } + + if(!video_decoder_) + { + if(audio == flush_audio()) + muxer_->push(flush_video(), 0); + else if(!muxer_->video_ready()) + muxer_->push(empty_video(), 0); + } + } + + return result; } }; -safe_ptr create_producer(const safe_ptr& frame_factory, const std::vector& params) +spl::shared_ptr create_producer(const spl::shared_ptr& frame_factory, const std::vector& params) { - static const std::vector extensions = boost::assign::list_of - (L"mpg")(L"mpeg")(L"m2v")(L"m4v")(L"mp3")(L"mp4")(L"mpga") - (L"avi") - (L"mov") - (L"qt") - (L"webm") - (L"dv") - (L"f4v")(L"flv") - (L"mkv")(L"mka") - (L"wmv")(L"wma")(L"wav") - (L"rm")(L"ram") - (L"ogg")(L"ogv")(L"oga")(L"ogx") - (L"divx")(L"xvid"); - - std::wstring filename = env::media_folder() + L"\\" + params[0]; - - auto ext = boost::find_if(extensions, [&](const std::wstring& ex) - { - return boost::filesystem::is_regular_file(boost::filesystem::wpath(filename + L"." + ex)); - }); + auto filename = probe_stem(env::media_folder() + L"\\" + params.at(0)); - if(ext == extensions.end()) + if(filename.empty()) return core::frame_producer::empty(); - - auto path = filename + L"." + *ext; + auto loop = boost::range::find(params, L"LOOP") != params.end(); - auto start = core::get_param(L"SEEK", params, 0); - auto length = core::get_param(L"LENGTH", params, std::numeric_limits::max()); - auto filter_str = core::get_param(L"FILTER", params, L""); + auto start = get_param(L"SEEK", params, static_cast(0)); + auto length = get_param(L"LENGTH", params, std::numeric_limits::max()); + auto filter_str = get_param(L"FILTER", params, L""); boost::replace_all(filter_str, L"DEINTERLACE", L"YADIF=0:-1"); boost::replace_all(filter_str, L"DEINTERLACE_BOB", L"YADIF=1:-1"); - return make_safe(frame_factory, path, filter_str, loop, start, length); + return core::wrap_producer(spl::make_shared(frame_factory, filename, filter_str, loop, start, length)); } }} \ No newline at end of file