欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 微信小視頻錄制功能實(shí)現(xiàn)詳細(xì)介紹

 更新時(shí)間:2016年11月02日 09:07:22   投稿:lqh  
這篇文章主要介紹了Android 微信小視頻錄制功能實(shí)現(xiàn)詳解的相關(guān)資料,這里提供了具體的實(shí)現(xiàn)思路及代碼,需要的朋友可以參考下

Android 微信小視頻錄制功能

開發(fā)之前

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

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

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

  1. Android Studio 2.2.2
  2. JDK1.7
  3. API 24
  4. Gradle 2.2.2

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

  1. 視頻錄制界面 SurfaceView 的使用
  2. Camera的使用
  3. 相機(jī)的對(duì)焦, 變焦
  4. 視頻錄制控件MediaRecorder的使用
  5. 簡單自定義View
  6. GestureDetector(手勢(shì)檢測(cè))的使用

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

開始開發(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自動(dòng)對(duì)焦
      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: 上面添加了自動(dòng)對(duì)焦的代碼, 但是部分手機(jī)可能不支持

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

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

step1: 繼承View, 完成初始化

private static final String TAG = "BothWayProgressBar";
//取消狀態(tài)為紅色bar, 反之為綠色bar
private boolean isCancel = false;
private Context mContext;
//正在錄制的畫筆
private Paint mRecordPaint;
//上滑取消時(shí)的畫筆
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ā)的事件包括四個(gè):

  1. 長按錄制
  2. 抬起保存
  3. 上滑取消
  4. 雙擊放大(變焦) 

 現(xiàn)在對(duì)這4個(gè)事件逐個(gè)分析:
前三這個(gè)事件, 我都放在了一個(gè)onTouch()回調(diào)方法中了
對(duì)于第4個(gè), 我們待會(huì)談
我們先把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()這個(gè)方法先不說, 我們只需要知道執(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)用戶抬起過快時(shí)(錄制的時(shí)間過短), 不需要保存. 而且, 在這個(gè)事件中包含了取消狀態(tài)的抬起, 解釋一下: 就是當(dāng)上滑取消時(shí)抬起的一瞬間取消錄制, 大家看代碼

case MotionEvent.ACTION_UP:
 if (ex > left && ex < right) {
   mTvTip.setVisibility(View.INVISIBLE);
   mProgressBar.setVisibility(View.INVISIBLE);
   //判斷是否為錄制結(jié)束, 或者為成功錄制(時(shí)間過短)
   if (!isCancel) {
     if (mProgress < 50) {
       //時(shí)間太短不保存
       stopRecordUnSave();
       Toast.makeText(this, "時(shí)間太短", 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();大家先不要考慮, 我們會(huì)在后面介紹, 他倆從名字就能看出 前者用來停止錄制但不保存, 后者停止錄制并保存

上滑取消

配合上一部分說得抬起取消事件, 實(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: 主要原理不難, 只要按下并且向上移動(dòng)一定距離 就會(huì)觸發(fā),當(dāng)手抬起時(shí)視頻錄制取消

雙擊放大(變焦)

這個(gè)事件比較特殊, 使用了Google提供的GestureDetector手勢(shì)檢測(cè) 來判斷雙擊事件

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

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)雙擊事件

///////////////////////////////////////////////////////////////////////////
// 變焦手勢(shì)處理類
///////////////////////////////////////////////////////////////////////////
class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener {
  //雙擊手勢(shì)事件
  @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)完成了對(duì)所有事件的監(jiān)聽, 看到這里大家也許有些疲憊了, 不過不要灰心, 現(xiàn)在完成我們的核心部分, 實(shí)現(xiàn)視頻的錄制

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

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

/**
 * 開始錄制
 */
private void startRecord() {
  if (mMediaRecorder != null) {
    //沒有外置存儲(chǔ), 直接停止錄制
    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 臨時(shí)寫個(gè)文件地址, 稍候該!!!
      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)視頻的停止

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

停止并保存

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: 這個(gè)停止不保存是我自己的一種想法, 如果大家有更好的想法, 歡迎大家到評(píng)論中指出, 不勝感激

完整代碼

源碼我已經(jīng)放在了github上了, 寫博客真是不易! 寫篇像樣的博客更是不易, 希望大家多多支持

總結(jié)

終于寫完了!!! 這是我最想說得話, 從案例一開始到現(xiàn)在已經(jīng)過去很長時(shí)間. 這是我寫得最長的一篇博客, 發(fā)現(xiàn)能表達(dá)清楚自己的想法還是很困難的, 這是我最大的感受!!!

實(shí)話說這個(gè)案例不是很困難, 但是像我這樣的初學(xué)者拿來練練手還是非常好的, 在這里還要感謝再見杰克的博客, 也給我提供了很多幫助

感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!

相關(guān)文章

最新評(píng)論