I Just Wanted Clean HDMI on a Note 10+ (Telephoto)

This week's obsession came when I decided I wanted to repurpose my recently repaired Samsung Galaxy Note S10+ as an overhead camera for the workbench. Its image sensors vastly outperform any ordinary webcam, including my $200 Razer Kio Pro. The distance from my homemade ceiling mount to the workbench tabletop makes the 2x optical zoom of the Note 10+ a perfect fit. But in all the years I owned this phone, I never realized that only the native Samsung Camera app allowed use of its telephoto lens. All other applications, free or paid, are only able to interface with the front-facing cameras, wide angle and standard 1x back lens.

Unlucky for me but fortunately for everyone with slightly newer models, Samsung finally introduced clean HDMI out with their native Samsung Camera app as part of the One UI 5.0 release. Note 10+ and lower are ineligible for the upgrade. My Note 10+ is currently running One UI 4.1 and Android OS 12 with kernel 4.14, all of which are pretty much end-of-life for the phone beyond security patches.

This led me down the rabbit hole of discovery that Samsung's native camera service explicitly whitelists particular package bundle identifiers. All others including those for sale on Google Play will be unable to tap into specific lens IDs and raw configurations, including the telephoto camera on the back. Analysis of the internal camera manager shows that it contains a dozen or more hidden package names that are granted open access to all camera IDs. This includes first-party Samsung applications like Quick Measure (Ruler) and 3D Scanner (Scan3D), as well as the built-in diagnostic check that you can reach by dialing *#0*#. Depending on your model Samsung phone, any of the following may be whitelisted:

(Credit to the folks at XDA Developer forums as well as jnlm for parsing through the native Samsung Camera interface to extract those IDs.)

With that knowledge, it becomes possible to mimic one of the whitelisted packages to create a custom camera app that can use all available lenses. The amazing jailbreak community at XDA also have a running effort of porting Google Camera (GCam) to non-Google devices and take this approach to support as many native camera sensors and characteristics as possible. This package spoofing approach eliminates the need for rooting or jailbreaking the phone, which on some sets like my Snapdragon SM-N975U have severely limited and often disadvantageous rooting capabilities, if at all.

The only requirement for this to work is that you uninstall the original app that consumes the shared package name. For Samsung, the most commonly exploited package is com.samsung.android.scan3d, which can easily be installed and uninstalled from the Galaxy Store. You will often find patched Google Camera bundles that use the com.samsung.android.scan3d package name. I found on my 10+ that only the Scan3D and Ruler applications could be natively uninstalled. With Scan3D uninstalled, I was able to one-click install the MGC 8.7.250 package directly through the Note 10+ and move a tailored config file to the GCam directory for near flawless operation of the unsupported application (at least all three back cameras are functional on it; front-facing require additional configs/considerations).

Running a modified version of Google Camera (GCam) on a Samsung Galaxy Note S10+, with telephoto lens support via package spoofing.

While GCam has some amazing qualities to it and the modified version works well, there is no easy way that I'm aware of to hide the UI, as required if I plan to stream the display to an OBS input source for video capture. Almost every camera app I tried did not contain a "clean HDMI" or "hide UI" feature at all. Filmic Pro is commonly used for this, but they just switched their business model to subscription-based, with predictable user outlash and scathing reviews, and it still wouldn't support telephoto on the Note 10+ so is meaningless for my use...though I'm still bummed out that I had purchased the full version for iOS this past year and now they are flipping to a reoccurring subscription plan, not practical when I may go weeks or months without needing to use it.

Camera API Complexities

The sheer diversity of Android devices and massively wide-ranging OS support (from Android 5.0 through 13+ still in active use) has made it a wild west in the world of camera support. The large variety of target APIs/SDKs, logical and physical cameras, custom manufacturer ROMs and endless other variables play into the complexity of developing universal camera applications. Originally, it was not as complicated when Android had their vanilla Camera SDK, which provided a basic universal means of interfacing with the default FRONT and REAR cameras. But as phones and tablets evolved, so did the number of cameras they included and unique device-specific characteristics.

This in part led to most manufacturers creating hidden and private APIs as part of their native applications and firmware, so that their own software could interface correctly with all attached cameras while outside developers would only see a singular ID for use, correlating to one of the physical cameras. Google eliminated private APIs starting with Android 9, so as an alternative introduced the exceptionally sophisticated Camera2 API for logical and physical multi-camera interfacing. Later on they also rolled out the CameraX Library to help reduce the exhaustion of implementing Camera2 components.

