diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build
index 1a71ce5a..37c4435a 100644
--- a/include/libcamera/base/meson.build
+++ b/include/libcamera/base/meson.build
@@ -13,6 +13,7 @@ libcamera_base_headers = files([
     'flags.h',
     'log.h',
     'message.h',
+    'mutex.h',
     'object.h',
     'private.h',
     'semaphore.h',
diff --git a/include/libcamera/base/mutex.h b/include/libcamera/base/mutex.h
new file mode 100644
index 00000000..2792551c
--- /dev/null
+++ b/include/libcamera/base/mutex.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * thread.h - Thread support
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+
+#include <libcamera/base/thread_annotations.h>
+
+namespace libcamera {
+
+class ConditionVariable;
+
+#ifndef __DOXYGEN__
+class LIBCAMERA_TSA_SCOPED_CAPABILITY MutexLocker;
+#else
+class MutexLocker;
+#endif /* __DOXYGEN__ */
+
+/* \todo using Mutex = std::mutex if lib++ is used. */
+
+#ifndef __DOXYGEN__
+class LIBCAMERA_TSA_CAPABILITY("mutex") Mutex final
+#else
+class Mutex final
+#endif /* __DOXYGEN__ */
+{
+public:
+	constexpr Mutex()
+	{
+	}
+
+	void lock() LIBCAMERA_TSA_ACQUIRE()
+	{
+		mutex_.lock();
+	}
+
+	void unlock() LIBCAMERA_TSA_RELEASE()
+	{
+		mutex_.unlock();
+	}
+
+private:
+	friend MutexLocker;
+
+	std::mutex mutex_;
+};
+
+#ifndef __DOXYGEN__
+class LIBCAMERA_TSA_SCOPED_CAPABILITY MutexLocker final
+#else
+class MutexLocker final
+#endif /* __DOXYGEN__ */
+{
+public:
+	explicit MutexLocker(Mutex &mutex) LIBCAMERA_TSA_ACQUIRE(mutex)
+		: lock_(mutex.mutex_)
+	{
+	}
+
+	MutexLocker(Mutex &mutex, std::defer_lock_t t) noexcept LIBCAMERA_TSA_EXCLUDES(mutex)
+		: lock_(mutex.mutex_, t)
+	{
+	}
+
+	~MutexLocker() LIBCAMERA_TSA_RELEASE()
+	{
+	}
+
+	void lock() LIBCAMERA_TSA_ACQUIRE()
+	{
+		lock_.lock();
+	}
+
+	bool try_lock() LIBCAMERA_TSA_TRY_ACQUIRE(true)
+	{
+		return lock_.try_lock();
+	}
+
+	void unlock() LIBCAMERA_TSA_RELEASE()
+	{
+		lock_.unlock();
+	}
+
+private:
+	friend ConditionVariable;
+
+	std::unique_lock<std::mutex> lock_;
+};
+
+class ConditionVariable final
+{
+public:
+	ConditionVariable()
+	{
+	}
+
+	void notify_one() noexcept
+	{
+		cv_.notify_one();
+	}
+
+	void notify_all() noexcept
+	{
+		cv_.notify_all();
+	}
+
+	template<class Predicate>
+	void wait(MutexLocker &locker, Predicate stopWaiting)
+	{
+		cv_.wait(locker.lock_, stopWaiting);
+	}
+
+	template<class Rep, class Period, class Predicate>
+	bool wait_for(MutexLocker &locker,
+		      const std::chrono::duration<Rep, Period> &relTime,
+		      Predicate stopWaiting)
+	{
+		return cv_.wait_for(locker.lock_, relTime, stopWaiting);
+	}
+
+private:
+	std::condition_variable cv_;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/base/thread.h b/include/libcamera/base/thread.h
index 1ebf8363..44678c34 100644
--- a/include/libcamera/base/thread.h
+++ b/include/libcamera/base/thread.h
@@ -7,15 +7,14 @@
 
 #pragma once
 
-#include <condition_variable>
 #include <memory>
-#include <mutex>
 #include <sys/types.h>
 #include <thread>
 
 #include <libcamera/base/private.h>
 
 #include <libcamera/base/message.h>
+#include <libcamera/base/mutex.h>
 #include <libcamera/base/signal.h>
 #include <libcamera/base/utils.h>
 
@@ -27,10 +26,6 @@ class Object;
 class ThreadData;
 class ThreadMain;
 
-using ConditionVariable = std::condition_variable;
-using Mutex = std::mutex;
-using MutexLocker = std::unique_lock<std::mutex>;
-
 class Thread
 {
 public:
diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
index 05fed7ac..b93b8505 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -11,6 +11,7 @@ libcamera_base_sources = files([
     'flags.cpp',
     'log.cpp',
     'message.cpp',
+    'mutex.cpp',
     'object.cpp',
     'semaphore.cpp',
     'signal.cpp',
diff --git a/src/libcamera/base/mutex.cpp b/src/libcamera/base/mutex.cpp
new file mode 100644
index 00000000..327b90c0
--- /dev/null
+++ b/src/libcamera/base/mutex.cpp
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * mutex.cpp - Mutex classes with clang thread safety annotation
+ */
+
+#include <libcamera/base/mutex.h>
+
+/**
+ * \file base/mutex.h
+ * \brief Mutex classes with clang thread safety annotation
+ */
+
+namespace libcamera {
+
+/**
+ * \class Mutex
+ * \brief std::mutex wrapper with clang thread safety annotation
+ */
+
+/**
+ * \fn Mutex::Mutex()
+ * \brief Construct a Mutex instance
+ */
+
+/**
+ * \fn Mutex::lock()
+ * \brief Locks std::mutex
+ */
+
+/**
+ * \fn Mutex::unlock()
+ * \brief Unlocks std::mutex
+ */
+
+/**
+ * \class MutexLocker
+ * \brief std::unique_lock wrapper with clang thread safety annotation
+ */
+
+/**
+ * \fn MutexLocker::MutexLocker(Mutex &mutex)
+ * \brief Constructs MutexLocker with \a mutex as as the associated mutex and
+ * locks \a mutex
+ * \param[in] mutex Mutex to be locked/unlocked by MutexLocker
+ *
+ * This blocks until \a mutex is acquired.
+ */
+
+/**
+ * \fn MutexLocker::MutexLocker(Mutex &mutex, std::defer_lock_t t)
+ * \brief Constructs MutexLocker with \a mutex as as the associated mutex but
+ * does not lock \a mutex
+ * \param[in] mutex Mutex to be locked/unlocked by MutexLocker
+ * \param[in] t mark to specify this constructor is called1
+ *
+ */
+
+/**
+ * \fn MutexLocker::~MutexLocker()
+ * \brief Destroys MutexLocker and unlock the associated mutex if it is locked
+ */
+
+/**
+ * \fn MutexLocker::lock()
+ * \brief Locks the associated mutex
+ */
+
+/**
+ * \fn MutexLocker::try_lock()
+ * \brief Tries to lock the associated mutex
+ * \return True if the ownership of the mutex has been acquired successfully,
+    false otherwise.
+ */
+
+/**
+ * \fn MutexLocker::unlock()
+ * \brief Unlocks the associated mutex
+ */
+
+/**
+ * \class ConditionVariable
+ * \brief std::condition_variable wrapper with clang thread safety annotation
+ */
+
+/**
+ * \fn ConditionVariable::ConditionVariable()
+ * \brief Constructs ConditionVariable
+ */
+
+/**
+ * \fn ConditionVariable::notify_one()
+ * \brief Delegates std::condition_variable::notify_one()
+ */
+
+/**
+ * \fn ConditionVariable::notify_all()
+ * \brief Delegates std::condition_variable::notify_all()
+ */
+
+/**
+ * \fn ConditionVariable::wait(MutexLocker& locker, Predicate stopWaiting)
+ * \brief Call std::condition_variable::wait() with \a locker and \a stopWaiting
+ * \param[in] locker MutexLocker that is locked/unlocked during wait()
+ * \param[in] stopWaiting The predicate to be satisfied
+ */
+
+/**
+ * \fn ConditionVariable::wait_for(MutexLocker& locker,
+                                   const std::chrono::duration<Rep, Period> &relTime,
+				   Predicate stopWaiting)
+ * \brief Call std::condition_variable::wait_for() with \a locker, \a relTime,
+   \a stopWaiting
+ * \param[in] locker MutexLocker that is locked/unlocked during wait()
+ * \param[in] relTime std::chrono::duration representing the maximum time to
+   spend waiting
+ * \param[in] stopWaiting The predicate to be satisfied
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
index b893135f..b2043b7e 100644
--- a/src/libcamera/base/thread.cpp
+++ b/src/libcamera/base/thread.cpp
@@ -204,21 +204,6 @@ ThreadData *ThreadData::current()
 	return data;
 }
 
-/**
- * \typedef ConditionVariable
- * \brief An alias for std::condition_variable
- */
-
-/**
- * \typedef Mutex
- * \brief An alias for std::mutex
- */
-
-/**
- * \typedef MutexLocker
- * \brief An alias for std::unique_lock<std::mutex>
- */
-
 /**
  * \class Thread
  * \brief A thread of execution
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 040954dd..23be995d 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -14,7 +14,8 @@
 #include <sys/types.h>
 #include <vector>
 
-#include <libcamera/base/thread.h>
+#include <libcamera/base/mutex.h>
+#include <libcamera/base/thread_annotations.h>
 
 #include <libcamera/camera.h>
 
@@ -59,7 +60,7 @@ private:
 	int vidioc_querybuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
 	int vidioc_qbuf(V4L2CameraFile *file, struct v4l2_buffer *arg);
 	int vidioc_dqbuf(V4L2CameraFile *file, struct v4l2_buffer *arg,
-			 libcamera::Mutex *lock);
+			 libcamera::Mutex *lock) LIBCAMERA_TSA_REQUIRES(*lock);
 	int vidioc_streamon(V4L2CameraFile *file, int *arg);
 	int vidioc_streamoff(V4L2CameraFile *file, int *arg);
 
