Android中MediaRecorder类实现视频录制

一、MediaRecorder类概述

Android的MediaRecorder包含了Audio和video的记录功能,在Android的界面上,Music和Video两个应用程序都是调用MediaRecorder实现的。MediaRecorder在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个MediaRecorder程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。

1、MediaRecorder方法说明:

方法 说明
setAudioChannels(int numChannels) 设置录制的音频通道数
setAudioEncoder(int audio_encoder) 设置audio的编码格式
setAudioEncodingBitRate(int bitRate) 设置录制的音频编码比特率
setAudioSamplingRate(int samplingRate) 设置录制的音频采样率
setAudioSource(int audio_source) 设置用于录制的音源
setAuxiliaryOutputFile(String path) 辅助时间的推移视频文件的路径传递
setAuxiliaryOutputFile(FileDescriptor fd) 在文件描述符传递的辅助时间的推移视频
setCamera(Camera c) 设置一个recording的摄像头
setCaptureRate(double fps) 设置视频帧的捕获率
setMaxDuration(int max_duration_ms) 设置记录会话的最大持续时间(毫秒)
setMaxFileSize(long max_filesize_bytes) 设置记录会话的最大大小(以字节为单位)
setOutputFile(FileDescriptor fd) 传递要写入的文件的文件描述符
setOutputFile(String path) 设置输出文件的路径
setOutputFormat(int output_format) 设置在录制过程中产生的输出文件的格式
setPreviewDisplay(Surface sv) 表面设置显示记录媒体(视频)的预览
setVideoEncoder(int video_encoder) 设置视频编码器,用于录制
setVideoEncodingBitRate(int bitRate) 设置录制的视频编码比特率
setVideoFrameRate(int rate) 设置要捕获的视频帧速率
setVideoSize(int width, int height) 设置要捕获的视频的宽度和高度
setVideoSource(int video_source) 开始捕捉和编码数据到setOutputFile(指定的文件)

2、MediaRecorder中音视频编码格式和资源说明:

  • 视频编码格式:default,H263,H264,MPEG_4_SP
  • 获得视频资源:default,CAMERA
  • 音频编码格式:default,AAC,AMR_NB,AMR_WB
  • 获得音频资源:defalut,camcorder,mic,voice_call,voice_communication,voice_downlink,voice_recognition, voice_uplink
  • 输出方式:amr_nb,amr_wb,default,mpeg_4,raw_amr,three_gpp

例如:

recorder.setAudioSource(MediaRecorder.AudioSource.MIC);  //设置音频源为麦克风
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);//设置声音格式为3gp
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //设置编码为AMR
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);   //设置视频源为Camera
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //设置视频输出格式为MP4
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);//设置视频编码
recorder.setOutputFile(filePath);  //设置视频输出路径
以上代码在android 2.3.3及以下可以运行,但是在更高版本的android系统中不能运行,所以更改后的代码为:
recorder.setCamera(camera); recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源 
recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 录音源为麦克风
recorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)); // 默认视频格式

**二、Android 中MediaRecorder和Camera的关系**

从功能的角度MediaRecorder一般包含音频,视频记录,视频预览的功能,Camera包含了取景区预览,静态图像捕获的功能。在Android中,应用程序自上而下分成JAVA应用,JAVA框架,JNI,C框架,具体实现几个部分。多媒体方面的程序尤其是这样。MediaRecorder 和Camera在Android中都有自上而下的架构,它们在顶层JAVA应用层,共用一个应用程序Camera(其中的程序也是独立的),在JAVA框 架和JNI层是独立的,主要的联系在于Camer的C框架以下的内容被MediaRecorder实现(也就是PVAuthor)所调用,作为 MediaRecorder实现的视频输入设备,它的作用是负责传输视频数据和提供显示预览。本身Camera C框架以下的代码基本提供了取景器预览(Preview)、视频数据流获取、静止图像获取三方面的功能,MediaRecorder实现使用其取景器预览和视频数据流获取的功能,而Camera的JNI使用其取景器预览和静止图像获取两方面的功能。

