blob: 08e2adb760817fa133c85f62f1f5b3a5fb5839f9 [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_LIB_MEDIA_TEST_INCLUDE_LIB_MEDIA_TEST_CODEC_CLIENT_H_
#define GARNET_LIB_MEDIA_TEST_INCLUDE_LIB_MEDIA_TEST_CODEC_CLIENT_H_
#include "codec_buffer.h"
#include "codec_output.h"
#include <fuchsia/mediacodec/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fidl/cpp/binding.h>
#include <list>
// This class is just _a_ codec client, and should be read as an example only,
// and probably not a fully complete example either. This class is just here
// to organize the code involved in setting up a Codec with input buffers and
// packets, feeding it input data in a single Stream, setting up the output
// buffers and packets, and ensuring that all input data is processed into
// output.
//
// A Codec client that wants to seek a logical stream or re-use a Codec to
// decode another logical stream would likely want to make more use of the
// stream_lifetime_ordinal to feed input data and to accept output data (vs.
// this example which has only one Stream lifetime which isn't visible outside
// this class. Re-using a Codec instance for a new stream is encouraged,
// especially when the output format isn't likely to change from stream to
// stream, which avoids re-configuring output buffers across the stream switch.
//
// The use of particular threads of execution to call this class is intended to
// clarify reasonable ordering and concurrency of messages sent and processed on
// the Codec interface. There is no requirement that a Codec client use
// dedicated threads to achieve a permitted and useful ordering. The client does
// of course need to stay within the sequencing rules of the interface.
class CodecClient {
public:
// loop - The loop that all the FIDL work will run on. We configure this
// explicitly instead of using the default loop per thread mechanism, because
// we want to be very sure that we'll be posting to the correct loop to send
// messages using that loop's single thread, as ProxyController doesn't have
// a lock_ in it.
CodecClient(async::Loop* loop, fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem);
~CodecClient();
// Separate from Start() because we don't wan this class to handle the Codec
// creation, so the caller needs a server endpoint to send off to a Codec
// server (via the CodecFactory).
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> GetTheRequestOnce();
// Get the Codec into a state where it's ready to process input data.
void Start();
// On this thread, wait for an available input packet_index, and when one is
// available, create a new Packet object to represent that packet_index
// and return that. The packet_index will be filled out, but not the rest of
// the packet. It's up to the caller to set stream_lifetime_ordinal and other
// fields.
//
// Since in this example we're using a buffer per packet, waiting for a free
// packet is also waiting for a free buffer, with the same index as
// packet_index.
//
// To return eventually, this call relies on output being accepted on an
// ongoing basis from the Codec using some other thread(s), processed, and
// those output packets freed back to the codec.
std::unique_ptr<fuchsia::media::Packet> BlockingGetFreeInputPacket();
const CodecBuffer& GetInputBufferByIndex(uint32_t packet_index);
const CodecBuffer& GetOutputBufferByIndex(uint32_t packet_index);
void QueueInputFormatDetails(
uint64_t stream_lifetime_ordinal,
fuchsia::media::FormatDetails input_format_details);
// Queue an input packet to the codec.
void QueueInputPacket(std::unique_ptr<fuchsia::media::Packet> packet);
void QueueInputEndOfStream(uint64_t stream_lifetime_ordinal);
void FlushEndOfStreamAndCloseStream(uint64_t stream_lifetime_ordinal);
// Use the current thread to do what is necessary to get an output packet.
// Near the start, this will include configuring output buffers once. In
// steady state this thread will just wait for an output packet to show up or
// the stream to be done. If an end_of_stream packet shows up, this method
// will return that packet.
//
// The returned Packet itself will remain valid and readable as long as
// the caller keeps it around. However, if the caller calls
// BlockingGetEmittedOutput() again, the entire set of output buffers
// can get deallocated and replacement buffers allocated, rendering the
// meaning of the returned Packet only usable until
// BlockingGetEmittedOutput() is called again. This means the calling
// code needs to go ahead and do whatever it wants to do with the output
// data in the corresponding output buffer before calling this method again.
//
// A real client can delay output buffer re-configuration until previous
// output data has been fully processed, or can ensure that old output buffers
// remain live until the old output data is done with them (configuring new
// output buffers doesn't inherently delete the old ones, but having both
// around at once does use more resources concurrently).
std::unique_ptr<CodecOutput> BlockingGetEmittedOutput();
// Recycle an output packet for re-use.
void RecycleOutputPacket(fuchsia::media::PacketHeader free_packet);
void Stop();
// On this thread, while the codec is being fed input data on
private:
friend class CodecStream;
void PostToFidlThread(fit::closure to_run);
void CallSyncAndWaitForResponse();
void TrackOutputStreamLifetimeOrdinal(
uint64_t output_stream_lifetime_ordinal);
bool CreateAndSyncBufferCollection(
fuchsia::sysmem::BufferCollectionSyncPtr* out_buffer_collection,
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>*
out_codec_sysmem_token);
bool WaitForSysmemBuffersAllocated(
fuchsia::sysmem::BufferCollectionSyncPtr* buffer_collection_param,
fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info);
bool ConfigurePortBufferCollection(
bool is_output, uint64_t new_buffer_lifetime_ordinal,
uint64_t buffer_constraints_version_ordinal,
uint32_t packet_count_for_server, uint32_t packet_count_for_client,
uint32_t* out_packet_count,
fuchsia::sysmem::BufferCollectionPtr* out_buffer_collection,
fuchsia::sysmem::BufferCollectionInfo_2* out_buffer_collection_info);
//
// Events:
//
void OnStreamFailed(uint64_t stream_lifetime_ordinal);
void OnInputConstraints(
fuchsia::media::StreamBufferConstraints input_constraints);
void OnFreeInputPacket(fuchsia::media::PacketHeader free_input_packet);
// This example ignores any buffer constraints with
// buffer_constraints_action_required false.
//
// As with any proper Codec client we must tolerate this event getting sent by
// the server more times than would be necessary if it were only for the
// client's benefit. The server is allowed to force an output buffer
// re-configuration just because it wants one. This rule simplifies some
// codec server implementations substantially and allows increased coverage of
// format change handling in clients, at least in the sense of ever seeing
// more than one of this message per Codec instance (though not quite to the
// degree needed to fully cover client handling of true mid-stream format
// changes).
void OnOutputConstraints(
fuchsia::media::StreamOutputConstraints output_config);
// Ever output format is stream-specific with stream_lifetime_ordinal set, and
// the server is required to elide any unnecessary OnOutputFormat messages
// without any subsequent OnOutputPacket message to which the OnOutputFormat
// applies. The format continues to apply to all subsequent output packets of
// the same stream until the stream ends or a new OnOutputFormat message is
// sent by the server.
void OnOutputFormat(
fuchsia::media::StreamOutputFormat output_format);
// Every output packet is stream-specific with stream_lifetime_ordinal set.
void OnOutputPacket(fuchsia::media::Packet output_packet,
bool error_detected_before, bool error_detected_during);
void OnOutputEndOfStream(uint64_t stream_lifetime_ordinal,
bool error_detected_before);
std::mutex lock_;
async::Loop* loop_ = nullptr; // must override
async_dispatcher_t* dispatcher_ = nullptr; // must override
fuchsia::media::StreamProcessorPtr codec_;
// This only temporarily holds the Codec request that was created during the
// constructor. If the caller asks for this more than once, the subsequent
// requests give back a !is_valid() request.
fidl::InterfaceRequest<fuchsia::media::StreamProcessor> temp_codec_request_;
fuchsia::sysmem::AllocatorPtr sysmem_;
fuchsia::sysmem::BufferCollectionPtr input_buffer_collection_;
fuchsia::sysmem::BufferCollectionPtr output_buffer_collection_;
// We're use unique_ptr<> here only for it's optional-ness.
std::unique_ptr<fuchsia::media::StreamBufferConstraints> input_constraints_;
std::condition_variable input_constraints_exist_condition_;
// In this example, we use buffer-per-packet mode, but for input buffers it
// is allowed to share parts of a single buffer across all input packets.
// This example doesn't yet demonstrate that mode however.
//
// TODO(dustingreen): There's not presently any input mode that allows any
// packet to refer to any of a set of multiple buffers, but if we think that
// would be of any real use, we could add it. Either add that mode to Codec
// interface and down, or remove this comment.
//
// The index into the vector is the same as packet_id, since we're running in
// buffer-per-packet mode.
std::vector<std::unique_ptr<CodecBuffer>> all_input_buffers_;
// We don't even create the output buffers until after the output format is
// known, which can require some input data first.
std::vector<std::unique_ptr<CodecBuffer>> all_output_buffers_;
// In contrast to buffers, packets don't really exist in full continuously.
// The set of packet_index values is a thing, but each packet_index re-use is
// really best thought of as a new fuchsia::media::Packet lifetime,
// so that's how we represent the packets in this example - as created and
// owned by their input and output arcs, not kept allocated continuously by
// CodecClient.
// This vector is used to track which input packet_id(s) are free. A free
// packet_id means the buffer at all_input_buffers_[packet_id] is free. We
// push to the end and pop from the end since that's what vector<> is good at.
std::vector<uint32_t> input_free_list_;
std::condition_variable input_free_list_not_empty_;
// In this example, we do verify that the server is being sane with respect
// to free/busy status of packets. In general a client shouldn't let a
// badly-behaved server cause the client to crash.
//
// true - free
// false - not free (from when we queue a lambda that'll end up sending the
// packet to the codec, to when we receive the message from the codec saying
// the packet is free again)
std::vector<bool> input_free_bits_;
// Which output packets are free from the client point of view. If the server
// tries to emit the same packet more than once concurrently, these bits are
// how we notice.
std::vector<bool> output_free_bits_;
// The server must order its output strictly by stream_lifetime_ordinal - once
// a new stream_lifetime_ordinal has started the server can't output anything
// with an older stream_lifetime_ordinal.
uint64_t output_stream_lifetime_ordinal_ = 0;
// In contrast to free input packets, we care about the content of emitted
// output packets and their order. In addition, OnOutputConstraints() is ordered
// with respect to output packets, so we just queue those along with the
// output packets to avoid any ambiguity.
//
// A client that is immediately processing every output packet and just tracks
// the most recent output config would work as long as it always associates
// an output packet with the closest prior StreamOutputConstraints.
std::list<std::unique_ptr<CodecOutput>> emitted_output_;
// For input, in this example we just know what the input format details are
// and we send those to CodecFactory as part of CreateAudioDecoder_Params,
// so we don't really need them as a member variable.
// For output, we have StreamOutputConstraints here as a shared_ptr<> so we can
// explicitly associate each output packet with the config that applies to the
// output packet.
//
// Note that stream_lifetime_ordinal is nearly entirely orthogonal from which
// config applies. The only interaction is that sometimes a new stream will
// happen to have a different format so will cause format_details to update.
std::shared_ptr<const fuchsia::media::StreamOutputConstraints> last_output_constraints_;
std::shared_ptr<const fuchsia::media::StreamOutputConstraints>
last_required_output_constraints_;
// Most non-test clients will want to track this per-stream, since a
// StreamOutputFormat from an old stream_lifetime_ordinal() isn't relevant to
// the current stream_lifetime_ordinal. A server is not allowed to send
// OnOutputFormat for an old stream_lifetime_ordinal.
std::shared_ptr<const fuchsia::media::StreamOutputFormat> last_output_format_;
// True if OnOutputFormat is more recent than OnOutputPacket. This is
// intentionally tracked regardless of whether last_output_format_ has
// since been "forgotten" (set to nullptr) and regardless of whether the
// stream has since switched to a new stream, because we require servers to
// elide all unnecessary OnOutputFormat messages. An OnOutputFormat without
// any subsequent OnOutputPacket of the same stream is by definition
// unnecessary.
bool is_format_since_last_packet_ = false;
// Becomes true when we get a new last_output_constraints_ with action
// required, and becomes false just before taking the needed action based on
// last_output_constraints_.
bool output_constraints_action_pending_ = false;
// Only odd values are allowed for buffer_lifetime_ordinal.
uint64_t next_output_buffer_lifetime_ordinal_ = 1;
uint64_t current_output_buffer_lifetime_ordinal_ = 0;
// Invariant:
// output_pending_ == (!emitted_output_.empty() ||
// output_config_action_pending_)
bool ComputeOutputPendingLocked() {
return !emitted_output_.empty() || output_constraints_action_pending_;
}
bool output_pending_ = false;
std::condition_variable output_pending_condition_;
};
#endif // GARNET_LIB_MEDIA_TEST_INCLUDE_LIB_MEDIA_TEST_CODEC_CLIENT_H_