文档章节

全屏录制播放控件--Android 开发中关于视频录制和播放的诸多问题处理

Reone的开发笔记
 Reone的开发笔记
发布于 2016/07/12 14:54
字数 1375
阅读 705
收藏 2

1.解决录制后文件较大的问题    
2.解决清晰度问题   
3.同一控件上实现录制和播放   
4.解决VideoView播放时不能全屏问题   
5.解决了预览图拉抻的问题 
6.自定义播/录组件 连续录制/播放

 

所有解决方案在代码注释中:

package in.langhua.spray.view.customview;

import android.content.Context;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.VideoView;

import java.io.File;
import java.io.IOException;
import java.util.List;

import in.langhua.spray.common.AppConstant;
import in.langhua.spray.common.tools.LogUtils;


/**
 * 视频播放控件
 * <p>
 * Created by wangxingsheng
 */
public class MyMovieRecorderView extends LinearLayout implements SurfaceHolder.Callback {


    private FullScreenVideoView mVideoView;
    private String mVideoFileAbPath;
    private static String suffix = ".mp4";
    private Camera mCamera;
    private String TAG = "MyMovieRecorderView";
    private MediaRecorder record;
    private int cameraPosition = 0;//默认前摄
    private SurfaceHolder mHolder;


    public MyMovieRecorderView(Context context) {
        this(context, null);
    }

