diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc index 620f85a9692..212958d2673 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags.cc @@ -1360,7 +1360,14 @@ Result InitializeCuttlefishConfiguration( CF_EXPECT_EQ(media_configs_bindings.size(), 1, "Expected a single binding?"); auto media_configs = media_configs_bindings[0]->GetConfigs(); - instance.set_media_configs(media_configs); + std::vector instance_media_configs; + for (const auto& config : media_configs) { + if (!config.instance_index.has_value() || + config.instance_index.value() == instance_index) { + instance_media_configs.push_back(config); + } + } + instance.set_media_configs(instance_media_configs); instance_index++; } // end of num_instances loop diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/media.cpp b/base/cvd/cuttlefish/host/commands/assemble_cvd/media.cpp index 3be31f72f09..833b212d275 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/media.cpp +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/media.cpp @@ -118,6 +118,9 @@ class MediaConfigsFragmentImpl : public MediaConfigsFragment { Json::Value json(Json::objectValue); json[kType] = static_cast(config.type); json[kLensFacing] = config.lens_facing; + if (config.instance_index.has_value()) { + json[kInstanceIndex] = *config.instance_index; + } configs_json.append(json); } return configs_json; @@ -139,6 +142,9 @@ class MediaConfigsFragmentImpl : public MediaConfigsFragment { if (json.isMember(kLensFacing)) { config.lens_facing = json[kLensFacing].asString(); } + if (json.isMember(kInstanceIndex)) { + config.instance_index = json[kInstanceIndex].asInt(); + } configs.emplace_back(config); } @@ -150,6 +156,7 @@ class MediaConfigsFragmentImpl : public MediaConfigsFragment { static constexpr char kMediaConfigs[] = "media_configs"; static constexpr char kType[] = "type"; static constexpr char kLensFacing[] = "lens_facing"; + static constexpr char kInstanceIndex[] = "instance_index"; MediaConfigs& configs_; }; diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/parser/flags_parser_test.cc b/base/cvd/cuttlefish/host/commands/cvd/cli/parser/flags_parser_test.cc index 3cf74ad22ac..c5febc267bf 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/parser/flags_parser_test.cc +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/parser/flags_parser_test.cc @@ -479,6 +479,160 @@ TEST(FlagsParserTest, ParseMediaV4l2Proxy) { << "media flag is missing or wrongly formatted"; } +TEST(FlagsParserTest, ParseMediaV4l2StreamProxy) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "media": { + "devices": [ + { + "v4l2_stream_proxy": { + "input_path": "/tmp/v4l2_fifo", + "input_width": 320, + "input_height": 240, + "input_fps": "10" + }, + "lens_facing": "BACK" + } + ] + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + + auto serialized_data = LaunchCvdParserTester(json_configs); + + ASSERT_THAT(serialized_data, IsOk()); + EXPECT_TRUE(FindConfig( + *serialized_data, + "--media=v4l2_stream_proxy:input_path=/tmp/v4l2_fifo:input_width=320:input_height=240:input_fps=10:lens_facing=BACK")) + << "media flag is missing or wrongly formatted"; +} + +TEST(FlagsParserTest, ParseMediaMultiInstanceSingleMedia) { + const char* test_string = R""""( +{ + "instances" : + [ + {}, + { + "media": { + "devices": [ + { + "v4l2_stream_proxy": { + "input_path": "/tmp/v4l2_fifo", + "input_width": 320, + "input_height": 240, + "input_fps": "10" + } + } + ] + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + + auto serialized_data = LaunchCvdParserTester(json_configs); + + ASSERT_THAT(serialized_data, IsOk()); + EXPECT_TRUE(FindConfig( + *serialized_data, + "--media=v4l2_stream_proxy:input_path=/tmp/v4l2_fifo:input_width=320:input_height=240:input_fps=10:instance=1")) + << "media flag is missing or wrongly formatted"; +} + +TEST(FlagsParserTest, ParseMediaMultiInstanceFirstInstanceMedia) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "media": { + "devices": [ + { + "v4l2_stream_proxy": { + "input_path": "/tmp/v4l2_fifo", + "input_width": 320, + "input_height": 240, + "input_fps": "10" + } + } + ] + } + }, + {} + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + + auto serialized_data = LaunchCvdParserTester(json_configs); + + ASSERT_THAT(serialized_data, IsOk()); + EXPECT_TRUE(FindConfig( + *serialized_data, + "--media=v4l2_stream_proxy:input_path=/tmp/v4l2_fifo:input_width=320:input_height=240:input_fps=10:instance=0")) + << "media flag is missing or wrongly formatted"; +} + +TEST(FlagsParserTest, ParseMediaMultiInstanceMultipleMediaFails) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "media": { + "devices": [ + { + "v4l2_stream_proxy": { + "input_path": "/tmp/v4l2_fifo_1" + } + } + ] + } + }, + { + "media": { + "devices": [ + { + "v4l2_stream_proxy": { + "input_path": "/tmp/v4l2_fifo_2" + } + } + ] + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_FALSE(serialized_data.has_value()); +} + TEST(ConnectivityFlagsParserTest, ParseModemSimulatorSimTypeValidInt) { const char* test_string = R""""( { diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/parser/instance/cf_media_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/parser/instance/cf_media_configs.cpp index 43d7f9f5cda..fbfe02f2047 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/parser/instance/cf_media_configs.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/parser/instance/cf_media_configs.cpp @@ -34,10 +34,11 @@ Result> GenerateMediaFlags( int has_media_count = std::count_if(cfg.instances().rbegin(), cfg.instances().rend(), [](const auto& v) { return v.has_media(); }); - CF_EXPECT(has_media_count == 0 || cfg.instances().size() == 1, + CF_EXPECT(has_media_count <= 1, "TODO(b/520098369): support media devices for multiple instances"); std::vector flags; - for (const auto& instance : cfg.instances()) { + for (size_t i = 0; i < cfg.instances().size(); ++i) { + const auto& instance = cfg.instances(i); if (instance.has_media()) { for (const auto& device : instance.media().devices()) { std::string flag = "--media="; @@ -63,6 +64,9 @@ Result> GenerateMediaFlags( if (device.has_lens_facing()) { flag += ":lens_facing=" + device.lens_facing(); } + if (cfg.instances().size() > 1) { + flag += ":instance=" + std::to_string(i); + } flags.push_back(flag); } } diff --git a/base/cvd/cuttlefish/host/commands/run_cvd/launch/vhost_user_media_devices.cpp b/base/cvd/cuttlefish/host/commands/run_cvd/launch/vhost_user_media_devices.cpp index 224ef82fb07..7d42343a30e 100644 --- a/base/cvd/cuttlefish/host/commands/run_cvd/launch/vhost_user_media_devices.cpp +++ b/base/cvd/cuttlefish/host/commands/run_cvd/launch/vhost_user_media_devices.cpp @@ -83,8 +83,8 @@ class VhostUserMediaDevices : public CommandSource { CF_EXPECT(config.v4l2_stream_proxy.has_value(), "Missing v4l2_stream_proxy config"); binary_path = VhostUserMediaV4l2StreamProxyBinary(); - cmd.emplace( - NewCommand(binary_path, instance_.media_socket_path(index), "")); + cmd.emplace(NewCommand(binary_path, instance_.media_socket_path(index), + config.lens_facing)); cmd->AddParameter("--input_path=", config.v4l2_stream_proxy->input_path); cmd->AddParameter( diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h index 8d3c9092c83..d9dcccec95d 100644 --- a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h @@ -124,6 +124,7 @@ class CuttlefishConfig { struct MediaConfig { MediaType type; std::string lens_facing; + std::optional instance_index; struct V4l2StreamProxyConfig { std::string input_path; diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp index 25f2a13a7e9..3f682375552 100644 --- a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp @@ -2001,6 +2001,7 @@ static constexpr char kMediaInputPath[] = "input_path"; static constexpr char kMediaInputWidth[] = "input_width"; static constexpr char kMediaInputHeight[] = "input_height"; static constexpr char kMediaInputFps[] = "input_fps"; +static constexpr char kMediaInstanceIndex[] = "instance_index"; std::vector CuttlefishConfig::InstanceSpecific::media_configs() const { @@ -2012,6 +2013,9 @@ CuttlefishConfig::InstanceSpecific::media_configs() const { if (json.isMember(kMediaLensFacing)) { config.lens_facing = json[kMediaLensFacing].asString(); } + if (json.isMember(kMediaInstanceIndex)) { + config.instance_index = json[kMediaInstanceIndex].asInt(); + } if (config.type == CuttlefishConfig::MediaType::kV4l2StreamProxy) { CuttlefishConfig::MediaConfig::V4l2StreamProxyConfig stream_config = {}; if (json.isMember(kMediaInputPath)) { @@ -2041,6 +2045,9 @@ void CuttlefishConfig::MutableInstanceSpecific::set_media_configs( Json::Value json(Json::objectValue); json[kMediaType] = static_cast(config.type); json[kMediaLensFacing] = config.lens_facing; + if (config.instance_index.has_value()) { + json[kMediaInstanceIndex] = *config.instance_index; + } if (config.type == CuttlefishConfig::MediaType::kV4l2StreamProxy && config.v4l2_stream_proxy.has_value()) { json[kMediaInputPath] = config.v4l2_stream_proxy->input_path; diff --git a/base/cvd/cuttlefish/host/libs/config/media.cpp b/base/cvd/cuttlefish/host/libs/config/media.cpp index 35399febdea..229538aac70 100644 --- a/base/cvd/cuttlefish/host/libs/config/media.cpp +++ b/base/cvd/cuttlefish/host/libs/config/media.cpp @@ -79,6 +79,16 @@ Result> ParseMediaConfig( "Invalid lens_facing value: " << lens_facing); } + std::optional instance_index; + auto instance_it = props.find("instance"); + if (instance_it != props.end()) { + int idx = 0; + CF_EXPECT(android::base::ParseInt(instance_it->second, &idx), + "Failed to parse instance index: " << instance_it->second); + CF_EXPECT(idx >= 0, "instance index must be non-negative: " << idx); + instance_index = idx; + } + std::optional v4l2_stream_proxy; if (type == CuttlefishConfig::MediaType::kV4l2StreamProxy) { @@ -87,6 +97,8 @@ Result> ParseMediaConfig( auto source_it = props.find("input_path"); CF_EXPECT(source_it != props.end(), "Missing 'input_path' for v4l2_stream_proxy"); + CF_EXPECT(!source_it->second.empty(), + "'input_path' must not be empty for v4l2_stream_proxy"); stream_config.input_path = source_it->second; auto width_it = props.find("input_width"); @@ -110,6 +122,8 @@ Result> ParseMediaConfig( auto fps_it = props.find("input_fps"); CF_EXPECT(fps_it != props.end(), "Missing 'input_fps' for v4l2_stream_proxy"); + CF_EXPECT(!fps_it->second.empty(), + "'input_fps' must not be empty for v4l2_stream_proxy"); stream_config.input_fps = fps_it->second; v4l2_stream_proxy = stream_config; @@ -118,6 +132,7 @@ Result> ParseMediaConfig( return CuttlefishConfig::MediaConfig{ .type = type, .lens_facing = lens_facing, + .instance_index = instance_index, .v4l2_stream_proxy = v4l2_stream_proxy, }; } diff --git a/base/cvd/cuttlefish/host/libs/config/media.h b/base/cvd/cuttlefish/host/libs/config/media.h index c42e21cc139..ecbddb32a78 100644 --- a/base/cvd/cuttlefish/host/libs/config/media.h +++ b/base/cvd/cuttlefish/host/libs/config/media.h @@ -41,7 +41,8 @@ constexpr const char kMediaHelp[] = " 'input_fps': frames per second (e.g., 30 or 30000/1001)\n\n" "Supported keys:\n" " 'lens_facing': optional, supported values: 'FRONT', 'BACK', " - "'EXTERNAL'\n\n" + "'EXTERNAL'\n" + " 'instance': optional, zero-based instance index to attach device to\n\n" "Example usage:\n" " --media=v4l2_emulated_camera_mplane:lens_facing=BACK\n" " --media=v4l2_stream_proxy:input_path=/tmp/fifo:"