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默認是橫屏的,所以我們要使用豎屏錄制要旋轉(zhuǎn)90度
int degree = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 0 : 90; mCamera.setDisplayOrientation(degree);
camera預(yù)覽
我們要選擇一個與我們要顯示的SurfaceView大小比例最接近的一個camera預(yù)覽大小,這里要特別注意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() { // 創(chuàng)建MediaPlayer對象 mCamera.unlock(); mRecorder = new MediaRecorder(); mRecorder.reset(); mRecorder.setCamera(mCamera); // 設(shè)置從麥克風采集聲音(或來自錄像機的聲音AudioSource.CAMCORDER) mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設(shè)置從攝像頭采集圖像 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; }
這里也要設(shè)置視頻的旋轉(zhuǎn)參數(shù)
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.設(shè)置預(yù)覽功能 */ @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", "獲取預(yù)覽幀..."); Log.d("TAG", "預(yù)覽幀大?。? + String.valueOf(data.length)); } }); } } catch (IOException e) { e.printStackTrace(); } } private boolean prepareMediaRecorder() { // 創(chuàng)建MediaPlayer對象 mCamera.unlock(); mRecorder = new MediaRecorder(); mRecorder.reset(); mRecorder.setCamera(mCamera); // 設(shè)置從麥克風采集聲音(或來自錄像機的聲音AudioSource.CAMCORDER) mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設(shè)置從攝像頭采集圖像 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(); } /** * 獲取最佳預(yù)覽大小 * * @param parameters 相機參數(shù) * @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"> <!-- 顯示視頻預(yù)覽的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 { //內(nèi)部存儲不可用 path = context.getCacheDir().getPath(); } return path; } }
以上就是Android 錄制音視頻的詳細內(nèi)容,更多關(guān)于Android 錄制音視頻的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)Kotlin語言協(xié)程中的并發(fā)問題和互斥鎖
Android開發(fā)Kotlin語言提供了多種機制來處理并發(fā)和同步,其中包括高層次和低層次的工具,對于常規(guī)的并發(fā)任務(wù),可以利用 Kotlin 協(xié)程提供的結(jié)構(gòu)化并發(fā)方式,而對于需要更低層次的鎖定機制,可以使用Mutex(互斥鎖)來實現(xiàn)對共享資源的線程安全訪問2024-06-06Android GestureDetector實現(xiàn)手勢滑動效果
這篇文章主要為大家詳細介紹了Android GestureDetector實現(xiàn)手勢滑動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05Android使用元數(shù)據(jù)實現(xiàn)配置信息的傳遞方法詳細介紹
這篇文章主要介紹了Android使用元數(shù)據(jù)實現(xiàn)配置信息的傳遞方法,也就是實現(xiàn)配置快捷菜單功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-09-09Android使用Room數(shù)據(jù)庫解決本地持久化的操作
Room 是一個持久性庫,屬于 Android Jetpack 的一部分,Room 是 SQLite 數(shù)據(jù)庫之上的一個抽象層,Room 并不直接使用 SQLite,而是負責簡化數(shù)據(jù)庫設(shè)置和配置以及與數(shù)據(jù)庫交互方面的瑣碎工作,本文介紹了Android使用Room數(shù)據(jù)庫解決本地持久化的操作,需要的朋友可以參考下2024-09-09android實現(xiàn)App活動定時自動跳轉(zhuǎn)效果
本篇文章主要介紹了android實現(xiàn)App活動定時自動跳轉(zhuǎn)效果,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02Android開發(fā)實現(xiàn)按鈕點擊切換背景并修改文字顏色的方法
這篇文章主要介紹了Android開發(fā)實現(xiàn)按鈕點擊切換背景并修改文字顏色的方法,涉及Android界面布局與相關(guān)屬性設(shè)置技巧,需要的朋友可以參考下2018-01-01使用adb命令向Android模擬器中導入通訊錄聯(lián)系人的方法
這篇文章主要介紹了使用adb命令向Android模擬器中導入通訊錄聯(lián)系人的方法,實例分析了導入通訊錄存儲文件的技巧,需要的朋友可以參考下2015-01-01Android Studio報錯unable to access android sdk add-on list解決方案
這篇文章主要介紹了Android Studio報錯unable to access android sdk add-on list解決方案,本文通過多種方式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03詳解Flutter Image組件如何處理圖片加載過程中的錯誤
在Flutter中,Image組件可以通過監(jiān)聽加載過程中的錯誤來處理圖片加載過程中的錯誤,本文小編將給大家詳細介紹了Flutter Image組件是如何處理圖片加載過程中的錯誤,文中有詳細的代碼示例供大家參考,需要的朋友可以參下2023-10-10