| // 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 "omx_codec_runner.h" |
| |
| #include "so_entry_point.h" |
| |
| #include <dlfcn.h> |
| #include "OMX_Core.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/time.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fxl/arraysize.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| |
| // 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 { |
| |
| 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_; |
| FXL_DISALLOW_IMPLICIT_CONSTRUCTORS(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_; |
| FXL_DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedRelock); |
| }; |
| |
| // 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; |
| // Input constraints always have version ordinal 1 because version 0 isn't a |
| // valid ordinal (to simplify initial state handling) and there's only ever one |
| // version. |
| constexpr uint64_t kInputBufferConstraintsVersionOrdinal = 1; |
| // This is fairly arbitrary, but avoid recommending buffers that are pointlessly |
| // large. This is subject to change. |
| constexpr uint32_t kOmxRecommendedBufferVsMinBufferFactor = 1; |
| // This is fairly arbitrary. This is subject to change. Note that this places |
| // a constraint on the max vs. min at Codec layer, not the max at OMX layer, |
| // because at the OMX layer the nBufferSize is virtualized fairly heavily in |
| // single-buffer mode, so the OMX layer max nBufferSize value can become much |
| // larger than this factor (vs the initial value of nBufferSize). |
| constexpr uint32_t kOmxMaxBufferVsMinBufferFactor = 5; |
| |
| // This does not auto-add any buffers for client use or for performance, and we |
| // don't want to have every layer adding more buffer count for such reasons, so |
| // pass through the nBufferCountMin as the min and the recommended number. |
| constexpr uint32_t kOmxRecommendedBufferCountVsMinBufferCountFactor = 1; |
| // More than 3 times the min is probably pointless. This is fairly arbitrary. |
| constexpr uint32_t kOmxRecommendedMaxBufferCountVsMinBufferCountFactor = 3; |
| // This is fairly arbitrary. |
| constexpr uint32_t kOmxMaxBufferCountVsMinBufferCountFactor = 5; |
| |
| // These are packet_count based, so 0 means one beyond the last normal packet |
| // index, 1 means 2 beyond the last normal packet index. OMX knows about these |
| // packets (as OMX buffers) but the Codec client does not. |
| constexpr uint32_t kHiddenInputPacketIndexOffsetOob = 0; |
| constexpr uint32_t kHiddenInputPacketIndexOffsetEos = 1; |
| constexpr uint32_t kHiddenInputPacketCount = 2; |
| |
| // For input, we only send OnInputBufferSettings() once at the very beginning, |
| // so for now it makes sense (barely) to help the client select the client's |
| // buffer_lifetime_ordinal. |
| constexpr uint32_t kBestFirstBufferLifetimeOrdinal = 1; |
| // For output, don't try to help the client count from the wrong end of the |
| // channel. At best this would be of marginal value to simple clients and at |
| // worst it would lead to an expectation that the server knows what |
| // buffer_lifetime_ordinal values the client has used so far which the server |
| // has no way of knowing at any given instant. |
| constexpr uint32_t kInvalidDefaultBufferLifetimeOrdinal = 0; |
| |
| constexpr fuchsia::media::AudioChannelId |
| kOmxAudioChannelTypeToAudioChannelId[] = { |
| fuchsia::media::AudioChannelId::SKIP, // OMX_AUDIO_ChannelNone |
| fuchsia::media::AudioChannelId::LF, // OMX_AUDIO_ChannelLF |
| fuchsia::media::AudioChannelId::RF, // OMX_AUDIO_ChannelRF |
| fuchsia::media::AudioChannelId::CF, // OMX_AUDIO_ChannelCF |
| fuchsia::media::AudioChannelId::LS, // OMX_AUDIO_ChannelLS |
| fuchsia::media::AudioChannelId::RS, // OMX_AUDIO_ChannelRS |
| fuchsia::media::AudioChannelId::LFE, // OMX_AUDIO_ChannelLFE |
| fuchsia::media::AudioChannelId::CS, // OMX_AUDIO_ChannelCS |
| fuchsia::media::AudioChannelId::LR, // OMX_AUDIO_ChannelLR |
| fuchsia::media::AudioChannelId::RR, // OMX_AUDIO_ChannelRR |
| }; |
| // We do allow translating OMX_AUDIO_ChannelNone ("unused or empty") to Skip. |
| constexpr uint32_t kOmxAudioChannelTypeSupportedMin = 0; |
| constexpr uint32_t kOmxAudioChannelTypeSupportedMax = 9; |
| |
| 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); |
| } |
| |
| template <typename OMX_STRUCT> |
| void InitOmxStruct(OMX_STRUCT* omx_struct) { |
| memset(omx_struct, 0, sizeof(*omx_struct)); |
| omx_struct->nSize = sizeof(*omx_struct); |
| // Same as in SoftOMXComponent.cpp. |
| omx_struct->nVersion.s.nVersionMajor = 1; |
| omx_struct->nVersion.s.nVersionMinor = 0; |
| omx_struct->nVersion.s.nRevision = 0; |
| omx_struct->nVersion.s.nStep = 0; |
| } |
| |
| } // namespace |
| |
| namespace codec_runner { |
| |
| OmxCodecRunner::OmxCodecRunner(async_dispatcher_t* fidl_dispatcher, |
| thrd_t fidl_thread, std::string_view mime_type, |
| std::string_view lib_filename) |
| : CodecRunner(fidl_dispatcher, fidl_thread), |
| mime_type_(mime_type), |
| lib_filename_(lib_filename) { |
| // nothing else to do here |
| } |
| |
| // |
| // CodecRunner |
| // |
| |
| bool OmxCodecRunner::Load() { |
| // Load the per-omx-codec .so and find the one entry point. |
| createSoftOMXComponent_fn createSoftOMXComponent = nullptr; |
| void* dl = dlopen(lib_filename_.c_str(), RTLD_NOW | RTLD_LOCAL); |
| if (!dl) { |
| printf("dl is nullptr\n"); |
| return false; |
| } |
| VLOGF("loaded codec .so file.\n"); |
| createSoftOMXComponent = reinterpret_cast<createSoftOMXComponent_fn>( |
| dlsym(dl, "entrypoint_createSoftOMXComponent")); |
| if (!createSoftOMXComponent) { |
| printf("dlsym() failed.\n"); |
| return false; |
| } |
| VLOGF("found entrypoint.\n"); |
| |
| // This lock hold interval isn't really needed, but it also doesn't hurt, and |
| // it might make it easier to get FXL_GUARDED_BY annotations to work. |
| std::unique_lock<std::mutex> lock(lock_); |
| |
| omx_callbacks_.EventHandler = omx_EventHandler; |
| omx_callbacks_.EmptyBufferDone = omx_EmptyBufferDone; |
| omx_callbacks_.FillBufferDone = omx_FillBufferDone; |
| auto app_data = reinterpret_cast<OMX_PTR>(this); |
| // The direct_ version bypasses the .so stuff above, because it's an easier |
| // workflow if we don't have to replace two files, for now, until we figure |
| // out if there's a clean way to load an .so from an arbitrary file - like |
| // with dlopen_vmo or something maybe... Or with a different config that lets |
| // dlopen() find our .so in places other than /system/lib, which is currently |
| // hard to replace. Maybe package server stuff will make this easier soon. |
| createSoftOMXComponent("OMX.google.aac.decoder", &omx_callbacks_, app_data, |
| &omx_component_); |
| if (!omx_component_) { |
| printf("failed to create component_\n"); |
| return false; |
| } |
| VLOGF("successfully created omx_component_\n"); |
| |
| OMX_ERRORTYPE omx_result; |
| |
| // SetCallbacks() is nullptr, so apparently we don't need to call it, and the |
| // callbacks are passed in above, so that should do it. |
| |
| OMX_STATETYPE omx_state; |
| omx_result = omx_component_->GetState(omx_component_, &omx_state); |
| if (omx_result != OMX_ErrorNone) { |
| printf("omx_component->GetState() failed: %d\n", omx_result); |
| return false; |
| } |
| if (omx_state != OMX_StateLoaded) { |
| printf("unexpected OMX component state: %d\n", omx_state); |
| return false; |
| } |
| assert(omx_state == OMX_StateLoaded); |
| VLOGF("omx_component state is: %d\n", omx_state); |
| // Nobody is waiting for the state to change yet, so we can just set |
| // omx_state_ here without notifying omx_state_changed_. |
| omx_state_ = omx_state; |
| // This is OMX_StateLoaded. |
| omx_state_desired_ = omx_state; |
| |
| // OMX_GetComponentVersion entry point is nullptr. |
| // OMX_GetConfig and OMX_SetConfig just return OMX_ErrorUndefined. |
| |
| // Find input port and output port indexes. |
| // |
| // Also check that there are the expected number of ports, though this is |
| // slightly indirect and approximate given the lack of any direct way that I |
| // can find to check this via OMX. |
| OMX_PARAM_PORTDEFINITIONTYPE port_def; |
| for (int i = 0; i < 3; i++) { |
| InitOmxStruct(&port_def); |
| port_def.nPortIndex = i; |
| omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamPortDefinition, &port_def); |
| // check for errors differently depending on whether port index 2 or less |
| // than 2 |
| if (i != 2) { |
| if (omx_result != OMX_ErrorNone) { |
| printf("component_->GetParameter() failed: %d\n", omx_result); |
| return false; |
| } |
| if (port_def.eDir == OMX_DirInput) { |
| omx_port_index_[kInput] = i; |
| } else if (port_def.eDir == OMX_DirOutput) { |
| omx_port_index_[kOutput] = i; |
| } else { |
| printf("unexpected port_def.eDir: %d\n", port_def.eDir); |
| return false; |
| } |
| } else { |
| assert(i == 2); |
| // Avoid caring which specific error is return for port index 2, but it |
| // shouldn't succeed. |
| if (omx_result == OMX_ErrorNone) { |
| // For now, bail out if we don't find exactly two ports. There might |
| // be reasonable ways to deal with exceptions to this, but until we have |
| // an example of a codec that has more than two ports, postpone handling |
| // it. |
| printf("more than two ports found\n"); |
| return false; |
| } |
| } |
| } |
| if (omx_port_index_[kInput] == 0xFFFFFFFF) { |
| printf("failed to find input port\n"); |
| return false; |
| } |
| if (omx_port_index_[kOutput] == 0xFFFFFFFF) { |
| printf("failed to find output port\n"); |
| return false; |
| } |
| |
| VLOGF("input_port_index_: %d\n", omx_port_index_[kInput]); |
| VLOGF("output_port_index_: %d\n", omx_port_index_[kOutput]); |
| |
| // The default behavior is fine, since we don't need this to be the default |
| // loop for any thread. |
| // |
| // Go ahead and get the StreamControl domain's thread created and started, but |
| // its first item will be to wait for the Setup ordering domain to be done, |
| // which prevents any overlap between Setup items and StreamControl items. |
| // |
| // The StreamControl thread is allowed to block. |
| stream_control_ = |
| std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread); |
| zx_status_t start_thread_result = stream_control_->StartThread( |
| "StreamControl_ordering_domain", &stream_control_thread_); |
| if (start_thread_result != ZX_OK) { |
| printf("stream_control_->StartThread() failed\n"); |
| return false; |
| } |
| stream_control_dispatcher_ = stream_control_->dispatcher(); |
| PostSerial(stream_control_dispatcher_, [this] { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| while (!is_setup_done_) { |
| // We don't share this process across Codec instances, so currently we |
| // don't need a way to give up here, short of exiting the whole process. |
| is_setup_done_condition_.wait(lock); |
| } |
| } |
| }); |
| |
| return true; |
| } |
| |
| // TODO(dustingreen): this method needs to understand how to translate between |
| // Codec and OMX for every entry in local_codec_factory.cc. That means this |
| // method and similar AudioEncoder/VideoDecoder/VideoEncoder methods will likely |
| // involve more fan-out to deal with all the formats. |
| // |
| // For now it's a non-goal to deal with formats outside the set listed in |
| // local_codec_factory.cc, and certainly a non-goal here to try to anticipate or |
| // handle any format beyond what OMX can describe. Any format future-proofing |
| // belongs in CodecFactory and Codec interfaces (if anywhere), but not here for |
| // now. |
| void OmxCodecRunner::SetDecoderParams( |
| fuchsia::mediacodec::CreateDecoder_Params audio_decoder_params) { |
| struct AudioDecoder { |
| std::string_view codec_mime_type; |
| std::string_view omx_mime_type; |
| OMX_AUDIO_CODINGTYPE omx_coding_type; |
| void (OmxCodecRunner::*set_input_method_ptr)(); |
| }; |
| static const AudioDecoder known_audio_decoders[] = { |
| // TODO(dustingreen): add audio/aac soon. |
| {"audio/aac-adts", "audio/aac", OMX_AUDIO_CodingAAC, |
| &OmxCodecRunner::SetInputAacAdts}, |
| }; |
| const AudioDecoder* dec = nullptr; |
| for (const auto& known_audio_decoder : known_audio_decoders) { |
| if (known_audio_decoder.codec_mime_type == |
| audio_decoder_params.input_details.mime_type) { |
| dec = &known_audio_decoder; |
| break; |
| } |
| } |
| // Reject up front any mime types that we don't handle at all yet. |
| if (!dec) { |
| // TODO(dustingreen): epitaph |
| binding_.reset(); |
| Exit("SetAudioDecoderParams() couldn't find a suitable decoder"); |
| } |
| |
| decoder_params_ = std::make_unique<fuchsia::mediacodec::CreateDecoder_Params>( |
| std::move(audio_decoder_params)); |
| initial_input_format_details_ = fuchsia::media::FormatDetails::New(); |
| zx_status_t clone_result = |
| decoder_params_->input_details.Clone(initial_input_format_details_.get()); |
| if (clone_result != ZX_OK) { |
| Exit("FormatDetails::Clone() failed - exiting"); |
| } |
| |
| // For the moment, let's check that the input is AAC. |
| // |
| // TODO(dustingreen): Do this generically across all codecs, probably based on |
| // fields in a built-in codec table. |
| InitOmxStruct(&omx_initial_port_def_[kInput]); |
| omx_initial_port_def_[kInput].nPortIndex = omx_port_index_[kInput]; |
| OMX_ERRORTYPE omx_result = |
| omx_component_->GetParameter(omx_component_, OMX_IndexParamPortDefinition, |
| &omx_initial_port_def_[kInput]); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("omx_result->GetParameter(port def, input port) failed: %d\n", |
| omx_result); |
| } |
| if (omx_initial_port_def_[kInput].eDomain != OMX_PortDomainAudio) { |
| Exit("unexpected input port eDomain: %d\n", |
| omx_initial_port_def_[kInput].eDomain); |
| } |
| if (omx_initial_port_def_[kInput].format.audio.cMIMEType != |
| dec->omx_mime_type) { |
| Exit("unexpected input port mime type: %s\n", |
| omx_initial_port_def_[kInput].format.audio.cMIMEType); |
| } |
| if (omx_initial_port_def_[kInput].format.audio.eEncoding != |
| dec->omx_coding_type) { |
| Exit("unexpected input port format.audio.eEncoding: %d\n", |
| omx_initial_port_def_[kInput].format.audio.eEncoding); |
| } |
| if (omx_initial_port_def_[kInput].nBufferAlignment != 1) { |
| Exit("unexpected input buffer alignment: %d\n", |
| omx_initial_port_def_[kInput].nBufferAlignment); |
| } |
| |
| // For audio decoders, let's check that the output is PCM. |
| // |
| // TODO(dustingreen): Do this generically across all codecs, probably based on |
| // fields in a built-in codec table. |
| InitOmxStruct(&omx_initial_port_def_[kOutput]); |
| omx_initial_port_def_[kOutput].nPortIndex = omx_port_index_[kOutput]; |
| omx_result = |
| omx_component_->GetParameter(omx_component_, OMX_IndexParamPortDefinition, |
| &omx_initial_port_def_[kOutput]); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("omx_component->GetParameter(port def, output port) failed: %d\n", |
| omx_result); |
| } |
| if (omx_initial_port_def_[kOutput].eDomain != OMX_PortDomainAudio) { |
| Exit("unexpected output port eDomain: %d\n", |
| omx_initial_port_def_[kOutput].eDomain); |
| } |
| std::string expected_output_mime("audio/raw"); |
| if (expected_output_mime != |
| omx_initial_port_def_[kOutput].format.audio.cMIMEType) { |
| Exit("unexpected output port mime type: %s\n", |
| omx_initial_port_def_[kOutput].format.audio.cMIMEType); |
| } |
| if (omx_initial_port_def_[kOutput].format.audio.eEncoding != |
| OMX_AUDIO_CodingPCM) { |
| Exit("unexpected output port format.audio.eEncoding: %d\n", |
| omx_initial_port_def_[kOutput].format.audio.eEncoding); |
| } |
| if (omx_initial_port_def_[kOutput].nBufferAlignment != 2) { |
| Exit("unexpected output buffer alignment: %d\n", |
| omx_initial_port_def_[kOutput].nBufferAlignment); |
| } |
| |
| for (Port port = kFirstPort; port < kPortCount; port++) { |
| // intentional copy |
| omx_port_def_[port] = omx_initial_port_def_[port]; |
| } |
| |
| // Handle per-format parameter setting. Method call to a method like |
| // SetInputAacAdts() or analogous method. |
| (this->*(dec->set_input_method_ptr))(); |
| |
| // next is ComputeInputConstraints() |
| } |
| |
| // Set the AAC decoder to ADTS mode. |
| void OmxCodecRunner::SetInputAacAdts() { |
| OMX_AUDIO_PARAM_AACPROFILETYPE aac_profile; |
| InitOmxStruct(&aac_profile); |
| aac_profile.nPortIndex = omx_port_index_[kInput]; |
| OMX_ERRORTYPE omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamAudioAac, &aac_profile); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("omx_component->GetParameter(input, aac profile) failed: %d", |
| omx_result); |
| } |
| // For now, we won't strip off the ADTS-ness from the input .adts file, so put |
| // the AAC decoder in ADTS mode. |
| aac_profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS; |
| omx_result = omx_component_->SetParameter( |
| omx_component_, OMX_IndexParamAudioAac, &aac_profile); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("omx_component->SetParameter(input, ADTS) failed: %d\n", omx_result); |
| } |
| } |
| |
| // This is called before the Codec channel is bound, so this class is still |
| // single-threaded during this method. |
| void OmxCodecRunner::ComputeInputConstraints() { |
| // The OMX info we need to generate input_constraints_ is all in |
| // omx_initial_port_def_[kInput] + omx_min_nBufferSize_[kInput] and was |
| // already queried from the OMX codec previously. |
| // |
| // The kInputBufferConstraintsVersionOrdinal is required in the sense that it |
| // is the only one and also required, vs output which has both required and |
| // not-required buffer_constraints_version_ordinal(s). Input has this only |
| // to allow sharing more code with output. |
| last_required_buffer_constraints_version_ordinal_[kInput] = |
| kInputBufferConstraintsVersionOrdinal; |
| sent_buffer_constraints_version_ordinal_[kInput] = |
| kInputBufferConstraintsVersionOrdinal; |
| OMX_U32 omx_min_buffer_size = omx_initial_port_def_[kInput].nBufferSize; |
| uint32_t packet_count_for_server_recommended = |
| kOmxRecommendedBufferCountVsMinBufferCountFactor * |
| omx_initial_port_def_[kInput].nBufferCountMin; |
| uint32_t per_packet_buffer_bytes_recommended = |
| kOmxRecommendedBufferVsMinBufferFactor * omx_min_buffer_size; |
| input_constraints_ = |
| std::make_unique<fuchsia::media::StreamBufferConstraints>( |
| fuchsia::media::StreamBufferConstraints{ |
| .buffer_constraints_version_ordinal = |
| kInputBufferConstraintsVersionOrdinal, |
| .per_packet_buffer_bytes_min = omx_min_buffer_size, |
| .per_packet_buffer_bytes_recommended = |
| per_packet_buffer_bytes_recommended, |
| .per_packet_buffer_bytes_max = |
| kOmxMaxBufferVsMinBufferFactor * omx_min_buffer_size, |
| .packet_count_for_server_min = |
| omx_initial_port_def_[kInput].nBufferCountMin, |
| .packet_count_for_server_recommended = |
| packet_count_for_server_recommended, |
| .packet_count_for_server_recommended_max = |
| kOmxRecommendedMaxBufferCountVsMinBufferCountFactor * |
| omx_initial_port_def_[kInput].nBufferCountMin, |
| .packet_count_for_server_max = |
| kOmxMaxBufferCountVsMinBufferCountFactor * |
| omx_initial_port_def_[kInput].nBufferCountMin, |
| .packet_count_for_client_max = |
| std::numeric_limits<uint32_t>::max(), |
| // TODO(dustingreen): verify that this works end to end for the |
| // OmxCodecRunner... |
| .single_buffer_mode_allowed = true, |
| |
| // default_settings |
| // |
| // Initial input buffer_lifetime_ordinal of 1 is ok. It's also |
| // ok if it's any larger odd number, but 1 is the best choice. |
| .default_settings.buffer_lifetime_ordinal = |
| kBestFirstBufferLifetimeOrdinal, |
| // The buffer_constraints_version_ordinal is a pass-through value |
| // so clients will have no reason to change this - it's just so |
| // the server knows what version of constraints the client was |
| // aware of so far. |
| .default_settings.buffer_constraints_version_ordinal = |
| kInputBufferConstraintsVersionOrdinal, |
| .default_settings.packet_count_for_server = |
| packet_count_for_server_recommended, |
| .default_settings.packet_count_for_client = |
| ::fuchsia::media::kDefaultInputPacketCountForClient, |
| .default_settings.per_packet_buffer_bytes = |
| per_packet_buffer_bytes_recommended, |
| .default_settings.single_buffer_mode = |
| ::fuchsia::media::kDefaultInputIsSingleBufferMode, |
| }); |
| |
| // We're about to be bound to the Codec channel, which will immediately send |
| // the input_constraints_ to the client as the first server to client message. |
| } |
| |
| // |
| // Codec |
| // |
| |
| // The base class is about to send input_constraints_ using |
| // OnInputConstraints(). Since OMX codecs demand to have output buffers |
| // configured before generating OMX_EventPortSettingsChanged on the output port, |
| // and because OMX codecs can potentially not generate that event and just |
| // output into the initial buffers instead, and because this class doesn't |
| // virtualize that away with a bunch of memcpy + complicated tracking that would |
| // be required, the OmxCodecRunner will want to send the output constraints |
| // asap, which is when this method gets called. |
| // |
| // We want to send this _before_ the input constraints to encourage the client |
| // to configure output before queueing any input data for the first stream, else |
| // we can end up triggering another output re-config. |
| // |
| // This is called on the FIDL thread, but we post any sent messages back to the |
| // FIDL thread to be sent on a clean thread without lock_ held anyway. |
| void OmxCodecRunner::onInputConstraintsReady() { |
| std::unique_lock<std::mutex> lock(lock_); |
| StartIgnoringClientOldOutputConfigLocked(); |
| GenerateAndSendNewOutputConfig(lock, true); |
| |
| // Next is the client sending SetInputBufferSettings()+AddInputBuffer() or |
| // SetOutputBufferSettings()+AddOutputBuffer(). Preferably the latter first, |
| // but either is permitted. |
| } |
| |
| void OmxCodecRunner::GenerateAndSendNewOutputConfig( |
| std::unique_lock<std::mutex>& lock, |
| bool buffer_constraints_action_required) { |
| // This method is only called on these ordering domains: |
| // * Setup ordering domain |
| // * StreamControl ordering domain |
| // * InputData domain if buffer_constraints_action_required is false |
| // |
| // TODO(dustingreen): Create an assert that checks for the above. This |
| // commented-out assert doesn't include possibility of InputData domain: |
| // assert(!is_setup_done_ || thrd_current() == stream_control_thread_); |
| |
| 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_[kOutput] before calling |
| // this method (using StartIgnoringClientOldOutputConfigLocked()), 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_. |
| assert(!buffer_constraints_action_required || |
| (last_required_buffer_constraints_version_ordinal_[kOutput] == |
| 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 OMX under the lock_, because we can avoid doing so, and |
| // because of paranoia that OMX might call EventHandler() at any time using |
| // the same stack that we call OMX on - it's only partly paranoia, since OMX |
| // _does_ do that sometimes, for some calls into OMX - so assume that's the |
| // contract for all calls into OMX. |
| |
| // We know we're the only thread calling this currently, because this method |
| // is part of the Setup ordering domain and the onSetupDone() method |
| // prevents any overlap between Setup and StreamControl. |
| output_config = |
| BuildNewOutputConfig(current_stream_lifetime_ordinal, |
| new_output_buffer_constraints_version_ordinal, |
| new_output_format_details_version_ordinal, |
| buffer_constraints_action_required); |
| } // ~unlock |
| 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(). |
| |
| assert(sent_buffer_constraints_version_ordinal_[kOutput] + 1 == |
| new_output_buffer_constraints_version_ordinal); |
| assert(sent_format_details_version_ordinal_[kOutput] + 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_[kOutput] = |
| new_output_buffer_constraints_version_ordinal; |
| sent_format_details_version_ordinal_[kOutput] = |
| new_output_format_details_version_ordinal; |
| |
| // Intentional copy of fuchsia::media::StreamOutputConfig output_config_ here, |
| // as we want output_config_ to remain valid (at least for debugging reasons |
| // for now). |
| fuchsia::media::StreamOutputConfig config_copy; |
| zx_status_t clone_status = output_config_->Clone(&config_copy); |
| if (clone_status != ZX_OK) { |
| Exit("StreamOutputConfig::Clone() failed - exiting - status: %d\n", |
| clone_status); |
| } |
| VLOGF("GenerateAndSendNewOutputConfig() - fidl_dispatcher_: %p\n", |
| fidl_dispatcher_); |
| PostSerial(fidl_dispatcher_, |
| [this, output_config = std::move(config_copy)]() mutable { |
| binding_->events().OnOutputConfig(std::move(output_config)); |
| }); |
| } |
| |
| void OmxCodecRunner::onSetupDone() { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| is_setup_done_ = true; |
| } // ~lock |
| is_setup_done_condition_.notify_all(); |
| } |
| |
| // 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 OmxCodecRunner::EnsureCodecStreamClosedLockedInternal() { |
| assert(thrd_current() == stream_control_thread_); |
| if (stream_lifetime_ordinal_ % 2 == 0) { |
| // Already closed. |
| return; |
| } |
| 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. |
| 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_. |
| void OmxCodecRunner::EnsureFutureStreamSeenLocked( |
| uint64_t stream_lifetime_ordinal) { |
| if (future_stream_lifetime_ordinal_ == stream_lifetime_ordinal) { |
| return; |
| } |
| if (stream_lifetime_ordinal < future_stream_lifetime_ordinal_) { |
| Exit("stream_lifetime_ordinal went backward - exiting\n"); |
| } |
| assert(stream_lifetime_ordinal > future_stream_lifetime_ordinal_); |
| if (future_stream_lifetime_ordinal_ % 2 == 1) { |
| EnsureFutureStreamCloseSeenLocked(future_stream_lifetime_ordinal_); |
| } |
| future_stream_lifetime_ordinal_ = stream_lifetime_ordinal; |
| stream_queue_.push_back(std::make_unique<Stream>(stream_lifetime_ordinal)); |
| if (stream_queue_.size() > kMaxInFlightStreams) { |
| Exit( |
| "kMaxInFlightStreams reached - clients capable of causing this are " |
| "instead supposed to wait/postpone to prevent this from occurring - " |
| "exiting\n"); |
| } |
| } |
| |
| // 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_. |
| void OmxCodecRunner::EnsureFutureStreamCloseSeenLocked( |
| uint64_t stream_lifetime_ordinal) { |
| if (future_stream_lifetime_ordinal_ % 2 == 0) { |
| // Already closed. |
| if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_ - 1) { |
| Exit( |
| "CloseCurrentStream() seen with stream_lifetime_ordinal != " |
| "most-recent seen stream - exiting\n"); |
| } |
| return; |
| } |
| if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_) { |
| Exit( |
| "attempt to close a stream other than the latest seen stream - " |
| "exiting\n"); |
| } |
| assert(stream_lifetime_ordinal == future_stream_lifetime_ordinal_); |
| assert(stream_queue_.size() >= 1); |
| Stream* closing_stream = stream_queue_.back().get(); |
| 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_++; |
| assert(future_stream_lifetime_ordinal_ % 2 == 0); |
| } |
| |
| // 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_. |
| void OmxCodecRunner::EnsureFutureStreamFlushSeenLocked( |
| uint64_t stream_lifetime_ordinal) { |
| if (stream_lifetime_ordinal != future_stream_lifetime_ordinal_) { |
| Exit( |
| "FlushCurrentStream() stream_lifetime_ordinal inconsistent - " |
| "exiting\n"); |
| } |
| 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. |
| assert(!flushing_stream->future_discarded()); |
| if (flushing_stream->future_flush_end_of_stream()) { |
| Exit("FlushCurrentStream() used twice on same stream - exiting\n"); |
| } |
| |
| // 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. |
| EnsureFutureStreamCloseSeenLocked(stream_lifetime_ordinal); |
| } |
| |
| // Caller must ensure that this is called only on one thread at a time. |
| std::unique_ptr<const fuchsia::media::StreamOutputConfig> |
| OmxCodecRunner::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) { |
| return CreateNewOutputConfigFromOmxOutputFormat( |
| OmxGetOutputFormat(), stream_lifetime_ordinal, |
| new_output_buffer_constraints_version_ordinal, |
| new_output_format_details_version_ordinal, |
| buffer_constraints_action_required); |
| } |
| |
| // Caller must ensure that this is called only on one thread at a time. |
| std::unique_ptr<const fuchsia::media::StreamOutputConfig> |
| OmxCodecRunner::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) { |
| // Unfortunately OMX only allows nBufferSize to increase, never decrease, so |
| // we have to convey that to the output constraints also, since we don't have |
| // any per-omx-buffer-lifetime way of reducing how much output data might be |
| // generated per output buffer. So we really are stuck with a min that's |
| // whatever OMX's nBufferSize is so far. For input the situation is |
| // different since we can control how many valid bytes per input buffer |
| // lifetime. |
| OMX_U32 per_packet_buffer_bytes_min = |
| omx_output_format->definition.nBufferSize; |
| const OMX_PARAM_PORTDEFINITIONTYPE& port = omx_output_format->definition; |
| uint32_t per_packet_buffer_bytes_recommended = |
| kOmxRecommendedBufferVsMinBufferFactor * per_packet_buffer_bytes_min; |
| uint32_t packet_count_for_server_recommended = |
| kOmxRecommendedBufferCountVsMinBufferCountFactor * port.nBufferCountMin; |
| std::unique_ptr<fuchsia::media::StreamOutputConfig> result = |
| std::make_unique<fuchsia::media::StreamOutputConfig>( |
| fuchsia::media::StreamOutputConfig{ |
| .stream_lifetime_ordinal = stream_lifetime_ordinal_, |
| .buffer_constraints_action_required = |
| buffer_constraints_action_required, |
| .buffer_constraints.buffer_constraints_version_ordinal = |
| new_output_buffer_constraints_version_ordinal, |
| .buffer_constraints.per_packet_buffer_bytes_min = |
| per_packet_buffer_bytes_min, |
| .buffer_constraints.per_packet_buffer_bytes_recommended = |
| per_packet_buffer_bytes_recommended, |
| .buffer_constraints.per_packet_buffer_bytes_max = |
| kOmxMaxBufferVsMinBufferFactor * per_packet_buffer_bytes_min, |
| .buffer_constraints.packet_count_for_server_min = |
| port.nBufferCountMin, |
| .buffer_constraints.packet_count_for_server_recommended = |
| packet_count_for_server_recommended, |
| .buffer_constraints.packet_count_for_server_recommended_max = |
| kOmxRecommendedMaxBufferCountVsMinBufferCountFactor * |
| port.nBufferCountMin, |
| .buffer_constraints.packet_count_for_server_max = |
| kOmxMaxBufferCountVsMinBufferCountFactor * |
| port.nBufferCountMin, |
| .buffer_constraints.packet_count_for_client_max = |
| std::numeric_limits<uint32_t>::max(), |
| .buffer_constraints.single_buffer_mode_allowed = false, |
| |
| // default_settings |
| // |
| // Can't/won't help the client pick the client's |
| // buffer_lifetime_ordinal for output. |
| .buffer_constraints.default_settings.buffer_lifetime_ordinal = |
| kInvalidDefaultBufferLifetimeOrdinal, |
| // The buffer_constraints_version_ordinal is a pass-through value |
| // so clients will have no reason to change this - it's just so |
| // the server knows what version of constraints the client was |
| // aware of so far. |
| .buffer_constraints.default_settings |
| .buffer_constraints_version_ordinal = |
| new_output_buffer_constraints_version_ordinal, |
| .buffer_constraints.default_settings.packet_count_for_server = |
| packet_count_for_server_recommended, |
| .buffer_constraints.default_settings.packet_count_for_client = |
| ::fuchsia::media::kDefaultOutputPacketCountForClient, |
| .buffer_constraints.default_settings.per_packet_buffer_bytes = |
| per_packet_buffer_bytes_recommended, |
| .buffer_constraints.default_settings.single_buffer_mode = |
| ::fuchsia::media::kDefaultOutputIsSingleBufferMode, |
| |
| .format_details.format_details_version_ordinal = |
| new_output_format_details_version_ordinal, |
| }); |
| switch (omx_output_format->definition.eDomain) { |
| case OMX_PortDomainAudio: |
| PopulateFormatDetailsFromOmxOutputFormat_Audio(*omx_output_format.get(), |
| &result->format_details); |
| break; |
| case OMX_PortDomainVideo: |
| // TODO(dustingreen): handle video format details - it likely makes sense |
| // to switch to the common format details FIDL struct/table first though. |
| Exit("for now, video OMX eDomain is not handled"); |
| break; |
| default: |
| // TODO(dustingreen): epitaph |
| Exit("unrecognized OMX eDomain: %d", |
| omx_output_format->definition.eDomain); |
| break; |
| } |
| return result; |
| } |
| |
| // Fill out everything except format_details_version_ordinal. |
| // |
| // TODO(dustingreen): handle audio encoders, which will need to fill out |
| // codec_oob_config based on the first output data, if available. |
| void OmxCodecRunner::PopulateFormatDetailsFromOmxOutputFormat_Audio( |
| const OmxCodecRunner::OMX_GENERIC_PORT_FORMAT& omx_output_format, |
| fuchsia::media::FormatDetails* format_details) { |
| assert(omx_output_format.definition.eDir == OMX_DirOutput); |
| assert(omx_output_format.definition.eDomain == OMX_PortDomainAudio); |
| const OMX_AUDIO_PORTDEFINITIONTYPE& omx_audio_port_def = |
| omx_output_format.definition.format.audio; |
| const OMX_AUDIO_PARAM_PORTFORMATTYPE& omx_audio_param_port_format = |
| omx_output_format.audio.format; |
| format_details->mime_type = omx_audio_port_def.cMIMEType; |
| if (omx_audio_port_def.eEncoding != omx_audio_param_port_format.eEncoding) { |
| Exit("inconsistent eEncoding from OMX - exiting"); |
| } |
| assert(omx_audio_port_def.eEncoding == omx_audio_param_port_format.eEncoding); |
| fuchsia::media::AudioFormat audio_format{}; |
| switch (omx_audio_param_port_format.eEncoding) { |
| case OMX_AUDIO_CodingPCM: { |
| const OMX_AUDIO_PARAM_PCMMODETYPE& omx_pcm = omx_output_format.audio.pcm; |
| fuchsia::media::PcmFormat pcm{}; |
| switch (omx_pcm.ePCMMode) { |
| case OMX_AUDIO_PCMModeLinear: |
| pcm.pcm_mode = fuchsia::media::AudioPcmMode::LINEAR; |
| break; |
| default: |
| Exit("unhandled OMX_AUDIO_PARAM_PCMMODETYPE.ePCMMode value: %d", |
| omx_pcm.ePCMMode); |
| break; |
| } |
| pcm.bits_per_sample = omx_pcm.nBitPerSample; |
| pcm.frames_per_second = omx_pcm.nSamplingRate; |
| std::vector<fuchsia::media::AudioChannelId> channel_map( |
| omx_pcm.nChannels); |
| for (uint32_t i = 0; i < omx_pcm.nChannels; i++) { |
| channel_map[i] = |
| AudioChannelIdFromOmxAudioChannelType(omx_pcm.eChannelMapping[i]); |
| } |
| pcm.channel_map = std::move(channel_map); |
| fuchsia::media::AudioUncompressedFormat uncompressed{}; |
| uncompressed.set_pcm(std::move(pcm)); |
| audio_format.set_uncompressed(std::move(uncompressed)); |
| } break; |
| case OMX_AUDIO_CodingAAC: |
| // TODO(dustingreen): implement, at least for AAC encode |
| // fallthrough for now |
| default: |
| Exit("unhandled OMX output format - value: %d", |
| omx_audio_param_port_format.eEncoding); |
| break; |
| } |
| format_details->domain = fuchsia::media::DomainFormat::New(); |
| format_details->domain->set_audio(std::move(audio_format)); |
| } |
| |
| std::unique_ptr<const OmxCodecRunner::OMX_GENERIC_PORT_FORMAT> |
| OmxCodecRunner::OmxGetOutputFormat() { |
| std::unique_ptr<OMX_GENERIC_PORT_FORMAT> result = |
| std::make_unique<OMX_GENERIC_PORT_FORMAT>(); |
| // Grab all the output format info. |
| InitOmxStruct(&result->definition); |
| result->definition.nPortIndex = omx_port_index_[kOutput]; |
| OMX_ERRORTYPE omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamPortDefinition, &result->definition); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("Couldn't get output port definition from OMX: %d", omx_result); |
| } |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| // intentional copy |
| // |
| // We're stashing this structure from here because this method happens to be |
| // the common code path involved in all OMX updates of the output port |
| // definition where constraints might change which we need to pay attention |
| // to later. Mainly we care about nBufferSize. |
| omx_port_def_[kOutput] = result->definition; |
| } |
| switch (result->definition.eDomain) { |
| case OMX_PortDomainAudio: |
| InitOmxStruct(&result->audio.format); |
| result->audio.format.nPortIndex = omx_port_index_[kOutput]; |
| omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamAudioPortFormat, &result->audio.format); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "GetParameter(OMX_IndexParamAudioPortFormat) failed: %d - " |
| "exiting\n", |
| omx_result); |
| } |
| switch (result->audio.format.eEncoding) { |
| case OMX_AUDIO_CodingPCM: |
| InitOmxStruct(&result->audio.pcm); |
| result->audio.pcm.nPortIndex = omx_port_index_[kOutput]; |
| omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamAudioPcm, &result->audio.pcm); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("GetParameter(OMX_IndexParamAudioPcm) failed: %d - exiting\n", |
| omx_result); |
| } |
| break; |
| default: |
| Exit( |
| "un-handled output_port_format_.audio.format.eEncoding: %d - " |
| "exiting\n", |
| result->audio.format.eEncoding); |
| } |
| break; |
| case OMX_PortDomainVideo: |
| Exit("currently un-handled eDomain video: %d - exiting\n", |
| result->definition.eDomain); |
| default: |
| Exit("un-handled eDomain: %d - exiting\n", result->definition.eDomain); |
| } |
| return result; |
| } |
| |
| void OmxCodecRunner::EnableOnStreamFailed() { |
| std::unique_lock<std::mutex> lock(lock_); |
| enable_on_stream_failed_ = true; |
| } |
| |
| void OmxCodecRunner::SetInputBufferSettings( |
| fuchsia::media::StreamBufferSettings input_settings) { |
| PostSerial(stream_control_dispatcher_, |
| [this, input_settings = input_settings] { |
| SetInputBufferSettings_StreamControl(input_settings); |
| }); |
| } |
| |
| void OmxCodecRunner::SetInputBufferSettings_StreamControl( |
| fuchsia::media::StreamBufferSettings input_settings) { |
| assert(thrd_current() == stream_control_thread_); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| |
| if (!input_constraints_sent_) { |
| Exit( |
| "client sent SetInputBufferSettings() before first " |
| "OnInputConstraints()"); |
| } |
| |
| if (IsStreamActiveLocked()) { |
| Exit( |
| "client sent SetInputBufferSettings() with stream active - " |
| "exiting\n"); |
| } |
| |
| SetBufferSettingsCommonLocked(kInput, input_settings, *input_constraints_); |
| } // ~lock |
| } |
| |
| void OmxCodecRunner::AddInputBuffer(fuchsia::media::StreamBuffer buffer) { |
| PostSerial(stream_control_dispatcher_, |
| [this, buffer = std::move(buffer)]() mutable { |
| AddInputBuffer_StreamControl(std::move(buffer)); |
| }); |
| } |
| |
| void OmxCodecRunner::AddInputBuffer_StreamControl( |
| fuchsia::media::StreamBuffer buffer) { |
| assert(thrd_current() == stream_control_thread_); |
| AddBufferCommon(kInput, std::move(buffer)); |
| } |
| |
| void OmxCodecRunner::SetBufferSettingsCommonLocked( |
| Port port, const fuchsia::media::StreamBufferSettings& settings, |
| const fuchsia::media::StreamBufferConstraints& constraints) { |
| // Invariant |
| 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]) { |
| Exit( |
| "settings.buffer_lifetime_ordinal <= " |
| "protocol_buffer_lifetime_ordinal_[port] - exiting - port: %d\n", |
| port); |
| } |
| protocol_buffer_lifetime_ordinal_[port] = settings.buffer_lifetime_ordinal; |
| |
| if (settings.buffer_lifetime_ordinal % 2 == 0) { |
| Exit( |
| "only odd values for buffer_lifetime_ordinal are permitted - exiting - " |
| "port: %d value: %lu\n", |
| port, settings.buffer_lifetime_ordinal); |
| } |
| |
| if (settings.buffer_constraints_version_ordinal > |
| sent_buffer_constraints_version_ordinal_[port]) { |
| Exit( |
| "client sent too-new buffer_constraints_version_ordinal - exiting - " |
| "port: %d\n", |
| port); |
| } |
| |
| 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. |
| 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. |
| assert(!port_settings_[port] || |
| settings.buffer_lifetime_ordinal > buffer_lifetime_ordinal_[port]); |
| |
| ValidateBufferSettingsVsConstraints(port, settings, constraints); |
| |
| // Regardless of mid-stream output config change or not (only relevant to |
| // output), we know that buffers aren't with OMX currently, so we can just |
| // de-ref low-layer output buffers without needing to interact with OMX here. |
| |
| // Little if any reason to do this outside the lock. |
| EnsureBuffersNotConfiguredLocked(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 OmxCodecRunner::SetOutputBufferSettings( |
| fuchsia::media::StreamBufferSettings output_settings) { |
| { // 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(). |
| Exit( |
| "client sent SetOutputBufferSettings() when no output_config_ - " |
| "exiting\n"); |
| } |
| |
| // 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()) { |
| Exit( |
| "client sent SetOutputBufferSettings() with IsStreamActiveLocked() + " |
| "already-configured output"); |
| } |
| |
| SetBufferSettingsCommonLocked(kOutput, output_settings, |
| output_config_->buffer_constraints); |
| } // ~lock |
| } |
| |
| void OmxCodecRunner::AddOutputBuffer(fuchsia::media::StreamBuffer buffer) { |
| bool output_done_configuring = AddBufferCommon(kOutput, std::move(buffer)); |
| if (output_done_configuring) { |
| // The StreamControl domain _might_ be waiting for output to be configured. |
| wake_stream_control_.notify_all(); |
| } |
| } |
| |
| bool OmxCodecRunner::AddBufferCommon(Port port, |
| fuchsia::media::StreamBuffer buffer) { |
| bool done_configuring = false; |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| |
| if (buffer.buffer_lifetime_ordinal % 2 == 0) { |
| Exit( |
| "client sent even buffer_lifetime_ordinal, but must be odd - exiting " |
| "- port: %u\n", |
| port); |
| } |
| |
| if (buffer.buffer_lifetime_ordinal != |
| protocol_buffer_lifetime_ordinal_[port]) { |
| Exit( |
| "incoherent SetOutputBufferSettings()/SetInputBufferSettings() + " |
| "AddOutputBuffer()/AddInputBuffer()s - exiting - port: %d\n", |
| port); |
| } |
| |
| // 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. |
| assert(port == kOutput); |
| |
| // Ignore the client's message. The client will probably catch up later. |
| return false; |
| } |
| |
| if (buffer.buffer_index != all_buffers_[port].size()) { |
| Exit( |
| "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()); |
| } |
| |
| uint32_t required_buffer_count = |
| BufferCountFromPortSettings(*port_settings_[port]); |
| if (buffer.buffer_index >= required_buffer_count) { |
| Exit("AddOutputBuffer()/AddInputBuffer() extra buffer - port: %d", port); |
| } |
| |
| // 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<Buffer> local_buffer = |
| std::make_unique<Buffer>(this, port, std::move(buffer)); |
| if (!local_buffer->Init()) { |
| Exit( |
| "AddOutputBuffer()/AddInputBuffer() couldn't Init() new buffer - " |
| "port: %d", |
| port); |
| } |
| all_buffers_[port].push_back(std::move(local_buffer)); |
| if (all_buffers_[port].size() == required_buffer_count) { |
| // Now we allocate all_packets_[port]. |
| assert(all_packets_[port].empty()); |
| uint32_t packet_count = |
| PacketCountFromPortSettings(*port_settings_[port]); |
| for (uint32_t i = 0; i < packet_count; i++) { |
| uint32_t buffer_index = required_buffer_count == 1 ? 0 : i; |
| Buffer* buffer = all_buffers_[port][buffer_index].get(); |
| assert(buffer_lifetime_ordinal_[port] == |
| port_settings_[port]->buffer_lifetime_ordinal); |
| all_packets_[port].push_back(std::make_unique<Packet>( |
| port_settings_[port]->buffer_lifetime_ordinal, i, buffer)); |
| } |
| // On input, free with client. On output, free with Codec server. |
| // Either way, initially free with the producer of data. |
| packet_free_bits_[port].resize(packet_count, true); |
| |
| // Now we allocate omx_input_packet_oob_ and omx_input_packet_eos_, if |
| // this is input. |
| if (port == kInput) { |
| // For the oob packet, we do need a real buffer, and it needs to be able |
| // to hold real (oob) data, so we have to allocate a buffer for this |
| // purpose server-side, since the Codec client won't be providing one. |
| // |
| // For now, we just allocate kMaxOobBytesSize for this (none of the |
| // relevant codecs need larger, and kMaxOobBytesSize is 1 page |
| // which is a non-zero-sized VMO's minimum size). |
| // |
| // We (in general) lie to OMX about the size being at least |
| // OMX_PARAM_PORTDEFINITIONTYPE.nBufferSize when allocating an OMX |
| // buffer for this packet, then we don't actually fill beyond |
| // kMaxOobBytesSize. |
| // |
| // We don't really care about OMX_PARAM_PORTDEFINITIONTYPE.nBufferSize |
| // aside from properly lying to OMX, since the size of normal buffers |
| // was never really directly relevant to how large OOB data can be. In |
| // other words, we don't force ourselves to support up to |
| // OMX_PARAM_PORTDEFINITIONTYPE.nBufferSize bytes of OOB config data, |
| // because there's no real value in doing so, despite OMX essentially |
| // sortof supporting up to that much OOB data in a |
| // OMX_BUFFERFLAG_CODECCONFIG buffer. |
| // |
| // If kMaxOobBytesSize isn't page size aligned, zx_vmo_create() |
| // will round up for us, so we don't have to handle that possibility |
| // here. |
| assert(!omx_input_buffer_oob_); |
| assert(!omx_input_packet_oob_); |
| static_assert( |
| fuchsia::media::kMaxOobBytesSize <= ZX_CHANNEL_MAX_MSG_BYTES, |
| "fuchsia::media::kMaxOobBytesSize must be <= " |
| "ZX_CHANNEL_MAX_MSG_BYTES"); |
| zx::vmo oob_vmo; |
| zx_status_t vmo_create_status = |
| zx::vmo::create(fuchsia::media::kMaxOobBytesSize, 0, &oob_vmo); |
| if (vmo_create_status != ZX_OK) { |
| Exit("zx::vmo::create() failed for omx_input_buffer_oob_"); |
| } |
| fuchsia::media::StreamBuffer oob_buffer{ |
| .buffer_lifetime_ordinal = |
| port_settings_[port]->buffer_lifetime_ordinal, |
| // We don't really use this for anything, so just set it to one |
| // beyond the last Codec protocol buffer_index, to avoid any |
| // ambiguity with any real buffer_index. |
| .buffer_index = required_buffer_count, |
| }; |
| oob_buffer.data.set_vmo(fuchsia::media::StreamBufferDataVmo{ |
| .vmo_handle = std::move(oob_vmo), |
| .vmo_usable_start = 0, |
| .vmo_usable_size = fuchsia::media::kMaxOobBytesSize, |
| }); |
| omx_input_buffer_oob_ = |
| std::make_unique<Buffer>(this, kInput, std::move(oob_buffer)); |
| // Unlike most input packets, the server requires the ability to write |
| // to this input packet's buffer. |
| if (!omx_input_buffer_oob_->Init(true)) { |
| Exit("omx_input_buffer_oob_->Init() failed"); |
| } |
| omx_input_packet_oob_ = std::make_unique<Packet>( |
| port_settings_[port]->buffer_lifetime_ordinal, |
| packet_count + kHiddenInputPacketIndexOffsetOob, |
| omx_input_buffer_oob_.get()); |
| |
| // For the eos packet, we don't really need a real buffer, so we just |
| // share buffer 0. |
| assert(!omx_input_packet_eos_); |
| Buffer* buffer = all_buffers_[port][0].get(); |
| assert(buffer_lifetime_ordinal_[port] == |
| port_settings_[port]->buffer_lifetime_ordinal); |
| omx_input_packet_eos_ = std::make_unique<Packet>( |
| port_settings_[port]->buffer_lifetime_ordinal, |
| packet_count + kHiddenInputPacketIndexOffsetEos, buffer); |
| } |
| |
| // 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. |
| |
| // 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. |
| |
| done_configuring = true; |
| } |
| } |
| return done_configuring; |
| } |
| |
| void OmxCodecRunner::FlushEndOfStreamAndCloseStream( |
| uint64_t stream_lifetime_ordinal) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureFutureStreamFlushSeenLocked(stream_lifetime_ordinal); |
| } |
| PostSerial(stream_control_dispatcher_, [this, stream_lifetime_ordinal] { |
| FlushEndOfStreamAndCloseStream_StreamControl(stream_lifetime_ordinal); |
| }); |
| } |
| |
| void OmxCodecRunner::FlushEndOfStreamAndCloseStream_StreamControl( |
| uint64_t stream_lifetime_ordinal) { |
| assert(thrd_current() == stream_control_thread_); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| |
| // 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 (FIDL thread) is for the future-tracking's own convenience only. |
| // The checking here is the real checking. |
| |
| CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal); |
| assert(stream_lifetime_ordinal >= stream_lifetime_ordinal_); |
| if (!IsStreamActiveLocked() || |
| stream_lifetime_ordinal != stream_lifetime_ordinal_) { |
| // TODO(dustingreen): epitaph |
| Exit( |
| "FlushEndOfStreamAndCloseStream() only valid on an active current " |
| "stream (flush does not auto-create a new stream)"); |
| } |
| // 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). |
| assert(stream_); |
| assert(stream_->stream_lifetime_ordinal() == stream_lifetime_ordinal); |
| if (!stream_->input_end_of_stream()) { |
| Exit( |
| "FlushEndOfStreamAndCloseStream() is only permitted after " |
| "QueueInputEndOfStream()"); |
| } |
| 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). |
| // |
| // 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 |
| } |
| |
| // This message is required to be idempotent. |
| void OmxCodecRunner::CloseCurrentStream(uint64_t stream_lifetime_ordinal, |
| bool release_input_buffers, |
| bool release_output_buffers) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureFutureStreamCloseSeenLocked(stream_lifetime_ordinal); |
| } // ~lock |
| PostSerial(stream_control_dispatcher_, [this, stream_lifetime_ordinal, |
| release_input_buffers, |
| release_output_buffers] { |
| CloseCurrentStream_StreamControl( |
| stream_lifetime_ordinal, release_input_buffers, release_output_buffers); |
| }); |
| } |
| |
| void OmxCodecRunner::CloseCurrentStream_StreamControl( |
| uint64_t stream_lifetime_ordinal, bool release_input_buffers, |
| bool release_output_buffers) { |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureStreamClosed(lock); |
| if (release_input_buffers) { |
| EnsureBuffersNotConfiguredLocked(kInput); |
| } |
| if (release_output_buffers) { |
| EnsureBuffersNotConfiguredLocked(kOutput); |
| } |
| } |
| |
| void OmxCodecRunner::Sync(SyncCallback callback) { |
| // By posting to StreamControl ordering domain before calling the callback, we |
| // sync the Output ordering domain and the StreamControl ordering domain. |
| PostSerial(stream_control_dispatcher_, |
| [this, callback = std::move(callback)]() mutable { |
| Sync_StreamControl(std::move(callback)); |
| }); |
| } |
| |
| void OmxCodecRunner::Sync_StreamControl(SyncCallback callback) { callback(); } |
| |
| void OmxCodecRunner::RecycleOutputPacket( |
| fuchsia::media::PacketHeader available_output_packet) { |
| std::unique_lock<std::mutex> lock(lock_); |
| CheckOldBufferLifetimeOrdinalLocked( |
| kOutput, available_output_packet.buffer_lifetime_ordinal); |
| if (available_output_packet.buffer_lifetime_ordinal < |
| buffer_lifetime_ordinal_[kOutput]) { |
| // 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_[kOutput] to the next even value |
| // when de-configuring output server-side until the client has re-configured |
| // output. |
| return; |
| } |
| assert(available_output_packet.buffer_lifetime_ordinal == |
| buffer_lifetime_ordinal_[kOutput]); |
| if (!IsOutputConfiguredLocked()) { |
| Exit( |
| "client sent RecycleOutputPacket() for buffer_lifetime_ordinal that " |
| "isn't fully configured yet - bad client behavior"); |
| } |
| assert(IsOutputConfiguredLocked()); |
| assert(!packet_free_bits_[kOutput].empty()); |
| assert(all_packets_[kOutput].size() == packet_free_bits_[kOutput].size()); |
| if (available_output_packet.packet_index >= all_packets_[kOutput].size()) { |
| Exit("out of range packet_index from client in RecycleOutputPacket()"); |
| } |
| uint32_t packet_index = available_output_packet.packet_index; |
| if (packet_free_bits_[kOutput][packet_index]) { |
| Exit( |
| "packet_index already free at protocol level - invalid client message"); |
| } |
| // Mark free at protocol level. |
| packet_free_bits_[kOutput][packet_index] = true; |
| |
| // Recycle to OMX layer, if presently in acceptable OMX state. |
| OmxTryRecycleOutputPacketLocked( |
| all_packets_[kOutput][packet_index]->omx_header()); |
| } |
| |
| // TODO(dustingreen): At least for decoders, get the OOB config data if any, |
| // stash it temporarily, and convert to CODECCONFIG (instead of the codec |
| // creation format details). |
| void OmxCodecRunner::QueueInputFormatDetails( |
| uint64_t stream_lifetime_ordinal, |
| fuchsia::media::FormatDetails format_details) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureFutureStreamSeenLocked(stream_lifetime_ordinal); |
| } // ~lock |
| PostSerial(stream_control_dispatcher_, |
| [this, stream_lifetime_ordinal, |
| format_details = std::move(format_details)]() mutable { |
| QueueInputFormatDetails_StreamControl(stream_lifetime_ordinal, |
| std::move(format_details)); |
| }); |
| } |
| |
| void OmxCodecRunner::QueueInputFormatDetails_StreamControl( |
| uint64_t stream_lifetime_ordinal, |
| fuchsia::media::FormatDetails format_details) { |
| assert(thrd_current() == stream_control_thread_); |
| |
| std::unique_lock<std::mutex> lock(lock_); |
| CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal); |
| assert(stream_lifetime_ordinal >= stream_lifetime_ordinal_); |
| if (stream_lifetime_ordinal > stream_lifetime_ordinal_) { |
| StartNewStream(lock, stream_lifetime_ordinal); |
| } |
| assert(stream_lifetime_ordinal == stream_lifetime_ordinal_); |
| if (stream_->input_end_of_stream()) { |
| Exit("QueueInputFormatDetails() after QueueInputEndOfStream() unexpected"); |
| } |
| 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. |
| stream_->SetOobConfigPending(true); |
| } |
| |
| void OmxCodecRunner::QueueInputPacket(fuchsia::media::Packet packet) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureFutureStreamSeenLocked(packet.stream_lifetime_ordinal); |
| } // ~lock |
| PostSerial(stream_control_dispatcher_, |
| [this, packet = std::move(packet)]() mutable { |
| QueueInputPacket_StreamControl(std::move(packet)); |
| }); |
| } |
| |
| void OmxCodecRunner::QueueInputPacket_StreamControl( |
| fuchsia::media::Packet packet) { |
| // Unless we cancel this cleanup, we'll free the input packet back to the |
| // client. |
| // |
| // This is an example of where not being able to copy a FIDL struct using |
| // language-level copy can be a bit verbose, but overall it's probably worth |
| // forcing copy to be explicit. |
| fuchsia::media::PacketHeader temp_header_copy; |
| zx_status_t clone_result = packet.header.Clone(&temp_header_copy); |
| if (clone_result != ZX_OK) { |
| Exit("PacketHeader::Clone() failed"); |
| } |
| |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| auto send_free_input_packet_locked = |
| fit::defer([this, header = std::move(temp_header_copy)]() mutable { |
| SendFreeInputPacketLocked(std::move(header)); |
| }); |
| |
| CheckOldBufferLifetimeOrdinalLocked(kInput, |
| packet.header.buffer_lifetime_ordinal); |
| |
| // For input, mid-stream config changes are not a thing and input buffers |
| // are never unilaterally de-configured by the Codec server. |
| assert(buffer_lifetime_ordinal_[kInput] == |
| port_settings_[kInput]->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_[kInput]->buffer_lifetime_ordinal) { |
| Exit("client QueueInputPacket() with invalid buffer_lifetime_ordinal."); |
| } |
| |
| CheckStreamLifetimeOrdinalLocked(packet.stream_lifetime_ordinal); |
| 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. |
| StartNewStream(lock, packet.stream_lifetime_ordinal); |
| } |
| assert(packet.stream_lifetime_ordinal == stream_lifetime_ordinal_); |
| |
| if (packet.header.packet_index >= all_packets_[kInput].size()) { |
| Exit("client QueueInputPacket() with packet_index out of range"); |
| } |
| if (packet.buffer_index >= all_buffers_[kInput].size()) { |
| Exit("client QueueInputPacket() with buffer_index out of range"); |
| } |
| |
| // Protocol check re. free/busy coherency. |
| if (!packet_free_bits_[kInput][packet.header.packet_index]) { |
| Exit("client QueueInputPacket() with packet_index !free - exiting\n"); |
| } |
| packet_free_bits_[kInput][packet.header.packet_index] = false; |
| |
| if (stream_->input_end_of_stream()) { |
| Exit("QueueInputPacket() after QueueInputEndOfStream() unexpeted"); |
| } |
| |
| 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. |
| // |
| // If the stream didn't fully start - as in, the client moved on to |
| // another stream before fully configuring output, then OMX is not |
| // presently in a state compatible with queueing input, but the Codec |
| // interface is. So in that case, we must avoid queueing to OMX for |
| // correctness. |
| // |
| // If the stream was just future-discarded after fully starting, then this |
| // is just an optimization to avoid giving OMX 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 OMX gives |
| // back the packet. |
| send_free_input_packet_locked.cancel(); |
| } // ~lock |
| |
| if (stream_->oob_config_pending()) { |
| OmxQueueInputOOB(); |
| stream_->SetOobConfigPending(false); |
| } |
| |
| // 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 SendCommand to OMX |
| // will start after this. |
| OmxQueueInputPacket(packet); |
| } |
| |
| void OmxCodecRunner::StartNewStream(std::unique_lock<std::mutex>& lock, |
| uint64_t stream_lifetime_ordinal) { |
| assert(thrd_current() == stream_control_thread_); |
| assert((stream_lifetime_ordinal % 2 == 1) && |
| "new stream_lifetime_ordinal must be odd"); |
| |
| EnsureStreamClosed(lock); |
| assert((stream_lifetime_ordinal_ % 2 == 0) && "expecting no current stream"); |
| assert(!stream_); |
| |
| // Now it's time to start the new stream. We start the new stream at |
| // Codec layer first then OMX layer. |
| |
| if (!IsInputConfiguredLocked()) { |
| Exit("input not configured before start of stream (QueueInputPacket())"); |
| } |
| |
| assert(stream_queue_.size() >= 1); |
| 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. |
| assert(stream_lifetime_ordinal > stream_lifetime_ordinal_); |
| stream_lifetime_ordinal_ = stream_lifetime_ordinal; |
| assert(stream_->stream_lifetime_ordinal() == stream_lifetime_ordinal_); |
| |
| // At this point, when driving an OMX codec, we need the output to be |
| // configured to _something_, as OMX doesn't support giving us the real |
| // output config unless the output is configured to at least something at |
| // first. If the client has not yet configured output, we also are |
| // required to tell the client about the output config needed by this |
| // stream in particular (at least the config needed by this stream in |
| // particular at the start of this stream - there's no guarantee that this |
| // stream will be able to continue without another output config change |
| // before OMX emits any data). |
| // |
| // At this point we know we've never sent a config that's tagged with the |
| // new stream yet. Send that now, if output isn't already configured. |
| |
| if (!IsOutputConfiguredLocked() || |
| port_settings_[kOutput]->buffer_constraints_version_ordinal <= |
| omx_meh_output_buffer_constraints_version_ordinal_) { |
| StartIgnoringClientOldOutputConfigLocked(); |
| EnsureBuffersNotConfiguredLocked(kOutput); |
| // 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 (!stream_->future_discarded() && !IsOutputConfiguredLocked()) { |
| wake_stream_control_.wait(lock); |
| } |
| |
| if (stream_->future_discarded()) { |
| return; |
| } |
| |
| // Now we have both input and output configured, so we can move OMX from |
| // OMX loaded state to OMX executing state. This also calls or re-calls |
| // FillThisBuffer() on any currently-free output packets. |
| EnsureOmxStateExecuting(lock); |
| } |
| |
| void OmxCodecRunner::EnsureStreamClosed(std::unique_lock<std::mutex>& lock) { |
| // Move OMX codec to OMX loaded (from OMX executing), by using this thread to |
| // directly drive the codec from executing down to loaded. We do this first |
| // so OMX won't try to send us output while we have no stream at the Codec |
| // layer. |
| // |
| // We can de-init OMX codec here regardless of whether output buffers are yet |
| // ready. For some codecs we try to encourage the client to have the output |
| // buffers be ready before a stream starts, but that's generally not required |
| // by all codecs, and the client is not required to configure output before |
| // feeding input. |
| EnsureOmxStateLoaded(lock); |
| |
| // Now close the old stream at the Codec layer. |
| EnsureCodecStreamClosedLockedInternal(); |
| |
| assert((stream_lifetime_ordinal_ % 2 == 0) && "expecting no current stream"); |
| assert(!stream_); |
| } |
| |
| void OmxCodecRunner::EnsureOmxStateLoaded(std::unique_lock<std::mutex>& lock) { |
| assert(thrd_current() == stream_control_thread_); |
| // We never leave the OMX codec in OMX_StateIdle, because the only way to |
| // reset an OMX codec between streams is to drop all the way down to |
| // OMX_StateLoaded. |
| assert(omx_state_ == OMX_StateLoaded || omx_state_ == OMX_StateExecuting); |
| assert(omx_state_desired_ == omx_state_); |
| if (omx_state_ == OMX_StateLoaded) { |
| // Already done |
| return; |
| } |
| assert(omx_state_ == OMX_StateExecuting); |
| |
| is_omx_recycle_enabled_ = false; |
| |
| // Drop the codec from executing to idle, then from idle to loaded. |
| |
| OmxStartStateSetLocked(OMX_StateIdle); |
| |
| // (FillBufferDone() is ignoring the buffers returned thanks to |
| // omx_state_desired_ set other than OMX_StateExecuting by |
| // OmxStartStateSetLocked() above, and EmptyBufferDone() is sending |
| // OnFreeInputPacket() like usual.) |
| |
| VLOGF("waiting for idle state...\n"); |
| OmxWaitForState(lock, OMX_StateExecuting, OMX_StateIdle); |
| VLOGF("idle state reached\n"); |
| |
| // The codec by this point will have "returned" all the buffers by calling |
| // FillBufferDone() and/or EmptyBufferDone(). The buffers are still |
| // allocated. Unlike for port disable, we don't have to wait for this count |
| // to reach zero ourselves, because OMX essentially has two steps to get from |
| // OMX_StateExecuting to OMX_StateLoaded, but only one step to go from port |
| // enabled to port disabled. Only during port disable is this count reaching |
| // zero used as a signalling mechanism. If we wanted, we could pretend to |
| // wait for this just above or below waiting to reach OMX_StateIdle, but there |
| // would be no point. |
| assert(omx_output_buffer_with_omx_count_ == 0); |
| |
| OmxStartStateSetLocked(OMX_StateLoaded); |
| |
| // We've started the state change from OMX_StateIdle to OMX_StateLoaded, but |
| // for that state change to complete, we must call OMX FreeBuffer() on all the |
| // OMX buffer headers. We completely ignore the OMX spec where it says that |
| // low-layer buffers need to be deallocated before calling FreeBuffer(). |
| // Instead we leave our low-layer buffers completely allocated and will |
| // (potentially, if not reconfigured) use them again when moving from |
| // OMX_StateLoaded to OMX_StateIdle in future. |
| |
| // We know input is not happening currently because we're on StreamControl |
| // domain. We know RecycleOutputPacket() is not actually recycling output |
| // buffers back to OMX thanks to OmxTryRecycleOutputPacketLocked() checking |
| // omx_state_desired_ != OMX_StateExecuting. |
| |
| // We don't deallocate Packet(s) here, we only deallocate all the OMX buffer |
| // headers. |
| OmxFreeAllBufferHeaders(lock); |
| |
| VLOGF("waiting for loaded state...\n"); |
| OmxWaitForState(lock, OMX_StateIdle, OMX_StateLoaded); |
| VLOGF("loaded state reached\n"); |
| |
| // Ensure output port is enabled, to get it back to same state as if we had |
| // just loaded the codec. This is effectively the end of cancelling a |
| // mid-stream output config change. |
| OMX_PARAM_PORTDEFINITIONTYPE output_port_def; |
| InitOmxStruct(&output_port_def); |
| output_port_def.nPortIndex = omx_port_index_[kOutput]; |
| OMX_ERRORTYPE omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamPortDefinition, &output_port_def); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "Couldn't get port definition from OMX (during ensure output enable) - " |
| "result: %d", |
| omx_result); |
| } |
| if (!output_port_def.bEnabled) { |
| OmxOutputStartSetEnabledLocked(true); |
| // In this case we can immediately wait because we're in OMX_StateLoaded, |
| // so nothing to do before waiting in this case. |
| OmxWaitForOutputEnableStateChangeDone(lock); |
| } |
| |
| // Reset OMX codec state tracking. |
| omx_output_enabled_ = true; |
| omx_output_enabled_desired_ = true; |
| assert(omx_state_ == OMX_StateLoaded && |
| omx_state_desired_ == OMX_StateLoaded); |
| assert(omx_output_enabled_ && omx_output_enabled_desired_); |
| |
| // The OMX codec, and our associated tracking state, is now reset. |
| } |
| |
| void OmxCodecRunner::OmxOutputStartSetEnabledLocked(bool enable) { |
| // We post because we always post all FillThisBuffer() and |
| // SendCommand(), and because we want to call OMX only outside lock_. |
| omx_output_enabled_desired_ = enable; |
| PostSerial(fidl_dispatcher_, [this, enable] { |
| OMX_ERRORTYPE omx_result = omx_component_->SendCommand( |
| omx_component_, enable ? OMX_CommandPortEnable : OMX_CommandPortDisable, |
| omx_port_index_[kOutput], nullptr); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "SendCommand(OMX_CommandPortEnable/OMX_CommandPortDisable) failed - " |
| "exiting - enable: %d result: %d\n", |
| enable, omx_result); |
| } |
| }); |
| } |
| |
| // packet is modified; packet is not stashed |
| void OmxCodecRunner::OmxFreeBufferHeader(std::unique_lock<std::mutex>& lock, |
| Port port, Packet* packet) { |
| OMX_BUFFERHEADERTYPE* header = packet->omx_header(); |
| packet->SetOmxHeader(nullptr); |
| // We make all ScopedUnlock scopes stand out, even if a scope just goes to |
| // the end of a method. |
| { // scope unlock |
| ScopedUnlock unlock(lock); |
| OMX_ERRORTYPE omx_result = omx_component_->FreeBuffer( |
| omx_component_, omx_port_index_[port], header); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("FreeBuffer() failed - exiting - port: %d\n", port); |
| } |
| } // ~unlock |
| } |
| |
| void OmxCodecRunner::OmxWaitForState(std::unique_lock<std::mutex>& lock, |
| OMX_STATETYPE from_state, |
| OMX_STATETYPE desired_state) { |
| while (omx_state_ != omx_state_desired_) { |
| if (omx_state_ != from_state && omx_state_ != desired_state) { |
| // We went off the expected state transition rails. This is treated as a |
| // fatal error. We don't expect this to happen. We don't have any |
| // reasonable way to handle this short of starting over with a new codec |
| // process. |
| Exit( |
| "while waiting for state transition, went off expected state rails - " |
| "from_state: %d desired_state: %d omx_state_: %d\n", |
| from_state, desired_state, omx_state_); |
| } |
| omx_state_changed_.wait(lock); |
| } |
| } |
| |
| void OmxCodecRunner::OmxWaitForOutputEnableStateChangeDone( |
| std::unique_lock<std::mutex>& lock) { |
| while (omx_output_enabled_ != omx_output_enabled_desired_) { |
| omx_output_enabled_changed_.wait(lock); |
| } |
| } |
| |
| void OmxCodecRunner::EnsureOmxStateExecuting( |
| std::unique_lock<std::mutex>& lock) { |
| assert(stream_control_thread_ == thrd_current()); |
| for (Port port = kFirstPort; port < kPortCount; port++) { |
| // In contrast to Codec interface, OMX doesn't permit the output buffers to |
| // be not yet configured when moving to OMX_StateExecuting, so the caller |
| // takes care of ensuring that the client has configured output buffers. |
| uint32_t packet_count = PacketCountFromPortSettings(*port_settings_[port]); |
| (void)packet_count; |
| assert(all_packets_[port].size() == packet_count); |
| } |
| assert(omx_input_buffer_oob_); |
| assert(omx_input_packet_oob_); |
| assert(omx_input_packet_eos_); |
| if (omx_state_ == OMX_StateExecuting) { |
| // TODO(dustingreen): We don't actually use this method this way currently. |
| // If that stays true for much longer, rename and don't check for this case |
| // (but still assert below). |
| return; |
| } |
| assert(omx_state_ == OMX_StateLoaded); |
| |
| // First, make sure OMX has the proper buffer count, for each port. |
| EnsureOmxBufferCountCurrent(lock); |
| |
| VLOGF("starting transition to OMX_StateIdle\n"); |
| OmxStartStateSetLocked(OMX_StateIdle); |
| VLOGF("transition to idle started.\n"); |
| |
| // Allocate an OMX_BUFFERHEADERTYPE for each packet in all_packets_, and one |
| // for omx_input_packet_oob_ and one for omx_input_packet_eos_. |
| for (Port port = kFirstPort; port < kPortCount; port++) { |
| OmxPortUseBuffers(lock, port); |
| } |
| omx_input_packet_oob_->SetOmxHeader( |
| OmxUseBuffer(lock, kInput, *omx_input_packet_oob_)); |
| omx_input_packet_eos_->SetOmxHeader( |
| OmxUseBuffer(lock, kInput, *omx_input_packet_eos_)); |
| |
| // We've told the codec about all the buffers, so the codec should transition |
| // to idle soon if it isn't already. |
| VLOGF("waiting for OMX_StateIdle...\n"); |
| OmxWaitForState(lock, OMX_StateLoaded, OMX_StateIdle); |
| VLOGF("OMX_StateIdle reached\n"); |
| |
| // Now that the codec is idle, we can immediately transition the codec to |
| // executing. |
| VLOGF("starting codec transition to executing state\n"); |
| OmxStartStateSetLocked(OMX_StateExecuting); |
| VLOGF("transition to OMX_StateExecuting started\n"); |
| |
| OmxWaitForState(lock, OMX_StateIdle, OMX_StateExecuting); |
| VLOGF("done with transition to OMX_StateExecuting\n"); |
| |
| // Tell the codec to fill all the output buffers that are free. This is how |
| // the non-action sometimes in OmxTryRecycleOutputPacketLocked() ends up |
| // calling FillThisBuffer() at the appropriate time. This is that time. |
| // |
| // If an output packet is not free, that that packet is still with the Codec |
| // client, which can be entirely reasonable even for long periods of time if |
| // the Codec client set a non-zero packet_count_for_client. |
| // |
| // The input buffers thankfully start with the OMX client so that's already |
| // consistent with existing packet_free_bits_[kInput]. |
| for (auto& output_packet : all_packets_[kOutput]) { |
| if (packet_free_bits_[kOutput][output_packet->packet_index()]) { |
| OmxFillThisBufferLocked(output_packet->omx_header()); |
| } |
| } |
| is_omx_recycle_enabled_ = true; |
| } |
| |
| // Make sure OMX has the current buffer count for each port. |
| // |
| // During mid-stream format change, this method relies on input config changes |
| // being prohibited with an active stream - that's how this method avoids |
| // telling OMX to change the input config with the input port presently enabled. |
| void OmxCodecRunner::EnsureOmxBufferCountCurrent( |
| std::unique_lock<std::mutex>& lock) { |
| assert(stream_control_thread_ == thrd_current()); |
| // This method isn't called at a time when input or output config can be |
| // changing. Since we call OMX in here, we force the caller to provide the |
| // caller's unique_lock<> only so we can be more sure that we're not holding |
| // that lock while calling OMX. |
| // |
| // TODO(dustingreen): Change to locks that are capable of asserting that the |
| // current thread doesn't hold the lock, and switch to asserting here instead. |
| ScopedUnlock unlock(lock); |
| OMX_PARAM_PORTDEFINITIONTYPE port_definition[kPortCount]; |
| for (Port port = kFirstPort; port < kPortCount; port++) { |
| OMX_PARAM_PORTDEFINITIONTYPE& port_def = port_definition[port]; |
| InitOmxStruct(&port_def); |
| port_def.nPortIndex = omx_port_index_[port]; |
| OMX_ERRORTYPE omx_result = omx_component_->GetParameter( |
| omx_component_, OMX_IndexParamPortDefinition, &port_def); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "Couldn't get port definition from OMX - exiting - port: %d result: " |
| "%d\n", |
| port, omx_result); |
| } |
| assert(port_def.nBufferCountActual >= port_def.nBufferCountMin); |
| // We don't use the omx_input_packet_oob_ unless we're sending OOB data (and |
| // similar for omx_input_packet_eos_ unless we're sending an EOS), so those |
| // buffers don't really count for nBufferCountMin purposes. As in, we |
| // shouldn't expect the OMX codec to work properly unless there are as many |
| // normal buffers as required by OMX, not counting the |
| // kHiddenInputPacketCount. |
| uint32_t packet_count = PacketCountFromPortSettings(*port_settings_[port]); |
| assert(packet_count >= port_def.nBufferCountMin); |
| uint32_t omx_buffer_count = packet_count; |
| if (port == kInput) { |
| // for omx_input_packet_oob_ and omx_input_packet_eos_ |
| omx_buffer_count += kHiddenInputPacketCount; |
| assert(omx_buffer_count >= |
| port_def.nBufferCountMin + kHiddenInputPacketCount); |
| } |
| if (port_def.nBufferCountActual != omx_buffer_count) { |
| port_def.nBufferCountActual = omx_buffer_count; |
| assert(port_def.nBufferCountActual >= port_def.nBufferCountMin); |
| omx_result = omx_component_->SetParameter( |
| omx_component_, OMX_IndexParamPortDefinition, &port_def); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("SetParamter(port_definition) failed - exiting\n"); |
| } |
| } |
| } |
| } |
| |
| void OmxCodecRunner::OmxPortUseBuffers(std::unique_lock<std::mutex>& lock, |
| Port port) { |
| assert(!all_packets_[port].empty()); |
| for (auto& packet : all_packets_[port]) { |
| packet->SetOmxHeader(OmxUseBuffer(lock, port, *packet)); |
| } |
| } |
| |
| OMX_BUFFERHEADERTYPE* OmxCodecRunner::OmxUseBuffer( |
| std::unique_lock<std::mutex>& lock, Port port, const Packet& packet) { |
| assert(!packet.omx_header()); |
| const Buffer& buffer = packet.buffer(); |
| size_t codec_buffer_size = buffer.buffer_size(); |
| // For input, we can report larger size to OMX than we'll actually use for |
| // any delivered input buffer when our input packets are smaller than OMX |
| // thinks they ought to be. |
| // |
| // For output, our codec packet buffers must be at least as large as what |
| // we're telling OMX, since OMX is free to fill up to header->nAllocLen. |
| const size_t omx_min_buffer_size = omx_port_def_[port].nBufferSize; |
| size_t omx_buffer_size_raw = std::max(omx_min_buffer_size, codec_buffer_size); |
| assert(omx_buffer_size_raw >= omx_min_buffer_size); |
| if (omx_buffer_size_raw > std::numeric_limits<OMX_U32>::max()) { |
| Exit("internal buffer size limit exceeded - exiting\n"); |
| } |
| auto omx_buffer_size = static_cast<OMX_U32>(omx_buffer_size_raw); |
| if (port == kOutput) { |
| // If codec_buffer_size is smaller, we won't have room for the amount of |
| // output OMX might create. If that happened somehow, when did OMX |
| // unilaterally change nBufferSize to be larger without informing us? It |
| // shouldn't. |
| // |
| // The codec_buffer_size can't be larger than omx_buffer_size due to the |
| // max above. We do want OMX to be able to fill as much of each codec |
| // packet as it wants. |
| assert(codec_buffer_size == omx_buffer_size); |
| } |
| OMX_BUFFERHEADERTYPE* header = nullptr; |
| { // scope unlock |
| ScopedUnlock unlock(lock); |
| OMX_ERRORTYPE omx_result = omx_component_->UseBuffer( |
| omx_component_, &header, omx_port_index_[port], |
| reinterpret_cast<OMX_PTR>(const_cast<Packet*>(&packet)), |
| omx_buffer_size, buffer.buffer_base()); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("UseBuffer() failed - exiting - port: %d\n", port); |
| } |
| } // ~unlock |
| return header; |
| } |
| |
| void OmxCodecRunner::OmxStartStateSetLocked(OMX_STATETYPE omx_state_desired) { |
| omx_state_desired_ = omx_state_desired; |
| PostSerial(fidl_dispatcher_, [this, omx_state_desired] { |
| OMX_ERRORTYPE omx_result = omx_component_->SendCommand( |
| omx_component_, OMX_CommandStateSet, omx_state_desired, nullptr); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("SendCommand(StateSet) failed - result: %d omx_state_desired: %d\n", |
| omx_result, omx_state_desired); |
| } |
| }); |
| } |
| |
| void OmxCodecRunner::QueueInputEndOfStream(uint64_t stream_lifetime_ordinal) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| EnsureFutureStreamSeenLocked(stream_lifetime_ordinal); |
| } // ~lock |
| PostSerial(stream_control_dispatcher_, [this, stream_lifetime_ordinal] { |
| QueueInputEndOfStream_StreamControl(stream_lifetime_ordinal); |
| }); |
| } |
| |
| void OmxCodecRunner::QueueInputEndOfStream_StreamControl( |
| uint64_t stream_lifetime_ordinal) { |
| std::unique_lock<std::mutex> lock(lock_); |
| CheckStreamLifetimeOrdinalLocked(stream_lifetime_ordinal); |
| assert(stream_lifetime_ordinal >= stream_lifetime_ordinal_); |
| if (stream_lifetime_ordinal > stream_lifetime_ordinal_) { |
| // It might seem odd to start a new stream given an end-of-stream for a |
| // stream we've not seen before, but in my experience, allowing empty things |
| // to not be errors is better. |
| StartNewStream(lock, stream_lifetime_ordinal); |
| } |
| |
| 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; |
| } |
| |
| // Convert to an input OMX packet with EOS set. We have an extra OMX buffer |
| // reserved for this purpose. |
| OmxQueueInputEOS(); |
| } |
| |
| void OmxCodecRunner::OmxQueueInputPacket( |
| const fuchsia::media::Packet& packet) { |
| assert(thrd_current() == stream_control_thread_); |
| // The OMX codec can report an error unilaterally, but it can't change state |
| // unilaterally. So on the StreamControl ordering domain it's ok to check the |
| // omx_state_ outside lock_. |
| assert(omx_state_ == OMX_StateExecuting); |
| // We only modify all_packets_[kInput] on StreamControl, so it's ok to read |
| // from it outside lock_. |
| if (!decoder_params_->promise_separate_access_units_on_input && |
| packet.timestamp_ish != 0) { |
| Exit( |
| "timestamp_ish must be 0 unless promise_separate_access_units_on_input " |
| "- exiting\n"); |
| } |
| OMX_BUFFERHEADERTYPE* header = |
| all_packets_[kInput][packet.header.packet_index]->omx_header(); |
| header->nFilledLen = packet.valid_length_bytes; |
| header->nOffset = 0; |
| header->nTimeStamp = packet.timestamp_ish; |
| header->nFlags = 0; |
| OMX_ERRORTYPE omx_result = |
| omx_component_->EmptyThisBuffer(omx_component_, header); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("component_->EmptyThisBuffer() failed - exiting - omx_result: %d\n", |
| omx_result); |
| } |
| } |
| |
| void OmxCodecRunner::OmxQueueInputOOB() { |
| assert(thrd_current() == stream_control_thread_); |
| assert(omx_state_ == OMX_StateExecuting); |
| |
| // Unlike for the omx_input_packet_eos_, there's no particular guarantee that |
| // the OOB packet is actually free at this point, so wait for it to be free |
| // first. This relies on the InputData domain not being the same as the |
| // StreamControl domain. |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| while (!omx_input_packet_oob_free_) { |
| omx_input_packet_oob_free_condition_.wait(lock); |
| } |
| } // ~lock |
| |
| // Whether oob_bytes is needed can depend on codec type or specific |
| // input format. If there is no oob_bytes, we won't queue any |
| // OMX_BUFFERFLAG_CODECCONFIG buffer to OMX. |
| // |
| // TODO(dustingreen): For SoftAAC2 used in ADTS mode, extract OOB info from |
| // the first input data instead of requiring a client to provide OOB info that |
| // ultimately came from the first ADTS input data anyway... |
| // |
| // TODO(dustingreen): Consider enforcing whether a codec needs oob_bytes |
| // or whether it must not have oob_bytes, rather than relying on OMX SW |
| // codecs to fail in a reasonably sane way. Cover the case of empty |
| // oob_bytes also. |
| |
| assert(initial_input_format_details_); |
| const std::vector<uint8_t>* oob_bytes = nullptr; |
| if (stream_->input_format_details() && |
| stream_->input_format_details()->oob_bytes) { |
| oob_bytes = &stream_->input_format_details()->oob_bytes.get(); |
| } else if (initial_input_format_details_->oob_bytes) { |
| oob_bytes = &initial_input_format_details_->oob_bytes.get(); |
| } |
| if (!oob_bytes) { |
| // This is potentially fine. Let the OMX SW codec fail later if it wants to |
| // based on lack of OOB data, or maybe this codec and/or format doesn't need |
| // OOB data. |
| printf("!oob_bytes - potentially fine\n"); |
| return; |
| } |
| assert(oob_bytes); |
| if (oob_bytes->empty()) { |
| Exit("oob_bytes was non-null but empty - exiting\n"); |
| } |
| assert(omx_input_packet_oob_->buffer().buffer_size() >= |
| fuchsia::media::kMaxOobBytesSize); |
| if (oob_bytes->size() > fuchsia::media::kMaxOobBytesSize) { |
| Exit( |
| "oob_bytes.size() > fuchsia::media::kMaxOobBytesSize - " |
| "exiting\n"); |
| } |
| assert(oob_bytes->size() <= |
| omx_input_packet_oob_->buffer().buffer_size()); |
| |
| size_t copy_size = oob_bytes->size(); |
| uint8_t* buffer_base = omx_input_packet_oob_->buffer().buffer_base(); |
| for (size_t i = 0; i < copy_size; i++) { |
| buffer_base[i] = (*oob_bytes)[i]; |
| } |
| |
| // This lock interval isn't strictly necessary, but to describe why it's not |
| // necessary would require delving into happens-before relationships in OMX, |
| // so go ahead and just grab the lock to assign false. It's not worth having |
| // different sync rules for how omx_input_packet_oob_free_ becomes true vs. |
| // becomes false. We can't do this assignment up above the state checks in |
| // the previous lock hold interval, because that would potentially incorrectly |
| // leave omx_input_packet_oob_free_ set to false in a case where we return |
| // early and don't use omx_input_packet_oob_. |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| omx_input_packet_oob_free_ = false; |
| } // ~lock |
| |
| VLOGF("OmxQueueInputOOB() is queueing oob_bytes to the OMX codec.\n"); |
| OMX_BUFFERHEADERTYPE* header = omx_input_packet_oob_->omx_header(); |
| header->nFlags = OMX_BUFFERFLAG_CODECCONFIG; |
| header->nFilledLen = copy_size; |
| header->nOffset = 0; |
| header->nTimeStamp = 0; |
| OMX_ERRORTYPE omx_result = |
| omx_component_->EmptyThisBuffer(omx_component_, header); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "component_->EmptyThisBuffer() failed (OOB case) - exiting - " |
| "omx_result: %d\n", |
| omx_result); |
| } |
| } |
| |
| void OmxCodecRunner::OmxQueueInputEOS() { |
| assert(thrd_current() == stream_control_thread_); |
| assert(omx_state_ == OMX_StateExecuting); |
| assert(omx_input_packet_eos_free_); |
| omx_input_packet_eos_free_ = false; |
| OMX_BUFFERHEADERTYPE* header = omx_input_packet_eos_->omx_header(); |
| header->nFlags = OMX_BUFFERFLAG_EOS; |
| header->nFilledLen = 0; |
| header->nOffset = 0; |
| header->nTimeStamp = 0; |
| OMX_ERRORTYPE omx_result = |
| omx_component_->EmptyThisBuffer(omx_component_, header); |
| if (omx_result != OMX_ErrorNone) { |
| Exit( |
| "component_->EmptyThisBuffer() failed (EOS case) - exiting - " |
| "omx_result: %d\n", |
| omx_result); |
| } |
| } |
| |
| bool OmxCodecRunner::IsInputConfiguredLocked() { |
| return IsPortConfiguredCommonLocked(kInput); |
| } |
| |
| bool OmxCodecRunner::IsOutputConfiguredLocked() { |
| return IsPortConfiguredCommonLocked(kOutput); |
| } |
| |
| bool OmxCodecRunner::IsPortConfiguredCommonLocked(Port port) { |
| if (!port_settings_[port]) { |
| return false; |
| } |
| assert(all_buffers_[port].size() <= |
| BufferCountFromPortSettings(*port_settings_[port])); |
| return all_buffers_[port].size() == |
| BufferCountFromPortSettings(*port_settings_[port]); |
| } |
| |
| OMX_ERRORTYPE OmxCodecRunner::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) { |
| VLOGF("omx_EventHandler eEvent: %d nData1: %d, nData2: %d pEventData: %p\n", |
| eEvent, nData1, nData2, pEventData); |
| fflush(nullptr); |
| auto* me = reinterpret_cast<OmxCodecRunner*>(pAppData); |
| assert(me->omx_component_ == hComponent); |
| return me->EventHandler(eEvent, nData1, nData2, pEventData); |
| } |
| |
| OMX_ERRORTYPE OmxCodecRunner::omx_EmptyBufferDone( |
| OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData, |
| OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { |
| auto* me = reinterpret_cast<OmxCodecRunner*>(pAppData); |
| assert(me->omx_component_ == hComponent); |
| return me->EmptyBufferDone(pBuffer); |
| } |
| |
| OMX_ERRORTYPE OmxCodecRunner::omx_FillBufferDone( |
| OMX_IN OMX_HANDLETYPE hComponent, OMX_IN OMX_PTR pAppData, |
| OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { |
| auto* me = reinterpret_cast<OmxCodecRunner*>(pAppData); |
| assert(me->omx_component_ == hComponent); |
| return me->FillBufferDone(pBuffer); |
| } |
| |
| OMX_ERRORTYPE OmxCodecRunner::EventHandler(OMX_IN OMX_EVENTTYPE eEvent, |
| OMX_IN OMX_U32 nData1, |
| OMX_IN OMX_U32 nData2, |
| OMX_IN OMX_PTR pEventData) { |
| // We intentionally don't acquire lock_ yet. We postpone acquiring until the |
| // more detailed handlers called after we've parsed all the fan-out in this |
| // method. The point of this is to allow more optimal notification of |
| // condition variables while we're not presently holding the lock, without |
| // resorting to queuing "todo" work up the stack via the lock holds, to be run |
| // upon lock release, because we don't really need to go there to achieve the |
| // goal. |
| switch (eEvent) { |
| case OMX_EventCmdComplete: |
| // completed a command |
| VLOGF("OMX_EventCmdComplete\n"); |
| switch (nData1) { |
| case OMX_CommandStateSet: |
| VLOGF(" OMX_CommandStateSet - state reached: %d\n", nData2); |
| assert(pEventData == nullptr); |
| onOmxStateSetComplete(static_cast<OMX_STATETYPE>(nData2)); |
| break; |
| case OMX_CommandFlush: |
| printf(" OMX_CommandFlush - port index: %d\n", nData2); |
| assert(pEventData == nullptr); |
| assert(false && "we nver send OMX_CommandFlush\n"); |
| break; |
| case OMX_CommandPortDisable: |
| VLOGF(" OMX_CommandPortDisable - port index: %d\n", nData2); |
| assert(pEventData == nullptr); |
| if (nData2 == omx_port_index_[kOutput]) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| omx_output_enabled_ = false; |
| assert(omx_output_enabled_ == omx_output_enabled_desired_); |
| } |
| omx_output_enabled_changed_.notify_all(); |
| } |
| break; |
| case OMX_CommandPortEnable: |
| VLOGF(" OMX_CommandPortEnable - port index: %d\n", nData2); |
| assert(pEventData == nullptr); |
| if (nData2 == omx_port_index_[kOutput]) { |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| omx_output_enabled_ = true; |
| assert(omx_output_enabled_ == omx_output_enabled_desired_); |
| } |
| omx_output_enabled_changed_.notify_all(); |
| } |
| break; |
| case OMX_CommandMarkBuffer: |
| printf(" OMX_CommandMarkBuffer - port index: %d\n", nData2); |
| assert(pEventData == nullptr); |
| assert(false && "we nver send OMX_CommandMarkBuffer\n"); |
| break; |
| } |
| break; |
| case OMX_EventError: |
| // detected an error condition |
| { |
| // OMX spec says nData2 and pEventData are 0, but apparently not |
| // actually true... |
| printf("OMX_EventError - error: %d, nData2: %d, pEventData: %p\n", |
| nData1, nData2, pEventData); |
| const char* error_string = nullptr; |
| auto which_error = static_cast<OMX_ERRORTYPE>(nData1); |
| // recoverable means recoverable by faling the stream, not recoverable |
| // within a stream - there doesn't appear to be any way for AOSP OMX SW |
| // codecs to report mid-stream errors (despite what the OMX spec says) |
| bool recoverable = false; |
| switch (which_error) { |
| case OMX_ErrorNone: |
| error_string = "OMX_ErrorNone"; |
| // Not recoverable, because delivering this would make no sense. |
| break; |
| case OMX_ErrorInsufficientResources: |
| error_string = "OMX_ErrorInsufficientResources"; |
| // not recoverable because we don't use any OMX codecs where this |
| // would make any sense |
| break; |
| case OMX_ErrorUndefined: |
| error_string = "OMX_ErrorUndefined"; |
| // Some AOSP OMX SW codecs report recoverable errors this way. |
| recoverable = true; |
| break; |
| case OMX_ErrorInvalidComponentName: |
| error_string = "OMX_ErrorInvalidComponentName"; |
| break; |
| case OMX_ErrorComponentNotFound: |
| error_string = "OMX_ErrorComponentNotFound"; |
| break; |
| case OMX_ErrorInvalidComponent: |
| error_string = "OMX_ErrorInvalidComponent"; |
| break; |
| case OMX_ErrorBadParameter: |
| error_string = "OMX_ErrorBadParameter"; |
| break; |
| case OMX_ErrorNotImplemented: |
| error_string = "OMX_ErrorNotImplemented"; |
| break; |
| case OMX_ErrorUnderflow: |
| error_string = "OMX_ErrorUnderflow"; |
| break; |
| case OMX_ErrorOverflow: |
| error_string = "OMX_ErrorOverflow"; |
| break; |
| case OMX_ErrorHardware: |
| error_string = "OMX_ErrorHardware"; |
| break; |
| case OMX_ErrorInvalidState: |
| error_string = "OMX_ErrorInvalidState"; |
| break; |
| case OMX_ErrorStreamCorrupt: |
| error_string = "OMX_ErrorStreamCorrupt"; |
| // At least SoftAAC2.cpp can report a recoverable error this way in |
| // ADTS mode. Recoverable in the stream failure sense, not in the |
| // continues to process normally sense that the OMX spec talks about |
| // being "typical". Not typical in practice with these codecs... |
| recoverable = true; |
| break; |
| case OMX_ErrorPortsNotCompatible: |
| error_string = "OMX_ErrorPortsNotCompatible"; |
| break; |
| case OMX_ErrorResourcesLost: |
| error_string = "OMX_ErrorResourcesLost"; |
| break; |
| case OMX_ErrorNoMore: |
| error_string = "OMX_ErrorNoMore"; |
| break; |
| case OMX_ErrorVersionMismatch: |
| error_string = "OMX_ErrorVersionMismatch"; |
| break; |
| case OMX_ErrorNotReady: |
| error_string = "OMX_ErrorNotReady"; |
| break; |
| case OMX_ErrorTimeout: |
| error_string = "OMX_ErrorTimeout"; |
| break; |
| case OMX_ErrorSameState: |
| error_string = "OMX_ErrorSameState"; |
| break; |
| case OMX_ErrorResourcesPreempted: |
| error_string = "OMX_ErrorResourcesPreempted"; |
| break; |
| case OMX_ErrorPortUnresponsiveDuringAllocation: |
| error_string = "OMX_ErrorPortUnresponsiveDuringAllocation"; |
| break; |
| case OMX_ErrorPortUnresponsiveDuringDeallocation: |
| error_string = "OMX_ErrorPortUnresponsiveDuringDeallocation"; |
| break; |
| case OMX_ErrorPortUnresponsiveDuringStop: |
| error_string = "OMX_ErrorPortUnresponsiveDuringStop"; |
| break; |
| case OMX_ErrorIncorrectStateTransition: |
| error_string = "OMX_ErrorIncorrectStateTransition"; |
| break; |
| case OMX_ErrorIncorrectStateOperation: |
| error_string = "OMX_ErrorIncorrectStateOperation"; |
| break; |
| case OMX_ErrorUnsupportedSetting: |
| error_string = "OMX_ErrorUnsupportedSetting"; |
| break; |
| case OMX_ErrorUnsupportedIndex: |
| error_string = "OMX_ErrorUnsupportedIndex"; |
| break; |
| case OMX_ErrorBadPortIndex: |
| error_string = "OMX_ErrorBadPortIndex"; |
| break; |
| case OMX_ErrorPortUnpopulated: |
| error_string = "OMX_ErrorPortUnpopulated"; |
| break; |
| case OMX_ErrorComponentSuspended: |
| error_string = "OMX_ErrorComponentSuspended"; |
| break; |
| case OMX_ErrorDynamicResourcesUnavailable: |
| error_string = "OMX_ErrorDynamicResourcesUnavailable"; |
| break; |
| case OMX_ErrorMbErrorsInFrame: |
| error_string = "OMX_ErrorMbErrorsInFrame"; |
| break; |
| case OMX_ErrorFormatNotDetected: |
| error_string = "OMX_ErrorFormatNotDetected"; |
| break; |
| case OMX_ErrorContentPipeOpenFailed: |
| error_string = "OMX_ErrorContentPipeOpenFailed"; |
| break; |
| case OMX_ErrorContentPipeCreationFailed: |
| error_string = "OMX_ErrorContentPipeCreationFailed"; |
| break; |
| case OMX_ErrorSeperateTablesUsed: |
| error_string = "OMX_ErrorSeperateTablesUsed"; |
| break; |
| case OMX_ErrorTunnelingUnsupported: |
| error_string = "OMX_ErrorTunnelingUnsupported"; |
| break; |
| default: |
| error_string = "UNRECOGNIZED ERROR"; |
| } |
| printf("OMX_EventError error: %s\n", error_string); |
| if (!recoverable) { |
| Exit( |
| "error is not known to be recoverable - exiting - error_string: " |
| "%s\n", |
| error_string); |
| } |
| assert(recoverable); |
| // To recover, we need to get over to StreamControl domain, and we do |
| // care whether the stream is the same stream as when this error was |
| // delivered. For this snap of the stream_lifetime_ordinal to be |
| // meaningful we rely on the current thread to be the codec's processing |
| // thread for all recoverable errors. |
| // |
| // TODO(dustingreen): See if we can find a good way to check that we're |
| // on that thread, and if not, treat the error as not recoverable after |
| // all. |
| uint64_t stream_lifetime_ordinal; |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| stream_lifetime_ordinal = stream_lifetime_ordinal_; |
| } |
| PostSerial(stream_control_dispatcher_, [this, stream_lifetime_ordinal] { |
| onOmxStreamFailed(stream_lifetime_ordinal); |
| }); |
| } |
| break; |
| case OMX_EventMark: |
| // detected a buffer mark |
| printf("OMX_EventMark\n"); |
| // Before anyone gets excited, OMX buffer marking doesn't actually do |
| // anything in of the codecs we're interested in. |
| assert(false && "we never mark buffers"); |
| break; |
| case OMX_EventPortSettingsChanged: { |
| // This is the fun one. |
| |
| // For input port, we rely on the fact that OMX SW codecs, driven the |
| // way omx_codec_runner drives them, don't change the input port |
| // definition's nBufferSize (because we don't drive that way) or |
| // nBufferCountMin (because it probably doesn't change this field ever), |
| // and also don't notify via this event even if they were to change the |
| // input port definition (if we got out of sync on nBufferSize, that |
| // would be unfortunate; the Codec protocol doesn't have any way to |
| // force the Codec client to re-configure input, by design). |
| assert(nData1 == kOutput); |
| |
| bool output_re_config_required = |
| ((nData2 == 0) || (nData2 == OMX_IndexParamPortDefinition)); |
| VLOGF("OMX_EventPortSettingsChanged - output_re_config_required: %d\n", |
| output_re_config_required); |
| |
| // 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 |
| break; |
| } |
| |
| // We have an OMX_EventPortSettingsChanged that does demand output |
| // buffer re-config before more output data. |
| 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::unique_lock<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. |
| omx_meh_output_buffer_constraints_version_ordinal_ = |
| port_settings_[kOutput]->buffer_constraints_version_ordinal; |
| // 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 |
| PostSerial( |
| stream_control_dispatcher_, |
| [this, stream_lifetime_ordinal = local_stream_lifetime_ordinal] { |
| onOmxEventPortSettingsChanged(stream_lifetime_ordinal); |
| }); |
| } break; |
| case OMX_EventBufferFlag: |
| // detected and EOS (end of stream) |
| // |
| // According to the EOS spec this is generated by a sink that doesn't |
| // propagate anything downstream when the sink is done processing an EOS |
| // that arrived at the sink. None of the OMX SW codecs do this, |
| // presumably because none of them are sinks. If this were to arrive, it |
| // would make no sense, so don't ignore. |
| Exit("OMX_EventBufferFlag is unexpected"); |
| break; |
| case OMX_EventResourcesAcquired: |
| // component wanting to go to StateIdle |
| // |
| // None of the OMX SW codecs do this. |
| Exit("OMX_EventResouresAcquired is unexpected"); |
| break; |
| case OMX_EventComponentResumed: |
| // due to reacquistion of resources |
| // |
| // None of the OMX SW codecs do this. |
| Exit("OMX_EventComponentResumed is unexpected"); |
| break; |
| case OMX_EventDynamicResourcesAvailable: |
| // acquired previously unavailable dynamic resources |
| // |
| // None of the OMX SW codecs do this. |
| Exit("OMX_EventDynamicResourcesAvailable is unexpected"); |
| break; |
| case OMX_EventPortFormatDetected: |
| // deteted a supported format |
| // |
| // None of the OMX SW codecs do this. |
| Exit("OMX_EventPortFormatDetected is unexpected"); |
| break; |
| default: |
| // Despite getting annoyed for unexpected events above, we ignore any |
| // events that we don't even recognize the number of. |
| // |
| // TODO(dustingreen): See if we hit any of these, and if not, consider |
| // just failing here since ... we really don't expect these. |
| Exit("OMX_Event unrecognized and ignored."); |
| break; |
| } |
| return OMX_ErrorNone; |
| } |
| |
| void OmxCodecRunner::onOmxEventPortSettingsChanged( |
| uint64_t stream_lifetime_ordinal) { |
| assert(thrd_current() == stream_control_thread_); |
| 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; |
| } |
| assert(stream_lifetime_ordinal == stream_lifetime_ordinal_); |
| |
| is_omx_recycle_enabled_ = false; |
| |
| // 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. |
| StartIgnoringClientOldOutputConfigLocked(); |
| |
| // Tell the codec to disable its output port, because that's how OMX deals |
| // with an output format change. |
| OmxOutputStartSetEnabledLocked(false); |
| // We can assert this because we still have lock_ and we've only posted the |
| // disable so far. |
| assert(omx_output_enabled_ && !omx_output_enabled_desired_); |
| |
| OmxWaitForOutputBuffersDoneReturning(lock); |
| |
| OmxFreeAllPortBufferHeaders(lock, kOutput); |
| |
| // State of omx_output_enabled_ in flux here (well, actually it's probably |
| // already false based on how OMX just used this thread during FreeHeader() |
| // just above to call back EventHandler(), but we don't assume that particular |
| // behavior so we don't assert what omx_output_enabled_ is here. |
| assert(!omx_output_enabled_desired_); |
| OmxWaitForOutputEnableStateChangeDone(lock); |
| assert(!omx_output_enabled_ && !omx_output_enabled_desired_); |
| |
| EnsureBuffersNotConfiguredLocked(kOutput); |
| |
| 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 (!stream_->future_discarded() && !IsOutputConfiguredLocked()) { |
| wake_stream_control_.wait(lock); |
| } |
| |
| if (stream_->future_discarded()) { |
| // We already know how to handle this case, and |
| // omx_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; |
| } |
| |
| // Ensure OMX has the latest buffer count (nBufferCountActual) for the output |
| // port. |
| // |
| // This will only actually update the output port config. The input port |
| // config won't have changed since SetInputBufferSettings() with an active |
| // stream is prohibited (and that is enforced elsewhere). |
| EnsureOmxBufferCountCurrent(lock); |
| |
| // Re-enable output port. |
| |
| OmxOutputStartSetEnabledLocked(true); |
| |
| // allocate OMX headers for output |
| OmxPortUseBuffers(lock, kOutput); |
| |
| OmxWaitForOutputEnableStateChangeDone(lock); |
| |
| // In this path, all output packets are free and with the Codec from a |
| // protocol point of view (not under client control because we have yet to |
| // deliver any packet under the new buffer_lifetime_ordinal). |
| for (auto& output_packet : all_packets_[kOutput]) { |
| assert(packet_free_bits_[kOutput][output_packet->packet_index()]); |
| OmxFillThisBufferLocked(output_packet->omx_header()); |
| } |
| is_omx_recycle_enabled_ = true; |
| |
| VLOGF("Done with mid-stream format change.\n"); |
| } |
| |
| // 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_[kOutput]. |
| void OmxCodecRunner::StartIgnoringClientOldOutputConfigLocked() { |
| // buffer_constraints_action_required true processing is only performed on the |
| // StreamControl ordering domain (except during setup). |
| assert(!is_setup_done_ || thrd_current() == stream_control_thread_); |
| |
| // The buffer_lifetime_ordinal_[kOutput] 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_[kOutput]. |
| if (buffer_lifetime_ordinal_[kOutput] % 2 == 1) { |
| assert(buffer_lifetime_ordinal_[kOutput] % 2 == 1); |
| assert(buffer_lifetime_ordinal_[kOutput] == |
| port_settings_[kOutput]->buffer_lifetime_ordinal); |
| buffer_lifetime_ordinal_[kOutput]++; |
| assert(buffer_lifetime_ordinal_[kOutput] % 2 == 0); |
| assert(buffer_lifetime_ordinal_[kOutput] == |
| port_settings_[kOutput]->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_[kOutput] = |
| next_output_buffer_constraints_version_ordinal_; |
| } |
| |
| void OmxCodecRunner::OmxFreeAllBufferHeaders( |
| std::unique_lock<std::mutex>& lock) { |
| for (Port port = kFirstPort; port < kPortCount; port++) { |
| OmxFreeAllPortBufferHeaders(lock, port); |
| } |
| // And same for the omx_input_packet_oob_ |
| OmxFreeBufferHeader(lock, kInput, omx_input_packet_oob_.get()); |
| // And same for the omx_input_packet_eos_ |
| OmxFreeBufferHeader(lock, kInput, omx_input_packet_eos_.get()); |
| } |
| |
| void OmxCodecRunner::OmxFreeAllPortBufferHeaders( |
| std::unique_lock<std::mutex>& lock, Port port) { |
| for (auto& packet : all_packets_[port]) { |
| OmxFreeBufferHeader(lock, port, packet.get()); |
| } |
| } |
| |
| void OmxCodecRunner::OmxWaitForOutputBuffersDoneReturning( |
| std::unique_lock<std::mutex>& lock) { |
| // We only actually call this when !omx_output_enabled_desired_, but there |
| // wouldn't be any harm in calling it during move out of executing, so allow |
| // that. |
| assert(!omx_output_enabled_desired_ || |
| omx_state_desired_ != OMX_StateExecuting); |
| while (omx_output_buffer_with_omx_count_) { |
| omx_output_buffers_done_returning_condition_.wait(lock); |
| } |
| } |
| |
| void OmxCodecRunner::onOmxStreamFailed(uint64_t stream_lifetime_ordinal) { |
| // When we come in here, we've just landed on the StreamControl domain, but |
| // nothing has stopped the client from moving on to a new stream before we |
| // got here. Given how the relevant OMX codecs refuse to process any more |
| // stream data of the stream when they fail with a "recoverable" error, it's |
| // reasonable to just ignore any stale stream failures, since the stream |
| // failure would only result in the client moving on to a new stream anyway, |
| // so if that's already happened we can ignore the old stream failure. |
| // |
| // We prefer to check the state of things on the StreamControl domain since |
| // this domain is in charge of stream transitions, so it's the easiest to |
| // reason about why checking here is safe. It would probably also be possible |
| // to check robustly on the Output ordering domain and avoid creating any |
| // invalid message orderings, but checking here is more obviously ok. |
| assert(thrd_current() == stream_control_thread_); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| assert(stream_lifetime_ordinal <= stream_lifetime_ordinal_); |
| if (stream_lifetime_ordinal < stream_lifetime_ordinal_) { |
| // ignore - old stream is already gone, so OMX codec is already reset. No |
| // point in telling the client about the failure of an old stream. |
| return; |
| } |
| assert(stream_lifetime_ordinal == stream_lifetime_ordinal_); |
| // 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 dispatcher, in the sense that the client may still be |
| // sending input data, and the OMX codec is expected to not reject that |
| // input data. |
| // |
| // There's not actually any need to track that the stream failed anywhere |
| // in the OmxCodecRunner. The client needs to move on from the failed |
| // stream to a new stream, or close the Codec channel. |
| printf("onOmxStreamFailed() - stream_lifetime_ordinal: %lu\n", |
| stream_lifetime_ordinal); |
| if (!enable_on_stream_failed_) { |
| Exit( |
| "onOmxStreamFailed() with a client that didn't send " |
| "EnableOnOmxStreamFailed(), so closing the Codec channel instead."); |
| } |
| PostSerial(fidl_dispatcher_, [this, stream_lifetime_ordinal] { |
| binding_->events().OnStreamFailed(stream_lifetime_ordinal); |
| }); |
| } // ~lock |
| } |
| |
| // OMX is freeing an input packet. |
| // |
| // Called on InputData ordering domain. A call from StreamControl would also |
| // work as long as the call into OMX that triggers this (if any) is made without |
| // lock_ held (which should be the case). I don't think |
| // SimpleSoftOMXComponent.cpp will call EmptyBufferDone using an incoming |
| // thread, but OMX spec doesn't specify AFAICT. |
| OMX_ERRORTYPE OmxCodecRunner::EmptyBufferDone( |
| OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { |
| auto* packet = reinterpret_cast<Packet*>(pBuffer->pAppPrivate); |
| assert(packet->omx_header() == pBuffer); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| |
| // We don't care if omx_state_desired_ is OMX_StateExecuting or not. OMX |
| // may be giving back a buffer which was actually "emptied", or may be |
| // giving back a buffer that OMX will not be emptying because we're moving |
| // from OMX executing to OMX idle. Either way, the OMX input buffer is |
| // free, the corresponding input packet is free, and the client should be |
| // told about the free packet. |
| |
| // If the client did a CloseCurrentStream() with release_input_buffers true, |
| // then the server is permitted to optimize away sending the free buffers |
| // back to the client, but at the moment this server doesn't optimize that |
| // away. |
| |
| // Because re-configuring input is only legal when there's no current |
| // stream, and stopping a stream at OMX layer involves OMX giving back all |
| // the OMX buffers (our packets) using this method first, this method can't |
| // be called for a packet with mis-matched buffer_lifetime_ordinal. |
| assert(packet->buffer_lifetime_ordinal() == |
| port_settings_[kInput]->buffer_lifetime_ordinal); |
| assert(buffer_lifetime_ordinal_[kInput] == |
| port_settings_[kInput]->buffer_lifetime_ordinal); |
| |
| // If the free packet is the omx_input_packet_oob_, don't tell the client |
| // about that packet/buffer, because it's not actually a packet at the Codec |
| // interface layer. |
| if (packet == omx_input_packet_oob_.get()) { |
| // ok, it's free - this is likely to happen before the stream is closed, |
| // but must happen by the time the stream is closing and the OMX codec |
| // is moving from OMX executing to OMX idle. |
| omx_input_packet_oob_free_ = true; |
| // Don't tell the client about this packet; it's not an official |
| // Packet. |
| goto oob_free_notify_outside_lock; |
| } |
| |
| // omx_input_packet_eos_ is handled similarly to omx_input_packet_oob_. |
| if (packet == omx_input_packet_eos_.get()) { |
| omx_input_packet_eos_free_ = true; |
| return OMX_ErrorNone; |
| } |
| |
| // Free/busy coherency from Codec interface to OMX doesn't involve trusting |
| // the client, so assert we're doing it right server-side. |
| assert(!packet_free_bits_[kInput][packet->packet_index()]); |
| packet_free_bits_[kInput][packet->packet_index()] = true; |
| SendFreeInputPacketLocked(fuchsia::media::PacketHeader{ |
| .buffer_lifetime_ordinal = packet->buffer_lifetime_ordinal(), |
| .packet_index = packet->packet_index()}); |
| } // ~lock |
| return OMX_ErrorNone; |
| oob_free_notify_outside_lock:; |
| omx_input_packet_oob_free_condition_.notify_all(); |
| return OMX_ErrorNone; |
| } |
| |
| void OmxCodecRunner::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. |
| assert(thrd_current() == stream_control_thread_ || |
| thrd_current() != fidl_thread_); |
| // We only send using the FIDL thread. |
| PostSerial(fidl_dispatcher_, [this, header = std::move(header)] { |
| binding_->events().OnFreeInputPacket(std::move(header)); |
| }); |
| } |
| |
| // OMX is either emitting some output data, or just handing us back an OMX |
| // buffer that OMX is done with. |
| OMX_ERRORTYPE OmxCodecRunner::FillBufferDone( |
| OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer) { |
| auto* packet = reinterpret_cast<Packet*>(pBuffer->pAppPrivate); |
| assert(packet->omx_header() == pBuffer); |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| assert(stream_); |
| // We don't update packet_free_bits_[kOutput] for this, because the |
| // packets aren't really free or busy during this - it's more that they're |
| // not allocated. Instead we use a count. |
| omx_output_buffer_with_omx_count_--; |
| if (!omx_output_enabled_desired_ || |
| omx_state_desired_ != OMX_StateExecuting) { |
| VLOGF( |
| "FillBufferDone() short circuit because OMX just returning the " |
| "buffer\n"); |
| |
| // OMX can be giving us an actual output buffer under this path, or can be |
| // just trying to give us back a buffer without any data in it. |
| // |
| // We're not supposed to give this buffer back to OMX; we're trying to |
| // disable the output port or move the OMX codec back to Loaded state. |
| // |
| // This is only able to be checked this way because we make sure that |
| // calls to FillThisBuffer() always set the buffer to nFilledLen = 0 |
| // before sending the buffer to the codec. In addition, we can only check |
| // that nFilledLen == 0 if !omx_output_enabled_desired_, because only in |
| // that case do we know that the OMX codec itself is yet aware of the fact |
| // that we're returning output buffers without filling them, because |
| // because only in this case did the OMX codec initiate the change. |
| if (!omx_output_enabled_desired_ && |
| packet->omx_header()->nFilledLen != 0) { |
| Exit( |
| "OMX codec seems to be emitting a non-empty output buffer during " |
| "mid-stream output config change"); |
| } |
| |
| // Only need to notify re. buffers done returning if we're trying to |
| // disable port. We also notify when moving out of executing state but |
| // nobody actually cares about that notify since in that path we can just |
| // wait to reach OMX_StateIdle instead. |
| if (!omx_output_buffer_with_omx_count_) { |
| // notify outside lock - none of the output buffers are with OMX - some |
| // can still be with the client though. |
| goto notify_buffers_done_returning_outside_lock; |
| } |
| return OMX_ErrorNone; |
| } |
| auto recycle_packet = fit::defer([this, pBuffer] { |
| // A non-EOS zero-length buffer is allowed by OMX spec AFAICT, but we |
| // don't want to allow this in the Codec interface, so hand this buffer |
| // back to OMX so OMX can try filling it again. |
| // |
| // Similarly, when we're converting from a zero-length EOS packet to |
| // OnOutputEndOfStream(), the client never sees the packet, so hand the |
| // buffer back to OMX. |
| // |
| // To avoid assuming it's safe to call OMX on this thread directly with |
| // lock_ held (OMX spec basically implies it's not safe in general even |
| // though it would be safe assuming SimpleSoftOMXComponent.cpp), we queue |
| // the FillThisBuffer() instead. We always queue SendCommand() the same |
| // way, so we know that any SendCommand() that would change the OMX state |
| // so that calling FillThisBuffer() is no longer valid will be called |
| // after this FillThisBuffer() has already been called. |
| // |
| // We don't queue EmptyThisBuffer() from StreamControl to Output domain, |
| // but that's ok because StreamControl always synchronously waits for |
| // SendCommand() (which we do queue) to be done before StreamControl uses |
| // EmptyThisBuffer() to queue more input to the OMX codec, and all input |
| // is on StreamControl domain, so ordering is preserved between |
| // EmptyThisBuffer() and SendCommand(), in both directions. |
| printf("FillBufferDone() back to OMX without going to client\n"); |
| OmxFillThisBufferLocked(pBuffer); |
| }); |
| // Because we already checked that both "desired" ones are set this way, and |
| // because when moving to executing state or enabling the port, we don't |
| assert(omx_state_ == OMX_StateExecuting && |
| omx_state_desired_ == OMX_StateExecuting && omx_output_enabled_ && |
| omx_output_enabled_desired_); |
| // We don't want the Codec interface to send the client empty packets, |
| // except for the empty end_of_stream packet after the last stream data, so |
| // we take different action here depending on what OMX is handing us. |
| // |
| // If OMX is emitting an empty packet without EOS set, we want to send the |
| // packet back in to OMX, but not on this thread. |
| bool is_eos = ((pBuffer->nFlags & OMX_BUFFERFLAG_EOS) != 0); |
| if (pBuffer->nFilledLen != 0) { |
| // The output packet gets recycled later by the client. |
| recycle_packet.cancel(); |
| uint64_t timestamp_ish = 0; |
| if (decoder_params_->promise_separate_access_units_on_input) { |
| timestamp_ish = pBuffer->nTimeStamp; |
| } |
| packet_free_bits_[kOutput][packet->packet_index()] = false; |
| PostSerial( |
| fidl_dispatcher_, |
| [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 = pBuffer->nOffset, |
| .valid_length_bytes = pBuffer->nFilledLen, |
| // TODO(dustingreen): verify whether other relevant codecs |
| // mess with this value - set to zero if codec wasn't |
| // created with promise_separate_access_units_on_input. |
| .timestamp_ish = timestamp_ish, |
| // TODO(dustingreen): Figure out what to do for other codec |
| // types here, especially encoders. Might be able to be |
| // true always on output for OMX, hopefully. |
| .start_access_unit = decoder_params_ ? true : false, |
| .known_end_access_unit = decoder_params_ ? true : false, |
| }] { |
| binding_->events().OnOutputPacket(std::move(p), false, false); |
| }); |
| } |
| if (is_eos) { |
| VLOGF("sending OnOutputEndOfStream()\n"); |
| PostSerial(fidl_dispatcher_, |
| [this, stream_lifetime_ordinal = stream_lifetime_ordinal_] { |
| // OMX in AOSP in practice appears to have zero ways to |
| // report mid-stream failures that don't fail the whole |
| // stream. I looked at both OMX_BUFFERFLAG_DATACORRUPT (OMX |
| // spec doesn't necessarily provide for this to be used on |
| // output buffers, and sparse if any usage by AOSP OMX |
| // codecs) and OMX_ErrorStreamCorrupt (OMX spec sounds |
| // somewhat promising if a bit wishy-washy, but in practice |
| // all the codecs stop processing the stream so it's |
| // effectively OnOmxStreamFailed()). I see no other |
| // potential ways in OMX for an OMX codec to report |
| // non-stream-fatal errors. I'm not sure to what degree |
| // various AOSP OMX codecs might silently tolerate corrupted |
| // input data. Some AOSP OMX codecs seem to actively try to |
| // detect corrupted input data and fail the stream (as in, |
| // require moving the OMX codec to Loaded state to achieve a |
| // reset before any more data will get processed). Those |
| // codecs do appear to report the problem via OMX_EventError |
| // with OMX_ErrorUndefined or OMX_ErrorStreamCorrupt, but |
| // refuse to process more input data until the codec goes |
| // through loaded state, so we treat those as |
| // OnOmxStreamFailed(), not error_detected_before. We don't |
| // currently try to compensate for OMX codec behavior by |
| // tracking specific input data, re-queuing input data that |
| // had been queued to a failed OMX stream, hide the stream |
| // failure from the Codec interface, etc. |
| bool error_detected_before = false; |
| binding_->events().OnOutputEndOfStream( |
| stream_lifetime_ordinal, error_detected_before); |
| }); |
| } |
| // ~recycle_packet may recycle if not already cancelled - this happens if |
| // OMX outputs a zero-length buffer, whether EOS or not. |
| } // ~lock |
| return OMX_ErrorNone; |
| notify_buffers_done_returning_outside_lock:; |
| omx_output_buffers_done_returning_condition_.notify_all(); |
| return OMX_ErrorNone; |
| } |
| |
| void OmxCodecRunner::OmxFillThisBufferLocked(OMX_BUFFERHEADERTYPE* header) { |
| // This is the only reason we expect to see nFilledLen == 0 when disabling the |
| // output port an getting buffers back from the codec via FillBufferDone() |
| // callback. It's also at least polite to the codec, and _maybe_ even |
| // required by some - but no proof of that. |
| header->nFilledLen = 0; |
| // rest of these are paranoia |
| header->nOffset = 0; |
| header->nTimeStamp = 0; |
| header->nFlags = 0; |
| omx_output_buffer_with_omx_count_++; |
| // Get out from under lock_ before calling OMX. We need to queue to OMX under |
| // the lock_ though, to ensure proper ordering with respect to SendCommand, |
| // which is also always queued. Since we also always queue SendCommand, the |
| // header will remain valid long enough. This is true for the same reason it |
| // would be true if we were only talking about the queueing that already |
| // exists internal to SimpleSoftOMXComponent.cpp. We queue despite that |
| // queueing because the OMX spec says the codec is allowed to call us back on |
| // the same thread we call in on. |
| PostSerial(fidl_dispatcher_, [this, header] { |
| OMX_ERRORTYPE omx_result = |
| omx_component_->FillThisBuffer(omx_component_, header); |
| if (omx_result != OMX_ErrorNone) { |
| Exit("FillThisBuffer() failed: %d", omx_result); |
| } |
| }); |
| } |
| |
| void OmxCodecRunner::onOmxStateSetComplete(OMX_STATETYPE state_reached) { |
| if (state_reached != OMX_StateLoaded && state_reached != OMX_StateIdle && |
| state_reached != OMX_StateExecuting) { |
| Exit( |
| "onOmxStateSetComplete() state_reached unexpected - exiting - " |
| "state_reached: %d\n", |
| state_reached); |
| } |
| { |
| std::unique_lock<std::mutex> lock(lock_); |
| omx_state_ = state_reached; |
| } |
| omx_state_changed_.notify_all(); |
| } |
| |
| bool OmxCodecRunner::IsStreamActiveLocked() { |
| return stream_lifetime_ordinal_ % 2 == 1; |
| } |
| |
| void OmxCodecRunner::EnsureBuffersNotConfiguredLocked(Port 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_. |
| |
| assert(thrd_current() == stream_control_thread_ || |
| (port == kOutput && (thrd_current() == fidl_thread_))); |
| assert(omx_state_ == omx_state_desired_); |
| assert(omx_state_ == OMX_StateLoaded || |
| (OMX_StateExecuting && !omx_output_enabled_desired_ && |
| !omx_output_enabled_ && (port == kOutput))); |
| // For mid-stream output config change, the caller is responsible for ensuring |
| // that OMX headers have been freed first. |
| assert(all_packets_[port].empty() || !all_packets_[port][0]->omx_header()); |
| all_packets_[port].resize(0); |
| if (port == kInput) { |
| omx_input_packet_oob_.reset(nullptr); |
| omx_input_buffer_oob_.reset(nullptr); |
| omx_input_packet_eos_.reset(nullptr); |
| } |
| all_buffers_[port].resize(0); |
| packet_free_bits_[port].resize(0); |
| assert(all_packets_[port].empty()); |
| assert(all_buffers_[port].empty()); |
| assert(packet_free_bits_[port].empty()); |
| } |
| |
| void OmxCodecRunner::CheckOldBufferLifetimeOrdinalLocked( |
| Port 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) { |
| Exit( |
| "CheckOldBufferLifetimeOrdinalLocked() - buffer_lifetime_ordinal must " |
| "be odd - exiting\n"); |
| } |
| if (buffer_lifetime_ordinal > protocol_buffer_lifetime_ordinal_[port]) { |
| Exit( |
| "client sent new buffer_lifetime_ordinal in message type that doesn't " |
| "allow new buffer_lifetime_ordinals"); |
| } |
| } |
| |
| void OmxCodecRunner::CheckStreamLifetimeOrdinalLocked( |
| uint64_t stream_lifetime_ordinal) { |
| if (stream_lifetime_ordinal % 2 != 1) { |
| Exit("stream_lifetime_ordinal must be odd.\n"); |
| } |
| if (stream_lifetime_ordinal < stream_lifetime_ordinal_) { |
| Exit("client sent stream_lifetime_ordinal that went backwards"); |
| } |
| } |
| |
| void OmxCodecRunner::OmxTryRecycleOutputPacketLocked( |
| OMX_BUFFERHEADERTYPE* header) { |
| if (!is_omx_recycle_enabled_) { |
| // We'll rely on packet_free_bits_ to track which packets need to be sent |
| // back to OMX with FillThisBuffer() just after we've finished moving the |
| // OMX codec back to a suitable state. |
| return; |
| } |
| // We can assert all these things whenever is_omx_recycle_enabled_ is true. |
| // |
| // However, the reverse is not a valid statement, because we don't re-enable |
| // is_omx_recycle_enabled_ until we're back under lock_ on StreamControl |
| // ordering domain. Specifically, this condition becomes true on an OMX |
| // thread, followed by lock_ release, followed by lock_ acquire on |
| // StreamControl, followed by sending any packet_free_bits_ true packets back |
| // to OMX, followed by setting is_omx_recycle_enabled_ to true. |
| assert(omx_state_ == OMX_StateExecuting && |
| omx_state_desired_ == OMX_StateExecuting && omx_output_enabled_ && |
| omx_output_enabled_desired_); |
| // The caller only calls this method if the output buffers are configured at |
| // codec level, and for now at least, configured at codec level == configured |
| // at OMX level. |
| assert(IsOutputConfiguredLocked()); |
| OmxFillThisBufferLocked(header); |
| } |
| |
| fuchsia::media::AudioChannelId |
| OmxCodecRunner::AudioChannelIdFromOmxAudioChannelType( |
| OMX_AUDIO_CHANNELTYPE omx_audio_channeltype) { |
| uint32_t input_channeltype = omx_audio_channeltype; |
| if (input_channeltype > kOmxAudioChannelTypeSupportedMax || |
| input_channeltype < kOmxAudioChannelTypeSupportedMin) { |
| Exit("unsuppored OMX_AUDIO_CHANNELTYPE - exiting - value: %d\n", |
| omx_audio_channeltype); |
| } |
| return kOmxAudioChannelTypeToAudioChannelId[input_channeltype]; |
| } |
| |
| void OmxCodecRunner::ValidateBufferSettingsVsConstraints( |
| Port port, const fuchsia::media::StreamBufferSettings& settings, |
| const fuchsia::media::StreamBufferConstraints& constraints) { |
| if (settings.packet_count_for_server < |
| constraints.packet_count_for_server_min) { |
| Exit("packet_count_for_server < packet_count_for_server_min"); |
| } |
| if (settings.packet_count_for_server > |
| constraints.packet_count_for_server_max) { |
| Exit("packet_count_for_server > packet_count_for_server_max"); |
| } |
| if (settings.packet_count_for_client > |
| constraints.packet_count_for_client_max) { |
| Exit("packet_count_for_client > packet_count_for_client_max"); |
| } |
| if (settings.per_packet_buffer_bytes < |
| constraints.per_packet_buffer_bytes_min) { |
| Exit( |
| "settings.per_packet_buffer_bytes < " |
| "constraints.per_packet_buffer_bytes_min - exiting - port: %u " |
| "settings: %u constraint: %u", |
| port, settings.per_packet_buffer_bytes, |
| constraints.per_packet_buffer_bytes_min); |
| } |
| if (settings.per_packet_buffer_bytes > |
| constraints.per_packet_buffer_bytes_max) { |
| Exit( |
| "settings.per_packet_buffer_bytes > " |
| "constraints.per_packet_buffer_bytes_max"); |
| } |
| if (settings.single_buffer_mode && !constraints.single_buffer_mode_allowed) { |
| Exit( |
| "settings.single_buffer_mode && " |
| "!constraints.single_buffer_mode_allowed"); |
| } |
| } |
| |
| void OmxCodecRunner::PostSerial(async_dispatcher_t* dispatcher, |
| fit::closure to_run) { |
| zx_status_t post_result = async::PostTask(dispatcher, std::move(to_run)); |
| if (post_result != ZX_OK) { |
| Exit("async::PostTask() failed - post_result %d", post_result); |
| } |
| } |
| |
| OmxCodecRunner::Buffer::Buffer(OmxCodecRunner* parent, Port port, |
| fuchsia::media::StreamBuffer buffer) |
| : parent_(parent), port_(port), buffer_(std::move(buffer)) { |
| // nothing else to do here |
| } |
| |
| OmxCodecRunner::Buffer::~Buffer() { |
| if (buffer_base_) { |
| zx_status_t res = zx::vmar::root_self()->unmap( |
| reinterpret_cast<uintptr_t>(buffer_base()), buffer_size()); |
| if (res != ZX_OK) { |
| parent_->Exit( |
| "OmxCodecRunner::Buffer::~Buffer() failed to unmap() Buffer"); |
| } |
| buffer_base_ = nullptr; |
| } |
| } |
| |
| bool OmxCodecRunner::Buffer::Init(bool input_require_write) { |
| assert(!input_require_write || port_ == kInput); |
| // Map the VMO in the local address space. |
| uintptr_t tmp; |
| zx_vm_option_t flags = ZX_VM_PERM_READ; |
| if (port_ == kOutput || input_require_write) { |
| flags |= ZX_VM_PERM_WRITE; |
| } |
| zx_status_t res = zx::vmar::root_self()->map( |
| 0, buffer_.data.vmo().vmo_handle, buffer_.data.vmo().vmo_usable_start, |
| buffer_.data.vmo().vmo_usable_size, flags, &tmp); |
| if (res != ZX_OK) { |
| printf("Failed to map %zu byte buffer vmo (res %d)\n", |
| buffer_.data.vmo().vmo_usable_size, res); |
| return false; |
| } |
| buffer_base_ = reinterpret_cast<uint8_t*>(tmp); |
| return true; |
| } |
| |
| uint64_t OmxCodecRunner::Buffer::buffer_lifetime_ordinal() const { |
| return buffer_.buffer_lifetime_ordinal; |
| } |
| |
| uint32_t OmxCodecRunner::Buffer::buffer_index() const { |
| return buffer_.buffer_index; |
| } |
| |
| uint8_t* OmxCodecRunner::Buffer::buffer_base() const { |
| assert(buffer_base_ && "Shouldn't be using if Init() didn't work."); |
| return buffer_base_; |
| } |
| |
| size_t OmxCodecRunner::Buffer::buffer_size() const { |
| return buffer_.data.vmo().vmo_usable_size; |
| } |
| |
| OmxCodecRunner::Packet::Packet(uint64_t buffer_lifetime_ordinal, |
| uint32_t packet_index, Buffer* buffer) |
| : buffer_lifetime_ordinal_(buffer_lifetime_ordinal), |
| packet_index_(packet_index), |
| buffer_(buffer) { |
| // nothing else to do here |
| } |
| |
| uint64_t OmxCodecRunner::Packet::buffer_lifetime_ordinal() const { |
| return buffer_lifetime_ordinal_; |
| } |
| |
| uint32_t OmxCodecRunner::Packet::packet_index() const { return packet_index_; } |
| |
| const OmxCodecRunner::Buffer& OmxCodecRunner::Packet::buffer() const { |
| return *buffer_; |
| } |
| |
| // 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 OmxCodecRunner::Packet::SetOmxHeader(OMX_BUFFERHEADERTYPE* omx_header) { |
| omx_header_ = omx_header; |
| } |
| |
| OMX_BUFFERHEADERTYPE* OmxCodecRunner::Packet::omx_header() const { |
| return omx_header_; |
| } |
| |
| OmxCodecRunner::Stream::Stream(uint64_t stream_lifetime_ordinal) |
| : stream_lifetime_ordinal_(stream_lifetime_ordinal) { |
| // nothing else to do here |
| } |
| |
| uint64_t OmxCodecRunner::Stream::stream_lifetime_ordinal() { |
| return stream_lifetime_ordinal_; |
| } |
| |
| void OmxCodecRunner::Stream::SetFutureDiscarded() { |
| assert(!future_discarded_); |
| future_discarded_ = true; |
| } |
| |
| bool OmxCodecRunner::Stream::future_discarded() { return future_discarded_; } |
| |
| void OmxCodecRunner::Stream::SetFutureFlushEndOfStream() { |
| assert(!future_flush_end_of_stream_); |
| future_flush_end_of_stream_ = true; |
| } |
| |
| bool OmxCodecRunner::Stream::future_flush_end_of_stream() { |
| return future_flush_end_of_stream_; |
| } |
| |
| OmxCodecRunner::Stream::~Stream() { |
| VLOGF("~Stream() stream_lifetime_ordinal: %lu\n", stream_lifetime_ordinal_); |
| } |
| |
| void OmxCodecRunner::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* |
| OmxCodecRunner::Stream::input_format_details() { |
| return input_format_details_.get(); |
| } |
| |
| void OmxCodecRunner::Stream::SetOobConfigPending(bool pending) { |
| // SetOobConfigPending(true) is legal regardless of current state, but |
| // SetOobConfigPending(false) is only legal if the state is currently true. |
| assert(pending || oob_config_pending_); |
| oob_config_pending_ = pending; |
| } |
| |
| bool OmxCodecRunner::Stream::oob_config_pending() { |
| return oob_config_pending_; |
| } |
| |
| void OmxCodecRunner::Stream::SetInputEndOfStream() { |
| assert(!input_end_of_stream_); |
| input_end_of_stream_ = true; |
| } |
| |
| bool OmxCodecRunner::Stream::input_end_of_stream() { |
| return input_end_of_stream_; |
| } |
| |
| void OmxCodecRunner::Stream::SetOutputEndOfStream() { |
| assert(!output_end_of_stream_); |
| output_end_of_stream_ = true; |
| } |
| |
| bool OmxCodecRunner::Stream::output_end_of_stream() { |
| return output_end_of_stream_; |
| } |
| |
| } // namespace codec_runner |