视频录制(MediaRecorder)

简介

参考

7.0前(不含7.0)

说明

MediaRecorder API不支持暂停续录。故MediaRecorder原生API实现暂停续录可通直点关按钮并拼接视频实现。setProfile可设录视频质量,但此时不可再设setOutputFormat、setAudioEncoder、setVideoEncoder属性,设或致不兼容。

拼接思路一

拼接过程避卡顿。每录都独生一视频文件,头点暂停生文件A,不处理;第二次点暂停有文件A和文件B,也不处理;再录时,录过程合并A、B文件为C文件后删A、B文件。用户再点暂停生该阶段D文件及上次合生C文件。即最多有两文件,最后停录时合并最后两文件即可。

拼接思路二

拼接过程避卡顿。每录都独生一视频文件,头点暂停生文件A,不处理;第二次点暂停有文件A和文件B,合并后更名合并文件名为A并删B文件。用户再点暂停生该阶段C文件及上次合生并更名后A文件。即最多有两文件,最后停录时合并最后两文件(即A与xxx)即可。

准备

app->libs 官网

isoviewer-1.0-RC-28.jar

只可2728,其它报错

清单文件

<!-- 硬件特性 -->
<uses-feature
    android:name="android.hardware.camera"
    android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />

代码

VideoUtils

package util;

import android.media.MediaMetadataRetriever;

import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.TimeToSampleBox;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * @decs: VideoUtils
 * @date: 2018/8/27 10:44
 * @version: v 1.0
 */
public final class VideoUtils {
    public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
        Movie movie = MovieCreator.build(src.getAbsolutePath());
        // remove all tracks we will create new tracks from the old
        List<Track> tracks = movie.getTracks();
        movie.setTracks(new LinkedList<Track>());
        /*for (Track track : tracks) {
            printTime(track);
        }*/
        double startTime = startMs / 1000;
        double endTime = endMs / 1000;
        boolean timeCorrected = false;
        // Here we try to find a track that has sync samples. Since we can only start decoding
        // at such a sample we SHOULD make sure that the start of the new fragment is exactly
        // such a frame
        for (Track track : tracks) {
            if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
                if (timeCorrected) {
                    // This exception here could be a false positive in case we have multiple tracks
                    // with sync samples at exactly the same positions. E.g. a single movie containing
                    // multiple qualities of the same video (Microsoft Smooth Streaming file)
                    throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
                }
                // true
                startTime = correctTimeToSyncSample(track, startTime, false);
                // false
                endTime = correctTimeToSyncSample(track, endTime, true);
                timeCorrected = true;
            }
        }
        LogUtils.e("trim startTime: " + startTime);
        LogUtils.e("trim endTime: " + endTime);
        for (Track track : tracks) {
            long currentSample = 0;
            double currentTime = 0;
            long startSample = -1;
            long endSample = -1;
            for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
                TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
                for (int j = 0; j < entry.getCount(); j++) {
                    // entry.getDelta() is the amount of time the current sample covers.
                    if (currentTime <= startTime) {
                        // current sample is still before the new startTime
                        startSample = currentSample;
                    }
                    if (currentTime <= endTime) {
                        // current sample is after the new startTime and still before the new endTime
                        endSample = currentSample;
                    } else {
                        // current sample is after the end of the cropped video
                        break;
                    }
                    currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                    currentSample++;
                }
            }
            System.out.println("trim startSample:" + startSample);
            System.out.println("trim endSample:" + endSample);
            movie.addTrack(new CroppedTrack(track, startSample, endSample));
            /*break;*/
        }
        Container container = new DefaultMp4Builder().build(movie);
        if (!dst.exists()) {
            dst.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(dst);
        FileChannel fc = fos.getChannel();
        // This one build up the memory.
        /*out.getBox(fc);*/
        container.writeContainer(fc);
        fc.close();
        fos.close();
        /*randomAccessFile.close();*/
    }

    public static void videoAppend(String saveVideoPath, String[] videos) throws IOException {
        Movie[] inMovies = new Movie[videos.length];
        int index = 0;
        for (String video : videos) {
            inMovies[index] = MovieCreator.build(video);
            index++;
        }
        // 分取音轨和视轨
        List<Track> videoTracks = new LinkedList<>();
        List<Track> audioTracks = new LinkedList<>();
        for (Movie m : inMovies) {
            for (Track t : m.getTracks()) {
                if ("soun".equals(t.getHandler())) {
                    audioTracks.add(t);
                }
                if ("vide".equals(t.getHandler())) {
                    videoTracks.add(t);
                }
            }
        }
        // 合并至最终视频文件
        Movie result = new Movie();
        if (audioTracks.size() > 0) {
            result.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
        }
        if (videoTracks.size() > 0) {
            result.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
        }
        Container out = new DefaultMp4Builder().build(result);
        FileChannel fc = new RandomAccessFile(saveVideoPath, "rw").getChannel();
        out.writeContainer(fc);
        fc.close();
    }

    private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
        long currentSample = 0;
        double currentTime = 0;
        for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
            TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
            for (int j = 0; j < entry.getCount(); j++) {
                if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
                    // samples always start with 1 but we start with zero therefore +1
                    timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
                }
                currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
                currentSample++;
            }
        }
        double previous = 0;
        for (double timeOfSyncSample : timeOfSyncSamples) {
            if (timeOfSyncSample > cutHere) {
                if (next) {
                    return timeOfSyncSample;
                } else {
                    return previous;
                }
            }
            previous = timeOfSyncSample;
        }
        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
    }

    /**
     * 视频宽高(分辨率)
     *
     * @param path 路径
     * @return 宽高
     */
    public static String[] videoWidthHeight(String path) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        retriever.setDataSource(path);
        String videoWidth = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
        String videoHeight = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
        return new String[]{videoWidth, videoHeight};
    }
}