Google: "Camera capabilities typically only available to privileged applications."

I have no intention of diving heavy into the world of dedicated camera application development anytime soon, I just need a way to interface with my 2x telephoto lens without a UI so that screen sharing or HDMI out of my phone will offer me a usable camera for my workbench! But even experts on the topic admit the Camera2 API, needed for the lowest level interfacing, can be a challenge to wrap your head around as it relies on fragments and texture mappings and other advanced control elements. Thankfully there are a couple excellent foundations that I can piggyback off of to achieve my simple intention, without investing months of life toward learning the camera API with no practical need to know the ins-and-outs at this point.

Open Camera

Since 2013, Mark Harman has been developing an incredible open source Android camera product called Open Camera. Prebuilt APKs are available including from Google Play, but you can also grab the source code for any version from SourceForge.net. It has been an ambitious project given how many different evolutions the Android camera APIs have experienced including the entire introduction of Camera2 and Multi-Camera interfacing. It has a massive list of features and capabilities including immersive mode for hiding the UI, Bluetooth and remote operations.

New builds of Open Camera are released every few months so the latest build has no trouble compiling within Android Studio. One annoyance found in so many Github projects is that the target SDKs and API levels rapidly grow obsolete. Google has taken even more sweeping steps in recent years to mandate newer APIs for Google Play releases, as of February 2023 all Google Play releases have to be built against Target API Level 31(Android 12). One downside to the Open Camera approach is that the application is so complex for my needs, and therefore may require more reworking to retrofit for use as a simple dummy camera out display.

Camera Samples Repository

An alternative that I found inviting is Google's own Camera Samples on Github. They offer self-contained examples spanning from Camera2 and CameraX basics through more advanced interfacing. The Camera2Basic repository is one of the most stripped down demonstrations of the Camera2 API around, while still covering all of its core features.  This example is comprised of only the following core components:

  • fragments
    • CameraFragment
    • ImageViewerFragment
    • PermissionsFragment
    • SelectorFrament
  • CameraActivity
  • activity_camera.xml
  • fragment_camera.xml

The benefit of modifying this sample for my use with the hidden telephoto camera ID is that it is almost already exactly what I need with a minimal interface and simple on-screen camera viewer. By contrast, Open Camera has hundreds of features and customization options and is truly designed as a normal camera replacement application and most of those features are irrelevant to my use case.

Regardless of which option we use, or even if we wanted to start from scratch, we will need to spoof the package to one of the whitelisted identifiers seen above.

Camera ID Detection and Package Considerations

As discussed, the main requirement for engaging the hidden camera ID(s) is to use one of the package names that are already on the whitelist within the operating system's camera manager. Since the Google Camera (GCam) mods for the 10+ are largely pre-built around the com.samsung.android.scan3d identifier, I would rather my "basic clean HDMI out with telephoto lens" hack app be something different so I can keep GCam installed. The "Quick Measure" tool was able to easily be uninstalled, so I will use com.samsung.android.ruler for this mod.

As a basic test if you'd like to compare the non-whitelist identifiers and all camera properties with those that can be retrieved when using a whitelisted package, you can grab the CameraHW package off of Github. This iterates at a low-level through all identifiable camera characteristics and allows you to export the results to a text file. You may need to update the target and build APIs and a few other alterations to get it to compile since it has been more than two years since the last commit.

For the reader's convenience, I have upgraded all the necessary files and target API levels to produce clean APKs of this CameraHW app using its default identifier, and two whitelisted ones:

For reference, below are the output text files when running the CameraHW application in its default state, and with the spoofed package name.

Running the application with a non-whitelisted package name (com.rakin.vibhorcameraids) returns five accessible camera IDs: [0, 1, 2, 3, 4] but of these, only 0, 1, 2 and 3 are operational.

  • ID 0: Physical Back Main
  • ID 1: Physical Front Wide
  • ID 2: Physical Back Wide Angle
  • ID 3: Physical Front Main
  • ID 4: Physical Back Telephoto (Inaccessible)

