Commit f65c38dc authored by Alexandre Courbot's avatar Alexandre Courbot Committed by Commit Bot

media/gpu/v4l2: add V4L2Device poller class

Each of the V4L2 decoder/encoder classes reimplement the same polling
mechanism, where a dedicated thread is used to poll() an open V4L2
device and signal the decoder/encoder thread when a buffer or event is
ready to be dequeued. This adds considerable amounts of code to each of
these classes and complicates them.

This CL replicates the polling feature into a dedicated class that is
self-contained, properly documented, and easy to understand. This class
is not intended to be used directly: instead, V4L2Device now has an
EnablePolling() method that will invoke a callback on the client's
sequence whenever an event or buffer is ready.

This approach considerably simplifies the client's code, which on top of
not having to reimplement the polling functionality, also doesn't need
to guess when it should schedule polling: V4L2Device has all the
information it needs to make this decision when queueing or dequeueing a
buffer.

Bug: 1003223
Test: vdatests passing on Kevin.
Change-Id: I6b8619a89132c34c7424d83613c7681aedfc46ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1799652
Commit-Queue: Alexandre Courbot <acourbot@chromium.org>
Reviewed-by: default avatarChih-Yu Huang <akahuang@chromium.org>
Reviewed-by: default avatarHirokazu Honda <hiroh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#700122}
parent 489221aa
...@@ -33,6 +33,8 @@ source_set("v4l2") { ...@@ -33,6 +33,8 @@ source_set("v4l2") {
"v4l2_decode_surface_handler.h", "v4l2_decode_surface_handler.h",
"v4l2_device.cc", "v4l2_device.cc",
"v4l2_device.h", "v4l2_device.h",
"v4l2_device_poller.cc",
"v4l2_device_poller.h",
"v4l2_h264_accelerator.cc", "v4l2_h264_accelerator.cc",
"v4l2_h264_accelerator.h", "v4l2_h264_accelerator.h",
"v4l2_h264_accelerator_legacy.cc", "v4l2_h264_accelerator_legacy.cc",
......
...@@ -903,6 +903,8 @@ bool V4L2Queue::QueueBuffer(struct v4l2_buffer* v4l2_buffer) { ...@@ -903,6 +903,8 @@ bool V4L2Queue::QueueBuffer(struct v4l2_buffer* v4l2_buffer) {
auto inserted = queued_buffers_.emplace(v4l2_buffer->index); auto inserted = queued_buffers_.emplace(v4l2_buffer->index);
DCHECK_EQ(inserted.second, true); DCHECK_EQ(inserted.second, true);
device_->SchedulePoll();
return true; return true;
} }
...@@ -946,6 +948,9 @@ std::pair<bool, V4L2ReadableBufferRef> V4L2Queue::DequeueBuffer() { ...@@ -946,6 +948,9 @@ std::pair<bool, V4L2ReadableBufferRef> V4L2Queue::DequeueBuffer() {
DCHECK(it != queued_buffers_.end()); DCHECK(it != queued_buffers_.end());
queued_buffers_.erase(*it); queued_buffers_.erase(*it);
if (QueuedBuffersCount() > 0)
device_->SchedulePoll();
DCHECK(free_buffers_); DCHECK(free_buffers_);
return std::make_pair(true, return std::make_pair(true,
V4L2BufferRefFactory::CreateReadableRef( V4L2BufferRefFactory::CreateReadableRef(
...@@ -1759,4 +1764,37 @@ V4L2Device::EnumerateSupportedEncodeProfiles() { ...@@ -1759,4 +1764,37 @@ V4L2Device::EnumerateSupportedEncodeProfiles() {
return profiles; return profiles;
} }
bool V4L2Device::StartPolling(V4L2DevicePoller::EventCallback event_callback,
base::RepeatingClosure error_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
if (!device_poller_) {
device_poller_ =
std::make_unique<V4L2DevicePoller>(this, "V4L2DeviceThreadPoller");
}
bool ret = device_poller_->StartPolling(std::move(event_callback),
std::move(error_callback));
if (!ret)
device_poller_ = nullptr;
return ret;
}
bool V4L2Device::StopPolling() {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
return !device_poller_ || device_poller_->StopPolling();
}
void V4L2Device::SchedulePoll() {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
if (!device_poller_ || !device_poller_->IsPolling())
return;
device_poller_->SchedulePoll();
}
} // namespace media } // namespace media
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "media/base/video_frame.h" #include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h" #include "media/base/video_frame_layout.h"
#include "media/gpu/media_gpu_export.h" #include "media/gpu/media_gpu_export.h"
#include "media/gpu/v4l2/v4l2_device_poller.h"
#include "media/video/video_decode_accelerator.h" #include "media/video/video_decode_accelerator.h"
#include "media/video/video_encode_accelerator.h" #include "media/video/video_encode_accelerator.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
...@@ -539,6 +540,19 @@ class MEDIA_GPU_EXPORT V4L2Device ...@@ -539,6 +540,19 @@ class MEDIA_GPU_EXPORT V4L2Device
virtual bool IsJpegDecodingSupported() = 0; virtual bool IsJpegDecodingSupported() = 0;
virtual bool IsJpegEncodingSupported() = 0; virtual bool IsJpegEncodingSupported() = 0;
// Start polling on this V4L2Device. |event_callback| will be posted to
// the caller's sequence if a buffer is ready to be dequeued and/or a V4L2
// event has been posted. |error_callback| will be posted to the client's
// sequence if a polling error has occurred.
bool StartPolling(V4L2DevicePoller::EventCallback event_callback,
base::RepeatingClosure error_callback);
// Stop polling this V4L2Device if polling was active. No new events will
// be posted after this method has returned.
bool StopPolling();
// Schedule a polling event if polling is enabled. This method is intended
// to be called from V4L2Queue, clients should not need to call it directly.
void SchedulePoll();
protected: protected:
friend class base::RefCountedThreadSafe<V4L2Device>; friend class base::RefCountedThreadSafe<V4L2Device>;
V4L2Device(); V4L2Device();
...@@ -563,6 +577,10 @@ class MEDIA_GPU_EXPORT V4L2Device ...@@ -563,6 +577,10 @@ class MEDIA_GPU_EXPORT V4L2Device
// in queues_. // in queues_.
void OnQueueDestroyed(v4l2_buf_type buf_type); void OnQueueDestroyed(v4l2_buf_type buf_type);
// Used if EnablePolling() is called to signal the user that an event
// happened or a buffer is ready to be dequeued.
std::unique_ptr<V4L2DevicePoller> device_poller_;
SEQUENCE_CHECKER(client_sequence_checker_); SEQUENCE_CHECKER(client_sequence_checker_);
}; };
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/v4l2/v4l2_device_poller.h"
#include <string>
#include "base/bind.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_checker.h"
#include "media/gpu/macros.h"
#include "media/gpu/v4l2/v4l2_device.h"
namespace media {
V4L2DevicePoller::V4L2DevicePoller(V4L2Device* const device,
const std::string& thread_name)
: device_(device),
poll_thread_(std::move(thread_name)),
trigger_poll_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED),
stop_polling_(false) {
DETACH_FROM_SEQUENCE(client_sequence_checker_);
}
V4L2DevicePoller::~V4L2DevicePoller() {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
StopPolling();
}
bool V4L2DevicePoller::StartPolling(EventCallback event_callback,
base::RepeatingClosure error_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
if (IsPolling())
return true;
client_task_runner_ = base::SequencedTaskRunnerHandle::Get();
error_callback_ = error_callback;
if (!poll_thread_.Start()) {
VLOGF(1) << "Failed to start device poll thread";
return false;
}
event_callback_ = std::move(event_callback);
stop_polling_.store(false);
poll_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&V4L2DevicePoller::DevicePollTask,
base::Unretained(this)));
SchedulePoll();
return true;
}
bool V4L2DevicePoller::StopPolling() {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
if (!IsPolling())
return true;
stop_polling_.store(true);
trigger_poll_.Signal();
if (!device_->SetDevicePollInterrupt()) {
VLOGF(1) << "Failed to interrupt device poll.";
return false;
}
DVLOGF(3) << "Stop device poll thread";
poll_thread_.Stop();
if (!device_->ClearDevicePollInterrupt()) {
VLOGF(1) << "Failed to clear interrupting device poll.";
return false;
}
return true;
}
bool V4L2DevicePoller::IsPolling() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
return poll_thread_.IsRunning();
}
void V4L2DevicePoller::SchedulePoll() {
DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
// A call to DevicePollTask() will be posted when we actually start polling.
if (!IsPolling())
return;
trigger_poll_.Signal();
}
void V4L2DevicePoller::DevicePollTask() {
DCHECK(poll_thread_.task_runner()->RunsTasksInCurrentSequence());
while (true) {
trigger_poll_.Wait();
if (stop_polling_)
break;
bool event_pending = false;
if (!device_->Poll(true, &event_pending)) {
client_task_runner_->PostTask(FROM_HERE, error_callback_);
return;
}
client_task_runner_->PostTask(FROM_HERE,
base::Bind(event_callback_, event_pending));
}
}
} // namespace media
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_GPU_V4L2_V4L2_DEVICE_POLLER_H_
#define MEDIA_GPU_V4L2_V4L2_DEVICE_POLLER_H_
#include <atomic>
#include "base/callback_forward.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
namespace media {
class V4L2Device;
// Allows a client to poll() on a given V4L2Device and be signaled when
// a buffer is ready to be dequeued or a V4L2 event has been received. Polling
// is done on a dedicated thread, and notifications are delivered in the form of
// a callback to the listener's sequence.
//
// All the methods of this class (with the exception of the constructor) must be
// called from the same sequence.
//
// Note that the service callback may also be called when no particular event
// occurred due to the way poll() works. It is the responsibility of the caller
// to call SchedulePoll() again if there may still be pending events.
class V4L2DevicePoller {
public:
// Callback to be called when buffer ready/V4L2 event has potentially been
// polled. |event| is set if a V4L2 event has been detected.
using EventCallback = base::RepeatingCallback<void(bool event)>;
// Create a poller for |device|, using a thread named |thread_name|.
// Notification won't start until |StartPolling()| is called.
V4L2DevicePoller(V4L2Device* const device, const std::string& thread_name);
~V4L2DevicePoller();
// Starts polling. |event_callback| will be posted on the caller's sequence
// every time an event occurs. The client is then responsible for consuming
// all pending events in that callback. If new events may still happen after
// the callback has run, the client must call |SchedulePoll()| again in order
// to be notified for them.
//
// If an error occurs during polling, |error_callback| will be posted on the
// caller's sequence.
bool StartPolling(EventCallback event_callback,
base::RepeatingClosure error_callback);
// Stop polling and stop the thread. The poller won't post any new event to
// the caller's sequence after this method has returned.
bool StopPolling();
// Returns true if currently polling, false otherwise.
bool IsPolling() const;
// Attempts polling the V4L2 device. This method should be called whenever
// doing something that may trigger an event of interest (buffer dequeue or
// V4L2 event), for instance queueing a buffer. In the absence of a pending
// event, poll() will return immediately and the service callback will be
// posted to the caller's sequence. The client is then responsible for calling
// this method again when it is interested in receiving events.
void SchedulePoll();
private:
// Perform a poll() on |device_| and post either |service_task_| or
// |error_callback_| on the client's sequence when poll() returns.
void DevicePollTask();
// V4L2 device we are polling.
V4L2Device* const device_;
// Thread on which polling is done.
base::Thread poll_thread_;
// Callback to post to the client's sequence when an event occurs.
EventCallback event_callback_;
// Closure to post to the client's sequence when an error occurs.
base::RepeatingClosure error_callback_;
// Client sequence's task runner, where closures are posted.
scoped_refptr<base::SequencedTaskRunner> client_task_runner_;
// Since poll() returns immediately if no buffers have been queued, we cannot
// rely on it to pause the polling thread until an event occurs. Instead,
// the polling thread will wait on this WaitableEvent (signaled by
// |SchedulePoll| before calling poll(), so we only call it when we are
// actually waiting for an event.
base::WaitableEvent trigger_poll_;
// Set to true when we wish to stop polling, instructing the poller thread
// to break its loop.
std::atomic_bool stop_polling_;
SEQUENCE_CHECKER(client_sequence_checker_);
};
} // namespace media
#endif // MEDIA_GPU_V4L2_V4L2_DEVICE_POLLER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment