您的位置:首页 > 移动开发 > Android开发

Android实现视频剪切、视频拼接以及音视频合并

2017-07-06 18:10 435 查看
              因公司项目有需求,要实现视频剪切,视频拼接以及音视频合并的功能,自己通过在网上查找大量的资料终于把功能实现了,把实现的公共类提取出来,以便以后复习巩固。

使用map4parser作为视频处理包,android studio引入 compile 'com.googlecode.mp4parser:isoparser:1.1.21'//视频处理

工具类

一、视频处理工具类  

/**
* mp4处理公共类
* Created by lxy on 17-4-21.
*/

public class Mp4ParseUtil {

/**
* 对Mp4文件集合进行追加合并(按照顺序一个一个拼接起来)
*
* @param mp4PathList [输入]Mp4文件路径的集合(支持m4a)(不支持wav)
* @param outPutPath  [输出]结果文件全部名称包含后缀(比如.mp4)
* @throws IOException 格式不支持等情况抛出异常
*/
public static void appendMp4List(List<String> mp4PathList, String outPutPath){

try {

List<Movie> mp4MovieList = new ArrayList<>();// Movie对象集合[输入]
for (String mp4Path : mp4PathList) {// 将每个文件路径都构建成一个Movie对象
mp4MovieList.add(MovieCreator.build(mp4Path));
}

List<Track> audioTracks = new LinkedList<>();// 音频通道集合
List<Track> videoTracks = new LinkedList<>();// 视频通道集合

for (Movie mp4Movie : mp4MovieList) {// 对Movie对象集合进行循环
for (Track inMovieTrack : mp4Movie.getTracks()) {
if ("soun".equals(inMovieTrack.getHandler())) {// 从Movie对象中取出音频通道
audioTracks.add(inMovieTrack);
}
if ("vide".equals(inMovieTrack.getHandler())) {// 从Movie对象中取出视频通道
videoTracks.add(inMovieTrack);
}
}
}
Movie resultMovie = new Movie();// 结果Movie对象[输出]
if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}

if (!videoTracks.isEmpty()) {// 将所有视频通道追加合并
resultMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()])));
}

Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
fileChannel.close();

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

}

/**
* 对AAC文件集合进行追加合并(按照顺序一个一个拼接起来)
*
* @param aacPathList [输入]AAC文件路径的集合(不支持wav)
* @param outPutPath  [输出]结果文件全部名称包含后缀(比如.aac)
* @throws IOException 格式不支持等情况抛出异常
*/
public static void appendAacList(List<String> aacPathList, String outPutPath){

try{

List<Track> audioTracks = new LinkedList<>();// 音频通道集合
for (int i = 0; i < aacPathList.size(); i++) {// 将每个文件路径都构建成一个AACTrackImpl对象
audioTracks.add(new AACTrackImpl(new FileDataSourceImpl(aacPathList.get(i))));
}

Movie resultMovie = new Movie();// 结果Movie对象[输出]
if (!audioTracks.isEmpty()) {// 将所有音频通道追加合并
resultMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()])));
}

Container outContainer = new DefaultMp4Builder().build(resultMovie);// 将结果Movie对象封装进容器
FileChannel fileChannel = new RandomAccessFile(String.format(outPutPath), "rw").getChannel();
outContainer.writeContainer(fileChannel);// 将容器内容写入磁盘
fileChannel.close();

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

}

private static List<Movie> moviesList = new ArrayList<>();
private static List<Track> videoTracks = new ArrayList<>();
private static List<Track> audioTracks = new ArrayList<>();
//将两个mp4视频进行拼接
public static void appendMp4(List<String> mMp4List,String outputpath){

try {
for (int i=0;i<mMp4List.size();i++) {
Movie movie=MovieCreator.build(mMp4List.get(i));
moviesList.add(movie);
}
} catch (IOException e) {
e.printStackTrace();
}

for (Movie m : moviesList) {
for (Track t : m.getTracks()) {
if (t.getHandler().equals("soun")) {
audioTracks.add(t);
}
if (t.getHandler().equals("vide")) {
videoTracks.add(t);
}
}
}

Movie result = new Movie();

try {
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()])));
}
} catch (IOException e) {
e.printStackTrace();
}

Container out = new DefaultMp4Builder().build(result);

try {
FileChannel fc = new FileOutputStream(new File(outputpath)).getChannel();
out.writeContainer(fc);
fc.close();
} catch (Exception e) {
e.printStackTrace();
}

moviesList.clear();
}

/**
* 将 AAC 和 MP4 进行混合[替换了视频的音轨]
*
* @param aacPath .aac
* @param mp4Path .mp4
* @param outPath .mp4
*/
public static boolean muxAacMp4(String aacPath, String mp4Path, String outPath) {
boolean flag=false;
try {
AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath));
Movie videoMovie = MovieCreator.build(mp4Path);
Track videoTracks = null;// 获取视频的单纯视频部分
for (Track videoMovieTrack : videoMovie.getTracks()) {
if ("vide".equals(videoMovieTrack.getHandler())) {
videoTracks = videoMovieTrack;
}
}