布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".dataentry.add.VideoRecordAfterNougatActivity">

    <SurfaceView
        android:id="@+id/svVideoRecordNougat"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/d12">

        <ImageView
            android:id="@+id/ivVideoRecordNougatLight"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:alpha="0"
            android:contentDescription="@string/toDo"
            app:srcCompat="@drawable/ic_light_off" />

        <Chronometer
            android:id="@+id/ctVideoRecordNougat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:fontFamily="sans-serif-light"
            android:textColor="@color/background"
            android:textSize="@dimen/s13"
            tools:targetApi="jelly_bean" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@color/viewfinder_mask"
        android:padding="@dimen/d24">

        <ImageView
            android:id="@+id/ivVideoRecordNougatControl"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:contentDescription="@string/toDo"
            app:srcCompat="@drawable/ic_video_record" />

        <ImageView
            android:id="@+id/ivVideoRecordNougatPause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:alpha="0"
            android:contentDescription="@string/toDo"
            app:srcCompat="@drawable/ic_video_record_pause" />
    </RelativeLayout>
</RelativeLayout>

主代码

package com.self.zsp.dfs.dataentry.add;

import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Chronometer;
import android.widget.ImageView;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.self.zsp.dfs.R;

import java.io.File;
import java.io.IOException;

import base.BaseActivity;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import util.AnimationUtils;
import util.DateUtils;
import util.LightControl;
import util.LogUtils;
import util.VideoUtils;
import value.Flag;
import value.Magic;
import widget.ZsDialog;

/**
 * @decs: 录视频(7.0前)
 * @date: 2018/8/27 10:34
 * @version: v 1.0
 */
