| // 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 |