blob: b319cdbe8771cc5b14e7887f5bd056d8e21515e9 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#ifndef MEDIA_BASE_CODEC_WORKER_IMPL_H_
#define MEDIA_BASE_CODEC_WORKER_IMPL_H_
#include <cstring>
#include "base/functional/bind.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
// This file contains guts of thread wrappers for libvpx and libaom to help
// avoid code duplication.
namespace media {
// Template class for returning codec worker implementations
template <class WorkerInterface,
class WorkerImpl,
class Worker,
class WorkerStatus,
WorkerStatus StatusNotOk,
WorkerStatus StatusOk,
WorkerStatus StatusWork>
class CodecWorkerImpl {
public:
// Returns the codec worker interface.
static constexpr WorkerInterface GetCodecWorkerInterface() {
return WorkerInterface{.init = Init,
.reset = Reset,
.sync = Sync,
.launch = Launch,
.execute = Execute,
.end = End};
}
private:
CodecWorkerImpl()
: thread_("CodecWorker"),
event_(base::WaitableEvent::ResetPolicy::AUTOMATIC) {
thread_.Start();
}
static CodecWorkerImpl* GetImpl(Worker* const worker) {
return reinterpret_cast<CodecWorkerImpl*>(worker->impl_);
}
static void Init(Worker* const worker) {
memset(worker, 0, sizeof(*worker));
worker->status_ = StatusNotOk;
worker->impl_ = nullptr;
}
static void Execute(Worker* const worker) {
if (worker->hook) {
worker->had_error |= !worker->hook(worker->data1, worker->data2);
}
}
void ChangeStateImpl(Worker* const worker, WorkerStatus new_status) {
// Await work complete if necessary before setting the new state.
bool deplete_work = false;
{
base::AutoLock lock(mutex_);
deplete_work = worker->status_ == StatusWork;
if (!deplete_work) {
worker->status_ = new_status;
}
}
if (deplete_work) {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce([](base::WaitableEvent* event) { event->Signal(); },
// Unretained is safe because End waits until
// work is complete (see `deplete_work` above).
base::Unretained(&event_)));
// Sequences calling into libvpx & libaom with a threading configuration
// need to be annotated with base::MayBlock. This is because the threading
// interface requires Launch/Sync/End to deplete scheduled work before
// completion.
base::ScopedAllowBaseSyncPrimitives allow_wait;
event_.Wait();
base::AutoLock lock(mutex_);
worker->status_ = new_status;
}
// Schedule new work.
if (new_status == StatusWork) {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CodecWorkerImpl::ExecuteOnTaskRunner,
// Unretained is safe because End waits until
// work is complete (see `deplete_work` above).
base::Unretained(this), base::Unretained(worker)));
}
}
void ExecuteOnTaskRunner(Worker* worker) {
base::AutoLock lock(mutex_);
CHECK_EQ(worker->status_, StatusWork);
Execute(worker);
worker->status_ = StatusOk;
}
static void ChangeState(Worker* const worker, WorkerStatus new_status) {
// No-op when attempting to change state on a thread that didn't come up
// with VpxWorkerReset.
if (!worker->impl_) {
return;
}
CodecWorkerImpl* impl = GetImpl(worker);
impl->ChangeStateImpl(worker, new_status);
}
static int Sync(Worker* const worker) {
ChangeState(worker, StatusOk);
return !worker->had_error;
}
static int Reset(Worker* const worker) {
if (worker->status_ < StatusOk) {
worker->had_error = false;
worker->impl_ = reinterpret_cast<WorkerImpl*>(new CodecWorkerImpl());
worker->status_ = StatusOk;
return 1;
}
int ok = Sync(worker);
worker->had_error = false;
CHECK(!ok || worker->status_ == StatusOk);
return ok;
}
static void Launch(Worker* const worker) { ChangeState(worker, StatusWork); }
static void End(Worker* const worker) {
if (worker->impl_) {
ChangeState(worker, StatusNotOk);
CodecWorkerImpl* impl = GetImpl(worker);
base::ScopedAllowBaseSyncPrimitives allow_wait;
delete impl;
worker->impl_ = nullptr;
}
}
// Protects `Worker::status_`. The other attributes in `Worker` are expected
// to be updated in isolation.
base::Lock mutex_;
// TODO(crbug.com/41486982): consider using sequenced task runner instead of
// dedicated threads.
base::Thread thread_;
// Avoids creating a WaitableEvent on stack when work needs to be depleted.
// Sequences calling into libvpx & libaom with a threading configuration need
// to be annotated with base::MayBlock. This is because the threading
// interface requires Launch/Sync/End to deplete scheduled work before
// completion.
base::WaitableEvent event_;
};
} // namespace media
#endif // MEDIA_BASE_CODEC_WORKER_IMPL_H_