public class VideoRecordBeforeNougatActivity extends BaseActivity {
    private static final int STATE_INIT = 0;
    private static final int STATE_RECORDING = 1;
    private static final int STATE_PAUSE = 2;
    @BindView(R.id.svVideoRecordNougat)
    SurfaceView svVideoRecordNougat;
    @BindView(R.id.ivVideoRecordNougatLight)
    ImageView ivVideoRecordNougatLight;
    @BindView(R.id.ctVideoRecordNougat)
    Chronometer ctVideoRecordNougat;
    @BindView(R.id.ivVideoRecordNougatControl)
    ImageView ivVideoRecordNougatControl;
    @BindView(R.id.ivVideoRecordNougatPause)
    ImageView ivVideoRecordNougatPause;
    /**
     * Camera、SurfaceHolder、MediaRecorder
     */
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private MediaRecorder mediaRecorder;
    /**
     * 录标识
     */
    private int recordState;
    /**
     * 录暂停间隔
     */
    private long pauseTime = 0;
    /**
     * 路径
     */
    private String currentVideoFilePath;
    private String saveVideoFilePath;
    /**
     * SurfaceHolder.Callback
     */
    private SurfaceHolder.Callback mSurfaceCallBack = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
            stepCamera();
        }

        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
            if (surfaceHolder.getSurface() == null) {
                return;
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            releaseCamera();
        }
    };
    /**
     * MediaRecorder.OnInfoListener
     */
    private MediaRecorder.OnInfoListener onInfoListener = new MediaRecorder.OnInfoListener() {
        @Override
        public void onInfo(MediaRecorder mr, int what, int extra) {
            if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
                recordAchieve(true);
            }
        }
    };
    /**
     * MediaRecorder.OnErrorListener
     */
    private MediaRecorder.OnErrorListener onErrorListener = new MediaRecorder.OnErrorListener() {
        @Override
        public void onError(MediaRecorder mediaRecorder, int what, int extra) {
            try {
                if (mediaRecorder != null) {
                    mediaRecorder.reset();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    protected void initContentView(Bundle savedInstanceState) {
        setContentView(R.layout.activity_video_record_nougat);
        ButterKnife.bind(this);
    }

    @Override
    protected void stepUI() {

    }

    @Override
    protected void initConfiguration() {
        stepSurfaceHolder();
        stepCamera();
    }

    @Override
    protected void initData() {

    }

    @Override
    protected void startLogic() {

    }

    @Override
    protected void setListener() {

    }

    /**
     * 初始SurfaceHolder
     */
    private void stepSurfaceHolder() {
        surfaceHolder = svVideoRecordNougat.getHolder();
        // SurfaceHolder无需维护自己缓冲区
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        // 分辨率
        surfaceHolder.setFixedSize(320, 280);
        // 该组件不允屏幕自关
        surfaceHolder.setKeepScreenOn(true);
        // 回调接口
        surfaceHolder.addCallback(mSurfaceCallBack);
    }

    /**
     * 初始摄像头
     */
    private void stepCamera() {
        if (camera != null) {
            releaseCamera();
        }
        camera = Camera.open();
        if (camera == null) {
            toastShort(getString(R.string.cameraNotObtain));
            return;
        }
        try {
            // 相机绑SurfaceHolder
            camera.setPreviewDisplay(surfaceHolder);
            // 配CameraParams
            cameraParamsConfig();
            // 启相机预览
            camera.startPreview();
        } catch (IOException e) {
            // 某些机型兼容出错(适配特定机型)
            LogUtils.e("Error starting camera preview: " + e.getMessage());
        }
    }

    /**
     * 配CameraParams
     */
    private void cameraParamsConfig() {
        Camera.Parameters params = camera.getParameters();
        // 相机横竖屏(竖屏转90°)
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            params.set("orientation", "portrait");
            camera.setDisplayOrientation(90);
        } else {
            params.set("orientation", "landscape");
            camera.setDisplayOrientation(0);
        }
        // 聚焦模式
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        // 缩Recording启时
        params.setRecordingHint(true);
        // 影像稳定能力
        if (params.isVideoStabilizationSupported()) {
            params.setVideoStabilization(true);
        }
        camera.setParameters(params);
    }

    /**
     * 配MediaRecorder
     */
    private void configMediaRecorder() {
        if (mediaRecorder == null) {
            mediaRecorder = new MediaRecorder();
        }
        mediaRecorder.reset();
        mediaRecorder.setCamera(camera);
        // SurfaceView预览
        mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
        // 采集声音
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        // 采集图像
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        // 视频、音频输出格式(mp4)
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        // 音频编码格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        // 图像编码格式
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        // 立体声
        mediaRecorder.setAudioChannels(2);
        // 最大录时(毫秒)
        mediaRecorder.setMaxDuration(20 * 1000);
        // 最大录大小(字节)
        /*mediaRecorder.setMaxFileSize(1024 * 1024);*/
        // 音频一秒含多少数据位
        CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        mediaRecorder.setAudioEncodingBitRate(44100);
        if (camcorderProfile.videoBitRate > Magic.INT_ER * Magic.INT_YLES * Magic.INT_YLES) {
            mediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024);
        } else {
            mediaRecorder.setVideoEncodingBitRate(1024 * 1024);
        }
        mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
        // 观看存后视频角度(顺时)(默逆90度,顺时图像正常显)
        mediaRecorder.setOrientationHint(90);
        // 录像分辨率
        mediaRecorder.setVideoSize(352, 288);
        // 录像视频输出地址
        mediaRecorder.setOutputFile(currentVideoFilePath);
        // 信息监听
        mediaRecorder.setOnInfoListener(onInfoListener);
        // 错误监听
        mediaRecorder.setOnErrorListener(onErrorListener);
    }

    /**
     * 开始
     */
    private boolean start() {
        // 录前相机解锁
        camera.unlock();
        // 配MediaRecorder
        configMediaRecorder();
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 停止
     */
    private void stop() {
        // 设后不crash
        mediaRecorder.setOnErrorListener(null);
        mediaRecorder.setPreviewDisplay(null);
        // 停录
        mediaRecorder.stop();
        mediaRecorder.reset();
        // 释放
        mediaRecorder.release();
        mediaRecorder = null;
    }

    /**
     * 录完
     *
     * @param effective 有效否
     */
    private void recordAchieve(boolean effective) {
        // 停录加锁
        stop();
        camera.lock();
        // 合并视频(一次录制从第二次录制状点停止开始合并)
        if (saveVideoFilePath == null) {
            saveVideoFilePath = currentVideoFilePath;
        } else {
            videoFileMerge();
        }
        if (effective) {
            // 延迟一秒(视频合完)操作
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
                    finish();
                }
            }, 1000);
        } else {
            finish();
        }
    }

    /**
     * 释放摄像头资源
     */
    private void releaseCamera() {
        if (camera != null) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    /**
     * 更新控制UI
     */
    private void refreshControlUI() {
        if (recordState == STATE_INIT) {
            ivVideoRecordNougatControl.setEnabled(false);
            ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_recording);
            ivVideoRecordNougatPause.setEnabled(false);
            // 归零开始计时
            ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
            ctVideoRecordNougat.start();
            // 一秒后可更
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatLight, 100);
                    AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatPause, 100);
                    ivVideoRecordNougatControl.setEnabled(true);
                    ivVideoRecordNougatPause.setVisibility(View.VISIBLE);
                    ivVideoRecordNougatPause.setEnabled(true);
                }
            }, 1000);
        } else if (recordState == STATE_RECORDING) {
            // 停止计时
            ctVideoRecordNougat.stop();
            // UI
            ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
            ivVideoRecordNougatPause.setVisibility(View.INVISIBLE);
            ivVideoRecordNougatPause.setEnabled(false);
            // 录暂停间隔
            pauseTime = 0;
        }
    }

    /**
     * 更新暂停UI
     */
    private void refreshPauseUI() {
        if (recordState == STATE_RECORDING) {
            ctVideoRecordNougat.stop();
            ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_two);
            pauseTime = SystemClock.elapsedRealtime();
        } else if (recordState == STATE_PAUSE) {
            ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_pause);
            if (pauseTime == 0) {
                ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
            } else {
                ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime() - (pauseTime - ctVideoRecordNougat.getBase()));
            }
            ctVideoRecordNougat.start();
        }
    }

    /**
     * 视频路径
     *
     * @return 路径
     */
    private String videoFilePath() {
        String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo";
        File file1 = new File(path);
        if (!file1.exists()) {
            file1.mkdirs();
        }
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo" + File.separator;
    }

    /**
     * 视频名
     *
     * @return 名
     */
    private String videoFileName() {
        return getString(R.string.videoTwo) + DateUtils.timeStamp() + ".mp4";
    }

    /**
     * 视频合并
     */
    private void videoFileMerge() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String[] str = new String[]{saveVideoFilePath, currentVideoFilePath};
                    // 合并两视频至append.mp4
                    VideoUtils.videoAppend(videoFilePath() + "append.mp4", str);
                    File resultFile = new File(saveVideoFilePath);
                    File appendFile = new File(videoFilePath() + "append.mp4");
                    // 再移合成append.mp4至saveVideoFilePath
                    appendFile.renameTo(resultFile);
                    if (resultFile.exists()) {
                        appendFile.delete();
                        new File(currentVideoFilePath).delete();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @OnClick({R.id.ivVideoRecordNougatControl, R.id.ivVideoRecordNougatPause, R.id.ivVideoRecordNougatLight})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            /*
              控制
             */
            case R.id.ivVideoRecordNougatControl:
                if (recordState == STATE_INIT) {
                    // 视频存路径(configMediaRecorder设)
                    currentVideoFilePath = videoFilePath() + videoFileName();
                    // 开录
                    if (start()) {
                        refreshControlUI();
                        recordState = STATE_RECORDING;
                    }
                } else if (recordState == STATE_RECORDING) {
                    recordAchieve(true);
                } else if (recordState == STATE_PAUSE) {
                    camera.lock();
                    ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
                    setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
                    finish();
                }
                break;
            /*
              暂停
             */
            case R.id.ivVideoRecordNougatPause:
                if (recordState == STATE_RECORDING) {
                    refreshPauseUI();
                    recordState = STATE_PAUSE;
                    // 取自动对焦
                    camera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            if (success) {
                                VideoRecordBeforeNougatActivity.this.camera.cancelAutoFocus();
                            }
                        }
                    });
                    stop();
                    // 合并视频(一次录制从第二次点暂停开始合并)
                    if (saveVideoFilePath == null) {
                        saveVideoFilePath = currentVideoFilePath;
                    } else {
                        videoFileMerge();
                    }
                } else if (recordState == STATE_PAUSE) {
                    // 视频存路径(configMediaRecorder设)
                    currentVideoFilePath = videoFilePath() + videoFileName();
                    // 继录
                    if (start()) {
                        refreshPauseUI();
                        recordState = STATE_RECORDING;
                    }
                }
                break;
            /*
              闪光灯
             */
            case R.id.ivVideoRecordNougatLight:
                if (LightControl.isLightOn(camera)) {
                    LightControl.lightOff(camera);
                    ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_off));
                } else {
                    LightControl.lightOn(camera);
                    ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_on));
                }
                break;
            default:
                break;
        }
    }

    /**
     * 返
     *
     * @param keyCode 键码值
     * @param event   按键事件
     * @return boolean
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
                    ZsDialog.materialContentDialogTwoClick(this, R.string.videoRecordingHint, R.string.continueRecord, R.string.back, R.color.fontHint)
                            .onPositive(new MaterialDialog.SingleButtonCallback() {
                                @Override
                                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                                    dialog.dismiss();
                                }
                            })
                            .onNegative(new MaterialDialog.SingleButtonCallback() {
                                @Override
                                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                                    dialog.dismiss();
                                    recordAchieve(false);
                                }
                            }).cancelable(false).show();
                    return true;
                }
                break;
            default:
                break;
        }
        return super.onKeyUp(keyCode, event);
    }
}

压缩

baseHintDialogCreate(this, 5, getString(R.string.compressing), false, this).show();
videoCompress(path);

/**
 * 视频压缩(10:1)
 *
 * @param videoPath 视频路径
 */
