Reputation: 504
This IOException started happening in my Android app after upgrading to AndroidX and increasing the target SDK to version 28.
Previous to this, the same code was working fine.
It is a Capacitor app, and uses the capacitor-video-recorder plugin. Under the hood, this plugin uses the fancycamera java library which interacts with android.media.MediaRecorder
.
Here is the stack trace, which is thrown when calling VideoRecorder.startRecording
, eventually leading to the call to MediaRecorder.prepare:
I/IMediaRecorder: prepare (BpMediaRecorder client) in file frameworks/av/media/libmedia/IMediaRecorder.cpp, function prepare, line 253
E/MediaRecorder: prepare failed: -2147483648
W/System.err: java.io.IOException: prepare failed.
W/System.err: at android.media.MediaRecorder._prepare(Native Method)
W/System.err: at android.media.MediaRecorder.prepare(MediaRecorder.java:1038)
W/System.err: at co.fitcom.fancycamera.Camera2.setUpMediaRecorder(Camera2.java:607)
W/System.err: at co.fitcom.fancycamera.Camera2.startRecording(Camera2.java:837)
W/System.err: at co.fitcom.fancycamera.FancyCamera.startRecording(FancyCamera.java:323)
W/System.err: at com.github.sbannigan.capacitor.VideoRecorder.startRecording(VideoRecorder.java:267)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99)
W/System.err: at com.getcapacitor.Bridge$1.run(Bridge.java:515)
W/System.err: at android.os.Handler.handleCallback(Handler.java:907)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:105)
W/System.err: at android.os.Looper.loop(Looper.java:216)
W/System.err: at android.os.HandlerThread.run(HandlerThread.java:65)
The Android app is requesting CAMERA and RECORD_AUDIO permissions, and I can confirm that the user is prompted for these first.
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
I can also see that an output file is created in the app's data directory. However the file remains empty. I've also manually tried writing to the same file and it is possible, so file IO does not appear to be the problem.
The crash is coming from a Native Method, so the debugger isn't very helpful. However I can at least confirm that MediaRecorder's mPath
variable appears to be set correctly just before the call to _prepare
.
I have created an example app with an identical stack, where it's possible to recreate the error: https://github.com/disbelief/video-recorder-test
I'd also be interested to hear what other possible reasons there might be for MediaRecorder.prepare
to throw this exception.
Upvotes: 1
Views: 1123
Reputation: 4171
June, 2022
The mistake in my case was very simple. Some phones have Camera resolution as screen size by default but others dont. That is why on many devices it throws prepare failed: -2147483648
That MediaRecorder in depth is that camera itself. Thus you need to pass allowed size . Obviously at best the maximum (to mMediaRecorder.setVideoSize()
)
CameraPropeties props_ = new CameraPropeties(this);
int width_= = props_.getWidth();
int height_ = props_.getHeight();
...
mMediaRecorder.setVideoSize(width_,height_)
...
and CameraPropeties class:
package <my.package>;
import android.content.Context;
import android.content.res.Configuration;
import android.media.CamcorderProfile;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import androidx.annotation.RequiresApi;
import static android.content.Context.WINDOW_SERVICE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
public class CameraPropeties {
private int _width;
private int _hight;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void _getSupportedSizes() {
CameraInfo recordingInfo = _getRecordingInfo();
_width = recordingInfo.width;
_hight = recordingInfo.height;
Log.e("MaxSupportedSizes --", "WIDTH = " + recordingInfo.width + " HEIGHT = " + recordingInfo.height);
}
private CameraInfo _getRecordingInfo() {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) _context.getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getRealMetrics(displayMetrics);
int displayWidth = displayMetrics.widthPixels;
int displayHeight = displayMetrics.heightPixels;
int displayDensity = displayMetrics.densityDpi;
Configuration configuration = _context.getResources().getConfiguration();
boolean isLandscape = configuration.orientation == ORIENTATION_LANDSCAPE;
CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
int cameraWidth = camcorderProfile != null ? camcorderProfile.videoFrameWidth : -1;
int cameraHeight = camcorderProfile != null ? camcorderProfile.videoFrameHeight : -1;
int cameraFrameRate = camcorderProfile != null ? camcorderProfile.videoFrameRate : 30;
return calculateCameraInfo(displayWidth, displayHeight, displayDensity, isLandscape,
cameraWidth, cameraHeight, cameraFrameRate, 100);
}
private Context _context;
public CameraPropeties(Context c) {
_context = c;
_getSupportedSizes();
}
public int getWidth() {
return _width;
}
public int getHeight() {
return _hight;
}
static final class CameraInfo {
final int width;
final int height;
final int frameRate;
final int density;
CameraInfo(int width$, int height$, int frameRate$, int density$) {
this.width = width$;
this.height = height$;
this.frameRate = frameRate$;
this.density = density$;
}
}
static CameraInfo calculateCameraInfo(int displayWidth$, int displayHeight$, int displayDensity$,
boolean isLandscapeDevice$, int cameraWidth$, int cameraHeight$, int cameraFrameRate$,
int sizePercentage$) {
// Scale the display size before any maximum size calculations.
displayWidth$ = displayWidth$ * sizePercentage$ / 100;
displayHeight$ = displayHeight$ * sizePercentage$ / 100;
if (cameraWidth$ == -1 && cameraHeight$ == -1) {
// No cameras. Fall back to the display size.
return new CameraInfo(displayWidth$, displayHeight$, cameraFrameRate$, displayDensity$);
}
int frameWidth_ = isLandscapeDevice$ ? cameraWidth$ : cameraHeight$;
int frameHeight_ = isLandscapeDevice$ ? cameraHeight$ : cameraWidth$;
if (frameWidth_ >= displayWidth$ && frameHeight_ >= displayHeight$) {
// Frame can hold the entire display. Use exact values.
return new CameraInfo(displayWidth$, displayHeight$, cameraFrameRate$, displayDensity$);
}
// Calculate new width or height to preserve aspect ratio.
if (isLandscapeDevice$) {
frameWidth_ = displayWidth$ * frameHeight_ / displayHeight$;
} else {
frameHeight_ = displayHeight$ * frameWidth_ / displayWidth$;
}
return new CameraInfo(frameWidth_, frameHeight_, cameraFrameRate$, displayDensity$);
}
}
Upvotes: 1