diff --git a/components/codecs/src/nvidia_encoder.cpp b/components/codecs/src/nvidia_encoder.cpp
index a15d6e5980de3f3fd985300ff60cbd678cbc37c9..10e3d2e0b5938a276a36202223963d97b07c3a07 100644
--- a/components/codecs/src/nvidia_encoder.cpp
+++ b/components/codecs/src/nvidia_encoder.cpp
@@ -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,7 +214,7 @@ 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;
+	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 36ad0d382a2b3485856d6d56b7a04da07214369d..f53b37dd7ea343e94bec2271c3a015e56ad10afd 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 2feb3bbe032594866b6ec79fd4d13e9e218cda47..0ae404bfb551c1210c25f46acf12a9524222fd8a 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);
@@ -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 6974066b7ed68c12f7bb4779e2256a71b455c2f5..101cdee7d309dd5140adbe45f10be929cacbb612 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 a7000329a73e4cfe7ca50f0949cde6a17722f94b..72ab29c7189a7c5f4d7304080ae054aa1f314f7b 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]);
@@ -397,9 +394,11 @@ 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 isfloat = ftl::codecs::type(c) == CV_32F;
 
-	bool lossless = (isfloat) ? value("lossless_float", true) : value("lossless_colour", false);
+	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>(
 		(isfloat) ?	value("codec_float", static_cast<int>(codec_t::Any)) :
diff --git a/components/streams/test/sender_unit.cpp b/components/streams/test/sender_unit.cpp
index 59c7f916aa4c33a4533c41c1c047aa093298a8e5..8c8331607f16e44f125618f503fb92cc9f5d6311 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 );