    public MyMovieRecorderView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyMovieRecorderView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        //String VIDEO_PATH = "/video";
        mVideoFileAbPath = Environment.getExternalStorageDirectory() + AppConstant.RunningConfig.VIDEO_PATH;
        //保证路径存在
        File dir = new File(mVideoFileAbPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        mVideoView = new FullScreenVideoView(getContext());
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addView(mVideoView, params);
        mHolder = mVideoView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public String getAllPath(String name) {
        return mVideoFileAbPath + name + suffix;
    }
    public String getPath(){
        return mVideoFileAbPath;
    }

    /**
     * 停止播放
     */
    public void stopPlayVideo() {
        try {
            stopPlayingRecording();
        } catch (Exception e) {
            LogUtils.SystemOut(TAG, e.toString());
            e.printStackTrace();
        }
    }

    /**
     * 播放指定视频
     *
     * @param tempVideoName
     */
    public void startPlayVideo(String tempVideoName) {
        try {
            playRecording(getAllPath(tempVideoName));
        } catch (Exception e) {
            LogUtils.SystemOut(TAG, e.toString());
            e.printStackTrace();
        }
    }


    /**
     * 停止录制
     */
    public void stopRecorder() {
        try {
            stopRecording();
        } catch (Exception e) {
            LogUtils.SystemOut(TAG, e.toString());
            e.printStackTrace();
        }
    }

    /**
     * 开始录制,指定视频名称  例:  tempVideo
     *
     * @param tempVideoName
     */
    public void startRecorder(String tempVideoName) {
        try {
            beginRecording(mHolder, getAllPath(tempVideoName), -1);
        } catch (IOException e) {
            e.printStackTrace();
            stopRecording();
        }
    }

    /**
     * 开始录制,指定视频录制时间
     *
     * @param tempVideoName
     * @param duration
     */
    public void startRecorder(String tempVideoName, int duration) {
        try {
            beginRecording(mHolder, getAllPath(tempVideoName), duration);
        } catch (IOException e) {
            e.printStackTrace();
            stopRecording();
        }
    }


    private void beginRecording(SurfaceHolder holder, String path, int duration) throws IOException {
        // TODO Auto-generated method stub
        LogUtils.SystemOut(TAG, "camera beginRecording record = " + record);
        if (record != null) {
            record.stop();
            record.release();
        }

        File outFile = new File(path);
        if (outFile.exists()) {
            outFile.delete();
        }
        if(mCamera == null){
            initCamera(holder);
        }
        record = new MediaRecorder();
        record.setCamera(mCamera);
        record.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
        record.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
        if(cameraPosition == 1){
            record.setOrientationHint(90);//后摄 时保证输出正向
            record.setProfile(getBestCamcorderProfile(Camera.CameraInfo.CAMERA_FACING_BACK));
        }else{
            record.setOrientationHint(270);//前摄 时保证输出正向
            record.setProfile(getBestCamcorderProfile(Camera.CameraInfo.CAMERA_FACING_FRONT));
        }
        record.setPreviewDisplay(holder.getSurface());
        record.setOutputFile(path);
        record.prepare();
        if (duration != -1) {
             record.setMaxDuration(duration);
        }
        record.start();
    }

    /**
     *
     * 解决录像时清晰度问题
     *
     * 视频清晰度顺序 High 1080 720 480 cif qvga gcif 详情请查看 CamcorderProfile.java
     * 在12秒mp4格式视频大小维持在1M左右时,以下四个选择效果最佳
     *
     * 不同的CamcorderProfile.QUALITY_ 代表每帧画面的清晰度,
     * 变换 profile.videoBitRate 可减少每秒钟帧数
     *
     * @param cameraID 前摄 Camera.CameraInfo.CAMERA_FACING_FRONT /后摄 Camera.CameraInfo.CAMERA_FACING_BACK
     * @return
     */
    private CamcorderProfile getBestCamcorderProfile(int cameraID){
        CamcorderProfile profile = CamcorderProfile.get(cameraID,CamcorderProfile.QUALITY_LOW);
        if(CamcorderProfile.hasProfile(cameraID,CamcorderProfile.QUALITY_480P)){
            //对比下面720 这个选择 每帧不是很清晰
            LogUtils.SystemOut("camera getBestCamcorderProfile 480P");
            profile = CamcorderProfile.get(cameraID, CamcorderProfile.QUALITY_480P);
            profile.videoBitRate = profile.videoBitRate/5;
            return profile;
        }
        if(CamcorderProfile.hasProfile(cameraID,CamcorderProfile.QUALITY_720P)){
            //对比上面480 这个选择 动作大时马赛克!!
            LogUtils.SystemOut("camera getBestCamcorderProfile 720P");
            profile = CamcorderProfile.get(cameraID,CamcorderProfile.QUALITY_720P);
            profile.videoBitRate = profile.videoBitRate/35;
            return profile;
        }
        if(CamcorderProfile.hasProfile(cameraID,CamcorderProfile.QUALITY_CIF)){
            LogUtils.SystemOut("camera getBestCamcorderProfile CIF");
            profile = CamcorderProfile.get(cameraID, CamcorderProfile.QUALITY_CIF);
            return profile;
        }
        if(CamcorderProfile.hasProfile(cameraID,CamcorderProfile.QUALITY_QVGA)){
            LogUtils.SystemOut("camera getBestCamcorderProfile QVGA");
            profile = CamcorderProfile.get(cameraID, CamcorderProfile.QUALITY_QVGA);
            return profile;
        }
        LogUtils.SystemOut("camera getBestCamcorderProfile QUALITY_LOW");
        return profile;
    }

    private void stopRecording() {
        // TODO Auto-generated method stub
        LogUtils.SystemOut(TAG, "camera stopRecording record = " + record);
        if (record != null) {
            try{
                record.stop();
                record.release();
                record = null;
            }catch (Exception e){
                e.printStackTrace();
                //如果stop时报错 release,保证资源释放
                record.reset();
                record.stop();
                record.release();
                record = null;
            }
        }
        freeCameraResource();
    }

    private void playRecording(String path) {
        // TODO Auto-generated method stub
        LogUtils.SystemOut(TAG, " playRecording path  = " + path);
        //添加控制器,前进后退 暂停
        freeCameraResource();
        MediaController mc = new MediaController(getContext());
        mVideoView.setMediaController(mc);
        mVideoView.setVideoPath(path);
        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mVideoView.start();
            }
        });

    }

    private void stopPlayingRecording() throws Exception {
        LogUtils.SystemOut(TAG, " stopPlayingRecording ");
        // TODO Auto-generated method stub
        //停止播放,手动调用这个方法,才能保证该组件连续录像 一次接一次录像
        mVideoView.stopPlayback();
    }

    private void initCamera(SurfaceHolder holder) {
        LogUtils.SystemOut(TAG, "camera initCamera ");
        if (mCamera != null) {
            freeCameraResource();
        }
        try {
            mCamera = getCamera();
            if (mCamera == null)
                return;

            if (mCamera != null) {
                Camera.Parameters params = mCamera.getParameters();
                params.set("orientation", "portrait");
                //从系统相机所支持的size列表中找到与屏幕长宽比最相近的size
                Camera.Size size = getCloselyPreSize(mVideoView.getWidth(),mVideoView.getHeight(),params.getSupportedPreviewSizes());
                params.setPreviewSize(size.width,size.height);
                mCamera.setParameters(params);
            }
            //调正相机预览
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
            mCamera.unlock();
        } catch (Exception e) {
            e.printStackTrace();
            freeCameraResource();
        }
    }

    private void freeCameraResource() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.lock();
            mCamera.release();
            mCamera = null;
        }
    }


    /**
     * 切换前后摄像头
     */
    public void switchCamera() {
        cameraPosition = cameraPosition == 1 ? 0 : 1;
        initCamera(mHolder);
    }

    /**
     * 激活相机预览
     */
    public void refCamera(){
        initCamera(mHolder);
    }

    /**
     * open 前/后摄像头
     *
     * @return
     */
    private Camera getCamera() {
        LogUtils.SystemOut("camera getCamera");
        int numberOfCameras = Camera.getNumberOfCameras();
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraPosition == 1) {
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    return Camera.open(i);
                }
            } else {
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    return Camera.open(i);
                }
            }
        }
        return null;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        LogUtils.SystemOut("camera sufraceCreate");
        mHolder = holder;
        initCamera(mHolder);
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        LogUtils.SystemOut("camera sufraceChanged");
        mHolder = holder;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        LogUtils.SystemOut("camera surfaceDestroyed");
        mHolder = holder;
        stopRecording();
    }

    /**
     * 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择)
     *
     * @param surfaceWidth
     *            需要被进行对比的原宽
     * @param surfaceHeight
     *            需要被进行对比的原高
     * @param preSizeList
     *            需要对比的预览尺寸列表
     * @return 得到与原宽高比例最接近的尺寸
     */
    private Camera.Size getCloselyPreSize(int surfaceWidth, int surfaceHeight,
                                            List<Camera.Size> preSizeList) {

        //因为预览相机图像需要旋转90度,所以在找相机预览size时切换长宽
        int ReqTmpWidth = surfaceHeight;
        int ReqTmpHeight = surfaceWidth;

        // 得到与传入的宽高比最接近的size
        float reqRatio = ((float) ReqTmpWidth) / ReqTmpHeight;
        float curRatio, deltaRatio;
        float deltaRatioMin = Float.MAX_VALUE;
        Camera.Size retSize = null;
        for (Camera.Size size : preSizeList) {
            if ((size.width == ReqTmpWidth) && (size.height == ReqTmpHeight)) {
                return size;
            }
            curRatio = ((float) size.width) / size.height;
            deltaRatio = Math.abs(reqRatio - curRatio);
            if (deltaRatio < deltaRatioMin) {
                deltaRatioMin = deltaRatio;
                retSize = size;
            }
        }

        return retSize;
    }

        /**
         * 保证播放时全屏
         */
    class FullScreenVideoView extends VideoView{


        public FullScreenVideoView(Context context) {
            super(context);
        }
        public FullScreenVideoView (Context context, AttributeSet attrs)
        {
            super(context,attrs);
        }
        public FullScreenVideoView(Context context, AttributeSet attrs,int defStyle)
        {
            super(context,attrs,defStyle);
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            int width = getDefaultSize(0, widthMeasureSpec);
            int height = getDefaultSize(0, heightMeasureSpec);
            setMeasuredDimension(width , height);
        }
    }
}

可直接在xml中引用此控件.

本博客原地址:http://my.oschina.net/reone/blog/710346

© 著作权归作者所有

Reone的开发笔记
粉丝 6
博文 70
码字总数 29350
作品 0
松江
程序员
私信 提问
加载中

评论(7)

Reone的开发笔记
Reone的开发笔记 博主

引用来自“小卒搬砖”的评论

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?

系统的录像也是这样的,你看看

引用来自“yuxiujuan”的评论

三星的测试机,和微信效果一样,但是上面的代码是翻转的
那你需要加录像的回调,把二维数组手动倒过来。具体怎么实现百度吧,我忘记那个方法了
yuxiujuan
yuxiujuan

引用来自“小卒搬砖”的评论

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?

系统的录像也是这样的,你看看
三星的测试机,和微信效果一样,但是上面的代码是翻转的
yuxiujuan
yuxiujuan

引用来自“小卒搬砖”的评论

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?

系统的录像也是这样的,你看看
我的乐视手机试了下,但是微信是翻转的,咋做的呢?
Reone的开发笔记
Reone的开发笔记 博主

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?

系统的录像也是这样的,你看看
yuxiujuan
yuxiujuan

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?

引用来自“小卒搬砖”的评论

文中initCamera方法中mCamera.setDisplayOrientation(90);这句话便是调整角度的。
这个拍摄的显示是对的,但是保存的和预览的都翻转了,比如有个头像在左边,拍完了的预览在右边,请问你遇到了吗?
Reone的开发笔记
Reone的开发笔记 博主

