diff --git a/components/rgbd-sources/include/ftl/rgbd/source.hpp b/components/rgbd-sources/include/ftl/rgbd/source.hpp
index a7eae31090446e5794fc3964b40184a1a8b98b0b..a9ba6ddb6f000b61e1b1b9ac0083a3c0984cbd3e 100644
--- a/components/rgbd-sources/include/ftl/rgbd/source.hpp
+++ b/components/rgbd-sources/include/ftl/rgbd/source.hpp
@@ -14,6 +14,7 @@
 
 #include <ftl/cuda_common.hpp>
 #include <ftl/data/new_frame.hpp>
+#include <ftl/data/new_frameset.hpp>
 #include <ftl/data/creators.hpp>
 
 namespace ftl {
@@ -164,6 +165,21 @@ class Source : public ftl::Configurable, public ftl::data::DiscreteSource {
 	void _swap();
 };
 
+class SourceGenerator : public ftl::data::Generator {
+	public:
+	explicit SourceGenerator(Source *src) : source_(src) {}
+
+	inline ftl::Handle onFrameSet(const std::function<bool(std::shared_ptr<ftl::data::FrameSet>)> &cb) override {
+		return source_->onFrame([this,cb](ftl::data::Frame &frame) {
+			auto fs = ftl::data::FrameSet::fromFrame(frame);
+			return cb(fs);
+		});
+	}
+
+	private:
+	Source *source_;
+};
+
 }
 }
 
