586 lines
16 KiB
Java
586 lines
16 KiB
Java
package com.mabeijianxi.smallvideorecord2.model;
|
||
|
||
import com.mabeijianxi.smallvideorecord2.FileUtils;
|
||
import com.mabeijianxi.smallvideorecord2.StringUtils;
|
||
|
||
import java.io.File;
|
||
import java.io.FileOutputStream;
|
||
import java.io.IOException;
|
||
import java.io.Serializable;
|
||
import java.util.Iterator;
|
||
import java.util.LinkedList;
|
||
import java.util.List;
|
||
import java.util.concurrent.CopyOnWriteArrayList;
|
||
import java.util.concurrent.locks.Lock;
|
||
import java.util.concurrent.locks.ReentrantLock;
|
||
import java.util.function.Consumer;
|
||
|
||
|
||
@SuppressWarnings("serial")
|
||
public class MediaObject implements Serializable {
|
||
|
||
/**
|
||
* 拍摄
|
||
*/
|
||
public final static int MEDIA_PART_TYPE_RECORD = 0;
|
||
/**
|
||
* 导入视频
|
||
*/
|
||
public final static int MEDIA_PART_TYPE_IMPORT_VIDEO = 1;
|
||
/**
|
||
* 导入图片
|
||
*/
|
||
public final static int MEDIA_PART_TYPE_IMPORT_IMAGE = 2;
|
||
/**
|
||
* 使用系统拍摄mp4
|
||
*/
|
||
public final static int MEDIA_PART_TYPE_RECORD_MP4 = 3;
|
||
/**
|
||
* 默认最大时长
|
||
*/
|
||
public final static int DEFAULT_MAX_DURATION = 10 * 1000;
|
||
/**
|
||
* 默认码率
|
||
*/
|
||
public final static int DEFAULT_VIDEO_BITRATE = 800;
|
||
|
||
/**
|
||
* 视频最大时长,默认10秒
|
||
*/
|
||
private int mMaxDuration;
|
||
/**
|
||
* 视频目录
|
||
*/
|
||
private String mOutputDirectory;
|
||
/**
|
||
* 对象文件
|
||
*/
|
||
private String mOutputObjectPath;
|
||
/**
|
||
* 视频码率
|
||
*/
|
||
private int mVideoBitrate;
|
||
/**
|
||
* 最终视频输出路径
|
||
*/
|
||
private String mOutputVideoPath;
|
||
/**
|
||
* 最终视频截图输出路径
|
||
*/
|
||
private String mOutputVideoThumbPath;
|
||
/**
|
||
* 文件夹、文件名
|
||
*/
|
||
private String mKey;
|
||
/**
|
||
* 当前分块
|
||
*/
|
||
private volatile transient MediaPart mCurrentPart;
|
||
/**
|
||
* 获取所有分块
|
||
*/
|
||
private List<MediaPart> mMediaList = new CopyOnWriteArrayList<MediaPart>();
|
||
|
||
private Lock lock = new ReentrantLock();
|
||
|
||
|
||
/**
|
||
* 主题
|
||
*/
|
||
public MediaThemeObject mThemeObject;
|
||
private String outputTempVideoPath;
|
||
|
||
public MediaObject(String key, String path) {
|
||
this(key, path, DEFAULT_VIDEO_BITRATE);
|
||
}
|
||
|
||
public MediaObject(String key, String path, int videoBitrate) {
|
||
this.mKey = key;
|
||
this.mOutputDirectory = path;
|
||
this.mVideoBitrate = videoBitrate;
|
||
this.mOutputObjectPath = mOutputDirectory + File.separator + mKey + ".obj";
|
||
this.mOutputVideoPath = mOutputDirectory + ".mp4";
|
||
this.mOutputVideoThumbPath = mOutputDirectory + File.separator + mKey + ".jpg";
|
||
this.mMaxDuration = DEFAULT_MAX_DURATION;
|
||
this.outputTempVideoPath = mOutputDirectory + File.separator + mKey + "_temp.mp4";
|
||
}
|
||
|
||
|
||
public String getBaseName(){
|
||
return mKey;
|
||
}
|
||
/**
|
||
* 获取视频码率
|
||
*/
|
||
public int getVideoBitrate() {
|
||
return mVideoBitrate;
|
||
}
|
||
|
||
/**
|
||
* 获取视频最大长度
|
||
*/
|
||
public int getMaxDuration() {
|
||
return mMaxDuration;
|
||
}
|
||
|
||
/**
|
||
* 设置最大时长,必须大于1秒
|
||
*/
|
||
public void setMaxDuration(int duration) {
|
||
if (duration >= 1000) {
|
||
mMaxDuration = duration;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取视频临时文件夹
|
||
*/
|
||
public String getOutputDirectory() {
|
||
return mOutputDirectory;
|
||
}
|
||
|
||
/**
|
||
* 获取视频临时输出播放
|
||
*/
|
||
public String getOutputTempVideoPath() {
|
||
return outputTempVideoPath;
|
||
}
|
||
|
||
public void setOutputTempVideoPath(String path) {
|
||
this.outputTempVideoPath = path;
|
||
}
|
||
|
||
public String getOutputTempTranscodingVideoPath() {
|
||
return mOutputDirectory +
|
||
File.separator + mKey + ".mp4";
|
||
}
|
||
|
||
/**
|
||
* 清空主题
|
||
*/
|
||
public void cleanTheme() {
|
||
mThemeObject = null;
|
||
if (mMediaList != null) {
|
||
for (MediaPart part : mMediaList) {
|
||
part.cutStartTime = 0;
|
||
part.cutEndTime = part.duration;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取视频信息春促路径
|
||
*/
|
||
public String getObjectFilePath() {
|
||
if (StringUtils.isEmpty(mOutputObjectPath)) {
|
||
File f = new File(mOutputVideoPath);
|
||
String obj = mOutputDirectory + File.separator + f.getName() + ".obj";
|
||
mOutputObjectPath = obj;
|
||
}
|
||
return mOutputObjectPath;
|
||
}
|
||
|
||
/**
|
||
* 获取视频最终输出地址
|
||
*/
|
||
public String getOutputVideoPath() {
|
||
return mOutputVideoPath;
|
||
}
|
||
|
||
/**
|
||
* 获取视频截图最终输出地址
|
||
*/
|
||
public String getOutputVideoThumbPath() {
|
||
return mOutputVideoThumbPath;
|
||
}
|
||
|
||
/**
|
||
* 获取录制的总时长
|
||
*/
|
||
public int getDuration() {
|
||
int duration = 0;
|
||
if (mMediaList != null) {
|
||
for (MediaPart part : mMediaList) {
|
||
duration += part.getDuration();
|
||
}
|
||
}
|
||
return duration;
|
||
}
|
||
|
||
/**
|
||
* 获取剪切后的总时长
|
||
*/
|
||
public int getCutDuration() {
|
||
int duration = 0;
|
||
if (mMediaList != null) {
|
||
for (MediaPart part : mMediaList) {
|
||
int cut = (part.cutEndTime - part.cutStartTime);
|
||
if (part.speed != 10) {
|
||
cut = (int) (cut * (10F / part.speed));
|
||
}
|
||
duration += cut;
|
||
}
|
||
}
|
||
return duration;
|
||
}
|
||
|
||
public void eachPart(Consumer<MediaPart> consumer){
|
||
if(mMediaList == null){
|
||
return;
|
||
}
|
||
Iterator<MediaPart> iterator = mMediaList.iterator();
|
||
synchronized (iterator){
|
||
while(iterator.hasNext()){
|
||
consumer.accept(iterator.next());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除分块
|
||
*/
|
||
public void removePart(MediaPart part, boolean deleteFile) {
|
||
if (mMediaList != null)
|
||
mMediaList.remove(part);
|
||
|
||
if (part != null) {
|
||
part.stop();
|
||
// 删除文件
|
||
if (deleteFile) {
|
||
part.delete();
|
||
}
|
||
mMediaList.remove(part);
|
||
if (mCurrentPart != null && part.equals(mCurrentPart)) {
|
||
mCurrentPart = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成分块信息,主要用于拍摄
|
||
*
|
||
* @param cameraId 记录摄像头是前置还是后置
|
||
* @return
|
||
*/
|
||
public MediaPart buildMediaPart(int cameraId) {
|
||
mCurrentPart = new MediaPart();
|
||
mCurrentPart.position = getDuration();
|
||
mCurrentPart.index = mMediaList.size();
|
||
mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + ".v";
|
||
mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a";
|
||
mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg";
|
||
mCurrentPart.cameraId = cameraId;
|
||
mCurrentPart.prepare();
|
||
mCurrentPart.recording = true;
|
||
mCurrentPart.startTime = System.currentTimeMillis();
|
||
mCurrentPart.type = MEDIA_PART_TYPE_IMPORT_VIDEO;
|
||
mMediaList.add(mCurrentPart);
|
||
return mCurrentPart;
|
||
}
|
||
|
||
public MediaPart buildMediaPart(int cameraId, String videoSuffix) {
|
||
mCurrentPart = new MediaPart();
|
||
mCurrentPart.position = getDuration();
|
||
mCurrentPart.index = mMediaList.size();
|
||
mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + videoSuffix;
|
||
mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a";
|
||
mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg";
|
||
mCurrentPart.recording = true;
|
||
mCurrentPart.cameraId = cameraId;
|
||
mCurrentPart.startTime = System.currentTimeMillis();
|
||
mCurrentPart.type = MEDIA_PART_TYPE_IMPORT_VIDEO;
|
||
mMediaList.add(mCurrentPart);
|
||
return mCurrentPart;
|
||
}
|
||
|
||
/**
|
||
* 生成分块信息,主要用于视频导入
|
||
*
|
||
* @param path
|
||
* @param duration
|
||
* @param type
|
||
* @return
|
||
*/
|
||
public MediaPart buildMediaPart(String path, int duration, int type) {
|
||
mCurrentPart = new MediaPart();
|
||
mCurrentPart.position = getDuration();
|
||
mCurrentPart.index = mMediaList.size();
|
||
mCurrentPart.mediaPath = mOutputDirectory + File.separator + mCurrentPart.index + ".v";
|
||
mCurrentPart.audioPath = mOutputDirectory + File.separator + mCurrentPart.index + ".a";
|
||
mCurrentPart.thumbPath = mOutputDirectory + File.separator + mCurrentPart.index + ".jpg";
|
||
mCurrentPart.duration = duration;
|
||
mCurrentPart.startTime = 0;
|
||
mCurrentPart.endTime = duration;
|
||
mCurrentPart.cutStartTime = 0;
|
||
mCurrentPart.cutEndTime = duration;
|
||
mCurrentPart.tempPath = path;
|
||
mCurrentPart.type = type;
|
||
mMediaList.add(mCurrentPart);
|
||
return mCurrentPart;
|
||
}
|
||
|
||
public String getConcatYUV() {
|
||
StringBuilder yuv = new StringBuilder();
|
||
if (mMediaList != null && mMediaList.size() > 0) {
|
||
if (mMediaList.size() == 1) {
|
||
if (StringUtils.isEmpty(mMediaList.get(0).tempMediaPath))
|
||
yuv.append(mMediaList.get(0).mediaPath);
|
||
else
|
||
yuv.append(mMediaList.get(0).tempMediaPath);
|
||
} else {
|
||
yuv.append("concat:");
|
||
for (int i = 0, j = mMediaList.size(); i < j; i++) {
|
||
MediaPart part = mMediaList.get(i);
|
||
if (StringUtils.isEmpty(part.tempMediaPath))
|
||
yuv.append(part.mediaPath);
|
||
else
|
||
yuv.append(part.tempMediaPath);
|
||
if (i + 1 < j) {
|
||
yuv.append("|");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return yuv.toString();
|
||
}
|
||
|
||
public String getConcatPCM() {
|
||
StringBuilder yuv = new StringBuilder();
|
||
if (mMediaList != null && mMediaList.size() > 0) {
|
||
if (mMediaList.size() == 1) {
|
||
if (StringUtils.isEmpty(mMediaList.get(0).tempAudioPath))
|
||
yuv.append(mMediaList.get(0).audioPath);
|
||
else
|
||
yuv.append(mMediaList.get(0).tempAudioPath);
|
||
} else {
|
||
yuv.append("concat:");
|
||
for (int i = 0, j = mMediaList.size(); i < j; i++) {
|
||
MediaPart part = mMediaList.get(i);
|
||
if (StringUtils.isEmpty(part.tempAudioPath))
|
||
yuv.append(part.audioPath);
|
||
else
|
||
yuv.append(part.tempAudioPath);
|
||
if (i + 1 < j) {
|
||
yuv.append("|");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return yuv.toString();
|
||
}
|
||
|
||
/**
|
||
* 获取当前分块
|
||
*/
|
||
public MediaPart getCurrentPart() {
|
||
if (mCurrentPart != null)
|
||
return mCurrentPart;
|
||
if (mMediaList != null && mMediaList.size() > 0)
|
||
mCurrentPart = mMediaList.get(mMediaList.size() - 1);
|
||
return mCurrentPart;
|
||
}
|
||
|
||
public int getCurrentIndex() {
|
||
MediaPart part = getCurrentPart();
|
||
if (part != null)
|
||
return part.index;
|
||
return 0;
|
||
}
|
||
|
||
public MediaPart getPart(int index) {
|
||
if (mCurrentPart != null && index < mMediaList.size())
|
||
return mMediaList.get(index);
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 取消拍摄
|
||
*/
|
||
public void delete() {
|
||
if (mMediaList != null) {
|
||
for (MediaPart part : mMediaList) {
|
||
part.stop();
|
||
}
|
||
}
|
||
FileUtils.deleteDir(mOutputDirectory);
|
||
}
|
||
|
||
public List<MediaPart> getMedaParts() {
|
||
return mMediaList;
|
||
}
|
||
|
||
/**
|
||
* 预处理数据对象
|
||
*/
|
||
public static void preparedMediaObject(MediaObject mMediaObject) {
|
||
if (mMediaObject != null && mMediaObject.mMediaList != null) {
|
||
int duration = 0;
|
||
for (MediaPart part : mMediaObject.mMediaList) {
|
||
part.startTime = duration;
|
||
part.endTime = part.startTime + part.duration;
|
||
duration += part.duration;
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
StringBuffer result = new StringBuffer();
|
||
if (mMediaList != null) {
|
||
result.append("[" + mMediaList.size() + "]");
|
||
for (MediaPart part : mMediaList) {
|
||
result.append(part.mediaPath + ":" + part.duration + "\n");
|
||
}
|
||
}
|
||
return result.toString();
|
||
}
|
||
|
||
public static class MediaPart implements Serializable {
|
||
|
||
/**
|
||
* 索引
|
||
*/
|
||
public int index;
|
||
/**
|
||
* 视频路径
|
||
*/
|
||
public String mediaPath;
|
||
/**
|
||
* 音频路径
|
||
*/
|
||
public String audioPath;
|
||
/**
|
||
* 临时视频路径
|
||
*/
|
||
public String tempMediaPath;
|
||
/**
|
||
* 临时音频路径
|
||
*/
|
||
public String tempAudioPath;
|
||
/**
|
||
* 截图路径
|
||
*/
|
||
public String thumbPath;
|
||
/**
|
||
* 存放导入的视频和图片
|
||
*/
|
||
public String tempPath;
|
||
/**
|
||
* 类型
|
||
*/
|
||
public int type = MEDIA_PART_TYPE_RECORD;
|
||
/**
|
||
* 剪切视频(开始时间)
|
||
*/
|
||
public int cutStartTime;
|
||
/**
|
||
* 剪切视频(结束时间)
|
||
*/
|
||
public int cutEndTime;
|
||
/**
|
||
* 分段长度
|
||
*/
|
||
public int duration;
|
||
/**
|
||
* 总时长中的具体位置
|
||
*/
|
||
public int position;
|
||
/**
|
||
* 0.2倍速-3倍速(取值2~30)
|
||
*/
|
||
public int speed = 10;
|
||
/**
|
||
* 摄像头
|
||
*/
|
||
public int cameraId;
|
||
/**
|
||
* 视频尺寸
|
||
*/
|
||
public int yuvWidth;
|
||
/**
|
||
* 视频高度
|
||
*/
|
||
public int yuvHeight;
|
||
public transient boolean remove;
|
||
public transient long startTime;
|
||
public transient long endTime;
|
||
public transient FileOutputStream mCurrentOutputVideo;
|
||
public transient FileOutputStream mCurrentOutputAudio;
|
||
public transient volatile boolean recording;
|
||
|
||
public MediaPart() {
|
||
|
||
}
|
||
|
||
public void delete() {
|
||
FileUtils.deleteFile(mediaPath);
|
||
FileUtils.deleteFile(audioPath);
|
||
FileUtils.deleteFile(thumbPath);
|
||
FileUtils.deleteFile(tempMediaPath);
|
||
FileUtils.deleteFile(tempAudioPath);
|
||
}
|
||
|
||
/**
|
||
* 写入音频数据
|
||
*/
|
||
public void writeAudioData(byte[] buffer) throws IOException {
|
||
if (mCurrentOutputAudio != null)
|
||
mCurrentOutputAudio.write(buffer);
|
||
}
|
||
|
||
/**
|
||
* 写入视频数据
|
||
*/
|
||
public void writeVideoData(byte[] buffer) throws IOException {
|
||
if (mCurrentOutputVideo != null)
|
||
mCurrentOutputVideo.write(buffer);
|
||
}
|
||
|
||
public void prepare() {
|
||
try {
|
||
mCurrentOutputVideo = new FileOutputStream(mediaPath);
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
prepareAudio();
|
||
}
|
||
|
||
public void prepareAudio() {
|
||
try {
|
||
mCurrentOutputAudio = new FileOutputStream(audioPath);
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
|
||
public int getDuration() {
|
||
return duration > 0 ? duration : (int) (System.currentTimeMillis() - startTime);
|
||
}
|
||
|
||
public void stop() {
|
||
if (mCurrentOutputVideo != null) {
|
||
try {
|
||
mCurrentOutputVideo.flush();
|
||
mCurrentOutputVideo.close();
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
mCurrentOutputVideo = null;
|
||
}
|
||
|
||
if (mCurrentOutputAudio != null) {
|
||
try {
|
||
mCurrentOutputAudio.flush();
|
||
mCurrentOutputAudio.close();
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
}
|
||
|
||
mCurrentOutputAudio = null;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
}
|