Android视频压缩

一.业务场景

Android设备使用相机进行视频录像后,生成视频文件;视频文件上传云端服务后,进行业务方面的处理; 如何减少视频文件大小呢?

二.解决方案

考虑到安装包大小,视频压缩速度,采用了VideoProcessor进行压缩。

视频压缩方式

  • fps压缩:fps大于25的,控制在20
  • 720P压缩:720P以上的视频进行720P压缩
  • H264转码H265(HEIF): 在相机视频取景时,配置支持HEIF格式或针对所有视频文件转码为H265编码

三.fps压缩

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

 void fpsCompressVidio(File file) {

        String sourcePath = file.getPath();
        String fileName = file.getName();
        long fileSize =  file.length()/1000;
        String resFilePath = fpsFilePath + fileName;
        Uri uri = Uri.parse(sourcePath);

        long duration = 0;
        int width = 0;
        int height = 0;
        float aveFrameRate = 0.0f;
        int frameRate = 0;
        try {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(this,uri);
            int bitrate = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
            retriever.release();
            MediaExtractor extractor = new MediaExtractor();
            extractor.setDataSource(this,uri,null);
            MediaFormat format = extractor.getTrackFormat(VideoUtil.selectTrack(extractor, false));
            frameRate = format.containsKey(MediaFormat.KEY_FRAME_RATE) ? format.getInteger(MediaFormat.KEY_FRAME_RATE) : -1;
            aveFrameRate = VideoUtil.getAveFrameRate(new VideoProcessor.MediaSource(this,uri));
            width = format.getInteger(MediaFormat.KEY_WIDTH);
            height = format.getInteger(MediaFormat.KEY_HEIGHT);
            int rotation = format.containsKey(MediaFormat.KEY_ROTATION) ? format.getInteger(MediaFormat.KEY_ROTATION) : -1;
            duration = format.containsKey(MediaFormat.KEY_DURATION) ? format.getLong(MediaFormat.KEY_DURATION) : -1;
            String KEY_MIME= format.containsKey(MediaFormat.KEY_MIME) ? format.getString(MediaFormat.KEY_MIME) : "";
            String  videoInfo = String.format(Locale.ENGLISH,"size:%dX%d,framerate:%d,aveFrameRate:%f,rotation:%d,bitrate:%d,duration:%.1fs", width, height, frameRate,aveFrameRate, rotation, bitrate,
                    duration / 1000f / 1000f);

            extractor.release();

            Log.e("--testVidio",videoInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }

        long startTimestamp = System.currentTimeMillis();

        if (frameRate > 25) {
            frameRate = 20;
        }

        final int tempFrameRate = frameRate;

        sharedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {

                    VideoProcessor.processor(getApplicationContext())
                            .input(sourcePath)
                            .output(resFilePath)
                            .frameRate(tempFrameRate)
                            .progressListener(new VideoProgressListener() {
                                @Override
                                public void onProgress(float progress) {
                                    int intProgress = (int) (progress * 100);
                                    Log.e("-VideoProcessor:", String.valueOf(intProgress));

                                    uiAction(new Runnable() {
                                        @Override
                                        public void run() {
                                            tv_count.setText(fileName + "压缩进度:"+ intProgress + "%" );
                                        }
                                    });

                                    if (intProgress >=100) {

                                        long curTime = System.currentTimeMillis();
                                        long deltaTime = curTime - startTimestamp;

                                        String res = fileName + "压缩耗时为:" + deltaTime + "ms";
                                        Log.e("-VideoProcessor:", res);

                                    }
                                }
                            }).process();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

四.720P压缩

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

    void compressVideo(File file) {

        String sourcePath = file.getPath();
        String fileName = file.getName();
        String resFilePath = w_h_FilePath + fileName;
        
        long startTimestamp = System.currentTimeMillis();
        
        sharedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    VideoProcessor.processor(getApplicationContext())
                            .input(sourcePath)
                            .output(resFilePath)
                            .outWidth(1280).outHeight(720)
                            .progressListener(new VideoProgressListener() {
                                @Override
                                public void onProgress(float progress) {
                                    int intProgress = (int) (progress * 100);
                                    Log.e("-VideoProcessor:", String.valueOf(intProgress));
                                    
                                    if (intProgress >=100) {

                                        long curTime = System.currentTimeMillis();
                                        long deltaTime = curTime - startTimestamp;

                                        String res = fileName + "压缩耗时为:" + deltaTime + "ms";
                                        Log.e("-VideoProcessor:", res);
                                    }
                                }
                            }).process();

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

五.视频文件转码为H265

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    def start_codec_compressed(self):

        if len(self.fps_list) == 0:
            return

        for file_path in self.fps_list:
            file_name = os.path.basename(file_path)
            dest_file_path = FfmpegManager.CODEC_DEST_FILE_PATH + file_name

            subprocess.run(["ffmpeg", "-i", file_path, "-c:v", "libx265", "-preset", "medium", "-x265-params", "crf=28", dest_file_path], check=True)

使用ffmpeg进行265转码验证下压缩效果

1
2
3
python_fps/high 目录下所有文件大小:4367638719, 共4.0676805367693305G
python_fps/high_codec目录下所有文件大小:1686972766,共1.5711158197373152G
---所有视频文件采用H264转H265压缩方式的压缩率 = 61.38%

在Android设备中,在使用相机录制时,检查设备是否支持H.265编码?如果支持,对相机录制进行配置。如果不支持,使用H264编码。

1.检查设备支持

检查设备是否支持 H.265 编码器,通过检查CamcorderProfile类中的QUALITY_HEVC属性来判断,例如:

1
boolean isHEVCSupported = CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HEVC);

2.配置 MediaRecorder

使用 MediaRecorder 对象进行录制时,你需要将视频编码器属性设置为 VideoEncoder.HEVC,例如:

1
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.HEVC);

3.配置CamcorderProfile类

如果你想使用 CamcorderProfile 类来设置摄像机参数,你可以将 videoCodec 属性设置为 MediaRecorder.VideoEncoder.HEVC,例如:

1
2
CamcorderProfile camcorderProfile = CamcorderProfile.get(cameCamcorderProfile.QUALITY_HIGH);
camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.HEVC;

4.Android系统关于H.265支持情况

HEVC(H.265)是高级视频编码标准,目前有许多现代 Android 设备都支持该标准。以下是一些支持HEVC(H.265)标准的Android系统版本:

  • Android 5.0 及以上版本

    在 Android 5.0(Lollipop)及以上版本中,Google 开始支持 HEVC 解码并播放,该功能可通过 MediaCodec API 访问。

  • Android 7.0 及以上版本

    在 Android 7.0(Nougat)中,Google 添加了对 HEVC 编码的支持。该支持使 Android 设备可以使用 HEVC 标准将视频编码成更小的文件,同时保持更高画质。

  • Android 8.0 及以上版本

    在 Android 8.0(Oreo)及以上版本中,Google 进一步改进了对 HEVC 的支持,并将其作为系统支持的标准编解码器之一。

需要注意的是,设备支持 HEVC 的主要因素是硬件编解码器的支持。如果设备没有内置硬件加速器来处理 HEVC 编解码,则可能无法支持 HEVC 标准。此外,有些 Android 设备可能通过软件方式支持 HEVC 解码,但可能会消耗更多的 CPU 资源或导致性能下降。因此,在使用 HEVC 执行视频编解码时,需要考虑设备硬件和软件资源,以获得最佳的性能和效果

六.结论

  • fps压缩,效果不佳
  • 720P压缩:效果有限
  • 转码H265:如果设备支持,则相机录制视频时进行H265配置