blob: 29696c56de4b39f72afb569e20477ab34ee46564 [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 <dirent.h>
#include <fuchsia/hardware/mediacodec/cpp/fidl.h>
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <fuchsia/metrics/cpp/fidl.h>
#include <fuchsia/sysinfo/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fidl/cpp/string.h>
#include <lib/fidl/cpp/vector.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <algorithm>
#include <random>
#include "codec_factory_impl.h"
#include "codec_isolate.h"
#include "src/lib/fsl/io/device_watcher.h"
#include "src/lib/fxl/strings/concatenate.h"
namespace {
constexpr char kDeviceClass[] = "/dev/class/media-codec";
constexpr char kGpuDeviceClass[] = "/dev/class/gpu";
const char* kLogTag = "CodecFactoryApp";
struct SwVideoDecoderInfo {
const char* mime_type;
fuchsia::media::CodecProfile profile;
uint32_t min_width;
uint32_t min_height;
uint32_t max_width;
uint32_t max_height;
};
const SwVideoDecoderInfo kSwVideoDecoderInfos[] = {
{
.mime_type = "video/h264", // VIDEO_ENCODING_H264
.profile = fuchsia::media::CodecProfile::H264PROFILE_HIGH,
.min_width = 32,
.min_height = 32,
// The decode performance will likely not be realtime at these dimensions.
.max_width = 3840,
.max_height = 2160,
},
};
const char* CodecProfileToString(fuchsia::media::CodecProfile profile) {
switch (profile) {
case fuchsia::media::CodecProfile::H264PROFILE_BASELINE:
return "H264PROFILE_BASELINE";
case fuchsia::media::CodecProfile::H264PROFILE_MAIN:
return "H264PROFILE_MAIN";
case fuchsia::media::CodecProfile::H264PROFILE_EXTENDED:
return "H264PROFILE_EXTENDED";
case fuchsia::media::CodecProfile::H264PROFILE_HIGH:
return "H264PROFILE_HIGH";
case fuchsia::media::CodecProfile::H264PROFILE_HIGH10PROFILE:
return "H264PROFILE_HIGH10PROFILE";
case fuchsia::media::CodecProfile::H264PROFILE_HIGH422PROFILE:
return "H264PROFILE_HIGH422PROFILE";
case fuchsia::media::CodecProfile::H264PROFILE_HIGH444PREDICTIVEPROFILE:
return "H264PROFILE_HIGH444PREDICTIVEPROFILE";
case fuchsia::media::CodecProfile::H264PROFILE_SCALABLEBASELINE:
return "H264PROFILE_SCALABLEBASELINE";
case fuchsia::media::CodecProfile::H264PROFILE_SCALABLEHIGH:
return "H264PROFILE_SCALABLEHIGH";
case fuchsia::media::CodecProfile::H264PROFILE_STEREOHIGH:
return "H264PROFILE_STEREOHIGH";
case fuchsia::media::CodecProfile::H264PROFILE_MULTIVIEWHIGH:
return "H264PROFILE_MULTIVIEWHIGH";
case fuchsia::media::CodecProfile::VP8PROFILE_ANY:
return "VP8PROFILE_ANY";
case fuchsia::media::CodecProfile::VP9PROFILE_PROFILE0:
return "VP9PROFILE_PROFILE0";
case fuchsia::media::CodecProfile::VP9PROFILE_PROFILE1:
return "VP9PROFILE_PROFILE1";
case fuchsia::media::CodecProfile::VP9PROFILE_PROFILE2:
return "VP9PROFILE_PROFILE2";
case fuchsia::media::CodecProfile::VP9PROFILE_PROFILE3:
return "VP9PROFILE_PROFILE3";
case fuchsia::media::CodecProfile::HEVCPROFILE_MAIN:
return "HEVCPROFILE_MAIN";
case fuchsia::media::CodecProfile::HEVCPROFILE_MAIN10:
return "HEVCPROFILE_MAIN10";
case fuchsia::media::CodecProfile::HEVCPROFILE_MAIN_STILL_PICTURE:
return "HEVCPROFILE_MAIN_STILL_PICTURE";
case fuchsia::media::CodecProfile::MJPEG_BASELINE:
return "MJPEG_BASELINE";
default:
return "UNKNOWN";
}
}
fuchsia::mediacodec::CodecDescription CreateDeprecatedCodecDescriptionFromDetailedCodecDescription(
fuchsia::mediacodec::DetailedCodecDescription& detailed) {
fuchsia::mediacodec::CodecDescription deprecated;
deprecated.codec_type = detailed.codec_type();
deprecated.mime_type = detailed.mime_type();
deprecated.is_hw = detailed.is_hw();
switch (detailed.codec_type()) {
case fuchsia::mediacodec::CodecType::DECODER:
// Default to more-capable, but if any per-profile info indicates less-capable, aggregate by
// marking less-capable.
deprecated.can_stream_bytes_input = true;
deprecated.can_find_start = true;
deprecated.can_re_sync = true;
deprecated.will_report_all_detected_errors = true;
deprecated.split_header_handling = true;
for (auto& profile : detailed.profile_descriptions().decoder_profile_descriptions()) {
if (!profile.has_can_stream_bytes_input() || !profile.can_stream_bytes_input()) {
deprecated.can_stream_bytes_input = false;
}
if (!profile.has_can_find_start() || !profile.can_find_start()) {
deprecated.can_find_start = false;
}
if (!profile.has_can_re_sync() || !profile.can_re_sync()) {
deprecated.can_re_sync = false;
}
if (!profile.has_will_report_all_detected_errors() ||
!profile.will_report_all_detected_errors()) {
deprecated.will_report_all_detected_errors = false;
}
if (!profile.has_split_header_handling() || !profile.split_header_handling()) {
deprecated.split_header_handling = false;
}
}
break;
case fuchsia::mediacodec::CodecType::ENCODER:
// These don't make sense for encoders, so set them all to false. The ambiguity of these
// fields appearing to apply to encoders when really they make no sense for encoders is
// among the reasons we're deprecating OnCodecList.
deprecated.can_stream_bytes_input = false;
deprecated.can_find_start = false;
deprecated.can_re_sync = false;
deprecated.will_report_all_detected_errors = false;
deprecated.split_header_handling = false;
}
return deprecated;
}
} // namespace
// board_name_ initialization requires startup_context_ already initialized.
// policy_ initialization requires board_name_ already initialized.
CodecFactoryApp::CodecFactoryApp(async_dispatcher_t* dispatcher, ProdOrTest prod_or_test)
: dispatcher_(dispatcher),
prod_or_test_(prod_or_test),
startup_context_(sys::ComponentContext::Create()),
board_name_(GetBoardName()),
policy_(this) {
inspector_ =
std::make_unique<inspect::ComponentInspector>(dispatcher_, inspect::PublishOptions{});
inspector_->Health().StartingUp();
hardware_factory_nodes_ = inspector_->root().CreateChild("hardware_factory_nodes");
// Don't publish service or outgoing()->ServeFromStartupInfo() until after initial discovery is
// done, else the pumping of the loop will drop the incoming request for CodecFactory before
// AddPublicService() below has had a chance to register for it.
zx_status_t status =
outgoing_codec_aux_service_directory_parent_
.AddPublicService<fuchsia::metrics::MetricEventLoggerFactory>(
[this](fidl::InterfaceRequest<fuchsia::metrics::MetricEventLoggerFactory> request) {
ZX_DEBUG_ASSERT(startup_context_);
FX_SLOG(INFO, kLogTag,
"codec_factory handling request for MetricEventLoggerFactory" FX_KV(
"tag", kLogTag),
FX_KV("handle value", request.channel().get()));
startup_context_->svc()->Connect(std::move(request));
});
outgoing_codec_aux_service_directory_ =
outgoing_codec_aux_service_directory_parent_.GetOrCreateDirectory("svc");
// Else codec_factory won't be able to provide what codecs expect to be able to rely on.
ZX_ASSERT(status == ZX_OK);
DiscoverMagmaCodecDriversAndListenForMoreAsync();
DiscoverMediaCodecDriversAndListenForMoreAsync();
}
void CodecFactoryApp::PublishService() {
// We delay doing this until we're completely ready to add services.
// We _rely_ on the driver to either fail the channel or respond to GetDetailedCodecDescriptions.
ZX_DEBUG_ASSERT(existing_devices_discovered_);
zx_status_t status =
startup_context_->outgoing()->AddPublicService<fuchsia::mediacodec::CodecFactory>(
[this](fidl::InterfaceRequest<fuchsia::mediacodec::CodecFactory> 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));
});
// else this codec_factory is useless
ZX_ASSERT(status == ZX_OK);
inspector_->Health().Ok();
if (prod_or_test_ == ProdOrTest::kProduction) {
status = startup_context_->outgoing()->ServeFromStartupInfo();
ZX_ASSERT(status == ZX_OK);
}
}
// All of the current supported hardware and software decoders, randomly shuffled
// so as to avoid clients depending on the order.
// TODO(schottm): send encoders as well
std::vector<fuchsia::mediacodec::CodecDescription> CodecFactoryApp::MakeCodecList() const {
std::vector<fuchsia::mediacodec::CodecDescription> codecs;
for (const auto& info : kSwVideoDecoderInfos) {
codecs.push_back({
.codec_type = fuchsia::mediacodec::CodecType::DECODER,
.mime_type = info.mime_type,
// TODO(schottm): can some of these be true?
.can_stream_bytes_input = false,
.can_find_start = false,
.can_re_sync = false,
.will_report_all_detected_errors = false,
.is_hw = false,
.split_header_handling = true,
});
}
for (const auto& factory : hw_factories_) {
for (const auto& codec : factory->hw_codecs) {
codecs.push_back(codec->deprecated_codec_description);
}
}
auto rng = std::default_random_engine();
std::shuffle(codecs.begin(), codecs.end(), rng);
return codecs;
}
std::vector<fuchsia::mediacodec::DetailedCodecDescription>
CodecFactoryApp::MakeDetailedCodecDescriptions() const {
std::vector<fuchsia::mediacodec::DetailedCodecDescription> codec_descriptions;
for (const auto& info : kSwVideoDecoderInfos) {
fuchsia::mediacodec::DetailedCodecDescription description;
description.set_codec_type(fuchsia::mediacodec::CodecType::DECODER);
description.set_mime_type(info.mime_type);
description.set_is_hw(false);
fuchsia::mediacodec::DecoderProfileDescription profile_description;
profile_description.set_profile(info.profile);
profile_description.set_min_image_size({info.min_width, info.min_height});
profile_description.set_max_image_size({info.max_width, info.max_height});
// We leave the rest of the fields un-set intentionally, since clients must accept a non-set
// field and know that means what the comments on the field in the FIDL file say it means (no
// programmatic field defaults).
std::vector<fuchsia::mediacodec::DecoderProfileDescription> profiles;
profiles.push_back(std::move(profile_description));
fuchsia::mediacodec::ProfileDescriptions profile_descriptions;
profile_descriptions.set_decoder_profile_descriptions(std::move(profiles));
description.set_profile_descriptions(std::move(profile_descriptions));
codec_descriptions.push_back(std::move(description));
}
for (const auto& factory : hw_factories_) {
for (const auto& codec : factory->hw_codecs) {
// Need to clone the |DetailedCodecDescription| since it is not copyable
fuchsia::mediacodec::DetailedCodecDescription detailed_description;
ZX_ASSERT(codec->detailed_description.Clone(&detailed_description) == ZX_OK);
codec_descriptions.push_back(std::move(detailed_description));
}
}
return codec_descriptions;
}
const fuchsia::mediacodec::CodecFactoryPtr* CodecFactoryApp::FindHwCodec(
fit::function<bool(const fuchsia::mediacodec::DetailedCodecDescription&)> is_match) {
for (auto& hw_factory : hw_factories_) {
// HW codecs are connected to using factory, not by launching a component using the URL.
if (!hw_factory->component_url.empty()) {
continue;
}
auto iter = std::find_if(hw_factory->hw_codecs.begin(), hw_factory->hw_codecs.end(),
[&is_match](const std::unique_ptr<CodecListEntry>& entry) -> bool {
return is_match(entry->detailed_description);
});
if (iter != hw_factory->hw_codecs.end()) {
return hw_factory->codec_factory.get();
}
}
return nullptr;
}
std::optional<std::string> CodecFactoryApp::FindHwIsolate(
fit::function<bool(const fuchsia::mediacodec::DetailedCodecDescription&)> is_match) {
for (auto& hw_factory : hw_factories_) {
// Isolate codecs are connected by launching a component using the URL.
if (hw_factory->component_url.empty()) {
continue;
}
auto iter = std::find_if(hw_factory->hw_codecs.begin(), hw_factory->hw_codecs.end(),
[&is_match](const std::unique_ptr<CodecListEntry>& entry) -> bool {
return is_match(entry->detailed_description);
});
if (iter != hw_factory->hw_codecs.end()) {
return hw_factory->component_url;
}
}
return std::nullopt;
}
void CodecFactoryApp::IdledCodecDiscovery() {
ZX_ASSERT(num_codec_discoveries_in_flight_ >= 1);
if (--num_codec_discoveries_in_flight_ == 0) {
// 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();
}
}
void CodecFactoryApp::DiscoverMediaCodecDriversAndListenForMoreAsync() {
num_codec_discoveries_in_flight_++;
// 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
// possibility 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](const fidl::ClientEnd<fuchsia_io::Directory>& dir, const std::string& filename) {
fuchsia::hardware::mediacodec::DevicePtr device_interface;
if (zx_status_t status =
fdio_service_connect_at(dir.channel().get(), filename.c_str(),
device_interface.NewRequest().TakeChannel().release());
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to connect to device '" << filename << "'";
return;
}
fidl::InterfaceHandle<fuchsia::io::Directory> aux_service_directory;
if (zx_status_t status = outgoing_codec_aux_service_directory_->Serve(
fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE |
fuchsia::io::OpenFlags::DIRECTORY,
aux_service_directory.NewRequest().TakeChannel(), dispatcher_);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "outgoing_codec_aux_service_directory_.Serve() failed";
return;
}
auto discovery_entry = std::make_unique<DeviceDiscoveryEntry>();
discovery_entry->device_path = fxl::Concatenate({kDeviceClass, "/", filename});
discovery_entry->codec_factory = std::make_shared<fuchsia::mediacodec::CodecFactoryPtr>();
// It's ok for a codec that doesn't need the aux service directory to just close the client
// handle to it, so there's no need to attempt to detect a codec closing the aux service
// directory client end.
//
// TODO(dustingreen): Combine these two calls into "Connect" and use FIDL table with the
// needed fields.
device_interface->SetAuxServiceDirectory(std::move(aux_service_directory));
device_interface->GetCodecFactory(
discovery_entry->codec_factory->NewRequest(dispatcher()).TakeChannel());
// 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.
discovery_entry->codec_factory->set_error_handler(
[this, device_path = discovery_entry->device_path,
factory = discovery_entry->codec_factory.get()](zx_status_t status) {
// 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_factories_.remove_if(
[factory](const std::unique_ptr<CodecFactoryEntry>& factory_entry) {
return factory == factory_entry->codec_factory.get();
});
});
// Queue up a call to |GetDetailedCodecDescriptions()| and store the output in the
// discovery_entry.
FX_LOGS(INFO) << "Calling GetDetailedCodecDescriptions for "
<< discovery_entry->device_path;
(*discovery_entry->codec_factory)
->GetDetailedCodecDescriptions(
[this, discovery_entry = discovery_entry.get()](
fuchsia::mediacodec::CodecFactoryGetDetailedCodecDescriptionsResponse
response) {
ZX_ASSERT(response.has_codecs());
ZX_ASSERT(response.mutable_codecs());
FX_LOGS(INFO) << "GetDetailedCodecDescriptions response received for "
<< discovery_entry->device_path;
discovery_entry->detailed_codec_descriptions =
std::move(*response.mutable_codecs());
// In case discovery_entry is the first item which is now ready to
// process, process the discovery queue.
PostDiscoveryQueueProcessing();
});
device_discovery_queue_.emplace_back(std::move(discovery_entry));
},
[this] { IdledCodecDiscovery(); });
}
void CodecFactoryApp::TeardownMagmaCodec(
const std::shared_ptr<fuchsia::gpu::magma::IcdLoaderDevicePtr>& magma_device) {
// Any given magma device 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(
[&magma_device](const std::unique_ptr<DeviceDiscoveryEntry>& entry) {
return magma_device.get() == entry->magma_device.get();
});
hw_factories_.remove_if([&magma_device](const std::unique_ptr<CodecFactoryEntry>& factory_entry) {
return magma_device.get() == factory_entry->magma_device.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();
}
void CodecFactoryApp::DiscoverMagmaCodecDriversAndListenForMoreAsync() {
num_codec_discoveries_in_flight_++;
gpu_device_watcher_ = fsl::DeviceWatcher::CreateWithIdleCallback(
kGpuDeviceClass,
[this](const fidl::ClientEnd<fuchsia_io::Directory>& dir, const std::string& filename) {
auto magma_device = std::make_shared<fuchsia::gpu::magma::IcdLoaderDevicePtr>();
zx_status_t status =
fdio_service_connect_at(dir.channel().get(), filename.c_str(),
magma_device->NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to connect to device '" << filename << "'";
return;
}
auto discovery_entry = std::make_unique<DeviceDiscoveryEntry>();
discovery_entry->device_path = fxl::Concatenate({kGpuDeviceClass, "/", filename});
discovery_entry->magma_device = magma_device;
discovery_entry->magma_device->set_error_handler(
[this, magma_device](zx_status_t status) { TeardownMagmaCodec(magma_device); });
(*magma_device)
->GetIcdList([this, discovery_entry = discovery_entry.get(), magma_device,
device_path = discovery_entry->device_path](
const std::vector<fuchsia::gpu::magma::IcdInfo>& icd_infos) {
bool found_media_icd = false;
for (auto& icd_entry : icd_infos) {
if (!icd_entry.has_flags() || !icd_entry.has_component_url())
continue;
if (!(icd_entry.flags() &
fuchsia::gpu::magma::IcdFlags::SUPPORTS_MEDIA_CODEC_FACTORY)) {
continue;
}
discovery_entry->component_url = icd_entry.component_url();
ForwardToIsolate(
icd_entry.component_url(), IsolateType::kMagma, startup_context_.get(),
[this, magma_device, device_path](fuchsia::mediacodec::CodecFactoryPtr ptr) {
auto it = std::find_if(
device_discovery_queue_.begin(), device_discovery_queue_.end(),
[magma_device](const std::unique_ptr<DeviceDiscoveryEntry>& entry) {
return magma_device.get() == entry->magma_device.get();
});
if (it == device_discovery_queue_.end()) {
// Device was removed from the queue due to the magma error handler running.
return;
}
auto discovery_entry = it->get();
discovery_entry->codec_factory =
std::make_shared<fuchsia::mediacodec::CodecFactoryPtr>();
discovery_entry->codec_factory->Bind(ptr.Unbind());
discovery_entry->codec_factory->set_error_handler(
[this, device_path, magma_device](zx_status_t status) {
TeardownMagmaCodec(magma_device);
});
// Queue up a call to |GetDetailedCodecDescriptions()| and store the output
// in the discovery_entry.
FX_LOGS(INFO) << "Calling GetDetailedCodecDescriptions for "
<< discovery_entry->component_url;
(*discovery_entry->codec_factory)
->GetDetailedCodecDescriptions(
[this, discovery_entry](
fuchsia::mediacodec::
CodecFactoryGetDetailedCodecDescriptionsResponse response) {
ZX_ASSERT(response.has_codecs());
ZX_ASSERT(response.mutable_codecs());
FX_LOGS(INFO)
<< "GetDetailedCodecDescriptions response received for "
<< discovery_entry->component_url;
discovery_entry->detailed_codec_descriptions =
std::move(*response.mutable_codecs());
// In case discovery_entry is the first item which is now ready to
// process, process the discovery queue.
PostDiscoveryQueueProcessing();
});
},
[this, magma_device]() { TeardownMagmaCodec(magma_device); });
found_media_icd = true;
// Only support a single codec factory per magma device.
break;
}
if (!found_media_icd) {
TeardownMagmaCodec(magma_device);
}
});
device_discovery_queue_.emplace_back(std::move(discovery_entry));
},
[this] { IdledCodecDiscovery(); });
}
void CodecFactoryApp::PostDiscoveryQueueProcessing() {
async::PostTask(dispatcher_, fit::bind_member<&CodecFactoryApp::ProcessDiscoveryQueue>(this));
}
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 responded
// to GetDetailedCodecDescriptions before failing; without the
// device_discovery_queue_ that previously-discovered device's response could
// re-order vs. the replacement device's response.
//
// 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 preferred 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>& discovered_device = device_discovery_queue_.front();
if (!discovered_device->codec_factory && !discovered_device->magma_device) {
// All pre-existing devices have been processed.
//
// Now the CodecFactory can begin serving (shortly).
if (!existing_devices_discovered_) {
existing_devices_discovered_ = true;
FX_LOGS(INFO) << "CodecFactory calling PublishService()";
PublishService();
}
// The marker has done its job, so remove the marker.
device_discovery_queue_.pop_front();
return;
}
// Wait for detailed_codec_descriptions. We don't wait for driver_codec_list, which is
// generated from detailed_codec_descriptions below.
if (!discovered_device->detailed_codec_descriptions) {
// The first item is not yet ready. The current method will get re-posted
// when the first item is potentially ready.
return;
}
if (!discovered_device->component_url.empty()) {
// If there's a component URL then a new instance will be launched for every codec, so
// codec_factory won't be used anymore.
discovered_device->codec_factory = {};
}
FX_DCHECK(!discovered_device->device_path.empty());
// Create the hardware codec factory that these codecs will belong to
auto hw_codec_factory = std::make_unique<CodecFactoryEntry>();
hw_codec_factory->component_url = discovered_device->component_url;
hw_codec_factory->codec_factory = discovered_device->codec_factory;
hw_codec_factory->magma_device = discovered_device->magma_device;
// Device paths are always unique and must be provided for hardware factories
hw_codec_factory->factory_node =
hardware_factory_nodes_.CreateChild(discovered_device->device_path);
const std::string& device_path =
!discovered_device->device_path.empty() ? discovered_device->device_path : "N/A";
hw_codec_factory->factory_node.RecordString("device_path", device_path);
const std::string& component_url =
!discovered_device->component_url.empty() ? discovered_device->component_url : "N/A";
hw_codec_factory->factory_node.RecordString("component_url", component_url);
// All codecs must implement GetDetailedCodecDescriptions. We ignore any OnCodecList from each
// codec in favor of building an OnCodecList from GetDetailedCodecDescriptions responses, so
// that each codec (roughly speaking) can stop sending the deprecated OnCodecList before all
// clients have stopped listening to OnCodecList.
FX_CHECK(discovered_device->detailed_codec_descriptions);
while (!discovered_device->detailed_codec_descriptions->empty()) {
auto detailed_description = std::move(discovered_device->detailed_codec_descriptions->back());
discovered_device->detailed_codec_descriptions->pop_back();
FX_LOGS(INFO) << "Registering "
<< (detailed_description.codec_type() == fuchsia::mediacodec::CodecType::DECODER
? "decoder"
: "encoder")
<< ", mime_type: " << detailed_description.mime_type()
<< ", device_path: " << discovered_device->device_path
<< ", component url: " << discovered_device->component_url;
if (!detailed_description.has_profile_descriptions()) {
FX_LOGS(WARNING) << "!has_profile_descriptions for " << detailed_description.mime_type()
<< " "
<< ((detailed_description.codec_type() ==
fuchsia::mediacodec::CodecType::DECODER)
? "DECODER"
: "ENCODER");
continue;
}
auto deprecated_codec_description =
CreateDeprecatedCodecDescriptionFromDetailedCodecDescription(detailed_description);
auto& parent_node = hw_codec_factory->factory_node;
inspect::Node node = parent_node.CreateChild(detailed_description.mime_type());
node.RecordString("type",
detailed_description.codec_type() == fuchsia::mediacodec::CodecType::DECODER
? "decoder"
: "encoder");
node.RecordString("mime_type", detailed_description.mime_type());
// Populate the detailed inspect values, which is different for decoders vs. encoders.
switch (detailed_description.codec_type()) {
case fuchsia::mediacodec::CodecType::DECODER:
node.RecordChild("supported_profiles", [&detailed_description](
inspect::Node& supported_profile_nodes) {
if (!detailed_description.has_profile_descriptions()) {
// A warning was alraedy printed above.
return;
}
if (!detailed_description.profile_descriptions().is_decoder_profile_descriptions()) {
FX_LOGS(WARNING) << "DECODER !is_decoder_profile_descriptions for "
<< detailed_description.mime_type();
return;
}
uint32_t profile_ordinal = 0u;
const auto& profiles =
detailed_description.profile_descriptions().decoder_profile_descriptions();
for (const auto& profile : profiles) {
supported_profile_nodes.RecordChild(
std::to_string(profile_ordinal), [&profile](inspect::Node& profile_node) {
FX_DCHECK(profile.has_profile());
profile_node.RecordString("profile", CodecProfileToString(profile.profile()));
if (profile.has_min_image_size()) {
const auto& min_size = profile.min_image_size();
profile_node.RecordUint("min_width", min_size.width);
profile_node.RecordUint("min_height", min_size.height);
}
if (profile.has_max_image_size()) {
const auto& max_size = profile.max_image_size();
profile_node.RecordUint("max_width", max_size.width);
profile_node.RecordUint("max_height", max_size.height);
}
});
profile_ordinal += 1u;
}
});
break;
case fuchsia::mediacodec::CodecType::ENCODER:
node.RecordChild("supported_profiles", [&detailed_description](
inspect::Node& supported_profile_nodes) {
if (!detailed_description.has_profile_descriptions()) {
// A warning was already printed above.
return;
}
if (!detailed_description.profile_descriptions().is_encoder_profile_descriptions()) {
FX_LOGS(WARNING) << "ENCODER !is_encoder_profile_descriptions for "
<< detailed_description.mime_type();
return;
}
uint32_t profile_ordinal = 0u;
const auto& profiles =
detailed_description.profile_descriptions().encoder_profile_descriptions();
for (const auto& profile : profiles) {
supported_profile_nodes.RecordChild(
std::to_string(profile_ordinal), [&profile](inspect::Node& profile_node) {
FX_DCHECK(profile.has_profile());
profile_node.RecordString("profile", CodecProfileToString(profile.profile()));
});
profile_ordinal += 1u;
}
});
break;
}
hw_codec_factory->hw_codecs.emplace_front(std::make_unique<CodecListEntry>(CodecListEntry{
.detailed_description = std::move(detailed_description),
.codec_node = std::move(node),
.deprecated_codec_description = std::move(deprecated_codec_description),
}));
}
hw_factories_.push_back(std::move(hw_codec_factory));
device_discovery_queue_.pop_front();
}
}
// This is called during field initialization portion of the constructor, so needs to avoid reading
// any fields that are not yet initialized.
std::string CodecFactoryApp::GetBoardName() {
fuchsia::sysinfo::SysInfoSyncPtr sysinfo;
zx_status_t status =
startup_context_->svc()->Connect<fuchsia::sysinfo::SysInfo>(sysinfo.NewRequest());
// CodecFactoryApp's process can't necessarily work correctly without the board name.
ZX_ASSERT(status == ZX_OK);
fidl::StringPtr board_name;
zx_status_t fidl_status = sysinfo->GetBoardName(&status, &board_name);
if (fidl_status != ZX_OK || status != ZX_OK) {
// This path is only taken if CodecFactory can't contact fuchsia.sysinfo.SysInfo. Most often
// this happens in tests that don't correctly route the capability for protocol
// fuchsia.sysinfo.SysInfo.
FX_LOGS(WARNING) << "#############################";
FX_LOGS(WARNING) << "sysinfo->GetBoardName() failed. "
"CodecFactoryApp needs access to fuchsia.sysinfo.SysInfo. fidl_status: "
<< fidl_status << " status: " << status;
FX_LOGS(WARNING) << "#############################";
return "<UNKNOWN>";
}
ZX_ASSERT(fidl_status == ZX_OK && status == ZX_OK);
return board_name.value();
}