引用来自“yuxiujuan”的评论

前置摄像头录制的视频,播放会是翻转的,怎么办?
文中initCamera方法中mCamera.setDisplayOrientation(90);这句话便是调整角度的。
yuxiujuan
yuxiujuan
前置摄像头录制的视频,播放会是翻转的,怎么办?
求问Android录制视频时,为什么用html5的video控件播放不出声音

我做了一个Android的视频录像功能,录像完毕后上传到服务器,然后可以在网页打开! 在Android内部录制完毕预览跟在服务器用迅雷等播放器打开视频文件都能正常的播放! 但是在HTML5的Video中播...

小爷胡汉三
2015/11/13
1K
1
H5视频兼容安卓、IOS踩坑相关

1.视频暂停和播放 将JQ对象转换为原生JS对象 有遮罩层的视频播放和暂停 2.音乐开关 3.视频切换+音量播放 pug js 4.微信不能够自动播放 5.页面强制横屏问题 6.H5--移动端视频video的android兼...

谭瞎
2018/07/28
0
0
android进阶4step2:Android音视频处理——音视频录制与播放

录音 MediaRecoder Android有一个内置的麦克风,通过它可以捕获音频和存储,或在手机进行播放。 有很多方法可以做到这一点,但最常见的方法是通 过MediaRecorder类。 MediaRecoder常用方法 ...

发条鱼
2018/12/17
0
0
【前端帮帮忙】第6期 移动端使用video标签需要注意的一些问题

相信使用过标签的都知道,标签在pc端跟手机端显示的样式并不一样,而且还有个很蛋疼的就是微信中内置的浏览器,播放的时候会自动全屏问题。 接下来我们通过例子来分析下使用过程中可能碰到的...

大志_前端
05/10
0
0
Android短视频开发业务中视频编解码的相关知识阅读

随着互联网对人们生活习惯的改变,Android短视频开发业务不断升温。移动端各个媒体平台成为流量市场中增长的主力,各式各样的短视频应用迅速抢占人们的手机屏幕。而在Android端想要实现录制功...

q3557873521
2018/12/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
今天
9
0
golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架...

非正式解决方案
今天
6
0
前端——使用base64编码在页面嵌入图片

因为页面中插入一个图片都要写明图片的路径——相对路径或者绝对路径。而除了具体的网站图片的图片地址,如果是在自己电脑文件夹里的图片,当我们的HTML文件在别人电脑上打开的时候图片则由于...

被毒打的程序猿
今天
8
0
Flutter 系列之Dart语言概述

Dart语言与其他语言究竟有什么不同呢?在已有的编程语言经验的基础上,我们该如何快速上手呢?本篇文章从编程语言中最重要的组成部分,也就是基础语法与类型变量出发,一起来学习Dart吧 一、...

過愙
今天
5
0
rime设置为默认简体

转载 https://github.com/ModerRAS/ModerRAS.github.io/blob/master/_posts/2018-11-07-rime%E8%AE%BE%E7%BD%AE%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%AE%80%E4%BD%93.md 写在开始 我的Arch Linux上......

zhenruyan
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部