From ffb9b72e4e29f28c8b0aa86da91e770654eafef4 Mon Sep 17 00:00:00 2001 From: Ari Hausman-Cohen Date: Fri, 9 Sep 2016 12:06:26 -0700 Subject: [PATCH] Add configs & validation to static properties Static properties read and validate stream configurations, stall durations, and reprocess formats. Static properties can be used to validate a given camera3_stream_configuration_t to check if the streams all work together as defined by the metadata properties. BUG: 31044638 TEST: unit tests pass Change-Id: I2c8eecb680cd86fbe0340c07e4d73bd25429b4e9 --- .../3_4/metadata/metadata_reader_mock.h | 7 + modules/camera/3_4/static_properties.cpp | 424 ++++++++++++- modules/camera/3_4/static_properties.h | 53 +- modules/camera/3_4/static_properties_test.cpp | 566 +++++++++++++++++- 4 files changed, 1041 insertions(+), 9 deletions(-) diff --git a/modules/camera/3_4/metadata/metadata_reader_mock.h b/modules/camera/3_4/metadata/metadata_reader_mock.h index 096cefe4..fcd0704d 100644 --- a/modules/camera/3_4/metadata/metadata_reader_mock.h +++ b/modules/camera/3_4/metadata/metadata_reader_mock.h @@ -31,6 +31,13 @@ class MetadataReaderMock : public MetadataReader { MOCK_CONST_METHOD0(raw_metadata, const camera_metadata_t*()); MOCK_CONST_METHOD1(Facing, int(int*)); MOCK_CONST_METHOD1(Orientation, int(int*)); + MOCK_CONST_METHOD1(MaxInputStreams, int(int32_t*)); + MOCK_CONST_METHOD3(MaxOutputStreams, int(int32_t*, int32_t*, int32_t*)); + MOCK_CONST_METHOD1(StreamConfigurations, + int(std::vector*)); + MOCK_CONST_METHOD1(StreamStallDurations, + int(std::vector*)); + MOCK_CONST_METHOD1(ReprocessFormats, int(ReprocessFormatMap*)); }; } // namespace default_camera_hal diff --git a/modules/camera/3_4/static_properties.cpp b/modules/camera/3_4/static_properties.cpp index a55e9e05..0584290f 100644 --- a/modules/camera/3_4/static_properties.cpp +++ b/modules/camera/3_4/static_properties.cpp @@ -25,25 +25,441 @@ namespace default_camera_hal { +// Build and capabilities from configs + stall durations. +static bool ConstructCapabilities( + const std::vector& configs, + const std::vector& stalls, + StaticProperties::CapabilitiesMap* capabilities) { + // Extract directional capabilities from the configs. + for (const auto& config : configs) { + switch (config.direction) { + case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT: + (*capabilities)[config.spec].output_supported = true; + break; + case ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT: + (*capabilities)[config.spec].input_supported = true; + break; + default: + // Should never happen when using the MetadataReader; + // it should validate directions. + ALOGE("%s: Unrecognized stream config direction %d.", + __func__, + config.direction); + return false; + } + } + + // Extract stall durations from the stalls. + for (const auto& stall : stalls) { + (*capabilities)[stall.spec].stall_duration = stall.duration; + } + + return true; +} + +// Check that each output config has a valid corresponding stall duration +// (extra durations not matching any output config are ignored). +static bool ValidateCapabilities( + StaticProperties::CapabilitiesMap capabilities) { + for (const auto& spec_capabilities : capabilities) { + // Only non-negative stall durations are valid. This should only happen + // due to output streams without an associated stall duration, as + // MetadataReader validates the metadata stall durations. + if (spec_capabilities.second.output_supported && + spec_capabilities.second.stall_duration < 0) { + ALOGE( + "%s: Static metadata does not have a stall duration for " + "each output configuration. ", + __func__); + return false; + } + } + return true; +} + +// Validate that the input/output formats map matches up with +// the capabilities listed for all formats. +bool ValidateReprocessFormats( + const StaticProperties::CapabilitiesMap& capabilities, + const ReprocessFormatMap& reprocess_map) { + // Get input formats. + std::set all_input_formats; + std::set all_output_formats; + for (const auto& spec_capabilities : capabilities) { + if (spec_capabilities.second.input_supported) { + all_input_formats.insert(spec_capabilities.first.format); + } + if (spec_capabilities.second.output_supported) { + all_output_formats.insert(spec_capabilities.first.format); + } + } + + // Must be at least one input format. + if (all_input_formats.size() < 1) { + ALOGE("%s: No input formats, reprocessing can't be supported.", __func__); + return false; + } + + // Check that the reprocess map input formats are exactly all available + // input formats (check size here, then checking for actual value + // matches will happen as part of the loop below). + if (all_input_formats.size() != reprocess_map.size()) { + ALOGE( + "%s: Stream configuration input formats do not match " + "input/output format map input formats.", + __func__); + return false; + } + + // Check that each input format has at least one matching output format. + for (const auto& input_format : all_input_formats) { + const auto input_outputs_iterator = reprocess_map.find(input_format); + if (input_outputs_iterator == reprocess_map.end()) { + ALOGE( + "%s: No output formats for input format %d.", __func__, input_format); + return false; + } + // No need to check that the output formats vector is non-empty; + // MetadataReader validates this. Instead just check that + // all outputs are actually output formats. + for (const auto& output_format : input_outputs_iterator->second) { + if (all_output_formats.count(output_format) < 1) { + ALOGE( + "%s: Output format %d for input format %d " + "is not a supported output format.", + __func__, + input_format, + output_format); + return false; + } + } + } + + return true; +} + StaticProperties* StaticProperties::NewStaticProperties( std::unique_ptr metadata_reader) { int facing = 0; int orientation = 0; + int32_t max_input_streams = 0; + int32_t max_raw_output_streams = 0; + int32_t max_non_stalling_output_streams = 0; + int32_t max_stalling_output_streams = 0; + std::vector configs; + std::vector stalls; + CapabilitiesMap capabilities; + ReprocessFormatMap reprocess_map; + // If reading any data returns an error, something is wrong. if (metadata_reader->Facing(&facing) || - metadata_reader->Orientation(&orientation)) { + metadata_reader->Orientation(&orientation) || + metadata_reader->MaxInputStreams(&max_input_streams) || + metadata_reader->MaxOutputStreams(&max_raw_output_streams, + &max_non_stalling_output_streams, + &max_stalling_output_streams) || + metadata_reader->StreamConfigurations(&configs) || + metadata_reader->StreamStallDurations(&stalls) || + !ConstructCapabilities(configs, stalls, &capabilities) || + // MetadataReader validates configs and stall seperately, + // but not that they match. + !ValidateCapabilities(capabilities) || + // Reprocessing metadata only necessary if input streams are allowed. + (max_input_streams > 0 && + (metadata_reader->ReprocessFormats(&reprocess_map) || + // MetadataReader validates configs and the reprocess map seperately, + // but not that they match. + !ValidateReprocessFormats(capabilities, reprocess_map)))) { return nullptr; } - return new StaticProperties(std::move(metadata_reader), facing, orientation); + return new StaticProperties(std::move(metadata_reader), + facing, + orientation, + max_input_streams, + max_raw_output_streams, + max_non_stalling_output_streams, + max_stalling_output_streams, + std::move(capabilities), + std::move(reprocess_map)); } StaticProperties::StaticProperties( std::unique_ptr metadata_reader, int facing, - int orientation) + int orientation, + int32_t max_input_streams, + int32_t max_raw_output_streams, + int32_t max_non_stalling_output_streams, + int32_t max_stalling_output_streams, + CapabilitiesMap stream_capabilities, + ReprocessFormatMap supported_reprocess_outputs) : metadata_reader_(std::move(metadata_reader)), facing_(facing), - orientation_(orientation) {} + orientation_(orientation), + max_input_streams_(max_input_streams), + max_raw_output_streams_(max_raw_output_streams), + max_non_stalling_output_streams_(max_non_stalling_output_streams), + max_stalling_output_streams_(max_stalling_output_streams), + stream_capabilities_(std::move(stream_capabilities)), + supported_reprocess_outputs_(std::move(supported_reprocess_outputs)) {} + +// Helper functions for checking stream properties when verifying support. +static bool IsInputType(int stream_type) { + return stream_type == CAMERA3_STREAM_INPUT || + stream_type == CAMERA3_STREAM_BIDIRECTIONAL; +} + +static bool IsOutputType(int stream_type) { + return stream_type == CAMERA3_STREAM_OUTPUT || + stream_type == CAMERA3_STREAM_BIDIRECTIONAL; +} + +static bool IsRawFormat(int format) { + return format == HAL_PIXEL_FORMAT_RAW10 || format == HAL_PIXEL_FORMAT_RAW12 || + format == HAL_PIXEL_FORMAT_RAW16 || + format == HAL_PIXEL_FORMAT_RAW_OPAQUE; +} + +bool StaticProperties::StreamConfigurationSupported( + const camera3_stream_configuration_t* stream_config) { + return SanityCheckStreamConfiguration(stream_config) && + InputStreamsSupported(stream_config) && + OutputStreamsSupported(stream_config) && + OperationModeSupported(stream_config); +} + +bool StaticProperties::SanityCheckStreamConfiguration( + const camera3_stream_configuration_t* stream_config) { + // Check for null/empty values. + if (stream_config == nullptr) { + ALOGE("%s: NULL stream configuration array", __func__); + return false; + } else if (stream_config->num_streams == 0) { + ALOGE("%s: Empty stream configuration array", __func__); + return false; + } else if (stream_config->streams == nullptr) { + ALOGE("%s: NULL stream configuration streams", __func__); + return false; + } + + // Check that all streams are either inputs or outputs (or both). + for (size_t i = 0; i < stream_config->num_streams; ++i) { + const camera3_stream_t* stream = stream_config->streams[i]; + if (stream == nullptr) { + ALOGE("%s: Stream %d is null", __func__, i); + return false; + } else if (!IsInputType(stream->stream_type) && + !IsOutputType(stream->stream_type)) { + ALOGE("%s: Stream %d type %d is neither an input nor an output type", + __func__, + i, + stream->stream_type); + return false; + } + } + + return true; +} + +bool StaticProperties::InputStreamsSupported( + const camera3_stream_configuration_t* stream_config) { + // Find the input stream(s). + size_t num_input_streams; + int input_format; + for (size_t i = 0; i < stream_config->num_streams; ++i) { + const camera3_stream_t* stream = stream_config->streams[i]; + if (IsInputType(stream->stream_type)) { + // Check that this stream is valid as an input. + const auto capabilities_iterator = stream_capabilities_.find(stream); + if (capabilities_iterator == stream_capabilities_.end() || + !capabilities_iterator->second.input_supported) { + ALOGE("%s: %d x %d stream of format %d is not a supported input setup.", + __func__, + stream->width, + stream->height, + stream->format); + return false; + } + + // Valid input stream; count it. + ++num_input_streams; + input_format = stream->format; + } + } + + // Check the count. + if (num_input_streams > max_input_streams_) { + ALOGE( + "%s: Requested number of input streams %d is greater than " + "the maximum number supported by the device (%d).", + __func__, + num_input_streams, + max_input_streams_); + return false; + } + if (num_input_streams > 1) { + ALOGE("%s: Camera HAL 3.4 only supports 1 input stream max.", __func__); + return false; + } + + // If there's an input stream, the configuration must have at least one + // supported output format for reprocessing that input. + if (num_input_streams > 0) { + const auto input_output_formats_iterator = + supported_reprocess_outputs_.find(input_format); + if (input_output_formats_iterator == supported_reprocess_outputs_.end()) { + // Should never happen; factory should verify that all valid inputs + // have one or more valid outputs. + ALOGE("%s: No valid output formats for input format %d.", + __func__, + input_format); + return false; + } + bool match_found = false; + // Go through outputs looking for a supported one. + for (size_t i = 0; i < stream_config->num_streams; ++i) { + const camera3_stream_t* stream = stream_config->streams[i]; + if (IsOutputType(stream->stream_type)) { + if (input_output_formats_iterator->second.count(stream->format) > 0) { + match_found = true; + break; + } + } + } + if (!match_found) { + ALOGE("%s: No supported output format provided for input format %d.", + __func__, + input_format); + return false; + } + } + + return true; +} + +bool StaticProperties::OutputStreamsSupported( + const camera3_stream_configuration_t* stream_config) { + // Find and count output streams. + size_t num_raw = 0; + size_t num_stalling = 0; + size_t num_non_stalling = 0; + for (int i = 0; i < stream_config->num_streams; ++i) { + const camera3_stream_t* stream = stream_config->streams[i]; + if (IsOutputType(stream->stream_type)) { + // Check that this stream is valid as an output. + const auto capabilities_iterator = stream_capabilities_.find(stream); + if (capabilities_iterator == stream_capabilities_.end() || + !capabilities_iterator->second.output_supported) { + ALOGE( + "%s: %d x %d stream of format %d " + "is not a supported output setup.", + __func__, + stream->width, + stream->height, + stream->format); + return false; + } + + // Valid output; count it. + if (IsRawFormat(stream->format)) { + ++num_raw; + } else if (capabilities_iterator->second.stall_duration > 0) { + ++num_stalling; + } else { + ++num_non_stalling; + } + } + } + + // Check that the counts are within bounds. + if (num_raw > max_raw_output_streams_) { + ALOGE( + "%s: Requested stream configuration exceeds maximum supported " + "raw output streams %d (requested %d).", + __func__, + max_raw_output_streams_, + num_raw); + return false; + } else if (num_stalling > max_stalling_output_streams_) { + ALOGE( + "%s: Requested stream configuration exceeds maximum supported " + "stalling output streams %d (requested %d).", + __func__, + max_stalling_output_streams_, + num_stalling); + return false; + } else if (num_non_stalling > max_non_stalling_output_streams_) { + ALOGE( + "%s: Requested stream configuration exceeds maximum supported " + "non-stalling output streams %d (requested %d).", + __func__, + max_non_stalling_output_streams_, + num_non_stalling); + return false; + } + + return true; +} + +bool StaticProperties::OperationModeSupported( + const camera3_stream_configuration_t* stream_config) { + switch (stream_config->operation_mode) { + case CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE: + return true; + case CAMERA3_STREAM_CONFIGURATION_CONSTRAINED_HIGH_SPEED_MODE: + // TODO(b/31370792): Check metadata for high speed support, + // check that requested streams have support for high speed. + ALOGE("%s: Support for CONSTRAINED_HIGH_SPEED not implemented", __func__); + return false; + default: + ALOGE("%s: Unrecognized stream configuration mode: %d", + __func__, + stream_config->operation_mode); + return false; + } +} + +bool StaticProperties::ReprocessingSupported( + const camera3_stream_t* input_stream, + const std::set& output_streams) { + // There must be an input. + if (!input_stream) { + ALOGE("%s: No input stream.", __func__); + return false; + } + // There must be an output. + if (output_streams.size() < 1) { + ALOGE("%s: No output stream.", __func__); + return false; + } + + const auto input_output_formats = + supported_reprocess_outputs_.find(input_stream->format); + if (input_output_formats == supported_reprocess_outputs_.end()) { + // Should never happen for a valid input stream. + ALOGE("%s: Input format %d does not support any output formats.", + __func__, + input_stream->format); + return false; + } + + // Check that all output streams can be outputs for the input stream. + const std::set& supported_output_formats = + input_output_formats->second; + for (const auto output_stream : output_streams) { + if (supported_output_formats.count(output_stream->format) < 1) { + ALOGE( + "%s: Output format %d is not a supported output " + "for request input format %d.", + __func__, + output_stream->format, + input_stream->format); + return false; + } + } + + return true; +} } // namespace default_camera_hal diff --git a/modules/camera/3_4/static_properties.h b/modules/camera/3_4/static_properties.h index 77d5c924..a687a805 100644 --- a/modules/camera/3_4/static_properties.h +++ b/modules/camera/3_4/static_properties.h @@ -18,15 +18,34 @@ #define DEFAULT_CAMERA_HAL_STATIC_PROPERTIES_H_ #include +#include + +#include #include "common.h" #include "metadata/metadata_reader.h" +#include "metadata/types.h" namespace default_camera_hal { // StaticProperties provides a wrapper around useful static metadata entries. class StaticProperties { public: + // Helpful types for interpreting some static properties. + struct StreamCapabilities { + int64_t stall_duration; + int32_t input_supported; + int32_t output_supported; + // Default constructor ensures no support + // and an invalid stall duration. + StreamCapabilities() + : stall_duration(-1), input_supported(0), output_supported(0) {} + }; + // Map stream spec (format, size) to their + // capabilities (input, output, stall). + typedef std::map + CapabilitiesMap; + // Use this method to create StaticProperties objects. // Functionally equivalent to "new StaticProperties", // except that it may return nullptr in case of failure (missing entries). @@ -34,6 +53,7 @@ class StaticProperties { std::unique_ptr metadata_reader); virtual ~StaticProperties(){}; + // Simple accessors. int facing() const { return facing_; }; int orientation() const { return orientation_; }; // Carrying on the promise of the underlying reader, @@ -42,16 +62,47 @@ class StaticProperties { return metadata_reader_->raw_metadata(); }; + // Validators (check that values are consistent with the capabilities + // this object represents/base requirements of the camera HAL). + bool StreamConfigurationSupported( + const camera3_stream_configuration_t* stream_config); + // Check that the inputs and outputs for a request don't conflict. + bool ReprocessingSupported( + const camera3_stream_t* input_stream, + const std::set& output_streams); + private: // Constructor private to allow failing on bad input. // Use NewStaticProperties instead. StaticProperties(std::unique_ptr metadata_reader, int facing, - int orientation); + int orientation, + int32_t max_input_streams, + int32_t max_raw_output_streams, + int32_t max_non_stalling_output_streams, + int32_t max_stalling_output_streams, + CapabilitiesMap stream_capabilities, + ReprocessFormatMap supported_reprocess_outputs); + + // Helper functions for StreamConfigurationSupported. + bool SanityCheckStreamConfiguration( + const camera3_stream_configuration_t* stream_config); + bool InputStreamsSupported( + const camera3_stream_configuration_t* stream_config); + bool OutputStreamsSupported( + const camera3_stream_configuration_t* stream_config); + bool OperationModeSupported( + const camera3_stream_configuration_t* stream_config); const std::unique_ptr metadata_reader_; const int facing_; const int orientation_; + const int32_t max_input_streams_; + const int32_t max_raw_output_streams_; + const int32_t max_non_stalling_output_streams_; + const int32_t max_stalling_output_streams_; + const CapabilitiesMap stream_capabilities_; + const ReprocessFormatMap supported_reprocess_outputs_; DISALLOW_COPY_AND_ASSIGN(StaticProperties); }; diff --git a/modules/camera/3_4/static_properties_test.cpp b/modules/camera/3_4/static_properties_test.cpp index 7c05ad33..4e41adaa 100644 --- a/modules/camera/3_4/static_properties_test.cpp +++ b/modules/camera/3_4/static_properties_test.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include "metadata/metadata_reader_mock.h" @@ -33,7 +34,7 @@ namespace default_camera_hal { class StaticPropertiesTest : public Test { protected: - void SetUp() { + virtual void SetUp() { // Ensure tests will probably fail if PrepareDUT isn't called. dut_.reset(); mock_reader_ = std::make_unique(); @@ -43,6 +44,12 @@ class StaticPropertiesTest : public Test { dut_.reset(StaticProperties::NewStaticProperties(std::move(mock_reader_))); } + void PrepareDefaultDUT() { + SetDefaultExpectations(); + PrepareDUT(); + ASSERT_NE(dut_, nullptr); + } + void SetDefaultExpectations() { EXPECT_CALL(*mock_reader_, Facing(_)) .Times(AtMost(1)) @@ -50,21 +57,142 @@ class StaticPropertiesTest : public Test { EXPECT_CALL(*mock_reader_, Orientation(_)) .Times(AtMost(1)) .WillOnce(DoAll(SetArgPointee<0>(test_orientation_), Return(0))); + EXPECT_CALL(*mock_reader_, MaxInputStreams(_)) + .Times(AtMost(1)) + .WillOnce(DoAll(SetArgPointee<0>(test_max_inputs_), Return(0))); + EXPECT_CALL(*mock_reader_, MaxOutputStreams(_, _, _)) + .Times(AtMost(1)) + .WillOnce(DoAll(SetArgPointee<0>(test_max_raw_outputs_), + SetArgPointee<1>(test_max_non_stalling_outputs_), + SetArgPointee<2>(test_max_stalling_outputs_), + Return(0))); + EXPECT_CALL(*mock_reader_, StreamConfigurations(_)) + .Times(AtMost(1)) + .WillOnce(DoAll(SetArgPointee<0>(test_configs_), Return(0))); + EXPECT_CALL(*mock_reader_, StreamStallDurations(_)) + .Times(AtMost(1)) + .WillOnce(DoAll(SetArgPointee<0>(test_stalls_), Return(0))); + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)) + .Times(AtMost(1)) + .WillOnce(DoAll(SetArgPointee<0>(test_reprocess_map_), Return(0))); + } + + camera3_stream_t MakeStream(int32_t format, + bool output = true, + bool input = false, + int32_t width = kWidth, + int32_t height = kHeight) { + int type = -1; + if (output && input) { + type = CAMERA3_STREAM_BIDIRECTIONAL; + } else if (output) { + type = CAMERA3_STREAM_OUTPUT; + } else if (input) { + type = CAMERA3_STREAM_INPUT; + } + return {static_cast(type), + static_cast(width), + static_cast(height), + static_cast(format)}; + } + + void ExpectConfigurationSupported(std::vector& streams, + bool expected) { + std::vector stream_addresses; + for (size_t i = 0; i < streams.size(); ++i) { + stream_addresses.push_back(&streams[i]); + } + camera3_stream_configuration_t config = { + stream_addresses.size(), + stream_addresses.data(), + CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; + PrepareDefaultDUT(); + EXPECT_EQ(dut_->StreamConfigurationSupported(&config), expected); } std::unique_ptr dut_; std::unique_ptr mock_reader_; + // Some helper values used for stream testing. + static constexpr int32_t kWidth = 320; + static constexpr int32_t kHeight = 240; + static constexpr int32_t kAlternateWidth = 640; + static constexpr int32_t kAlternateHeight = 480; + const int test_facing_ = CAMERA_FACING_FRONT; const int test_orientation_ = 90; + const int32_t test_max_inputs_ = 3; + const int32_t test_max_raw_outputs_ = 1; + const int32_t test_max_non_stalling_outputs_ = 2; + const int32_t test_max_stalling_outputs_ = 3; + + // Some formats for various purposes (in various combinations, + // these types should be capable of testing all failure conditions). + const int32_t output_multisize_non_stalling_ = 1; + const int32_t bidirectional_self_supporting_stalling_ = 2; + const int32_t bidirectional_raw_ = HAL_PIXEL_FORMAT_RAW10; + const int32_t input_ = 3; + const int32_t other = input_; + + const std::vector test_configs_ = { + {{{output_multisize_non_stalling_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}, + {{{output_multisize_non_stalling_, + kAlternateWidth, + kAlternateHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}, + {{{bidirectional_self_supporting_stalling_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}}, + {{{bidirectional_self_supporting_stalling_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}, + {{{bidirectional_raw_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}}, + {{{bidirectional_raw_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}, + {{{input_, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}}}; + // Raw having a stall duration shouldn't matter, + // it should still be counted as the raw type. + const std::vector test_stalls_ = { + {{{output_multisize_non_stalling_, kWidth, kHeight, 0}}}, + {{{output_multisize_non_stalling_, + kAlternateWidth, + kAlternateHeight, + 0}}}, + {{{bidirectional_self_supporting_stalling_, kWidth, kHeight, 10}}}, + {{{bidirectional_raw_, kWidth, kHeight, 15}}}}; + // Format 2 can go to itself or 1. 3 and RAW can only go to 1. + const ReprocessFormatMap test_reprocess_map_ = { + {bidirectional_self_supporting_stalling_, + {output_multisize_non_stalling_, + bidirectional_self_supporting_stalling_}}, + {bidirectional_raw_, {output_multisize_non_stalling_}}, + {input_, {output_multisize_non_stalling_}}}; + // Codify the above information about format capabilities in some helpful + // vectors. + int32_t multi_size_format_ = 1; + const std::vector input_formats_ = {2, 3, HAL_PIXEL_FORMAT_RAW10}; + const std::vector output_formats_ = {1, 2, HAL_PIXEL_FORMAT_RAW10}; }; TEST_F(StaticPropertiesTest, FactorySuccess) { - SetDefaultExpectations(); - PrepareDUT(); - ASSERT_NE(dut_, nullptr); + PrepareDefaultDUT(); EXPECT_EQ(dut_->facing(), test_facing_); EXPECT_EQ(dut_->orientation(), test_orientation_); + + // Stream configurations tested seperately. } TEST_F(StaticPropertiesTest, FactoryFailedFacing) { @@ -83,4 +211,434 @@ TEST_F(StaticPropertiesTest, FactoryFailedOrientation) { EXPECT_EQ(dut_, nullptr); } +TEST_F(StaticPropertiesTest, FactoryFailedMaxInputs) { + SetDefaultExpectations(); + // Override with a failure expectation. + EXPECT_CALL(*mock_reader_, MaxInputStreams(_)).WillOnce(Return(99)); + PrepareDUT(); + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryFailedMaxOutputs) { + SetDefaultExpectations(); + // Override with a failure expectation. + EXPECT_CALL(*mock_reader_, MaxOutputStreams(_, _, _)).WillOnce(Return(99)); + PrepareDUT(); + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryFailedStreamConfigs) { + SetDefaultExpectations(); + // Override with a failure expectation. + EXPECT_CALL(*mock_reader_, StreamConfigurations(_)).WillOnce(Return(99)); + PrepareDUT(); + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryFailedStallDurations) { + SetDefaultExpectations(); + // Override with a failure expectation. + EXPECT_CALL(*mock_reader_, StreamStallDurations(_)).WillOnce(Return(99)); + PrepareDUT(); + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryFailedReprocessFormats) { + SetDefaultExpectations(); + // Override with a failure expectation. + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)).WillOnce(Return(99)); + PrepareDUT(); + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryNoReprocessFormats) { + // If there are no inputs allowed, the reprocess formats shouldn't matter. + SetDefaultExpectations(); + // Override max inputs. + EXPECT_CALL(*mock_reader_, MaxInputStreams(_)) + .WillOnce(DoAll(SetArgPointee<0>(0), Return(0))); + // Override reprocess formats with a failure expectation. + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)) + .Times(AtMost(1)) + .WillOnce(Return(99)); + PrepareDUT(); + // Should be ok. + EXPECT_NE(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, FactoryInvalidCapabilities) { + SetDefaultExpectations(); + // Override configs with an extra output format. + std::vector configs = test_configs_; + configs.push_back( + {{{5, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}); + EXPECT_CALL(*mock_reader_, StreamConfigurations(_)) + .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0))); + PrepareDUT(); + // Should fail because not every output has a stall. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, InvalidReprocessNoInputs) { + SetDefaultExpectations(); + // Override configs by removing all inputs. + std::vector configs = test_configs_; + for (auto it = configs.begin(); it != configs.end();) { + if ((*it).direction == + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) { + it = configs.erase(it); + } else { + ++it; + } + } + EXPECT_CALL(*mock_reader_, StreamConfigurations(_)) + .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0))); + PrepareDUT(); + // Should fail because inputs are supported but there are no input formats. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, InvalidReprocessExtraInput) { + SetDefaultExpectations(); + // Override configs with an extra input format. + std::vector configs = test_configs_; + configs.push_back({{{5, + kWidth, + kHeight, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT}}}); + EXPECT_CALL(*mock_reader_, StreamConfigurations(_)) + .WillOnce(DoAll(SetArgPointee<0>(configs), Return(0))); + PrepareDUT(); + // Should fail because no reprocess outputs are listed for the extra input. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, InvalidReprocessExtraMapEntry) { + SetDefaultExpectations(); + // Override the reprocess map with an extra entry. + ReprocessFormatMap reprocess_map = test_reprocess_map_; + reprocess_map[5] = {1}; + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)) + .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0))); + PrepareDUT(); + // Should fail because the extra map entry doesn't correspond to an input. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, InvalidReprocessWrongMapEntries) { + SetDefaultExpectations(); + // Override the reprocess map replacing the entry for the + // input-only format with the output-only format. + ReprocessFormatMap reprocess_map = test_reprocess_map_; + reprocess_map.erase(input_); + reprocess_map[output_multisize_non_stalling_] = {1}; + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)) + .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0))); + PrepareDUT(); + // Should fail because not all input formats are present/ + // one of the map "input" formats is output only. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, InvalidReprocessNotAnOutput) { + SetDefaultExpectations(); + // Override the reprocess map with a non-output output entry. + ReprocessFormatMap reprocess_map = test_reprocess_map_; + reprocess_map[input_].insert(input_); + EXPECT_CALL(*mock_reader_, ReprocessFormats(_)) + .WillOnce(DoAll(SetArgPointee<0>(reprocess_map), Return(0))); + PrepareDUT(); + // Should fail because a specified output format doesn't support output. + EXPECT_EQ(dut_, nullptr); +} + +TEST_F(StaticPropertiesTest, ConfigureSingleOutput) { + std::vector streams; + streams.push_back(MakeStream(output_multisize_non_stalling_)); + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureMultipleOutputs) { + std::vector streams; + // 2 outputs, of different sizes. + streams.push_back(MakeStream(bidirectional_raw_)); + // Use the alternate size. + streams.push_back(MakeStream(output_multisize_non_stalling_, + true, + false, + kAlternateWidth, + kAlternateHeight)); + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureInput) { + std::vector streams; + // Single input -> different output. + streams.push_back(MakeStream(input_, false, true)); + // Use the alternate size, it should be ok. + streams.push_back(MakeStream(output_multisize_non_stalling_, + true, + false, + kAlternateWidth, + kAlternateHeight)); + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureBidirectional) { + std::vector streams; + // Single input -> same output. + streams.push_back( + MakeStream(bidirectional_self_supporting_stalling_, true, true)); + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureMultipleReprocess) { + std::vector streams; + // Single input -> multiple outputs. + streams.push_back( + MakeStream(bidirectional_self_supporting_stalling_, true, true)); + streams.push_back(MakeStream(output_multisize_non_stalling_)); + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureNull) { + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->StreamConfigurationSupported(nullptr)); +} + +TEST_F(StaticPropertiesTest, ConfigureEmptyStreams) { + std::vector streams(1); + camera3_stream_configuration_t config = { + 0, streams.data(), CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->StreamConfigurationSupported(&config)); +} + +TEST_F(StaticPropertiesTest, ConfigureNullStreams) { + std::vector streams(2, nullptr); + camera3_stream_configuration_t config = { + streams.size(), streams.data(), CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->StreamConfigurationSupported(&config)); +} + +TEST_F(StaticPropertiesTest, ConfigureNullStreamVector) { + // Even if the camera claims to have multiple streams, check for null. + camera3_stream_configuration_t config = { + 3, nullptr, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->StreamConfigurationSupported(&config)); +} + +TEST_F(StaticPropertiesTest, ConfigureNoOutput) { + std::vector streams; + // Only an input stream, no output. + streams.push_back(MakeStream(input_, false, true)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureInvalidType) { + std::vector streams; + // Not input, output, or bidirectional. + streams.push_back(MakeStream(output_multisize_non_stalling_, false, false)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureSpecFormatDoesNotExist) { + std::vector streams; + // Format 99 is not supported in any form. + streams.push_back(MakeStream(99)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureSpecSizeDoesNotExist) { + std::vector streams; + // Size 99 x 99 not supported for the output format. + streams.push_back( + MakeStream(output_multisize_non_stalling_, true, false, 99, 99)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureNotAnInput) { + std::vector streams; + streams.push_back(MakeStream(output_multisize_non_stalling_)); + // Can't use output-only format as an input. + streams.push_back(MakeStream(output_multisize_non_stalling_, false, true)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureNotAnOutput) { + std::vector streams; + // Can't use input-only format as an output. + streams.push_back(MakeStream(input_)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureTooManyInputs) { + std::vector streams; + // At the threshold is ok. + for (int32_t i = 0; i < test_max_inputs_; ++i) { + streams.push_back(MakeStream(input_, false, true)); + } + // Have a valid output still. + streams.push_back(MakeStream(output_multisize_non_stalling_)); + ExpectConfigurationSupported(streams, false); + + // Reset. + mock_reader_ = std::make_unique(); + streams.clear(); + + // Try again with too many. + for (int32_t i = 0; i <= test_max_inputs_; ++i) { + streams.push_back(MakeStream(input_, false, true)); + } + // Have a valid output still. + streams.push_back(MakeStream(output_multisize_non_stalling_)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureTooManyRaw) { + std::vector streams; + // At the threshold is ok. + for (int32_t i = 0; i < test_max_raw_outputs_; ++i) { + streams.push_back(MakeStream(bidirectional_raw_)); + } + ExpectConfigurationSupported(streams, true); + + // Reset. + mock_reader_ = std::make_unique(); + streams.clear(); + + // Try again with too many. + for (int32_t i = 0; i <= test_max_raw_outputs_; ++i) { + streams.push_back(MakeStream(bidirectional_raw_)); + } + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureTooManyStalling) { + std::vector streams; + // At the threshold is ok. + for (int32_t i = 0; i < test_max_stalling_outputs_; ++i) { + streams.push_back(MakeStream(bidirectional_self_supporting_stalling_)); + } + ExpectConfigurationSupported(streams, true); + + // Reset. + mock_reader_ = std::make_unique(); + streams.clear(); + + // Try again with too many. + for (int32_t i = 0; i <= test_max_stalling_outputs_; ++i) { + streams.push_back(MakeStream(bidirectional_self_supporting_stalling_)); + } + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureTooManyNonStalling) { + std::vector streams; + // At the threshold is ok. + for (int32_t i = 0; i < test_max_non_stalling_outputs_; ++i) { + streams.push_back(MakeStream(output_multisize_non_stalling_)); + } + ExpectConfigurationSupported(streams, true); + + // Reset. + mock_reader_ = std::make_unique(); + streams.clear(); + + // Try again with too many. + for (int32_t i = 0; i <= test_max_non_stalling_outputs_; ++i) { + streams.push_back(MakeStream(output_multisize_non_stalling_)); + } + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureUnuspportedInput) { + std::vector streams; + streams.push_back(MakeStream(input_, false, true)); + streams.push_back(MakeStream(bidirectional_raw_)); + // No matching output format for input. + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureUnsupportedOutput) { + std::vector streams; + streams.push_back(MakeStream(input_, false, true)); + // The universal output does match input. + streams.push_back(MakeStream(output_multisize_non_stalling_)); + // Raw does not match input. + streams.push_back(MakeStream(bidirectional_raw_)); + // Input is matched; it's ok that raw doesn't match (only the actual + // requests care). + ExpectConfigurationSupported(streams, true); +} + +TEST_F(StaticPropertiesTest, ConfigureUnsupportedBidirectional) { + std::vector streams; + // The test raw format, while supporting both input and output, + // does not actually support itself. + streams.push_back(MakeStream(bidirectional_raw_, true, true)); + ExpectConfigurationSupported(streams, false); +} + +TEST_F(StaticPropertiesTest, ConfigureBadOperationMode) { + // A valid stream set. + camera3_stream_t stream = MakeStream(output_multisize_non_stalling_); + camera3_stream_t* stream_address = &stream; + // But not a valid config. + camera3_stream_configuration_t config = { + 1, + &stream_address, + 99 // Not a valid operation mode. + }; + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->StreamConfigurationSupported(&config)); +} + +TEST_F(StaticPropertiesTest, ReprocessingSingleOutput) { + camera3_stream_t input_stream = MakeStream(input_); + camera3_stream_t output_stream = MakeStream(output_multisize_non_stalling_); + PrepareDefaultDUT(); + EXPECT_TRUE(dut_->ReprocessingSupported(&input_stream, {&output_stream})); +} + +TEST_F(StaticPropertiesTest, ReprocessingMultipleOutputs) { + camera3_stream_t input_stream = + MakeStream(bidirectional_self_supporting_stalling_, false, true); + // Bi-directional self-supporting supports the universal output and itself. + camera3_stream_t output_stream1 = MakeStream(output_multisize_non_stalling_); + camera3_stream_t output_stream2 = + MakeStream(bidirectional_self_supporting_stalling_); + PrepareDefaultDUT(); + EXPECT_TRUE(dut_->ReprocessingSupported(&input_stream, + {&output_stream1, &output_stream2})); +} + +TEST_F(StaticPropertiesTest, ReprocessingNoInput) { + camera3_stream_t output_stream = MakeStream(output_multisize_non_stalling_); + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->ReprocessingSupported(nullptr, {&output_stream})); +} + +TEST_F(StaticPropertiesTest, ReprocessingNoOutput) { + camera3_stream_t input_stream = MakeStream(input_); + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->ReprocessingSupported(&input_stream, {})); +} + +TEST_F(StaticPropertiesTest, ReprocessingInvalidOutput) { + camera3_stream_t input_stream = MakeStream(input_, false, true); + // The universal output does match input. + camera3_stream_t output_stream1 = MakeStream(output_multisize_non_stalling_); + // Raw does not match input. + camera3_stream_t output_stream2 = MakeStream(bidirectional_raw_); + PrepareDefaultDUT(); + EXPECT_FALSE(dut_->ReprocessingSupported(&input_stream, + {&output_stream1, &output_stream2})); +} + } // namespace default_camera_hal