今天遇到一个手机录制视频的错误,mx4前置摄像头不能进行录制,后置摄像头ok,错误日志如下:
com.example E/MediaRecorder: start failed: -19
com.example W/System.err: java.lang.RuntimeException: start failed.
com.example W/System.err: at android.media.MediaRecorder.start(Native Method)
com.example W/System.err: at com.example.activity.message.media.CaptureVideoActivity.startRecorderInternal(CaptureVideoActivity.java:448)
com.example W/System.err: at com.example.activity.message.media.CaptureVideoActivity.startRecorder(CaptureVideoActivity.java:406)
com.example W/System.err: at com.example.activity.message.media.CaptureVideoActivity.onClick(CaptureVideoActivity.java:166)
com.example W/System.err: at android.view.View.performClick(View.java:4869)
com.example W/System.err: at android.view.View$PerformClick.run(View.java:20243)
com.example W/System.err: at android.os.Handler.handleCallback(Handler.java:815)
com.example W/System.err: at android.os.Handler.dispatchMessage(Handler.java:104)
com.example W/System.err: at android.os.Looper.loop(Looper.java:194)
com.example W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5590)
com.example W/System.err: at java.lang.reflect.Method.invoke(Native Method)
com.example W/System.err: at java.lang.reflect.Method.invoke(Method.java:372)
com.example W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
com.example W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
com.example D/Camera-JNI: (tid:13619)[native_release] + context=0x9bf6afb0 camera=0x9eb867b0
com.example D/Camera-JNI: [native_release] context->getStrongCount(2) camera->getStrongCount(2)
com.example D/Camera-JNI: (tid:13619)[native_release] - context=0x9bf6afb0 camera=0x0
com.example D/Camera-JNI: (tid:13619)[release camera] - X context=0x9bf6afb0
com.example I/SurfaceView: updateWindow -- OnPreDrawListener, mHaveFrame = true, this = android.view.SurfaceView{19c70f61 V.E..... ........ 0,82-1152,1618 #7f0e0704 app:id/videoView}
com.example D/ColorDrawable: Color = -328966canvas = android.view.GLES20RecordingCanvas@1af8758emTintMode = SRC_INmTint = nullColorDrawable = android.graphics.drawable.ColorDrawable@3832cd03
com.example D/ColorDrawable: Color = -16777216canvas = android.view.GLES20RecordingCanvas@2a207d60mTintMode = SRC_INmTint = nullColorDrawable = android.graphics.drawable.ColorDrawable@fe26d27
com.example D/IMGSRV: gralloc_register_buffer:1390: hnd=0x9a9c6be0 ID=72118 fd=135 ref=1
com.example D/GraphicBuffer: register, handle(0x9a9c6be0) (w:648 h:105 s:672 f:0x1 u:0x000b00)
com.example D/Camera-JNI: (tid:13848)[~MtkJNICameraContext] this:0x9bf6afb0
com.example D/OpenGLRenderer: Flushing caches (mode 0)
com.example D/GraphicBuffer: unregister, handle(0x9a9c6be0) (w:648 h:105 s:672 f:0x1 u:0x000b00)
com.example D/IMGSRV: gralloc_unregister_buffer:1503: ID=72118 ref=0
com.example D/OpenGLRenderer: Flushing caches (mode 0)
从日志中只能看出打开失败,并且错误码19,但是根本不知道19代表什么? 因为MediaRecorder.start函数是一个native函数,必须要进入native才知道19代表了什么!
我们进入native的start函数看看查看链接:
static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
ALOGV("start");
sp mr = getMediaRecorder(env, thiz);
process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}
static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const char* exception, const char* message)
{
ALOGV("process_media_recorder_call");
if (opStatus == (status_t)INVALID_OPERATION) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return true;
} else if (opStatus != (status_t)OK) {
jniThrowException(env, exception, message);
return true;
}
return false;
}
可以看到如果opStatus不等于ok则抛出异常,则我们需要查看opStatus的值为多少。opStatus是有mr->start()调用得出的。我们看看mr-start函数:
status_t MediaRecorder::start()
{
ALOGV("start");
if (mMediaRecorder == NULL) {
ALOGE("media recorder is not initialized yet");
return INVALID_OPERATION;
}
if (!(mCurrentState & MEDIA_RECORDER_PREPARED)) {
ALOGE("start called in an invalid state: %d", mCurrentState);
return INVALID_OPERATION;
}
status_t ret = mMediaRecorder->start();
if (OK != ret) {
ALOGE("start failed: %d", ret);
mCurrentState = MEDIA_RECORDER_ERROR;
return ret;
}
mCurrentState = MEDIA_RECORDER_RECORDING;
return ret;
}
经过一系列调用最终到到了MediaPlayerService.cpp中查看链接。
status_t MediaPlayerService::Client::start()
{
ALOGV("[%d] start", mConnId);
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
p->setLooping(mLoop);
return p->start();
}
到此又调用了MediaPlayerBase的start函数。由于代码是在繁多,等有空再read the fuck source code。换条道路进行问题解决。
因此只好排查代码了,不过在排查之前先google了一把,把错误范围定位在了设置参数,在start之前我们我们设置了MediaRecorder的一些参数,设置参数如下:
private CamcorderProfile getProfile(int cameraId, int quality) {
if (CamcorderProfile.hasProfile(cameraId, quality)) {
return CamcorderProfile.get(cameraId, quality);
}
return null;
}
@SuppressLint("NewApi")
private void setCamcorderProfile() {
CamcorderProfile profile = getProfile(cameraId, CamcorderProfile.QUALITY_CIF);
if (profile == null) {
profile = getProfile(cameraId, CamcorderProfile.QUALITY_LOW);
}
if (profile != null) {
profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
if (Build.MODEL.equalsIgnoreCase("MB525") || Build.MODEL.equalsIgnoreCase("C8812") || Build.MODEL
.equalsIgnoreCase("C8650")) {
profile.videoCodec = MediaRecorder.VideoEncoder.H263;
} else {
profile.videoCodec = MediaRecorder.VideoEncoder.H264;
}
if (android.os.Build.VERSION.SDK_INT >= 14) {
profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
} else {
if (android.os.Build.DISPLAY != null && android.os.Build.DISPLAY.indexOf("MIUI") >= 0) {
profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
} else {
profile.audioCodec = MediaRecorder.AudioEncoder.AMR_NB;
}
}
mediaRecorder.setProfile(profile);
} else {
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
}
}
首先根据期望的显示效果获取CamcorderProfile,调整视频的输出格式,音频编码,视频编码,最后调用MediaRecorder.setProfile(profile)将调整后的参数进行设置。setProfile的代码如下:
/**
* Uses the settings from a CamcorderProfile object for recording. This method should
* be called after the video AND audio sources are set, and before setOutputFile().
* If a time lapse CamcorderProfile is used, audio related source or recording
* parameters are ignored.
*
* @param profile the CamcorderProfile to use
* @see android.media.CamcorderProfile
*/
public void setProfile(CamcorderProfile profile) {
setOutputFormat(profile.fileFormat);
setVideoFrameRate(profile.videoFrameRate);
setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
setVideoEncodingBitRate(profile.videoBitRate);
setVideoEncoder(profile.videoCodec);
if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW &&
profile.quality <= CamcorderProfile.QUALITY_TIME_LAPSE_QVGA) {
// Nothing needs to be done. Call to setCaptureRate() enables
// time lapse video recording.
} else {
setAudioEncodingBitRate(profile.audioBitRate);
setAudioChannels(profile.audioChannels);
setAudioSamplingRate(profile.audioSampleRate);
setAudioEncoder(profile.audioCodec);
}
}
网上的答案都是说因为setVideoSize导致错误,但是目前手里的手机只有mx4会前置摄像头错误,因此注释掉setVideoSize的地方重新测试。注释后,录制ok。
Android就是一个大坑啊!profile是从系统获取的值,再将该值设置到MediaRecorder,居然系统获取的值不能使用。。。。
@SuppressLint("NewApi")
private void setCamcorderProfile() {
CamcorderProfile profile = getProfile(cameraId, CamcorderProfile.QUALITY_CIF);
if (profile == null) {
profile = getProfile(cameraId, CamcorderProfile.QUALITY_LOW);
}
if (profile != null) {
profile.videoCodec = CameraUtil.getVideoCodec();
profile.audioCodec = CameraUtil.getAudioCodec();
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoFrameRate(profile.videoFrameRate);
// mx4 前置摄像头尺寸设置后,硬件不识别,导致打开失败
if (Build.MODEL.equalsIgnoreCase("MX4") && cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
} else {
mediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
}
mediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
if (profile.quality >= CamcorderProfile.QUALITY_TIME_LAPSE_LOW && profile.quality <= CamcorderProfile
.QUALITY_TIME_LAPSE_QVGA) {
// nothing to do
} else {
mediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
mediaRecorder.setAudioChannels(profile.audioChannels);
mediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
mediaRecorder.setAudioEncoder(profile.audioCodec);
}
} else {
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
}
}
根据出现的问题和表现,具体的错误原因我大致推测是因为设置的录制宽高与硬件所支持的宽高不符合,导致录制错误。具体还需要跟进代码查找真正的原因。
网上说setVideoFrameRate也会导致出现上诉的错误,不过目前手里的机器还未出现。