At GameChanger, video streaming has become a huge part of our business and thus our tech stack. But as a small company that practices shipping often, we can’t ship everything feature complete from day one and thus video streaming launched with the ability to only stream from your default rear camera lens.
But as we know, ultrawide lenses on phones have become common place and sure enough, customers began writing in, asking to be able to use their ultrawide cameras to stream their event. Baseball and softball fields are actually quite wide and it makes a lot of sense to be able to capture more of the field. So in time, ultrawide streaming became our priority and thus we engaged in battle with one of the most brittle Android APIs we have seen…
Streaming in the olden days
Well, not really in the olden days, because we are using the most up to date APIs, but before we implemented ultrawide streaming, selecting the camera we wanted to stream with was generally pretty simple:
TL;DR: Basically, get the first rear camera that supports 720p. Note the cameraId
—an ID corresponds to each camera on the device…right?
Nope. Well sometimes, it depends.
Enter Multi-Camera API
At the time of writing, the not-deprecated API for accessing cameras on Android is camera2
. camera1
is deprecated. cameraX
is built on top of camera2
. Obviously.
Here are some references for camera2
. We are going to focus on the multi-camera training here as a jumping off point.
The multi-camera training page does a great job of explaining the differences between logical and physical camera setups, when it was introduced and why but for the purposes of this article here’s what you need to know:
- An Android device running above API level 28 runs either a logical or physical camera setup. Below 28 is strictly a physical camera setup.
- Physical camera setups expose each camera sensor individually with
cameraId
s throughcameraManager.cameraIdList
. If you are lucky, you will have one camera id per physical sensor and be able to choose any id you want to stream with. - Logical camera setups hide the details of the different physical cameras sensors on the back side of the phone, giving you just one id for the front and back of the device in the
cameraManager.cameraIdList
. However, if you continue to poke the camera API, you can get those physical sensor ids, but you still can’t use them to open a camera session. You must use ids fromcameraManager.cameraIdList
. Thus, to actually stream with an ultrawide sensor on a logical camera setup, you have to do more…things.
Okay, doesn’t sound too bad. It’s easy enough to figure out if a device is a physical or logical camera setup:
So let’s start with the easy one.
Supporting physical camera setups
Once we know that we are dealing with a physical camera setup, it’s simply a matter of iterating over rear ids and calculating the widest one. Our camera feature only exposes the default and the widest sensor to the user, so this is the logic that works for us:
Note that we basically ripped the widest calculation logic from various SO posts. Here’s one that offers a good explanation of what’s going on there.
This logic along with the original logic to fetch the default rear camera yields two camera ids. Switching between them is just restarting your preview/capture session with the new id.
Supporting logical camera setups
Okay, we have to jump through a few more hoops when supporting logical camera setups. Once we have determined we do have a logical camera setup present, we have to determine which rear camera id has the logical cameras behind it:
Now we have a list of rear camera ids that have logical multi camera capabilities. This means that this camera id is a logical camera id. This means that this logical camera id has 2 or more physical camera ids behind it. We need those to address individual lenses. This is how we get them:
Now we have physical ids paired up with their logical id. Now we need to figure out the widest lens of the physical ones. This is easier than the physical setup, because now we have LENS_INFO_AVAILABLE_FOCAL_LENGTHS
available to us:
Exhausted yet? Finally, logical camera setups require you to set a zoom ratio to get the widest focal length. We get the number like so:
Putting it all together:
So we have the correct id to open the rear camera session with and a control zoom ratio. The capture request is built the same but now we use the control zoom ratio:
Note that you don’t use physicalCameraIds
to actually open a camera session. With logical camera setups, you still use a camera id found in cameraManager.cameraIdList
to open a camera session. You then just give it a minimum zoom control ratio. Then the OS itself takes care of selecting the widest lens to reach the desired zoom control.
Gotcha!
Okay, so code stuff out of the way. Figuring all the correct ways of doing this was tough as there are not a lot of code samples out there. But there is one…
OpenCamera. OpenCamera is a highly featured, open source camera app. And it includes support for physical and logical multi camera setups! Great, a perfect reference.
So I install OpenCamera on my OnePlus 7 Pro and it seamlessly switches between wide and ultrawide lenses. So a couple cmd+c, cmd+v strokes from the OpenCamera source later I had the multi-camera implementation inside the TeamManager app. And…it didn’t work. cameraManager.cameraIdList
showed only the front camera and the rear standard lenses in my app (Note this is a physical setup). But in the OpenCamera app, the same API call cameraManager.cameraIdList
showed the front camera, rear standard and rear ultrawide.
This really threw us for a loop. For whatever reason, the OpenCamera package name was white listed and thus allowed to access more camera ids.* Why? We aren’t sure. Just OnePlus things, amirite? But what it means is that our app can not support wide angle streaming for OnePlus devices.
*I can’t find where I found this anymore, but it was buried deep in a SO post. Took us a couple of days at least to find out.
And this was just the tip of the iceberg for dealing with manufacturers’ implementation…
At the mercy of the manufacturers
Reading this whole article, you may ask, how do we know which phones support which setup? Well, the short answer is that we have no idea. Here’s a short list of what we have found so far, if the device has an ultrawide rear lens:
OnePlus devices: Physical setup that doesn’t expose ultrawide to our app, does expose ultrawide to OpenCamera. Ultrawide works in native camera app.
Motorola devices: Physical setup that doesn’t expose ultrawide to any app. Ultrawide works in native camera app.
Samsung devices: Physical setup that exposes standard rear and ultrawide. Ultrawide works in native camera app and OpenCamera. We were able to support Samsung devices.
Pixel devices: Logical setup, but only the Pixel 5 has an ultrawide. The Pixel 4 has a standard and telephoto. So we needed to check if the device has a logical rear camera that is wider than the default camera. Pixels are the only devices we have found that support logical setups.
And these are just the ones we know about! We don’t have every device in the world and this can change with software updates and new devices.
As you can see, how each manufacturer decides to implement the multi-camera API is completely random and illogical. We ended not being able to support as many devices as we thought when the project was conceived. It is very disappointing to see the state of the multi camera API as manufacturers implement it. Especially considering how many devices are being built with multiple lenses.
But hey, I think we are “future-proofed”, whatever that means. Until the multi-camera API is deprecated anyway…