溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用Android實(shí)現(xiàn)微信小視頻錄制功能

發(fā)布時間:2021-03-10 10:39:47 來源:億速云 閱讀:234 作者:小新 欄目:移動開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)如何使用Android實(shí)現(xiàn)微信小視頻錄制功能,小編覺得挺實(shí)用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

Android 微信小視頻錄制功能

開發(fā)之前

這幾天接觸了一下和視頻相關(guān)的控件, 所以, 繼之前的微信搖一搖, 我想到了來實(shí)現(xiàn)一下微信小視頻錄制的功能, 它的功能點(diǎn)比較多, 我每天都抽出點(diǎn)時間來寫寫, 說實(shí)話, 有些東西還是比較費(fèi)勁, 希望大家認(rèn)真看看, 說得不對的地方還請大家在評論中指正. 廢話不多說, 進(jìn)入正題.

開發(fā)環(huán)境

最近剛更新的, 沒更新的小伙伴們抓緊了

  1. Android Studio 2.2.2

  2. JDK1.7

  3. API 24

  4. Gradle 2.2.2

相關(guān)知識點(diǎn)

  1. 視頻錄制界面 SurfaceView 的使用

  2. Camera的使用

  3. 相機(jī)的對焦, 變焦

  4. 視頻錄制控件MediaRecorder的使用

  5. 簡單自定義View

  6. GestureDetector(手勢檢測)的使用

用到的東西真不少, 不過別著急, 咱們一個一個來.

開始開發(fā)

案例分析

大家可以打開自己微信里面的小視頻, 一塊簡單的分析一下它的功能點(diǎn)有哪些 ?

  1. 基本的視頻預(yù)覽功能

  2. 長按 “按住拍” 實(shí)現(xiàn)視頻的錄制

  3. 錄制過程中的進(jìn)度條從兩側(cè)向中間變短

  4. 當(dāng)松手或者進(jìn)度條走到盡頭視頻停止錄制 并保存

  5. 從 “按住拍” 上滑取消視頻的錄制

  6. 雙擊屏幕 變焦 放大

根據(jù)上述的分析, 我們一步一步的完成

搭建布局

布局界面的實(shí)現(xiàn)還可以, 難度不大

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TextView
    android:id="@+id/main_tv_tip"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center_horizontal"
    android:layout_marginBottom="150dp"
    android:elevation="1dp"
    android:text="雙擊放大"
    android:textColor="#FFFFFF"/>
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <SurfaceView
      android:id="@+id/main_surface_view"
      android:layout_width="match_parent"
      android:layout_height="0dp"
      android:layout_weight="3"/>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="0dp"
      android:layout_weight="1"
      android:background="@color/colorApp"
      android:orientation="vertical">
      <RelativeLayout
        android:id="@+id/main_press_control"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.lulu.weichatsamplevideo.BothWayProgressBar
          android:id="@+id/main_progress_bar"
          android:layout_width="match_parent"
          android:layout_height="2dp"
          android:background="#000"/>
        <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerInParent="true"
          android:text="按住拍"
          android:textAppearance="@style/TextAppearance.AppCompat.Large"
          android:textColor="#00ff00"/>
      </RelativeLayout>
    </LinearLayout>
  </LinearLayout>
</FrameLayout>

視頻預(yù)覽的實(shí)現(xiàn)

step1: 得到SufaceView控件, 設(shè)置基本屬性和相應(yīng)監(jiān)聽(該控件的創(chuàng)建是異步的, 只有在真正”準(zhǔn)備”好之后才能調(diào)用)

mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view);
 //設(shè)置屏幕分辨率
mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);

step2: 實(shí)現(xiàn)接口的方法, surfaceCreated方法中開啟視頻的預(yù)覽, 在surfaceDestroyed中銷毀

