blob: 2ac90b6a0d6a5c446fb2237872a92d88da72e0aa [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 <lib/media/codec_impl/codec_impl.h>
#include <fbl/macros.h>
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fit/defer.h>
#include <threads.h>
// "is_bound_checks" - In several lambdas that just send a message, we check
// is_bound() first, only because of ZX_POL_BAD_HANDLE ZX_POL_ACTION_EXCEPTION.
// If it weren't for that, we really wouldn't care about passing
// ZX_HANDLE_INVALID to zx_channel_write(), since the channel error handling is
// async (we Unbind(), sweep the in-proc send queue, and only then delete the
// Binding).
// The VLOGF() and LOGF() macros are here because we want the calls sites to
// look like FX_VLOGF and FX_LOGF, but without hard-wiring to those. For now,
// printf() seems to work fine.
#define VLOG_ENABLED 0
#if (VLOG_ENABLED)
#define VLOGF(...) printf(__VA_ARGS__)
#else
#define VLOGF(...) \
do { \
} while (0)
#endif
#define LOGF(...) printf(__VA_ARGS__)
namespace {
// The protocol does not permit an unbounded number of in-flight streams, as
// that would potentially result in unbounded data queued in the incoming
// channel with no valid circuit-breaker value for the incoming channel data.
constexpr size_t kMaxInFlightStreams = 10;
constexpr uint64_t kInputBufferConstraintsVersionOrdinal = 1;
constexpr uint64_t kInputDefaultBufferConstraintsVersionOrdinal =
kInputBufferConstraintsVersionOrdinal;
// TODO(dustingreen): Make these defaults/settings overridable per CodecAdapter
// implementation. For a few of them, maybe require the CodecAdapter to
// specify (as in no default for some of them).
constexpr uint32_t kInputPacketCountForCodecMin = 2;
// This is fairly arbitrary, but roughly speaking, 1 to be decoding, 1 to be in
// flight from the client, 1 to be in flight back to the client. We may want
// to adjust this upward if we find it's needed to keep the HW busy when there's
// any backlog.
constexpr uint32_t kInputPacketCountForCodecRecommended = 3;
constexpr uint32_t kInputPacketCountForCodecRecommendedMax = 16;
constexpr uint32_t kInputPacketCountForCodecMax = 64;
constexpr uint32_t kInputDefaultPacketCountForCodec =
kInputPacketCountForCodecRecommended;
constexpr uint32_t kInputPacketCountForClientMax =
std::numeric_limits<uint32_t>::max();
// This is fairly arbitrary, but rough speaking, 1 to be filling, 1 to be in
// flight toward the codec, and 1 to be in flight from the codec. This doesn't
// intend to be large enough to ride out any hypothetical decoder performance
// variability vs. needed decode rate.
constexpr uint32_t kInputDefaultPacketCountForClient = 3;
// TODO(dustingreen): Implement and permit single-buffer mode. (The default
// will probably remain buffer per packet mode though.)
constexpr bool kInputSingleBufferModeAllowed = false;
constexpr bool kInputDefaultSingleBufferMode = false;
// A client using the min shouldn't necessarily expect performance to be
// acceptable when running higher bit-rates.
constexpr uint32_t kInputPerPacketBufferBytesMin = 8 * 1024;
// This is fairly arbitrary, but roughly speaking, ~266 KiB for an average frame
// at 50 Mbps for 4k video, rounded up to 512 KiB buffer space per packet to
// allow most but not all frames to fit in one packet. It could be equally
// reasonable to say the average-size compressed from should barely fit in one
// packet's buffer space, or the average-size compressed frame should split to
// ~1.5 packets, but we don't want an excessive number of packets required per
// frame (not even for I frames).
constexpr uint32_t kInputPerPacketBufferBytesRecommended = 512 * 1024;
// This is an arbitrary cap for now. The only reason it's larger than
// recommended is to allow some room to profile whether larger buffer space per
// packet might be useful for performance.
constexpr uint32_t kInputPerPacketBufferBytesMax = 4 * 1024 * 1024;
constexpr uint32_t kInputDefaultPerPacketBufferBytes =
kInputPerPacketBufferBytesRecommended;
class ScopedUnlock {
public:
explicit ScopedUnlock(std::unique_lock<std::mutex>& unique_lock)
: unique_lock_(unique_lock) {
unique_lock_.unlock();
}
~ScopedUnlock() { unique_lock_.lock(); }
private:
std::unique_lock<std::mutex>& unique_lock_;
ScopedUnlock() = delete;
DISALLOW_COPY_ASSIGN_AND_MOVE(ScopedUnlock);
};
// Used within ScopedUnlock only. Normally we'd just leave a std::unique_lock
// locked until it's destructed.
class ScopedRelock {
public:
explicit ScopedRelock(std::unique_lock<std::mutex>& unique_lock)
: unique_lock_(unique_lock) {
unique_lock_.lock();
}
~ScopedRelock() { unique_lock_.unlock(); }
private:
std::unique_lock<std::mutex>& unique_lock_;
ScopedRelock() = delete;
DISALLOW_COPY_ASSIGN_AND_MOVE(ScopedRelock);
};
uint32_t PacketCountFromPortSettings(
const fuchsia::media::StreamBufferSettings& settings) {
return settings.packet_count_for_server + settings.packet_count_for_client;
}
uint32_t BufferCountFromPortSettings(
const fuchsia::media::StreamBufferSettings& settings) {
if (settings.single_buffer_mode) {
return 1;
}
return PacketCountFromPortSettings(settings);
}
} // namespace
// TODO(rjascani): Remove after soft transition complete.
CodecImpl::CodecImpl(
std::unique_ptr<CodecAdmission> codec_admission,
async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
fidl::InterfaceRequest<fuchsia::mediacodec::Codec> codec_request)
// The parameters to CodecAdapter constructor here aren't important.
: CodecAdapter(lock_, this),
codec_admission_(std::move(codec_admission)),
shared_fidl_dispatcher_(shared_fidl_dispatcher),
shared_fidl_thread_(shared_fidl_thread),
// TODO(dustingreen): Maybe have another parameter for encoder params, or
// maybe separate constructor.
decoder_params_(std::move(decoder_params)),
binding_(this, std::move(codec_request)),
stream_control_loop_(&kAsyncLoopConfigNoAttachToThread) {
// For now, decoder_params is required.
//
// TODO(dustingreen): Make decoder_params || encoder_params required.
ZX_DEBUG_ASSERT(decoder_params_);
// This is the binding_'s error handler, not the owner_error_handler_ which
// is related but separate.
binding_.set_error_handler([this](zx_status_t status) { this->Unbind(); });
initial_input_format_details_ = &decoder_params_->input_details;
}
CodecImpl::CodecImpl(
std::unique_ptr<CodecAdmission> codec_admission,
async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request)
: CodecImpl(std::move(codec_admission), shared_fidl_dispatcher,
shared_fidl_thread, std::move(decoder_params), nullptr,
std::move(codec_request)) {}
CodecImpl::CodecImpl(
std::unique_ptr<CodecAdmission> codec_admission,
async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
std::unique_ptr<fuchsia::mediacodec::CreateEncoder_Params> encoder_params,
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request)
: CodecImpl(std::move(codec_admission), shared_fidl_dispatcher,
shared_fidl_thread, nullptr, std::move(encoder_params),
std::move(codec_request)) {}
CodecImpl::CodecImpl(
std::unique_ptr<CodecAdmission> codec_admission,
async_dispatcher_t* shared_fidl_dispatcher, thrd_t shared_fidl_thread,
std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params,
std::unique_ptr<fuchsia::mediacodec::CreateEncoder_Params> encoder_params,
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> codec_request)
// The parameters to CodecAdapter constructor here aren't important.
: CodecAdapter(lock_, this),
codec_admission_(std::move(codec_admission)),
shared_fidl_dispatcher_(shared_fidl_dispatcher),
shared_fidl_thread_(shared_fidl_thread),
// TODO(dustingreen): Maybe have another parameter for encoder params, or
// maybe separate constructor.
decoder_params_(std::move(decoder_params)),
encoder_params_(std::move(encoder_params)),
// tmp_interface_request_(std::move(codec_request)),
binding_(this, std::move(codec_request)),
stream_control_loop_(&kAsyncLoopConfigNoAttachToThread) {
ZX_DEBUG_ASSERT(!!decoder_params_ ^ !!encoder_params_);
// This is the binding_'s error handler, not the owner_error_handler_ which
// is related but separate.
binding_.set_error_handler([this](zx_status_t status) { this->Unbind(); });
initial_input_format_details_ = decoder_params_
? &decoder_params_->input_details
: &encoder_params_->input_format;
}
CodecImpl::~CodecImpl() {
// We need ~binding_ to run on fidl_thread() else it's not safe to
// un-bind unilaterally. Unless not ever bound in the first place.
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
ZX_DEBUG_ASSERT(was_unbind_started_ && was_unbind_completed_ ||
!was_logically_bound_);
// Ensure the CodecAdmission is deleted entirely after ~this, including after
// any relevant base class destructors have run.
PostSerial(shared_fidl_dispatcher_,
[codec_admission = std::move(codec_admission_),
codec_to_close = std::move(codec_to_close_)]() mutable {
// Ensure codec_to_close is destructed only after the
// codec_admission is destructed. We have to be fairly explicit
// about this since the order of lambda members is explicitly
// unspecified in C++, so their destruction order is also
// unspecified.
codec_admission.reset();
// ~codec_to_close (after ~CodecAdmission above).
});
}
std::mutex& CodecImpl::lock() { return lock_; }
void CodecImpl::SetCoreCodecAdapter(
std::unique_ptr<CodecAdapter> codec_adapter) {
ZX_DEBUG_ASSERT(!codec_adapter_);
codec_adapter_ = std::move(codec_adapter);
}
void CodecImpl::BindAsync(fit::closure error_handler) {
// While it would potentially be safe to call Bind() from a thread other than
// fidl_thread(), we have no reason to permit that.
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
// Up to once only. No re-use.
ZX_DEBUG_ASSERT(!was_bind_async_called_);
ZX_DEBUG_ASSERT(!binding_.is_bound());
// TODO(rjascani): Uncomment after soft transition complete.
// ZX_DEBUG_ASSERT(tmp_interface_request_);
was_bind_async_called_ = true;
zx_status_t start_thread_result = stream_control_loop_.StartThread(
"StreamControl_loop", &stream_control_thread_);
if (start_thread_result != ZX_OK) {
// Handle the error async, to be consistent with later errors that must
// occur async anyway. Inability to start StreamControl is the only case
// where we just allow the owner to "delete this" without using
// UnbindLocked(), since UnbindLocked() relies on StreamControl.
PostToSharedFidl(std::move(error_handler));
return;
}
// From here on, we'll only fail the CodecImpl via UnbindLocked().
was_logically_bound_ = true;
// This doesn't really need to be set until the start of the posted lambda
// below, but here is also fine.
owner_error_handler_ = std::move(error_handler);
// Do most of the bind work on StreamControl async, since CoreCodecInit()
// might potentially take a little while longer than makes sense to run on
// fidl_thread(). Potential examples: if CoreCodecInit() ends up
// essentially evicting some other CodecImpl, or if setting up HW can take a
// while, or if getting a scheduling slot on decode HW can require some
// waiting, or similar.
PostToStreamControl([this] {
// This is allowed to take a little while if necessary, using the current
// StreamControl thread, which is not shared with any other CodecImpl.
CoreCodecInit(*initial_input_format_details_);
is_core_codec_init_called_ = true;
// We touch FIDL stuff only from the fidl_thread(). While it would
// be more efficient to post once to bind and send up to two messages below,
// by posting individually we can share more code and have simpler rules for
// calling that code.
// Once this is posted, we can be dispatching incoming FIDL messages,
// concurrent with the rest of the current lambda. Aside from Sync(), most
// of that dispatching would tend to land in FailLocked(). The concurrency
// is just worth keeping in mind for the rest of the current lambda is all.
PostToSharedFidl([this] {
// TODO(rjascani): Uncomment after soft transition complete.
// zx_status_t bind_result =
// binding_.Bind(std::move(tmp_interface_request_),
// shared_fidl_dispatcher_);
zx_status_t bind_result = binding_.Bind(shared_fidl_dispatcher_);
if (bind_result != ZX_OK) {
Fail("binding_.Bind() failed");
return;
}
// TODO(rjascani): Uncomment after soft transition complete.
// ZX_DEBUG_ASSERT(!tmp_interface_request_);
});
input_constraints_ =
std::make_unique<fuchsia::media::StreamBufferConstraints>(
fuchsia::media::StreamBufferConstraints{
.buffer_constraints_version_ordinal =
kInputBufferConstraintsVersionOrdinal,
// This is not really a suggestion; actual values must be odd,
// and the client should be the source of this value.
.default_settings.buffer_lifetime_ordinal = 0,
.default_settings.buffer_constraints_version_ordinal =
kInputDefaultBufferConstraintsVersionOrdinal,
.default_settings.packet_count_for_server =
kInputDefaultPacketCountForCodec,
.default_settings.packet_count_for_client =
kInputDefaultPacketCountForClient,
.default_settings.per_packet_buffer_bytes =
kInputDefaultPerPacketBufferBytes,
.default_settings.single_buffer_mode =
kInputDefaultSingleBufferMode,
.per_packet_buffer_bytes_min = kInputPerPacketBufferBytesMin,
.per_packet_buffer_bytes_recommended =
kInputPerPacketBufferBytesRecommended,
.per_packet_buffer_bytes_max = kInputPerPacketBufferBytesMax,
.packet_count_for_server_min = kInputPacketCountForCodecMin,
.packet_count_for_server_recommended =
kInputPacketCountForCodecRecommended,
.packet_count_for_server_recommended_max =
kInputPacketCountForCodecRecommendedMax,
.packet_count_for_server_max = kInputPacketCountForCodecMax,
.packet_count_for_client_max = kInputPacketCountForClientMax,
.single_buffer_mode_allowed = kInputSingleBufferModeAllowed,
});
// If/when this sends OnOutputConfig(), it posts to do so.
onInputConstraintsReady();
sent_buffer_constraints_version_ordinal_[kInputPort] =
kInputBufferConstraintsVersionOrdinal;
PostToSharedFidl([this] {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnInputConstraints(fidl::Clone(*input_constraints_));
}
});
});
}
void CodecImpl::EnableOnStreamFailed() {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
is_on_stream_failed_enabled_ = true;
}
void CodecImpl::SetInputBufferSettings(
fuchsia::media::StreamBufferSettings input_settings) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
PostToStreamControl([this, input_settings = std::move(input_settings)] {
SetInputBufferSettings_StreamControl(std::move(input_settings));
});
}
void CodecImpl::SetInputBufferSettings_StreamControl(
fuchsia::media::StreamBufferSettings input_settings) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
if (IsStreamActiveLocked()) {
Fail("client sent SetInputBufferSettings() with stream active");
return;
}
SetBufferSettingsCommon(lock, kInputPort, input_settings,
*input_constraints_);
} // ~lock
}
void CodecImpl::AddInputBuffer(fuchsia::media::StreamBuffer buffer) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
PostToStreamControl([this, buffer = std::move(buffer)]() mutable {
AddInputBuffer_StreamControl(std::move(buffer));
});
}
void CodecImpl::AddInputBuffer_StreamControl(
fuchsia::media::StreamBuffer buffer) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
if (IsStopping()) {
return;
}
// We must check, because __WARN_UNUSED_RESULT, and it's worth it for the
// enforcement and consistency.
if (!AddBufferCommon(kInputPort, std::move(buffer))) {
return;
}
}
void CodecImpl::SetOutputBufferSettings(
fuchsia::media::StreamBufferSettings output_settings) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!output_config_) {
// invalid client behavior
//
// client must have received at least the initial OnOutputConfig() first
// before sending SetOutputBufferSettings().
FailLocked(
"client sent SetOutputBufferSettings() when no output_config_");
return;
}
// For a mid-stream output format change, this also enforces that the client
// can only catch up to the mid-stream format change once. In other words,
// if the client has already caught up to the mid-stream config change, the
// client no longer has an excuse to re-configure again with a stream
// active.
//
// There's a check in SetBufferSettingsCommonLocked() that ignores this
// message if the client's buffer_constraints_version_ordinal is behind
// last_required_buffer_constraints_version_ordinal_, which gets updated
// under the same lock hold interval as the server's de-configuring of
// output buffers.
//
// There's a check in SetBufferSettingsCommonLocked() that closes the
// channel if the client is sending a buffer_constraints_version_ordinal
// that's newer than the last sent_buffer_constraints_version_ordinal_.
if (IsOutputConfiguredLocked() && IsStreamActiveLocked()) {
FailLocked(
"client sent SetOutputBufferSettings() with IsStreamActiveLocked() + "
"already-configured output");
return;
}
SetBufferSettingsCommon(lock, kOutputPort, output_settings,
output_config_->buffer_constraints);
} // ~lock
}
void CodecImpl::AddOutputBuffer(fuchsia::media::StreamBuffer buffer) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
bool output_done_configuring =
AddBufferCommon(kOutputPort, std::move(buffer));
if (output_done_configuring) {
// The StreamControl domain _might_ be waiting for output to be configured.
wake_stream_control_condition_.notify_all();
}
}
void CodecImpl::FlushEndOfStreamAndCloseStream(
uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!EnsureFutureStreamFlushSeenLocked(stream_lifetime_ordinal)) {
return;
}
}
PostToStreamControl([this, stream_lifetime_ordinal] {
FlushEndOfStreamAndCloseStream_StreamControl(stream_lifetime_ordinal);
});
}
void CodecImpl::FlushEndOfStreamAndCloseStream_StreamControl(
uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
// We re-check some things which were already future-verified a different
// way, to allow for flexibility in the future-tracking stuff to permit less
// checking in the Output ordering domain (fidl_thread()) without
// breaking overall verification of a flush. Any checking in the Output
// ordering domain is for the future-tracking's own convenience only. The
// checking here is the real checking.
if (!CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal)) {
return;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal >= stream_lifetime_ordinal_);
if (!IsStreamActiveLocked() ||
stream_lifetime_ordinal != stream_lifetime_ordinal_) {
// TODO(dustingreen): epitaph
FailLocked(
"FlushEndOfStreamAndCloseStream() only valid on an active current "
"stream (flush does not auto-create a new stream)");
return;
}
// At this point we know that the stream is not discarded, and not already
// flushed previously (because flush will discard the stream as there's
// nothing more that the stream is permitted to do).
ZX_DEBUG_ASSERT(stream_);
ZX_DEBUG_ASSERT(stream_->stream_lifetime_ordinal() ==
stream_lifetime_ordinal);
if (!stream_->input_end_of_stream()) {
FailLocked(
"FlushEndOfStreamAndCloseStream() is only permitted after "
"QueueInputEndOfStream()");
return;
}
while (!stream_->output_end_of_stream()) {
// While waiting, we'll continue to send OnOutputPacket(),
// OnOutputConfig(), and continue to process RecycleOutputPacket(), until
// the client catches up to the latest config (as needed) and we've
// started the send of output end_of_stream packet to the client.
//
// There is no way for the client to cancel a
// FlushEndOfStreamAndCloseStream() short of closing the Codec channel.
// Before long, the server will either send the OnOutputEndOfStream(), or
// will send OnOmxStreamFailed(), or will close the Codec channel. The
// server must do one of those things before long (not allowed to get
// stuck while flushing).
//
// Some core codecs (such as OMX codecs) have no way to report mid-stream
// input data corruption errors or similar without it being a stream
// failure, so if there's any stream error it turns into OnStreamFailed().
// It's also permitted for a server to set error_detected_ bool(s) on
// output packets and send OnOutputEndOfStream() despite detected errors,
// but this is only a reasonable behavior for the server if the server
// normally would detect and report mid-stream input corruption errors
// without an OnStreamFailed().
output_end_of_stream_seen_.wait(lock);
}
// Now that flush is done, we close the current stream because there is not
// any subsequent message for the current stream that's valid.
EnsureStreamClosed(lock);
} // ~lock
}
void CodecImpl::CloseCurrentStream(uint64_t stream_lifetime_ordinal,
bool release_input_buffers,
bool release_output_buffers) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!EnsureFutureStreamCloseSeenLocked(stream_lifetime_ordinal)) {
return;
}
} // ~lock
PostToStreamControl([this, stream_lifetime_ordinal, release_input_buffers,
release_output_buffers] {
CloseCurrentStream_StreamControl(
stream_lifetime_ordinal, release_input_buffers, release_output_buffers);
});
}
void CodecImpl::CloseCurrentStream_StreamControl(
uint64_t stream_lifetime_ordinal, bool release_input_buffers,
bool release_output_buffers) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
EnsureStreamClosed(lock);
if (release_input_buffers) {
EnsureBuffersNotConfigured(lock, kInputPort);
}
if (release_output_buffers) {
EnsureBuffersNotConfigured(lock, kOutputPort);
}
}
void CodecImpl::Sync(SyncCallback callback) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
// By posting to StreamControl ordering domain, we sync both Output ordering
// domain (on fidl_thread()) and the StreamControl ordering domain.
PostToStreamControl([this, callback = std::move(callback)]() mutable {
Sync_StreamControl(std::move(callback));
});
}
void CodecImpl::Sync_StreamControl(SyncCallback callback) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
if (IsStopping()) {
// In this case ~callback will happen instead of callback(), in which case
// the response won't be sent, which is appropriate - the channel is getting
// closed soon instead, and the client has to tolerate that.
return;
}
callback();
}
void CodecImpl::RecycleOutputPacket(
fuchsia::media::PacketHeader available_output_packet) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
CodecPacket* packet = nullptr;
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!CheckOldBufferLifetimeOrdinalLocked(
kOutputPort, available_output_packet.buffer_lifetime_ordinal)) {
return;
}
if (available_output_packet.buffer_lifetime_ordinal <
buffer_lifetime_ordinal_[kOutputPort]) {
// ignore arbitrarily-stale required by protocol
//
// Thanks to even values from the client being prohibited, this also
// covers mid-stream output config change where the server has already
// de-configured output buffers but the client doesn't know about that
// yet. We include that case here by setting
// buffer_lifetime_ordinal_[kOutputPort] to the next even value
// when de-configuring output server-side until the client has
// re-configured output.
return;
}
ZX_DEBUG_ASSERT(available_output_packet.buffer_lifetime_ordinal ==
buffer_lifetime_ordinal_[kOutputPort]);
if (!IsOutputConfiguredLocked()) {
FailLocked(
"client sent RecycleOutputPacket() for buffer_lifetime_ordinal that "
"isn't fully configured yet - bad client behavior");
return;
}
ZX_DEBUG_ASSERT(IsOutputConfiguredLocked());
if (available_output_packet.packet_index >=
all_packets_[kOutputPort].size()) {
FailLocked(
"out of range packet_index from client in RecycleOutputPacket()");
return;
}
uint32_t packet_index = available_output_packet.packet_index;
if (all_packets_[kOutputPort][packet_index]->is_free()) {
FailLocked(
"packet_index already free at protocol level - invalid client "
"message");
return;
}
// Mark free at protocol level.
all_packets_[kOutputPort][packet_index]->SetFree(true);
// Before handing the packet to the core codec, clear some fields that the
// core codec is expected to set (or optionally set in the case of
// timestamp_ish). In addition to these parameters, a core codec can emit
// output config changes via onCoreCodecMidStreamOutputConfigChange().
packet = all_packets_[kOutputPort][packet_index].get();
packet->ClearStartOffset();
packet->ClearValidLengthBytes();
packet->ClearTimestampIsh();
}
// Recycle to core codec.
CoreCodecRecycleOutputPacket(packet);
}
void CodecImpl::QueueInputFormatDetails(
uint64_t stream_lifetime_ordinal,
fuchsia::media::FormatDetails format_details) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!EnsureFutureStreamSeenLocked(stream_lifetime_ordinal)) {
return;
}
} // ~lock
PostToStreamControl([this, stream_lifetime_ordinal,
format_details = std::move(format_details)]() mutable {
QueueInputFormatDetails_StreamControl(stream_lifetime_ordinal,
std::move(format_details));
});
}
// TODO(dustingreen): Need test coverage for this method, to cover at least
// the same format including OOB bytes as were specified during codec creation,
// and codec creation with no OOB bytes then this method setting OOB bytes (not
// the ideal client usage pattern in the long run since the CreateDecoder()
// might decline to provide a optimized but partial Codec implementation, but
// should be allowed nonetheless).
void CodecImpl::QueueInputFormatDetails_StreamControl(
uint64_t stream_lifetime_ordinal,
fuchsia::media::FormatDetails format_details) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
if (!CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal)) {
return;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal >= stream_lifetime_ordinal_);
if (stream_lifetime_ordinal > stream_lifetime_ordinal_) {
if (!StartNewStream(lock, stream_lifetime_ordinal)) {
return;
}
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal == stream_lifetime_ordinal_);
if (stream_->input_end_of_stream()) {
FailLocked(
"QueueInputFormatDetails() after QueueInputEndOfStream() unexpected");
return;
}
if (stream_->future_discarded()) {
// No reason to handle since the stream is future-discarded.
return;
}
stream_->SetInputFormatDetails(
std::make_unique<fuchsia::media::FormatDetails>(
std::move(format_details)));
// SetOobConfigPending(true) to ensure oob_config_pending() is true.
//
// This call is needed only to properly handle a call to
// QueueInputFormatDetails() mid-stream. For new streams that lack any calls
// to QueueInputFormatDetails() before an input packet arrives, the
// oob_config_pending() will already be true because it starts true for a new
// stream. For QueueInputFormatDetails() at the start of a stream before any
// packets, oob_config_pending() will already be true.
//
// For decoders this is basically a pending oob_bytes. For encoders
// this pending config change can potentially include uncompressed format
// details, if mid-stream format change is supported by the encoder.
stream_->SetOobConfigPending(true);
}
void CodecImpl::QueueInputPacket(fuchsia::media::Packet packet) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
if (!EnsureFutureStreamSeenLocked(packet.stream_lifetime_ordinal)) {
return;
}
} // ~lock
PostToStreamControl([this, packet = std::move(packet)]() mutable {
QueueInputPacket_StreamControl(std::move(packet));
});
}
void CodecImpl::QueueInputPacket_StreamControl(fuchsia::media::Packet packet) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
fuchsia::media::PacketHeader temp_header_copy = fidl::Clone(packet.header);
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
// Unless we cancel this cleanup, we'll free the input packet back to the
// client.
auto send_free_input_packet_locked =
fit::defer([this, header = std::move(temp_header_copy)]() mutable {
// Mute sending this if FailLocked() was called previously, in case
// the reason we're here is something horribly wrong with the packet
// header. This way we avoid repeating gibberish back to the client.
// While that gibberish might be a slight clue for debugging in some
// cases, it's not valid protocol, so don't send it. If
// IsStoppingLocked(), the Codec channel will close soon, making this
// response unnecessary.
if (!IsStoppingLocked()) {
SendFreeInputPacketLocked(std::move(header));
}
});
if (!CheckOldBufferLifetimeOrdinalLocked(
kInputPort, packet.header.buffer_lifetime_ordinal)) {
return;
}
// For input, mid-stream config changes are not a thing and input buffers
// are never unilaterally de-configured by the Codec server.
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kInputPort] ==
port_settings_[kInputPort]->buffer_lifetime_ordinal);
// For this message we're extra-strict re. buffer_lifetime_ordinal, at least
// for now.
//
// In contrast to output, the server doesn't use even values to track config
// changes that the client doesn't know about yet, since the server can't
// unilaterally demand any changes to the input settings after initially
// specifying the input constraints.
//
// One could somewhat-convincingly argue that this field in this particular
// message is a bit pointless, but it might serve to detect client-side
// bugs faster thanks to this check.
if (packet.header.buffer_lifetime_ordinal !=
port_settings_[kInputPort]->buffer_lifetime_ordinal) {
FailLocked(
"client QueueInputPacket() with invalid buffer_lifetime_ordinal.");
return;
}
if (!CheckStreamLifetimeOrdinalLocked(packet.stream_lifetime_ordinal)) {
return;
}
ZX_DEBUG_ASSERT(packet.stream_lifetime_ordinal >= stream_lifetime_ordinal_);
if (packet.stream_lifetime_ordinal > stream_lifetime_ordinal_) {
// This case implicitly starts a new stream. If the client wanted to
// ensure that the old stream would be fully processed, the client would
// have sent FlushEndOfStreamAndCloseStream() previously, whose
// processing (previous to reaching here) takes care of the flush.
//
// Start a new stream, synchronously.
if (!StartNewStream(lock, packet.stream_lifetime_ordinal)) {
return;
}
}
ZX_DEBUG_ASSERT(packet.stream_lifetime_ordinal == stream_lifetime_ordinal_);
if (!IsInputConfiguredLocked()) {
FailLocked("client QueueInputPacket() with input buffers not configured");
return;
}
if (packet.header.packet_index >= all_packets_[kInputPort].size()) {
FailLocked("client QueueInputPacket() with packet_index out of range");
return;
}
if (packet.buffer_index >= all_buffers_[kInputPort].size()) {
FailLocked("client QueueInputPacket() with buffer_index out of range");
return;
}
// Protocol check re. free/busy coherency. This applies to packets only,
// not buffers.
if (!all_packets_[kInputPort][packet.header.packet_index]->is_free()) {
FailLocked("client QueueInputPacket() with packet_index !free");
return;
}
all_packets_[kInputPort][packet.header.packet_index]->SetFree(false);
if (stream_->input_end_of_stream()) {
FailLocked("QueueInputPacket() after QueueInputEndOfStream() unexpeted");
return;
}
if (stream_->future_discarded()) {
// Don't queue to core codec. The stream_ may have never fully started,
// or may have been future-discarded since. Either way, skip queueing to
// the core codec.
//
// If the stream didn't fully start - as in, the client moved on to
// another stream before fully configuring output, then the core codec is
// not presently in a state compatible with queueing input, but the Codec
// interface is. So in that case, we must avoid queueing to the core
// codec for correctness.
//
// If the stream was just future-discarded after fully starting, then this
// is just an optimization to avoid giving the core codec more work to do
// for a stream the client has already discarded.
//
// ~send_free_input_packet_locked
// ~lock
return;
}
// Sending OnFreeInputPacket() will happen later instead, when the core
// codec gives back the packet.
send_free_input_packet_locked.cancel();
} // ~lock
if (stream_->oob_config_pending()) {
HandlePendingInputFormatDetails();
stream_->SetOobConfigPending(false);
}
CodecPacket* core_codec_packet =
all_packets_[kInputPort][packet.header.packet_index].get();
core_codec_packet->SetBuffer(
all_buffers_[kInputPort][packet.buffer_index].get());
core_codec_packet->SetStartOffset(packet.start_offset);
core_codec_packet->SetValidLengthBytes(packet.valid_length_bytes);
if (packet.has_timestamp_ish) {
core_codec_packet->SetTimstampIsh(packet.timestamp_ish);
} else {
core_codec_packet->ClearTimestampIsh();
}
// We don't need to be under lock for this, because the fact that we're on the
// StreamControl domain is enough to guarantee that any other control of the
// core codec will occur after this.
CoreCodecQueueInputPacket(core_codec_packet);
}
void CodecImpl::QueueInputEndOfStream(uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (!EnsureFutureStreamSeenLocked(stream_lifetime_ordinal)) {
return;
}
} // ~lock
PostToStreamControl([this, stream_lifetime_ordinal] {
QueueInputEndOfStream_StreamControl(stream_lifetime_ordinal);
});
}
void CodecImpl::QueueInputEndOfStream_StreamControl(
uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
return;
}
if (!CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal)) {
return;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal >= stream_lifetime_ordinal_);
if (stream_lifetime_ordinal > stream_lifetime_ordinal_) {
// We start a new stream given an end-of-stream for a stream we've not
// seen before, since allowing empty streams to not be errors may be nicer
// to use.
if (!StartNewStream(lock, stream_lifetime_ordinal)) {
return;
}
}
if (stream_->input_end_of_stream()) {
FailLocked("client already sent QueueInputEndOfStream() for this stream");
return;
}
stream_->SetInputEndOfStream();
if (stream_->future_discarded()) {
// Don't queue to OMX. The stream_ may have never fully started, or may
// have been future-discarded since. Either way, skip queueing to OMX. We
// only really must do this because the stream may not have ever fully
// started, in the case where the client moves on to a new stream before
// catching up to latest output config.
return;
}
} // ~lock
CoreCodecQueueInputEndOfStream();
}
void CodecImpl::onInputConstraintsReady() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
if (!IsCoreCodecRequiringOutputConfigForFormatDetection()) {
return;
}
std::unique_lock<std::mutex> lock(lock_);
StartIgnoringClientOldOutputConfig(lock);
GenerateAndSendNewOutputConfig(lock, true);
}
void CodecImpl::UnbindLocked() {
// We must have first gotten far enough through BindAsync() before calling
// UnbindLocked().
ZX_DEBUG_ASSERT(was_logically_bound_);
if (was_unbind_started_) {
// Ignore the second trigger if we have a near-simultaneous failure from
// StreamControl thread (for example) and from fidl_thread() (for
// example). The first will start unbinding, and the second will be
// ignored. Since completion of the Unbind() call doesn't imply anything
// about how done the unbind is, there's no need for the second caller to
// be blocked waiting for the first caller's unbind to be done.
return;
}
was_unbind_started_ = true;
wake_stream_control_condition_.notify_all();
// Unbind() / UnbindLocked() can be called from any thread.
//
// Regardless of what thread UnbindLocked() is called on, "this" will remain
// allocated at least until the caller of UnbindLocked() releases lock_.
//
// The shutdown sequence here is meant to be general enough to accommodate
// code changes without being super brittle. Not all the potential cases
// accounted for in this sequence can necessarily happen currently, but it
// seems good to stop all activity in a way that'll hold up even if a change
// posts another lambda or similar.
//
// In all cases, this posted lambda runs after BindAsync()'s work that's
// posted to StreamControl, because any/all calls to UnbindLocked() happen
// after BindAsync() has posted to StreamControl.
PostToStreamControl([this] {
// At this point we know that no more streams will be started by
// StreamControl ordering domain (thanks to was_unbind_started_ /
// IsStoppingLocked() checks), but lambdas posted to the StreamControl
// ordering domain (by the fidl_thread() or by core codec) may still
// be creating other activity such as posting lambdas to StreamControl or
// fidl_thread().
//
// There are two purposes to this lock acquire, one of which is subtle.
//
// This lock acquire also delays execution here until the caller of
// UnbindLocked() has released lock_. This delay is nice to do on the
// stream control thread instead of later on the fidl_thread(), and
// we need the lock here to call EnsureStreamClosed() anyway.
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
// Stop core codec associated with this CodecImpl, partly to make sure it
// stops running code that could make calls into this CodecImpl, and
// partly to ensure the core codec isn't in the middle of anything when it
// gets deleted.
//
// We know the core codec won't start more activity because the core codec
// isn't allowed to initiate actions while there's no active stream, and
// because no new active stream will be created. All _StreamControl
// methods check IsStoppingLocked() at the start, and the StreamControl
// ordering domain is the only domain that ever starts a stream.
//
// We intentionally don't check for IsStoppingLocked() in protocol
// dispatch methods running on fidl_thread(). For example the codec
// must tolerate calls to configure buffers after EnsureStreamClosed()
// here. The Unbind() later is what silences the protocol message
// dispatch methods. Checking for IsStoppingLocked() in protocol dispatch
// methods would only decrease the probability of certain event orderings,
// not eliminate those orderings, so it's actually better to let them
// happen to get more coverage of those orderings.
if (is_core_codec_init_called_) {
EnsureStreamClosed(lock);
}
} // ~lock
PostToSharedFidl([this] {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
// If not being called from binding_'s error handler, unbind from the
// channel so we won't see any more incoming FIDL messages. This binding
// doesn't own "this".
//
// The Unbind() stops any additional FIDL dispatching re. this CodecImpl,
// but it doesn't stop lambdas re. this CodecImpl from being queued to
// fidl_thread(). Potentially such lambdas can be coming from
// StreamControl domain still at this point (even after the Unbind()).
// Those which are sending a FIDL message can omit their send if binding_
// is no longer bound, but they need binding_ to still be allocated when
// they run.
//
// We close the Codec channel only after ~CodecAdmission has freed up the
// concurrency tally, so that a client that re-tries on channel closure
// can retry immediately without potentially bouncing off still-existing
// old CodecAdmission.
if (binding_.is_bound()) {
codec_to_close_ = binding_.Unbind().TakeChannel();
}
// We need to shut down the StreamControl thread, which can be shut down
// quickly (it's not waiting any significant duration on anything) thanks
// to was_unbind_started_ and wake_stream_control_condition_. Normally
// the fidl_thread() waiting for the StreamControl thread to do
// anything would be bad, because the fidl_thread() is non-blocking
// and the StreamControl thread can block on stuff, but StreamControl
// thread behavior after was_unbind_started_ = true and
// wake_stream_control_condition_.notify_all() does not block and does not
// wait on fidl_thread(). So in this case it's ok to wait here.
stream_control_loop_.Quit();
stream_control_loop_.JoinThreads();
// This is when we first know that StreamControl can't be queueing any
// more lambdas re. this CodecImpl toward fidl_thread(). (We
// already know the core codec isn't queuing any more). If any lambdas
// are queued to StreamControl at/beyond this point, we rely on those
// being safe to just delete.
stream_control_loop_.Shutdown();
// Before calling the owner_error_handler_, we declare that unbind is
// done so that during the destructor we can check that unbind is done.
was_unbind_completed_ = true;
// This post ensures that any other items posted to the
// fidl_thread() for this CodecImpl run before "delete this". By
// the time we post here, we know that no further lambdas will be posted
// to fidl_thread() regarding this CodecImpl other than this post
// itself - specifically:
// * The core codec has been stopped, in the sense that it has no
// current stream. The core codec is required to be delete-able when
// it has no current stream, and required not to asynchronously post
// more work to the CodecImpl (because calling onCoreCodec... methods
// is not allowed when there is no current stream).
// * The binding_.Unbind() has run, so no more FIDL dispatching to this
// CodecImpl.
// * The stream_control_loop_.JoinThreads() has run, so no more posting
// from the stream_control_thread_ since it's no longer running.
// * The previous bullets are the complete list of sources of items
// posted to the fidl_thread() regarding this CodecImpl.
//
// By posting to run _after_ any of the above sources, we know that by the
// time this posted lambda runs, the "delete this" in this lambda will be
// after any other posted lambdas.
//
// For example, any lambdas previously posted to send a message via
// this->binding_ (which is soon to be deleted) will run before the lambda
// posted here.
//
// This relies on other previously-posted _lambdas_ running on
// fidl_thread() re. this CodecImpl to not re-post to the fidl_thread().
// In contrast, it is ok if a FIDL dispatch (on FIDL thread) re-posts to
// the fidl_thread(); this is ok because we've already stopped any more
// FIDL dispatching by binding_.Unbind() above.
PostSerial(shared_fidl_dispatcher_,
[client_error_handler = std::move(owner_error_handler_)] {
// This call deletes the CodecImpl.
client_error_handler();
});
// "this" will be deleted shortly async when lambda posted just above
// runs.
return;
});
});
// "this" remains allocated until caller releases lock_.
}
void CodecImpl::Unbind() {
std::lock_guard<std::mutex> lock(lock_);
UnbindLocked();
// ~lock
//
// "this" may be deleted very shortly after ~lock, depending on what thread
// Unbind() is called from.
}
bool CodecImpl::IsStreamActiveLocked() {
return stream_lifetime_ordinal_ % 2 == 1;
}
void CodecImpl::SetBufferSettingsCommon(
std::unique_lock<std::mutex>& lock, CodecPort port,
const fuchsia::media::StreamBufferSettings& settings,
const fuchsia::media::StreamBufferConstraints& constraints) {
ZX_DEBUG_ASSERT(port == kInputPort &&
thrd_current() == stream_control_thread_ ||
port == kOutputPort && thrd_current() == fidl_thread());
ZX_DEBUG_ASSERT(!IsStoppingLocked());
// Invariant
//
// Either we've never seen settings, or the logical buffer_lifetime_ordinal_
// is either the last accepted from the client or one more than that as a way
// of cleanly permitting the server to unilaterally de-configure output
// buffers.
ZX_DEBUG_ASSERT(
(!port_settings_[port && buffer_lifetime_ordinal_[port] == 0]) ||
(buffer_lifetime_ordinal_[port] >=
port_settings_[port]->buffer_lifetime_ordinal &&
buffer_lifetime_ordinal_[port] <=
port_settings_[port]->buffer_lifetime_ordinal + 1));
if (settings.buffer_lifetime_ordinal <=
protocol_buffer_lifetime_ordinal_[port]) {
FailLocked(
"settings.buffer_lifetime_ordinal <= "
"protocol_buffer_lifetime_ordinal_[port] - port: %d",
port);
return;
}
protocol_buffer_lifetime_ordinal_[port] = settings.buffer_lifetime_ordinal;
if (settings.buffer_lifetime_ordinal % 2 == 0) {
FailLocked(
"Only odd values for buffer_lifetime_ordinal are permitted - port: %d "
"value %lu",
port, settings.buffer_lifetime_ordinal);
return;
}
if (settings.buffer_constraints_version_ordinal >
sent_buffer_constraints_version_ordinal_[port]) {
FailLocked(
"Client sent too-new buffer_constraints_version_ordinal - port: %d",
port);
return;
}
if (settings.buffer_constraints_version_ordinal <
last_required_buffer_constraints_version_ordinal_[port]) {
// ignore - client will probably catch up later
return;
}
// We've peeled off too new and too old above.
ZX_DEBUG_ASSERT(settings.buffer_constraints_version_ordinal >=
last_required_buffer_constraints_version_ordinal_[port] &&
settings.buffer_constraints_version_ordinal <=
sent_buffer_constraints_version_ordinal_[port]);
// We've already checked above that the buffer_lifetime_ordinal is in
// sequence.
ZX_DEBUG_ASSERT(!port_settings_[port] || settings.buffer_lifetime_ordinal >
buffer_lifetime_ordinal_[port]);
if (!ValidateBufferSettingsVsConstraintsLocked(port, settings, constraints)) {
// This assert is safe only because this thread still holds lock_.
ZX_DEBUG_ASSERT(IsStoppingLocked());
return;
}
// Little if any reason to do this outside the lock.
EnsureBuffersNotConfigured(lock, port);
// This also starts the new buffer_lifetime_ordinal.
port_settings_[port] = std::make_unique<fuchsia::media::StreamBufferSettings>(
std::move(settings));
buffer_lifetime_ordinal_[port] =
port_settings_[port]->buffer_lifetime_ordinal;
}
void CodecImpl::EnsureBuffersNotConfigured(std::unique_lock<std::mutex>& lock,
CodecPort port) {
// This method can be called on input only if there's no current stream.
//
// On output, this method can be called if there's no current stream or if
// we're in the middle of an output config change.
//
// On input, this can only be called on stream_control_thread_.
//
// On output, this can be called on stream_control_thread_ or output_thread_.
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_ ||
(port == kOutputPort && (thrd_current() == fidl_thread())));
is_port_configured_[port] = false;
// Ensure that buffers aren't with the core codec.
{ // scope unlock
ScopedUnlock unlock(lock);
CoreCodecEnsureBuffersNotConfigured(port);
}
// For mid-stream output config change, the caller is responsible for ensuring
// that buffers are not with the HW first.
//
// TODO(dustingreen): Check anything relevant to buffers not presently being
// with the HW.
// ZX_DEBUG_ASSERT(all_packets_[port].empty() ||
// !all_packets_[port][0]->is_with_hw());
all_packets_[port].clear();
all_buffers_[port].clear();
ZX_DEBUG_ASSERT(all_packets_[port].empty());
ZX_DEBUG_ASSERT(all_buffers_[port].empty());
}
bool CodecImpl::ValidateBufferSettingsVsConstraintsLocked(
CodecPort port, const fuchsia::media::StreamBufferSettings& settings,
const fuchsia::media::StreamBufferConstraints& constraints) {
if (settings.packet_count_for_server <
constraints.packet_count_for_server_min) {
FailLocked("packet_count_for_server < packet_count_for_server_min");
return false;
}
if (settings.packet_count_for_server >
constraints.packet_count_for_server_max) {
FailLocked("packet_count_for_server > packet_count_for_server_max");
return false;
}
if (settings.packet_count_for_client >
constraints.packet_count_for_client_max) {
FailLocked("packet_count_for_client > packet_count_for_client_max");
return false;
}
if (settings.per_packet_buffer_bytes <
constraints.per_packet_buffer_bytes_min) {
FailLocked("per_packet_buffer_bytes < per_packet_buffer_bytes_min");
return false;
}
if (settings.per_packet_buffer_bytes >
constraints.per_packet_buffer_bytes_max) {
FailLocked("per_packet_buffer_bytes > per_packet_buffer_bytes_max");
return false;
}
if (settings.single_buffer_mode && !constraints.single_buffer_mode_allowed) {
FailLocked("single_buffer_mode && !single_buffer_mode_allowed");
return false;
}
return true;
}
bool CodecImpl::AddBufferCommon(CodecPort port,
fuchsia::media::StreamBuffer buffer) {
ZX_DEBUG_ASSERT(port == kInputPort &&
(thrd_current() == stream_control_thread_) ||
port == kOutputPort && (thrd_current() == fidl_thread()));
bool done_configuring = false;
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (buffer.buffer_lifetime_ordinal % 2 == 0) {
FailLocked(
"Client sent even buffer_lifetime_ordinal, but must be odd - exiting "
"- port: %u\n",
port);
return false;
}
if (buffer.buffer_lifetime_ordinal !=
protocol_buffer_lifetime_ordinal_[port]) {
FailLocked(
"Incoherent SetOutputBufferSettings()/SetInputBufferSettings() + "
"AddOutputBuffer()/AddInputBuffer()s - exiting - port: %d\n",
port);
return false;
}
// If the server is not interested in the client's buffer_lifetime_ordinal,
// the client's buffer_lifetime_ordinal won't match the server's
// buffer_lifetime_ordinal_. The client will probably later catch up.
if (buffer.buffer_lifetime_ordinal != buffer_lifetime_ordinal_[port]) {
// The case that ends up here is when a client's output configuration
// (whole or last part) is being ignored because it's not yet caught up
// with last_required_buffer_constraints_version_ordinal_.
// This case won't happen for input, at least for now. This is an assert
// rather than a client behavior check, because previous client protocol
// checks have already peeled off any invalid client behavior that might
// otherwise cause this assert to trigger.
ZX_DEBUG_ASSERT(port == kOutputPort);
// Ignore the client's message. The client will probably catch up later.
return false;
}
if (buffer.buffer_index != all_buffers_[port].size()) {
FailLocked(
"AddOutputBuffer()/AddInputBuffer() had buffer_index out of sequence "
"- port: %d buffer_index: %u all_buffers_[port].size(): %lu",
port, buffer.buffer_index, all_buffers_[port].size());
return false;
}
uint32_t required_buffer_count =
BufferCountFromPortSettings(*port_settings_[port]);
if (buffer.buffer_index >= required_buffer_count) {
FailLocked("AddOutputBuffer()/AddInputBuffer() extra buffer - port: %d",
port);
return false;
}
// So far, there's little reason to avoid doing the Init() part under the
// lock, even if it can be a bit more time consuming, since there's no data
// processing happening at this point anyway, and there wouldn't be any
// happening in any other code location where we could potentially move the
// Init() either.
std::unique_ptr<CodecBuffer> local_buffer = std::unique_ptr<CodecBuffer>(
new CodecBuffer(this, port, std::move(buffer)));
if (!local_buffer->Init()) {
FailLocked(
"AddOutputBuffer()/AddInputBuffer() couldn't Init() new buffer - "
"port: %d",
port);
return false;
}
{
ScopedUnlock unlock(lock);
// Inform the core codec up-front about each buffer.
CoreCodecAddBuffer(port, local_buffer.get());
}
all_buffers_[port].push_back(std::move(local_buffer));
if (all_buffers_[port].size() == required_buffer_count) {
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[port] ==
port_settings_[port]->buffer_lifetime_ordinal);
// Stash this while we can, before the client de-configures.
last_provided_buffer_constraints_version_ordinal_[port] =
port_settings_[port]->buffer_constraints_version_ordinal;
// Now we allocate all_packets_[port].
ZX_DEBUG_ASSERT(all_packets_[port].empty());
uint32_t packet_count =
PacketCountFromPortSettings(*port_settings_[port]);
for (uint32_t i = 0; i < packet_count; i++) {
// Private constructor to prevent core codec maybe creating its own
// Packet instances (which isn't the intent) seems worth the hassle of
// not using make_unique<>() here.
all_packets_[port].push_back(std::unique_ptr<CodecPacket>(
new CodecPacket(port_settings_[port]->buffer_lifetime_ordinal, i)));
}
{ // scope unlock
ScopedUnlock unlock(lock);
// A core codec can take action here to finish configuring buffers if
// it's able, or can delay configuring buffers until
// CoreCodecStartStream() if that works better for the core codec.
CoreCodecConfigureBuffers(port, all_packets_[port]);
// All output packets need to start with the core codec. This is
// implicit for the Codec interface (implied by adding the last output
// buffer) but explicit in the CodecAdapter interface.
if (port == kOutputPort) {
for (uint32_t i = 0; i < packet_count; i++) {
CoreCodecRecycleOutputPacket(all_packets_[kOutputPort][i].get());
}
}
} // ~unlock
// For OMX case, we tell OMX about the potentially-new buffer count
// separately later, just before moving from OMX loaded to OMX idle, or as
// part of mid-stream output config change.
// For OMX case, we don't allocate OMX_BUFFERHEADERTYPE yet here by
// calling OMX UseBuffer() yet, because we can be in OMX_StateLoaded
// currently, and OMX UseBuffer() isn't valid until we're moving from
// OMX_StateLoaded to OMX_StateIdle.
is_port_configured_[port] = true;
done_configuring = true;
}
}
return done_configuring;
}
bool CodecImpl::CheckOldBufferLifetimeOrdinalLocked(
CodecPort port, uint64_t buffer_lifetime_ordinal) {
// The client must only send odd values. 0 is even so we don't need a
// separate check for that.
if (buffer_lifetime_ordinal % 2 == 0) {
FailLocked(
"CheckOldBufferLifetimeOrdinalLocked() - buffer_lifetime_ordinal must "
"be odd");
return false;
}
if (buffer_lifetime_ordinal > protocol_buffer_lifetime_ordinal_[port]) {
FailLocked(
"client sent new buffer_lifetime_ordinal in message type that doesn't "
"allow new buffer_lifetime_ordinals");
return false;
}
return true;
}
bool CodecImpl::CheckStreamLifetimeOrdinalLocked(
uint64_t stream_lifetime_ordinal) {
if (stream_lifetime_ordinal % 2 != 1) {
FailLocked("stream_lifetime_ordinal must be odd.\n");
return false;
}
if (stream_lifetime_ordinal < stream_lifetime_ordinal_) {
FailLocked("client sent stream_lifetime_ordinal that went backwards");
return false;
}
return true;
}
bool CodecImpl::StartNewStream(std::unique_lock<std::mutex>& lock,
uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
ZX_DEBUG_ASSERT((stream_lifetime_ordinal % 2 == 1) &&
"new stream_lifetime_ordinal must be odd");
if (IsStoppingLocked()) {
// Don't start a new stream if the whole CodecImpl is already stopping.
//
// A completely different path will take care of calling
// EnsureStreamClosed() during CodecImpl stop.
//
// TODO(dustingreen): If all callers are already checking this at the top
// of each relevant .*_StreamControl method, then we don't necessarily need
// this check, but consider any intervals where lock_ isn't held also - we
// don't want the wait for stream_control_thread_ to exit to ever be long
// when stopping this CodecImpl.
return false;
}
EnsureStreamClosed(lock);
ZX_DEBUG_ASSERT((stream_lifetime_ordinal_ % 2 == 0) &&
"expecting no current stream");
ZX_DEBUG_ASSERT(!stream_);
// Now it's time to start the new stream. We start the new stream at
// Codec layer first then core codec layer.
if (!IsInputConfiguredLocked()) {
FailLocked(
"input not configured before start of stream (QueueInputPacket())");
return false;
}
ZX_DEBUG_ASSERT(stream_queue_.size() >= 1);
ZX_DEBUG_ASSERT(stream_lifetime_ordinal ==
stream_queue_.front()->stream_lifetime_ordinal());
stream_ = stream_queue_.front().get();
// Update the stream_lifetime_ordinal_ to the new stream. We need to do
// this before we send new output config, since the output config will be
// generated using the current stream ordinal.
ZX_DEBUG_ASSERT(stream_lifetime_ordinal > stream_lifetime_ordinal_);
stream_lifetime_ordinal_ = stream_lifetime_ordinal;
ZX_DEBUG_ASSERT(stream_->stream_lifetime_ordinal() ==
stream_lifetime_ordinal_);
// The client is not permitted to unilaterally re-configure output while a
// stream is active, but the client may still be responding to a previous
// server-initiated mid-stream format change.
//
// ###########################################################################
// We don't attempt to optimize every case as much as might be possible here.
// The main overall optimization is that it's possible to switch streams
// without reallocating buffers. We also need to make sure it's possible to
// detect output format at the start of a stream regardless of what happened
// before, and possible to perform a mid-stream format change.
// ###########################################################################
//
// Given the above, our *main concern* here is that we get to a state where we
// *know* the client isn't trying to re-configure output during format
// detection, which at best would be confusing to allow, so we avoid that
// possibility here by forcing a client to catch up with the server, if
// there's *any possibility* that the client might still be working on
// catching up with the server.
//
// If the client's most recently fully-completed output config is less than
// the most recently sent output constraints with action_required true, then
// we force an even fresher output constraints here tagged as being relevant
// to the current stream, and wait for the client to catch up to that before
// continuing. By marking as being for this stream, we ensure that the client
// will bother to finish configuring output, which gets us to a state where we
// know it's safe to do another mid-stream format change as needed (vs. the
// client maybe finishing the old config or maybe not).
//
// We also force the client to catch up if the core codec previously indicated
// that the current config is "meh". This may not be strictly necessary since
// the "meh" was with respect to the old stream, but just in case a core codec
// cares, we move on from the old config before delivering new stream data.
//
// Some core codecs (such as OMX codecs) require the output to be configured
// to _something_ as they don't support giving us the real output config
// unless the output is configured to at least something at first.
//
// Other core codecs (such as some HW-based codecs) can deal with no output
// configured while detecting the output format, but even for those codecs, we
// only do this if the above cases don't apply. These codecs have to deal
// with an output config that's already set across a stream switch anyway, to
// permit buffers to stay configured across a stream switch when possible, so
// the cases above potentially setting an output config that's not super
// relevant to the new stream doesn't really complicate the core codec since
// an old stream's config might not be super relevant to a new stream either.
//
// Format detection is separate and handled like a mid-stream format change.
// This stuff here is just getting output config into a non-changing state
// before we start format detection.
bool is_new_config_needed;
// The statement below could obviously be re-written as a giant boolean
// expression, but this way seems easier to comment.
if (last_provided_buffer_constraints_version_ordinal_[kOutputPort] <
last_required_buffer_constraints_version_ordinal_[kOutputPort]) {
// The client _might_ still be trying to catch up, so to disambiguate,
// require an even fresher config with respect to this new stream to
// unambiguously force the client to catch up to the even newer config.
is_new_config_needed = true;
} else if (IsCoreCodecRequiringOutputConfigForFormatDetection() &&
!IsOutputConfiguredLocked()) {
// The core codec requires output to be configured before format detection,
// so we force the client to provide an output config before format
// detection.
is_new_config_needed = true;
} else if (IsOutputConfiguredLocked() &&
port_settings_[kOutputPort]->buffer_constraints_version_ordinal <=
core_codec_meh_output_buffer_constraints_version_ordinal_) {
// The core codec previously expressed "meh" regarding the current config's
// buffer_constraints_version_ordinal, so to avoid mixing that with core
// codec stream switch, force the client to configure output buffers before
// format detection for the new stream.
is_new_config_needed = true;
} else {
// The core codec is ok to perform format detection in the current state,
// and we know that a well-behaved client is not currently trying to change
// the output config.
is_new_config_needed = false;
}
if (is_new_config_needed) {
StartIgnoringClientOldOutputConfig(lock);
EnsureBuffersNotConfigured(lock, kOutputPort);
// This does count as a mid-stream output config change, even when this is
// at the start of a stream - it's still while a stream is active, and still
// prevents this stream from outputting any data to the Codec client until
// the Codec client re-configures output while this stream is active.
GenerateAndSendNewOutputConfig(lock, true);
// Now we can wait for the client to catch up to the current output config
// or for the client to tell the server to discard the current stream.
while (!IsStoppingLocked() && !stream_->future_discarded() &&
!IsOutputConfiguredLocked()) {
wake_stream_control_condition_.wait(lock);
}
if (IsStoppingLocked()) {
return false;
}
if (stream_->future_discarded()) {
// A discarded stream isn't an error for the CodecImpl instance.
return true;
}
}
// Now we have input configured, and output configured if needed by the core
// codec, so we can move the core codec to running state.
{ // scope unlock
ScopedUnlock unlock(lock);
CoreCodecStartStream();
} // ~unlock
// Track this so the core codec doesn't have to bother with "ensure"
// semantics, just start/stop, where stop isn't called unless the core codec
// has a started stream.
is_core_codec_stream_started_ = true;
return true;
}
void CodecImpl::EnsureStreamClosed(std::unique_lock<std::mutex>& lock) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
// Stop the core codec, by using this thread to directly drive the core codec
// from running to stopped (if not already stopped). We do this first so the
// core codec won't try to send us output while we have no stream at the Codec
// layer.
if (is_core_codec_stream_started_) {
{ // scope unlock
ScopedUnlock unlock(lock);
CoreCodecStopStream();
}
is_core_codec_stream_started_ = false;
}
// Now close the old stream at the Codec layer.
EnsureCodecStreamClosedLockedInternal();
ZX_DEBUG_ASSERT((stream_lifetime_ordinal_ % 2 == 0) &&
"expecting no current stream");
ZX_DEBUG_ASSERT(!stream_);
}
// The only valid caller of this is EnsureStreamClosed(). We have this in a
// separate method only to make it easier to assert a couple things in the
// caller.
void CodecImpl::EnsureCodecStreamClosedLockedInternal() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
if (stream_lifetime_ordinal_ % 2 == 0) {
// Already closed.
return;
}
ZX_DEBUG_ASSERT(stream_queue_.front()->stream_lifetime_ordinal() ==
stream_lifetime_ordinal_);
stream_ = nullptr;
stream_queue_.pop_front();
stream_lifetime_ordinal_++;
// Even values mean no current stream.
ZX_DEBUG_ASSERT(stream_lifetime_ordinal_ % 2 == 0);
}
// This is called on Output ordering domain (FIDL thread) any time a message is
// received which would be able to start a new stream.
//
// More complete protocol validation happens on StreamControl ordering domain.
// The validation here is just to validate to degree needed to not break our
// stream_queue_ and future_stream_lifetime_ordinal_.
bool CodecImpl::EnsureFutureStreamSeenLocked(uint64_t stream_lifetime_ordinal) {
if (future_stream_lifetime_ordinal_ == stream_lifetime_ordinal) {
return true;
}
if (stream_lifetime_ordinal < future_stream_lifetime_ordinal_) {
FailLocked("stream_lifetime_ordinal went backward - exiting\n");
return false;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal > future_stream_lifetime_ordinal_);
if (future_stream_lifetime_ordinal_ % 2 == 1) {
if (!EnsureFutureStreamCloseSeenLocked(future_stream_lifetime_ordinal_)) {
return false;
}
}
future_stream_lifetime_ordinal_ = stream_lifetime_ordinal;
stream_queue_.push_back(std::make_unique<Stream>(stream_lifetime_ordinal));
if (stream_queue_.size() > kMaxInFlightStreams) {
FailLocked(
"kMaxInFlightStreams reached - clients capable of causing this are "
"instead supposed to wait/postpone to prevent this from occurring - "
"exiting\n");
return false;
}
return true;
}
// This is called on Output ordering domain (FIDL thread) any time a message is
// received which would close a stream.
//
// More complete protocol validation happens on StreamControl ordering domain.
// The validation here is just to validate to degree needed to not break our
// stream_queue_ and future_stream_lifetime_ordinal_.
bool CodecImpl::EnsureFutureStreamCloseSeenLocked(
uint64_t stream_lifetime_ordinal) {
if (future_stream_lifetime_ordinal_ % 2 == 0) {
// Already closed.
if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_ - 1) {
FailLocked(
"CloseCurrentStream() seen with stream_lifetime_ordinal != "
"most-recent seen stream");
return false;
}
return true;
}
if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_) {
FailLocked("attempt to close a stream other than the latest seen stream");
return false;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal == future_stream_lifetime_ordinal_);
ZX_DEBUG_ASSERT(stream_queue_.size() >= 1);
Stream* closing_stream = stream_queue_.back().get();
ZX_DEBUG_ASSERT(closing_stream->stream_lifetime_ordinal() ==
stream_lifetime_ordinal);
// It is permitted to see a FlushCurrentStream() before a CloseCurrentStream()
// and this can make sense if a client just wants to inform the server of all
// stream closes, or if the client wants to release_input_buffers or
// release_output_buffers after the flush is done.
//
// If we didn't previously flush, then this close is discarding.
if (!closing_stream->future_flush_end_of_stream()) {
closing_stream->SetFutureDiscarded();
}
future_stream_lifetime_ordinal_++;
ZX_DEBUG_ASSERT(future_stream_lifetime_ordinal_ % 2 == 0);
return true;
}
// This is called on Output ordering domain (FIDL thread) any time a flush is
// seen.
//
// More complete protocol validation happens on StreamControl ordering domain.
// The validation here is just to validate to degree needed to not break our
// stream_queue_ and future_stream_lifetime_ordinal_.
bool CodecImpl::EnsureFutureStreamFlushSeenLocked(
uint64_t stream_lifetime_ordinal) {
if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_) {
FailLocked("FlushCurrentStream() stream_lifetime_ordinal inconsistent");
return false;
}
ZX_DEBUG_ASSERT(stream_queue_.size() >= 1);
Stream* flushing_stream = stream_queue_.back().get();
// Thanks to the above future_stream_lifetime_ordinal_ check, we know the
// future stream is not discarded yet.
ZX_DEBUG_ASSERT(!flushing_stream->future_discarded());
if (flushing_stream->future_flush_end_of_stream()) {
FailLocked("FlushCurrentStream() used twice on same stream");
return false;
}
// We don't future-verify that we have a QueueInputEndOfStream(). We'll verify
// that later when StreamControl catches up to this stream.
// Remember the flush so we later know that a close doesn't imply discard.
flushing_stream->SetFutureFlushEndOfStream();
// A FlushEndOfStreamAndCloseStream() is also a close, after the flush. This
// keeps future_stream_lifetime_ordinal_ consistent.
if (!EnsureFutureStreamCloseSeenLocked(stream_lifetime_ordinal)) {
return false;
}
return true;
}
// This method is only called when buffer_constraints_action_required will be
// true in an OnOutputConfig() message sent shortly after this method call.
//
// Even if the client is switching streams rapidly without configuring output,
// this method and GenerateAndSendNewOutputConfig() with
// buffer_constraints_action_required true always run in pairs.
//
// This is what starts the interval during which
// OmxTryRecycleOutputPacketLocked() won't call OMX.
//
// If the client is in the middle of configuring output, we'll start ignoring
// the client's messages re. the old buffer_lifetime_ordinal and old
// buffer_constraints_version_ordinal until the client catches up to the new
// last_required_buffer_constraints_version_ordinal_[kOutputPort].
void CodecImpl::StartIgnoringClientOldOutputConfig(
std::unique_lock<std::mutex>& lock) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
// The buffer_lifetime_ordinal_[kOutputPort] can be even on entry due to at
// least two cases: 0, and when the client is switching streams repeatedly
// without setting a new buffer_lifetime_ordinal_[kOutputPort].
if (buffer_lifetime_ordinal_[kOutputPort] % 2 == 1) {
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] % 2 == 1);
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] ==
port_settings_[kOutputPort]->buffer_lifetime_ordinal);
buffer_lifetime_ordinal_[kOutputPort]++;
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] % 2 == 0);
ZX_DEBUG_ASSERT(buffer_lifetime_ordinal_[kOutputPort] ==
port_settings_[kOutputPort]->buffer_lifetime_ordinal + 1);
}
// When buffer_constraints_action_required true, we can assert in
// GenerateAndSendNewOutputConfig() that this value is still the
// next_output_buffer_constraints_version_ordinal_ in that method.
last_required_buffer_constraints_version_ordinal_[kOutputPort] =
next_output_buffer_constraints_version_ordinal_;
// Now that we've stopped any new calls to CoreCodecRecycleOutputPacket(),
// fence through any previously-started call to CoreCodecRecycleOutputPacket()
// that maybe have been started previously, before returning from this method.
//
// We can't just be holding lock_ during the call to
// CoreCodecRecycleOutputPacket() because it acquires the video_decoder_lock_
// and in other paths the video_decoder_lock_ is held while acquiring lock_.
//
// It's ok for the StreamControl domain to wait on the Output domain (but not
// the other way around).
bool is_output_ordering_domain_done_with_recycle_output_packet = false;
std::condition_variable condition_changed;
PostToSharedFidl([this,
&is_output_ordering_domain_done_with_recycle_output_packet,
&condition_changed] {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
is_output_ordering_domain_done_with_recycle_output_packet = true;
}
condition_changed.notify_all();
});
while (!is_output_ordering_domain_done_with_recycle_output_packet) {
condition_changed.wait(lock);
}
ZX_DEBUG_ASSERT(is_output_ordering_domain_done_with_recycle_output_packet);
}
void CodecImpl::GenerateAndSendNewOutputConfig(
std::unique_lock<std::mutex>& lock,
bool buffer_constraints_action_required) {
// When client action is required, this can only happen on the StreamControl
// ordering domain. When client action is not required, it can happen from
// the InputData ordering domain.
ZX_DEBUG_ASSERT(buffer_constraints_action_required &&
thrd_current() == stream_control_thread_ ||
!buffer_constraints_action_required &&
IsPotentiallyCoreCodecThread());
uint64_t current_stream_lifetime_ordinal = stream_lifetime_ordinal_;
uint64_t new_output_buffer_constraints_version_ordinal =
next_output_buffer_constraints_version_ordinal_++;
uint64_t new_output_format_details_version_ordinal =
next_output_format_details_version_ordinal_++;
// If buffer_constraints_action_required true, the caller bumped the
// last_required_buffer_constraints_version_ordinal_[kOutputPort] before
// calling this method (using StartIgnoringClientOldOutputConfig()), to
// ensure any output config messages from the client are ignored until the
// client catches up to at least
// last_required_buffer_constraints_version_ordinal_.
ZX_DEBUG_ASSERT(
!buffer_constraints_action_required ||
(last_required_buffer_constraints_version_ordinal_[kOutputPort] ==
new_output_buffer_constraints_version_ordinal));
// printf("GenerateAndSendNewOutputConfig
// new_output_buffer_constraints_version_ordinal: %lu
// buffer_constraints_action_required: %d\n",
// new_output_buffer_constraints_version_ordinal,
// buffer_constraints_action_required);
std::unique_ptr<const fuchsia::media::StreamOutputConfig> output_config;
{ // scope unlock
ScopedUnlock unlock(lock);
// Don't call the core codec under the lock_, because we can avoid doing so,
// and to allow the core codec to use this thread to call back into
// CodecImpl using this stack if needed. So far we don't have any actual
// known examples of a core codec using this thread to call back into
// CodecImpl using this stack.
output_config = CoreCodecBuildNewOutputConfig(
current_stream_lifetime_ordinal,
new_output_buffer_constraints_version_ordinal,
new_output_format_details_version_ordinal,
buffer_constraints_action_required);
} // ~unlock
// We only call GenerateAndSendNewOutputConfig() from contexts that won't be
// changing the stream_lifetime_ordinal_, so the fact that we released the
// lock above doesn't mean the stream_lifetime_ordinal_ could have changed, so
// we can assert here that it's still the same as above.
ZX_DEBUG_ASSERT(current_stream_lifetime_ordinal == stream_lifetime_ordinal_);
output_config_ = std::move(output_config);
// Stay under lock after setting output_config_, to get proper ordering of
// sent messages even if a hostile client deduces the content of this message
// before we've sent it and manages to get the server to send another
// subsequent OnOutputConfig().
ZX_DEBUG_ASSERT(sent_buffer_constraints_version_ordinal_[kOutputPort] + 1 ==
new_output_buffer_constraints_version_ordinal);
ZX_DEBUG_ASSERT(sent_format_details_version_ordinal_[kOutputPort] + 1 ==
new_output_format_details_version_ordinal);
// Setting this within same lock hold interval as we queue the message to be
// sent in order vs. other OnOutputConfig() messages. This way we can verify
// that the client's incoming messages are not trying to configure with
// respect to a buffer_constraints_version_ordinal that is newer than we've
// actually sent the client.
sent_buffer_constraints_version_ordinal_[kOutputPort] =
new_output_buffer_constraints_version_ordinal;
sent_format_details_version_ordinal_[kOutputPort] =
new_output_format_details_version_ordinal;
// Intentional copy of fuchsia::media::OutputConfig output_config_ here,
// as we want output_config_ to remain valid (at least for debugging reasons
// for now).
PostToSharedFidl(
[this, output_config = fidl::Clone(*output_config_)]() mutable {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnOutputConfig(std::move(output_config));
}
});
}
void CodecImpl::MidStreamOutputConfigChange(uint64_t stream_lifetime_ordinal) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (stream_lifetime_ordinal < stream_lifetime_ordinal_) {
// ignore; The omx_meh_output_buffer_constraints_version_ordinal_ took
// care of it.
return;
}
ZX_DEBUG_ASSERT(stream_lifetime_ordinal == stream_lifetime_ordinal_);
// Now we need to start disabling the port, wait for buffers to come back
// from OMX, free buffer headers, wait for the port to become fully
// disabled, unilaterally de-configure output buffers, demand a new output
// config from the client, wait for the client to configure output (but be
// willing to bail on waiting for the client if we notice future stream
// discard), re-enable the output port, allocate headers, wait for the port
// to be fully enabled, call FillThisBuffer() on the protocol-free buffers.
// This is what starts the interval during which
// OmxTryRecycleOutputPacketLocked() won't call OMX, and the interval during
// which we'll ignore any in-progress client output config until the client
// catches up.
StartIgnoringClientOldOutputConfig(lock);
{ // scope unlock
ScopedUnlock unlock(lock);
CoreCodecMidStreamOutputBufferReConfigPrepare();
} // ~unlock
EnsureBuffersNotConfigured(lock, kOutputPort);
GenerateAndSendNewOutputConfig(lock, true);
// Now we can wait for the client to catch up to the current output config
// or for the client to tell the server to discard the current stream.
while (!IsStoppingLocked() && !stream_->future_discarded() &&
!IsOutputConfiguredLocked()) {
wake_stream_control_condition_.wait(lock);
}
if (IsStoppingLocked()) {
return;
}
if (stream_->future_discarded()) {
// We already know how to handle this case, and
// core_codec_meh_output_buffer_constraints_version_ordinal_ is still set
// such that the client will be forced to re-configure output buffers at
// the start of the new stream.
return;
}
} // ~lock
CoreCodecMidStreamOutputBufferReConfigFinish();
VLOGF("Done with mid-stream format change.\n");
}
thrd_t CodecImpl::fidl_thread() { return shared_fidl_thread_; }
void CodecImpl::SendFreeInputPacketLocked(fuchsia::media::PacketHeader header) {
// We allow calling this method on StreamControl or InputData ordering domain.
// Because the InputData ordering domain thread isn't visible to this code,
// if this isn't the StreamControl then we can only assert that this thread
// isn't the FIDL thread, because we know the codec's InputData thread isn't
// the FIDL thread.
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_ ||
thrd_current() != fidl_thread());
// We only send using fidl_thread().
PostToSharedFidl([this, header = std::move(header)] {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnFreeInputPacket(std::move(header));
}
});
}
bool CodecImpl::IsInputConfiguredLocked() {
return IsPortConfiguredCommonLocked(kInputPort);
}
bool CodecImpl::IsOutputConfiguredLocked() {
return IsPortConfiguredCommonLocked(kOutputPort);
}
bool CodecImpl::IsPortConfiguredCommonLocked(CodecPort port) {
// In addition to what we're able to assert here, when
// is_port_configured_[port], the core codec also has the port
// configured.
ZX_DEBUG_ASSERT(!is_port_configured_[port] ||
port_settings_[port] &&
all_buffers_[port].size() ==
BufferCountFromPortSettings(*port_settings_[port]));
return is_port_configured_[port];
}
void CodecImpl::Fail(const char* format, ...) {
va_list args;
va_start(args, format);
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
vFailLocked(false, format, args);
} // ~lock
// "this" can be deallocated by this point (as soon as ~lock above).
va_end(args);
}
void CodecImpl::FailLocked(const char* format, ...) {
va_list args;
va_start(args, format);
vFailLocked(false, format, args);
va_end(args);
// At this point know "this" is still allocated only because we still hold
// lock_. As soon as lock_ is released by the caller, "this" can immediately
// be deallocated by another thread, if this isn't currently the
// fidl_thread().
}
void CodecImpl::FailFatalLocked(const char* format, ...) {
va_list args;
va_start(args, format);
// This doesn't return.
vFailLocked(true, format, args);
va_end(args);
}
void CodecImpl::vFail(bool is_fatal, const char* format, va_list args) {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
vFailLocked(false, format, args);
} // ~lock
}
// Only meant to be called from Fail() and FailLocked(). Only meant to be
// called for async failure cases after was_logically_bound_ has become true.
// Failures before that point are handled separately.
void CodecImpl::vFailLocked(bool is_fatal, const char* format, va_list args) {
// TODO(dustingreen): Send epitaph when possible.
// Let's not have a buffer on the stack, not because it couldn't be done
// safely, but because we'd potentially run into stack size vs. message length
// tradeoffs, stack expansion granularity fun, or whatever else.
va_list args2;
va_copy(args2, args);
size_t buffer_bytes = vsnprintf(nullptr, 0, format, args) + 1;
// ~buffer never actually runs since this method never returns
std::unique_ptr<char[]> buffer(new char[buffer_bytes]);
size_t buffer_bytes_2 =
vsnprintf(buffer.get(), buffer_bytes, format, args2) + 1;
(void)buffer_bytes_2;
// sanity check; should match so go ahead and assert that it does.
ZX_DEBUG_ASSERT(buffer_bytes == buffer_bytes_2);
va_end(args2);
// TODO(dustingreen): It might be worth wiring this up to the log in a more
// official way, especially if doing so would print a timestamp automatically
// and/or provide filtering goodness etc.
const char* message =
is_fatal ? "devhost will fail" : "Codec channel will close async";
printf("%s -- %s\n", buffer.get(), message);
// TODO(dustingreen): Send string in buffer via epitaph, when possible. First
// we should switch to events so we'll only have the Codec channel not the
// CodecEvents channel. Note to self: The channel failing server-side may race
// with trying to send.
if (is_fatal) {
abort();
} else {
UnbindLocked();
}
// At this point we know "this" is still allocated only because we still hold
// lock_. As soon as lock_ is released by the caller, "this" can immediately
// be deallocated by another thread, if this isn't currently the
// fidl_thread().
}
void CodecImpl::PostSerial(async_dispatcher_t* async, fit::closure to_run) {
zx_status_t result = async::PostTask(async, std::move(to_run));
ZX_ASSERT(result == ZX_OK);
}
void CodecImpl::PostToSharedFidl(
fit::closure to_run,
bool promise_not_on_previously_posted_fidl_thread_lambda) {
// Re-posting to fidl_thread() is potentially problematic because of
// how CodecImpl::UnbindLocked() relies on re-posting itself to run "delete
// this" after any other work posted to fidl_thread() previously - that
// only works if re-posts to the fidl_thread() aren't allowed.
// TODO(dustingreen): Avoid the need for
// promise_not_on_previously_posted_fidl_thread_lambda; make enforcement
// self-contained while still permitting FIDL dispatch to lambda self-post.
ZX_DEBUG_ASSERT(promise_not_on_previously_posted_fidl_thread_lambda ||
(thrd_current() != fidl_thread()));
PostSerial(shared_fidl_dispatcher_, std::move(to_run));
}
void CodecImpl::PostToStreamControl(fit::closure to_run) {
PostSerial(stream_control_loop_.dispatcher(), std::move(to_run));
}
bool CodecImpl::IsStoppingLocked() { return was_unbind_started_; }
bool CodecImpl::IsStopping() {
std::lock_guard<std::mutex> lock(lock_);
return IsStoppingLocked();
}
// true - maybe it's the core codec thread.
// false - it's definitely not the core codec thread.
bool CodecImpl::IsPotentiallyCoreCodecThread() {
// FXL_CHECK(false) << "not yet implemented";
return (thrd_current() != stream_control_thread_) &&
(thrd_current() != fidl_thread());
}
void CodecImpl::HandlePendingInputFormatDetails() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
const fuchsia::media::FormatDetails* input_details = nullptr;
if (stream_->input_format_details()) {
input_details = stream_->input_format_details();
} else {
input_details = initial_input_format_details_;
}
ZX_DEBUG_ASSERT(input_details);
CoreCodecQueueInputFormatDetails(*input_details);
}
void CodecImpl::onCoreCodecFailCodec(const char* format, ...) {
std::string local_format =
std::string("onCoreCodecFailCodec() called -- ") + format;
va_list args;
va_start(args, format);
vFail(false, local_format.c_str(), args);
// "this" can be deallocated by this point (as soon as ~lock above).
va_end(args);
}
void CodecImpl::onCoreCodecFailStream() {
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
if (IsStoppingLocked()) {
// This CodecImpl is already stopping due to a previous FailLocked(),
// which will result in the Codec channel getting closed soon. So don't
// send OnStreamFailed().
return;
}
// We rely on the CodecAdapter to only call this method when there's a
// current stream.
ZX_DEBUG_ASSERT(stream_ && stream_->stream_lifetime_ordinal() ==
stream_lifetime_ordinal_);
if (stream_->output_end_of_stream()) {
// Tolerate a CodecAdapter failing the stream after output EndOfStream
// seen, and avoid notifying the client of a stream failure that's too
// late to matter.
return;
}
if (stream_->failure_seen()) {
// We already know. We don't auto-close the stream because the client is
// in control of stream lifetime, so it's plausible that a CodecAdapter
// could notify of stream failure more than once. We can ignore the
// redundant stream failure to avoid sending OnStreamFailed() again.
return;
}
stream_->SetFailureSeen();
// We're failing the current stream. We should still queue to the output
// ordering domain to ensure ordering vs. any previously-sent output on this
// stream that was sent directly from codec processing thread.
//
// This failure is async, in the sense that the client may still be sending
// input data, and the core codec is expected to just hold onto those
// packets until the client has moved on from this stream.
printf("onStreamFailed() - stream_lifetime_ordinal_: %lu\n",
stream_lifetime_ordinal_);
if (!is_on_stream_failed_enabled_) {
FailLocked(
"onStreamFailed() with a client that didn't send "
"EnableOnStreamFailed(), so closing the Codec channel instead.");
return;
}
// There's not actually any need to track that the stream failed anywhere
// in the CodecImpl. The client needs to move on from the failed
// stream to a new stream, or close the Codec channel.
PostToSharedFidl(
[this, stream_lifetime_ordinal = stream_lifetime_ordinal_] {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnStreamFailed(stream_lifetime_ordinal);
}
});
} // ~lock
}
void CodecImpl::onCoreCodecMidStreamOutputConfigChange(
bool output_re_config_required) {
// For now, the core codec thread is the only thread this gets called from.
ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread());
// For a OMX_EventPortSettingsChanged that doesn't demand output buffer
// re-config before more output data, this translates to an ordered emit
// of a no-action-required OnOutputConfig() that just updates to the new
// format, without demanding output buffer re-config. HDR info can be
// conveyed this way, ordered with respect to output frames. OMX
// requires that we use this thread to collect OMX format info during
// EventHandler().
if (!output_re_config_required) {
std::unique_lock<std::mutex> lock(lock_);
GenerateAndSendNewOutputConfig(
lock,
false); // buffer_constraints_action_required
return;
}
// We have an OMX_EventPortSettingsChanged that does demand output
// buffer re-config before more output data.
ZX_DEBUG_ASSERT(output_re_config_required);
// We post over to StreamControl domain because we need to synchronize
// with any changes to stream state that might be driven by the client.
// When we get over there to StreamControl, we'll check if we're still
// talking about the same stream_lifetime_ordinal, and if not, we ignore
// the event, because a new stream may or may not have the same output
// settings, and we'll be re-generating an OnOutputConfig() as needed
// from current/later OMX output config anyway. Here are the
// possibilities:
// * Prior to the client moving to a new stream, we process this event
// on StreamControl ordering domain and have bumped
// buffer_lifetime_ordinal by the time we start any subsequent
// new stream from the client, which means we'll require the client
// to catch up to the new buffer_lifetime_ordinal before we start
// that new stream.
// * The client moves to a new stream before this event gets over to
// StreamControl. In this case we ignore the event on StreamControl
// domain since its stale by that point, but instead we use
// omx_meh_output_buffer_constraints_version_ordinal_ to cause the
// client's next stream to start with a new OnOutputConfig() that
// the client must catch up to before the stream can fully start.
// This way we know we're not ignoring a potential change to
// nBufferCountMin or anything like that.
uint64_t local_stream_lifetime_ordinal;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// This part is not speculative. OMX has indicated that it's at least
// meh about the current output config, so ensure we do a required
// OnOutputConfig() before the next stream starts, even if the client
// moves on to a new stream such that the speculative part below becomes
// stale.
core_codec_meh_output_buffer_constraints_version_ordinal_ =
port_settings_[kOutputPort]
? port_settings_[kOutputPort]->buffer_constraints_version_ordinal
: 0;
// Speculative part - this part is speculative, in that we don't know if
// this post over to StreamControl will beat any client driving to a new
// stream. So we snap the stream_lifetime_ordinal so we know whether to
// ignore the post once it reaches StreamControl.
local_stream_lifetime_ordinal = stream_lifetime_ordinal_;
} // ~lock
PostToStreamControl(
[this, stream_lifetime_ordinal = local_stream_lifetime_ordinal] {
MidStreamOutputConfigChange(stream_lifetime_ordinal);
});
}
void CodecImpl::onCoreCodecInputPacketDone(CodecPacket* packet) {
// Free/busy coherency from Codec interface to OMX doesn't involve trusting
// the client, so assert we're doing it right server-side.
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
// The core codec says the buffer-referening in-flight lifetime of this
// packet is over. We'll set the buffer again when this packet get's used
// by the client again to deliver more input data.
packet->SetBuffer(nullptr);
// Unfortunately we have to insist that the core codec not call
// onCoreCodecInputPacketDone() arbitrarily late because we need to know
// when it's safe to deallocate binding_, and the core codec, etc. So the
// rule is the core codec needs to ensure that all calls to stream-related
// callbacks have completed (to structure-touching degree; not
// code-unloading degree) before CoreCodecStopStream() returns.
ZX_DEBUG_ASSERT(is_core_codec_stream_started_);
ZX_DEBUG_ASSERT(
!all_packets_[kInputPort][packet->packet_index()]->is_free());
all_packets_[kInputPort][packet->packet_index()]->SetFree(true);
SendFreeInputPacketLocked(fuchsia::media::PacketHeader{
.buffer_lifetime_ordinal = packet->buffer_lifetime_ordinal(),
.packet_index = packet->packet_index()});
} // ~lock
}
void CodecImpl::onCoreCodecOutputPacket(CodecPacket* packet,
bool error_detected_before,
bool error_detected_during) {
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
// This helps verify that packet lifetimes are coherent, but we can't do the
// same for buffer_index because VP9 has show_existing_frame which is
// allowed to output the same buffer repeatedly.
//
// TODO(dustingreen): We could _optionally_ verify that buffer lifetimes are
// coherent for codecs that don't output the same buffer repeatedly and
// concurrently.
all_packets_[kOutputPort][packet->packet_index()]->SetFree(false);
ZX_DEBUG_ASSERT(packet->has_start_offset());
ZX_DEBUG_ASSERT(packet->has_valid_length_bytes());
// packet->has_timestamp_ish() is optional even if
// promise_separate_access_units_on_input is true. We do want to enforce
// that the client gets no set timestamp_ish values if the client didn't
// promise_separate_access_units_on_input.
bool has_timestamp_ish =
(encoder_params_ ||
decoder_params_->promise_separate_access_units_on_input) &&
packet->has_timestamp_ish();
uint64_t timestamp_ish = has_timestamp_ish ? packet->timestamp_ish() : 0;
PostToSharedFidl(
[this,
p =
fuchsia::media::Packet{
.header.buffer_lifetime_ordinal =
packet->buffer_lifetime_ordinal(),
.header.packet_index = packet->packet_index(),
.buffer_index = packet->buffer()->buffer_index(),
.stream_lifetime_ordinal = stream_lifetime_ordinal_,
.start_offset = packet->start_offset(),
.valid_length_bytes = packet->valid_length_bytes(),
.has_timestamp_ish = has_timestamp_ish,
.timestamp_ish = timestamp_ish,
.start_access_unit = true,
.known_end_access_unit = true,
},
error_detected_before, error_detected_during] {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnOutputPacket(
std::move(p), error_detected_before, error_detected_during);
}
});
} // ~lock
}
void CodecImpl::onCoreCodecOutputEndOfStream(bool error_detected_before) {
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
stream_->SetOutputEndOfStream();
output_end_of_stream_seen_.notify_all();
VLOGF("sending OnOutputEndOfStream()\n");
PostSerial(shared_fidl_dispatcher_,
[this, stream_lifetime_ordinal = stream_lifetime_ordinal_,
error_detected_before] {
// See "is_bound_checks" comment up top.
if (binding_.is_bound()) {
binding_.events().OnOutputEndOfStream(
stream_lifetime_ordinal, error_detected_before);
}
});
} // ~lock
}
CodecImpl::Stream::Stream(uint64_t stream_lifetime_ordinal)
: stream_lifetime_ordinal_(stream_lifetime_ordinal) {
// nothing else to do here
}
uint64_t CodecImpl::Stream::stream_lifetime_ordinal() {
return stream_lifetime_ordinal_;
}
void CodecImpl::Stream::SetFutureDiscarded() {
ZX_DEBUG_ASSERT(!future_discarded_);
future_discarded_ = true;
}
bool CodecImpl::Stream::future_discarded() { return future_discarded_; }
void CodecImpl::Stream::SetFutureFlushEndOfStream() {
ZX_DEBUG_ASSERT(!future_flush_end_of_stream_);
future_flush_end_of_stream_ = true;
}
bool CodecImpl::Stream::future_flush_end_of_stream() {
return future_flush_end_of_stream_;
}
CodecImpl::Stream::~Stream() {
VLOGF("~Stream() stream_lifetime_ordinal: %lu\n", stream_lifetime_ordinal_);
}
void CodecImpl::Stream::SetInputFormatDetails(
std::unique_ptr<fuchsia::media::FormatDetails> input_format_details) {
// This is allowed to happen multiple times per stream.
input_format_details_ = std::move(input_format_details);
}
const fuchsia::media::FormatDetails* CodecImpl::Stream::input_format_details() {
return input_format_details_.get();
}
void CodecImpl::Stream::SetOobConfigPending(bool pending) {
// SetOobConfigPending(true) is legal regardless of current state, but
// SetOobConfigPending(false) is only legal if the state is currently true.
ZX_DEBUG_ASSERT(pending || oob_config_pending_);
oob_config_pending_ = pending;
}
bool CodecImpl::Stream::oob_config_pending() { return oob_config_pending_; }
void CodecImpl::Stream::SetInputEndOfStream() {
ZX_DEBUG_ASSERT(!input_end_of_stream_);
input_end_of_stream_ = true;
}
bool CodecImpl::Stream::input_end_of_stream() { return input_end_of_stream_; }
void CodecImpl::Stream::SetOutputEndOfStream() {
ZX_DEBUG_ASSERT(!output_end_of_stream_);
output_end_of_stream_ = true;
}
bool CodecImpl::Stream::output_end_of_stream() { return output_end_of_stream_; }
void CodecImpl::Stream::SetFailureSeen() {
ZX_DEBUG_ASSERT(!failure_seen_);
failure_seen_ = true;
}
bool CodecImpl::Stream::failure_seen() { return failure_seen_; }
//
// CoreCodec wrappers, for the asserts. These asserts, and the way we ensure
// at compile time that this class has a method for every method of
// CodecAdapter, are essentially costing a double vtable call instead of a
// single vtable call. If we don't like that at some point, we can remove the
// private CodecAdapter inheritance from CodecImpl and have these be normal
// methods instead of virtual methods.
//
void CodecImpl::CoreCodecInit(
const fuchsia::media::FormatDetails& initial_input_format_details) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecInit(initial_input_format_details);
}
void CodecImpl::CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) {
ZX_DEBUG_ASSERT(port == kInputPort &&
thrd_current() == stream_control_thread_ ||
port == kOutputPort && thrd_current() == fidl_thread());
codec_adapter_->CoreCodecAddBuffer(port, buffer);
}
void CodecImpl::CoreCodecConfigureBuffers(
CodecPort port, const std::vector<std::unique_ptr<CodecPacket>>& packets) {
ZX_DEBUG_ASSERT(port == kInputPort &&
thrd_current() == stream_control_thread_ ||
port == kOutputPort && thrd_current() == fidl_thread());
codec_adapter_->CoreCodecConfigureBuffers(port, packets);
}
void CodecImpl::CoreCodecEnsureBuffersNotConfigured(CodecPort port) {
ZX_DEBUG_ASSERT(
port == kInputPort && thrd_current() == stream_control_thread_ ||
port == kOutputPort && (thrd_current() == fidl_thread() ||
thrd_current() == stream_control_thread_));
codec_adapter_->CoreCodecEnsureBuffersNotConfigured(port);
}
void CodecImpl::CoreCodecStartStream() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecStartStream();
}
void CodecImpl::CoreCodecQueueInputFormatDetails(
const fuchsia::media::FormatDetails& per_stream_override_format_details) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecQueueInputFormatDetails(
per_stream_override_format_details);
}
void CodecImpl::CoreCodecQueueInputPacket(CodecPacket* packet) {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecQueueInputPacket(packet);
}
void CodecImpl::CoreCodecQueueInputEndOfStream() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecQueueInputEndOfStream();
}
void CodecImpl::CoreCodecStopStream() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecStopStream();
}
bool CodecImpl::IsCoreCodecRequiringOutputConfigForFormatDetection() {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread() ||
thrd_current() == stream_control_thread_);
return codec_adapter_->IsCoreCodecRequiringOutputConfigForFormatDetection();
}
// Caller must ensure that this is called only on one thread at a time, only
// during setup, during a core codec initiated mid-stream format change, or
// during stream start before any input data has been delivered for the new
// stream.
std::unique_ptr<const fuchsia::media::StreamOutputConfig>
CodecImpl::CoreCodecBuildNewOutputConfig(
uint64_t stream_lifetime_ordinal,
uint64_t new_output_buffer_constraints_version_ordinal,
uint64_t new_output_format_details_version_ordinal,
bool buffer_constraints_action_required) {
ZX_DEBUG_ASSERT(IsPotentiallyCoreCodecThread() ||
thrd_current() == stream_control_thread_);
return codec_adapter_->CoreCodecBuildNewOutputConfig(
stream_lifetime_ordinal, new_output_buffer_constraints_version_ordinal,
new_output_format_details_version_ordinal,
buffer_constraints_action_required);
}
void CodecImpl::CoreCodecMidStreamOutputBufferReConfigPrepare() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecMidStreamOutputBufferReConfigPrepare();
}
void CodecImpl::CoreCodecMidStreamOutputBufferReConfigFinish() {
ZX_DEBUG_ASSERT(thrd_current() == stream_control_thread_);
codec_adapter_->CoreCodecMidStreamOutputBufferReConfigFinish();
}
void CodecImpl::CoreCodecRecycleOutputPacket(CodecPacket* packet) {
ZX_DEBUG_ASSERT(thrd_current() == fidl_thread());
codec_adapter_->CoreCodecRecycleOutputPacket(packet);
}