diff --git a/components/streams/include/ftl/streams/builder.hpp b/components/streams/include/ftl/streams/builder.hpp
index 3ed4eaf3f9b1dd90d7aa2fcc42056086f30f5585..891fac22440707dcad20a85945d4b58fb3c94874 100644
--- a/components/streams/include/ftl/streams/builder.hpp
+++ b/components/streams/include/ftl/streams/builder.hpp
@@ -27,7 +27,7 @@ class Builder {
 
 	//inline void setID(int id) { id_ = id; }
 
-	inline ftl::Handle onFrameSet(const std::function<bool(ftl::data::FrameSet&)> &cb) { return cb_.on(cb); }
+	inline ftl::Handle onFrameSet(const std::function<bool(std::shared_ptr<ftl::data::FrameSet>)> &cb) { return cb_.on(cb); }
 
 	/**
 	 * Instead of pushing a frame, find or create a direct reference to one.
@@ -37,7 +37,7 @@ class Builder {
 	/**
 	 * Get the entire frameset for a given timestamp.
 	 */
-	ftl::data::FrameSet *get(int64_t timestamp);
+	std::shared_ptr<ftl::data::FrameSet> get(int64_t timestamp);
 
 	/**
 	 * Mark a frame as completed.
@@ -63,11 +63,11 @@ class Builder {
 
 	private:
 	ftl::data::Pool *pool_;
-	std::list<ftl::data::FrameSet*> framesets_;  // Active framesets
+	std::list<std::shared_ptr<ftl::data::FrameSet>> framesets_;  // Active framesets
 	//std::list<ftl::data::FrameSet*> allocated_;  // Keep memory allocations
 
 	size_t head_;
-	ftl::Handler<ftl::data::FrameSet&> cb_;
+	ftl::Handler<std::shared_ptr<ftl::data::FrameSet>> cb_;
 	MUTEX mutex_;
 	int mspf_;
 	int64_t last_ts_;
@@ -89,15 +89,15 @@ class Builder {
 	/* Insert a new frameset into the buffer, along with all intermediate
 	 * framesets between the last in buffer and the new one.
 	 */
-	ftl::data::FrameSet *_addFrameset(int64_t timestamp);
+	std::shared_ptr<ftl::data::FrameSet> _addFrameset(int64_t timestamp);
 
 	/* Find a frameset with given latency in frames. */
-	ftl::data::FrameSet *_getFrameset();
-	ftl::data::FrameSet *_get(int64_t timestamp);
+	std::shared_ptr<ftl::data::FrameSet> _getFrameset();
+	std::shared_ptr<ftl::data::FrameSet> _get(int64_t timestamp);
 
 	/* Search for a matching frameset. */
-	ftl::data::FrameSet *_findFrameset(int64_t ts);
-	void _freeFrameset(ftl::data::FrameSet *);
+	std::shared_ptr<ftl::data::FrameSet> _findFrameset(int64_t ts);
+	//void _freeFrameset(std::shared_ptr<ftl::data::FrameSet>);
 
 	void _schedule();
 
diff --git a/components/streams/include/ftl/streams/receiver.hpp b/components/streams/include/ftl/streams/receiver.hpp
index 82587e5b5a9769904050b246c4ec8cb399aad783..90479a4bf2bcc36a8bf5ddc4fceb9a2a0369b0c7 100644
--- a/components/streams/include/ftl/streams/receiver.hpp
+++ b/components/streams/include/ftl/streams/receiver.hpp
@@ -15,7 +15,7 @@ namespace stream {
 /**
  * Convert packet streams into framesets.
  */
-class Receiver : public ftl::Configurable {
+class Receiver : public ftl::Configurable, public ftl::data::Generator {
 	public:
 	explicit Receiver(nlohmann::json &config, ftl::data::Pool *);
 	~Receiver();
@@ -34,7 +34,7 @@ class Receiver : public ftl::Configurable {
 	 * Register a callback for received framesets. Sources are automatically
 	 * created to match the data received.
 	 */
-	ftl::Handle onFrameSet(const std::function<bool(ftl::data::FrameSet &)> &cb);
+	ftl::Handle onFrameSet(const std::function<bool(std::shared_ptr<ftl::data::FrameSet>)> &cb) override;
 
 	/**
 	 * Add a frameset handler to a specific stream ID.
@@ -48,7 +48,7 @@ class Receiver : public ftl::Configurable {
 	private:
 	ftl::stream::Stream *stream_;
 	ftl::data::Pool *pool_;
-	ftl::SingletonHandler<ftl::data::FrameSet&> callback_;
+	ftl::SingletonHandler<std::shared_ptr<ftl::data::FrameSet>> callback_;
 	std::unordered_map<uint32_t, ftl::streams::Builder> builders_;
 	std::list<ftl::Handle> handles_;
 	ftl::codecs::Channel second_channel_;
diff --git a/components/streams/src/builder.cpp b/components/streams/src/builder.cpp
index 591485844055a0d780225da6214acc447b2e78a4..c869b882d2ceaf1194c1643a07b8994657f6d4c5 100644
--- a/components/streams/src/builder.cpp
+++ b/components/streams/src/builder.cpp
@@ -50,7 +50,7 @@ Builder::~Builder() {
 	}
 }
 
-ftl::data::FrameSet *Builder::get(int64_t timestamp) {
+std::shared_ptr<ftl::data::FrameSet> Builder::get(int64_t timestamp) {
 	if (timestamp <= 0) throw FTL_Error("Invalid frame timestamp");
 
 	UNIQUE_LOCK(mutex_, lk);
@@ -58,12 +58,12 @@ ftl::data::FrameSet *Builder::get(int64_t timestamp) {
 	return _get(timestamp);
 }
 
-ftl::data::FrameSet *Builder::_get(int64_t timestamp) {
+std::shared_ptr<ftl::data::FrameSet> Builder::_get(int64_t timestamp) {
 	if (timestamp <= last_frame_) {
 		throw FTL_Error("Frameset already completed: " << timestamp);
 	}
 
-	auto *fs = _findFrameset(timestamp);
+	auto fs = _findFrameset(timestamp);
 
 	if (!fs) {
 		// Add new frameset
@@ -88,7 +88,7 @@ ftl::data::Frame &Builder::get(int64_t timestamp, size_t ix) {
 		size_ = ix+1;
 	}
 
-	auto *fs = _get(timestamp);
+	auto fs = _get(timestamp);
 
 	//if (ix >= fs->frames.size()) {
 		//throw FTL_Error("Frame index out-of-bounds - " << ix << "(" << fs->frames.size() << ")");
@@ -109,7 +109,7 @@ ftl::data::Frame &Builder::get(int64_t timestamp, size_t ix) {
 }
 
 void Builder::completed(int64_t ts, size_t ix) {
-	ftl::data::FrameSet *fs = nullptr;
+	std::shared_ptr<ftl::data::FrameSet> fs;
 
 	{
 		UNIQUE_LOCK(mutex_, lk);
@@ -141,7 +141,7 @@ void Builder::completed(int64_t ts, size_t ix) {
 }
 
 void Builder::markPartial(int64_t ts) {
-	ftl::data::FrameSet *fs = nullptr;
+	std::shared_ptr<ftl::data::FrameSet> fs;
 
 	{
 		UNIQUE_LOCK(mutex_, lk);
@@ -152,7 +152,7 @@ void Builder::markPartial(int64_t ts) {
 
 void Builder::_schedule() {
 	if (size_ == 0) return;
-	ftl::data::FrameSet *fs = nullptr;
+	std::shared_ptr<ftl::data::FrameSet> fs;
 
 	// Still working on a previously scheduled frame
 	if (jobs_ > 0) return;
@@ -174,7 +174,7 @@ void Builder::_schedule() {
 			fs->store();
 
 			try {
-				cb_.trigger(*fs);
+				cb_.trigger(fs);
 			} catch(const ftl::exception &e) {
 				LOG(ERROR) << "Exception in frameset builder: " << e.what();
 				LOG(ERROR) << "Trace = " << e.trace();
@@ -183,7 +183,7 @@ void Builder::_schedule() {
 			}
 
 			UNIQUE_LOCK(mutex_, lk);
-			_freeFrameset(fs);
+			//_freeFrameset(fs);
 			jobs_--;
 
 			// Schedule another frame immediately (or try to)
@@ -231,7 +231,7 @@ std::pair<float,float> Builder::getStatistics() {
 	return {fps,latency};
 }
 
-ftl::data::FrameSet *Builder::_findFrameset(int64_t ts) {
+std::shared_ptr<ftl::data::FrameSet> Builder::_findFrameset(int64_t ts) {
 	// Search backwards to find match
 	for (auto f : framesets_) {
 		if (f->timestamp() == ts) {
@@ -248,7 +248,7 @@ ftl::data::FrameSet *Builder::_findFrameset(int64_t ts) {
  * Get the most recent completed frameset that isn't stale.
  * Note: Must occur inside a mutex lock.
  */
-ftl::data::FrameSet *Builder::_getFrameset() {
+std::shared_ptr<ftl::data::FrameSet> Builder::_getFrameset() {
 	//LOG(INFO) << "BUF SIZE = " << framesets_.size();
 
 	auto i = framesets_.begin();
@@ -263,7 +263,7 @@ ftl::data::FrameSet *Builder::_getFrameset() {
 	}
 
 	if (i != framesets_.end()) {
-		auto *f = *i;
+		auto f = *i;
 		//LOG(INFO) << "GET: " << f->count << " of " << size_;
 		//if (!f->stale && static_cast<unsigned int>(f->count) >= size_) {
 			//LOG(INFO) << "GET FRAMESET and remove: " << f->timestamp;
@@ -279,7 +279,7 @@ ftl::data::FrameSet *Builder::_getFrameset() {
 			while (j!=framesets_.end()) {
 				++count;
 
-				auto *f2 = *j;
+				auto f2 = *j;
 				j = framesets_.erase(j);
 				mergeFrameset(*f,*f2);
 				//_freeFrameset(f2);
@@ -298,23 +298,13 @@ ftl::data::FrameSet *Builder::_getFrameset() {
 	return nullptr;
 }
 
-void Builder::_freeFrameset(ftl::data::FrameSet *fs) {
-	//allocated_.push_back(fs);
-	delete fs;
-}
-
-ftl::data::FrameSet *Builder::_addFrameset(int64_t timestamp) {
+std::shared_ptr<ftl::data::FrameSet> Builder::_addFrameset(int64_t timestamp) {
 	if (framesets_.size() >= 32) {
 		LOG(WARNING) << "Frameset buffer full, resetting: " << timestamp;
-
-		// Do a complete reset
-		for (auto *fs : framesets_) {
-			delete fs;
-		}
 		framesets_.clear();
 	}
 
-	FrameSet *newf = new FrameSet(pool_, ftl::data::FrameID(id_,255), timestamp);
+	auto newf = std::make_shared<FrameSet>(pool_, ftl::data::FrameID(id_,255), timestamp);
 	for (size_t i=0; i<size_; ++i) {
 		newf->frames.push_back(std::move(pool_->allocate(ftl::data::FrameID(id_, i), timestamp)));
 	}
@@ -328,7 +318,7 @@ ftl::data::FrameSet *Builder::_addFrameset(int64_t timestamp) {
 
 	// Insertion sort by timestamp
 	for (auto i=framesets_.begin(); i!=framesets_.end(); i++) {
-		auto *f = *i;
+		auto f = *i;
 
 		if (timestamp > f->timestamp()) {
 			framesets_.insert(i, newf);
diff --git a/components/streams/src/receiver.cpp b/components/streams/src/receiver.cpp
index 3050fdf7750a2d31c7b98c018069f90c86acbdeb..78cb826b3db168beda34397e12870339fb51e4ac 100644
--- a/components/streams/src/receiver.cpp
+++ b/components/streams/src/receiver.cpp
@@ -63,7 +63,7 @@ ftl::streams::Builder &Receiver::builder(uint32_t id) {
 		b.setID(id);
 		b.setPool(pool_);
 		b.setBufferSize(value("frameset_buffer_size", 3));
-		handles_.push_back(std::move(b.onFrameSet([this](ftl::data::FrameSet &fs) {
+		handles_.push_back(std::move(b.onFrameSet([this](std::shared_ptr<ftl::data::FrameSet> fs) {
 			callback_.trigger(fs);
 			return true;
 		})));
@@ -249,7 +249,7 @@ void Receiver::_processVideo(const StreamPacket &spkt, const Packet &pkt) {
 
 	// Get the frameset
 	builder(spkt.streamID).get(spkt.timestamp, spkt.frame_number+pkt.frame_count-1);  // TODO: This is a hack
-	auto *fs = builder(spkt.streamID).get(spkt.timestamp);
+	auto fs = builder(spkt.streamID).get(spkt.timestamp);
 
 	if (!fs->frames[0].has(Channel::Calibration)) {
 		LOG(WARNING) << "No calibration, skipping frame";
@@ -437,7 +437,7 @@ void Receiver::setStream(ftl::stream::Stream *s) {
 	});
 }
 
-ftl::Handle Receiver::onFrameSet(const std::function<bool(ftl::data::FrameSet &)> &cb) {
+ftl::Handle Receiver::onFrameSet(const std::function<bool(std::shared_ptr<ftl::data::FrameSet>)> &cb) {
 	//for (auto &b : builders_)
 	//	b.second.onFrameSet(cb);
 	return callback_.on(cb);
diff --git a/components/streams/test/builder_unit.cpp b/components/streams/test/builder_unit.cpp
index 6e530464fdb60171534cb6e517d5b3299078a029..382590c73a5d68da5266015605c4fea1b5f10d41 100644
--- a/components/streams/test/builder_unit.cpp
+++ b/components/streams/test/builder_unit.cpp
@@ -13,7 +13,7 @@ TEST_CASE("ftl::streams::Builder can obtain a frameset", "[]") {
 		Builder builder(&pool, 44);
 
 		builder.get(100, 0);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		REQUIRE( fs->frameset() == 44 );
 		REQUIRE( fs->source() == 255);
@@ -30,7 +30,7 @@ TEST_CASE("ftl::streams::Builder can obtain a frameset", "[]") {
 
 		builder.get(100, 4);
 		builder.get(100, 0);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		REQUIRE( fs->frameset() == 44 );
 		REQUIRE( fs->timestamp() == 100 );
@@ -48,7 +48,7 @@ TEST_CASE("ftl::streams::Builder can complete a frame", "[]") {
 
 		builder.get(100, 1);
 		builder.get(100, 0);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		builder.completed(100, 0);
 
@@ -69,12 +69,12 @@ TEST_CASE("ftl::streams::Builder can complete a frameset", "[]") {
 		builder.setBufferSize(0);
 
 		builder.get(100, 0);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		int fsid = 0;
 
-		auto h = builder.onFrameSet([&fsid](ftl::data::FrameSet &fs) {
-			fsid = fs.frameset();
+		auto h = builder.onFrameSet([&fsid](std::shared_ptr<ftl::data::FrameSet> fs) {
+			fsid = fs->frameset();
 			return false;
 		});
 
@@ -92,12 +92,12 @@ TEST_CASE("ftl::streams::Builder can complete a frameset", "[]") {
 		builder.setBufferSize(0);
 
 		builder.get(100, 1);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		int fsid = 0;
 
-		auto h = builder.onFrameSet([&fsid](ftl::data::FrameSet &fs) {
-			fsid = fs.frameset();
+		auto h = builder.onFrameSet([&fsid](std::shared_ptr<ftl::data::FrameSet> fs) {
+			fsid = fs->frameset();
 			return false;
 		});
 
@@ -116,12 +116,12 @@ TEST_CASE("ftl::streams::Builder can complete a frameset", "[]") {
 		builder.setBufferSize(0);
 
 		builder.get(100, 1);
-		auto *fs = builder.get(100);
+		auto fs = builder.get(100);
 
 		int fsid = 0;
 
-		auto h = builder.onFrameSet([&fsid](ftl::data::FrameSet &fs) {
-			fsid = fs.frameset();
+		auto h = builder.onFrameSet([&fsid](std::shared_ptr<ftl::data::FrameSet> fs) {
+			fsid = fs->frameset();
 			return false;
 		});
 
diff --git a/components/streams/test/receiver_unit.cpp b/components/streams/test/receiver_unit.cpp
index ae589fcb59e50a62a3addd66cc0dc589f27b9be0..7ed518ae0d0767de7b1997909045742318683808 100644
--- a/components/streams/test/receiver_unit.cpp
+++ b/components/streams/test/receiver_unit.cpp
@@ -106,14 +106,14 @@ TEST_CASE( "Receiver generating onFrameSet" ) {
 		stream.post(spkt, pkt);
 
 		int count = 0;
-		auto h = receiver->onFrameSet([&count](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			REQUIRE( fs.timestamp() == 10 );
-			REQUIRE( fs.frames.size() == 1 );
-			REQUIRE( fs.frames[0].hasChannel(Channel::Colour) );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
+			REQUIRE( fs->timestamp() == 10 );
+			REQUIRE( fs->frames.size() == 1 );
+			REQUIRE( fs->frames[0].hasChannel(Channel::Colour) );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
 
 			return true;
 		});
@@ -134,8 +134,8 @@ TEST_CASE( "Receiver generating onFrameSet" ) {
 		stream.post(spkt, pkt);
 
 		std::atomic<int> mask = 0;
-		auto h = receiver->onFrameSet([&mask](ftl::rgbd::FrameSet &fs) {
-			mask |= 1 << fs.frameset();
+		auto h = receiver->onFrameSet([&mask](std::shared_ptr<ftl::data::FrameSet> fs) {
+			mask |= 1 << fs->frameset();
 			return true;
 		});
 
@@ -159,17 +159,17 @@ TEST_CASE( "Receiver generating onFrameSet" ) {
 		stream.post(spkt, pkt);
 
 		int count = 0;
-		auto h = receiver->onFrameSet([&count](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			REQUIRE( fs.timestamp() == 10 );
-			REQUIRE( fs.frames.size() == 2 );
-			REQUIRE( fs.frames[0].hasChannel(Channel::Colour) );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
-			REQUIRE( fs.frames[1].hasChannel(Channel::Colour) );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
+			REQUIRE( fs->timestamp() == 10 );
+			REQUIRE( fs->frames.size() == 2 );
+			REQUIRE( fs->frames[0].hasChannel(Channel::Colour) );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
+			REQUIRE( fs->frames[1].hasChannel(Channel::Colour) );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
 
 			return true;
 		});
@@ -193,17 +193,17 @@ TEST_CASE( "Receiver generating onFrameSet" ) {
 		stream.post(spkt, pkt);
 
 		int count = 0;
-		auto h = receiver->onFrameSet([&count](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			REQUIRE( fs.timestamp() == 10 );
-			REQUIRE( fs.frames.size() == 2 );
-			REQUIRE( fs.frames[0].hasChannel(Channel::Depth) );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
-			REQUIRE( fs.frames[1].hasChannel(Channel::Depth) );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
+			REQUIRE( fs->timestamp() == 10 );
+			REQUIRE( fs->frames.size() == 2 );
+			REQUIRE( fs->frames[0].hasChannel(Channel::Depth) );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
+			REQUIRE( fs->frames[1].hasChannel(Channel::Depth) );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
 
 			return true;
 		});
@@ -228,17 +228,17 @@ TEST_CASE( "Receiver generating onFrameSet" ) {
 		stream.post(spkt, pkt);
 
 		int count = 0;
-		auto h = receiver->onFrameSet([&count](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			REQUIRE( fs.timestamp() == 10 );
-			REQUIRE( fs.frames.size() == 2 );
-			REQUIRE( fs.frames[0].hasChannel(Channel::Depth) );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
-			REQUIRE( fs.frames[1].hasChannel(Channel::Depth) );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
-			REQUIRE( fs.frames[1].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
+			REQUIRE( fs->timestamp() == 10 );
+			REQUIRE( fs->frames.size() == 2 );
+			REQUIRE( fs->frames[0].hasChannel(Channel::Depth) );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
+			REQUIRE( fs->frames[1].hasChannel(Channel::Depth) );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Depth).rows == 720 );
+			REQUIRE( fs->frames[1].get<cv::cuda::GpuMat>(Channel::Depth).type() == CV_32F );
 
 			return true;
 		});
@@ -311,11 +311,11 @@ TEST_CASE( "Receiver sync bugs" ) {
 		int count = 0;
 		int64_t ts = 0;
 		bool haswrongchan = false;
-		auto h = receiver->onFrameSet([&count,&ts,&haswrongchan](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count,&ts,&haswrongchan](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			ts = fs.timestamp();
-			haswrongchan = fs.frames[0].hasChannel(Channel::ColourHighRes);
+			ts = fs->timestamp();
+			haswrongchan = fs->frames[0].hasChannel(Channel::ColourHighRes);
 
 			return true;
 		});
@@ -395,14 +395,14 @@ TEST_CASE( "Receiver non zero buffer" ) {
 		REQUIRE( r );
 
 		int count = 0;
-		auto h = receiver->onFrameSet([&count](ftl::rgbd::FrameSet &fs) {
+		auto h = receiver->onFrameSet([&count](std::shared_ptr<ftl::data::FrameSet> fs) {
 			++count;
 
-			REQUIRE( fs.timestamp() == 10 );
-			REQUIRE( fs.frames.size() == 1 );
-			REQUIRE( fs.frames[0].hasChannel(Channel::Colour) );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
-			REQUIRE( fs.frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
+			REQUIRE( fs->timestamp() == 10 );
+			REQUIRE( fs->frames.size() == 1 );
+			REQUIRE( fs->frames[0].hasChannel(Channel::Colour) );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).rows == 720 );
+			REQUIRE( fs->frames[0].get<cv::cuda::GpuMat>(Channel::Colour).type() == CV_8UC4 );
 
 			return true;
 		});
diff --git a/components/structures/include/ftl/data/new_frameset.hpp b/components/structures/include/ftl/data/new_frameset.hpp
index 95d80377c216cb78d0edc861339935e62d9be821..68d58ca8659c8377ffe96ae4157019ddabef75ae 100644
--- a/components/structures/include/ftl/data/new_frameset.hpp
+++ b/components/structures/include/ftl/data/new_frameset.hpp
@@ -99,6 +99,11 @@ class FrameSet : public ftl::data::Frame {
 	std::atomic<int> flags_;
 };
 
+class Generator {
+	public:
+	virtual ftl::Handle onFrameSet(const std::function<bool(std::shared_ptr<ftl::data::FrameSet>)> &)=0;
+};
+
 /**
  * Callback type for receiving video frames.
  */