//////////////////////////////////////////////
// SurfaceView回調(diào)
/////////////////////////////////////////////
@Override
public void surfaceCreated(SurfaceHolder holder) {
  mSurfaceHolder = holder;
  startPreView(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
  if (mCamera != null) {
    Log.d(TAG, "surfaceDestroyed: ");
    //停止預(yù)覽并釋放攝像頭資源
    mCamera.stopPreview();
    mCamera.release();
    mCamera = null;
  }
  if (mMediaRecorder != null) {
    mMediaRecorder.release();
    mMediaRecorder = null;
  }
}

step3: 實(shí)現(xiàn)視頻預(yù)覽的方法

/**
 * 開啟預(yù)覽
 *
 * @param holder
 */
private void startPreView(SurfaceHolder holder) {
  Log.d(TAG, "startPreView: ");

  if (mCamera == null) {
    mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
  }
  if (mMediaRecorder == null) {
    mMediaRecorder = new MediaRecorder();
  }
  if (mCamera != null) {
    mCamera.setDisplayOrientation(90);
    try {
      mCamera.setPreviewDisplay(holder);
      Camera.Parameters parameters = mCamera.getParameters();
      //實(shí)現(xiàn)Camera自動對焦
      List<String> focusModes = parameters.getSupportedFocusModes();
      if (focusModes != null) {
        for (String mode : focusModes) {
          mode.contains("continuous-video");
          parameters.setFocusMode("continuous-video");
        }
      }
      mCamera.setParameters(parameters);
      mCamera.startPreview();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

}

Note: 上面添加了自動對焦的代碼, 但是部分手機(jī)可能不支持

自定義雙向縮減的進(jìn)度條

有些像我一樣的初學(xué)者一看到自定義某某View, 就覺得比較牛X. 其實(shí)呢, Google已經(jīng)替我們寫好了很多代碼, 所以我們用就行了.而且咱們的這個進(jìn)度條也沒啥, 不就是一根線, 今天咱就來說說.

step1: 繼承View, 完成初始化

private static final String TAG = "BothWayProgressBar";
//取消狀態(tài)為紅色bar, 反之為綠色bar
private boolean isCancel = false;
private Context mContext;
//正在錄制的畫筆
private Paint mRecordPaint;
//上滑取消時的畫筆
private Paint mCancelPaint;
//是否顯示
private int mVisibility;
// 當(dāng)前進(jìn)度
private int progress;
//進(jìn)度條結(jié)束的監(jiān)聽
private OnProgressEndListener mOnProgressEndListener;

public BothWayProgressBar(Context context) {
   super(context, null);
}
public BothWayProgressBar(Context context, AttributeSet attrs) {
  super(context, attrs);
  mContext = context;
  init();
}
private void init() {
  mVisibility = INVISIBLE;
  mRecordPaint = new Paint();
  mRecordPaint.setColor(Color.GREEN);
  mCancelPaint = new Paint();
  mCancelPaint.setColor(Color.RED);
}

Note: OnProgressEndListener, 主要用于當(dāng)進(jìn)度條走到中間了, 好通知相機(jī)停止錄制, 接口如下:

public interface OnProgressEndListener{
  void onProgressEndListener();
}
/**
 * 當(dāng)進(jìn)度條結(jié)束后的 監(jiān)聽
 * @param onProgressEndListener
 */
public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {
  mOnProgressEndListener = onProgressEndListener;
}

step2 :設(shè)置Setter方法用于通知我們的Progress改變狀態(tài)

/**
 * 設(shè)置進(jìn)度
 * @param progress
 */
public void setProgress(int progress) {
  this.progress = progress;
  invalidate();
}

/**
 * 設(shè)置錄制狀態(tài) 是否為取消狀態(tài)
 * @param isCancel
 */
public void setCancel(boolean isCancel) {
  this.isCancel = isCancel;
  invalidate();
}
/**
 * 重寫是否可見方法
 * @param visibility
 */
@Override
public void setVisibility(int visibility) {
  mVisibility = visibility;
  //重新繪制
  invalidate();
}

step3 :最重要的一步, 畫出我們的進(jìn)度條,使用的就是View中的onDraw(Canvas canvas)方法

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  if (mVisibility == View.VISIBLE) {
    int height = getHeight();
    int width = getWidth();
    int mid = width / 2;


    //畫出進(jìn)度條
    if (progress < mid){
      canvas.drawRect(progress, 0, width-progress, height, isCancel ? mCancelPaint : mRecordPaint);
    } else {
      if (mOnProgressEndListener != null) {
        mOnProgressEndListener.onProgressEndListener();
      }
    }
  } else {
    canvas.drawColor(Color.argb(0, 0, 0, 0));
  }
}

錄制事件的處理

錄制中觸發(fā)的事件包括四個:

  1. 長按錄制

  2. 抬起保存

  3. 上滑取消

  4. 雙擊放大(變焦)

現(xiàn)在對這4個事件逐個分析:
前三這個事件, 我都放在了一個onTouch()回調(diào)方法中了
對于第4個, 我們待會談
我們先把onTouch()中局部變量列舉一下:

@Override
public boolean onTouch(View v, MotionEvent event) {
  boolean ret = false;
  int action = event.getAction();
  float ey = event.getY();
  float ex = event.getX();
  //只監(jiān)聽中間的按鈕處
  int vW = v.getWidth();
  int left = LISTENER_START;
  int right = vW - LISTENER_START;
  float downY = 0;
  // ...
}

長按錄制

長按錄制我們需要監(jiān)聽ACTION_DOWN事件, 使用線程延遲發(fā)送Handler來實(shí)現(xiàn)進(jìn)度條的更新

switch (action) {
 case MotionEvent.ACTION_DOWN:
   if (ex > left && ex < right) {
     mProgressBar.setCancel(false);
     //顯示上滑取消
     mTvTip.setVisibility(View.VISIBLE);
     mTvTip.setText("↑ 上滑取消");
     //記錄按下的Y坐標(biāo)
     downY = ey;
     // TODO: 2016/10/20 開始錄制視頻, 進(jìn)度條開始走
     mProgressBar.setVisibility(View.VISIBLE);
     //開始錄制
     Toast.makeText(this, "開始錄制", Toast.LENGTH_SHORT).show();
     startRecord();
     mProgressThread = new Thread() {
       @Override
       public void run() {
         super.run();
         try {
           mProgress = 0;
           isRunning = true;
           while (isRunning) {
             mProgress++;
             mHandler.obtainMessage(0).sendToTarget();
             Thread.sleep(20);
           }
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     };
     mProgressThread.start();
     ret = true;
   }
   break;
   // ...
   return true;
}

Note: startRecord()這個方法先不說, 我們只需要知道執(zhí)行了它就可以錄制了, 但是Handler事件還是要說的, 它只負(fù)責(zé)更新進(jìn)度條的進(jìn)度

////////////////////////////////////////////////////
// Handler處理
/////////////////////////////////////////////////////
private static class MyHandler extends Handler {
  private WeakReference<MainActivity> mReference;
  private MainActivity mActivity;

  public MyHandler(MainActivity activity) {
    mReference = new WeakReference<MainActivity>(activity);
    mActivity = mReference.get();
  }

  @Override
  public void handleMessage(Message msg) {
    switch (msg.what) {
      case 0:
        mActivity.mProgressBar.setProgress(mActivity.mProgress);
        break;
    }
  }
}

抬起保存

同樣我們這兒需要監(jiān)聽ACTION_UP事件, 但是要考慮當(dāng)用戶抬起過快時(錄制的時間過短), 不需要保存. 而且, 在這個事件中包含了取消狀態(tài)的抬起, 解釋一下: 就是當(dāng)上滑取消時抬起的一瞬間取消錄制, 大家看代碼

case MotionEvent.ACTION_UP:
 if (ex > left && ex < right) {
   mTvTip.setVisibility(View.INVISIBLE);
   mProgressBar.setVisibility(View.INVISIBLE);
   //判斷是否為錄制結(jié)束, 或者為成功錄制(時間過短)
   if (!isCancel) {
     if (mProgress < 50) {
       //時間太短不保存
       stopRecordUnSave();
       Toast.makeText(this, "時間太短", Toast.LENGTH_SHORT).show();
       break;
     }
     //停止錄制
     stopRecordSave();
   } else {
     //現(xiàn)在是取消狀態(tài),不保存
     stopRecordUnSave();
     isCancel = false;
     Toast.makeText(this, "取消錄制", Toast.LENGTH_SHORT).show();
     mProgressBar.setCancel(false);
   }

   ret = false;
 }
 break;

Note: 同樣的, 內(nèi)部的stopRecordUnSave()和stopRecordSave();大家先不要考慮, 我們會在后面介紹, 他倆從名字就能看出 前者用來停止錄制但不保存, 后者停止錄制并保存

上滑取消

配合上一部分說得抬起取消事件, 實(shí)現(xiàn)上滑取消

case MotionEvent.ACTION_MOVE:
 if (ex > left && ex < right) {
   float currentY = event.getY();
   if (downY - currentY > 10) {
     isCancel = true;
     mProgressBar.setCancel(true);
   }
 }
 break;

Note: 主要原理不難, 只要按下并且向上移動一定距離 就會觸發(fā),當(dāng)手抬起時視頻錄制取消

雙擊放大(變焦)

這個事件比較特殊, 使用了Google提供的GestureDetector手勢檢測 來判斷雙擊事件

step1: 對SurfaceView進(jìn)行單獨(dú)的Touch事件監(jiān)聽, why? 因?yàn)镚estureDetector需要Touch事件的完全托管, 如果只給它傳部分事件會造成某些事件失效

mDetector = new GestureDetector(this, new ZoomGestureListener());
/**
 * 單獨(dú)處理mSurfaceView的雙擊事件
 */
mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    mDetector.onTouchEvent(event);
    return true;
  }
});