public void videoCompress(final String videoPath) {
    final FfmpegTool ffmpegTool = FfmpegTool.getInstance(this);
    File file = new File(videoPath);
    if (file.exists()) {
        LogUtils.e("视频压缩中");
        // 压缩存路径
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "dfs" + File.separator + "VideoCompress";
        File file1 = new File(path);
        if (!file1.exists()) {
            file1.mkdirs();
        }
        ffmpegTool.compressVideo(videoPath, path + File.separator, 3, new FfmpegTool.VideoResult() {
            @Override
            public void clipResult(int i, String s, String s1, boolean b, int i1) {
                if (!b) {
                    baseHintDialogChange("视频压缩失败", getString(R.string.know), 1);
                }
                String[] dataStr = s1.split("/");
                StringBuilder stringBuilder = new StringBuilder(Environment.getExternalStorageDirectory().getAbsolutePath());
                for (int ii = Magic.INT_SI; ii < dataStr.length; ii++) {
                    stringBuilder.append("/").append(dataStr[ii]);
                }
                videoFileCompressPath = s1;
                LogUtils.e("视频压缩成功" + stringBuilder);
                LogUtils.e("压缩前视频", s);
                LogUtils.e("压缩后视频", s1);
                baseHintDialogDestroy();
            }
        });
    } else {
        baseHintDialogChange("未找到视频,重新操作", getString(R.string.know), 3);
    }
}

