Android 錄制音視頻的完整代碼
打開camera
private void openCamera(int position) { if (mCamera == null) { mCamera = Camera.open(position); int degree = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 0 : 90; mCamera.setDisplayOrientation(degree); } }
camera默認是橫屏的,所以我們要使用豎屏錄制要旋轉90度
int degree = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 0 : 90; mCamera.setDisplayOrientation(degree);
camera預覽
我們要選擇一個與我們要顯示的SurfaceView大小比例最接近的一個camera預覽大小,這裡要特別註意camera支持的寬高都是寬大於高。
所以就有瞭下面這段選擇代碼
private Size getBestCameraResolution(Camera.Parameters parameters, Size screenResolution) { float tmp = 0f; float mindiff = 100f; Log.e("yuanVideo", "screen width=" + screenResolution.getWidth() + " height=" + screenResolution.getHeight()); float width_d_height; if (screenResolution.getWidth() > screenResolution.getHeight()) { width_d_height = (float) screenResolution.getWidth() / (float) screenResolution.getHeight(); } else { width_d_height = (float) screenResolution.getHeight() / (float) screenResolution.getWidth(); } Log.e("yuanVideo", "width_d_height=" + width_d_height); Camera.Size best = null; List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes(); for (Camera.Size s : supportedPreviewSizes) { tmp = Math.abs(((float) s.width / (float) s.height) - width_d_height); Log.e("yuanVideo", "support width=" + s.width + " height=" + s.height + " ratio=" + tmp); if (tmp < mindiff) { mindiff = tmp; best = s; } } Log.e("yuanVideo", "best width=" + best.width + " height=" + best.height); return new Size(best.width, best.height); }
初始化MediaRecorder
private boolean prepareMediaRecorder() { // 創建MediaPlayer對象 mCamera.unlock(); mRecorder = new MediaRecorder(); mRecorder.reset(); mRecorder.setCamera(mCamera); // 設置從麥克風采集聲音(或來自錄像機的聲音AudioSource.CAMCORDER) mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設置從攝像頭采集圖像 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); Log.e("yuanProfile", "QUALITY_LOW=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)); Log.e("yuanProfile", "QUALITY_HIGH=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)); Log.e("yuanProfile", "QUALITY_QCIF=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QCIF)); Log.e("yuanProfile", "QUALITY_480P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)); Log.e("yuanProfile", "QUALITY_720P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)); Log.e("yuanProfile", "QUALITY_1080P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)); Log.e("yuanProfile", "QUALITY_QVGA=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)); Log.e("yuanProfile", "QUALITY_2160P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)); Log.e("yuanProfile", "QUALITY_VGA=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_VGA)); Log.e("yuanProfile", "QUALITY_4KDCI=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_4KDCI)); Log.e("yuanProfile", "QUALITY_QHD=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QHD)); Log.e("yuanProfile", "QUALITY_2K=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2K)); if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_720P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_480P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)); } else { return false; } // mTempList.add(mCurrentTempRecordData); mRecorder.setOutputFile(mCurPath); mRecorder.setPreviewDisplay(activtityVideoRecordBinding.sView.getHolder().getSurface()); // ① int degree; if(getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){ int degree ; if (cameraPosition == Camera.CameraInfo.CAMERA_FACING_FRONT) { degree = 270; } else { degree = 90; } mRecorder.setOrientationHint(degree); } try { mRecorder.prepare(); } catch (Exception e) { e.printStackTrace(); return false; } return true; }
這裡也要設置視頻的旋轉參數
if(getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){ int degree ; if (cameraPosition == Camera.CameraInfo.CAMERA_FACING_FRONT) { degree = 270; } else { degree = 90; } mRecorder.setOrientationHint(degree); }
下面是完整的代碼
package com.yuanxuzhen.ffmpeg; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.util.Size; import android.view.SurfaceHolder; import android.view.View; import android.view.Window; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.yuanxuzhen.ffmpeg.databinding.ActivtityVideoRecordBinding; import java.io.File; import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class VideoRecordActivity extends Activity { ActivtityVideoRecordBinding activtityVideoRecordBinding; MediaRecorder mRecorder; private boolean isRecording = false; private int cameraPosition = Camera.CameraInfo.CAMERA_FACING_FRONT;//0代表前置攝像頭,1代表後置攝像頭 private Camera mCamera; private Camera.Parameters mParameters; private String mCurPath = null; private VideoTempRecordData mCurrentTempRecordData = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); getWindow().setFormat(PixelFormat.TRANSLUCENT); mCurPath = DirUtil.getCacheDir(this) + File.separator + "out.mp4"; activtityVideoRecordBinding = ActivtityVideoRecordBinding.inflate(getLayoutInflater()); setContentView(activtityVideoRecordBinding.getRoot()); activtityVideoRecordBinding.sView.getHolder().setKeepScreenOn(true); activtityVideoRecordBinding.sView.getHolder().addCallback(new SurfaceHolder.Callback() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { openPreView(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { Log.e("yuanVideo", "surfaceChanged width=" + width + " height=" + height); } @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { } }); activtityVideoRecordBinding.recordOrStop.setText("開始"); activtityVideoRecordBinding.recordOrStop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isRecording) { Log.d("TAG", "停止錄像"); stopRecord(); } else { startRecord(); } } }); activtityVideoRecordBinding.change.setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onClick(View v) { if(isRecording){ return; } releaseCamera(); cameraPosition = cameraPosition == Camera.CameraInfo.CAMERA_FACING_FRONT ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT; openCamera(cameraPosition); openPreView(); } }); } /** * 1.打開相機 */ private void openCamera(int position) { if (mCamera == null) { mCamera = Camera.open(position); int degree = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 0 : 90; mCamera.setDisplayOrientation(degree); } } /** * initCameraAndSurfaceViewHolder初始化hoder後 * 2.設置預覽功能 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void openPreView() { try { if (mCamera != null) { mParameters = mCamera.getParameters(); mCamera.setPreviewDisplay(activtityVideoRecordBinding.sView.getHolder()); Size screenPoint = getScreenMetrics(VideoRecordActivity.this); Size bestPreviewSize = getBestCameraResolution(mCamera.getParameters(), screenPoint); mParameters.setPreviewSize(bestPreviewSize.getWidth(), bestPreviewSize.getHeight()); mCamera.setParameters(mParameters); mCamera.startPreview(); mCamera.autoFocus(new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { Log.e("yuanVideo", "autoFocus success=" + success); } }); mCamera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { Log.i("TAG", "獲取預覽幀..."); Log.d("TAG", "預覽幀大小:" + String.valueOf(data.length)); } }); } } catch (IOException e) { e.printStackTrace(); } } private boolean prepareMediaRecorder() { // 創建MediaPlayer對象 mCamera.unlock(); mRecorder = new MediaRecorder(); mRecorder.reset(); mRecorder.setCamera(mCamera); // 設置從麥克風采集聲音(或來自錄像機的聲音AudioSource.CAMCORDER) mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設置從攝像頭采集圖像 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); Log.e("yuanProfile", "QUALITY_LOW=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)); Log.e("yuanProfile", "QUALITY_HIGH=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)); Log.e("yuanProfile", "QUALITY_QCIF=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QCIF)); Log.e("yuanProfile", "QUALITY_480P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)); Log.e("yuanProfile", "QUALITY_720P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)); Log.e("yuanProfile", "QUALITY_1080P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)); Log.e("yuanProfile", "QUALITY_QVGA=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)); Log.e("yuanProfile", "QUALITY_2160P=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)); Log.e("yuanProfile", "QUALITY_VGA=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_VGA)); Log.e("yuanProfile", "QUALITY_4KDCI=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_4KDCI)); Log.e("yuanProfile", "QUALITY_QHD=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QHD)); Log.e("yuanProfile", "QUALITY_2K=" + CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2K)); if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_720P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_480P)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_LOW)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) { mRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)); } else { return false; } // mTempList.add(mCurrentTempRecordData); mRecorder.setOutputFile(mCurPath); mRecorder.setPreviewDisplay(activtityVideoRecordBinding.sView.getHolder().getSurface()); // ① if(getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE){ int degree ; if (cameraPosition == Camera.CameraInfo.CAMERA_FACING_FRONT) { degree = 270; } else { degree = 90; } mRecorder.setOrientationHint(degree); } try { mRecorder.prepare(); } catch (Exception e) { e.printStackTrace(); return false; } return true; } private void startRecord() { if (prepareMediaRecorder()) { mRecorder.start(); isRecording = true; activtityVideoRecordBinding.recordOrStop.setText("停止"); } else { releaseMediaRecorder(); isRecording = false; activtityVideoRecordBinding.recordOrStop.setText("開始"); } } private void stopRecord() { if (mRecorder == null) { return; } mRecorder.stop(); releaseMediaRecorder(); isRecording = false; activtityVideoRecordBinding.recordOrStop.setText("開始"); } @Nullable @Override public CharSequence onCreateDescription() { return super.onCreateDescription(); } @Override protected void onDestroy() { releaseCamera(); releaseMediaRecorder(); super.onDestroy(); } /** * 釋放相機資源 */ private void releaseCamera() { if (mCamera != null) { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } private void releaseMediaRecorder() { if (mRecorder != null) { mRecorder.reset(); mRecorder.release(); mRecorder = null; mCamera.lock(); } } @Override protected void onResume() { super.onResume(); openCamera(cameraPosition); } @Override protected void onPause() { super.onPause(); releaseMediaRecorder(); releaseCamera(); } /** * 獲取最佳預覽大小 * * @param parameters 相機參數 * @param screenResolution 屏幕寬高 * @return */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private Size getBestCameraResolution(Camera.Parameters parameters, Size screenResolution) { float tmp = 0f; float mindiff = 100f; Log.e("yuanVideo", "screen width=" + screenResolution.getWidth() + " height=" + screenResolution.getHeight()); float width_d_height; if (screenResolution.getWidth() > screenResolution.getHeight()) { width_d_height = (float) screenResolution.getWidth() / (float) screenResolution.getHeight(); } else { width_d_height = (float) screenResolution.getHeight() / (float) screenResolution.getWidth(); } Log.e("yuanVideo", "width_d_height=" + width_d_height); Camera.Size best = null; List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes(); for (Camera.Size s : supportedPreviewSizes) { tmp = Math.abs(((float) s.width / (float) s.height) - width_d_height); Log.e("yuanVideo", "support width=" + s.width + " height=" + s.height + " ratio=" + tmp); if (tmp < mindiff) { mindiff = tmp; best = s; } } Log.e("yuanVideo", "best width=" + best.width + " height=" + best.height); return new Size(best.width, best.height); } /** * 獲取屏幕寬度和高度,單位為px * * @param context * @return */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public static Size getScreenMetrics(Context context) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); int w_screen = dm.widthPixels; int h_screen = dm.heightPixels; return new Size(w_screen, h_screen); } }
佈局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 顯示視頻預覽的SurfaceView --> <com.yuanxuzhen.ffmpeg.ResizeAbleSurfaceView android:id="@+id/sView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" > <TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0秒" android:layout_centerInParent="true" android:textColor="@color/white" /> <Button android:id="@+id/change" android:layout_width="wrap_content" android:layout_height="66dp" android:text="切換攝像頭" android:layout_alignParentEnd="true" /> </RelativeLayout> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"> <Button android:id="@+id/record_or_stop" android:layout_width="66dp" android:layout_height="66dp" android:text="錄制" /> <Button android:id="@+id/save" android:layout_width="66dp" android:layout_height="66dp" android:text="保存" /> </LinearLayout> </RelativeLayout>
package com.yuanxuzhen.ffmpeg; import android.content.Context; import android.util.AttributeSet; import android.view.SurfaceView; public class ResizeAbleSurfaceView extends SurfaceView { private int mWidth = -1; private int mHeight = -1; public ResizeAbleSurfaceView(Context context) { super(context); } public ResizeAbleSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); } public ResizeAbleSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (-1 == mWidth || -1 == mHeight) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } else { setMeasuredDimension(mWidth, mHeight); } } public void resize(int width, int height) { mWidth = width; mHeight = height; getHolder().setFixedSize(width, height); requestLayout(); invalidate(); } }
package com.yuanxuzhen.ffmpeg; import android.content.Context; import android.os.Environment; import java.io.File; public class DirUtil { public static final String WEBVIEW_CACHE = ".webviewCache"; public static final String IMAGE_PATH = "image"; public static final String DOWNLOAD_PATH = "download"; public static final String VIDEO_PATH = ".video"; public static final String NET_PATH = ".net"; //image public static String getImageDir(Context context) { return getCacheDir(context) + File.separator + IMAGE_PATH; } //webview public static String getWebviewCache(Context context) { return getCacheDir(context) + File.separator + WEBVIEW_CACHE; } //download public static String getDownloadDir(Context context) { return getCacheDir(context) + File.separator + DOWNLOAD_PATH; } //video public static String getVideoPath(Context context) { return getCacheDir(context) + File.separator + VIDEO_PATH; } //net public static String getNetPath(Context context) { return getCacheDir(context) + File.separator + NET_PATH; } public static String getCacheDir(Context context) { if (context == null) { return ""; } String path = null; if (context.getExternalCacheDir() != null && (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable())) { //外部存儲可用 path = context.getExternalCacheDir().getPath(); } else { //內部存儲不可用 path = context.getCacheDir().getPath(); } return path; } }
以上就是Android 錄制音視頻的詳細內容,更多關於Android 錄制音視頻的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android Camera開發實現可復用的相機組件
- android中px、sp與dp之間進行轉換詳解
- Android 滑動Scrollview標題欄漸變效果(仿京東toolbar)
- Android seekbar實現可拖動進度條
- Android生成隨機數的方法實例