step2: 重寫GestureDetector.SimpleOnGestureListener, 實(shí)現(xiàn)雙擊事件

///////////////////////////////////////////////////////////////////////////
// 變焦手勢處理類
///////////////////////////////////////////////////////////////////////////
class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener {
  //雙擊手勢事件
  @Override
  public boolean onDoubleTap(MotionEvent e) {
    super.onDoubleTap(e);
    Log.d(TAG, "onDoubleTap: 雙擊事件");
    if (mMediaRecorder != null) {
      if (!isZoomIn) {
        setZoom(20);
        isZoomIn = true;
      } else {
        setZoom(0);
        isZoomIn = false;
      }
    }
    return true;
  }
}

step3: 實(shí)現(xiàn)相機(jī)的變焦的方法

/**
 * 相機(jī)變焦
 *
 * @param zoomValue
 */
public void setZoom(int zoomValue) {
  if (mCamera != null) {
    Camera.Parameters parameters = mCamera.getParameters();
    if (parameters.isZoomSupported()) {//判斷是否支持
      int maxZoom = parameters.getMaxZoom();
      if (maxZoom == 0) {
        return;
      }
      if (zoomValue > maxZoom) {
        zoomValue = maxZoom;
      }
      parameters.setZoom(zoomValue);
      mCamera.setParameters(parameters);
    }
  }

}

