diff --git a/applications/ftl2mkv/src/main.cpp b/applications/ftl2mkv/src/main.cpp
index e02e2de0d0464ab872e2877a48831a2649fe0570..0ebf1a0bfdecd03e1696b3c688744dc761f92862 100644
--- a/applications/ftl2mkv/src/main.cpp
+++ b/applications/ftl2mkv/src/main.cpp
@@ -41,18 +41,21 @@ static AVStream *add_video_stream(AVFormatContext *oc, const ftl::codecs::Packet
     //c->codec_id = codec_id;
     //c->codec_type = AVMEDIA_TYPE_VIDEO;
 
-	st->time_base.den = 20;
-	st->time_base.num = 1;
+	//st->time_base.den = 20;
+	//st->time_base.num = 1;
 	//st->id = oc->nb_streams-1;
 	//st->nb_frames = 0;
 	st->codecpar->codec_id = codec_id;
 	st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
 	st->codecpar->width = ftl::codecs::getWidth(pkt.definition);
+	//if (pkt.flags & ftl::codecs::kFlagStereo) st->codecpar->width *= 2;
 	st->codecpar->height = ftl::codecs::getHeight(pkt.definition);
 	st->codecpar->format = AV_PIX_FMT_NV12;
 	st->codecpar->bit_rate = 4000000;
 
-	if (pkt.flags & ftl::codecs::kFlagStereo) av_dict_set_int(&st->metadata, "stereo_mode", 1, 0);
+	if (pkt.flags & ftl::codecs::kFlagStereo) av_dict_set(&st->metadata, "stereo_mode", "left_right", 0);
+	//if (pkt.flags & ftl::codecs::kFlagStereo) av_dict_set(&oc->metadata, "stereo_mode", "1", 0);
+	//if (pkt.flags & ftl::codecs::kFlagStereo) av_dict_set_int(&st->metadata, "StereoMode", 1, 0);
 
     /* put sample parameters */
     //c->bit_rate = 4000000;
@@ -141,9 +144,11 @@ int main(int argc, char **argv) {
 
 	//bool stream_added[10] = {false};
 
+	int64_t first_ts = 10000000000000ll;
+
 	// TODO: In future, find a better way to discover number of streams...
 	// Read entire file to find all streams before reading again to write data
-	bool res = r.read(90000000000000, [&current_stream,&current_channel,&r,&video_st,oc](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+	bool res = r.read(90000000000000, [&first_ts,&current_stream,&current_channel,&r,&video_st,oc](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
         if (spkt.channel != static_cast<ftl::codecs::Channel>(current_channel) && current_channel != -1) return;
         if (spkt.frame_number == current_stream || current_stream == 255) {
 
@@ -153,6 +158,8 @@ int main(int argc, char **argv) {
 
 			if (spkt.frame_number >= 10) return;  // TODO: Allow for more than 10
 
+			if (spkt.timestamp < first_ts) first_ts = spkt.timestamp;
+
 			if (video_st[spkt.frame_number][(spkt.channel == Channel::Left) ? 0 : 1] == nullptr) {
 				video_st[spkt.frame_number][(spkt.channel == Channel::Left) ? 0 : 1] = add_video_stream(oc, pkt);
 			}
@@ -177,7 +184,7 @@ int main(int argc, char **argv) {
 
 	bool seen_key[10] = {false};
 
-    res = r.read(90000000000000, [&current_stream,&current_channel,&r,&video_st,oc,&seen_key](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
+    res = r.read(90000000000000, [first_ts,&current_stream,&current_channel,&r,&video_st,oc,&seen_key](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) {
         if (spkt.channel != static_cast<ftl::codecs::Channel>(current_channel) && current_channel != -1) return;
         if (spkt.frame_number == current_stream || current_stream == 255) {
 
@@ -203,10 +210,14 @@ int main(int argc, char **argv) {
 			}
 			if (!seen_key[spkt.frame_number]) return;
 
+			//if (spkt.timestamp > last_ts) framecount++;
+			//last_ts = spkt.timestamp;
+
             AVPacket avpkt;
 			av_init_packet(&avpkt);
 			if (keyframe) avpkt.flags |= AV_PKT_FLAG_KEY;
-			avpkt.pts = spkt.timestamp - r.getStartTime();
+			//avpkt.pts = framecount*50; //spkt.timestamp - r.getStartTime();
+			avpkt.pts = spkt.timestamp - first_ts;
 			avpkt.dts = avpkt.pts;
 			avpkt.stream_index= video_st[spkt.frame_number][(spkt.channel == Channel::Left) ? 0 : 1]->index;
 			avpkt.data= const_cast<uint8_t*>(pkt.data.data());
diff --git a/applications/gui/src/camera.cpp b/applications/gui/src/camera.cpp
index f7e5e2cc2be16f88941795c9ea0d4324c7b5119c..03306b423c8b0044d8170ee4122c3e59d704d22a 100644
--- a/applications/gui/src/camera.cpp
+++ b/applications/gui/src/camera.cpp
@@ -410,11 +410,6 @@ void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
 	// Normalize depth map
 	frame_.get<cv::cuda::GpuMat>(Channel::Depth).convertTo(frame_.get<cv::cuda::GpuMat>(Channel::Depth), CV_32F, 1.0/8.0);
 
-	// Unmap GL buffer from CUDA and finish updating GL texture
-	texture1_.unmap(renderer_->getCUDAStream());
-	depth1_.unmap(renderer_->getCUDAStream());
-	if (isStereo()) texture2_.unmap(renderer2_->getCUDAStream());
-
 	width_ = texture1_.width();
 	height_ = texture1_.height();
 
@@ -426,16 +421,26 @@ void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
 		fs2.mask = 1;
 		//fs2.stale = false;
 		fs2.set(ftl::data::FSFlag::STALE);
-		frame_.swapTo(Channels<0>(Channel::Colour), f);  // Channel::Colour + Channel::Depth
-		if (f.hasChannel(Channel::Colour)) {
-			f.create<cv::cuda::GpuMat>(Channel::Colour2).create(f.get<cv::cuda::GpuMat>(Channel::Colour).size(), f.get<cv::cuda::GpuMat>(Channel::Colour2).type());
-			f.swapChannels(Channel::Colour, Channel::Colour2);
-			cv::cuda::flip(f.get<cv::cuda::GpuMat>(Channel::Colour2), f.get<cv::cuda::GpuMat>(Channel::Colour), 0);
+		if (frame_.hasChannel(Channel::Colour2)) {
+			frame_.swapTo(Channels<0>(Channel::Colour) | Channel::Colour2, f);
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour), 0);
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour2), 0);
+		} else {
+			frame_.swapTo(Channels<0>(Channel::Colour), f);  // Channel::Colour + Channel::Depth
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour), 0);
 		}
+
 		fs2.timestamp = ftl::timer::get_time();
 		fs2.id = 0;
 		record_sender_->post(fs2);
 		record_stream_->select(0, Channels<0>(Channel::Colour));
+		// Reverse the flip
+		if (f.hasChannel(Channel::Colour2)) {
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour), 0);
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour2), 0);
+		} else {
+			ftl::cuda::flip(f.getTexture<uchar4>(Channel::Colour), 0);
+		}
 		f.swapTo(Channels<0>(Channel::Colour), frame_);
 	} else if (do_snapshot_) {
 		do_snapshot_ = false;
@@ -451,6 +456,11 @@ void ftl::gui::Camera::_draw(std::vector<ftl::rgbd::FrameSet*> &fss) {
 		cv::cvtColor(flipped, flipped, cv::COLOR_BGRA2BGR);
 		cv::imwrite(snapshot_filename_, flipped);
 	}
+
+	// Unmap GL buffer from CUDA and finish updating GL texture
+	texture1_.unmap(renderer_->getCUDAStream());
+	depth1_.unmap(renderer_->getCUDAStream());
+	if (isStereo()) texture2_.unmap(renderer2_->getCUDAStream());
 }
 
 void ftl::gui::Camera::update(int fsid, const ftl::codecs::Channels<0> &c) {
@@ -807,6 +817,7 @@ void ftl::gui::Camera::startVideoRecording(const std::string &filename) {
 		record_sender_ = ftl::create<ftl::stream::Sender>(screen_->root(), "videoEncode");
 		record_sender_->setStream(record_stream_);
 		record_sender_->value("codec", 2);  // Default H264
+		record_sender_->value("stereo", true);  // If both channels, then default to stereo
 	}
 
 	if (record_stream_->active()) return;
diff --git a/components/codecs/src/nvpipe_encoder.cpp b/components/codecs/src/nvpipe_encoder.cpp
index e4ad3589e20a3c261a3c16a1e61cbb1e01888b41..5e28fd4b0351afd00d62bb293c09a5367847db33 100644
--- a/components/codecs/src/nvpipe_encoder.cpp
+++ b/components/codecs/src/nvpipe_encoder.cpp
@@ -112,8 +112,10 @@ bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, ftl::codecs::Packet &pkt)
 		return false;
 	}
 
+	bool is_stereo = pkt.flags & ftl::codecs::kFlagStereo;
+
 	auto [tx,ty] = ftl::codecs::chooseTileConfig(pkt.frame_count);
-	pkt.definition = (pkt.definition == definition_t::Any) ? ftl::codecs::findDefinition(in.cols/tx, in.rows/ty) : pkt.definition;
+	pkt.definition = (pkt.definition == definition_t::Any) ? ftl::codecs::findDefinition((is_stereo) ? in.cols/tx/2 : in.cols/tx, in.rows/ty) : pkt.definition;
 	if (pkt.definition == definition_t::Invalid || pkt.definition == definition_t::Any) {
 		LOG(ERROR) << "Could not find appropriate definition";
 		return false;
@@ -133,7 +135,7 @@ bool NvPipeEncoder::encode(const cv::cuda::GpuMat &in, ftl::codecs::Packet &pkt)
 		return false;
 	}
 
-	if (tx*width != in.cols || ty*height != in.rows) {
+	if (((is_stereo) ? tx*width*2 : tx*width) != in.cols || ty*height != in.rows) {
 		// TODO: Resize if lower definition requested...
 		LOG(ERROR) << "Input size does not match expected: " << in.cols << " != " << tx*width;
 		pkt.definition = definition_t::Invalid;
diff --git a/components/streams/include/ftl/streams/sender.hpp b/components/streams/include/ftl/streams/sender.hpp
index 2a8afc8ce53ec46fedc26b9537be8b58fbe89b42..70d485172a19962d075b3836b49e324f095b9595 100644
--- a/components/streams/include/ftl/streams/sender.hpp
+++ b/components/streams/include/ftl/streams/sender.hpp
@@ -56,9 +56,9 @@ class Sender : public ftl::Configurable {
 
 	//ftl::codecs::Encoder *_getEncoder(int fsid, int fid, ftl::codecs::Channel c);
 	void _encodeChannel(ftl::rgbd::FrameSet &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);
+	int _generateTiles(const ftl::rgbd::FrameSet &fs, int offset, ftl::codecs::Channel c, cv::cuda::Stream &stream, bool, bool);
 	EncodingState &_getTile(int fsid, ftl::codecs::Channel c);
-	cv::Rect _generateROI(const ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, int offset);
+	cv::Rect _generateROI(const ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, int offset, bool stereo);
 	float _selectFloatMax(ftl::codecs::Channel c);
 };
 
diff --git a/components/streams/src/sender.cpp b/components/streams/src/sender.cpp
index 236f96f5ec959f124dc04fcea909b362591760fb..204e29e1155381ffabf701c07ad3925b03961825 100644
--- a/components/streams/src/sender.cpp
+++ b/components/streams/src/sender.cpp
@@ -262,6 +262,9 @@ void Sender::_encodeChannel(ftl::rgbd::FrameSet &fs, Channel c, bool reset) {
 	codec_t codec = static_cast<codec_t>(value("codec", static_cast<int>(codec_t::Any)));
 	device_t device = static_cast<device_t>(value("encoder_device", static_cast<int>(device_t::Any)));
 
+	// TODO: Support high res
+	bool is_stereo = value("stereo", false) && c == Channel::Colour && fs.firstFrame().hasChannel(Channel::Colour2);
+
 	uint32_t offset = 0;
 	while (offset < fs.frames.size()) {
 		Channel cc = c;
@@ -303,7 +306,7 @@ void Sender::_encodeChannel(ftl::rgbd::FrameSet &fs, Channel c, bool reset) {
 			}
 		}
 
-		int count = _generateTiles(fs, offset, cc, enc->stream(), lossless);
+		int count = _generateTiles(fs, offset, cc, enc->stream(), lossless, is_stereo);
 		if (count <= 0) {
 			LOG(ERROR) << "Could not generate tiles.";
 			break;
@@ -326,12 +329,13 @@ void Sender::_encodeChannel(ftl::rgbd::FrameSet &fs, Channel c, bool reset) {
 				if (!lossless && ftl::codecs::isFloatChannel(cc)) pkt.flags = ftl::codecs::kFlagFloat | ftl::codecs::kFlagMappedDepth;
 				else if (lossless && ftl::codecs::isFloatChannel(cc)) pkt.flags = ftl::codecs::kFlagFloat;
 				else pkt.flags = ftl::codecs::kFlagFlipRGB;
+				if (is_stereo) pkt.flags |= ftl::codecs::kFlagStereo;
 
 				// In the event of partial frames, add a flag to indicate that
 				if (static_cast<size_t>(fs.count) < fs.frames.size()) pkt.flags |= ftl::codecs::kFlagPartial;
 
 				// Choose correct region of interest into the surface.
-				cv::Rect roi = _generateROI(fs, cc, offset);
+				cv::Rect roi = _generateROI(fs, cc, offset, is_stereo);
 				cv::cuda::GpuMat sroi = tile.surface(roi);
 
 				FTL_Profile("Encoder",0.02);
@@ -357,9 +361,10 @@ void Sender::_encodeChannel(ftl::rgbd::FrameSet &fs, Channel c, bool reset) {
 	}
 }
 
-cv::Rect Sender::_generateROI(const ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, int offset) {
+cv::Rect Sender::_generateROI(const ftl::rgbd::FrameSet &fs, ftl::codecs::Channel c, int offset, bool stereo) {
 	const ftl::rgbd::Frame &cframe = fs.firstFrame();
 	int rwidth = cframe.get<cv::cuda::GpuMat>(c).cols;
+	if (stereo) rwidth *= 2;
 	int rheight = cframe.get<cv::cuda::GpuMat>(c).rows;
 	auto [tx,ty] = ftl::codecs::chooseTileConfig(fs.frames.size()-offset);
 	return cv::Rect(0, 0, tx*rwidth, ty*rheight);
@@ -393,7 +398,7 @@ float Sender::_selectFloatMax(Channel c) {
 	}
 }
 
-int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c, cv::cuda::Stream &stream, bool lossless) {
+int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c, cv::cuda::Stream &stream, bool lossless, bool stereo) {
 	auto &surface = _getTile(fs.id, c);
 
 	const ftl::rgbd::Frame *cframe = nullptr; //&fs.frames[offset];
@@ -402,7 +407,7 @@ int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c,
 
 	// Choose tile configuration and allocate memory
 	auto [tx,ty] = ftl::codecs::chooseTileConfig(fs.frames.size());
-	int rwidth = m.cols;
+	int rwidth = (stereo) ? m.cols*2 : m.cols;
 	int rheight = m.rows;
 	int width = tx * rwidth;
 	int height = ty * rheight;
@@ -423,7 +428,7 @@ int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c,
 		if (fs.hasFrame(offset+count)) {
 			cframe = &fs.frames[offset+count];
 			auto &m = cframe->get<cv::cuda::GpuMat>(c);
-			cv::Rect roi((count % tx)*rwidth, (count / tx)*rheight, rwidth, rheight);
+			cv::Rect roi((count % tx)*rwidth, (count / tx)*rheight, (stereo) ? rwidth/2 : rwidth, rheight);
 			cv::cuda::GpuMat sroi = surface.surface(roi);
 
 			if (m.type() == CV_32F) {
@@ -442,6 +447,30 @@ int Sender::_generateTiles(const ftl::rgbd::FrameSet &fs, int offset, Channel c,
 				LOG(ERROR) << "Unsupported colour format: " << m.type();
 				return 0;
 			}
+
+			// Do the right channel
+			if (stereo) {
+				auto &m = cframe->get<cv::cuda::GpuMat>((c == Channel::Colour) ? Channel::Colour2 : Channel::Colour2HighRes);
+				cv::Rect roi((count % tx)*rwidth + (rwidth/2), (count / tx)*rheight, rwidth/2, rheight);
+				cv::cuda::GpuMat sroi = surface.surface(roi);
+
+				if (m.type() == CV_32F) {
+					if (lossless) {
+						m.convertTo(sroi, CV_16UC1, 1000, stream);
+					} else {
+						ftl::cuda::depth_to_vuya(m, sroi, _selectFloatMax(c), stream);
+					}
+				} else if (m.type() == CV_8UC4) {
+					cv::cuda::cvtColor(m, sroi, cv::COLOR_BGRA2RGBA, 0, stream);
+				} else if (m.type() == CV_8UC3) {
+					cv::cuda::cvtColor(m, sroi, cv::COLOR_BGR2RGBA, 0, stream);
+				} else if (m.type() == CV_8UC1) {
+					m.copyTo(sroi, stream);
+				} else {
+					LOG(ERROR) << "Unsupported colour format: " << m.type();
+					return 0;
+				}
+			}
 		} else {
 			cv::Rect roi((count % tx)*rwidth, (count / tx)*rheight, rwidth, rheight);
 			cv::cuda::GpuMat sroi = surface.surface(roi);