blob: 0158d7bf935b9abb858ef201f1fdc484e08fb606 [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_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;
}
}