However, recompiling the package to a whitelisted variant (com.samsung.android.ruler) shows 14 potential camera IDs: [0, 1, 2, 3, 4, 20, 21, 23, 40, 41, 50, 52, 80, 91] but of course a lot of these are inoperable combinations or duplicates. We are still really just after the telephoto identifier for my intent.

  • ID 0-4: Same as above.
  • ID 20: Logical Back (0 + 50 + 52) ← Indistinguishable / Unavailable
  • ID 21: Logical Back (0 + 52) ← Telephoto
  • ID 23: Logical Back (0 + 50) ← Indistinguishable / Unavailable
  • ID 40: Physical Back Repeat ← ID 20
  • ID 41: Physical Back Repeat ← Crash
  • ID 52: Physical Back Repeat ← ID 21 
  • ID 80 Physical Back Repeat ← ID 23 / Crash
  • ID 91: Physical Front Repeat← Crash

After some testing, I determined that ID 21 (or ID 52) will trigger the telephoto 2x lens I'm after. The other ones were a mix of replicating the original physical cameras or crashing. Using the CameraHW project and swapping out the package name with a whitelisted alternative is a very quick way to evaluate how many hidden camera IDs exist under the hood.

Modifying Package Names the Easy Way

Android applications are comprised of one or more packages that are generally structured from a matching folder hierarchy.

  • The AndroidManifest.xml file includes the main package name (e.g., "com.example.android.camera2.basic")
  • The application's build.gradle file includes an applicationId property as part of its defaultConfig (e.g., "com.example.android.camera2.basic")
  • The actual Java/Kotlin source package is within a matched folder structure (e.g., "java/com/example/android/camera2/basic/*.*")
After renaming the original Open Camera package name to match the whitelisted one required by Samsung, the hierarchy looks like this.

There is no one-click solution to doing a full package rename and the various guides you'll find online are all over the map. The most widespread suggested solution is to disable "Compact Middle Packages" in the tree appearance properties of the Resource Manager, and then individually right-click and Refactor → Rename each part of the package hierarchy. After that, you have to commit the changes via "Do Refactor" and subsequently alter the manifest/gradle configurations. Even then, you are likely to encounter problems with the .R generated companion files and may find other straggling references to the original name throughout the code.

