diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 559ffc617..554e81f8d 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -11,6 +11,7 @@
 #include <errno.h>
 #include <numeric>
 #include <set>
+#include <stddef.h>
 #include <string.h>
 #include <sys/mman.h>
 #include <unistd.h>
@@ -39,6 +40,64 @@ using namespace std::literals::chrono_literals;
 
 LOG_DECLARE_CATEGORY(V4L2Compat)
 
+namespace {
+
+/*
+ * On 32-bit platforms the V4L2 compatibility layer is built with a 32-bit
+ * time_t (see src/v4l2/meson.build), while applications are commonly built
+ * with _TIME_BITS=64. struct v4l2_buffer embeds a struct timeval, so the two
+ * disagree on the structure size, and thus on the buffer ioctl numbers. Only
+ * the timestamp differs, the fields before and after it are the same.
+ */
+struct v4l2_buffer_time64 {
+	__u8 head[offsetof(struct v4l2_buffer, timestamp)];
+	struct {
+		long long tv_sec;
+		long long tv_usec;
+	} timestamp;
+	__u8 tail[sizeof(struct v4l2_buffer) - offsetof(struct v4l2_buffer, timecode)];
+};
+
+/*
+ * Return the native request for a 64-bit time_t buffer ioctl, or 0 if the
+ * request is not one or both layouts are the same.
+ */
+unsigned int bufferRequestFromTime64(unsigned int request)
+{
+	if (sizeof(struct v4l2_buffer_time64) == sizeof(struct v4l2_buffer))
+		return 0;
+
+	switch (request) {
+	case _IOWR('V', 9, struct v4l2_buffer_time64):	/* VIDIOC_QUERYBUF */
+	case _IOWR('V', 15, struct v4l2_buffer_time64):	/* VIDIOC_QBUF */
+	case _IOWR('V', 17, struct v4l2_buffer_time64):	/* VIDIOC_DQBUF */
+	case _IOWR('V', 93, struct v4l2_buffer_time64):	/* VIDIOC_PREPARE_BUF */
+		return _IOWR('V', _IOC_NR(request), struct v4l2_buffer);
+	default:
+		return 0;
+	}
+}
+
+void bufferFromTime64(const struct v4l2_buffer_time64 &src,
+		      struct v4l2_buffer *dst)
+{
+	memcpy(dst, src.head, sizeof(src.head));
+	dst->timestamp.tv_sec = src.timestamp.tv_sec;
+	dst->timestamp.tv_usec = src.timestamp.tv_usec;
+	memcpy(&dst->timecode, src.tail, sizeof(src.tail));
+}
+
+void bufferToTime64(const struct v4l2_buffer &src,
+		    struct v4l2_buffer_time64 *dst)
+{
+	memcpy(dst->head, &src, sizeof(dst->head));
+	dst->timestamp.tv_sec = src.timestamp.tv_sec;
+	dst->timestamp.tv_usec = src.timestamp.tv_usec;
+	memcpy(dst->tail, &src.timecode, sizeof(dst->tail));
+}
+
+} /* namespace */
+
 V4L2CameraProxy::V4L2CameraProxy(unsigned int index,
 				 std::shared_ptr<Camera> camera)
 	: refcount_(0), index_(index), bufferCount_(0), currentBuf_(0),
@@ -873,6 +932,20 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
 		return -1;
 	}
 
+	/*
+	 * Translate buffer ioctls from applications built with a 64-bit
+	 * time_t to the native struct v4l2_buffer layout.
+	 */
+	struct v4l2_buffer_time64 *buf64 = nullptr;
+	struct v4l2_buffer buf = {};
+	unsigned int bufRequest = bufferRequestFromTime64(request);
+	if (bufRequest) {
+		buf64 = static_cast<struct v4l2_buffer_time64 *>(arg);
+		bufferFromTime64(*buf64, &buf);
+		request = bufRequest;
+		arg = &buf;
+	}
+
 	if (supportedIoctls_.find(request) == supportedIoctls_.end()) {
 		errno = ENOTTY;
 		return -1;
@@ -955,6 +1028,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
 		return -1;
 	}
 
+	if (buf64)
+		bufferToTime64(buf, buf64);
+
 	return ret;
 }
 
