Android를 사용하여 WeChat 짧은 비디오 녹화 기능을 구현하는 방법에 대한 자세한 소개

高洛峰
풀어 주다: 2017-03-23 13:39:44
원래의
2257명이 탐색했습니다.

이 글에서는 주로 Android WeChat 짧은 동영상 녹화 기능 구현에 대한 관련 정보를 소개합니다. 구체적인 구현 아이디어와 코드는 여기에서 제공됩니다. 필요한 친구는

Android WeChat 짧은 동영상을 참조할 수 있습니다. 녹화 기능

개발 전

요 며칠 영상 관련 컨트롤에 노출이 되어서 이전 위챗 쉐이크에 이어 위챗 단편 영상을 구현할까 생각하게 되었어요 녹음 기능은 여러가지 기능이 있어서 매일 시간을 내어 글을 쓰는데 솔직히 좀 어려운 부분도 있으니, 틀린 부분이 있으면 댓글로 정정해주시기 바랍니다. 말도 안 되는 소리, 이제 본론으로 들어가겠습니다.

개발 환경

최근에 업데이트되었으니 업데이트하지 않은 친구들은 서두르세요

  1. Android 스튜디오 2.2.2

  2. JDK1.7

  3. API 24

  4. Gradle 2.2 .2

관련 지식 포인트

  1. 영상 녹화 인터페이스 SurfaceView 사용

  2. 카메라의 활용

  3. 카메라의 초점과 줌

  4. 영상 녹화 제어 MediaRecorder의 활용

  5. View의 간단한 사용자 정의

  6. GestureDetector 사용(제스처 감지)

많은 사용된 것들은 걱정하지 마시고 하나씩 가보세요.

개발 시작

사례 분석

짧은 영상을 열 수 있습니다. WeChat에서 기능을 간단히 분석해 보세요.

  1. 기본 동영상 미리보기 기능

  2. 길게 눌러 "촬영하려면 길게 누르세요. " 영상 녹화

  3. 녹화 중 진행률 표시줄이 양쪽에서 중앙으로 갈수록 짧아집니다.

  4. 손을 떼거나 진행률 표시줄에 도달하면 마지막에는 동영상 녹화가 중지되고 저장됩니다

  5. 동영상 녹화를 취소하려면 “길게 눌러 촬영”에서 위로 스와이프

  6. 화면 확대

위 분석에 따라 차근차근 완성했습니다

레이아웃 구축

레이아웃 인터페이스 구현도 괜찮고 어렵지 않아요


<?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>
로그인 후 복사

동영상 미리보기 구현

1단계: SurfaceView 가져오기 컨트롤, 기본 속성 및 해당 모니터링 설정(이 컨트롤의 생성은 비동기식이며 실제로 "준비"된 경우에만 나중에 호출할 수 있습니다)


mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view);
 //设置屏幕分辨率
mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);
로그인 후 복사

2단계: 인터페이스 메소드를 구현하고, SurfaceCreated 메소드에서 비디오 미리보기를 열고, SurfaceDestroyed에서 이를 파기합니다


//////////////////////////////////////////////
// SurfaceView回调
/////////////////////////////////////////////
@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: ");
    //停止预览并释放摄像头资源
    mCamera.stopPreview();
    mCamera.release();
    mCamera = null;
  }
  if (mMediaRecorder != null) {
    mMediaRecorder.release();
    mMediaRecorder = null;
  }
}
로그인 후 복사

3단계: 비디오 미리보기 구현 방법