Note: 至此我們已經(jīng)完成了對所有事件的監(jiān)聽, 看到這里大家也許有些疲憊了, 不過不要灰心, 現(xiàn)在完成我們的核心部分, 實(shí)現(xiàn)視頻的錄制

實(shí)現(xiàn)視頻的錄制

說是核心功能, 也只不過是我們不知道某些API方法罷了, 下面代碼中我已經(jīng)加了詳細(xì)的注釋, 部分不能理解的記住就好^v^

/**
 * 開始錄制
 */
private void startRecord() {
  if (mMediaRecorder != null) {
    //沒有外置存儲, 直接停止錄制
    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
      return;
    }
    try {
      //mMediaRecorder.reset();
      mCamera.unlock();
      mMediaRecorder.setCamera(mCamera);
      //從相機(jī)采集視頻
      mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
      // 從麥克采集音頻信息
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      // TODO: 2016/10/20 設(shè)置視頻格式
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      mMediaRecorder.setVideoSize(videoWidth, videoHeight);
      //每秒的幀數(shù)
      mMediaRecorder.setVideoFrameRate(24);
      //編碼格式
      mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
      // 設(shè)置幀頻率,然后就清晰了
      mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
      // TODO: 2016/10/20 臨時寫個文件地址, 稍候該!!!
      File targetDir = Environment.
          getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
      mTargetFile = new File(targetDir,
          SystemClock.currentThreadTimeMillis() + ".mp4");
      mMediaRecorder.setOutputFile(mTargetFile.getAbsolutePath());
      mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
      mMediaRecorder.prepare();
      //正式錄制
      mMediaRecorder.start();
      isRecording = true;
    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

實(shí)現(xiàn)視頻的停止

大家可能會問, 視頻的停止為什么單獨(dú)抽出來說呢? 仔細(xì)的同學(xué)看上面代碼會看到這兩個方法: stopRecordSave和stopRecordUnSave, 一個停止保存, 一個是停止不保存, 接下來我們就補(bǔ)上這個坑

停止并保存

private void stopRecordSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    Toast.makeText(this, "視頻已經(jīng)放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
  }
}

停止不保存

private void stopRecordUnSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    if (mTargetFile.exists()) {
      //不保存直接刪掉
      mTargetFile.delete();
    }
  }
}

Note: 這個停止不保存是我自己的一種想法, 如果大家有更好的想法, 歡迎大家到評論中指出, 不勝感激

關(guān)于“如何使用Android實(shí)現(xiàn)微信小視頻錄制功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI