| // 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_impl.h" |
| |
| #include <fuchsia/component/cpp/fidl.h> |
| #include <fuchsia/component/decl/cpp/fidl.h> |
| #include <fuchsia/sysinfo/cpp/fidl.h> |
| #include <inttypes.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fit/function.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <zircon/syscalls.h> |
| |
| #include <algorithm> |
| |
| #include "codec_isolate.h" |
| #include "lib/zx/eventpair.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace { |
| |
| // Isolates for SW encode/decode |
| // |
| // For HW-based codecs, we discover their "LocalCodecFactory" by watching for |
| // their device and sending the server end of a (local) CodecFactory to the |
| // driver. |
| |
| const std::string kIsolateRelativeUrlSbc = "#meta/codec_runner_sw_sbc.cm"; |
| const std::string kIsolateRelativeUrlAac = "#meta/codec_runner_sw_aac.cm"; |
| const std::string kIsolateRelativeUrlCvsd = "#meta/codec_runner_sw_cvsd.cm"; |
| const std::string kIsolateRelativeUrlFfmpeg = "#meta/codec_runner_sw_ffmpeg.cm"; |
| const std::string kIsolateRelativeUrlLc3 = "#meta/codec_runner_sw_lc3.cm"; |
| |
| struct EncoderSupportSpec { |
| std::string isolate_url; |
| std::vector<std::string> mime_types; |
| std::function<bool(const fuchsia::media::EncoderSettings&)> supports_settings; |
| bool supports_mime_type(const std::string& mime_type) const { |
| return std::find(mime_types.begin(), mime_types.end(), mime_type) != mime_types.end(); |
| } |
| bool supports(const std::string& mime_type, |
| const fuchsia::media::EncoderSettings& settings) const { |
| return supports_mime_type(mime_type) && supports_settings(settings); |
| } |
| }; |
| |
| const EncoderSupportSpec kSbcEncoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlSbc, |
| .mime_types = {"audio/pcm"}, |
| .supports_settings = |
| [](const fuchsia::media::EncoderSettings& settings) { |
| return settings.is_sbc() || settings.is_msbc(); |
| }, |
| }; |
| |
| const EncoderSupportSpec kAacEncoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlAac, |
| .mime_types = {"audio/pcm"}, |
| .supports_settings = |
| [](const fuchsia::media::EncoderSettings& settings) { return settings.is_aac(); }, |
| }; |
| |
| const EncoderSupportSpec kCvsdEncoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlCvsd, |
| .mime_types = {"audio/pcm"}, |
| .supports_settings = |
| [](const fuchsia::media::EncoderSettings& settings) { return settings.is_cvsd(); }, |
| }; |
| |
| const EncoderSupportSpec kLc3EncoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlLc3, |
| .mime_types = {"audio/pcm"}, |
| .supports_settings = |
| [](const fuchsia::media::EncoderSettings& settings) { return settings.is_lc3(); }, |
| }; |
| |
| const EncoderSupportSpec supported_encoders[] = {kSbcEncoderSupportSpec, kAacEncoderSupportSpec, |
| kCvsdEncoderSupportSpec, kLc3EncoderSupportSpec}; |
| |
| struct DecoderSupportSpec { |
| std::string isolate_url; |
| std::vector<std::string> mime_types; |
| bool supports(const std::string& mime_type) const { |
| return std::find(mime_types.begin(), mime_types.end(), mime_type) != mime_types.end(); |
| } |
| }; |
| |
| const DecoderSupportSpec kFfmpegSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlFfmpeg, |
| .mime_types = {"video/h264"}, |
| }; |
| |
| const DecoderSupportSpec kSbcDecoderSuportSpec = { |
| .isolate_url = kIsolateRelativeUrlSbc, |
| .mime_types = {"audio/sbc", "audio/msbc"}, |
| }; |
| |
| const DecoderSupportSpec kCvsdDecoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlCvsd, |
| .mime_types = {"audio/cvsd"}, |
| }; |
| |
| const DecoderSupportSpec kLc3DecoderSupportSpec = { |
| .isolate_url = kIsolateRelativeUrlLc3, |
| .mime_types = {"audio/lc3"}, |
| }; |
| |
| const DecoderSupportSpec supported_decoders[] = {kFfmpegSupportSpec, kSbcDecoderSuportSpec, |
| kCvsdDecoderSupportSpec, kLc3DecoderSupportSpec}; |
| |
| std::optional<std::string> FindEncoder(const std::string& mime_type, |
| const fuchsia::media::EncoderSettings& settings) { |
| auto encoder = std::find_if(std::begin(supported_encoders), std::end(supported_encoders), |
| [&mime_type, &settings](const EncoderSupportSpec& encoder) { |
| return encoder.supports(mime_type, settings); |
| }); |
| |
| if (encoder == std::end(supported_encoders)) { |
| return std::nullopt; |
| } |
| |
| return encoder->isolate_url; |
| } |
| |
| std::optional<std::string> FindDecoder(const std::string& mime_type) { |
| auto decoder = std::find_if( |
| std::begin(supported_decoders), std::end(supported_decoders), |
| [&mime_type](const DecoderSupportSpec& decoder) { return decoder.supports(mime_type); }); |
| |
| if (decoder == std::end(supported_decoders)) { |
| return std::nullopt; |
| } |
| |
| return decoder->isolate_url; |
| } |
| |
| } // namespace |
| |
| // TODO(dustingreen): Currently we assume, potentially incorrectly, that clients |
| // of CodecFactory won't spam CodecFactory channel creation. Rather than trying |
| // to mitigate that problem locally in this class, it seems better to intergrate |
| // with a more general-purpose request spam mitigation mechanism. |
| void CodecFactoryImpl::CreateSelfOwned( |
| CodecFactoryApp* app, sys::ComponentContext* component_context, |
| fidl::InterfaceRequest<fuchsia::mediacodec::CodecFactory> request) { |
| // I considered just doing "new CodecFactoryImpl(...)" here and declaring that |
| // it always inherently owns itself (and implementing it that way), but that |
| // seems less flexible for testing purposes and also not necessarily as safe |
| // if we were to add any error cases before the Binding has taken over |
| // ownership. |
| // |
| // As usual, can't use std::make_unique<> here since making it a friend would |
| // break the point of making the constructor private. |
| std::shared_ptr<CodecFactoryImpl> self( |
| new CodecFactoryImpl(app, component_context, std::move(request))); |
| auto* self_ptr = self.get(); |
| self_ptr->OwnSelf(std::move(self)); |
| assert(!self); |
| } |
| |
| void CodecFactoryImpl::OwnSelf(std::shared_ptr<CodecFactoryImpl> self) { self_ = std::move(self); } |
| |
| CodecFactoryImpl::CodecFactoryImpl( |
| CodecFactoryApp* app, sys::ComponentContext* component_context, |
| fidl::InterfaceRequest<fuchsia::mediacodec::CodecFactory> request) |
| : app_(app), |
| component_context_(component_context), |
| binding_(this, std::move(request), app_->dispatcher()) { |
| binding_.set_error_handler([this](zx_status_t status) { self_.reset(); }); |
| |
| // The app already has all hardware codecs loaded by the time we get to talk |
| // to it, so we don't need to wait for it now. |
| // |
| // This message is deprecated, but is sent for the benefit of clients that haven't yet moved to |
| // GetDetailedCodecDescriptions. |
| FX_LOGS(INFO) << "Sending OnCodecList"; |
| binding_.events().OnCodecList(app_->MakeCodecList()); |
| } |
| |
| void CodecFactoryImpl::GetDetailedCodecDescriptions(GetDetailedCodecDescriptionsCallback callback) { |
| fuchsia::mediacodec::CodecFactoryGetDetailedCodecDescriptionsResponse response; |
| response.set_codecs(app_->MakeDetailedCodecDescriptions()); |
| callback(std::move(response)); |
| } |
| |
| void CodecFactoryImpl::CreateDecoder( |
| fuchsia::mediacodec::CreateDecoder_Params params, |
| fidl::InterfaceRequest<fuchsia::media::StreamProcessor> decoder) { |
| if (!params.has_input_details()) { |
| FX_LOGS(WARNING) << "missing input_details"; |
| return; |
| } |
| |
| if (!params.input_details().has_mime_type()) { |
| FX_LOGS(WARNING) << "input details missing mime type"; |
| // Without mime_type we cannot search for a decoder. |
| return; |
| } |
| |
| // We don't have any need to bind the codec_request locally to this process. |
| // Instead, we find where to delegate the request to. |
| |
| std::optional<std::string> hw_isolate; |
| IsolateType isolate_type = IsolateType::kSw; |
| if (!params.has_require_sw() || !params.require_sw()) { |
| // First, try to find a hw-accelerated codec to satisfy the request. |
| auto mime_type = params.input_details().mime_type(); |
| const fuchsia::mediacodec::CodecFactoryPtr* factory = app_->FindHwCodec( |
| [&mime_type]( |
| const fuchsia::mediacodec::DetailedCodecDescription& hw_codec_description) -> bool { |
| // TODO(dustingreen): pay attention to the bool constraints of the |
| // params vs. the hw_codec_description bools. For the moment we just |
| // match the codec_type, mime_type. |
| constexpr fuchsia::mediacodec::CodecType codec_type = |
| fuchsia::mediacodec::CodecType::DECODER; |
| return (codec_type == hw_codec_description.codec_type()) && |
| (mime_type == hw_codec_description.mime_type()); |
| }); |
| if (factory && (!params.has_require_hw() || !params.require_hw()) && !AdmitHwDecoder(params)) { |
| factory = nullptr; |
| } |
| if (factory) { |
| // prefer HW-accelerated |
| FX_LOGS(INFO) << "CreateDecoder() found HW decoder for: " |
| << params.input_details().mime_type(); |
| AttachLifetimeTrackingEventpairDownstream(factory); |
| (*factory)->CreateDecoder(std::move(params), std::move(decoder)); |
| return; |
| } |
| hw_isolate = app_->FindHwIsolate( |
| [&mime_type]( |
| const fuchsia::mediacodec::DetailedCodecDescription& hw_codec_description) -> bool { |
| // TODO(dustingreen): pay attention to the bool constraints of the |
| // params vs. the hw_codec_description bools. For the moment we just |
| // match the codec_type, mime_type. |
| constexpr fuchsia::mediacodec::CodecType codec_type = |
| fuchsia::mediacodec::CodecType::DECODER; |
| return (codec_type == hw_codec_description.codec_type()) && |
| (mime_type == hw_codec_description.mime_type()); |
| }); |
| if (hw_isolate) { |
| isolate_type = IsolateType::kMagma; |
| } |
| } |
| |
| // This is outside the above if on purpose, in case the client specifies both require_hw and |
| // require_sw, in which case we should fail. |
| if (!hw_isolate && params.has_require_hw() && params.require_hw()) { |
| FX_LOGS(WARNING) << "require_hw, but no matching HW decoder factory found (" |
| << params.input_details().mime_type() << "); closing"; |
| // TODO(dustingreen): Send epitaph when possible. |
| return; |
| } |
| |
| auto maybe_decoder_isolate_url = hw_isolate; |
| if (!maybe_decoder_isolate_url) |
| maybe_decoder_isolate_url = FindDecoder(params.input_details().mime_type()); |
| |
| if (!maybe_decoder_isolate_url) { |
| FX_LOGS(WARNING) << "No decoder supports " << params.input_details().mime_type(); |
| return; |
| } |
| |
| FX_LOGS(INFO) << "CreateDecoder() found SW decoder for: " << params.input_details().mime_type(); |
| |
| ForwardToIsolate( |
| *maybe_decoder_isolate_url, isolate_type, component_context_, |
| [self = self_, params = std::move(params), decoder = std::move(decoder)]( |
| fuchsia::mediacodec::CodecFactoryPtr factory_delegate) mutable { |
| // Forward the request to the factory_delegate_ as-is. This |
| // avoids conversion to command-line parameters and back, |
| // and avoids creating a separate interface definition for |
| // the delegated call. The downside is potential confusion |
| // re. why we have several implementations of CodecFactory, |
| // but we can comment why. The presently-running |
| // implementation is the main implementation that clients |
| // use directly. |
| self->AttachLifetimeTrackingEventpairDownstream(&factory_delegate); |
| factory_delegate->CreateDecoder(std::move(params), std::move(decoder)); |
| ZX_DEBUG_ASSERT(self->lifetime_tracking_.empty()); |
| }, |
| []() {}); |
| } |
| |
| void CodecFactoryImpl::CreateEncoder( |
| fuchsia::mediacodec::CreateEncoder_Params encoder_params, |
| ::fidl::InterfaceRequest<fuchsia::media::StreamProcessor> encoder_request) { |
| if (!encoder_params.has_input_details()) { |
| FX_LOGS(WARNING) << "missing input_details"; |
| return; |
| } |
| |
| if (!encoder_params.input_details().has_mime_type()) { |
| FX_LOGS(WARNING) << "missing mime_type"; |
| return; |
| } |
| |
| if (!encoder_params.input_details().has_encoder_settings()) { |
| FX_LOGS(WARNING) << "missing encoder_settings"; |
| return; |
| } |
| |
| // We don't have any need to bind the codec_request locally to this process. |
| // Instead, we find where to delegate the request to. |
| |
| // First, try to find a hw-accelerated codec to satisfy the request. |
| const fuchsia::mediacodec::CodecFactoryPtr* factory = app_->FindHwCodec( |
| [&encoder_params]( |
| const fuchsia::mediacodec::DetailedCodecDescription& hw_codec_description) -> bool { |
| return (fuchsia::mediacodec::CodecType::ENCODER == hw_codec_description.codec_type()) && |
| (encoder_params.input_details().mime_type() == hw_codec_description.mime_type()); |
| }); |
| |
| if (factory && !AdmitHwEncoder(encoder_params)) { |
| factory = nullptr; |
| } |
| |
| if (factory) { |
| // prefer HW-accelerated |
| AttachLifetimeTrackingEventpairDownstream(factory); |
| (*factory)->CreateEncoder(std::move(encoder_params), std::move(encoder_request)); |
| return; |
| } |
| auto hw_isolate = app_->FindHwIsolate( |
| [&encoder_params]( |
| const fuchsia::mediacodec::DetailedCodecDescription& hw_codec_description) -> bool { |
| return (fuchsia::mediacodec::CodecType::ENCODER == hw_codec_description.codec_type()) && |
| (encoder_params.input_details().mime_type() == hw_codec_description.mime_type()); |
| }); |
| IsolateType isolate_type = IsolateType::kSw; |
| if (hw_isolate) { |
| isolate_type = IsolateType::kMagma; |
| } |
| |
| if (encoder_params.has_require_hw() && encoder_params.require_hw() && !hw_isolate) { |
| FX_LOGS(WARNING) << "require_hw, but no matching HW encoder factory found (" |
| << encoder_params.input_details().mime_type() << "); closing"; |
| // ~encoder |
| return; |
| } |
| |
| auto maybe_encoder_isolate_url = hw_isolate; |
| if (!maybe_encoder_isolate_url) { |
| maybe_encoder_isolate_url = FindEncoder(encoder_params.input_details().mime_type(), |
| encoder_params.input_details().encoder_settings()); |
| } |
| |
| if (!maybe_encoder_isolate_url) { |
| FX_LOGS(WARNING) << "No encoder supports " << encoder_params.input_details().mime_type() |
| << " input with these settings."; |
| return; |
| } |
| |
| ForwardToIsolate( |
| *maybe_encoder_isolate_url, isolate_type, component_context_, |
| [self = self_, encoder_params = std::move(encoder_params), |
| encoder_request = std::move(encoder_request)]( |
| fuchsia::mediacodec::CodecFactoryPtr factory_delegate) mutable { |
| self->AttachLifetimeTrackingEventpairDownstream(&factory_delegate); |
| factory_delegate->CreateEncoder(std::move(encoder_params), std::move(encoder_request)); |
| }, |
| []() {}); |
| } |
| |
| void CodecFactoryImpl::AttachLifetimeTracking(zx::eventpair codec_end) { |
| ZX_DEBUG_ASSERT(lifetime_tracking_.size() <= |
| fuchsia::mediacodec::CODEC_FACTORY_LIFETIME_TRACKING_EVENTPAIR_PER_CREATE_MAX); |
| if (lifetime_tracking_.size() >= |
| fuchsia::mediacodec::CODEC_FACTORY_LIFETIME_TRACKING_EVENTPAIR_PER_CREATE_MAX) { |
| binding_.Close(ZX_ERR_BAD_STATE); |
| // This call will delete this. |
| self_.reset(); |
| return; |
| } |
| lifetime_tracking_.emplace_back(std::move(codec_end)); |
| } |
| |
| void CodecFactoryImpl::AttachLifetimeTrackingEventpairDownstream( |
| const fuchsia::mediacodec::CodecFactoryPtr* factory) { |
| while (!lifetime_tracking_.empty()) { |
| zx::eventpair lifetime_tracking_eventpair = std::move(lifetime_tracking_.back()); |
| lifetime_tracking_.pop_back(); |
| (*factory)->AttachLifetimeTracking(std::move(lifetime_tracking_eventpair)); |
| } |
| } |
| |
| bool CodecFactoryImpl::AdmitHwDecoder(const fuchsia::mediacodec::CreateDecoder_Params& params) { |
| std::vector<zx::eventpair> lifetime_eventpairs; |
| if (app_->policy().AdmitHwDecoder(params, &lifetime_eventpairs)) { |
| for (auto& eventpair : lifetime_eventpairs) { |
| lifetime_tracking_.emplace_back(std::move(eventpair)); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool CodecFactoryImpl::AdmitHwEncoder(const fuchsia::mediacodec::CreateEncoder_Params& params) { |
| std::vector<zx::eventpair> lifetime_eventpairs; |
| if (app_->policy().AdmitHwEncoder(params, &lifetime_eventpairs)) { |
| for (auto& eventpair : lifetime_eventpairs) { |
| lifetime_tracking_.emplace_back(std::move(eventpair)); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |