X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fffmpeg%2Fconsumer%2Fffmpeg_consumer.cpp;h=bb89cea857c1884b046d937a31f78f2e38fa9fb8;hb=9e4b08cde6c6de9e83a3fff42d90affc3cd8e5bc;hp=6f8505a55a895663001a58bb737d368f5f34acb3;hpb=0a97673545d18aff7eb7230b95b0cbe1bd9d8fe0;p=casparcg diff --git a/modules/ffmpeg/consumer/ffmpeg_consumer.cpp b/modules/ffmpeg/consumer/ffmpeg_consumer.cpp index 6f8505a55..20a30cd83 100644 --- a/modules/ffmpeg/consumer/ffmpeg_consumer.cpp +++ b/modules/ffmpeg/consumer/ffmpeg_consumer.cpp @@ -1,387 +1,940 @@ -/* -* copyright (c) 2010 Sveriges Television AB -* -* This ffmpeg is part of CasparCG. -* -* 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 . -* -*/ - -#include "../StdAfx.h" - -#include "../ffmpeg_error.h" -#include "../producer/tbb_avcodec.h" - -#include "ffmpeg_consumer.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#if defined(_MSC_VER) -#pragma warning (push) -#pragma warning (disable : 4244) -#endif -extern "C" -{ - #define __STDC_CONSTANT_MACROS - #define __STDC_LIMIT_MACROS - #include - #include -} -#if defined(_MSC_VER) -#pragma warning (pop) -#endif - -namespace caspar { namespace ffmpeg { - -struct ffmpeg_consumer : boost::noncopyable -{ - const std::string filename_; - - const std::shared_ptr oc_; - const core::video_format_desc format_desc_; - - executor executor_; - - // Audio - std::shared_ptr audio_st_; - std::vector audio_outbuf_; - - std::vector audio_input_buffer_; - - // Video - std::shared_ptr video_st_; - std::vector video_outbuf_; - - std::vector picture_buf_; - std::shared_ptr img_convert_ctx_; - -public: - ffmpeg_consumer(const std::string& filename, const core::video_format_desc& format_desc, const std::string& codec, int bitrate) - : filename_(filename + ".mov") - , video_outbuf_(1920*1080*8) - , audio_outbuf_(48000) - , oc_(avformat_alloc_context(), av_free) - , format_desc_(format_desc) - , executor_(print()) - { - executor_.set_capacity(25); - - oc_->oformat = av_guess_format(nullptr, filename_.c_str(), nullptr); - if (!oc_->oformat) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Could not find suitable output format.")); - - THROW_ON_ERROR2(av_set_parameters(oc_.get(), nullptr), "[ffmpeg_consumer]"); - - strcpy_s(oc_->filename, filename_.c_str()); - - auto video_codec = avcodec_find_encoder_by_name(codec.c_str()); - if(video_codec == nullptr) - BOOST_THROW_EXCEPTION(invalid_argument() << arg_name_info(codec)); - - // Add the audio and video streams using the default format codecs and initialize the codecs . - video_st_ = add_video_stream(video_codec->id, bitrate); - audio_st_ = add_audio_stream(); - - dump_format(oc_.get(), 0, filename_.c_str(), 1); - - // Open the output ffmpeg, if needed. - if (!(oc_->oformat->flags & AVFMT_NOFILE)) - THROW_ON_ERROR2(avio_open(&oc_->pb, filename_.c_str(), URL_WRONLY), "[ffmpeg_consumer]"); - - THROW_ON_ERROR2(av_write_header(oc_.get()), "[ffmpeg_consumer]"); - - CASPAR_LOG(info) << print() << L" Successfully Initialized."; - } - - ~ffmpeg_consumer() - { - executor_.stop(); - executor_.join(); - - try - { - THROW_ON_ERROR2(av_write_trailer(oc_.get()), "[ffmpeg_consumer]"); - - audio_st_.reset(); - video_st_.reset(); - - for(size_t i = 0; i < oc_->nb_streams; i++) - { - av_freep(&oc_->streams[i]->codec); - av_freep(&oc_->streams[i]); - } - - if (!(oc_->oformat->flags & AVFMT_NOFILE)) - THROW_ON_ERROR2(avio_close(oc_->pb), "[ffmpeg_consumer]"); // Close the output ffmpeg. - - CASPAR_LOG(info) << print() << L" Successfully Uninitialized."; - } - catch(...) - { - CASPAR_LOG_CURRENT_EXCEPTION(); - } - - } - - std::wstring print() const - { - return L"ffmpeg[" + widen(filename_) + L"]"; - } - - std::shared_ptr add_video_stream(enum CodecID codec_id, int bitrate) - { - auto st = av_new_stream(oc_.get(), 0); - if (!st) - { - BOOST_THROW_EXCEPTION(caspar_exception() - << msg_info("Could not alloc video-stream") - << boost::errinfo_api_function("av_new_stream")); - } - - st->codec->codec_id = codec_id; - st->codec->codec_type = AVMEDIA_TYPE_VIDEO; - st->codec->width = format_desc_.width; - st->codec->height = format_desc_.height; - st->codec->time_base.den = format_desc_.time_scale; - st->codec->time_base.num = format_desc_.duration; - - if(st->codec->codec_id == CODEC_ID_PRORES) - { - st->codec->bit_rate = bitrate > 0 ? bitrate : format_desc_.width < 1280 ? 42*1000000 : 147*1000000; - st->codec->pix_fmt = PIX_FMT_YUV422P10; - } - else if(st->codec->codec_id == CODEC_ID_DNXHD) - { - st->codec->bit_rate = bitrate > 0 ? bitrate : 145*1000000; - st->codec->width = std::min(1280, format_desc_.width); - st->codec->height = std::min(720, format_desc_.height); - st->codec->pix_fmt = PIX_FMT_YUV422P; - } - else - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("unsupported codec")); - - if(oc_->oformat->flags & AVFMT_GLOBALHEADER) - st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; - - auto codec = avcodec_find_encoder(st->codec->codec_id); - if (!codec) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found")); - - THROW_ON_ERROR2(tbb_avcodec_open(st->codec, codec), "[ffmpeg_consumer]"); - - return std::shared_ptr(st, [](AVStream* st) - { - tbb_avcodec_close(st->codec); - }); - } - - std::shared_ptr add_audio_stream() - { - auto st = av_new_stream(oc_.get(), 1); - if (!st) - { - BOOST_THROW_EXCEPTION(caspar_exception() - << msg_info("Could not alloc audio-stream") - << boost::errinfo_api_function("av_new_stream")); - } - - st->codec->codec_id = CODEC_ID_PCM_S16LE; - st->codec->codec_type = AVMEDIA_TYPE_AUDIO; - st->codec->sample_rate = 48000; - st->codec->channels = 2; - st->codec->sample_fmt = SAMPLE_FMT_S16; - - if(oc_->oformat->flags & AVFMT_GLOBALHEADER) - st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; - - auto codec = avcodec_find_encoder(st->codec->codec_id); - if (!codec) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("codec not found")); - - THROW_ON_ERROR2(avcodec_open(st->codec, codec), "[ffmpeg_consumer]"); - - return std::shared_ptr(st, [](AVStream* st) - { - avcodec_close(st->codec); - }); - } - - void encode_video_frame(const safe_ptr& frame) - { - auto c = video_st_->codec; - - if(!img_convert_ctx_) - { - img_convert_ctx_.reset(sws_getContext(format_desc_.width, format_desc_.height, PIX_FMT_BGRA, c->width, c->height, c->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr), sws_freeContext); - if (img_convert_ctx_ == nullptr) - BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Cannot initialize the conversion context")); - } - - std::shared_ptr av_frame(avcodec_alloc_frame(), av_free); - avpicture_fill(reinterpret_cast(av_frame.get()), const_cast(frame->image_data().begin()), PIX_FMT_BGRA, format_desc_.width, format_desc_.height); - - std::shared_ptr local_av_frame(avcodec_alloc_frame(), av_free); - local_av_frame->interlaced_frame = format_desc_.field_mode != core::field_mode::progressive; - local_av_frame->top_field_first = format_desc_.field_mode == core::field_mode::upper; - - picture_buf_.resize(avpicture_get_size(c->pix_fmt, format_desc_.width, format_desc_.height)); - avpicture_fill(reinterpret_cast(local_av_frame.get()), picture_buf_.data(), c->pix_fmt, format_desc_.width, format_desc_.height); - - sws_scale(img_convert_ctx_.get(), av_frame->data, av_frame->linesize, 0, c->height, local_av_frame->data, local_av_frame->linesize); - - int out_size = THROW_ON_ERROR2(avcodec_encode_video(c, video_outbuf_.data(), video_outbuf_.size(), local_av_frame.get()), "[ffmpeg_consumer]"); - if(out_size > 0) - { - AVPacket pkt; - av_init_packet(&pkt); - - if (c->coded_frame->pts != AV_NOPTS_VALUE) - pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, video_st_->time_base); - - if(c->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - - pkt.stream_index = video_st_->index; - pkt.data = video_outbuf_.data(); - pkt.size = out_size; - - THROW_ON_ERROR2(av_interleaved_write_frame(oc_.get(), &pkt), L"[ffmpeg_consumer]"); - } - } - - void encode_audio_frame(const safe_ptr& frame) - { - auto c = audio_st_->codec; - - auto audio_data = core::audio_32_to_16(frame->audio_data()); - - AVPacket pkt; - av_init_packet(&pkt); - - if (c->coded_frame && c->coded_frame->pts != AV_NOPTS_VALUE) - pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st_->time_base); - - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.stream_index = audio_st_->index; - pkt.size = audio_data.size()*2; - pkt.data = reinterpret_cast(audio_data.data()); - - THROW_ON_ERROR2(av_interleaved_write_frame(oc_.get(), &pkt), L"[ffmpeg_consumer]"); - } - - void send(const safe_ptr& frame) - { - executor_.begin_invoke([=] - { - encode_video_frame(frame); - encode_audio_frame(frame); - }); - } -}; - -struct ffmpeg_consumer_proxy : public core::frame_consumer -{ - const std::wstring filename_; - const bool key_only_; - const std::string codec_; - const int bitrate_; - - std::unique_ptr consumer_; - -public: - - ffmpeg_consumer_proxy(const std::wstring& filename, bool key_only, const std::string codec, int bitrate) - : filename_(filename) - , key_only_(key_only) - , codec_(boost::to_lower_copy(codec)) - , bitrate_(bitrate) - { - } - - virtual void initialize(const core::video_format_desc& format_desc, int, int) - { - consumer_.reset(); - consumer_.reset(new ffmpeg_consumer(narrow(filename_), format_desc, codec_, bitrate_)); - } - - virtual bool send(const safe_ptr& frame) override - { - consumer_->send(frame); - return true; - } - - virtual std::wstring print() const override - { - return consumer_ ? consumer_->print() : L"[ffmpeg_consumer]"; - } - - virtual bool has_synchronization_clock() const override - { - return false; - } - - virtual size_t buffer_depth() const override - { - return 1; - } -}; - -safe_ptr create_ffmpeg_consumer(const std::vector& params) -{ - if(params.size() < 2 || params[0] != L"FILE") - return core::frame_consumer::empty(); - - // TODO: Ask stakeholders about case where file already exists. - boost::filesystem::remove(boost::filesystem::wpath(env::media_folder() + params[1])); // Delete the file if it exists - bool key_only = std::find(params.begin(), params.end(), L"KEY_ONLY") != params.end(); - - std::string codec = "dnxhd"; - auto codec_it = std::find(params.begin(), params.end(), L"CODEC"); - if(codec_it++ != params.end()) - codec = narrow(*codec_it); - - int bitrate = 0; - auto bitrate_it = std::find(params.begin(), params.end(), L"BITRATE"); - if(bitrate_it++ != params.end()) - bitrate = boost::lexical_cast(*codec_it); - - return make_safe(env::media_folder() + params[1], key_only, codec, bitrate); -} - -safe_ptr create_ffmpeg_consumer(const boost::property_tree::ptree& ptree) -{ - std::string filename = ptree.get("path"); - auto key_only = ptree.get("key-only", false); - auto codec = ptree.get("codec", "dnxhd"); - auto bitrate = ptree.get("bitrate", 0); - - return make_safe(env::media_folder() + widen(filename), key_only, codec, bitrate); -} - -}} +/* +* Copyright (c) 2011 Sveriges Television AB +* +* 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 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_error.h" + +#include "ffmpeg_consumer.h" + +#include "../producer/tbb_avcodec.h" +#include "../producer/util/util.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 +#include + +#if defined(_MSC_VER) +#pragma warning (push) +#pragma warning (disable : 4244) +#endif +extern "C" +{ + #define __STDC_CONSTANT_MACROS + #define __STDC_LIMIT_MACROS + #include + #include + #include + #include + #include + #include + #include +} +#if defined(_MSC_VER) +#pragma warning (pop) +#endif + +namespace caspar { namespace ffmpeg { + +int av_opt_set(void *obj, const char *name, const char *val, int search_flags) +{ + AVClass* av_class = *(AVClass**)obj; + + if((strcmp(name, "pix_fmt") == 0 || strcmp(name, "pixel_format") == 0) && strcmp(av_class->class_name, "AVCodecContext") == 0) + { + AVCodecContext* c = (AVCodecContext*)obj; + auto pix_fmt = av_get_pix_fmt(val); + if(pix_fmt == PIX_FMT_NONE) + return -1; + c->pix_fmt = pix_fmt; + return 0; + } + //if((strcmp(name, "r") == 0 || strcmp(name, "frame_rate") == 0) && strcmp(av_class->class_name, "AVCodecContext") == 0) + //{ + // AVCodecContext* c = (AVCodecContext*)obj; + + // if(c->codec_type != AVMEDIA_TYPE_VIDEO) + // return -1; + + // AVRational rate; + // int ret = av_parse_video_rate(&rate, val); + // if(ret < 0) + // return ret; + + // c->time_base.num = rate.den; + // c->time_base.den = rate.num; + // return 0; + //} + + return ::av_opt_set(obj, name, val, search_flags); +} + +struct option +{ + std::string name; + std::string value; + + option(std::string name, std::string value) + : name(std::move(name)) + , value(std::move(value)) + { + } +}; + +struct output_format +{ + AVOutputFormat* format; + int width; + int height; + AVCodecID vcodec; + AVCodecID acodec; + int croptop; + int cropbot; + + output_format(const core::video_format_desc& format_desc, const std::string& filename, std::vector