7.0后

说明

MediaRecorder API支持暂停续录。

准备

app->libs

isoviewer-1.0-RC-28.jar

清单文件

<!-- 硬件特性 -->
<uses-feature
    android:name="android.hardware.camera"
    android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />

代码

布局

如上

主代码

package com.self.zsp.dfs.dataentry.add;

import android.annotation.TargetApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Chronometer;
import android.widget.ImageView;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.self.zsp.dfs.R;

import java.io.File;
import java.io.IOException;

import base.BaseActivity;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import util.AnimationUtils;
import util.DateUtils;
import util.LightControl;
import util.LogUtils;
import value.Flag;
import value.Magic;
import widget.ZsDialog;

/**
 * @decs: 录视频(7.0后)
 * @date: 2018/8/16 16:36
 * @version: v 1.0
 */
public class VideoRecordAfterNougatActivity extends BaseActivity {
    private static final int STATE_INIT = 0;
    private static final int STATE_RECORDING = 1;
    private static final int STATE_PAUSE = 2;
    @BindView(R.id.svVideoRecordNougat)
    SurfaceView svVideoRecordNougat;
    @BindView(R.id.ivVideoRecordNougatLight)
    ImageView ivVideoRecordNougatLight;
    @BindView(R.id.ctVideoRecordNougat)
    Chronometer ctVideoRecordNougat;
    @BindView(R.id.ivVideoRecordNougatControl)
    ImageView ivVideoRecordNougatControl;
    @BindView(R.id.ivVideoRecordNougatPause)
    ImageView ivVideoRecordNougatPause;
    /**
     * Camera、SurfaceHolder、MediaRecorder
     */
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private MediaRecorder mediaRecorder;
    /**
     * 录标识
     */
    private int recordState;
    /**
     * 录暂停间隔
     */
    private long pauseTime = 0;
    /**
     * 路径
     */
    private String saveVideoFilePath;
    /**
     * SurfaceHolder.Callback
     */
    private SurfaceHolder.Callback mSurfaceCallBack = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
            stepCamera();
        }

        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
            if (surfaceHolder.getSurface() == null) {
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            releaseCamera();
        }
    };
    /**
     * MediaRecorder.OnInfoListener
     */
    private MediaRecorder.OnInfoListener onInfoListener = new MediaRecorder.OnInfoListener() {
        @Override
        public void onInfo(MediaRecorder mr, int what, int extra) {
            if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
                stop(true);
            }
        }
    };
    /**
     * MediaRecorder.OnErrorListener
     */
    private MediaRecorder.OnErrorListener onErrorListener = new MediaRecorder.OnErrorListener() {
        @Override
        public void onError(MediaRecorder mediaRecorder, int what, int extra) {
            try {
                if (mediaRecorder != null) {
                    mediaRecorder.reset();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    protected void initContentView(Bundle savedInstanceState) {
        setContentView(R.layout.activity_video_record_nougat);
        ButterKnife.bind(this);
    }

    @Override
    protected void stepUI() {

    }

    @Override
    protected void initConfiguration() {
        stepSurfaceHolder();
        stepCamera();
    }

    @Override
    protected void initData() {

    }

    @Override
    protected void startLogic() {

    }

    @Override
    protected void setListener() {

    }

    /**
     * 初始SurfaceHolder
     */
    private void stepSurfaceHolder() {
        surfaceHolder = svVideoRecordNougat.getHolder();
        // SurfaceHolder无需维护自己缓冲区
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        // 分辨率
        surfaceHolder.setFixedSize(320, 280);
        // 该组件不允屏幕自关
        surfaceHolder.setKeepScreenOn(true);
        // 回调接口
        surfaceHolder.addCallback(mSurfaceCallBack);
    }

    /**
     * 初始摄像头
     */
    private void stepCamera() {
        if (camera != null) {
            releaseCamera();
        }
        camera = Camera.open();
        if (camera == null) {
            toastShort(getString(R.string.cameraNotObtain));
            return;
        }
        try {
            // 相机绑SurfaceHolder
            camera.setPreviewDisplay(surfaceHolder);
            // 配CameraParams
            cameraParamsConfig();
            // 启相机预览
            camera.startPreview();
        } catch (IOException e) {
            // 某些机型兼容出错(适配特定机型)
            LogUtils.e("Error starting camera preview: " + e.getMessage());
        }
    }

    /**
     * 配CameraParams
     */
    private void cameraParamsConfig() {
        Camera.Parameters params = camera.getParameters();
        // 相机横竖屏(竖屏转90°)
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            params.set("orientation", "portrait");
            camera.setDisplayOrientation(90);
        } else {
            params.set("orientation", "landscape");
            camera.setDisplayOrientation(0);
        }
        // 聚焦模式
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        // 缩Recording启时
        params.setRecordingHint(true);
        // 影像稳定能力
        if (params.isVideoStabilizationSupported()) {
            params.setVideoStabilization(true);
        }
        camera.setParameters(params);
    }

    /**
     * 配MediaRecorder
     */
    private void configMediaRecorder() {
        if (mediaRecorder == null) {
            mediaRecorder = new MediaRecorder();
        }
        mediaRecorder.reset();
        mediaRecorder.setCamera(camera);
        // SurfaceView预览
        mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
        // 采集声音
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        // 采集图像
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        // 视频、音频输出格式(mp4)
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        // 音频编码格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        // 图像编码格式
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        // 立体声
        mediaRecorder.setAudioChannels(2);
        // 最大录时(毫秒)
        mediaRecorder.setMaxDuration(20 * 1000);
        // 最大录大小(字节)
        /*mediaRecorder.setMaxFileSize(1024 * 1024);*/
        // 音频一秒含多少数据位
        CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        mediaRecorder.setAudioEncodingBitRate(44100);
        if (camcorderProfile.videoBitRate > Magic.INT_ER * Magic.INT_YLES * Magic.INT_YLES) {
            mediaRecorder.setVideoEncodingBitRate(2 * 1024 * 1024);
        } else {
            mediaRecorder.setVideoEncodingBitRate(1024 * 1024);
        }
        mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
        // 观看存后视频角度(顺时)(默逆90度,顺时图像正常显)
        mediaRecorder.setOrientationHint(90);
        // 录像分辨率
        mediaRecorder.setVideoSize(352, 288);
        // 录像视频输出地址
        mediaRecorder.setOutputFile(saveVideoFilePath);
        // 信息监听
        mediaRecorder.setOnInfoListener(onInfoListener);
        // 错误监听
        mediaRecorder.setOnErrorListener(onErrorListener);
    }

    /**
     * 开始
     */
    private boolean start() {
        // 录前相机解锁
        camera.unlock();
        // 配MediaRecorder
        configMediaRecorder();
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 停止
     *
     * @param effective 有效否
     */
    private void stop(boolean effective) {
        // 停止计时
        ctVideoRecordNougat.stop();
        ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_record);
        // 闪光灯
        if (LightControl.isLightOn(camera)) {
            LightControl.lightOff(camera);
        }
        // 设后不crash
        mediaRecorder.setOnErrorListener(null);
        mediaRecorder.setPreviewDisplay(null);
        // 停录
        mediaRecorder.stop();
        mediaRecorder.reset();
        // 释放
        mediaRecorder.release();
        mediaRecorder = null;
        // 相机加锁
        camera.lock();
        if (effective) {
            setResult(RESULT_OK, new Intent().putExtra(Flag.VIDEO_PATH_BEAR, saveVideoFilePath));
            finish();
        } else {
            finish();
        }
    }

    /**
     * 释放摄像头资源
     */
    private void releaseCamera() {
        if (camera != null) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    /**
     * 更新控制UI
     */
    private void refreshControlUI() {
        ivVideoRecordNougatControl.setEnabled(false);
        ivVideoRecordNougatControl.setImageResource(R.drawable.ic_video_recording);
        // 归零开始计时
        ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
        ctVideoRecordNougat.start();
        // 一秒后可更
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatLight, 100);
                AnimationUtils.scaleXYAlphaShow(ivVideoRecordNougatPause, 100);
                ivVideoRecordNougatControl.setEnabled(true);
            }
        }, 1000);
    }

    /**
     * 更新暂停UI
     */
    private void refreshPauseUI() {
        if (recordState == STATE_RECORDING) {
            ctVideoRecordNougat.stop();
            ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_two);
            pauseTime = SystemClock.elapsedRealtime();
        } else if (recordState == STATE_PAUSE) {
            ivVideoRecordNougatPause.setImageResource(R.drawable.ic_video_record_pause);
            if (pauseTime == 0) {
                ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime());
            } else {
                ctVideoRecordNougat.setBase(SystemClock.elapsedRealtime() - (pauseTime - ctVideoRecordNougat.getBase()));
            }
            ctVideoRecordNougat.start();
        }
    }

    /**
     * 视频路径
     *
     * @return 路径
     */
    private String videoFilePath() {
        String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo";
        File file1 = new File(path);
        if (!file1.exists()) {
            file1.mkdirs();
        }
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "DfsVideo" + File.separator;
    }

    @TargetApi(Build.VERSION_CODES.N)
    @OnClick({R.id.ivVideoRecordNougatControl, R.id.ivVideoRecordNougatPause, R.id.ivVideoRecordNougatLight})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            /*
              控制
             */
            case R.id.ivVideoRecordNougatControl:
                if (recordState == STATE_INIT) {
                    // 视频存路径(configMediaRecorder设)
                    saveVideoFilePath = videoFilePath() + getString(R.string.videoTwo) + DateUtils.timeStamp() + ".mp4";
                    // 开录
                    if (start()) {
                        refreshControlUI();
                        recordState = STATE_RECORDING;
                    }
                } else if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
                    stop(true);
                }
                break;
            /*
              暂停
             */
            case R.id.ivVideoRecordNougatPause:
                if (recordState == STATE_RECORDING) {
                    refreshPauseUI();
                    recordState = STATE_PAUSE;
                    // 取自动对焦
                    camera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            if (success) {
                                VideoRecordAfterNougatActivity.this.camera.cancelAutoFocus();
                            }
                        }
                    });
                    mediaRecorder.pause();
                } else if (recordState == STATE_PAUSE) {
                    refreshPauseUI();
                    recordState = STATE_RECORDING;
                    mediaRecorder.resume();
                }
                break;
            /*
              闪光灯
             */
            case R.id.ivVideoRecordNougatLight:
                if (LightControl.isLightOn(camera)) {
                    LightControl.lightOff(camera);
                    ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_off));
                } else {
                    LightControl.lightOn(camera);
                    ivVideoRecordNougatLight.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_light_on));
                }
                break;
            default:
                break;
        }
    }

    /**
     * 返
     *
     * @param keyCode 键码值
     * @param event   按键事件
     * @return boolean
     */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                if (recordState == STATE_RECORDING || recordState == STATE_PAUSE) {
                    ZsDialog.materialContentDialogTwoClick(this, R.string.videoRecordingHint, R.string.continueRecord, R.string.back, R.color.fontHint)
                            .onPositive(new MaterialDialog.SingleButtonCallback() {
                                @Override
                                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                                    dialog.dismiss();
                                }
                            })
                            .onNegative(new MaterialDialog.SingleButtonCallback() {
                                @Override
                                public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                                    dialog.dismiss();
                                    stop(false);
                                }
                            }).cancelable(false).show();
                    return true;
                }
                break;
            default:
                break;
        }
        return super.onKeyUp(keyCode, event);
    }
}

压缩

如上

注意

于6.0错,需适配

java.lang.NoSuchMethodError: No virtual method resume()V in class Landroid/media/MediaRecorder; or its super classes (declaration of 'android.media.MediaRecorder' appears in /system/framework/framework.jar)
发布了608 篇原创文章 · 获赞 27 · 访问量 20万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术工厂 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览