xref: /aosp_15_r20/frameworks/av/services/camera/virtualcamera/VirtualCameraService.cc (revision ec779b8e0859a360c3d303172224686826e6e0e1)
1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // #define LOG_NDEBUG 0
18 #define LOG_TAG "VirtualCameraService"
19 #include "VirtualCameraService.h"
20 
21 #include <algorithm>
22 #include <array>
23 #include <cinttypes>
24 #include <cstdint>
25 #include <memory>
26 #include <mutex>
27 #include <optional>
28 #include <regex>
29 #include <variant>
30 
31 #include "VirtualCameraDevice.h"
32 #include "VirtualCameraProvider.h"
33 #include "VirtualCameraTestInstance.h"
34 #include "aidl/android/companion/virtualcamera/Format.h"
35 #include "aidl/android/companion/virtualcamera/LensFacing.h"
36 #include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
37 #include "android/binder_auto_utils.h"
38 #include "android/binder_interface_utils.h"
39 #include "android/binder_libbinder.h"
40 #include "android/binder_status.h"
41 #include "binder/Status.h"
42 #include "fmt/format.h"
43 #include "util/EglDisplayContext.h"
44 #include "util/EglUtil.h"
45 #include "util/Permissions.h"
46 #include "util/Util.h"
47 
48 using ::android::binder::Status;
49 
50 namespace android {
51 namespace companion {
52 namespace virtualcamera {
53 
54 using ::aidl::android::companion::virtualcamera::Format;
55 using ::aidl::android::companion::virtualcamera::LensFacing;
56 using ::aidl::android::companion::virtualcamera::SensorOrientation;
57 using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
58 using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
59 
60 namespace {
61 
62 constexpr char kCameraIdPrefix[] = "v";
63 constexpr int kVgaWidth = 640;
64 constexpr int kVgaHeight = 480;
65 constexpr int kMaxFps = 60;
66 constexpr int kTestCameraDefaultInputFps = 30;
67 constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
68 constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
69 constexpr char kHelp[] = "help";
70 constexpr char kShellCmdHelp[] = R"(
71 Usage:
72    cmd virtual_camera command [--option=value]
73 Available commands:
74  * enable_test_camera
75      Options:
76        --camera_id=(ID) - override numerical ID for test camera instance
77        --lens_facing=(front|back|external) - specifies lens facing for test camera instance
78        --input_fps=(fps) - specify input fps for test camera, valid values are from 1 to 1000
79        --sensor_orientation=(0|90|180|270) - Clockwise angle through which the output image
80            needs to be rotated to be upright on the device screen in its native orientation
81  * disable_test_camera
82 )";
83 constexpr char kCreateVirtualDevicePermission[] =
84     "android.permission.CREATE_VIRTUAL_DEVICE";
85 
86 constexpr std::array<const char*, 3> kRequiredEglExtensions = {
87     "GL_OES_EGL_image_external",
88     "GL_OES_EGL_image_external_essl3",
89     "GL_EXT_YUV_target",
90 };
91 
92 // Numerical portion for id to assign to next created camera.
93 static std::atomic_int sNextIdNumericalPortion{1000};
94 
validateConfiguration(const VirtualCameraConfiguration & configuration)95 ndk::ScopedAStatus validateConfiguration(
96     const VirtualCameraConfiguration& configuration) {
97   if (configuration.supportedStreamConfigs.empty()) {
98     ALOGE("%s: No supported input configuration specified", __func__);
99     return ndk::ScopedAStatus::fromServiceSpecificError(
100         Status::EX_ILLEGAL_ARGUMENT);
101   }
102 
103   if (configuration.virtualCameraCallback == nullptr) {
104     ALOGE("%s: Input configuration is missing virtual camera callback",
105           __func__);
106     return ndk::ScopedAStatus::fromServiceSpecificError(
107         Status::EX_ILLEGAL_ARGUMENT);
108   }
109 
110   for (const SupportedStreamConfiguration& config :
111        configuration.supportedStreamConfigs) {
112     if (!isFormatSupportedForInput(config.width, config.height,
113                                    config.pixelFormat, config.maxFps)) {
114       ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
115             config.width, config.height, static_cast<int>(config.pixelFormat));
116       return ndk::ScopedAStatus::fromServiceSpecificError(
117           Status::EX_ILLEGAL_ARGUMENT);
118     }
119   }
120 
121   if (configuration.sensorOrientation != SensorOrientation::ORIENTATION_0 &&
122       configuration.sensorOrientation != SensorOrientation::ORIENTATION_90 &&
123       configuration.sensorOrientation != SensorOrientation::ORIENTATION_180 &&
124       configuration.sensorOrientation != SensorOrientation::ORIENTATION_270) {
125     return ndk::ScopedAStatus::fromServiceSpecificError(
126         Status::EX_ILLEGAL_ARGUMENT);
127   }
128 
129   if (configuration.lensFacing != LensFacing::FRONT &&
130       configuration.lensFacing != LensFacing::BACK &&
131       configuration.lensFacing != LensFacing::EXTERNAL) {
132     return ndk::ScopedAStatus::fromServiceSpecificError(
133         Status::EX_ILLEGAL_ARGUMENT);
134   }
135 
136   return ndk::ScopedAStatus::ok();
137 }
138 
139 enum class Command {
140   ENABLE_TEST_CAMERA,
141   DISABLE_TEST_CAMERA,
142   HELP,
143 };
144 
145 struct CommandWithOptions {
146   Command command;
147   std::map<std::string, std::string> optionToValueMap;
148 };
149 
parseInt(const std::string & s)150 std::optional<int> parseInt(const std::string& s) {
151   if (!std::all_of(s.begin(), s.end(), [](char c) { return std::isdigit(c); })) {
152     return std::nullopt;
153   }
154   int ret = atoi(s.c_str());
155   return ret > 0 ? std::optional(ret) : std::nullopt;
156 }
157 
parseLensFacing(const std::string & s)158 std::optional<LensFacing> parseLensFacing(const std::string& s) {
159   static const std::map<std::string, LensFacing> strToLensFacing{
160       {"front", LensFacing::FRONT},
161       {"back", LensFacing::BACK},
162       {"external", LensFacing::EXTERNAL}};
163   auto it = strToLensFacing.find(s);
164   return it == strToLensFacing.end() ? std::nullopt : std::optional(it->second);
165 }
166 
parseCommand(const char ** args,const uint32_t numArgs)167 std::variant<CommandWithOptions, std::string> parseCommand(
168     const char** args, const uint32_t numArgs) {
169   static const std::regex optionRegex("^--(\\w+)(?:=(.+))?$");
170   static const std::map<std::string, Command> strToCommand{
171       {kHelp, Command::HELP},
172       {kEnableTestCameraCmd, Command::ENABLE_TEST_CAMERA},
173       {kDisableTestCameraCmd, Command::DISABLE_TEST_CAMERA}};
174 
175   if (numArgs < 1) {
176     return CommandWithOptions{.command = Command::HELP};
177   }
178 
179   // We interpret the first argument as command;
180   auto it = strToCommand.find(args[0]);
181   if (it == strToCommand.end()) {
182     return "Unknown command: " + std::string(args[0]);
183   }
184 
185   CommandWithOptions cmd{.command = it->second};
186 
187   for (int i = 1; i < numArgs; i++) {
188     std::cmatch cm;
189     if (!std::regex_match(args[i], cm, optionRegex)) {
190       return "Not an option: " + std::string(args[i]);
191     }
192 
193     cmd.optionToValueMap[cm[1]] = cm[2];
194   }
195 
196   return cmd;
197 }
198 
verifyRequiredEglExtensions()199 ndk::ScopedAStatus verifyRequiredEglExtensions() {
200   EglDisplayContext context;
201   for (const char* eglExtension : kRequiredEglExtensions) {
202     if (!isGlExtensionSupported(eglExtension)) {
203       ALOGE("%s not supported", eglExtension);
204       return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
205           EX_UNSUPPORTED_OPERATION,
206           fmt::format(
207               "Cannot create virtual camera, because required EGL extension {} "
208               "is not supported on this system",
209               eglExtension)
210               .c_str());
211     }
212   }
213   return ndk::ScopedAStatus::ok();
214 }
215 
createCameraId(const int32_t deviceId)216 std::string createCameraId(const int32_t deviceId) {
217   return kCameraIdPrefix + std::to_string(deviceId) + "_" +
218          std::to_string(sNextIdNumericalPortion++);
219 }
220 
221 }  // namespace
222 
VirtualCameraService(std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,const PermissionsProxy & permissionProxy)223 VirtualCameraService::VirtualCameraService(
224     std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
225     const PermissionsProxy& permissionProxy)
226     : mVirtualCameraProvider(virtualCameraProvider),
227       mPermissionProxy(permissionProxy) {
228 }
229 
registerCamera(const::ndk::SpAIBinder & token,const VirtualCameraConfiguration & configuration,const int32_t deviceId,bool * _aidl_return)230 ndk::ScopedAStatus VirtualCameraService::registerCamera(
231     const ::ndk::SpAIBinder& token,
232     const VirtualCameraConfiguration& configuration, const int32_t deviceId,
233     bool* _aidl_return) {
234   return registerCamera(token, configuration, createCameraId(deviceId),
235                         deviceId, _aidl_return);
236 }
237 
registerCamera(const::ndk::SpAIBinder & token,const VirtualCameraConfiguration & configuration,const std::string & cameraId,const int32_t deviceId,bool * _aidl_return)238 ndk::ScopedAStatus VirtualCameraService::registerCamera(
239     const ::ndk::SpAIBinder& token,
240     const VirtualCameraConfiguration& configuration,
241     const std::string& cameraId, const int32_t deviceId, bool* _aidl_return) {
242   if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
243     return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
244   }
245   return registerCameraNoCheck(token, configuration, cameraId, deviceId,
246                                _aidl_return);
247 }
248 
registerCameraNoCheck(const::ndk::SpAIBinder & token,const VirtualCameraConfiguration & configuration,const std::string & cameraId,const int32_t deviceId,bool * _aidl_return)249 ndk::ScopedAStatus VirtualCameraService::registerCameraNoCheck(
250     const ::ndk::SpAIBinder& token,
251     const VirtualCameraConfiguration& configuration,
252     const std::string& cameraId, const int32_t deviceId, bool* _aidl_return) {
253   if (_aidl_return == nullptr) {
254     return ndk::ScopedAStatus::fromServiceSpecificError(
255         Status::EX_ILLEGAL_ARGUMENT);
256   }
257 
258   if (mVerifyEglExtensions) {
259     auto status = verifyRequiredEglExtensions();
260     if (!status.isOk()) {
261       *_aidl_return = false;
262       return status;
263     }
264   }
265 
266   auto status = validateConfiguration(configuration);
267   if (!status.isOk()) {
268     *_aidl_return = false;
269     return status;
270   }
271 
272   std::lock_guard lock(mLock);
273   if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
274     ALOGE(
275         "Attempt to register camera corresponding to already registered binder "
276         "token: "
277         "0x%" PRIxPTR,
278         reinterpret_cast<uintptr_t>(token.get()));
279     *_aidl_return = false;
280     return ndk::ScopedAStatus::ok();
281   }
282 
283   std::shared_ptr<VirtualCameraDevice> camera =
284       mVirtualCameraProvider->createCamera(configuration, cameraId, deviceId);
285   if (camera == nullptr) {
286     ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
287           reinterpret_cast<uintptr_t>(token.get()));
288     *_aidl_return = false;
289     return ndk::ScopedAStatus::fromServiceSpecificError(
290         Status::EX_SERVICE_SPECIFIC);
291   }
292 
293   mTokenToCameraName[token] = camera->getCameraName();
294   *_aidl_return = true;
295   return ndk::ScopedAStatus::ok();
296 }
297 
unregisterCamera(const::ndk::SpAIBinder & token)298 ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
299     const ::ndk::SpAIBinder& token) {
300   if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
301     ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
302           getpid(), getuid(), kCreateVirtualDevicePermission);
303     return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
304   }
305 
306   std::lock_guard lock(mLock);
307 
308   auto it = mTokenToCameraName.find(token);
309   if (it == mTokenToCameraName.end()) {
310     ALOGE(
311         "Attempt to unregister camera corresponding to unknown binder token: "
312         "0x%" PRIxPTR,
313         reinterpret_cast<uintptr_t>(token.get()));
314     return ndk::ScopedAStatus::ok();
315   }
316 
317   mVirtualCameraProvider->removeCamera(it->second);
318 
319   mTokenToCameraName.erase(it);
320   return ndk::ScopedAStatus::ok();
321 }
322 
getCameraId(const::ndk::SpAIBinder & token,std::string * _aidl_return)323 ndk::ScopedAStatus VirtualCameraService::getCameraId(
324     const ::ndk::SpAIBinder& token, std::string* _aidl_return) {
325   if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
326     ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
327           getpid(), getuid(), kCreateVirtualDevicePermission);
328     return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
329   }
330 
331   if (_aidl_return == nullptr) {
332     return ndk::ScopedAStatus::fromServiceSpecificError(
333         Status::EX_ILLEGAL_ARGUMENT);
334   }
335 
336   auto camera = getCamera(token);
337   if (camera == nullptr) {
338     ALOGE(
339         "Attempt to get camera id corresponding to unknown binder token: "
340         "0x%" PRIxPTR,
341         reinterpret_cast<uintptr_t>(token.get()));
342     return ndk::ScopedAStatus::ok();
343   }
344 
345   *_aidl_return = camera->getCameraId();
346 
347   return ndk::ScopedAStatus::ok();
348 }
349 
getCamera(const::ndk::SpAIBinder & token)350 std::shared_ptr<VirtualCameraDevice> VirtualCameraService::getCamera(
351     const ::ndk::SpAIBinder& token) {
352   if (token == nullptr) {
353     return nullptr;
354   }
355 
356   std::lock_guard lock(mLock);
357   auto it = mTokenToCameraName.find(token);
358   if (it == mTokenToCameraName.end()) {
359     return nullptr;
360   }
361 
362   return mVirtualCameraProvider->getCamera(it->second);
363 }
364 
handleShellCommand(int,int out,int err,const char ** args,uint32_t numArgs)365 binder_status_t VirtualCameraService::handleShellCommand(int, int out, int err,
366                                                          const char** args,
367                                                          uint32_t numArgs) {
368   if (numArgs <= 0) {
369     dprintf(out, kShellCmdHelp);
370     fsync(out);
371     return STATUS_OK;
372   }
373 
374   auto isNullptr = [](const char* ptr) { return ptr == nullptr; };
375   if (args == nullptr || std::any_of(args, args + numArgs, isNullptr)) {
376     return STATUS_BAD_VALUE;
377   }
378 
379   std::variant<CommandWithOptions, std::string> cmdOrErrorMessage =
380       parseCommand(args, numArgs);
381   if (std::holds_alternative<std::string>(cmdOrErrorMessage)) {
382     dprintf(err, "Error: %s\n",
383             std::get<std::string>(cmdOrErrorMessage).c_str());
384     return STATUS_BAD_VALUE;
385   }
386 
387   const CommandWithOptions& cmd =
388       std::get<CommandWithOptions>(cmdOrErrorMessage);
389   binder_status_t status = STATUS_OK;
390   switch (cmd.command) {
391     case Command::HELP:
392       dprintf(out, kShellCmdHelp);
393       break;
394     case Command::ENABLE_TEST_CAMERA:
395       status = enableTestCameraCmd(out, err, cmd.optionToValueMap);
396       break;
397     case Command::DISABLE_TEST_CAMERA:
398       status = disableTestCameraCmd(out);
399       break;
400   }
401 
402   fsync(err);
403   fsync(out);
404   return status;
405 }
406 
enableTestCameraCmd(const int out,const int err,const std::map<std::string,std::string> & options)407 binder_status_t VirtualCameraService::enableTestCameraCmd(
408     const int out, const int err,
409     const std::map<std::string, std::string>& options) {
410   if (mTestCameraToken != nullptr) {
411     dprintf(out, "Test camera is already enabled (%s).\n",
412             getCamera(mTestCameraToken)->getCameraName().c_str());
413     return STATUS_OK;
414   }
415 
416   std::optional<std::string> cameraId;
417   auto it = options.find("camera_id");
418   if (it != options.end()) {
419     cameraId = it->second;
420     if (!cameraId.has_value()) {
421       dprintf(err, "Invalid camera_id: %s", it->second.c_str());
422       return STATUS_BAD_VALUE;
423     }
424   }
425 
426   std::optional<LensFacing> lensFacing;
427   it = options.find("lens_facing");
428   if (it != options.end()) {
429     lensFacing = parseLensFacing(it->second);
430     if (!lensFacing.has_value()) {
431       dprintf(err, "Invalid lens_facing: %s\n, must be front|back|external",
432               it->second.c_str());
433       return STATUS_BAD_VALUE;
434     }
435   }
436 
437   std::optional<int> inputFps;
438   it = options.find("input_fps");
439   if (it != options.end()) {
440     inputFps = parseInt(it->second);
441     if (!inputFps.has_value() || inputFps.value() < 1 ||
442         inputFps.value() > 1000) {
443       dprintf(err, "Invalid input fps: %s\n, must be integer in <1,1000> range.",
444               it->second.c_str());
445       return STATUS_BAD_VALUE;
446     }
447   }
448 
449   std::optional<SensorOrientation> sensorOrientation;
450   std::optional<int> sensorOrientationInt;
451   it = options.find("sensor_orientation");
452   if (it != options.end()) {
453     sensorOrientationInt = parseInt(it->second);
454     switch (sensorOrientationInt.value_or(0)) {
455       case 0:
456         sensorOrientation = SensorOrientation::ORIENTATION_0;
457         break;
458       case 90:
459         sensorOrientation = SensorOrientation::ORIENTATION_90;
460         break;
461       case 180:
462         sensorOrientation = SensorOrientation::ORIENTATION_180;
463         break;
464       case 270:
465         sensorOrientation = SensorOrientation::ORIENTATION_270;
466         break;
467       default:
468         dprintf(err, "Invalid sensor rotation: %s\n, must be 0, 90, 180 or 270.",
469                 it->second.c_str());
470         return STATUS_BAD_VALUE;
471     }
472   }
473 
474   sp<BBinder> token = sp<BBinder>::make();
475   mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
476 
477   bool ret;
478   VirtualCameraConfiguration configuration;
479   configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
480                                                   .height = kVgaHeight,
481                                                   Format::RGBA_8888,
482                                                   .maxFps = kMaxFps});
483   configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
484   configuration.sensorOrientation =
485       sensorOrientation.value_or(SensorOrientation::ORIENTATION_0);
486   configuration.virtualCameraCallback =
487       ndk::SharedRefBase::make<VirtualCameraTestInstance>(
488           inputFps.value_or(kTestCameraDefaultInputFps));
489   registerCameraNoCheck(
490       mTestCameraToken, configuration,
491       cameraId.value_or(std::to_string(sNextIdNumericalPortion++)),
492       kDefaultDeviceId, &ret);
493   if (!ret) {
494     dprintf(err, "Failed to create test camera (error %d)\n", ret);
495     return ret;
496   }
497 
498   dprintf(out, "Successfully registered test camera %s\n",
499           getCamera(mTestCameraToken)->getCameraName().c_str());
500   return STATUS_OK;
501 }
502 
disableTestCameraCmd(const int out)503 binder_status_t VirtualCameraService::disableTestCameraCmd(const int out) {
504   if (mTestCameraToken == nullptr) {
505     dprintf(out, "Test camera is not registered.");
506   }
507   binder_status_t ret = unregisterCamera(mTestCameraToken).getStatus();
508   mTestCameraToken.set(nullptr);
509   return ret;
510 }
511 
512 }  // namespace virtualcamera
513 }  // namespace companion
514 }  // namespace android
515