三、示例讲解

1、首先,我们需要在AndroidManifest.xml文件中声明录制视频要用到的权限:

<!—摄像头-->
<uses-permission android:name="android.permission.CAMERA" />
<!—硬件支持-->
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<!—音频即声音-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!—sd卡写入权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />

2、其次,页面布局文件中要知道我们录制的是什么,必须要有SurfaceView提供界面预览:

<SurfaceView
        android:id="@+id/videoView"
        android:layout_below="@id/head_video"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:visibility="visible" >
    </SurfaceView>

3、录制视频的Java代码主要实现代码:

开始录制视频:

try {
    // 关闭预览并释放资源
    if(camera!=null){
        camera.stopPreview();
        camera.release();
        camera = null;
    }
    recorder = new MediaRecorder();
    // 声明视频文件对象
    myRecVideoFile = new File(fileName);
    if(!myRecVideoFile.exists()){
        myRecVideoFile.getParentFile().mkdirs();
        myRecVideoFile.createNewFile();
    }
    recorder.reset();
    // 判断是否有前置摄像头,若有则打开,否则打开后置摄像头
    int CammeraIndex=FindFrontCamera();  
    if(CammeraIndex==-1){  
        ToastUtil.TextToast(getApplicationContext(), "您的手机不支持前置摄像头", ToastUtil.LENGTH_SHORT);
        CammeraIndex=FindBackCamera();  
    }
    camera = Camera.open(CammeraIndex);
    // 设置摄像头预览顺时针旋转90度,才能使预览图像显示为正确的,而不是逆时针旋转90度的。
    camera.setDisplayOrientation(90);
    camera.unlock();
    recorder.setCamera(camera); //设置摄像头为相机
    recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源 
    recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 录音源为麦克风      
    recorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)); //设置视频和声音的编码为系统自带的格式   
    recorder.setOutputFile(myRecVideoFile.getAbsolutePath());
    recorder.setPreviewDisplay(mSurfaceHolder.getSurface()); // 预览
    recorder.setMaxFileSize(10*1024*1024); //设置视频文件的最大值为10M,单位B
    //recorder.setMaxDuration(3*1000);//设置视频的最大时长,单位毫秒
    //  recorder.setOrientationHint(90);//视频旋转90度,没有用
    recorder.prepare(); // 准备录像
    recorder.start(); // 开始录像
    handler.post(timeRun); // 调用Runable
    recording = true; // 改变录制状态为正在录制
    } catch (IOException e1) {
        releaseMediaRecorder();
        handler.removeCallbacks(timeRun);
        minute = 0;
        second = 0;
        recording = false;
        btnStart.setEnabled(true);
    } catch (IllegalStateException e) {
        releaseMediaRecorder();
        handler.removeCallbacks(timeRun);
        minute = 0;
        second = 0;
        recording = false;
        btnStart.setEnabled(true);
}

停止录制视频:

if(recorder!=null){
recorder.stop();
    recorder.release();
    recorder = null;
    minute = 0;
    second = 0;
    handler.removeCallbacks(timeRun);
    recording = false;
}

4、效果图:

5、完整示例代码:

JAVA代码:

public class VideoActivity extends BaseActivity implements SurfaceHolder.Callback {

    private File myRecVideoFile;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private TextView tvTime;
    private TextView tvSize;
    private Button btnStart;
    private Button btnStop;
    private Button btnCancel;
    private MediaRecorder recorder;
    private Handler handler;
    private Camera camera;
    private boolean recording; // 记录是否正在录像,fasle为未录像, true 为正在录像
    private int minute = 0;
    private int second = 0;
    private String time="";
    private String size="";
    private String fileName;
    private String name="";

