Android自定义视频录制

设置权限

如果要在App中使用摄像头,必须先在Manifest中声明摄像头权限

<uses-permission android:name="android.permission.CAMERA" />

以及声明应用需要有摄像头

<uses-feature android:name="android.hardware.camera" /

如果摄像头并非应用必不可少的功能,则可以声明如下

<uses-feature android:name="android.hardware.camera" android:required="false" />

如果应用要在SD卡上存储图像或者视频,则需要在Manifest文件中指定以下权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在录制视频的时候,同时需要录制音频,所以也要声明录音权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />

自定义相机的一般步骤

  • 是否有摄像头、是否有访问摄像头的权限
  • 创建相机预览类,继承SurfaceView并实现SurfaceHolder接口,用来预览相机的即时图片;
  • 有了相机预览类之后,搭建预览布局将预览视图和界面控件结合起来。
  • 连接控件的监听器以响应用户的动作,拍摄照片或视频,比如一个按钮点击事件
  • 拍摄照片和视频并保存输出
  • 摄像头使用完毕后,要正确地释放相机以便于其他应用使用

相机是共享资源,必须合理管理才不至于和其他同样用到相机的应用发生冲突。

注意 : 当你的应用使用完相机之后,记得调用Camera.release()方法以释放Camera对象。如果没有正确地释放相机,设备上的任何应用,随后任何访问相机的尝试,都有可能失败、退出。

检测相机

如果没有在manifest中配置需要摄像头功能,则需要在运行时检测是否有相机。

private boolean checkCameraHardware(Context context) {
  // 支持所有版本
   return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
  //  Android 2.3 (API Level 9) 及以上的
  // return  Camera.getNumberOfCameras() > 0;
}

访问相机

public static Camera getCameraInstance(){
    Camera c = null;
    try {
        // 在多个摄像头时,默认打开后置摄像头
        c = Camera.open(); 
         // Android 2.3(API 9之后可指定cameraId摄像头id,可选值为后置(CAMERA_FACING_BACK)/前置( CAMERA_FACING_FRONT)
        //  c = Camera.open(cameraId); 
    } catch (Exception e){
        // Camera被占用或者设备上没有相机时会崩溃。
    }
    return c;  // returns null if camera is unavailable
}

注意: 在部分设备上,打开摄像头时可能会花费较长的时间,因此此操作应该在子线程上执行,然后以回调的方式传递Camera 对象,避免出现ANR;

检测相机功能

在获取到Camera对象之后,可以调用Camera.getParameters()方法并检测该方法返回的Camera.Parameters对象所支持的功能以获取到更多关于相机功能的信息。当使用API 9及以上时, 使用Camera.getCameraInfo()检测摄像头是前置还是后置、图像的方向.

创建预览类

摄像头预览类继承自SurfaceView,能够实时的展示来自摄像头的图像数据,方便用户捕捉图片和视频。
下面的代码演示了预览类的基本创建。这个类继承自SurfaceHolder.Callback以获取创建和销毁该预览视图的回调事件,这些事件在指派相机预览输入时所需要的。

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // 通知摄像头可以在这里绘制预览了
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // 什么都不做,但是在Activity中Camera要正确地释放预览视图
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 如果预览视图可变或者旋转,要在这里处理好这些事件
        // 在重置大小或格式化时,确保停止预览

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // 变更之前要停止预览
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // 在这里重置预览视图的大小、旋转、格式化

        // 使用新设置启动预览视图
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

如果要给预览视图设置指定的大小,请在surfaceChanged()方法中像上面的注释中提到的一样设置。当设置预览视图大小时,必须使用从getSupportedPreviewSizes()获取到的值。不要在setPreviewSize()中随意设置值。

注意:Android 7.0(API 24及以上), Depending on the window size and aspect ratio, you may may have to fit a wide camera preview into a portrait-orientated layout, or vice versa, using a letterbox layout.

在布局文件中放预览视图

下面是一个简单的例子,其中FrameLayout作为预览视图的容器:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1" />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" />
</LinearLayout>

通常以横屏录制视频,所以在Manifest中配置对应的Activity中配置:

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

在Activity中获取预览视图的容器视图,然后将预览视图放置在容器视图中

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // 创建Camera实例
        mCamera = getCameraInstance();

        // 创建预览视图,并作为Activity的内容
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }

  public static Camera getCameraInstance(){
      Camera c = null;
      try {
          c = Camera.open(); // attempt to get a Camera instance
      }catch (Exception e){
          // Camera is not available (in use or does not exist)
      }
      return c; // returns null if camera is unavailable
}
}

