| // 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 <fuchsia/cobalt/cpp/fidl.h> |
| #include <fuchsia/hardware/mediacodec/cpp/fidl.h> |
| #include <fuchsia/mediacodec/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/syslog/global.h> |
| #include <lib/trace-provider/provider.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <random> |
| |
| #include "codec_factory_impl.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "lib/sys/cpp/component_context.h" |
| #include "src/lib/fsl/io/device_watcher.h" |
| |
| namespace codec_factory { |
| |
| namespace { |
| |
| constexpr char kDeviceClass[] = "/dev/class/media-codec"; |
| const char* kLogTag = "CodecFactoryApp"; |
| |
| const std::string kAllSwDecoderMimeTypes[] = { |
| "video/h264", // VIDEO_ENCODING_H264 |
| }; |
| |
| } // namespace |
| |
| CodecFactoryApp::CodecFactoryApp(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) { |
| trace::TraceProviderWithFdio trace_provider(dispatcher_); |
| |
| // 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. |
| startup_context_ = sys::ComponentContext::Create(); |
| |
| zx_status_t status = |
| outgoing_codec_aux_service_directory_parent_.AddPublicService<fuchsia::cobalt::LoggerFactory>( |
| [this](fidl::InterfaceRequest<fuchsia::cobalt::LoggerFactory> request) { |
| ZX_DEBUG_ASSERT(startup_context_); |
| FX_LOGF(INFO, kLogTag, |
| "codec_factory handling request for LoggerFactory -- handle value: %u", |
| 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); |
| |
| 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 send OnCodecList(). |
| 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); |
| 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& mime_type : kAllSwDecoderMimeTypes) { |
| codecs.push_back({ |
| .codec_type = fuchsia::mediacodec::CodecType::DECODER, |
| .mime_type = 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& entry : hw_codecs_) { |
| codecs.push_back(entry->description); |
| } |
| auto rng = std::default_random_engine(); |
| std::shuffle(codecs.begin(), codecs.end(), rng); |
| |
| return codecs; |
| } |
| |
| const fuchsia::mediacodec::CodecFactoryPtr* CodecFactoryApp::FindHwCodec( |
| 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) { |
| std::string device_path = std::string(kDeviceClass) + "/" + filename; |
| zx::channel device_channel, device_remote; |
| zx_status_t status = zx::channel::create(0, &device_channel, &device_remote); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create channel - status: " << status; |
| return; |
| } |
| zx::channel client_factory_channel, client_factory_remote; |
| status = zx::channel::create(0, &client_factory_channel, &client_factory_remote); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to create channel - status: " << status; |
| return; |
| } |
| |
| status = fdio_service_connect(device_path.c_str(), device_remote.release()); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to connect to device by filename -" |
| << " status: " << status << " device_path: " << device_path; |
| return; |
| } |
| |
| fuchsia::hardware::mediacodec::DevicePtr device_interface; |
| status = device_interface.Bind(std::move(device_channel)); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to bind to interface -" |
| << " status: " << status << " device_path: " << device_path; |
| return; |
| } |
| |
| fidl::InterfaceHandle<fuchsia::io::Directory> aux_service_directory; |
| status = outgoing_codec_aux_service_directory_->Serve( |
| fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE | |
| fuchsia::io::OPEN_FLAG_DIRECTORY, |
| aux_service_directory.NewRequest().TakeChannel(), dispatcher_); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "outgoing_codec_aux_service_directory_.Serve() failed - status: " |
| << status; |
| return; |
| } |
| |
| // 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(std::move(client_factory_remote)); |
| |
| // 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) { |
| // 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), dispatcher()); |
| |
| device_discovery_queue_.emplace_back(std::move(discovery_entry)); |
| }, |
| [this] { |
| // 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::PostDiscoveryQueueProcessing() { |
| async::PostTask(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). |
| if (!existing_devices_discovered_) { |
| existing_devices_discovered_ = true; |
| PublishService(); |
| } |
| // 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; |
| } |
| FX_DCHECK(front->driver_codec_list.has_value()); |
| |
| for (auto& codec_description : front->driver_codec_list.value()) { |
| FX_LOGS(INFO) << "Registering " |
| << (codec_description.codec_type == fuchsia::mediacodec::CodecType::DECODER |
| ? "decoder" |
| : "encoder") |
| << ", 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 |