blob: 7d55699f923f10dfb670ac65ed27635d3bb27921 [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.
#ifndef GARNET_BIN_MEDIA_CODECS_SW_OMX_CODEC_RUNNER_SW_OMX_OMX_CODEC_RUNNER_H_
#define GARNET_BIN_MEDIA_CODECS_SW_OMX_CODEC_RUNNER_SW_OMX_OMX_CODEC_RUNNER_H_
#include <fuchsia/mediacodec/cpp/fidl.h>
#include "codec_runner.h"
#include <lib/async-loop/cpp/loop.h>
#include "lib/fidl/cpp/binding.h"
#include "lib/fxl/macros.h"
#include "OMX_Component.h"
#include "OMX_Core.h"
#include <list>
namespace codec_runner {
// The OmxCodecRunner is an implementation of CodecRunner (and of Codec) which
// loads and uses an OMX codec .so lib to perform processing.
// The OMX spec is a bit wishy-washy when it comes to threading. As a general
// rule (with zero known exceptions), we don't hold lock_ while calling OMX.
// The only calls to OMX where we know holding lock would be a problem for
// SimpleSoftOMXComponent.cpp are UseBuffer, AllocateBuffer, FreeBuffer as those
// call directly back into EventHandler on the same thread that calls them. But
// for other OMX codec implementations, the OMX spec would permit calls directly
// back to EventHandler from _any_ call to OMX, AFAICT. So we don't hold lock_
// when calling into OMX.
//
// We ensure proper ordering of calls to OMX using a combination of restricting
// which "ordering domain" can make a given call + queueing some calls to OMX
// (with ordering preserved) while holding lock_ but making the actual call to
// OMX outside lock_.
//
// In particular, we always queue calls to FillThisBuffer and SendCommand, even
// though SimpleSoftOMXComponent.cpp also posts them internally. We need to
// ensure the SendCommand happens after the FillThisBuffer, and holding a lock
// while queueing them is an easy way to ensure the later-queued SendCommand
// will be after the earlier-queued FillThisBuffer, but rather than rely on the
// codec to queue them internally, we take the OMX spec seriously when it says a
// codec might just do everything immediately on the incoming thread, so we
// queue ourselves. But... For EmptyThisBuffer(), we don't queue ourselves.
// Instead, we know that all calls to EmptyThisBuffer() and SendCommand() are
// initiated by the StreamControl ordering domain, so we know that an
// EmptyThisBuffer() call to OMX from StreamControl will happen before a
// SendCommand() queued later by the StreamControl domain. We don't care that
// the relative ordering of FillThisBuffer() and EmptyThisBuffer() calls is not
// preserved.
// For some mime type + omx codec lib combos, we might end up with a derived
// class implementation (derived from OmxCodecRunner) which overrides
// OmxCodecRunner behavior enough to work around the known open issues in an omx
// codec's handling of a particular format - the currently-known example is the
// OMX AAC decoder not handling split ADTS headers, and requiring an OOB codec
// config to be synthesized despite all needed information being present in-band
// in ADTS.
//
// This class expects the creating code to bind an instance of this class to
// a Codec interface request. This class expects that binding to use the same
// async_t that's provided to the constructor of this class.
//
// These are the threads relevant to understanding this class:
// * Caller of Load() + SetAudioDecoderParams() (or analogous). Need not be
// the FIDL thread, but we do expect these to be called in order and
// complete before binding to the channel.
// * "FIDL thread" - the thread backing the async_t passed to the constructor.
// Obviously it's safe for this thread to call Codec methods implemented by
// this class. This class also uses the same thread for all sends to the
// Codec channel. We also use this thread to directly push the OMX codec
// through state changes including waiting for those state changes to take
// effect. The direct state pushing on this thread is not expected to ever
// take a long duration (except for FlushEndOfStreamAndCloseStream(),
// which seems like a reasonable exception given that message's purpose),
// and is not doing anything that would require round-trips to other
// processes. Input packets are the only queued work; aside from input
// packets, there's not any additional queue of incoming messages after the
// incoming channel messages. There might be some minor Codec
// responsiveness benefits to queueing state change work to a separate
// state-driving thread, to do with being able to notice that
// previously-dequeued messages can be safely ignored/skipped, but at the
// moment any such benefits seem unlikely to be significant enough to justify
// the complexity increase that would imply.
// * "OMX thread" - The OMX codec has its own primary thread. Calls to
// EventHandler() method of this class can come from the OMX thread or from
// the FIDL thread (during calls into the OMX codec from the FIDL thread).
// Since we want to use the async_t thread to push state changes of the OMX
// codec, we can't post back to the async_t thread to handle events, as
// processing events is part of OMX state changes. The
// callback-on-same-thread behavior of the OMX codec means we can't be
// holding lock_ when calling into the OMX codec, as we need EventHandler()
// to be able to acquire lock_ as needed. Some OMX codecs use the OMX
// primary thread as the data processing thread, so it's important that we
// not stall the OMX thread by doing any long-duration work in
// EventHandler(). The OMX spec also essentially says that EventHandler()
// must return quickly.
// * Any additional secondary threads created internally by the OMX codec.
// These are for concurrent data processing and don't interact directly with
// OmxCodecRunner.
//
// Queueing of input data and output data helps pipeline and avoids stalling
// data processing thread(s) unnecessarily. Input packets arrive ordered on the
// channel and we call the OMX codec directly from there to queue those input
// packets over to the OMX primary thread. Output data is emitted in order from
// the OMX codec using the OMX primary thread - we queue first to the async_t
// thread by posting a lambda (ordered with respect to others posted the same
// way), then that posted lambda sends an output message to the channel.
//
// TODO(dustingreen): We may be able to avoid posting emitted output over to the
// async_t thread for sending once we know how event sending works and what it
// does or doesn't guarantee. For now we post over there to avoid being fragile
// across any event-related changes.
// Handling of OMX_EventPortSettingsChanged:
//
// It appears OMX is under-specified here. In practice it looks like nData2
// being 0 or OMX_IndexParamPortDefinition means the OMX codec will be waiting
// for a port disable/enable before delivering more output data, else it'll
// keep delivering output data without interruption.
//
// The codec waiting for a disable/enable is how we know whether
// buffer_constraints_action_required.
//
// Either way, the output format may have changed, so we'll generate an
// OnOutputConfig() message in-order with respect to output data.
//
// The way that we achieve an output-ordered OnOutputConfig() varies depending
// on whether action is required.
//
// If action is not required, we just post over to the Output ordering domain
// (Codec FIDL thread) like we would for any emitted output packet.
//
// If action is required, we know the OMX codec will not be generating any
// subsequent output until action is taken, and we know action won't be taken
// until the client has caught up with this most recent config which we haven't
// sent to the client yet, and we know any fakery by the client that appears to
// be catching up to this most recent config impossibly soon will be blocked by
// protocol checking, because we haven't yet sent this latest config to the
// client. We also must ensure that we don't send the OnOutputConfig() until
// _after_ we've de-configured the output buffers server-side. We can also
// double check that the OMX codec is following the rules with regard to not
// trying to generate more output until action is taken. So, in this case we
// can post over to the StreamControl ordering domain, and from there, if the
// output config is still relevant to the current stream, we can disable the
// output port and de-configure output buffers before posting over to the Output
// ordering domain to send the OnOutputConfig() message.
//
// Because the codec is free to _immediately_ change the output config again the
// moment we return from EventHandler(), we are forced to collect the relevant
// output config data from the OMX codec immediately during the call to
// EventHandler() - there is no other valid option.
//
// As with emitted output packets, the StreamOutputConfig has a
// stream_lifetime_ordinal, and the client is free to ignore a StreamOutputConfig
// with stale stream_lifetime_ordinal (though this can increase latency to
// emitted output data in some cases). We rely on the OMX codec resetting its
// "mOutputPortSettingsChange AWAITING_DISABLED / AWAITING_ENABLED" state when
// the codec runs onReset() on dropping to OMX loaded state between streams, and
// we put the output port back to enabled state (despite it having no buffers at
// the time, which OMX is ok with when in OMX loaded state), but we need to give
// the OMX codec output buffers before we can put the codec back to OMX
// executing state and feed input. This means we have to generate a new
// OnConfigChange() for the new stream without any triggering
// OMX_EventPortSettingsChanged if we get input data for a new stream without a
// complete output config yet. This is very similar to the situation we're in
// when the Codec has just been created and we see input data before the client
// has finished configuring the output, so we treat the two situations the same
// way.
//
// As with emitted output packets, we don't necessarily need to generate or send
// a config if we know that the StreamControl ordering domain has already begun
// shutting down the current stream. We know the StreamControl ordering domain
// hasn't finished shutting down the current stream by the fact that we're still
// running in EventHandler(). While it doesn't necessarily matter which we
// choose to do, what does matter is having a valid stream_lifetime_ordinal on
// any generated messages. So if StreamControl has moved on to an even-numbered
// stream_lifetime_ordinal already, we can just elide any emitted output packet
// or output config rather than send a message with an even-numbered
// stream_lifetime_ordinal.
//
// The setting of buffer_constraints_action_required true vs false is driven
// directly from whether nData2 in OMX_EventPortSettingsChanged is 0 or is
// OMX_IndexParamPortDefinition. If so, then action is required.
class OmxCodecRunner : public CodecRunner {
public:
OmxCodecRunner(async_dispatcher_t* fidl_dispatcher, thrd_t fidl_thread,
std::string_view mime_type, std::string_view lib_filename);
//
// CodecRunner
//
bool Load() override;
// Only one of these is called, corresponding to which codec type was
// requested via CodecFactory.
void SetDecoderParams(
fuchsia::mediacodec::CreateDecoder_Params decoder_params) override;
// TODO(dustingreen):
// virtual void SetAudioEncoderParams(...) override;
// virtual void SetVideoEncoderParams(...) override;
// (or combined)
// These are called by CodecRunner at the appropriate times.
void ComputeInputConstraints() override;
void onInputConstraintsReady() override;
void onSetupDone() override;
//
// Codec
//
void EnableOnStreamFailed() override;
void SetInputBufferSettings(
fuchsia::media::StreamBufferSettings input_settings) override;
void SetInputBufferSettings_StreamControl(
fuchsia::media::StreamBufferSettings input_settings);
void AddInputBuffer(fuchsia::media::StreamBuffer buffer) override;
void AddInputBuffer_StreamControl(fuchsia::media::StreamBuffer buffer);
void SetOutputBufferSettings(
fuchsia::media::StreamBufferSettings output_settings) override;
void AddOutputBuffer(fuchsia::media::StreamBuffer buffer) override;
void FlushEndOfStreamAndCloseStream(
uint64_t stream_lifetime_ordinal) override;
void FlushEndOfStreamAndCloseStream_StreamControl(
uint64_t stream_lifetime_ordinal);
void CloseCurrentStream(uint64_t stream_lifetime_ordinal,
bool release_input_buffers,
bool release_output_buffers) override;
void CloseCurrentStream_StreamControl(uint64_t stream_lifetime_ordinal,
bool release_input_buffers,
bool release_output_buffers);
void Sync(SyncCallback callback) override;
void Sync_StreamControl(SyncCallback callback);
void RecycleOutputPacket(
fuchsia::media::PacketHeader available_output_packet) override;
void QueueInputFormatDetails(
uint64_t stream_lifetime_ordinal,
fuchsia::media::FormatDetails format_details) override;
void QueueInputFormatDetails_StreamControl(
uint64_t stream_lifetime_ordinal,
fuchsia::media::FormatDetails format_details);
void QueueInputPacket(fuchsia::media::Packet packet) override;
void QueueInputPacket_StreamControl(fuchsia::media::Packet packet);
void QueueInputEndOfStream(uint64_t stream_lifetime_ordinal) override;
void QueueInputEndOfStream_StreamControl(uint64_t stream_lifetime_ordinal);
protected:
// Constants for indexing into our own member variable arrays.
using Port = uint32_t;
static constexpr uint32_t kFirstPort = 0;
static constexpr uint32_t kInput = 0;
static constexpr uint32_t kOutput = 1;
static constexpr uint32_t kPortCount = 2;
// TODO(dustingreen): maybe supporting non-VMO buffers would justify having a
// base class + a factory method in OmxCodecRunner maybe.
//
// These are tracked via shared_ptr<const Buffer>, to homogenize
// buffer-per-packet mode vs. single-buffer mode.
//
// These are 1:1 with Codec buffers, but not necessarily 1:1 with OMX
// "buffers".
class Buffer {
public:
Buffer(OmxCodecRunner* parent, Port port,
fuchsia::media::StreamBuffer buffer);
~Buffer();
bool Init(bool input_require_write = false);
uint64_t buffer_lifetime_ordinal() const;
uint32_t buffer_index() const;
uint8_t* buffer_base() const;
size_t buffer_size() const;
private:
// The parent OmxCodecRunner instance. Just so we can call parent_->Exit().
// The parent_ OmxCodecRunner out-lives the OmxCodecRunner::Buffer.
OmxCodecRunner* parent_;
Port port_ = 0;
// This msg still has the live vmo_handle.
fuchsia::media::StreamBuffer buffer_;
// This accounts for vmo_offset_begin. The content bytes are not part of
// a Buffer instance from a const-ness point of view.
uint8_t* buffer_base_ = nullptr;
};
// OMX buffers are most closely analogous to Codec packets, so we call these
// "Packet" despite them being 1:1 with OMX "buffers" while in OMX_StateIdle
// or OMX_StateExecuting. Our Codec buffers are represented by Buffer above.
//
// While a Packet instance continues to exist from stream to stream, an OMX
// buffer header does not, because the only way to reset an OMX codec is to
// drop it all the way to OMX_StateLoaded (OMX_CommandFlush isn't enough), and
// the only way to get to OMX_StateLoaded is to call OMX FreeBuffer on each
// OMX buffer header.
class Packet {
public:
// The buffer ptr is not owned. The buffer lifetime is slightly longer than
// the Packet lifetime.
Packet(uint64_t buffer_lifetime_ordinal, uint32_t packet_index,
Buffer* buffer);
uint64_t buffer_lifetime_ordinal() const;
uint32_t packet_index() const;
const Buffer& buffer() const;
// This can be called more than once, but must always either be moving from
// nullptr to non-nullptr, or from non-nullptr to nullptr. This pointer is
// not owned T lifetime of the omx_header pointer.
void SetOmxHeader(OMX_BUFFERHEADERTYPE* omx_header);
OMX_BUFFERHEADERTYPE* omx_header() const;
private:
uint64_t buffer_lifetime_ordinal_ = 0;
uint32_t packet_index_ = 0;
// not owned
Buffer* buffer_ = nullptr;
// not directly owned here - the caller uses this field as a place to stash
// this header while the caller owns the header
OMX_BUFFERHEADERTYPE* omx_header_ = nullptr;
FXL_DISALLOW_COPY_AND_ASSIGN(Packet);
};
// We keep a queue of Stream objects rather than just a single current stream
// object, so we can track which streams are future-discarded and which are
// not yet known to be future-discarded. This difference matters because
// clients are not required to process OnOutputConfig() with
// stream_lifetime_ordinal of a stream that the client has since told the
// server to discard, so we don't want StreamControl ordering domain getting
// stuck waiting on a client to catch up to an output config that the client
// won't process. Instead, the StreamControl ordering domain can ignore any
// additional messages related to the discarded stream until the stream
// discarding message is reached at which point the OMX codec's mid-stream
// output config change is cancelled/forgotten when we move the OMX codec to
// OMX loaded state.
//
// In addition, if we're behind, we can catch up by skipping past some
// messages for future-discarded streams to catch up to non-discarded stream
// input quicker. Theoretically we could do even better by having the FIDL
// thread delete messages previously queued to the StreamControl domain
// regarding a stream that is now known to be discarded by the FIDL thread,
// and collapse/combine CloseCurrentStream() messages, but that's unlikely to
// help much in practice and would make the implementation more difficult to
// read, and we can mitigate unbounded queuing by demanding that clients not
// get too far ahead else we close the channel. While forcing a client to
// wait isn't great, if we don't, we can't impose a circuit-breaker limit on
// the count and/or size of queued channel messages either - ideally setting
// such a limit should be possible for any protocol, so at some convenient
// point the client needs to wait or postpone, but only if the client is
// written to be able to get far ahead in the first place.
//
// We also keep some stream-specific tracking information in here as a
// reasonably clean way to ensure that a new stream's tracking info is
// initialized properly.
class Stream {
public:
// These mutations occur in Output ordering domain (FIDL thread):
explicit Stream(uint64_t stream_lifetime_ordinal);
uint64_t stream_lifetime_ordinal();
void SetFutureDiscarded();
bool future_discarded();
void SetFutureFlushEndOfStream();
bool future_flush_end_of_stream();
// These mutations occur in StreamControl ordering domain:
~Stream();
// This can be called 0-N times for a given stream, and each call replaces
// any previously-set details.
void SetInputFormatDetails(
std::unique_ptr<fuchsia::media::FormatDetails> input_format_details);
// Can be nullptr if no per-stream details have been set, in which case the
// caller should look at OmxCodecRunner::initial_input_format_details_
// instead. The returned pointer is only valid up until the next call to to
// SetInputFormatDetails() or when the stream is deleted, whichever comes
// first. This is only meant to be called on stream_control_thread_.
const fuchsia::media::FormatDetails* input_format_details();
// We send oob_bytes (if any) to the OMX codec just before sending a
// packet to the OMX codec, but only when the stream has OOB data pending.
// A new stream has OOB data initially pending, and it becomes pending again
// if SetInputFormatDetails() is used and the oob_bytes don't match
// the effective oob_bytes before. This way we avoid causing extra
// OMX_EventPortSettingsChanged(s).
void SetOobConfigPending(bool pending);
bool oob_config_pending();
void SetInputEndOfStream();
bool input_end_of_stream();
void SetOutputEndOfStream();
bool output_end_of_stream();
private:
const uint64_t stream_lifetime_ordinal_ = 0;
bool future_discarded_ = false;
bool future_flush_end_of_stream_ = false;
// Starts as nullptr for each new stream with implicit fallback to
// initial_input_format_details_, but can be overridden on a per-stream basis
// with QueueInputFormatDetails().
std::unique_ptr<fuchsia::media::FormatDetails> input_format_details_;
// This defaults to _true_, so that we send a OMX_BUFFERFLAG_CODECCONFIG
// buffer to OMX for each stream, if we have any oob_bytes to send.
bool oob_config_pending_ = true;
bool input_end_of_stream_ = false;
bool output_end_of_stream_ = false;
};
// TODO(dustingreen): For now this is essentially just a combined version of
// the OMX format structures for audio and video, but this is not how we want
// the codec interface to describe format (at least not in terms of the field
// names and inner struct names if nothing else), so this won't be the way the
// client sees the format.
struct OMX_GENERIC_PORT_FORMAT {
// While this is the same structure as out_port_def_, for uncompressed video
// output at least, it's important that this copy is filled out after the
// output port format has changed so that dimensions etc are known. For
// audio it's less important when this is filled out but we try to treat
// video and audio similarly where it's reasonable to do so.
OMX_PARAM_PORTDEFINITIONTYPE definition;
// Depends on which definition.eDomain, so never need more than one of these
// concurrently.
union {
struct {
// Mainly for the eEncoding field.
OMX_AUDIO_PARAM_PORTFORMATTYPE format;
// Depends on which format.eEncoding, so never need more than one of
// these concurrently.
union {
OMX_AUDIO_PARAM_AACPROFILETYPE aac;
OMX_AUDIO_PARAM_PCMMODETYPE pcm;
};
// Potentially for the eProfile field, to at least consider whether we
// should plumb that up to the codec client as potentially relevant
// info. The eProfile field is apparently OMX_AUDIO_AACPROFILETYPE or
// OMX_AUDIO_WMAPROFILETYPE depending on "context" which is presumably
// depending on format.eEncoding.
//
// TODO(dustingreen): We'll need to have a list of eProfile values here
// if we really want to plumb all the info - we're not currently
// sweeping nProfileIndex until we hit OMX_ErrorNoMore.
// OMX_AUDIO_PARAM_ANDROID_PROFILETYPE android_profile;
} audio;
struct {
// TODO(dustingreen): video
} video;
};
};
// Set AAC ADTS mode - called from SetAudioDecoderParams()
void SetInputAacAdts();
void onOmxStateSetComplete(OMX_STATETYPE state_reached);
// things that don't need to be protected by lock_
const std::string mime_type_;
const std::string lib_filename_;
// See codec.md "ordering domain" comments for why we have more than one
// async_t.
// The FIDL thread's async_t is in CodecRunner::dispatcher_ (parent class).
bool is_setup_done_ = false;
std::condition_variable is_setup_done_condition_;
// We don't run any FIDL interfaces on this thread - it's a way to queue
// stream control items such that FlushEndOfStreamAndCloseStream() can block
// on this thread while waiting for previously-queued input data to finish
// processing without getting in the way of recycling output buffers or
// required mid-stream output re-configs.
//
// If we find ourselves trying to get rid of as many thread switches as
// possible, we could refactor this class's implementation of the
// StreamControl ordering domain to not always use a separate thread (some
// complexity cost), or even to never use a separate thread (more complexity
// cost).
//
// We also handle OMX_EventPortSettingsChanged on the stream_control_ thread
// when buffer_constraints_action_required true.
std::unique_ptr<async::Loop> stream_control_;
async_dispatcher_t* stream_control_dispatcher_ = nullptr;
thrd_t stream_control_thread_ = 0;
//
// We separate state into two chunks - one for Codec-related state and one for
// OMX-related state.
//
// TODO(dustingreen): Decide whether to split this class into two classes.
// However, it's likely more fruitful to treat the Codec client as if it will
// always behave perfectly, and move any Codec interface usage validation into
// a separate process that sits in between a Codec client and each Codec
// implementation. Once we do that, the hope is that there would remain little
// point in a separate class to handle Codec interface aspects since those
// aspects would be pretty much 1:1 with incoming Codec method calls, and
// there's not really any fundamentally better middle interface with any
// better representation than the Codec interface itself is already providing.
// The fact is, the translation between Codec interface and OMX interface
// isn't super simple, and it has to happen somewhere. This class is that
// somewhere.
//
//
// Codec-related.
//
// Stuff that's here because of needing to implement the Codec iface. In the
// long run, this shouldn't be much - see above re. potentially moving any
// protocol validation / enforcement to a separate process to avoid each Codec
// implementation (and potentially each Codec client) needing to re-implement
// that part.
//
bool IsActiveStream();
// Some common handling for SetOutputBufferSettings() and
// SetInputBufferSettings()
void SetBufferSettingsCommonLocked(
Port port, const fuchsia::media::StreamBufferSettings& settings,
const fuchsia::media::StreamBufferConstraints& constraints);
// Returns true if adding this buffer completed the input or output
// configuration. For output we need to know this so we can wake up the
// StreamControl ordering domain.
bool AddBufferCommon(Port port, fuchsia::media::StreamBuffer buffer);
void EnsureFutureStreamSeenLocked(uint64_t stream_lifetime_ordinal);
void EnsureFutureStreamCloseSeenLocked(uint64_t stream_lifetime_ordinal);
void EnsureFutureStreamFlushSeenLocked(uint64_t stream_lifetime_ordinal);
bool IsStreamActiveLocked();
void CheckStreamLifetimeOrdinalLocked(uint64_t stream_lifetime_ordinal);
void CheckOldBufferLifetimeOrdinalLocked(Port port,
uint64_t buffer_lifetime_ordinal);
bool IsInputConfiguredLocked();
bool IsOutputConfiguredLocked();
bool IsPortConfiguredCommonLocked(Port port);
void SendFreeInputPacketLocked(fuchsia::media::PacketHeader header);
// During processing of a buffer_constraints_action_required true server-side,
// the server will be de-configuring output buffers unilaterally. Meanwhile,
// the client can concurrently be trying to configure output with an old
// buffer_constraints_version_ordinal. We call this method fairly early in
// the server-side processing to start ignoring any ongoing messages from the
// client re. stale output config.
void StartIgnoringClientOldOutputConfigLocked();
void ValidateBufferSettingsVsConstraints(
Port port, const fuchsia::media::StreamBufferSettings& settings,
const fuchsia::media::StreamBufferConstraints& constraints);
bool enable_on_stream_failed_ = false;
std::unique_ptr<fuchsia::mediacodec::CreateDecoder_Params> decoder_params_;
// TODO(dustingreen): Add these - consider whether/how to factor out strategy,
// consistent with overall factoring to compensate for particular OMX codec
// quirks. audio_encoder_params_ video_encoder_params_ (or combined)
// Regardless of which type of codec was created, these track the input
// FormatDetails.
//
// We keep a copy of the format details used to create the codec, and on a
// per-stream basis those details are used as the default details, but can be
// overridden with QueueInputFormatDetails(). A new stream will default back
// to the FormatDetails used to create the codec unless that stream uses
// QueueInputFormatDetails(). The QueueInputFormatDetails() is not persistent
// across streams.
//
// This field must not be nullptr beyond the codec-type-specific method such
// as SetAudioDecoderParams(). The oob_bytes field can be null if the
// codec type or specific format does not require oob_bytes.
std::unique_ptr<fuchsia::media::FormatDetails> initial_input_format_details_;
// This is the most recent settings received from the client and accepted,
// received via SetInputBufferSettings() or SetOutputBufferSettings(). The
// settings are as-received from the client.
std::unique_ptr<const fuchsia::media::StreamBufferSettings>
port_settings_[kPortCount];
// The server's buffer_lifetime_ordinal, per port. In contrast to
// port_settings_[port].buffer_lifetime_ordinal, this value is allowed to be
// even when the previous odd buffer_lifetime_ordinal is over, due to buffer
// de-allocation.
uint64_t buffer_lifetime_ordinal_[kPortCount] = {};
// I am not absolutely certain that ignoring OMX's
// OMX_EventPortSettingsChanged would be safe in the case where a client
// moves on to a new stream before we've processed
// OMX_EventPortSettingsChanged the normal way. So in that case, we use this
// field to force the client's next stream start to generate a new
// OnOutputConfig() based on current OMX output config. We do this at next
// stream start rather than between streams so the client is forced to pay
// attention to the OnOutputConfig(). The client might de-configure and
// re-configure a few times based on the most recent OnOutputConfig(), so we
// need to associate this with the buffer_constraints_ordinal that OMX said
// meh to.
//
// TODO(dustingreen): Prove that this is needed or not needed, and keep or
// remove.
uint64_t omx_meh_output_buffer_constraints_version_ordinal_ = 0;
// Allocating these values and sending these values are tracked separately,
// so that we can more tightly enforce the protocol. If a client tries to
// act on a newer ordinal before the server has actually sent it, the server
// will notice that invalid client behavior and close the channel (instead
// of just tracking a single number, which would potentially let the client
// drive the server into the weeds).
//
// The next value we'll use for output buffer_constraints_version_ordinal and
// output format_details_version_ordinal.
uint64_t next_output_buffer_constraints_version_ordinal_ = 1;
// For the OMX adapter, if the buffer constraints change, then the format
// details ordinal also changes (since there's not really any benefit to
// detecting lack of change). But for format-only changes that don't require
// buffer re-allocation, we can just increment the format details ordinal.
uint64_t next_output_format_details_version_ordinal_ = 1;
// Separately from ordinal allocation, we track the most recent ordinal that
// we've actually sent to the client, to allow tighter protocol enforcement in
// case of a hostile client.
uint64_t sent_buffer_constraints_version_ordinal_[kPortCount] = {0};
uint64_t sent_format_details_version_ordinal_[kPortCount] = {0};
uint64_t last_required_buffer_constraints_version_ordinal_[kPortCount] = {0};
// For OmxCodecRunner, the initial StreamOutputConfig is sent immediately after
// the input StreamBufferConstraints. The StreamOutputConfig is likely to
// change again before any output data is emitted, but it _may not_.
std::unique_ptr<const fuchsia::media::StreamOutputConfig> output_config_;
// We ignore the part of the OMX spec where it says we should free the
// underlying buffer "before" calling OMX FreeBuffer(). For one thing there's
// no valid/reasonable way for OMX to actually detect whether that occurred or
// not, and for another, it's none of OMX's business anyway. This allows us
// to move the OMX codec back to OMX loaded state to get
// SimpleSoftOMXComponent.cpp to call onReset(), which we need to happen
// between streams, without forcing us to free underlying buffers. It still
// requires us to call OMX FreeBuffer() for every input packet and every
// output packet between streams, but there's nothing we can do about that
// above OMX.
std::vector<std::unique_ptr<Buffer>> all_buffers_[kPortCount];
// This is the stream_lifetime_ordinal of the current stream as viewed from
// StreamControl ordering domain. This is the stream lifetime ordinal that
// gets removed from the head of the Stream queue when StreamControl is done
// with the stream.
uint64_t stream_lifetime_ordinal_ = 0;
// This is the stream_lifetime_ordinal of the most recent stream as viewed
// from the Output ordering domain (FIDL thread). This is the stream lifetime
// ordinal that we add to the tail of the Stream queue.
uint64_t future_stream_lifetime_ordinal_ = 0;
// The Output ordering domain (FIDL thread) adds items to the tail of this
// queue, and the StreamControl ordering domain removes items from the head
// of this queue. This queue is how the StreamControl ordering domain knows
// whether a stream is discarded or not. If a stream isn't discarded then the
// StreamControl domain can keep waiting for the client to process
// OnOutputConfig() for that stream. If the stream has been discarded, then
// StreamControl ordering domain cannot expect the client to ever process
// OnOutputConfig() for the stream, and the StreamControl ordering domain can
// instead move on to the next stream.
//
// In addition, this can allow the StreamControl ordering domain to skip past
// stream-specific items for a stream that's already known to be discarded by
// the client.
std::list<std::unique_ptr<Stream>> stream_queue_;
// When no current stream, this is nullptr. When there is a current stream,
// this points to that stream, owned by stream_queue_.
Stream* stream_ = nullptr;
// True means free at protocol level. False means in-flight at protocol
// level. A size of 0 means not-allocated at protocol level. This is used
// to check for nonsense from the client.
//
// TODO(dustingreen): Consider moving these into Packet, despite probably
// losing on packing efficiency.
std::vector<bool> packet_free_bits_[kPortCount];
// This is the buffer_lifetime_ordinal from SetOutputBufferSettings() or
// SetInputBufferSettings(). This is used for protocol enforcement, to
// enforce that AddOutputBuffer() or AddInputBuffer() is part of the same
// buffer_lifetime_ordinal.
uint64_t protocol_buffer_lifetime_ordinal_[kPortCount] = {};
//
// Adapter-related.
//
// Stuff that's in the middle between Codec iface and OMX.
//
void StartNewStream(std::unique_lock<std::mutex>& lock,
uint64_t stream_lifetime_ordinal);
void EnsureStreamClosed(std::unique_lock<std::mutex>& lock);
// Only EnsureStreamClosed() should call this. All other callers want
// EnsureStreamClosed() instead.
void EnsureCodecStreamClosedLockedInternal();
// Query OMX for output config info, convert that into StreamOutputConfig, and
// send that to the client with OnOutputConfig(). This can be called on the
// setup ordering domain, or on StreamControl ordering domain.
void GenerateAndSendNewOutputConfig(std::unique_lock<std::mutex>& lock,
bool buffer_constraints_action_required);
std::unique_ptr<const fuchsia::media::StreamOutputConfig>
BuildNewOutputConfig(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);
std::unique_ptr<const fuchsia::media::StreamOutputConfig>
CreateNewOutputConfigFromOmxOutputFormat(
std::unique_ptr<const OmxCodecRunner::OMX_GENERIC_PORT_FORMAT>
omx_output_format,
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);
void PopulateFormatDetailsFromOmxOutputFormat_Audio(
const OmxCodecRunner::OMX_GENERIC_PORT_FORMAT& omx_output_format,
fuchsia::media::FormatDetails* format_details);
void EnsureBuffersNotConfiguredLocked(Port port);
fuchsia::media::AudioChannelId AudioChannelIdFromOmxAudioChannelType(
OMX_AUDIO_CHANNELTYPE omx_audio_channeltype);
// These Packet(s) are both Codec packets and OMX "buffers".
//
// These vectors own these buffers.
std::vector<std::unique_ptr<Packet>> all_packets_[kPortCount];
uint32_t omx_output_buffer_with_omx_count_ = 0;
std::condition_variable omx_output_buffers_done_returning_condition_;
// This OMX input buffer is special because it does not correspond to any
// Packet, and is reserved by this class for sending an empty OMX buffer
// with EOS set to OMX. For this purpose, we don't need the buffer's data
// space to really be there, but just in case any OMX SW codec reads from an
// input buffer despite zero valid data indicated, we point the extra buffer
// at the same data space as packet 0 in all_packets_. Zero codecs should be
// writing to any input buffer, and we'll detect any violation of that rule
// since we map our input VMOs read-only.
//
// We wrap this in a "Packet" primarily so that we can use "Packet*" as the
// void* we pass to/from OMX re. an OMX buffer, without forcing an
// OmxBuffer layer of abstraction to exist.
//
// The "eos" is "end of stream".
std::unique_ptr<Packet> omx_input_packet_eos_;
bool omx_input_packet_eos_free_ = true;
// This OMX input buffer is special because it does not correspond to any
// Packet, and is reserved by this class for sending OMX the
// OMX_BUFFERFLAG_CODECCONFIG omx buffer, which contains the
// FormatDetails.oob_bytes. Unlike omx_input_packet_eos_, this
// packet's buffer corresponds to a VMO allocated server-side, since we do
// need it to be able to hold real data, unlike the eos packet.
//
// We wrap this in a "Packet" primarily so that we can use "Packet*" as the
// void* we pass to/from OMX re. an OMX buffer, without forcing an
// OmxBuffer layer of abstraction to exist.
//
// The "oob" is "out of band".
std::unique_ptr<Packet> omx_input_packet_oob_;
std::unique_ptr<Buffer> omx_input_buffer_oob_;
bool omx_input_packet_oob_free_ = true;
std::condition_variable omx_input_packet_oob_free_condition_;
// This condition variable notifies all waiting threads when any of the
// following occur:
// * Output goes from not configured to configured.
// * A stream gets marked as discarded.
// Note that input going from not configured to configured does not trigger
// this, as that's already inherently in the StreamControl ordering domain.
//
// This allows the StreamControl ordering domain to wait for a stream's output
// config to catch up _or_ for the stream to get discarded by the client, in
// which case the client won't necessarily ever be catching up to the stream's
// output config.
//
// As is typical with condition variables, we don't worry about spurious
// wakes. We might be waking re. a different stream than the StreamControl is
// actually working on currently, for example.
std::condition_variable wake_stream_control_;
// This is set when stream_.output_end_of_stream is set.
std::condition_variable output_end_of_stream_seen_;
//
// OMX-related.
//
// Stuff that's here to deal with OMX. This stuff would pretty much need to
// exist even if were just trying to use an OMX codec without adapting to
// the Codec interface.
//
// OMX handlers
// Shim handler that calls pAppData->EventHandler() essentially.
static OMX_ERRORTYPE omx_EventHandler(OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData, // this
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData);
static OMX_ERRORTYPE omx_EmptyBufferDone(
OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
static OMX_ERRORTYPE omx_FillBufferDone(OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
// These are called by omx_EventHanlder (or corresponding callback) with
// this == pAppData, and with hComponent not a parameter, because that's
// available as a member variable.
OMX_ERRORTYPE EventHandler(OMX_IN OMX_EVENTTYPE eEvent, OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2, OMX_IN OMX_PTR pEventData);
OMX_ERRORTYPE EmptyBufferDone(OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE FillBufferDone(OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer);
std::unique_ptr<const OMX_GENERIC_PORT_FORMAT> OmxGetOutputFormat();
void OmxTryRecycleOutputPacketLocked(OMX_BUFFERHEADERTYPE* header);
void OmxQueueInputPacket(const fuchsia::media::Packet& packet);
void EnsureOmxStateExecuting(std::unique_lock<std::mutex>& lock);
void EnsureOmxBufferCountCurrent(std::unique_lock<std::mutex>& lock);
void EnsureOmxStateLoaded(std::unique_lock<std::mutex>& lock);
void OmxStartStateSetLocked(OMX_STATETYPE omx_state_desired);
void OmxWaitForState(std::unique_lock<std::mutex>& lock,
OMX_STATETYPE from_state, OMX_STATETYPE desired_state);
void OmxFreeAllBufferHeaders(std::unique_lock<std::mutex>& lock);
void OmxFreeAllPortBufferHeaders(std::unique_lock<std::mutex>& lock,
Port port);
void OmxFreeBufferHeader(std::unique_lock<std::mutex>& lock, Port port,
Packet* packet);
void OmxPortUseBuffers(std::unique_lock<std::mutex>& lock, Port port);
OMX_BUFFERHEADERTYPE* OmxUseBuffer(std::unique_lock<std::mutex>& lock,
Port port, const Packet& packet);
void OmxOutputStartSetEnabledLocked(bool enable);
void OmxWaitForOutputEnableStateChangeDone(
std::unique_lock<std::mutex>& lock);
void OmxFillThisBufferLocked(OMX_BUFFERHEADERTYPE* header);
void OmxQueueInputOOB();
void OmxQueueInputEOS();
void onOmxStreamFailed(uint64_t stream_lifetime_ordinal);
void onOmxEventPortSettingsChanged(uint64_t stream_lifetime_ordinal);
void OmxWaitForOutputBuffersDoneReturning(std::unique_lock<std::mutex>& lock);
// nullptr if not created yet, or the OMX component handle if created
OMX_COMPONENTTYPE* omx_component_;
OMX_CALLBACKTYPE omx_callbacks_;
// what OMX state - starts in OMX_StateInvalid until the component is created
// at which point the component will be in OMX_StateLoaded initially. See OMX
// IL spec for more on how component states and state transitions work. We
// don't handle all possible OMX state transitions in this class, only the
// ones that are relevant/possible for the android SW codecs. For example we
// don't do anything with the "wait for resources" state.
OMX_STATETYPE omx_state_;
// This tracks the latest state that we've tried to move the codec to, and
// is updated as soon as we start trying to move the codec to the new state.
// Keeping this around helps us avoid pestering the OMX codec if/when client
// code is trying to ask for something unreasonable/impossible given an
// ongoing state change, without leaning on the OMX codec to do this check
// for us. At the least, this achieves a nicer error message than a generic
// failure message re. the OMX codec returning failure.
OMX_STATETYPE omx_state_desired_;
// Any time the omx_state_ changes this condition variable signals all
// waiters. See WaitForStateOrError() for a way to wait for a specific state
// to be reached. This condition variable must only be waited on using a
// unique_lock on lock_ (not on any other lock).
std::condition_variable omx_state_changed_;
bool omx_output_enabled_ = true;
bool omx_output_enabled_desired_ = true;
std::condition_variable omx_output_enabled_changed_;
bool is_omx_recycle_enabled_ = false;
OMX_U32 omx_port_index_[kPortCount] = {};
// These are the _initial_ OMX port defs. This initial-ness is important
// because OMX allows nBufferSize to change, but we'd like to use the initial
// nBufferSize as the Codec min buffer bytes per packet for input, and not
// require the Codec client to ever re-configure input buffers. This seems
// reasonable because even OMX doesn't actually force re-configuration of
// input buffers during dynamic output format detection when starting a
// stream, so we'll see if we can get the up-front input nBufferSize to work
// as a Codec input min buffer size.
//
// We'll smooth over when OMX nBufferSize is larger then Codec nBufferSize on
// input by reporting nBufferSize to OMX but not filling buffers beyond their
// actual Codec size. If any codec tries to read beyond the valid data bytes
// of an input packet, we'll have to re-evaluate this strategy...
//
// On output, OMX is allowed to change output format, and we'll let OMX
// nBufferSize drive per_packet_buffer_bytes_min.
//
// We never force nBufferSize to increase via SetParameter() with
// OMX_IndexParamPortDefinition since the OMX spec says nBufferSize is
// read-only, despite SimpleSoftOMXComponent.cpp permitting nBufferSize to
// increase but not decrease. But OMX may unilaterially change nBufferSize
// both up or down based on other parameters being set, or based on output
// format detection. For input, it isn't changed during format detection, but
// still can change up or down based on other parameters being set. See above
// re. how we smooth over any such input nBufferSize changes.
//
// TODO(dustingreen): We should only really be using this for asserts to do
// with the input buffer sizes - if that remains true then we could replace
// this with OMX_U32 omx_initial_input_nBufferSize_ or similar.
OMX_PARAM_PORTDEFINITIONTYPE omx_initial_port_def_[kPortCount] = {};
// This isn't the latest we've seen from OMX via any GetParameter call.
// Instead, this is the latest we've seen from OMX via any path where we
// expect OMX to potentially change OMX buffer constraints, in a way that we
// are forced to reflect via the Codec interface.
//
// For input, if nBufferSize increases in OMX unilaterally (we have no such
// cases yet but could in future), we report larger-than-actual input buffers
// to OMX and then don't fill them beyond their actual size.
//
// For output, we only expect nBufferSize to change when OMX triggers
// OMX_EventPortSettingsChanged. We update the output part of this member
// var during OmxGetOutputFormat(), just because that's the common path
// involved.
OMX_PARAM_PORTDEFINITIONTYPE omx_port_def_[kPortCount] = {};
//
// Overall behavior.
//
// Post to dispatcher in a way that's guaranteed to run the posted work in the
// same order as the posting order.
void PostSerial(async_dispatcher_t* dispatcher, fit::closure to_run);
FXL_DISALLOW_IMPLICIT_CONSTRUCTORS(OmxCodecRunner);
};
} // namespace codec_runner
#endif // GARNET_BIN_MEDIA_CODECS_SW_OMX_CODEC_RUNNER_SW_OMX_OMX_CODEC_RUNNER_H_