blob: 9a35a8be0ac7a38b936afc0a9965857b72fd964b [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "codec_factory_app.h"
#include "codec_factory_impl.h"
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <lib/fsl/io/device_watcher.h>
#include <lib/svc/cpp/services.h>
#include <trace-provider/provider.h>
#include <zircon/device/media-codec.h>
#include <zircon/status.h>
#include <fcntl.h>
namespace codec_factory {
namespace {
constexpr char kDeviceClass[] = "/dev/class/media-codec";
} // namespace
CodecFactoryApp::CodecFactoryApp(async::Loop* loop) : loop_(loop) {
// TODO(dustingreen): Determine if this is useful and if we're holding it
// right.
trace::TraceProvider trace_provider(loop_->dispatcher());
// We pump |loop| in here, so it's important that
// component::StartupContext::CreateFromStartupInfo() happen after
// DiscoverMediaCodecDrivers(), else the pumping of the loop will drop the
// incoming request for CodecFactory before AddServiceForName() below has had
// a chance to register for it.
DiscoverMediaCodecDriversAndListenForMoreAsync();
// We _rely_ on the driver to either fail the channel or send OnCodecList().
// We don't set a timeout here because under different conditions this could
// take different duration.
zx_status_t run_result;
do {
run_result = loop_->Run(zx::time::infinite(), true);
} while (run_result == ZX_OK && !existing_devices_discovered_);
if (run_result != ZX_OK) {
// ignore/skip the driver that failed the channel already
// The ~codec_factory takes care of un-binding.
FXL_LOG(ERROR) << "loop failed: " << zx_status_get_string(run_result);
return;
}
FXL_LOG(INFO)
<< "discovery of all pre-existing devices done - serving CodecFactory";
// We delay doing this until we're completely ready to add services, because
// CreateFromStartupInfo() binds to |loop| implicitly, so we don't want any
// pumping of |loop| up to this point to drop service connection requests.
//
// It's fine that AddServiceForName() happens after CreateFromStartupInfo()
// only because |loop| doesn't have a separate thread, and the current thread
// won't pump |loop| until after AddServiceForName() is also done.
startup_context_ = component::StartupContext::CreateFromStartupInfo();
startup_context_->outgoing().deprecated_services()->AddServiceForName(
[this](zx::channel request) {
// The CodecFactoryImpl is self-owned and will self-delete when the
// channel closes or an error occurs.
CodecFactoryImpl::CreateSelfOwned(this, startup_context_.get(),
std::move(request));
},
fuchsia::mediacodec::CodecFactory::Name_);
}
const fuchsia::mediacodec::CodecFactoryPtr* CodecFactoryApp::FindHwDecoder(
fit::function<bool(const fuchsia::mediacodec::CodecDescription&)>
is_match) {
auto iter = std::find_if(
hw_codecs_.begin(), hw_codecs_.end(),
[&is_match](const std::unique_ptr<CodecListEntry>& entry) -> bool {
return is_match(entry->description);
});
if (iter == hw_codecs_.end()) {
return nullptr;
}
return (*iter)->factory.get();
}
void CodecFactoryApp::DiscoverMediaCodecDriversAndListenForMoreAsync() {
// We use fsl::DeviceWatcher::CreateWithIdleCallback() instead of
// fsl::DeviceWatcher::Create() because the CodecFactory service is started on
// demand, and we don't want to start serving CodecFactory until we've
// discovered and processed all existing media-codec devices. That way, the
// first time a client requests a HW-backed codec, we robustly consider all
// codecs provided by pre-existing devices. The request for a HW-backed Codec
// will have a much higher probability of succeeding vs. if we just discovered
// pre-existing devices async. This doesn't prevent the possiblity that the
// device might not exist at the moment the CodecFactory is started, but as
// long as the device does exist by then, this will ensure the device's codecs
// are considered, including for the first client request.
device_watcher_ = fsl::DeviceWatcher::CreateWithIdleCallback(
kDeviceClass,
[this](int dir_fd, std::string filename) {
FXL_LOG(INFO) << "DeviceWatcher callback got filename: " << filename;
std::string device_path = std::string(kDeviceClass) + "/" + filename;
int fd = ::openat(dir_fd, filename.c_str(), O_RDONLY);
if (fd < 0) {
// This is ok (locally here) if the device (filename) really did go
// away in the meantime.
int snap_errno = errno;
FXL_LOG(INFO) << "Failed to open device by filename - resulting fd: "
<< fd << " errno: " << snap_errno
<< " device_path: " << device_path;
return;
}
zx::channel client_factory_channel;
ssize_t res = ioctl_media_codec_get_codec_factory_channel(
fd, client_factory_channel.reset_and_get_address());
::close(fd);
if (res != sizeof(client_factory_channel)) {
// This could be ok (locally here) if the device's devhost exited
// already or similar.
FXL_LOG(INFO) << "Failed to obtain channel - res: " << res
<< " device_path: " << device_path;
// ignore/skip the driver that failed the ioctl
return;
}
// From here on in the current lambda, we're doing stuff that can't fail
// here locally (at least, not without exiting the whole process). The
// error handler will handle channel error async.
auto discovery_entry = std::make_unique<DeviceDiscoveryEntry>();
discovery_entry->device_path = device_path;
discovery_entry->codec_factory =
std::make_shared<fuchsia::mediacodec::CodecFactoryPtr>();
discovery_entry->codec_factory->set_error_handler(
[this, device_path, factory = discovery_entry->codec_factory.get()](
zx_status_t status) {
FXL_LOG(INFO) << "Device's CodecFactoryPtr error handler - "
"cancelling discovery or de-registering: "
<< device_path;
// Any given factory won't be in both lists, but will be in one or
// the other by the time this error handler runs.
device_discovery_queue_.remove_if(
[factory](
const std::unique_ptr<DeviceDiscoveryEntry>& entry) {
return factory == entry->codec_factory.get();
});
// Perhaps the removed discovery item was the first item in the
// list; maybe now the new first item in the list can be
// processed.
PostDiscoveryQueueProcessing();
hw_codecs_.remove_if(
[factory](const std::unique_ptr<CodecListEntry>& entry) {
return factory == entry->factory.get();
});
});
discovery_entry->codec_factory->events().OnCodecList =
[this, discovery_entry = discovery_entry.get()](
std::vector<fuchsia::mediacodec::CodecDescription>
codec_list) {
discovery_entry->driver_codec_list = fidl::VectorPtr(codec_list);
// In case discovery_entry is the first item which is now ready to
// process, process the discovery queue.
PostDiscoveryQueueProcessing();
// We're no longer interested in OnCodecList events from the
// driver's CodecFactory, should the driver send any more. Sending
// more is not legal, but disconnect this event just in case,
// since we don't want the old lambda that touches
// driver_codec_list (this lambda).
discovery_entry->codec_factory->events().OnCodecList = nullptr;
};
discovery_entry->codec_factory->Bind(std::move(client_factory_channel),
loop_->dispatcher());
device_discovery_queue_.emplace_back(std::move(discovery_entry));
},
[this] {
FXL_LOG(INFO) << "DeviceWatcher idle_callback";
// The idle_callback indicates that all pre-existing devices have been
// seen, and by the time this item reaches the front of the discovery
// queue, all pre-existing devices have all been processed.
device_discovery_queue_.emplace_back(
std::make_unique<DeviceDiscoveryEntry>());
PostDiscoveryQueueProcessing();
});
FXL_LOG(INFO)
<< "CodecFactory::DiscoverMediaCodecDriversAndListenForMoreAsync() ran.";
}
void CodecFactoryApp::PostDiscoveryQueueProcessing() {
async::PostTask(
loop_->dispatcher(),
fit::bind_member(this, &CodecFactoryApp::ProcessDiscoveryQueue));
}
void CodecFactoryApp::ProcessDiscoveryQueue() {
// Both startup and steady-state use this processing loop.
//
// In startup, we care about ordering of the discovery queue because we want
// to allow serving of CodecFactory as soon as all pre-existing devices are
// done processing. We care that pre-existing devices are before
// newly-discovered devices in the queue. As far as startup is concerned,
// there are other ways we could track this without using a queue, but a queue
// works, and using the queue allows startup to share code with steady-state.
//
// In steady-state, we care (a little) about ordering of the discovery queue
// because we want (to a limited degree, for now) to prefer a
// more-recently-discovered device over a less-recently-discovered device (for
// now at least), so to make that robust, we preserve the device discovery
// order through the codec discovery sequence, to account for the possibility
// that an previously discovered device may have only just recently sent
// OnCodecList before failing; without the device_discovery_queue_ that
// previously-discovered device's OnCodecList could re-order vs. the
// replacement device's OnCodecList.
//
// The device_discovery_queue_ marginally increases the odds of a client
// request picking up a replacement devhost instead of an old devhost that
// failed quickly and which we haven't yet noticed is gone. This devhost
// replacement case is the main motivation for caring about the device
// discovery order in the first place (at least for now), since it should be
// robustly the case that discovery of the old devhost happens before
// discovery of the replacement devhost.
//
// The ordering of the hw_codec_ list is the main way in which
// more-recently-discovered codecs are prefered over less-recently-discovered
// codecs. The device_discovery_queue_ just makes the hw_codec_ ordering
// exactly correspond to the device discovery order (reversed) even when
// devices are discovered near each other in time.
//
// None of this changes the fact that a replacement devhost's arrival can race
// with a client's request, so if a devhost fails and is replaced, it's quite
// possible the client will see the Codec interface just fail. Even if this
// were mitigated, it wouldn't change the fact that a devhost failure later
// would result in Codec interface failure at that time, so failures near the
// start aren't really much different than async failures later. It can make
// sense for a client to retry a low number of times (if the client wants to
// work despite a devhost not always fully working), even if the Codec failure
// happens quite early.
while (!device_discovery_queue_.empty()) {
std::unique_ptr<DeviceDiscoveryEntry>& front =
device_discovery_queue_.front();
if (!front->codec_factory) {
// All pre-existing devices have been processed.
//
// Now the CodecFactory can begin serving (shortly).
existing_devices_discovered_ = true;
// The marker has done its job, so remove the marker.
device_discovery_queue_.pop_front();
return;
}
if (!front->driver_codec_list) {
// The first item is not yet ready. The current method will get re-posted
// when the first item is potentially ready.
return;
}
FXL_DCHECK(front->driver_codec_list);
for (auto& codec_description : front->driver_codec_list.get()) {
FXL_LOG(INFO) << "registering - codec_type: "
<< fidl::ToUnderlying(codec_description.codec_type)
<< " mime_type: " << codec_description.mime_type
<< " device_path: " << front->device_path;
hw_codecs_.emplace_front(std::make_unique<CodecListEntry>(CodecListEntry{
.description = std::move(codec_description),
// shared_ptr<>
.factory = front->codec_factory,
}));
}
device_discovery_queue_.pop_front();
}
}
} // namespace codec_factory