From 40079c2c63b7b41a9eb166a2fb6fa76463ac0f7e Mon Sep 17 00:00:00 2001 From: Nicolas Pope <nicolas.pope@utu.fi> Date: Fri, 31 Jul 2020 08:51:20 +0300 Subject: [PATCH] Client bitrate selection --- .../codecs/include/ftl/codecs/packet.hpp | 4 +- components/codecs/src/nvidia_encoder.cpp | 42 ++++++----- .../streams/include/ftl/streams/netstream.hpp | 1 + .../streams/include/ftl/streams/sender.hpp | 6 +- components/streams/src/netstream.cpp | 17 +++-- components/streams/src/sender.cpp | 71 +++++++++++-------- components/streams/test/sender_unit.cpp | 2 +- 7 files changed, 84 insertions(+), 59 deletions(-) diff --git a/components/codecs/include/ftl/codecs/packet.hpp b/components/codecs/include/ftl/codecs/packet.hpp index 040f8a1da..65c07580e 100644 --- a/components/codecs/include/ftl/codecs/packet.hpp +++ b/components/codecs/include/ftl/codecs/packet.hpp @@ -37,9 +37,9 @@ struct IndexHeader { struct Packet { ftl::codecs::codec_t codec; uint8_t reserved=0; - uint8_t frame_count; // v4+ Frames included in this packet + uint8_t frame_count=1; // v4+ Frames included in this packet uint8_t bitrate=0; // v4+ For multi-bitrate encoding, 0=highest - uint8_t flags; // Codec dependent flags (eg. I-Frame or P-Frame) + uint8_t flags=0; // Codec dependent flags (eg. I-Frame or P-Frame) std::vector<uint8_t> data; MSGPACK_DEFINE(codec, reserved, frame_count, bitrate, flags, data); diff --git a/components/codecs/src/nvidia_encoder.cpp b/components/codecs/src/nvidia_encoder.cpp index 62ae6b0f1..10e3d2e0b 100644 --- a/components/codecs/src/nvidia_encoder.cpp +++ b/components/codecs/src/nvidia_encoder.cpp @@ -1,5 +1,5 @@ #include <ftl/codecs/nvidia_encoder.hpp> -//#include <loguru.hpp> +#include <loguru.hpp> #include <ftl/timer.hpp> #include <ftl/codecs/codecs.hpp> #include <ftl/cuda_util.hpp> @@ -104,23 +104,27 @@ static ftl::codecs::NvidiaEncoder::Parameters generateParams(const cv::cuda::Gpu return params; } +/* + * Using image height and a 0 to 1 rate scale, calculate a Mbps value to be + * used. + */ static uint64_t calculateBitrate(int64_t pixels, float ratescale) { - /*float bitrate = 1.0f; // Megabits - switch (def) { - case definition_t::UHD4k : bitrate = 40.0f; break; - case definition_t::HTC_VIVE : bitrate = 32.0f; break; - case definition_t::HD1080 : bitrate = 12.0f; break; - case definition_t::HD720 : bitrate = 8.0f; break; - case definition_t::SD576 : - case definition_t::SD480 : bitrate = 4.0f; break; - case definition_t::LD360 : bitrate = 2.0f; break; - default : bitrate = 16.0f; - }*/ - - float bitrate = 16.0f * float(pixels); - - //bitrate *= 1000.0f*1000.0f; - float minrate = 0.05f * bitrate; + static constexpr float kTopResolution = 2160.0f; + static constexpr float kBottomResolution = 360.0f; + static constexpr float kTopBitrate = 40.0f; // Mbps + static constexpr float kBottomBitrate = 2.0f; // Mbps + static constexpr float kBitrateScale = 1024.0f*1024.0f; + static constexpr float kMinBitrateFactor = 0.05f; // 5% of max for resolution + + float resolution = (float(pixels) - kBottomResolution) / (kTopResolution - kBottomResolution); + float bitrate = (kTopBitrate - kBottomBitrate) * resolution + kBottomBitrate; + + // Limit to 80Mbps. + if (bitrate > 80.0f) bitrate = 80.0f; + + bitrate *= kBitrateScale; + + float minrate = kMinBitrateFactor * bitrate; return uint64_t((bitrate - minrate)*ratescale + minrate); } @@ -210,8 +214,8 @@ bool NvidiaEncoder::_createEncoder(const cv::cuda::GpuMat &in, const ftl::codecs Parameters params = generateParams(in, pkt); if (nvenc_ && (params == params_)) return true; - uint64_t bitrate = calculateBitrate(in.cols*in.rows, float(pkt.bitrate)/255.0f) * pkt.frame_count; - //LOG(INFO) << "Calculated bitrate " << ((params.is_float) ? "(float)" : "(rgb)") << ": " << bitrate; + uint64_t bitrate = calculateBitrate(in.rows, float(pkt.bitrate)/255.0f); + LOG(INFO) << "Calculated bitrate " << (float(bitrate) / 1024.0f / 1024.0f) << "Mbps (" << int(pkt.bitrate) << ")"; params_ = params; diff --git a/components/streams/include/ftl/streams/netstream.hpp b/components/streams/include/ftl/streams/netstream.hpp index 36ad0d382..f53b37dd7 100644 --- a/components/streams/include/ftl/streams/netstream.hpp +++ b/components/streams/include/ftl/streams/netstream.hpp @@ -86,6 +86,7 @@ class Net : public Stream { int tally_; std::array<std::atomic<int>,32> reqtally_; std::unordered_set<ftl::codecs::Channel> last_selected_; + uint8_t bitrate_=255; ftl::Handler<ftl::net::Peer*> connect_cb_; diff --git a/components/streams/include/ftl/streams/sender.hpp b/components/streams/include/ftl/streams/sender.hpp index 66b782b0c..0ae404bfb 100644 --- a/components/streams/include/ftl/streams/sender.hpp +++ b/components/streams/include/ftl/streams/sender.hpp @@ -87,6 +87,9 @@ class Sender : public ftl::Configurable { std::unordered_map<int, EncodingState> state_; std::unordered_map<int, AudioState> audio_state_; + std::map<uint8_t, int64_t> bitrate_map_; + SHARED_MUTEX bitrate_mtx_; + int bitrate_timeout_; //ftl::codecs::Encoder *_getEncoder(int fsid, int fid, ftl::codecs::Channel c); void _encodeChannel(ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, bool reset, bool last_flush); @@ -96,7 +99,7 @@ class Sender : public ftl::Configurable { void _encodeDataChannel(ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, bool reset, bool last_flush); void _encodeDataChannel(ftl::data::Frame &fs, ftl::codecs::Channel c, bool reset); - int _generateTiles(const ftl::rgbd::FrameSet &fs, int offset, ftl::codecs::Channel c, cv::cuda::Stream &stream, bool, bool); + int _generateTiles(const ftl::rgbd::FrameSet &fs, int offset, ftl::codecs::Channel c, cv::cuda::Stream &stream, bool); EncodingState &_getTile(int fsid, ftl::codecs::Channel c); cv::Rect _generateROI(const ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, int offset, bool stereo); float _selectFloatMax(ftl::codecs::Channel c); @@ -105,6 +108,7 @@ class Sender : public ftl::Configurable { void _sendPersistent(ftl::data::Frame &frame); bool _checkNeedsIFrame(int64_t ts, bool injecting); + uint8_t _getMinBitrate(); }; } diff --git a/components/streams/src/netstream.cpp b/components/streams/src/netstream.cpp index 6974066b7..101cdee7d 100644 --- a/components/streams/src/netstream.cpp +++ b/components/streams/src/netstream.cpp @@ -63,6 +63,12 @@ Net::Net(nlohmann::json &config, ftl::net::Universe *net) : Stream(config), acti last_frame_ = 0; time_peer_ = ftl::UUID(0); + + bitrate_ = static_cast<uint8_t>(std::max(0, std::min(255, value("bitrate", 255)))); + on("bitrate", [this]() { + bitrate_ = static_cast<uint8_t>(std::max(0, std::min(255, value("bitrate", 255)))); + tally_ = 0; + }); } Net::~Net() { @@ -195,7 +201,7 @@ bool Net::begin() { last_selected_ = sel; for (auto c : changed) { - _sendRequest(c, kAllFramesets, kAllFrames, 30, 0); + _sendRequest(c, kAllFramesets, kAllFrames, 30, 255); } } } @@ -207,7 +213,7 @@ bool Net::begin() { const auto &sel = selected(0); for (auto c : sel) { - _sendRequest(c, kAllFramesets, kAllFrames, 30, 0); + _sendRequest(c, kAllFramesets, kAllFrames, 30, 255); } } tally_ = 30*kTallyScale; @@ -295,7 +301,7 @@ bool Net::begin() { active_ = true; // Initially send a colour request just to create the connection - _sendRequest(Channel::Colour, kAllFramesets, kAllFrames, 30, 0); + _sendRequest(Channel::Colour, kAllFramesets, kAllFrames, 30, 255); return true; } @@ -307,7 +313,7 @@ void Net::reset() { auto sel = selected(0); for (auto c : sel) { - _sendRequest(c, kAllFramesets, kAllFrames, 30, 0); + _sendRequest(c, kAllFramesets, kAllFrames, 30, 255); } } tally_ = 30*kTallyScale; @@ -322,7 +328,8 @@ bool Net::_sendRequest(Channel c, uint8_t frameset, uint8_t frames, uint8_t coun codec_t::Any, // TODO: Allow specific codec requests 0, count, - bitrate, + //bitrate, + bitrate_, 0 }; diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp index ae56595b7..72ab29c71 100644 --- a/components/streams/src/sender.cpp +++ b/components/streams/src/sender.cpp @@ -32,6 +32,8 @@ Sender::Sender(nlohmann::json &config) : ftl::Configurable(config), stream_(null on("iframes", [this]() { add_iframes_ = value("iframes", 50); }); + + on("bitrate_timeout", bitrate_timeout_, 10000); } Sender::~Sender() { @@ -48,18 +50,35 @@ void Sender::setStream(ftl::stream::Stream*s) { handle_ = stream_->onPacket([this](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { if (pkt.data.size() > 0 || !(spkt.flags & ftl::codecs::kFlagRequest)) return true; - LOG(INFO) << "SENDER REQUEST : " << (int)spkt.channel; + if (int(spkt.channel) < 32) { + auto now = ftl::timer::get_time(); + + // Update the min bitrate selection + UNIQUE_LOCK(bitrate_mtx_, lk); + if (bitrate_map_.size() > 0) { + if (now - bitrate_map_.begin()->second > bitrate_timeout_) { + bitrate_map_.erase(bitrate_map_.begin()); + } + } + bitrate_map_[pkt.bitrate] = now; + } //if (state_cb_) state_cb_(spkt.channel, spkt.streamID, spkt.frame_number); if (reqcb_) reqcb_(spkt,pkt); // Inject state packets - //do_inject_ = true; do_inject_.clear(); + return true; }); } +uint8_t Sender::_getMinBitrate() { + SHARED_LOCK(bitrate_mtx_, lk); + if (bitrate_map_.size() > 0) return bitrate_map_.begin()->first; + else return 255; +} + void Sender::onRequest(const ftl::stream::StreamCallback &cb) { reqcb_ = cb; } @@ -84,28 +103,6 @@ static void writeValue(std::vector<unsigned char> &data, T value) { data.insert(data.end(), pvalue_start, pvalue_start+sizeof(T)); } -/*static void mergeNALUnits(const std::list<ftl::codecs::Packet> &pkts, ftl::codecs::Packet &pkt) { - size_t size = 0; - for (auto i=pkts.begin(); i!=pkts.end(); ++i) size += (*i).data.size(); - - // TODO: Check Codec, jpg etc can just use last frame. - // TODO: Also if iframe, can just use that instead - - const auto &first = pkts.front(); - pkt.codec = first.codec; - //pkt.definition = first.definition; - pkt.frame_count = first.frame_count; - pkt.bitrate = first.bitrate; - pkt.flags = first.flags | ftl::codecs::kFlagMultiple; // means merged packets - pkt.data.reserve(size+pkts.size()*sizeof(int)); - - for (auto i=pkts.begin(); i!=pkts.end(); ++i) { - writeValue<int>(pkt.data, (*i).data.size()); - //LOG(INFO) << "NAL Count = " << (*i).data.size(); - pkt.data.insert(pkt.data.end(), (*i).data.begin(), (*i).data.end()); - } -}*/ - void Sender::_sendPersistent(ftl::data::Frame &frame) { auto *session = frame.parent(); if (session) { @@ -379,7 +376,7 @@ void Sender::resetEncoders(uint32_t fsid) { void Sender::setActiveEncoders(uint32_t fsid, const std::unordered_set<Channel> &ec) { for (auto &t : state_) { - if ((t.first >> 8) == static_cast<int>(fsid)) { + if ((t.first >> 16) == static_cast<int>(fsid)) { if (t.second.encoder[0] && ec.count(static_cast<Channel>(t.first & 0xFF)) == 0) { // Remove unwanted encoder ftl::codecs::free(t.second.encoder[0]); @@ -395,12 +392,24 @@ void Sender::setActiveEncoders(uint32_t fsid, const std::unordered_set<Channel> } void Sender::_encodeVideoChannel(ftl::data::FrameSet &fs, Channel c, bool reset, bool last_flush) { - bool lossless = value("lossless", false); - int max_bitrate = std::max(0, std::min(255, value("max_bitrate", 128))); + bool isfloat = ftl::codecs::type(c) == CV_32F; + + bool lossless = (isfloat) ? value("lossless_float", false) : value("lossless_colour", false); + int bitrate = std::max(0, std::min(255, (isfloat) ? value("bitrate_float", 200) : value("bitrate_colour", 64))); + + bitrate = std::min(static_cast<uint8_t>(bitrate), _getMinBitrate()); + //int min_bitrate = std::max(0, std::min(255, value("min_bitrate", 0))); // TODO: Use this - codec_t codec = static_cast<codec_t>(value("codec", static_cast<int>(codec_t::Any))); + codec_t codec = static_cast<codec_t>( + (isfloat) ? value("codec_float", static_cast<int>(codec_t::Any)) : + value("codec_colour", static_cast<int>(codec_t::Any))); + device_t device = static_cast<device_t>(value("encoder_device", static_cast<int>(device_t::Any))); + if (codec == codec_t::Any) { + codec = (lossless) ? codec_t::HEVC_LOSSLESS : codec_t::HEVC; + } + // TODO: Support high res bool is_stereo = value("stereo", false) && c == Channel::Colour && fs.firstFrame().hasChannel(Channel::Colour2); @@ -457,7 +466,7 @@ void Sender::_encodeVideoChannel(ftl::data::FrameSet &fs, Channel c, bool reset, //} } - int count = _generateTiles(fs, offset, cc, enc->stream(), lossless, is_stereo); + int count = _generateTiles(fs, offset, cc, enc->stream(), is_stereo); if (count <= 0) { LOG(ERROR) << "Could not generate tiles."; break; @@ -473,7 +482,7 @@ void Sender::_encodeVideoChannel(ftl::data::FrameSet &fs, Channel c, bool reset, ftl::codecs::Packet pkt; pkt.frame_count = count; pkt.codec = codec; - pkt.bitrate = (!lossless && ftl::codecs::isFloatChannel(cc)) ? max_bitrate : max_bitrate/2; + pkt.bitrate = bitrate; pkt.flags = 0; // In the event of partial frames, add a flag to indicate that @@ -668,7 +677,7 @@ float Sender::_selectFloatMax(Channel c) { } } -int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c, cv::cuda::Stream &stream, bool lossless, bool stereo) { +int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c, cv::cuda::Stream &stream, bool stereo) { auto &surface = _getTile(fs.id(), c); const ftl::data::Frame *cframe = nullptr; //&fs.frames[offset]; diff --git a/components/streams/test/sender_unit.cpp b/components/streams/test/sender_unit.cpp index 59c7f916a..8c8331607 100644 --- a/components/streams/test/sender_unit.cpp +++ b/components/streams/test/sender_unit.cpp @@ -209,7 +209,7 @@ TEST_CASE( "Sender::post() video frames" ) { fs.frames[1].create<cv::cuda::GpuMat>(Channel::Depth).create(cv::Size(1280,720), CV_32F); fs.frames[1].set<cv::cuda::GpuMat>(Channel::Depth).setTo(cv::Scalar(0.0f)); - sender->set("codec", (int)codec_t::HEVC_LOSSLESS); + sender->set("codec_float", (int)codec_t::HEVC_LOSSLESS); sender->post(fs, Channel::Depth); REQUIRE( count == 1 ); -- GitLab