After a lot of frustration with these methods, I find the following solution to be way more direct and fool-proof and actually takes less time over-all. Instructions are based on Windows, but are also applicable to other platforms. Essentially you just want to do a global find/replace of the package name across all of the project files, then manually rename the package(s) to match the necessary hierarchy (using your operating system's file explorer, not Android Studio), then clean caches and rebuild.

  1. Open the original project in Android Studio, build it and make sure it runs using its existing configuration (e.g., "net.sourceforge.opencamera" for Open Camera)
  2. Press CTRL+SHIFT+R and do a global Find/Replace to replace the package name with the desired alternative (FIND: "net.sourceforge.opencamera" REPLACE: "com.samsung.android.ruler")
    1. For the search scope, select Directory and then point to the root project folder to ensure all files are searched.
  3. Select Build → Clean Project and then close Android Studio.
  4. Open Windows Explorer and navigate to the project folder. Now, rename each part of the package with the matching alternative, one level at a time beginning at the root package.

In instances where you are creating a deeper or shallower level package name (e.g., "x.y.z" vs. "w.x.y.z") you will have to merge the files accordingly. An example is given below, if converting the Open Camera package to the whitelisted Ruler alternative:

  1. Navigate to the "app/src/main/java" directory of the project to find the main package root.
  2. Rename "net" to "com" then enter that directory.
  3. Rename "sourceforge" to "samsung" then enter that directory.
  4. Rename "opencamera" to "android" then enter that directory.
  5. Highlight all of the original contents and cut them, create a new folder "ruler" then enter that directory.
  6. Paste the previously copied contents into that folder.
  7. Repeat steps 2-6 for "app/src/androidTest" and "app/src/test" if necessary.

After you have renamed the package(s), reopen the project in Android Studio. Then:

  1. Right-click on the root "app" directory in the Project panel and select "Reload from Disk" to update all open files to reflect any changes.
  2. Select File →  Invalidate Caches and check all options then click "Invalidate and Restart." This step will clean out any remnants of the ADB launch processes that would still otherwise try to call the old identifier on Run/Debug.
  3. After restarting, wait for the contents to download and sync. Then do a Build → Clean and Build → Rebuild Project.

If all went well, when you try to Run or Debug the application it should run fine but now using the whitelisted identifier. If you have any conflict, check the console output. Sometimes you may want to re-open the build.gradle file and change one property or another, even the versionCode or versionName, and then click "Sync Now" to refresh all of that.

Editing the Projects to Support Telephoto Lens

If you have followed along, you'll now have a project that is capable of being installed on your phone with all phone IDs exposed for use. The remaining task is to actually modify whatever project you are using to accommodate the extra camera(s). This is a very specific process based on your phone's characteristics, so I will only offer a few observations here.

The Camera Manager's getCameraIdList() and related functionality will still only see the publicly exposed camera IDs, so is not an accurate interfacing mechanism if you are trying to use an undocumented camera ID. In the case of the Note 10+, relying on this list even after spoofing the package name will only get you to ID 3 before crashing and ID 4 overall, so obviously ID 21 is unobtainable from this list alone.

As indicated, not all discovered logical and hidden camera IDs will run natively without crashing, while some are duplicates or for particular implementations that have no useful purpose to the outside world. You will want to test each returned camera ID to determine which ones are beneficial. For my Samsung Note 10+, the following IDs are practical and directly correspond to the physical cameras:

  • Front Facing Cameras
    • ID 1: Physical Front Wide Lens
    • ID 3: Physical Front Main Lens
  • Back Facing Cameras
    • ID 0: Physical Back Main Lens
    • ID 2: Physical Back Wide Angle Lens
    • ID 21: Physical Back Telephoto Lens

With this hammered out, it becomes a matter of passing the correct IDs to the app in question. However elegant you want to get is up to you.

The typical strategy would be to determine where in the code the camera list is being generated and then manually add the additional ID(s) that won't otherwise show up. Usually you can follow this through the code until referenced by the actual SetCamera() or related functionality, which will pass a string-based camera ID to initiate it.

In the Camera2Basic application...

Around line 85 of the SelectorFragment.kt is a function that iterates through the camera list and addes them to the selector:

This function checks for the current camera ID's supported image format(s) as well as front/back orientation, then uses a FormatItem(title: String, cameraId: String, format: Int) helper function to add each row to the selector list. To add the telephoto lens to the beginning of the list, I simply add the line below before the foreach iteration begins.

And while this telephoto camera also supports ImageFormat.RAW_SENSOR (or RAW_PRIVATE) data, without performing any post-processing on it the appearance is actually more graining than the computed JPEG, so I'll stick with that.

Since I will only ever be using the back camera lenses for this overhead workbench setup, and this modified version is tailored specifically to my own Samsung phone, the entirety of the enumeration is unnecessary. I can replace all of the list data with the following:

And now when I run the application, I can toggle any of those three without any others being in my way. If you only want to test the available camera IDs with this utility to verify the output, you can instead find the call to openCamera() around line 200 of CameraFragment.kt to alter. Simply replace args.cameraId with a string literal, such as "21" to test camera ID 21.

After this, for my use case I also hid the navigation bar on startup but it can always be accessed by swiping. I set the visibility of the shutter button the example provided to gone (so as not to remove any of that underlying functionality, in the rare chance I'd want to utilize that for something else including as an invisible fast-switch between cameras.) Finally I set it to automatically select the first list item (telephoto lens) and switch to that, so I can just fire up the app or keep it running to have it always streaming the telephoto view to my capture devices. Ultimately, I may do a little more with this so that tapping anywhere on the camera view will cycle the lens modes, but for now I'm good.

In the Open Camera application...

The core explanation and principles described for Camera2Basic apply here as well, except there are many more components and potential pitfalls to be aware of. You'll want to pay attention to the userSwitchToCamera(int cameraId) and clickedSwitchCamera(View view) functions in MainActivity.java.

As part of iterating through the cameras, special care is needed within the getNextCameraId() and preview.getCameraControllerManager().getNumberOfCameras(); The GetNumberOfCameras() in particular, within CameraControllerManager2.java, will return only the list of public camera IDs. This goes back to the original conundrum. Open Camera actually utilizes the returned value to check for camera validity, so you will need to do a bit of work around it.

You can see where the GetNumberOfCameras() functionality will cause problems if left unmodified from the Preview.java class, in the all-important setCamera(int cameraId) function.

It explicitly checks that the currently passed camera ID is between 0 and the total number of cameras returned by getNumberOfCameras(). If invalid, which would be the case if attempting to pass the hidden telephoto ID, it defaults to resetting the camera ID to 0.

After successful modification of this application, the HUD/UI can be hidden completely via Immersive Mode in the options; tap anywhere on the screen to bring back the UI at any time.

Leave a Reply

Time limit is exhausted. Please reload the CAPTCHA.