Movie resultMovie = new Movie();
resultMovie.addTrack(videoTracks);// 视频部分
resultMovie.addTrack(aacTrack);// 音频部分

Container out = new DefaultMp4Builder().build(resultMovie);
FileOutputStream fos = new <
19cc0
/strong>FileOutputStream([b]new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();
flag=true;
Log.e("update_tag","merge finish");
} catch (Exception e) {
e.printStackTrace();
flag=false;
}
return flag;
}

/**
* 将 M4A 和 MP4 进行混合[替换了视频的音轨]
*
* @param m4aPath .m4a[同样可以使用.mp4]
* @param mp4Path .mp4
* @param outPath .mp4
*/
public static void muxM4AMp4(String m4aPath, String mp4Path, String outPath) throws IOException {
Movie audioMovie = MovieCreator.build(m4aPath);
Track audioTracks = null;// 获取视频的单纯音频部分
for (Track audioMovieTrack : audioMovie.getTracks()) {
if ("soun".equals(audioMovieTrack.getHandler())) {
audioTracks = audioMovieTrack;
}
}

Movie videoMovie = MovieCreator.build(mp4Path);
Track videoTracks = null;// 获取视频的单纯视频部分
for (Track videoMovieTrack : videoMovie.getTracks()) {
if ("vide".equals(videoMovieTrack.getHandler())) {
videoTracks = videoMovieTrack;
}
}

Movie resultMovie = new Movie();
resultMovie.addTrack(videoTracks);// 视频部分
resultMovie.addTrack(audioTracks);// 音频部分

Container out = new DefaultMp4Builder().build(resultMovie);
FileOutputStream fos = new FileOutputStream(new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();
}

/**
* 分离mp4视频的音频部分,只保留视频部分
*
* @param mp4Path .mp4
* @param outPath .mp4
*/
public static void splitMp4(String mp4Path, String outPath){

try{
Movie videoMovie = MovieCreator.build(mp4Path);
Track videoTracks = null;// 获取视频的单纯视频部分
for (Track videoMovieTrack : videoMovie.getTracks()) {
if ("vide".equals(videoMovieTrack.getHandler())) {
videoTracks = videoMovieTrack;
}
}

Movie resultMovie = new Movie();
resultMovie.addTrack(videoTracks);// 视频部分

Container out = new DefaultMp4Builder().build(resultMovie);
FileOutputStream fos = new FileOutputStream(new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();
}catch (Exception e){
e.printStackTrace();
}

}

/**
* 分离mp4的视频部分,只保留音频部分
*
* @param mp4Path .mp4
* @param outPath .aac
*/
public static void splitAac(String mp4Path, String outPath){

try{
Movie videoMovie = MovieCreator.build(mp4Path);
Track videoTracks = null;// 获取音频的单纯视频部分
for (Track videoMovieTrack : videoMovie.getTracks()) {
if ("soun".equals(videoMovieTrack.getHandler())) {
videoTracks = videoMovieTrack;
}
}

Movie resultMovie = new Movie();
resultMovie.addTrack(videoTracks);// 音频部分

Container out = new DefaultMp4Builder().build(resultMovie);
FileOutputStream fos = new FileOutputStream(new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();
}catch (Exception e){
e.printStackTrace();
}

}

/**
* 分离mp4视频的音频部分,只保留视频部分
*
* @param mp4Path .mp4
* @param mp4OutPath  mp4视频输出路径
* @param aacOutPath  aac视频输出路径
*/
public static void splitVideo(String mp4Path, String mp4OutPath,String aacOutPath){

try{
Movie videoMovie = MovieCreator.build(mp4Path);
Track videTracks = null;// 获取视频的单纯视频部分
Track sounTracks = null;// 获取视频的单纯音频部分

for (Track videoMovieTrack : videoMovie.getTracks()) {
if ("vide".equals(videoMovieTrack.getHandler())) {
videTracks = videoMovieTrack;
}
if ("soun".equals(videoMovieTrack.getHandler())) {
sounTracks = videoMovieTrack;
}
}

Movie videMovie = new Movie();
videMovie.addTrack(videTracks);// 视频部分

Movie sounMovie = new Movie();
sounMovie.addTrack(sounTracks);// 音频部分

// 视频部分
Container videout = new DefaultMp4Builder().build(videMovie);
FileOutputStream videfos = new FileOutputStream(new File(mp4OutPath));
videout.writeContainer(videfos.getChannel());
videfos.close();

// 音频部分
Container sounout = new DefaultMp4Builder().build(sounMovie);
FileOutputStream sounfos = new FileOutputStream(new File(aacOutPath));
sounout.writeContainer(sounfos.getChannel());
sounfos.close();

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

}

/**
* 对 Mp4 添加字幕
*
* @param mp4Path .mp4 添加字幕之前
* @param outPath .mp4 添加字幕之后
*/
public static void addSubtitles(String mp4Path, String outPath) throws IOException {
Movie videoMovie = MovieCreator.build(mp4Path);

TextTrackImpl subTitleEng = new TextTrackImpl();// 实例化文本通道对象
subTitleEng.getTrackMetaData().setLanguage("eng");// 设置元数据(语言)

subTitleEng.getSubs().add(new TextTrackImpl.Line(0, 1000, "Five"));// 参数时间毫秒值
subTitleEng.getSubs().add(new TextTrackImpl.Line(1000, 2000, "Four"));
subTitleEng.getSubs().add(new TextTrackImpl.Line(2000, 3000, "Three"));
subTitleEng.getSubs().add(new TextTrackImpl.Line(3000, 4000, "Two"));
subTitleEng.getSubs().add(new TextTrackImpl.Line(4000, 5000, "one"));
subTitleEng.getSubs().add(new TextTrackImpl.Line(5001, 5002, " "));// 省略去测试
videoMovie.addTrack(subTitleEng);// 将字幕通道添加进视频Movie对象中

Container out = new DefaultMp4Builder().build(videoMovie);
FileOutputStream fos = new FileOutputStream(new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();
}

/**
* 将 MP4 切割
*
* @param mp4Path    .mp4
* @param fromSample 起始位置   不是传入的秒数
* @param toSample   结束位置   不是传入的秒数
* @param outPath    .mp4
*/
public static void cropMp4(String mp4Path, long fromSample, long toSample, String outPath){

try{

Movie mp4Movie = MovieCreator.build(mp4Path);
Track videoTracks = null;// 获取视频的单纯视频部分
for (Track videoMovieTrack : mp4Movie.getTracks()) {
if ("vide".equals(videoMovieTrack.getHandler())) {
videoTracks = videoMovieTrack;
}
}
Track audioTracks = null;// 获取视频的单纯音频部分
for (Track audioMovieTrack : mp4Movie.getTracks()) {
if ("soun".equals(audioMovieTrack.getHandler())) {
audioTracks = audioMovieTrack;
}
}

Movie resultMovie = new Movie();
resultMovie.addTrack(new AppendTrack(new CroppedTrack(videoTracks, fromSample, toSample)));// 视频部分
resultMovie.addTrack(new AppendTrack(new CroppedTrack(audioTracks, fromSample, toSample)));// 音频部分

Container out = new DefaultMp4Builder().build(resultMovie);
FileOutputStream fos = new FileOutputStream(new File(outPath));
out.writeContainer(fos.getChannel());
fos.close();

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

}

}


二、视频剪切工具类

/**
* 视频剪切
* Created by lxy on 17-4-21.
*/

public class VideoClip {

private static final String TAG = "VideoClip";
private String filePath;//视频路径
private String workingPath;//输出路径
private String outName;//输出文件名
private double startTime;//剪切起始时间
private double endTime;//剪切结束时间

public void setFilePath(String filePath) {
this.filePath = filePath;
}

public void setWorkingPath(String workingPath) {
this.workingPath = workingPath;
}

public void setOutName(String outName) {
this.outName = outName;
}

public void setEndTime(double endTime) {
this.endTime = endTime / 1000;
}

public void setStartTime(double startTime) {
this.startTime = startTime / 1000;
}

public synchronized void clip() {
try {
//将要剪辑的视频文件
Movie movie = MovieCreator.build(filePath);

List<Track> tracks = movie.getTracks();
movie.setTracks(new LinkedList<Track>());
//时间是否修正
boolean timeCorrected = false;

//计算并换算剪切时间
for (Track track : tracks) {
if (track.getSyncSamples() != null
&& track.getSyncSamples().length > 0) {
if (timeCorrected) {
throw new RuntimeException(
"The startTime has already been corrected by another track with SyncSample. Not Supported.");
}
//true,false表示短截取;false,true表示长截取
startTime = VideoHelper.correctTimeToSyncSample(track, startTime, false);//修正后的开始时间
endTime = VideoHelper.correctTimeToSyncSample(track, endTime, true);     //修正后的结束时间
timeCorrected = true;
}
}
//根据换算到的开始时间和结束时间来截取视频
for (Track track : tracks) {
long currentSample = 0; //视频截取到的当前的位置的时间
double currentTime = 0; //视频的时间长度
double lastTime = -1;    //上次截取到的最后的时间
long startSample1 = -1;  //截取开始的时间
long endSample1 = -1;    //截取结束的时间

//设置开始剪辑的时间和结束剪辑的时间  避免超出视频总长
for (int i = 0; i < track.getSampleDurations().length; i++) {
long delta = track.getSampleDurations()[i];
if (currentTime > lastTime && currentTime <= startTime) {
startSample1 = currentSample;//编辑开始的时间
}
if (currentTime > lastTime && currentTime <= endTime) {
endSample1 = currentSample;  //编辑结束的时间
}
lastTime = currentTime;          //上次截取到的时间(避免在视频最后位置了还在增加编辑结束的时间)
currentTime += (double) delta
/ (double) track.getTrackMetaData().getTimescale();//视频的时间长度
currentSample++;                 //当前位置+1
}
movie.addTrack(new CroppedTrack(track, startSample1, endSample1));// 创建一个新的视频文件
}

//合成视频mp4
Container out = new DefaultMp4Builder().build(movie);
File storagePath = new File(workingPath);
storagePath.mkdirs();
FileOutputStream fos = new FileOutputStream(new File(storagePath, outName));
FileChannel fco = fos.getChannel();
out.writeContainer(fco);
//关闭流
fco.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}


/**
* Created by lxy on 17-4-17.
*/

public class VideoHelper {

//换算剪切时间
public 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.getSampleDurations().length; i++) {
long delta = track.getSampleDurations()[i];
if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(),
currentSample + 1)] = currentTime;
}
currentTime += (double) delta
/ (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];
}

}


三、实现录音功能的类

/**
* 实现录音功能的类
*/
public class MediaRecorderUtil {

private MediaRecorder mediarecorder=null;//录音功能公共类
private static final String recordFilePath = FileUtil.getRecorderDir()+"/recorder.aac";
private RecorderThread recorderThread=null;
private static final String UPDATE_TAG="update_tag";

/**
* 开启录音功能
*/
public void recorderStart(){

//启动midiarecoder录音
recorderThread=new RecorderThread();
recorderThread.start();

}

/**
* 停止录音,并保存录音
*/
public void recorderSave(){

if(mediarecorder!=null){
mediarecorder.stop();
mediarecorder.release();
mediarecorder=null;

if(recorderThread!=null){
recorderThread=null;
}
Log.e(UPDATE_TAG,"Thread stop voice and save...");
}

}

//开启录音功能线程
class RecorderThread extends Thread{

@Override
public void run() {
super.run();
try {

//创建保存录音文件
File file=new File(recordFilePath);

if (mediarecorder==null) {
mediarecorder=new MediaRecorder();//实例化录音文件对象
}
mediarecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置获取录音文件来源
mediarecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);//设置录音文件输出格式
mediarecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//设置录音文件的编码格式
mediarecorder.setOutputFile(file.getAbsolutePath());//设置录音文件的输出路径

mediarecorder.prepare();//录音文件的准备工作
mediarecorder.start();//录音开始

Log.e(UPDATE_TAG,"Thread start voice...");

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

}


我项目主要用到的功能实现方法

一、视频剪切主要实现方法

/**
* 视频剪切
* @param startTime 视频剪切的开始时间
* @param endTime 视频剪切的结束时间
* @param FilePath 被剪切视频的路径
* @param WorkingPath 剪切成功保存的视频路径
* @param fileName 剪切成功保存的文件名
*/
private synchronized void cutMp4(final long startTime, final long endTime, final String FilePath, final String WorkingPath, final String fileName){
new Thread(new Runnable() {
@Override
public void run() {

try{
//视频剪切
VideoClip videoClip= new VideoClip();//实例化VideoClip类
videoClip.setFilePath(FilePath);//设置被编辑视频的文件路径  FileUtil.getMediaDir()+"/test/laoma3.mp4"
videoClip.setWorkingPath(WorkingPath);//设置被编辑的视频输出路径  FileUtil.getMediaDir()
videoClip.setStartTime(startTime);//设置剪辑开始的时间
videoClip.setEndTime(endTime);//设置剪辑结束的时间
videoClip.setOutName(fileName);//设置输出的文件名称
videoClip.clip();//调用剪辑并保存视频文件方法(建议作为点击保存时的操作并加入等待对话框)

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


二、开启录音功能

MediaRecorderUtil mrUtil=new MediaRecorderUtil();
mrUtil.recorderStart();

三、音视频合成

//aacPath录音的文件路径、mp4Path原视频路径、outPath合成之后输出的视频文件路径

boolean flag=Mp4ParseUtil.muxAacMp4(aacPath,mp4Path,outPath);


四、视频拼接

//mMp4List视频拼接的路径集合、outPath拼接成功的视频输出路径

Mp4ParseUtil.appendMp4List(mMp4List,outPath);


大致就这样了

我不是个呆若木鸡的小小英
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 视频 合并