利用MediaExtractor和MediaMuxer实现视频剪切

2023-11-04 09:10

本文主要是介绍利用MediaExtractor和MediaMuxer实现视频剪切,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

客户要在Android手机上做个能视频剪切的app,由于视频源只是MP4,所以就想到了用MediaExtractor和MediaMuxer来实现功能,直接上代码。

public class VideoDecoder {private final static String TAG = "VideoDecoder";private MediaCodec mediaDecoder;private MediaExtractor mediaExtractor;private MediaFormat mediaFormat;private MediaMuxer mediaMuxer;private String mime = null;public boolean decodeVideo(String url, long clipPoint, long clipDuration) {int videoTrackIndex = -1;int audioTrackIndex = -1;int videoMaxInputSize = 0;int audioMaxInputSize = 0;int sourceVTrack = 0;int sourceATrack = 0;long videoDuration, audioDuration;//创建分离器mediaExtractor = new MediaExtractor();try {//设置文件路径mediaExtractor.setDataSource(url);//创建合成器mediaMuxer = new MediaMuxer(url.substring(0, url.lastIndexOf(".")) + "_output.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);} catch (Exception e) {Log.e(TAG, "error path" + e.getMessage());}//获取每个轨道的信息for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {try {mediaFormat = mediaExtractor.getTrackFormat(i);mime = mediaFormat.getString(MediaFormat.KEY_MIME);if (mime.startsWith("video/")) {sourceVTrack = i;int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);videoMaxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);videoDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);//检测剪辑点和剪辑时长是否正确 if (clipPoint >= videoDuration) {Log.e(TAG, "clip point is error!");return false;}if ((clipDuration != 0) && ((clipDuration + clipPoint) >= videoDuration)) {Log.e(TAG, "clip duration is error!");return false;}Log.d(TAG, "width and height is " + width + " " + height+ ";maxInputSize is " + videoMaxInputSize+ ";duration is " + videoDuration);//向合成器添加视频轨videoTrackIndex = mediaMuxer.addTrack(mediaFormat);}else if (mime.startsWith("audio/")) {sourceATrack = i;int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);audioMaxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);audioDuration = mediaFormat.getLong(MediaFormat.KEY_DURATION);Log.d(TAG, "sampleRate is " + sampleRate+ ";channelCount is " + channelCount+ ";audioMaxInputSize is " + audioMaxInputSize+ ";audioDuration is " + audioDuration);//添加音轨audioTrackIndex = mediaMuxer.addTrack(mediaFormat);}Log.d(TAG, "file mime is " + mime);} catch (Exception e) {Log.e(TAG, " read error " + e.getMessage());}}//分配缓冲ByteBuffer inputBuffer = ByteBuffer.allocate(videoMaxInputSize);//根据官方文档的解释MediaMuxer的start一定要在addTrack之后mediaMuxer.start();//视频处理部分mediaExtractor.selectTrack(sourceVTrack);MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();videoInfo.presentationTimeUs = 0;long videoSampleTime;//获取源视频相邻帧之间的时间间隔。(1){mediaExtractor.readSampleData(inputBuffer, 0);//skip first I frameif (mediaExtractor.getSampleFlags() == MediaExtractor.SAMPLE_FLAG_SYNC)mediaExtractor.advance();mediaExtractor.readSampleData(inputBuffer, 0);long firstVideoPTS = mediaExtractor.getSampleTime();mediaExtractor.advance();mediaExtractor.readSampleData(inputBuffer, 0);long SecondVideoPTS = mediaExtractor.getSampleTime();videoSampleTime = Math.abs(SecondVideoPTS - firstVideoPTS);Log.d(TAG, "videoSampleTime is " + videoSampleTime);}//选择起点mediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);while (true) {int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);if (sampleSize < 0) {//这里一定要释放选择的轨道,不然另一个轨道就无法选中了mediaExtractor.unselectTrack(sourceVTrack);break;}int trackIndex = mediaExtractor.getSampleTrackIndex();//获取时间戳long presentationTimeUs = mediaExtractor.getSampleTime();//获取帧类型,只能识别是否为I帧int sampleFlag = mediaExtractor.getSampleFlags();Log.d(TAG, "trackIndex is " + trackIndex+ ";presentationTimeUs is " + presentationTimeUs+ ";sampleFlag is " + sampleFlag+ ";sampleSize is " + sampleSize);//剪辑时间到了就跳出if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {mediaExtractor.unselectTrack(sourceVTrack);break;}mediaExtractor.advance();videoInfo.offset = 0;videoInfo.size = sampleSize;videoInfo.flags = sampleFlag;mediaMuxer.writeSampleData(videoTrackIndex, inputBuffer, videoInfo);videoInfo.presentationTimeUs += videoSampleTime;//presentationTimeUs;}//音频部分mediaExtractor.selectTrack(sourceATrack);MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();audioInfo.presentationTimeUs = 0;long audioSampleTime;//获取音频帧时长{mediaExtractor.readSampleData(inputBuffer, 0);//skip first sampleif (mediaExtractor.getSampleTime() == 0)mediaExtractor.advance();mediaExtractor.readSampleData(inputBuffer, 0);long firstAudioPTS = mediaExtractor.getSampleTime();mediaExtractor.advance();mediaExtractor.readSampleData(inputBuffer, 0);long SecondAudioPTS = mediaExtractor.getSampleTime();audioSampleTime = Math.abs(SecondAudioPTS - firstAudioPTS);Log.d(TAG, "AudioSampleTime is " + audioSampleTime);}mediaExtractor.seekTo(clipPoint, MediaExtractor.SEEK_TO_CLOSEST_SYNC);while (true) {int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);if (sampleSize < 0) {mediaExtractor.unselectTrack(sourceATrack);break;}int trackIndex = mediaExtractor.getSampleTrackIndex();long presentationTimeUs = mediaExtractor.getSampleTime();Log.d(TAG, "trackIndex is " + trackIndex+ ";presentationTimeUs is " + presentationTimeUs);if ((clipDuration != 0) && (presentationTimeUs > (clipPoint + clipDuration))) {mediaExtractor.unselectTrack(sourceATrack);break;}mediaExtractor.advance();audioInfo.offset = 0;audioInfo.size = sampleSize;mediaMuxer.writeSampleData(audioTrackIndex, inputBuffer, audioInfo);audioInfo.presentationTimeUs += audioSampleTime;//presentationTimeUs;}//全部写完后释放MediaMuxer和MediaExtractormediaMuxer.stop();mediaMuxer.release();mediaExtractor.release();mediaExtractor = null;return true;}
}
  • 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
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 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
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173

这里要对(1)进行一下解释,如果视频中带了B帧,源视频时间戳会按照播放顺序来打,这样通过读每帧获取到的时间戳就不是一直增长的,MediaMuxer的writeSampleData函数在写每帧的时间戳时如果时间戳不是增长顺序就会报错,帧率也无法通过MediaFormat.KEY_FRAME_RATE来获取,也可以通过查找MP4文件的stsz关键字来获取总帧数,进而算出帧率,来按帧率给每一帧打时间戳,但是stsz一般都在文件的后面而且含有音视频的文件会有两个stsz,如果文件很大,查找起来就会很慢,所以这里我用了个偷懒的办法,用I帧后面两帧的时间戳绝对差值来当做每帧的时间,然后+=这个时间来为写入的帧打时间戳,虽然时间戳不是源视频的顺序了,但并不影响解码。 
还有一点就是unselectTrack这个函数,用selectTrack选择轨道后,如果要切换轨道一定要调用unselectTrack函数来释放轨道,不然读的还是之前的轨道。

这篇关于利用MediaExtractor和MediaMuxer实现视频剪切的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/344736

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核