/**
 * 开启预览
 *
 * @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();
      //实现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();
    }
  }

}
로그인 후 복사

참고: 위에 자동 초점 코드가 추가되어 있지만 일부 휴대폰에서는 이를 지원하지 않을 수 있습니다.

맞춤형 양방향 축소 진행 표시줄

나 같은 일부 초보자는 특정 뷰를 맞춤설정하는 것을 보고 멋지다고 생각합니다. 사실 Google에서는 이미 많은 코드를 작성했기 때문에 그냥 사용해도 됩니다. , 한 줄만 이야기해 보겠습니다.

1단계: 보기 상속 및 초기화 완료


private static final String TAG = "BothWayProgressBar";
//取消状态为红色bar, 反之为绿色bar
private boolean isCancel = false;
private Context mContext;
//正在录制的画笔
private Paint mRecordPaint;
//上滑取消时的画笔
private Paint mCancelPaint;
//是否显示
private int mVisibility;
// 当前进度
private int progress;
//进度条结束的监听
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);
}
로그인 후 복사

참고: OnProgressEndListener, 주로 진행률 표시줄이 표시될 때 사용됩니다. 카메라에 녹화 중지를 알리기 위해 인터페이스는 다음과 같습니다.


public interface OnProgressEndListener{
  void onProgressEndListener();
}
/**
 * 当进度条结束后的 监听
 * @param onProgressEndListener
 */
public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {
  mOnProgressEndListener = onProgressEndListener;
}
로그인 후 복사

step2: 상태 변경 진행 상황을 알리도록 Setter 메소드를 설정합니다. 🎜>


/**
 * 设置进度
 * @param progress
 */
public void setProgress(int progress) {
  this.progress = progress;
  invalidate();
}

/**
 * 设置录制状态 是否为取消状态
 * @param isCancel
 */
public void setCancel(boolean isCancel) {
  this.isCancel = isCancel;
  invalidate();
}
/**
 * 重写是否可见方法
 * @param visibility
 */
@Override
public void setVisibility(int visibility) {
  mVisibility = visibility;
  //重新绘制
  invalidate();
}
로그인 후 복사

3단계: 가장 중요한 단계는 진행률 표시줄을 그리는 것입니다. View의 onDraw(캔버스 캔버스) 메서드입니다



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


    //画出进度条
    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));
  }
}
로그인 후 복사

녹화 이벤트 처리

녹화 중에 발생하는 이벤트에는 다음 네 가지가 포함됩니다.

  1. 녹화하려면 길게 누르세요

  2. 위로 올려 저장

  3. 취소하려면 위로 스와이프

  4. 더블클릭하면 확대(확대)

이제 이 네 가지 이벤트를 하나씩 분석해 보세요.

처음 세 가지 이벤트에 대해 onTouch() 콜백 메서드를 배치했습니다.
네 번째 이벤트에 대해서는 나중에 이야기하겠습니다
먼저 onTouch()의 지역 변수를 나열해 보겠습니다.


@Override
public boolean onTouch(View v, MotionEvent event) {
  boolean ret = false;
  int action = event.getAction();
  float ey = event.getY();
  float ex = event.getX();
  //只监听中间的按钮处
  int vW = v.getWidth();
  int left = LISTENER_START;
  int right = vW - LISTENER_START;
  float downY = 0;
  // ...
}
로그인 후 복사

길게 눌러 녹음

길게 눌러 녹음하려면 ACTION_DOWN 이벤트를 수신하고 스레드를 사용하여 진행률 표시줄을 업데이트하기 위해 핸들러 전송을 지연해야 합니다



switch (action) {
 case MotionEvent.ACTION_DOWN:
   if (ex > left && ex < right) {
     mProgressBar.setCancel(false);
     //显示上滑取消
     mTvTip.setVisibility(View.VISIBLE);
     mTvTip.setText("↑ 上滑取消");
     //记录按下的Y坐标
     downY = ey;
     // TODO: 2016/10/20 开始录制视频, 进度条开始走
     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;
}
로그인 후 복사

참고: startRecord()에 대해 이야기하지 마세요. 메서드가 실행된 후에 기록할 수 있다는 것만 알면 되지만, Handler 이벤트는 진행률 표시줄의 진행 상황을 업데이트하는 역할만 담당합니다.


////////////////////////////////////////////////////
// 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;
    }
  }
}
로그인 후 복사

들어 올려 저장

마찬가지로 여기서 ACTION_UP 이벤트를 들어야 하는데, 사용자가 너무 빨리 들어 올리면(녹화 시간이 너무 길다는 점을 고려해야 합니다) 짧게), 저장할 필요도 없습니다. 또한, 이 이벤트에는 취소 상태 해제가 포함되어 있습니다. 취소를 위해 위로 스와이프하면 해제되는 순간 녹화가 취소됩니다.