拍照

(后面再补上,暂时用不到)

录制视频

录制视频需要注意管理Camera以及MediaRecorder类。通过摄像头录制视频时,必须控制 Camera.lock()Camera.unlock()的调用以允许MediaRecorder访问摄像头设备,另外还有 Camera.open()Camera.release()的调用。

从Android 4.0 (API 14)开始, Camera.lock() 和 Camera.unlock() 的调用已经被自动管理了。

使用摄像头录制视频和拍照不同,录制视频需要特别的调用顺序。要想做好准备工作,必须遵从特定的执行顺序。

  1. 打开摄像头 - 使用Camera.open()获取Camera实例
  2. 连接预览视图 - 使用Camera.setPreviewDisplay()连接SurfaceView
  3. 开始预览 - 使用Camera.startPreview() 方法显示即时录像图片
  4. 开始录制视频 - 要完成视频录制,必须按顺序完成下面的不步骤:
    • 解锁摄像头 - 调用Camera.unlock()解锁摄像头以供MediaRecorder使用

    • 配置MediaRecorder - 依次调用以下MediaRecorder的方法

      • setCamera() - 设置视频录制所用到的摄像头
      • setAudioSource - 设置音频源,使用 MediaRecorder.AudioSource.CAMCORDER
      • setVideoSource() - 设置视频源,使用MediaRecorder.VideoSource.CAMERA
      • 设置视频的输出格式和编码方式。Android 2.2 (API 8)及以上版本,用MediaRecorder.setProfile方法,用CamcorderProfile.get()方法获取 profile 实例
      • setOutputFile() - 设置输出文件
      • setPreviewDisplay()

        注意:MediaRecorder的这些方法必须依次调用,否则应用会出错,录制失败

    • 准备MediaRecorder - 调用MediaRecorder.prepare()方法

    • 开始录制 - 调用MediaRecorder.start()

  5. 停止录制 - 依次调用下面的方法以完成录制
    • Stop MediaRecorder - 调用 MediaRecorder.stop()
    • Reset MediaRecorder - 非必须的,调用MediaRecorder.reset()方法移除Recorder的配置设置
    • Release MediaRecorder - 调用MediaRecorder.release()方法
    • Lock the Camera - 调用Camera.lock()锁定摄像头,随后其他的MediaRecorder才能使用它。从Android 4.0(API 14)开始,只有在MediaRecorder.prepare()方法调用失败时,才需要调用这个方法。
  6. Stop the Preview - 当Activity完成使用完摄像头后,调用Camera.stopPreview()停止预览;
  7. Release Camera - 调用Camera.release()释放摄像头,其他应用才能使用。

温馨提示:如果应用通常用来拍摄视频,启动预览视图是使用 设置setRecordingHint(boolean)true。这个设置可以减少启动录制的时间。

配置MediaRecorder

当使用MediaRecorder录制视频时,必须以指定的顺序执行配置步骤,然后调用MediaRecorder.prepare()检查和使用配置。

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mMediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mMediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mMediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

以下MediaRecorder的视频录制参数已经有了预设值,不过你也可以用下面的方法调整以适用自己的应用:

  setVideoEncodingBitRate()
  setVideoSize()
  setVideoFrameRate()
  setAudioEncodingBitRate()
  setAudioChannels()
  setAudioSamplingRate()

启动和停止MediaRecorder
当使用MediaRecorder启动和停止录制时,必须遵从指定的顺序:

  1. Camera.unlock()
  2. 如以上的代码示例配置MediaRecorder
  3. MediaRecorder.start()启动录制
  4. 录制视频
  5. MediaRecorder.stop()停止录制
  6. MediaRecorder.release()释放MediaRecorder
  7. Camera.lock()锁定摄像头

以下代码演示了怎样使用Camera和MediaRecorder通过一个按钮正确的启动和停止视频录制

当完成一个视频录制后,不要释放Camera,否则预览视图也会停止

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mMediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mMediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

释放相机
一个设备上,摄像头是所有应用的共享资源,因此在自身的应用处于onPause状态时,要立即释放掉正在使用的Camera对象

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;

    ...

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();   // clear recorder configuration
            mMediaRecorder.release(); // release the recorder object
            mMediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容