| // 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(); |
| } |