diff --git a/src/android/camera_buffer.h b/src/android/camera_buffer.h
new file mode 100644
index 000000000000..00b061502d6e
--- /dev/null
+++ b/src/android/camera_buffer.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * camera_buffer.h - Frame buffer handling interface definition
+ */
+#ifndef __ANDROID_CAMERA_BUFFER_H__
+#define __ANDROID_CAMERA_BUFFER_H__
+
+#include <hardware/camera3.h>
+
+#include <libcamera/internal/buffer.h>
+
+class CameraBuffer : public libcamera::MappedBuffer
+{
+public:
+	CameraBuffer(const buffer_handle_t camera3buffer, int flags);
+	~CameraBuffer();
+};
+
+#endif /* __ANDROID_CAMERA_BUFFER_H__ */
diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index 16cb8c6d2b84..f47f28b8c7ee 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -257,36 +257,6 @@ void sortCamera3StreamConfigs(std::vector<Camera3StreamConfig> &unsortedConfigs,
 
 } /* namespace */
 
-MappedCamera3Buffer::MappedCamera3Buffer(const buffer_handle_t camera3buffer,
-					 int flags)
-{
-	maps_.reserve(camera3buffer->numFds);
-	error_ = 0;
-
-	for (int i = 0; i < camera3buffer->numFds; i++) {
-		if (camera3buffer->data[i] == -1)
-			continue;
-
-		off_t length = lseek(camera3buffer->data[i], 0, SEEK_END);
-		if (length < 0) {
-			error_ = -errno;
-			LOG(HAL, Error) << "Failed to query plane length";
-			break;
-		}
-
-		void *address = mmap(nullptr, length, flags, MAP_SHARED,
-				     camera3buffer->data[i], 0);
-		if (address == MAP_FAILED) {
-			error_ = -errno;
-			LOG(HAL, Error) << "Failed to mmap plane";
-			break;
-		}
-
-		maps_.emplace_back(static_cast<uint8_t *>(address),
-				   static_cast<size_t>(length));
-	}
-}
-
 /*
  * \struct Camera3RequestDescriptor
  *
@@ -1892,8 +1862,8 @@ void CameraDevice::requestComplete(Request *request)
 		 * separate thread.
 		 */
 
-		MappedCamera3Buffer mapped(*descriptor->buffers_[i].buffer,
-					   PROT_READ | PROT_WRITE);
+		CameraBuffer mapped(*descriptor->buffers_[i].buffer,
+				    PROT_READ | PROT_WRITE);
 		if (!mapped.isValid()) {
 			LOG(HAL, Error) << "Failed to mmap android blob buffer";
 			continue;
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 9cbfcad38433..e6c192c2100b 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -24,17 +24,12 @@
 #include "libcamera/internal/log.h"
 #include "libcamera/internal/message.h"
 
+#include "camera_buffer.h"
 #include "camera_metadata.h"
 #include "camera_stream.h"
 #include "camera_worker.h"
 #include "jpeg/encoder.h"
 
-class MappedCamera3Buffer : public libcamera::MappedBuffer
-{
-public:
-	MappedCamera3Buffer(const buffer_handle_t camera3buffer, int flags);
-};
-
 class CameraDevice : protected libcamera::Loggable
 {
 public:
diff --git a/src/android/meson.build b/src/android/meson.build
index 9719c42b65c6..7004d32d4b23 100644
--- a/src/android/meson.build
+++ b/src/android/meson.build
@@ -52,6 +52,8 @@ android_hal_sources = files([
     'yuv/post_processor_yuv.cpp'
 ])
 
+subdir('mm')
+
 android_camera_metadata_sources = files([
     'metadata/camera_metadata.c',
 ])
diff --git a/src/android/mm/generic_camera_buffer.cpp b/src/android/mm/generic_camera_buffer.cpp
new file mode 100644
index 000000000000..bd9152cf9a16
--- /dev/null
+++ b/src/android/mm/generic_camera_buffer.cpp
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * generic_camera_buffer.cpp - Generic Android frame buffer backend
+ */
+
+#include "../camera_buffer.h"
+
+#include "libcamera/internal/log.h"
+
+using namespace libcamera;
+
+LOG_DECLARE_CATEGORY(HAL)
+
+CameraBuffer::CameraBuffer(const buffer_handle_t camera3buffer, int flags)
+{
+	maps_.reserve(camera3buffer->numFds);
+	error_ = 0;
+
+	for (int i = 0; i < camera3buffer->numFds; i++) {
+		if (camera3buffer->data[i] == -1)
+			continue;
+
+		off_t length = lseek(camera3buffer->data[i], 0, SEEK_END);
+		if (length < 0) {
+			error_ = -errno;
+			LOG(HAL, Error) << "Failed to query plane length";
+			break;
+		}
+
+		void *address = mmap(nullptr, length, flags, MAP_SHARED,
+				     camera3buffer->data[i], 0);
+		if (address == MAP_FAILED) {
+			error_ = -errno;
+			LOG(HAL, Error) << "Failed to mmap plane";
+			break;
+		}
+
+		maps_.emplace_back(static_cast<uint8_t *>(address),
+				   static_cast<size_t>(length));
+	}
+}
+
+CameraBuffer::~CameraBuffer()
+{
+}
diff --git a/src/android/mm/meson.build b/src/android/mm/meson.build
new file mode 100644
index 000000000000..97f83f2a7380
--- /dev/null
+++ b/src/android/mm/meson.build
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: CC0-1.0
+
+platform = get_option('android_platform')
+if platform == 'generic'
+    android_hal_sources += files(['generic_camera_buffer.cpp'])
+endif
