使用 Android MediaRecorder 录制 webm
Recording webm with Android MediaRecorder
我在从 mp4 录制切换到 webm 录制时遇到了一些问题。
此代码以 24fps 和 640x480 分辨率录制 mp4 音频
private boolean prepareVideoRecorder(){
// BEGIN_INCLUDE (configure_preview)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if( ( checkSelfPermission(android.Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED ) &&
(checkSelfPermission(android.Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED ) &&
(checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED ) )
{
Log.v(TAG,"Permission is granted");
} else {
Log.v(TAG, "Permission is revoked");
//ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, 1);
ActivityCompat.requestPermissions(this, new String[]{
android.Manifest.permission.CAMERA,
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);
}
}
mCamera = CameraHelper.getDefaultCameraInstance();
if( mCamera == null )
{
return false;
}
// We need to make sure that our preview and recording video size are supported by the
// camera. Query camera to find all the sizes and choose the optimal size given the
// dimensions of our preview surface.
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
int rotationCorrected = correctCameraRotation();
parameters.setRotation( rotationCorrected );
Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
mSupportedPreviewSizes, mPreview.getWidth(), mPreview.getHeight());
// Use the same size for recording profile.
parameters.setPreviewSize( optimalSize.width, optimalSize.height);
mCamera.setParameters(parameters);
try {
// Requires API level 11+, For backward compatibility use {@link setPreviewDisplay}
// with {@link SurfaceView}
mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
} catch (IOException e) {
Log.e(TAG, "Surface texture is unavailable or unsuitable" + e.getMessage());
return false;
}
// Step 1: Unlock and set camera to MediaRecorder
mMediaRecorder = new MediaRecorder();
try
{
mCamera.unlock();
try {
mMediaRecorder.setCamera(mCamera);
}catch( Exception ex )
{
Log.e(TAG, "I don't know what happened... " + ex.getMessage());
}
// Step 2: Set sources mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT );
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
profile.videoFrameWidth = optimalSize.width;
profile.videoFrameHeight = optimalSize.height;
profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
profile.videoCodec = MediaRecorder.VideoEncoder.MPEG_4_SP;
profile.videoFrameRate = 24;
// reduce audio quality
profile.audioBitRate = 16000;
profile.audioChannels = 1;
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
/* This method should be called after the video AND audio sources
are set, and before setOutputFile() */
mMediaRecorder.setProfile(profile);
mMediaRecorder.setOrientationHint( rotationCorrected );
// Step 4: Set output file
mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
if (mOutputFile == null) {
return false;
}
mMediaRecorder.setOutputFile(mOutputFile.getPath());
// END_INCLUDE (configure_media_recorder)
}
catch( IllegalStateException ex )
{
Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + ex.getMessage());
return false;
}
catch( Exception ex )
{
Log.e(TAG, "I don't know what happened... " + ex.getMessage());
return false;
}
// more stuff here
}
所以,当我尝试将输出格式更改为 webm 时,我有这个
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
profile.videoFrameWidth = optimalSize.width;
profile.videoFrameHeight = optimalSize.height;
// FIXME mp4 to webm
profile.fileFormat = MediaRecorder.OutputFormat.WEBM;
profile.videoCodec = MediaRecorder.VideoEncoder.VP8;
profile.audioCodec = MediaRecorder.AudioEncoder.AMR_NB;
//profile.videoFrameRate = 24;
// reduce audio quality
//profile.audioBitRate = 16000;
//profile.audioChannels = 1;
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
/* This method should be called after the video AND audio sources
are set, and before setOutputFile() */
mMediaRecorder.setProfile(profile);
并且 setProfile 方法抛出一个 IllegalStateException,上面写着 E/MediaRecorder:输出格式 (9) 仅用于音频录制,与视频录制不兼容。
谷歌搜索这条消息我到达这里:
- https://android.googlesource.com/platform/frameworks/av/+/114819633470ebd5b346c13c2a82a0025d2d39c0/media/libmedia/mediarecorder.cpp
- https://android.googlesource.com/platform/frameworks/av/+/114819633470ebd5b346c13c2a82a0025d2d39c0/include/media/mediarecorder.h
所以,我的问题是我的 profile.fileFormat = MediaRecorder.OutputFormat.WEBM 触发了这个
if (mIsVideoSourceSet
&& of >= OUTPUT_FORMAT_AUDIO_ONLY_START //first non-video output format
&& of < OUTPUT_FORMAT_AUDIO_ONLY_END) {
ALOGE("output format (%d) is meant for audio recording only"
" and incompatible with video recording", of);
return INVALID_OPERATION;
}
查看 OUTPUT_FORMAT_AUDIO_ONLY_START 和 OUTPUT_FORMAT_AUDIO_ONLY_END 值,我可以看到 OUTPUT_FORMAT_AUDIO_ONLY_END 应该是 9,而在 MediaRecorder.java 中,Webm 格式是
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
如何将视频直接录制到 webm?
嗯,好像是AndroidSDK版本的问题
IllegalStateException 出现在带有 SDK 17 的设备上。运行相同的应用程序在带有 SDK 23 的设备上工作正常,我可以录制 webm 视频。
但是没有声音:(
据我所知,WebM容器不支持AMR-NB语音编解码。它仅支持 Vorbis 和 Opus 音频编解码器。看这里:https://www.webmproject.org/about/faq/
为什么您不想使用 WebM 的 Vorbis 音频编解码器(Opus 用于语音)?那是合乎逻辑的。
我在从 mp4 录制切换到 webm 录制时遇到了一些问题。
此代码以 24fps 和 640x480 分辨率录制 mp4 音频
private boolean prepareVideoRecorder(){
// BEGIN_INCLUDE (configure_preview)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if( ( checkSelfPermission(android.Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED ) &&
(checkSelfPermission(android.Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED ) &&
(checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED ) )
{
Log.v(TAG,"Permission is granted");
} else {
Log.v(TAG, "Permission is revoked");
//ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, 1);
ActivityCompat.requestPermissions(this, new String[]{
android.Manifest.permission.CAMERA,
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);
}
}
mCamera = CameraHelper.getDefaultCameraInstance();
if( mCamera == null )
{
return false;
}
// We need to make sure that our preview and recording video size are supported by the
// camera. Query camera to find all the sizes and choose the optimal size given the
// dimensions of our preview surface.
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
int rotationCorrected = correctCameraRotation();
parameters.setRotation( rotationCorrected );
Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
mSupportedPreviewSizes, mPreview.getWidth(), mPreview.getHeight());
// Use the same size for recording profile.
parameters.setPreviewSize( optimalSize.width, optimalSize.height);
mCamera.setParameters(parameters);
try {
// Requires API level 11+, For backward compatibility use {@link setPreviewDisplay}
// with {@link SurfaceView}
mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
} catch (IOException e) {
Log.e(TAG, "Surface texture is unavailable or unsuitable" + e.getMessage());
return false;
}
// Step 1: Unlock and set camera to MediaRecorder
mMediaRecorder = new MediaRecorder();
try
{
mCamera.unlock();
try {
mMediaRecorder.setCamera(mCamera);
}catch( Exception ex )
{
Log.e(TAG, "I don't know what happened... " + ex.getMessage());
}
// Step 2: Set sources mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT );
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
profile.videoFrameWidth = optimalSize.width;
profile.videoFrameHeight = optimalSize.height;
profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
profile.videoCodec = MediaRecorder.VideoEncoder.MPEG_4_SP;
profile.videoFrameRate = 24;
// reduce audio quality
profile.audioBitRate = 16000;
profile.audioChannels = 1;
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
/* This method should be called after the video AND audio sources
are set, and before setOutputFile() */
mMediaRecorder.setProfile(profile);
mMediaRecorder.setOrientationHint( rotationCorrected );
// Step 4: Set output file
mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
if (mOutputFile == null) {
return false;
}
mMediaRecorder.setOutputFile(mOutputFile.getPath());
// END_INCLUDE (configure_media_recorder)
}
catch( IllegalStateException ex )
{
Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + ex.getMessage());
return false;
}
catch( Exception ex )
{
Log.e(TAG, "I don't know what happened... " + ex.getMessage());
return false;
}
// more stuff here
}
所以,当我尝试将输出格式更改为 webm 时,我有这个
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
profile.videoFrameWidth = optimalSize.width;
profile.videoFrameHeight = optimalSize.height;
// FIXME mp4 to webm
profile.fileFormat = MediaRecorder.OutputFormat.WEBM;
profile.videoCodec = MediaRecorder.VideoEncoder.VP8;
profile.audioCodec = MediaRecorder.AudioEncoder.AMR_NB;
//profile.videoFrameRate = 24;
// reduce audio quality
//profile.audioBitRate = 16000;
//profile.audioChannels = 1;
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
/* This method should be called after the video AND audio sources
are set, and before setOutputFile() */
mMediaRecorder.setProfile(profile);
并且 setProfile 方法抛出一个 IllegalStateException,上面写着 E/MediaRecorder:输出格式 (9) 仅用于音频录制,与视频录制不兼容。 谷歌搜索这条消息我到达这里:
- https://android.googlesource.com/platform/frameworks/av/+/114819633470ebd5b346c13c2a82a0025d2d39c0/media/libmedia/mediarecorder.cpp
- https://android.googlesource.com/platform/frameworks/av/+/114819633470ebd5b346c13c2a82a0025d2d39c0/include/media/mediarecorder.h
所以,我的问题是我的 profile.fileFormat = MediaRecorder.OutputFormat.WEBM 触发了这个
if (mIsVideoSourceSet
&& of >= OUTPUT_FORMAT_AUDIO_ONLY_START //first non-video output format
&& of < OUTPUT_FORMAT_AUDIO_ONLY_END) {
ALOGE("output format (%d) is meant for audio recording only"
" and incompatible with video recording", of);
return INVALID_OPERATION;
}
查看 OUTPUT_FORMAT_AUDIO_ONLY_START 和 OUTPUT_FORMAT_AUDIO_ONLY_END 值,我可以看到 OUTPUT_FORMAT_AUDIO_ONLY_END 应该是 9,而在 MediaRecorder.java 中,Webm 格式是
/** VP8/VORBIS data in a WEBM container */
public static final int WEBM = 9;
如何将视频直接录制到 webm?
嗯,好像是AndroidSDK版本的问题
IllegalStateException 出现在带有 SDK 17 的设备上。运行相同的应用程序在带有 SDK 23 的设备上工作正常,我可以录制 webm 视频。
但是没有声音:(
据我所知,WebM容器不支持AMR-NB语音编解码。它仅支持 Vorbis 和 Opus 音频编解码器。看这里:https://www.webmproject.org/about/faq/
为什么您不想使用 WebM 的 Vorbis 音频编解码器(Opus 用于语音)?那是合乎逻辑的。