From: Helge Norberg Date: Mon, 3 Oct 2016 14:43:54 +0000 (+0200) Subject: [ffmpeg] Removed FFMPEG_Resource to simplify code and documented streaming video... X-Git-Tag: 2.1.0_Beta1~47 X-Git-Url: https://git.sesse.net/?p=casparcg;a=commitdiff_plain;h=a111f54e43d6b3974036b6e7812b39558409910f [ffmpeg] Removed FFMPEG_Resource to simplify code and documented streaming video playback and dshow playback. --- diff --git a/modules/ffmpeg/producer/audio/audio_decoder.cpp b/modules/ffmpeg/producer/audio/audio_decoder.cpp index cd5b37792..6ed608a88 100644 --- a/modules/ffmpeg/producer/audio/audio_decoder.cpp +++ b/modules/ffmpeg/producer/audio/audio_decoder.cpp @@ -37,7 +37,7 @@ #pragma warning (push) #pragma warning (disable : 4244) #endif -extern "C" +extern "C" { #include #include @@ -48,13 +48,13 @@ extern "C" #endif namespace caspar { namespace ffmpeg { - + struct audio_decoder::implementation : boost::noncopyable -{ +{ int index_ = -1; const spl::shared_ptr codec_context_; const int out_samplerate_; - + cache_aligned_vector buffer_; std::queue> packets_; @@ -85,29 +85,29 @@ public: : codec_context_(open_codec(*context, AVMEDIA_TYPE_AUDIO, index_, false)) , out_samplerate_(out_samplerate) , buffer_(10 * out_samplerate_ * codec_context_->channels) // 10 seconds of audio - { + { if(!swr_) - BOOST_THROW_EXCEPTION(bad_alloc()); - + CASPAR_THROW_EXCEPTION(bad_alloc()); + THROW_ON_ERROR2(swr_init(swr_.get()), "[audio_decoder]"); codec_context_->refcounted_frames = 1; } void push(const std::shared_ptr& packet) - { + { if(!packet) return; if(packet->stream_index == index_ || packet->data == nullptr) packets_.push(spl::make_shared_ptr(packet)); - } - + } + std::shared_ptr poll() { if(packets_.empty()) return nullptr; - + auto packet = packets_.front(); if(packet->data == nullptr) @@ -119,7 +119,7 @@ public: auto audio = decode(*packet); - if(packet->size == 0) + if(packet->size == 0) packets_.pop(); return audio; @@ -165,7 +165,7 @@ public: } std::wstring print() const - { + { return L"[audio-decoder] " + u16(codec_context_->codec->long_name); } }; diff --git a/modules/ffmpeg/producer/ffmpeg_producer.cpp b/modules/ffmpeg/producer/ffmpeg_producer.cpp index bffae75ed..5f2b995eb 100644 --- a/modules/ffmpeg/producer/ffmpeg_producer.cpp +++ b/modules/ffmpeg/producer/ffmpeg_producer.cpp @@ -79,8 +79,6 @@ struct ffmpeg_producer : public core::frame_producer_base const std::wstring filename_; const std::wstring path_relative_to_media_ = get_relative_or_original(filename_, env::media_folder()); - FFMPEG_Resource resource_type_; - const spl::shared_ptr graph_; timer frame_timer_; @@ -110,8 +108,7 @@ public: explicit ffmpeg_producer( const spl::shared_ptr& frame_factory, const core::video_format_desc& format_desc, - const std::wstring& filename, - FFMPEG_Resource resource_type, + const std::wstring& url_or_file, const std::wstring& filter, bool loop, uint32_t start, @@ -119,11 +116,10 @@ public: bool thumbnail_mode, const std::wstring& custom_channel_order, const ffmpeg_options& vid_params) - : filename_(filename) - , resource_type_(resource_type) + : filename_(url_or_file) , frame_factory_(frame_factory) , initial_logger_disabler_(temporary_enable_quiet_logging_for_thread(thumbnail_mode)) - , input_(graph_, filename_, resource_type, loop, start, length, thumbnail_mode, vid_params) + , input_(graph_, url_or_file, loop, start, length, thumbnail_mode, vid_params) , framerate_(read_framerate(*input_.context(), format_desc.framerate)) , start_(start) , length_(length) @@ -229,16 +225,16 @@ public: send_osc(); return std::make_pair(last_frame(), -1); } - else if (resource_type_ == FFMPEG_Resource::FFMPEG_FILE) + else if (!is_url()) { graph_->set_tag(diagnostics::tag_severity::WARNING, "underflow"); send_osc(); - return std::make_pair(core::draw_frame::late(), -1); + return std::make_pair(last_frame_, -1); } else { send_osc(); - return std::make_pair(last_frame(), -1); + return std::make_pair(last_frame_, -1); } } @@ -257,6 +253,11 @@ public: return frame; } + bool is_url() const + { + return boost::contains(filename_, L"://"); + } + void send_osc() { double fps = static_cast(framerate_.numerator()) / static_cast(framerate_.denominator()); @@ -325,7 +326,7 @@ public: if (grid < 1) { CASPAR_LOG(error) << L"configuration/thumbnails/video-grid cannot be less than 1"; - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1")); + CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info("configuration/thumbnails/video-grid cannot be less than 1")); } if (grid == 1) @@ -370,7 +371,7 @@ public: uint32_t nb_frames() const override { - if (resource_type_ == FFMPEG_Resource::FFMPEG_DEVICE || resource_type_ == FFMPEG_Resource::FFMPEG_STREAM || input_.loop()) + if (is_url() || input_.loop()) return std::numeric_limits::max(); uint32_t nb_frames = file_nb_frames(); @@ -434,7 +435,7 @@ public: std::wstring print() const override { - return L"ffmpeg[" + boost::filesystem::path(filename_).filename().wstring() + L"|" + return L"ffmpeg[" + (is_url() ? filename_ : boost::filesystem::path(filename_).filename().wstring()) + L"|" + print_mode() + L"|" + boost::lexical_cast(file_frame_number_) + L"/" + boost::lexical_cast(file_nb_frames()) + L"]"; } @@ -546,13 +547,14 @@ public: void describe_producer(core::help_sink& sink, const core::help_repository& repo) { sink.short_description(L"A producer for playing media files supported by FFmpeg."); - sink.syntax(L"[clip:string] {[loop:LOOP]} {SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}"); + sink.syntax(L"[clip,url:string] {[loop:LOOP]} {SEEK [start:int]} {LENGTH [start:int]} {FILTER [filter:string]} {CHANNEL_LAYOUT [channel_layout:string]}"); sink.para() ->text(L"The FFmpeg Producer can play all media that FFmpeg can play, which includes many ") ->text(L"QuickTime video codec such as Animation, PNG, PhotoJPEG, MotionJPEG, as well as ") ->text(L"H.264, FLV, WMV and several audio codecs as well as uncompressed audio."); sink.definitions() ->item(L"clip", L"The file without the file extension to play. It should reside under the media folder.") + ->item(L"url", L"If clip contains :// it is instead treated as the URL parameter. The URL can either be any streaming protocol supported by FFmpeg or dshow://video={webcam_name}.") ->item(L"loop", L"Will cause the media file to loop between start and start + length") ->item(L"start", L"Optionally sets the start frame. 0 by default. If loop is specified this will be the frame where it starts over again.") ->item(L"length", L"Optionally sets the length of the clip. If not specified the clip will be played to the end. If loop is specified the file will jump to start position once this number of frames has been played.") @@ -569,6 +571,8 @@ void describe_producer(core::help_sink& sink, const core::help_repository& repo) sink.example(L">> PLAY 1-10 folder/clip FILTER yadif=1,-1", L"to deinterlace the video."); sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT film", L"given the defaults in casparcg.config this will specifies that the clip has 6 audio channels of the type 5.1 and that they are in the order FL FC FR BL BR LFE regardless of what ffmpeg says."); sink.example(L">> PLAY 1-10 folder/clip CHANNEL_LAYOUT \"5.1:LFE FL FC FR BL BR\"", L"specifies that the clip has 6 audio channels of the type 5.1 and that they are in the specified order regardless of what ffmpeg says."); + sink.example(L">> PLAY 1-10 rtmp://example.com/live/stream", L"to play an RTMP stream."); + sink.example(L">> PLAY 1-10 \"dshow://video=Live! Cam Chat HD VF0790\"", L"to use a web camera as video input."); sink.para()->text(L"The FFmpeg producer also supports changing some of the settings via ")->code(L"CALL")->text(L":"); sink.example(L">> CALL 1-10 LOOP 1"); sink.example(L">> CALL 1-10 START 10"); @@ -582,34 +586,15 @@ spl::shared_ptr create_producer( const std::vector& params, const spl::shared_ptr& info_repo) { - // Infer the resource type from the resource_name - auto resource_type = FFMPEG_Resource::FFMPEG_FILE; - auto tokens = protocol_split(params.at(0)); - auto filename = params.at(0); + auto file_or_url = params.at(0); - if (!tokens[0].empty()) - { - if (tokens[0] == L"dshow") - { - // Camera - resource_type = FFMPEG_Resource::FFMPEG_DEVICE; - filename = tokens[1]; - } - else - { - // Stream - resource_type = FFMPEG_Resource::FFMPEG_STREAM; - filename = params.at(0); - } - } - else + if (!boost::contains(file_or_url, L"://")) { // File - resource_type = FFMPEG_Resource::FFMPEG_FILE; - filename = probe_stem(env::media_folder() + L"/" + params.at(0), false); + file_or_url = probe_stem(env::media_folder() + L"/" + file_or_url, false); } - if (filename.empty()) + if (file_or_url.empty()) return core::frame_producer::empty(); auto loop = contains_param(L"LOOP", params); @@ -642,8 +627,7 @@ spl::shared_ptr create_producer( auto producer = spl::make_shared( dependencies.frame_factory, dependencies.format_desc, - filename, - resource_type, + file_or_url, filter_str, loop, start, @@ -687,7 +671,6 @@ core::draw_frame create_thumbnail_frame( dependencies.frame_factory, dependencies.format_desc, filename, - FFMPEG_Resource::FFMPEG_FILE, filter_str, loop, start, diff --git a/modules/ffmpeg/producer/input/input.cpp b/modules/ffmpeg/producer/input/input.cpp index 1e081a17a..9103c7b26 100644 --- a/modules/ffmpeg/producer/input/input.cpp +++ b/modules/ffmpeg/producer/input/input.cpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -85,10 +87,10 @@ struct input::implementation : boost::noncopyable executor executor_; - explicit implementation(const spl::shared_ptr graph, const std::wstring& filename, FFMPEG_Resource resource_type, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params) + explicit implementation(const spl::shared_ptr graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params) : graph_(graph) - , format_context_(open_input(filename, resource_type, vid_params)) - , filename_(filename) + , format_context_(open_input(url_or_file, vid_params)) + , filename_(url_or_file) , thumbnail_mode_(thumbnail_mode) , executor_(print()) { @@ -235,69 +237,55 @@ struct input::implementation : boost::noncopyable }); } - spl::shared_ptr open_input(const std::wstring resource_name, FFMPEG_Resource resource_type, const ffmpeg_options& vid_params) + spl::shared_ptr open_input(const std::wstring& url_or_file, const ffmpeg_options& vid_params) { - AVFormatContext* weak_context = nullptr; + AVDictionary* format_options = nullptr; - switch (resource_type) { - case FFMPEG_Resource::FFMPEG_FILE: - THROW_ON_ERROR2(avformat_open_input(&weak_context, u8(resource_name).c_str(), nullptr, nullptr), resource_name); - break; - case FFMPEG_Resource::FFMPEG_DEVICE: - { - AVDictionary* format_options = NULL; - for (auto& option : vid_params) - { - av_dict_set(&format_options, option.first.c_str(), option.second.c_str(), 0); - } - AVInputFormat* input_format = av_find_input_format("dshow"); - THROW_ON_ERROR2(avformat_open_input(&weak_context, u8(resource_name).c_str(), input_format, &format_options), resource_name); - if (format_options != nullptr) - { - std::string unsupported_tokens = ""; - AVDictionaryEntry *t = NULL; - while ((t = av_dict_get(format_options, "", t, AV_DICT_IGNORE_SUFFIX)) != nullptr) - { - if (!unsupported_tokens.empty()) - unsupported_tokens += ", "; - unsupported_tokens += t->key; - } - avformat_close_input(&weak_context); - BOOST_THROW_EXCEPTION(ffmpeg_error() << msg_info(unsupported_tokens)); - } - av_dict_free(&format_options); - } - break; - case FFMPEG_Resource::FFMPEG_STREAM: - { - AVDictionary* format_options = NULL; - for (auto& option : vid_params) - { - av_dict_set(&format_options, option.first.c_str(), option.second.c_str(), 0); - } - THROW_ON_ERROR2(avformat_open_input(&weak_context, u8(resource_name).c_str(), nullptr, &format_options), resource_name); - if (format_options != nullptr) - { - std::string unsupported_tokens = ""; - AVDictionaryEntry *t = NULL; - while ((t = av_dict_get(format_options, "", t, AV_DICT_IGNORE_SUFFIX)) != nullptr) - { - if (!unsupported_tokens.empty()) - unsupported_tokens += ", "; - unsupported_tokens += t->key; - } - avformat_close_input(&weak_context); - BOOST_THROW_EXCEPTION(ffmpeg_error() << msg_info(unsupported_tokens)); - } + CASPAR_SCOPE_EXIT + { + if (format_options) av_dict_free(&format_options); - } - break; }; - spl::shared_ptr context(weak_context, [](AVFormatContext* p) + + for (auto& option : vid_params) + av_dict_set(&format_options, option.first.c_str(), option.second.c_str(), 0); + + auto resource_name = std::wstring(); + auto parts = caspar::protocol_split(url_or_file); + AVInputFormat* input_format = nullptr; + + if (parts.at(0).empty()) + resource_name = parts.at(1); + else if (parts.at(0) == L"dshow") + { + input_format = av_find_input_format("dshow"); + resource_name = parts.at(1); + } + else + resource_name = parts.at(0) + L"://" + parts.at(1); + + AVFormatContext* weak_context = nullptr; + THROW_ON_ERROR2(avformat_open_input(&weak_context, u8(resource_name).c_str(), input_format, &format_options), resource_name); + + spl::shared_ptr context(weak_context, [](AVFormatContext* ptr) { - avformat_close_input(&p); + avformat_close_input(&ptr); }); - THROW_ON_ERROR2(avformat_find_stream_info(weak_context, nullptr), resource_name); + + if (format_options) + { + std::string unsupported_tokens = ""; + AVDictionaryEntry *t = NULL; + while ((t = av_dict_get(format_options, "", t, AV_DICT_IGNORE_SUFFIX)) != nullptr) + { + if (!unsupported_tokens.empty()) + unsupported_tokens += ", "; + unsupported_tokens += t->key; + } + CASPAR_THROW_EXCEPTION(user_error() << msg_info(unsupported_tokens)); + } + + THROW_ON_ERROR2(avformat_find_stream_info(context.get(), nullptr), resource_name); fix_meta_data(*context); return context; } @@ -394,8 +382,8 @@ struct input::implementation : boost::noncopyable } }; -input::input(const spl::shared_ptr& graph, const std::wstring& filename, FFMPEG_Resource resource_type, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params) - : impl_(new implementation(graph, filename, resource_type, loop, start, length, thumbnail_mode, vid_params)){} +input::input(const spl::shared_ptr& graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params) + : impl_(new implementation(graph, url_or_file, loop, start, length, thumbnail_mode, vid_params)){} bool input::eof() const {return !impl_->executor_.is_running();} bool input::try_pop(std::shared_ptr& packet){return impl_->try_pop(packet);} spl::shared_ptr input::context(){return impl_->format_context_;} diff --git a/modules/ffmpeg/producer/input/input.h b/modules/ffmpeg/producer/input/input.h index 28dd7f480..eb4dc903a 100644 --- a/modules/ffmpeg/producer/input/input.h +++ b/modules/ffmpeg/producer/input/input.h @@ -43,13 +43,13 @@ namespace diagnostics { class graph; } - + namespace ffmpeg { class input : boost::noncopyable { public: - explicit input(const spl::shared_ptr& graph, const std::wstring& filename, FFMPEG_Resource resource_type, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params); + explicit input(const spl::shared_ptr& graph, const std::wstring& url_or_file, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode, const ffmpeg_options& vid_params); bool try_pop(std::shared_ptr& packet); bool eof() const; @@ -72,5 +72,5 @@ private: std::shared_ptr impl_; }; - + }} diff --git a/modules/ffmpeg/producer/muxer/frame_muxer.cpp b/modules/ffmpeg/producer/muxer/frame_muxer.cpp index bf4850ddc..1b6cedf90 100644 --- a/modules/ffmpeg/producer/muxer/frame_muxer.cpp +++ b/modules/ffmpeg/producer/muxer/frame_muxer.cpp @@ -206,7 +206,7 @@ struct frame_muxer::impl : boost::noncopyable } if (audio_streams_.back().size() > 32 * audio_cadence_.front() * audio_channel_layout_.num_channels) - BOOST_THROW_EXCEPTION(invalid_operation() << source_info("frame_muxer") << msg_info("audio-stream overflow. This can be caused by incorrect frame-rate. Check clip meta-data.")); + CASPAR_THROW_EXCEPTION(invalid_operation() << source_info("frame_muxer") << msg_info("audio-stream overflow. This can be caused by incorrect frame-rate. Check clip meta-data.")); } bool video_ready() const diff --git a/modules/ffmpeg/producer/muxer/frame_muxer.h b/modules/ffmpeg/producer/muxer/frame_muxer.h index 651cbb441..97984427a 100644 --- a/modules/ffmpeg/producer/muxer/frame_muxer.h +++ b/modules/ffmpeg/producer/muxer/frame_muxer.h @@ -49,10 +49,10 @@ public: const core::audio_channel_layout& channel_layout, const std::wstring& filter, bool multithreaded_filter); - + void push(const std::shared_ptr& video_frame); void push(const std::shared_ptr& audio_samples); - + bool video_ready() const; bool audio_ready() const; @@ -66,4 +66,4 @@ private: spl::shared_ptr impl_; }; -}} \ No newline at end of file +}} diff --git a/modules/ffmpeg/producer/util/util.h b/modules/ffmpeg/producer/util/util.h index be76ca5dc..1d2c647db 100644 --- a/modules/ffmpeg/producer/util/util.h +++ b/modules/ffmpeg/producer/util/util.h @@ -56,12 +56,6 @@ struct AVCodecContext; namespace caspar { namespace ffmpeg { -enum class FFMPEG_Resource { - FFMPEG_FILE, - FFMPEG_DEVICE, - FFMPEG_STREAM -}; - typedef std::vector> ffmpeg_options; // Utils diff --git a/modules/ffmpeg/producer/video/video_decoder.cpp b/modules/ffmpeg/producer/video/video_decoder.cpp index 5bc546078..c8a1fc860 100644 --- a/modules/ffmpeg/producer/video/video_decoder.cpp +++ b/modules/ffmpeg/producer/video/video_decoder.cpp @@ -103,7 +103,7 @@ public: packets_.pop(); file_frame_number_ = static_cast(packet->pos); avcodec_flush_buffers(codec_context_.get()); - return flush_video(); + return flush_video(); } packets_.pop();