case MotionEvent.ACTION_UP:
 if (ex > left && ex < right) {
   mTvTip.setVisibility(View.INVISIBLE);
   mProgressBar.setVisibility(View.INVISIBLE);
   //判断是否为录制结束, 或者为成功录制(时间过短)
   if (!isCancel) {
     if (mProgress < 50) {
       //时间太短不保存
       stopRecordUnSave();
       Toast.makeText(this, "时间太短", Toast.LENGTH_SHORT).show();
       break;
     }
     //停止录制
     stopRecordSave();
   } else {
     //现在是取消状态,不保存
     stopRecordUnSave();
     isCancel = false;
     Toast.makeText(this, "取消录制", Toast.LENGTH_SHORT).show();
     mProgressBar.setCancel(false);
   }

   ret = false;
 }
 break;
로그인 후 복사

참고: 마찬가지로 내부 stopRecordUnSave() 및 stopRecordSave()도 지금은 생각하지 마세요. 나중에 소개할 예정입니다. 전자는 녹화를 중지하지만 저장하지 않는 데 사용되고 후자는


취소하려면 위로 스와이프

와 연동됩니다. 이전 부분에서는 위로 스와이프하여 취소를 달성하기 위해 취소 이벤트를 발생시켰습니다


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

Note: 主要原理不难, 只要按下并且向上移动一定距离 就会触发,当手抬起时视频录制取消

双击放大(变焦)

这个事件比较特殊, 使用了Google提供的GestureDetector手势检测 来判断双击事件

step1: 对SurfaceView进行单独的Touch事件监听, why? 因为GestureDetector需要Touch事件的完全托管, 如果只给它传部分事件会造成某些事件失效


mDetector = new GestureDetector(this, new ZoomGestureListener());
/**
 * 单独处理mSurfaceView的双击事件
 */
mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    mDetector.onTouchEvent(event);
    return true;
  }
});
로그인 후 복사

step2: 重写GestureDetector.SimpleOnGestureListener, 实现双击事件


///////////////////////////////////////////////////////////////////////////
// 变焦手势处理类
///////////////////////////////////////////////////////////////////////////
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: 实现相机的变焦的方法


/**
 * 相机变焦
 *
 * @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: 至此我们已经完成了对所有事件的监听, 看到这里大家也许有些疲惫了, 不过不要灰心, 现在完成我们的核心部分, 实现视频的录制

实现视频的录制

说是核心功能, 也只不过是我们不知道某些API方法罢了, 下面代码中我已经加了详细的注释, 部分不能理解的记住就好^v^


/**
 * 开始录制
 */
private void startRecord() {
  if (mMediaRecorder != null) {
    //没有外置存储, 直接停止录制
    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
      return;
    }
    try {
      //mMediaRecorder.reset();
      mCamera.unlock();
      mMediaRecorder.setCamera(mCamera);
      //从相机采集视频
      mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
      // 从麦克采集音频信息
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      // TODO: 2016/10/20 设置视频格式
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      mMediaRecorder.setVideoSize(videoWidth, videoHeight);
      //每秒的帧数
      mMediaRecorder.setVideoFrameRate(24);
      //编码格式
      mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
      // 设置帧频率,然后就清晰了
      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();
    }

  }
}
로그인 후 복사

实现视频的停止

大家可能会问, 视频的停止为什么单独抽出来说呢? 仔细的同学看上面代码会看到这两个方法: stopRecordSave和stopRecordUnSave, 一个停止保存, 一个是停止不保存, 接下来我们就补上这个坑

停止并保存


private void stopRecordSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    Toast.makeText(this, "视频已经放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
  }
}
로그인 후 복사

停止不保存


private void stopRecordUnSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    if (mTargetFile.exists()) {
      //不保存直接删掉
      mTargetFile.delete();
    }
  }
}
로그인 후 복사

Note: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激

위 내용은 Android를 사용하여 WeChat 짧은 비디오 녹화 기능을 구현하는 방법에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!