    /**
     * 录制过程中,时间变化,大小变化
     */
    private Runnable timeRun = new Runnable() {

        @Override
        public void run() {
            long fileLength=myRecVideoFile.length();
            if(fileLength<1024 && fileLength>0){
                size=String.format("%dB/10M", fileLength);
            }else if(fileLength>=1024 && fileLength<(1024*1024)){
                fileLength=fileLength/1024;
                size=String.format("%dK/10M", fileLength);
            }else if(fileLength>(1024*1024*1024)){
                fileLength=(fileLength/1024)/1024;
                size=String.format("%dM/10M", fileLength);
            }
            second++;
            if (second == 60) {
                minute++;
                second = 0;
            }
            time = String.format("%02d:%02d", minute, second);
            tvSize.setText(size);
            tvTime.setText(time);
            handler.postDelayed(timeRun, 1000);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recorded_video);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        mSurfaceView = (SurfaceView) findViewById(R.id.videoView);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(this);
    mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceHolder.setKeepScreenOn(true);
        handler = new Handler();
        tvTime = (TextView) findViewById(R.id.tv_video_time);
        tvSize=(TextView)findViewById(R.id.tv_video_size);
        btnStop = (Button) findViewById(R.id.btn_video_stop);
        btnStart = (Button) findViewById(R.id.btn_video_start);
        btnCancel = (Button) findViewById(R.id.btn_video_cancel);
        btnCancel.setOnClickListener(listener);
        btnStart.setOnClickListener(listener);
        btnStop.setOnClickListener(listener);
        // 设置sdcard的路径
        fileName = Environment.getExternalStorageDirectory().getAbsolutePath();
        name="video_" +System.currentTimeMillis() + ".mp4";
        fileName += File.separator + File.separator+"Ruanko_Jobseeker"+File.separator+name;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 开启相机
        if (camera == null) {
            int CammeraIndex=FindFrontCamera();  
            if(CammeraIndex==-1){  
                ToastUtil.TextToast(getApplicationContext(), "您的手机不支持前置摄像头", ToastUtil.LENGTH_SHORT);
                CammeraIndex=FindBackCamera();  
            }
            camera = Camera.open(CammeraIndex); 
            try {
                camera.setPreviewDisplay(mSurfaceHolder);
                camera.setDisplayOrientation(90);
            } catch (IOException e) {
                e.printStackTrace();
                camera.release();
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // 开始预览
        camera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 关闭预览并释放资源
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    private OnClickListener listener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.btn_video_stop:
                if(recorder!=null){
                    releaseMediaRecorder();
                    minute = 0;
                    second = 0;
                    handler.removeCallbacks(timeRun);
                    recording = false;
                }
                btnStart.setEnabled(true);
                break;
            case R.id.btn_video_start:
                if(recorder!=null){
                    releaseMediaRecorder();
                    minute = 0;
                    second = 0;
                    handler.removeCallbacks(timeRun);
                    recording = false;
                }
                recorder();
                btnStart.setEnabled(false);
                break;
            case R.id.btn_video_cancel:
                releaseMediaRecorder();
                handler.removeCallbacks(timeRun);
                minute=0;
                second=0;
                recording = false;
                VideoActivity.this.finish();
                break;
            }
        }
    };
    //判断前置摄像头是否存在
     private int FindFrontCamera(){  
            int cameraCount = 0;  
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();  
            cameraCount = Camera.getNumberOfCameras(); // get cameras number  

            for ( int camIdx = 0; camIdx < cameraCount;camIdx++ ) {  
                Camera.getCameraInfo( camIdx, cameraInfo ); // get camerainfo  
                if ( cameraInfo.facing ==Camera.CameraInfo.CAMERA_FACING_FRONT ) {   
                    // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置  
                   return camIdx;  
                }  
            }  
            return -1;  
        }  
        //判断后置摄像头是否存在
        private int FindBackCamera(){  
            int cameraCount = 0;  
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();  
            cameraCount = Camera.getNumberOfCameras(); // get cameras number  

            for ( int camIdx = 0; camIdx < cameraCount;camIdx++ ) {  
                Camera.getCameraInfo( camIdx, cameraInfo ); // get camerainfo  
                if ( cameraInfo.facing ==Camera.CameraInfo.CAMERA_FACING_BACK ) {   
                    // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置  
                   return camIdx;  
                }  
            }  
            return -1;  
        }  

