if(output_format_.format->flags & AVFMT_GLOBALHEADER)
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
- THROW_ON_ERROR2(tbb_avcodec_open(c, encoder), "[ffmpeg_consumer]");
+ THROW_ON_ERROR2(tbb_avcodec_open(c, encoder, false), "[ffmpeg_consumer]");
return std::shared_ptr<AVStream>(st, [](AVStream* st)
{
dependencies.consumer_registry->register_preconfigured_consumer_factory(L"file", create_preconfigured_consumer);
dependencies.consumer_registry->register_preconfigured_consumer_factory(L"stream", create_preconfigured_streaming_consumer);
dependencies.producer_registry->register_producer_factory(L"FFmpeg Producer", create_producer, describe_producer);
-
+ dependencies.producer_registry->register_thumbnail_producer_factory(create_thumbnail_producer);
+
dependencies.media_info_repo->register_extractor(
[](const std::wstring& file, const std::wstring& extension, core::media_info& info) -> bool
{
return true;
}
- if (!is_valid_file(file))
+ if (!is_valid_file(file, true))
return false;
info.clip_type = L"MOVIE";
input& input_;
int index_;
const core::video_format_desc format_desc_;
- const spl::shared_ptr<AVCodecContext> codec_context_ = open_codec(input_.context(), AVMEDIA_TYPE_AUDIO, index_);
+ const spl::shared_ptr<AVCodecContext> codec_context_ = open_codec(input_.context(), AVMEDIA_TYPE_AUDIO, index_, false);
std::shared_ptr<SwrContext> swr_ {
swr_alloc_set_opts(
const double fps_ = read_fps(input_.context(), format_desc_.fps);
const uint32_t start_;
+ const bool thumbnail_mode_;
std::unique_ptr<video_decoder> video_decoder_;
std::unique_ptr<audio_decoder> audio_decoder_;
const std::wstring& filter,
bool loop,
uint32_t start,
- uint32_t length)
+ uint32_t length,
+ bool thumbnail_mode)
: filename_(filename)
, frame_factory_(frame_factory)
, format_desc_(format_desc)
- , input_(graph_, filename_, loop, start, length)
+ , input_(graph_, filename_, loop, start, length, thumbnail_mode)
, fps_(read_fps(input_.context(), format_desc_.fps))
, start_(start)
+ , thumbnail_mode_(thumbnail_mode)
{
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));
try
{
- video_decoder_.reset(new video_decoder(input_));
+ video_decoder_.reset(new video_decoder(input_, thumbnail_mode));
video_decoder_->monitor_output().attach_parent(monitor_subject_);
constraints_.width.set(video_decoder_->width());
constraints_.height.set(video_decoder_->height());
auto channel_layout = core::audio_channel_layout::invalid();
- try
+ if (!thumbnail_mode)
{
- audio_decoder_ .reset(new audio_decoder(input_, format_desc_, channel_layout_spec));
- audio_decoder_->monitor_output().attach_parent(monitor_subject_);
+ try
+ {
+ audio_decoder_.reset(new audio_decoder(input_, format_desc_, channel_layout_spec));
+ audio_decoder_->monitor_output().attach_parent(monitor_subject_);
- channel_layout = audio_decoder_->channel_layout();
-
- CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();
- }
- catch(averror_stream_not_found&)
- {
- //CASPAR_LOG(warning) << print() << " No audio-stream found. Running without audio.";
- }
- catch(...)
- {
- CASPAR_LOG_CURRENT_EXCEPTION();
- CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";
+ channel_layout = audio_decoder_->channel_layout();
+
+ CASPAR_LOG(info) << print() << L" " << audio_decoder_->print();
+ }
+ catch (averror_stream_not_found&)
+ {
+ CASPAR_LOG(debug) << print() << " No audio-stream found. Running without audio.";
+ }
+ catch (...)
+ {
+ CASPAR_LOG_CURRENT_EXCEPTION();
+ CASPAR_LOG(warning) << print() << " Failed to open audio-stream. Running without audio.";
+ }
}
if (start_ > file_nb_frames())
return frame;
}
+ core::draw_frame render_specific_frame(uint32_t file_position)
+ {
+ muxer_->clear();
+ input_.seek(file_position);
+
+ decode_next_frame();
+
+ if (muxer_->empty())
+ {
+ CASPAR_LOG(trace) << print() << " Giving up finding frame at " << file_position;
+
+ return core::draw_frame::empty();
+ }
+
+ auto frame = muxer_->front();
+ muxer_->pop();
+
+ return frame;
+ }
+
+ core::draw_frame create_thumbnail_frame() override
+ {
+ auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
+
+ auto total_frames = nb_frames();
+ auto grid = env::properties().get(L"configuration.thumbnails.video-grid", 2);
+
+ 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"));
+ }
+
+ if (grid == 1)
+ {
+ return render_specific_frame(total_frames / 2);
+ }
+
+ auto num_snapshots = grid * grid;
+
+ std::vector<core::draw_frame> frames;
+
+ for (int i = 0; i < num_snapshots; ++i)
+ {
+ int x = i % grid;
+ int y = i / grid;
+ int desired_frame;
+
+ if (i == 0)
+ desired_frame = 0; // first
+ else if (i == num_snapshots - 1)
+ desired_frame = total_frames - 1; // last
+ else
+ // evenly distributed across the file.
+ desired_frame = total_frames * i / (num_snapshots - 1);
+
+ auto frame = render_specific_frame(desired_frame);
+ frame.transform().image_transform.fill_scale[0] = 1.0 / static_cast<double>(grid);
+ frame.transform().image_transform.fill_scale[1] = 1.0 / static_cast<double>(grid);
+ frame.transform().image_transform.fill_translation[0] = 1.0 / static_cast<double>(grid) * x;
+ frame.transform().image_transform.fill_translation[1] = 1.0 / static_cast<double>(grid) * y;
+
+ frames.push_back(frame);
+ }
+
+ return core::draw_frame(frames);
+ }
+
core::draw_frame last_frame() override
{
end_seek();
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
{
- auto filename = probe_stem(env::media_folder() + L"/" + params.at(0));
+ auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), false);
if(filename.empty())
return core::frame_producer::empty();
auto length = get_param(L"LENGTH", params, std::numeric_limits<uint32_t>::max());
auto filter_str = get_param(L"FILTER", params, L"");
auto channel_layout = get_param(L"CHANNEL_LAYOUT", params, L"");
+ bool thumbnail_mode = false;
return create_destroy_proxy(spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
dependencies.frame_factory,
filter_str,
loop,
start,
- length)));
+ length,
+ thumbnail_mode)));
+}
+
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params)
+{
+ auto quiet_logging = temporary_enable_quiet_logging_for_thread(true);
+ auto filename = probe_stem(env::media_folder() + L"/" + params.at(0), true);
+
+ if(filename.empty())
+ return core::frame_producer::empty();
+
+ bool loop = false;
+ auto start = 0;
+ auto length = std::numeric_limits<uint32_t>::max();
+ auto filter_str = L"";
+ auto channel_layout = L"";
+ bool thumbnail_mode = true;
+
+ return spl::make_shared_ptr(std::make_shared<ffmpeg_producer>(
+ dependencies.frame_factory,
+ dependencies.format_desc,
+ channel_layout,
+ filename,
+ filter_str,
+ loop,
+ start,
+ length,
+ thumbnail_mode));
}
}}
void describe_producer(core::help_sink& sink, const core::help_repository& repo);
spl::shared_ptr<core::frame_producer> create_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
+spl::shared_ptr<core::frame_producer> create_thumbnail_producer(const core::frame_producer_dependencies& dependencies, const std::vector<std::wstring>& params);
}}
\ No newline at end of file
tbb::atomic<uint32_t> length_;
tbb::atomic<bool> loop_;
tbb::atomic<bool> eof_;
+ bool thumbnail_mode_;
double fps_ = read_fps(*format_context_, 0.0);
uint32_t frame_number_ = 0;
- stream video_stream_ { av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0) };
- stream audio_stream_ { av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) };
+ stream video_stream_ { av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0) };
+ stream audio_stream_ { thumbnail_mode_ ? -1 : av_find_best_stream(format_context_.get(), AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0) };
boost::optional<uint32_t> seek_target_;
boost::condition_variable cond_;
boost::thread thread_;
- impl(const spl::shared_ptr<diagnostics::graph> graph, const std::wstring& filename, const bool loop, const uint32_t start, const uint32_t length)
+ impl(
+ const spl::shared_ptr<diagnostics::graph> graph,
+ const std::wstring& filename,
+ const bool loop,
+ const uint32_t start,
+ const uint32_t length,
+ bool thumbnail_mode)
: graph_(graph)
, filename_(filename)
- {
+ , thumbnail_mode_(thumbnail_mode)
+ {
start_ = start;
length_ = length;
loop_ = loop;
for(int n = 0; n < 8; ++n)
tick();
- thread_ = boost::thread([this]{run();});
+ if (!thumbnail_mode)
+ thread_ = boost::thread([this]{run();});
}
~impl()
{
is_running_ = false;
cond_.notify_one();
- thread_.join();
+
+ if (!thumbnail_mode_)
+ thread_.join();
}
bool try_pop_video(std::shared_ptr<AVPacket>& packet)
if (!video_stream_.is_available())
return false;
+ if (thumbnail_mode_)
+ {
+ int ticks = 0;
+ while (!video_stream_.try_pop(packet))
+ {
+ tick();
+ if (++ticks > 32) // Infinite loop should not be possible
+ return false;
+
+ // Play nice
+ boost::this_thread::sleep(boost::posix_time::milliseconds(5));
+ }
+
+ return true;
+ }
+
bool result = video_stream_.try_pop(packet);
+
if(result)
cond_.notify_one();
video_stream_.clear();
audio_stream_.clear();
}
-
+
cond_.notify_one();
}
}
}
- auto stream = format_context_->streams[default_stream_index_];
- auto fps = read_fps(*format_context_, 0.0);
+ auto stream = format_context_->streams[default_stream_index_];
+ auto fps = read_fps(*format_context_, 0.0);
+ auto target_timestamp = static_cast<int64_t>((target / fps * stream->time_base.den) / stream->time_base.num);
THROW_ON_ERROR2(avformat_seek_file(
format_context_.get(),
default_stream_index_,
std::numeric_limits<int64_t>::min(),
- static_cast<int64_t>((target / fps * stream->time_base.den) / stream->time_base.num),
+ target_timestamp,
std::numeric_limits<int64_t>::max(),
0), print());
}
};
-input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length)
- : impl_(new impl(graph, filename, loop, start, length)){}
+input::input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length, bool thumbnail_mode)
+ : impl_(new impl(graph, filename, loop, start, length, thumbnail_mode)){}
bool input::try_pop_video(std::shared_ptr<AVPacket>& packet){return impl_->try_pop_video(packet);}
bool input::try_pop_audio(std::shared_ptr<AVPacket>& packet){return impl_->try_pop_audio(packet);}
AVFormatContext& input::context(){return *impl_->format_context_;}
class input : boost::noncopyable
{
public:
- explicit input(const spl::shared_ptr<diagnostics::graph>& graph, const std::wstring& filename, bool loop, uint32_t start, uint32_t length);
+ explicit input(
+ const spl::shared_ptr<diagnostics::graph>& graph,
+ const std::wstring& filename,
+ bool loop, uint32_t start,
+ uint32_t length,
+ bool thumbnail_mode);
bool try_pop_video(std::shared_ptr<AVPacket>& packet);
bool try_pop_audio(std::shared_ptr<AVPacket>& packet);
s->thread_opaque = nullptr;
}
-int tbb_avcodec_open(AVCodecContext* avctx, AVCodec* codec)
+int tbb_avcodec_open(AVCodecContext* avctx, AVCodec* codec, bool single_threaded)
{
if(codec->capabilities & CODEC_CAP_EXPERIMENTAL)
CASPAR_THROW_EXCEPTION(invalid_argument() << msg_info("Experimental codecs are not supported."));
avctx->thread_count = 1;
- if(codec->capabilities & CODEC_CAP_SLICE_THREADS)
+ if(!single_threaded && codec->capabilities & CODEC_CAP_SLICE_THREADS)
thread_init(avctx);
// ff_thread_init will not be executed since thread_opaque != nullptr || thread_count == 1.
namespace caspar {
-int tbb_avcodec_open(AVCodecContext *avctx, AVCodec *codec);
+int tbb_avcodec_open(AVCodecContext *avctx, AVCodec *codec, bool single_threaded);
int tbb_avcodec_close(AVCodecContext *avctx);
}
\ No newline at end of file
return frame;
}
-spl::shared_ptr<AVCodecContext> open_codec(AVFormatContext& context, enum AVMediaType type, int& index)
+spl::shared_ptr<AVCodecContext> open_codec(AVFormatContext& context, enum AVMediaType type, int& index, bool single_threaded)
{
AVCodec* decoder;
index = THROW_ON_ERROR2(av_find_best_stream(&context, type, -1, -1, &decoder, 0), "");
//if(strcmp(decoder->name, "prores") == 0 && decoder->next && strcmp(decoder->next->name, "prores_lgpl") == 0)
// decoder = decoder->next;
- THROW_ON_ERROR2(tbb_avcodec_open(context.streams[index]->codec, decoder), "");
+ THROW_ON_ERROR2(tbb_avcodec_open(context.streams[index]->codec, decoder, single_threaded), "");
return spl::shared_ptr<AVCodecContext>(context.streams[index]->codec, tbb_avcodec_close);
}
return boost::lexical_cast<std::wstring>(width) + L"x" + boost::lexical_cast<std::wstring>(height) + (!interlaced ? L"p" : L"i") + fps_ss.str();
}
-bool is_valid_file(const std::wstring& filename)
+bool is_valid_file(const std::wstring& filename, bool only_video)
{
static const auto invalid_exts = {
L".png",
L".swf",
L".ct"
};
+ static const auto only_audio = {
+ L".mp3",
+ L".wav",
+ L".wma"
+ };
static const auto valid_exts = {
L".m2t",
L".mov",
L".dv",
L".flv",
L".mpg",
- L".wav",
- L".mp3",
L".dnxhd",
L".h264",
L".prores"
auto ext = boost::to_lower_copy(boost::filesystem::path(filename).extension().wstring());
if(std::find(valid_exts.begin(), valid_exts.end(), ext) != valid_exts.end())
- return true;
+ return true;
+
+ if (!only_video && std::find(only_audio.begin(), only_audio.end(), ext) != only_audio.end())
+ return true;
if(std::find(invalid_exts.begin(), invalid_exts.end(), ext) != invalid_exts.end())
return false;
+ if (only_video && std::find(only_audio.begin(), only_audio.end(), ext) != only_audio.end())
+ return false;
+
auto u8filename = u8(filename);
int score = 0;
return true;
}
-std::wstring probe_stem(const std::wstring& stem)
+std::wstring probe_stem(const std::wstring& stem, bool only_video)
{
auto stem2 = boost::filesystem::path(stem);
auto parent = find_case_insensitive(stem2.parent_path().wstring());
for(auto it = boost::filesystem::directory_iterator(dir); it != boost::filesystem::directory_iterator(); ++it)
{
- if(boost::iequals(it->path().stem().wstring(), stem2.filename().wstring()) && is_valid_file(it->path().wstring()))
+ if(boost::iequals(it->path().stem().wstring(), stem2.filename().wstring()) && is_valid_file(it->path().wstring(), only_video))
return it->path().wstring();
}
return L"";
core::field_mode get_mode(const AVFrame& frame);
core::mutable_frame make_frame(const void* tag, const spl::shared_ptr<AVFrame>& decoded_frame, double fps, core::frame_factory& frame_factory, const core::audio_channel_layout& channel_layout);
spl::shared_ptr<AVFrame> make_av_frame(core::mutable_frame& frame);
-spl::shared_ptr<AVFrame> make_av_frame(core::const_frame& frame);
spl::shared_ptr<AVFrame> make_av_frame(std::array<uint8_t*, 4> data, const core::pixel_format_desc& pix_desc);
core::pixel_format_desc pixel_format_desc(PixelFormat pix_fmt, int width, int height);
spl::shared_ptr<AVPacket> create_packet();
spl::shared_ptr<AVFrame> create_frame();
-spl::shared_ptr<AVCodecContext> open_codec(AVFormatContext& context, AVMediaType type, int& index);
+spl::shared_ptr<AVCodecContext> open_codec(AVFormatContext& context, AVMediaType type, int& index, bool single_threaded);
spl::shared_ptr<AVFormatContext> open_input(const std::wstring& filename);
bool is_sane_fps(AVRational time_base);
std::wstring print_mode(int width, int height, double fps, bool interlaced);
-std::wstring probe_stem(const std::wstring& stem);
-bool is_valid_file(const std::wstring& filename);
+std::wstring probe_stem(const std::wstring& stem, bool only_video);
+bool is_valid_file(const std::wstring& filename, bool only_video);
bool try_get_duration(const std::wstring filename, std::int64_t& duration, boost::rational<std::int64_t>& time_base);
core::audio_channel_layout get_audio_channel_layout(const AVCodecContext& codec_context, const std::wstring& channel_layout_spec);
std::shared_ptr<AVPacket> current_packet_;
public:
- explicit impl(input& in)
+ explicit impl(input& in, bool single_threaded)
: input_(&in)
- , codec_context_(open_codec(input_->context(), AVMEDIA_TYPE_VIDEO, index_))
+ , codec_context_(open_codec(input_->context(), AVMEDIA_TYPE_VIDEO, index_, single_threaded))
, stream_(input_->context().streams[index_])
, nb_frames_(static_cast<uint32_t>(stream_->nb_frames))
, width_(codec_context_->width)
}
};
-video_decoder::video_decoder(input& in) : impl_(new impl(in)){}
+video_decoder::video_decoder(input& in, bool single_threaded) : impl_(new impl(in, single_threaded)){}
video_decoder::video_decoder(video_decoder&& other) : impl_(std::move(other.impl_)){}
video_decoder& video_decoder::operator=(video_decoder&& other){impl_ = std::move(other.impl_); return *this;}
std::shared_ptr<AVFrame> video_decoder::operator()(){return impl_->poll();}
class video_decoder : public boost::noncopyable
{
public:
- explicit video_decoder(class input& input);
+ explicit video_decoder(class input& input, bool single_threaded);
video_decoder(video_decoder&& other);
video_decoder& operator=(video_decoder&& other);