        //释放recorder资源 
        private void releaseMediaRecorder(){
            if (recorder != null) {
                recorder.stop();
                recorder.release();
                recorder = null;
            }
        }
    //开始录像
    public void recorder() {
        if (!recording) {
            try {
                // 关闭预览并释放资源
                if(camera!=null){
                    camera.stopPreview();
                    camera.release();
                    camera = null;
                }
                recorder = new MediaRecorder();
                // 声明视频文件对象
                myRecVideoFile = new File(fileName);
                if(!myRecVideoFile.exists()){
                    myRecVideoFile.getParentFile().mkdirs();
                    myRecVideoFile.createNewFile();
                }

                recorder.reset();
                // 判断是否有前置摄像头,若有则打开,否则打开后置摄像头
                int CammeraIndex=FindFrontCamera();  
                if(CammeraIndex==-1){  
                    ToastUtil.TextToast(getApplicationContext(), "您的手机不支持前置摄像头", ToastUtil.LENGTH_SHORT);
                    CammeraIndex=FindBackCamera();  
                }
                camera = Camera.open(CammeraIndex);
                 // 设置摄像头预览顺时针旋转90度,才能使预览图像显示为正确的,而不是逆时针旋转90度的。
                camera.setDisplayOrientation(90);
                camera.unlock();
recorder.setCamera(camera); //设置摄像头为相机recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);//视频源 
    recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 录音源为麦克风      recorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)); //设置视频和声音的编码为系统自带的格式   recorder.setOutputFile(myRecVideoFile.getAbsolutePath());
    recorder.setPreviewDisplay(mSurfaceHolder.getSurface()); // 预览
recorder.setMaxFileSize(10*1024*1024); //设置视频文件的最大值为10M,单位B
//recorder.setMaxDuration(3*1000);//设置视频的最大时长,单位毫秒
//  recorder.setOrientationHint(90);//视频旋转90度,没有用
    recorder.prepare(); // 准备录像
    recorder.start(); // 开始录像
    handler.post(timeRun); // 调用Runable
    recording = true; // 改变录制状态为正在录制
    } catch (IOException e1) {
        releaseMediaRecorder();
        handler.removeCallbacks(timeRun);
        minute = 0;
        second = 0;
        recording = false;
        btnStart.setEnabled(true);
    } catch (IllegalStateException e) {
        releaseMediaRecorder();
        handler.removeCallbacks(timeRun);
        minute = 0;
        second = 0;
        recording = false;
        btnStart.setEnabled(true);
            }
        } else
            ToastUtil.TextToast(getApplicationContext(), "视频录制中...", ToastUtil.LENGTH_SHORT);
    }
}

布局文件:

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

    <SurfaceView
        android:id="@+id/videoView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:visibility="visible" >
    </SurfaceView>

    <TextView 
        android:id="@+id/tv_video_size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:textColor="@color/orange"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:textSize="18sp"/>

    <TextView 
        android:id="@+id/tv_video_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/orange"
        android:text="00:00"
        android:layout_alignParentRight="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:textSize="18sp"/>

    <View 
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:background="@drawable/btn_alpha"
        android:alpha="0.5"
        android:layout_alignParentBottom="true"/>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingBottom="10dp"
        android:paddingTop="10dp">

        <Button
            android:id="@+id/btn_video_stop"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="5dp"
            android:background="@drawable/btn_position_selector"
            android:text="结束"
            android:textColor="@color/white" 
            android:layout_weight="1"
            android:textSize="18sp"/>

        <Button
            android:id="@+id/btn_video_start"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginRight="5dp"
            android:background="@drawable/btn_position_selector"
            android:text="开始"
            android:textColor="@color/white"
            android:layout_weight="1"
            android:textSize="18sp" />

        <Button
            android:id="@+id/btn_video_cancel"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_marginRight="10dp"
            android:background="@drawable/btn_position_selector"
            android:text="取消"
            android:textColor="@color/white" 
            android:layout_weight="1"
            android:textSize="18sp"/>
    </LinearLayout>

</RelativeLayout>