package com.lansosdk.videoeditor; import android.content.Context; import android.graphics.Bitmap; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaMetadataRetriever; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.text.TextUtils; import android.util.Log; import com.lansosdk.box.LSLog; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Locale; import static com.lansosdk.videoeditor.LanSongFileUtil.fileExist; /** * 最简单的调用方法: * //step1. 放在主线程中执行; * VideoEditor veditor=new VideoEditor(); * veditor.setOnProgessListener(xxx)进度; * * //step2. * 然后在AsyncTask或Thread中执行如下; * veditor.executeXXXXX(); * * * 杭州蓝松科技有限公司 * www.lansongtech.com */ public class VideoEditor { private static final String TAG = LSLog.TAG; public static final String version="VideoEditor_20100101"; /** * 使用软件编码的列表; */ public static String[] qilinCpulist ={ "EML-AL00", "EML-AL01", "LON-AL00", "MHA-AL00", "STF-AL00", "CLT-AL00", "ALP-AL00", "COL-AL10", "FRD-AL00", "EVR-AL00", "LYA-AL00", "PAR-AL00", "LON-AL00-PD", "VKY-AL00", "EDI-AL10", "DLI-AL10", "PRA-AL00X", "DUK-AL20", "DUK-AL20", "DUK-AL20", "WAS-AL00", "HUAWEI NXT-CL00", "RVL-AL09", "COR-AL00", "PAR-AL00", "INE-AL00", "EML-TL00", "EML-TL01", "LON-TL00", "MHA-TL00", "STF-TL00", "CLT-TL00", "ALP-TL00", "COL-TL10", "FRD-TL00", "EVR-TL00", "LYA-TL00" }; public static String[] useSoftDecoderlist ={ "SM919", "SM901" }; /** * 是否强制使用硬件编码器; * * 全局变量 * * 默认先硬件编码,如果无法完成则切换为软编码 */ public static boolean isForceHWEncoder=false; /** * 强制使用软件编码器 * * 全局变量 * 默认先硬件编码,如果无法完成则切换为软编码 */ public static boolean isForceSoftWareEncoder=false; /** * 强制使用软解码器 * * 全局变量 */ public static boolean isForceSoftWareDecoder=false; /** * 不检查是否是16的倍数. * */ private static boolean noCheck16Multi=false; /** * 给当前方法指定码率. * 此静态变量, 在execute执行后, 默认恢复为0; */ public int encodeBitRate=0; /** * 解析参数失败 返回1 无输出文件 2; 输入文件为空:3 sdk未授权 -1; 解码器错误:69 收到线程的中断信号:255 如硬件编码器错误,则返回:26625---26630 */ public static final int VIDEO_EDITOR_EXECUTE_SUCCESS1 = 0; public static final int VIDEO_EDITOR_EXECUTE_SUCCESS2 = 1; public static final int VIDEO_EDITOR_EXECUTE_FAILED = -101; //文件不存在。 private final int VIDEOEDITOR_HANDLER_PROGRESS = 203; private final int VIDEOEDITOR_HANDLER_COMPLETED = 204; private final int VIDEOEDITOR_HANDLER_ENCODERCHANGE = 205; private static LanSongLogCollector lanSongLogCollector =null; public void setEncodeBitRate(int bitRate){ encodeBitRate=bitRate; } /** * 使能在ffmpeg执行的时候, 收集错误信息; * * @param ctx */ public static void logEnable(Context ctx){ if(ctx!=null){ lanSongLogCollector =new LanSongLogCollector(ctx); }else{ if(lanSongLogCollector !=null && lanSongLogCollector.isRunning()){ lanSongLogCollector.stop(); lanSongLogCollector =null; } } } /** * 当执行失败后,返回错误信息; * @return */ public static String getErrorLog(){ if(lanSongLogCollector !=null && lanSongLogCollector.isRunning()){ return lanSongLogCollector.stop(); }else{ return null; } } /** * 构造方法. * 如果您想扩展ffmpeg的命令, 可以继承这个类, * 在其中像我们的各种executeXXX的举例一样来拼接ffmpeg的命令; * * 不要直接修改我们的这个文件, 以方便以后的sdk更新升级. */ public VideoEditor() { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; Log.w(TAG, "cannot get Looper handler. may be cannot receive video editor progress!!"); } } public onVideoEditorEncodeChangedListener mEncoderChangeListener=null; public void setOnEncodeChangedListener(onVideoEditorEncodeChangedListener listener) { mEncoderChangeListener = listener; } private void doEncoderChangedListener(boolean isSoft) { if (mEncoderChangeListener != null) mEncoderChangeListener.onChanged(this,isSoft); } public onVideoEditorProgressListener mProgressListener = null; public void setOnProgessListener(onVideoEditorProgressListener listener) { mProgressListener = listener; } private void doOnProgressListener(int timeMS) { if (mProgressListener != null) mProgressListener.onProgress(this, timeMS); } private EventHandler mEventHandler; private class EventHandler extends Handler { private final WeakReference<VideoEditor> mWeakExtract; public EventHandler(VideoEditor mp, Looper looper) { super(looper); mWeakExtract = new WeakReference<VideoEditor>(mp); } @Override public void handleMessage(Message msg) { VideoEditor videoextract = mWeakExtract.get(); if (videoextract == null) { Log.e(TAG, "VideoEditor went away with unhandled events"); return; } switch (msg.what) { case VIDEOEDITOR_HANDLER_PROGRESS: videoextract.doOnProgressListener(msg.arg1); break; case VIDEOEDITOR_HANDLER_ENCODERCHANGE: videoextract.doEncoderChangedListener(true); //暂停只要改变,就变成软编码; break; default: break; } } } /** * 异步线程执行的代码. */ public int executeVideoEditor(String[] array) { return execute(array); } @SuppressWarnings("unused") /* Used from JNI */ private void postEventFromNative(int what, int arg1, int arg2) { if (mEventHandler != null) { Message msg = mEventHandler.obtainMessage(VIDEOEDITOR_HANDLER_PROGRESS); msg.arg1 = what; mEventHandler.sendMessage(msg); } } protected void sendEncoderEnchange() { if (mEventHandler != null) { Message msg = mEventHandler.obtainMessage(VIDEOEDITOR_HANDLER_ENCODERCHANGE); mEventHandler.sendMessage(msg); } } public static native int getLimitYear(); public static native int getLimitMonth(); /** * 获取当前版本号 * @return */ public static native String getSDKVersion(); /** * 执行成功,返回0, 失败返回错误码. * * 解析参数失败 返回1 sdk未授权 -1; 解码器错误:69 收到线程的中断信号:255 如硬件编码器错误,则返回:26625---26630 * @param cmdArray ffmpeg命令的字符串数组, 可参考此文件中的各种方法举例来编写. * @return 执行成功, 返回0, 失败返回错误码. */ private native int execute(Object cmdArray); private native int setForceColorFormat(int format); /** * 新增 在执行过程中取消的方法. * 如果在执行中调用了这个方法, 则会直接终止当前的操作. * 此方法仅仅是在ffmpeg线程中设置一个标志位,当前这一帧处理完毕后, 会检测到这个标志位,从而退出. * 因为execute是阻塞执行, 你可以判断execute有没有执行完,来判断是否完成. */ public native void cancel(); /** * @param filelength * @param channel * @param sampleRate * @param bitperSample * @param outData */ public static native void createWavHeader(int filelength, int channel, int sampleRate, int bitperSample, byte[] outData); //----------------- public static String mergeAVDirectly(String audio, String video,boolean deleteVideo) { MediaInfo info=new MediaInfo(audio); if(info.prepare() && info.isHaveAudio()){ String retPath= LanSongFileUtil.createMp4FileInBox(); String inputAudio = audio; List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(inputAudio); cmdList.add("-i"); cmdList.add(video); cmdList.add("-map"); cmdList.add("0:a"); cmdList.add("-map"); cmdList.add("1:v"); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-absf"); cmdList.add("aac_adtstoasc"); cmdList.add("-y"); cmdList.add(retPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } VideoEditor editor = new VideoEditor(); int ret = editor.executeVideoEditor(command); if(ret==0){ if(deleteVideo){ LanSongFileUtil.deleteFile(video); } return retPath; }else{ return video; } } return video; } /** * 把一张图片变成视频 * * @param srcPath * @param duration 形成视频的总时长; * @return 返回处理后的视频; */ public String executePicture2Video(String srcPath, float duration) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-loop"); cmdList.add("1"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-t"); cmdList.add(String.valueOf(duration)); return executeAutoSwitch(cmdList); } return null; } @Deprecated public String executePcmMix(String srcPach1, int samplerate, int channel, String srcPach2, int samplerate2, int channel2,float value1, float value2) { List<String> cmdList = new ArrayList<String>(); String filter = String.format(Locale.getDefault(), "[0:a]volume=volume=%f[a1]; [1:a]volume=volume=%f[a2]; " + "[a1][a2]amix=inputs=2:duration=first:dropout_transition=2", value1, value2); String dstPath= LanSongFileUtil.createFileInBox("pcm"); cmdList.add("-f"); cmdList.add("s16le"); cmdList.add("-ar"); cmdList.add(String.valueOf(samplerate)); cmdList.add("-ac"); cmdList.add(String.valueOf(channel)); cmdList.add("-i"); cmdList.add(srcPach1); cmdList.add("-f"); ; cmdList.add("s16le"); cmdList.add("-ar"); cmdList.add(String.valueOf(samplerate2)); cmdList.add("-ac"); cmdList.add(String.valueOf(channel2)); cmdList.add("-i"); cmdList.add(srcPach2); cmdList.add("-y"); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-f"); cmdList.add("s16le"); cmdList.add("-acodec"); cmdList.add("pcm_s16le"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } @Deprecated public String executePcmEncodeAac(String srcPach, int samplerate, int channel) { List<String> cmdList = new ArrayList<String>(); String dstPath= LanSongFileUtil.createM4AFileInBox(); cmdList.add("-f"); cmdList.add("s16le"); cmdList.add("-ar"); cmdList.add(String.valueOf(samplerate)); cmdList.add("-ac"); cmdList.add(String.valueOf(channel)); cmdList.add("-i"); cmdList.add(srcPach); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-b:a"); cmdList.add("64000"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } /** * 把 pcm和视频文件合并在一起, pcm数据会编码成aac格式. * 注意:需要原视频文件里没有音频部分, * 如果有, 则需要先用 {@link #executeGetVideoTrack(String)} 拿到视频轨道, 在输入到这里. * * @param srcPcm 原pcm音频文件, * @param samplerate pcm的采样率 * @param channel pcm的通道数 * @param srcVideo 原视频文件, 没有音频部分 * @return 输出的视频文件路径, 需后缀是mp4格式. */ public String executePcmComposeVideo(String srcPcm, int samplerate, int channel, String srcVideo) { List<String> cmdList = new ArrayList<String>(); String dstPath= LanSongFileUtil.createMp4FileInBox(); cmdList.add("-f"); cmdList.add("s16le"); cmdList.add("-ar"); cmdList.add(String.valueOf(samplerate)); cmdList.add("-ac"); cmdList.add(String.valueOf(channel)); cmdList.add("-i"); cmdList.add(srcPcm); cmdList.add("-i"); cmdList.add(srcVideo); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-b:a"); cmdList.add("64000"); cmdList.add("-y"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } /** * 两个音频文件延迟混合, 即把第二个音频延迟多长时间后, 与第一个音频混合. * 混合后的编码为aac格式的音频文件. * 注意,如果两个音频的时长不同, 以第一个音频的音频为准. 如需修改可联系我们或查询ffmpeg命令即可. * * @param audioPath1 * @param audioPath2 * @param leftDelayMS 第二个音频的左声道 相对 于第一个音频的延迟时间 * @param rightDelayMS 第二个音频的右声道 相对 于第一个音频的延迟时间 * @return */ public String executeAudioDelayMix(String audioPath1, String audioPath2, int leftDelayMS, int rightDelayMS) { List<String> cmdList = new ArrayList<String>(); String overlayXY = String.format(Locale.getDefault(), "[1:a]adelay=%d|%d[delaya1]; " + "[0:a][delaya1]amix=inputs=2:duration=first:dropout_transition=2", leftDelayMS, rightDelayMS); String dstPath= LanSongFileUtil.createM4AFileInBox(); cmdList.add("-i"); cmdList.add(audioPath1); cmdList.add("-i"); cmdList.add(audioPath2); cmdList.add("-filter_complex"); cmdList.add(overlayXY); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } /** * 两个音频文件混合. * 混合后的文件压缩格式是aac格式, 故需要您dstPath的后缀是aac或m4a. * * @param audioPath1 主音频的完整路径 * @param audioPath2 次音频的完整路径 * @param value1 主音频的音量, 浮点类型, 大于1.0为放大音量, 小于1.0是减低音量.比如设置0.5则降低一倍. * @param value2 次音频的音量, 浮点类型. * @return 输出保存的完整路径. m4a格式. */ public String executeAudioVolumeMix(String audioPath1, String audioPath2, float value1, float value2) { List<String> cmdList = new ArrayList<String>(); String filter = String.format(Locale.getDefault(), "[0:a]volume=volume=%f[a1]; [1:a]volume=volume=%f[a2]; " + "[a1][a2]amix=inputs=2:duration=first:dropout_transition=2", value1, value2); String dstPath= LanSongFileUtil.createM4AFileInBox(); cmdList.add("-i"); cmdList.add(audioPath1); cmdList.add("-i"); cmdList.add(audioPath2); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-b:a"); cmdList.add("128000"); cmdList.add("-ac"); cmdList.add("2"); //LSNEW 增加这行和上面的 码率, 通道数 cmdList.add("-vn"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } /** * 视频转码. * 通过调整视频的bitrate来对视频文件大小的压缩,降低视频文件的大小, 注意:压缩可能导致视频画质下降. * * * @param srcPath 源视频 * @param percent 压缩百分比.值从0--1 * @return */ public String executeVideoCompress(String srcPath, float percent) { if (fileExist(srcPath)) { MediaInfo info = new MediaInfo(srcPath); if (info.prepare()) { setEncodeBitRate((int)(info.vBitRate *percent)); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } } return null; } /** * 分离mp4文件中的音频,并返回音频的路径, */ public String executeGetAudioTrack(String srcMp4Path) { MediaInfo info = new MediaInfo(srcMp4Path); if(info.prepare()){ String audioPath = null; if ("aac".equalsIgnoreCase(info.aCodecName)) { audioPath = LanSongFileUtil.createFileInBox(".m4a"); } else if ("mp3".equalsIgnoreCase(info.aCodecName)) audioPath = LanSongFileUtil.createFileInBox(".mp3"); if (audioPath != null) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(srcMp4Path); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-vn"); cmdList.add("-y"); cmdList.add(audioPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return audioPath; }else{ LanSongFileUtil.deleteFile(audioPath); } } } return null; } /** * * 获取视频中的视频轨道. * (一个mp4文件, 里面可能有音频和视频, 这个是获取视频轨道, 获取后的视频里面将没有音频部分) * @param srcMp4Path * @return */ public String executeGetVideoTrack(String srcMp4Path) { if(fileExist(srcMp4Path)){ String videoPath = LanSongFileUtil.createMp4FileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(srcMp4Path); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-an"); cmdList.add("-y"); cmdList.add(videoPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return videoPath; }else{ LanSongFileUtil.deleteFile(videoPath); } } Log.e(TAG,"执行获取视频轨道 错误, !!!!"); return null; } public String executeVideoMergeAudio(String video, String audio) { MediaInfo vInfo=new MediaInfo(video); MediaInfo aInfo=new MediaInfo(audio); if(vInfo.prepare() && aInfo.prepare() && aInfo.isHaveAudio()){ String retPath= LanSongFileUtil.createMp4FileInBox(); boolean isAAC="aac".equals(aInfo.aCodecName); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(video); cmdList.add("-i"); cmdList.add(audio); cmdList.add("-t"); cmdList.add(String.valueOf(vInfo.vDuration)); if(isAAC) { //删去视频的原音,直接增加音频 cmdList.add("-map"); cmdList.add("0:v"); cmdList.add("-map"); cmdList.add("1:a"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-absf"); cmdList.add("aac_adtstoasc"); }else { //删去视频的原音,并对音频编码 cmdList.add("-map"); cmdList.add("0:v"); cmdList.add("-map"); cmdList.add("1:a"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-ac"); cmdList.add("2"); cmdList.add("-ar"); cmdList.add("44100"); cmdList.add("-b:a"); cmdList.add("128000"); } cmdList.add("-y"); cmdList.add(retPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } VideoEditor editor = new VideoEditor(); int ret = editor.executeVideoEditor(command); if(ret==0){ return retPath; }else{ return video; } } return video; } /** * 建议用 AudioEditor中的executeVideoMergeAudio */ @Deprecated public String executeVideoMergeAudio(String video, String audio,float audiostartS) { MediaInfo vInfo=new MediaInfo(video); MediaInfo aInfo=new MediaInfo(audio); if(vInfo.prepare() && aInfo.prepare() && aInfo.isHaveAudio()){ String retPath= LanSongFileUtil.createMp4FileInBox(); boolean isAAC="aac".equals(aInfo.aCodecName); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(video); cmdList.add("-ss"); cmdList.add(String.valueOf(audiostartS)); cmdList.add("-i"); cmdList.add(audio); cmdList.add("-t"); cmdList.add(String.valueOf(vInfo.vDuration)); if(isAAC) { //删去视频的原音,直接增加音频 cmdList.add("-map"); cmdList.add("0:v"); cmdList.add("-map"); cmdList.add("1:a"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-absf"); cmdList.add("aac_adtstoasc"); }else { //删去视频的原音,并对音频编码 cmdList.add("-map"); cmdList.add("0:v"); cmdList.add("-map"); cmdList.add("1:a"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-ac"); cmdList.add("2"); cmdList.add("-ar"); cmdList.add("44100"); cmdList.add("-b:a"); cmdList.add("128000"); } cmdList.add("-y"); cmdList.add(retPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } VideoEditor editor = new VideoEditor(); int ret = editor.executeVideoEditor(command); if(ret==0){ return retPath; }else{ return video; } } return video; } /** * 用AudioEditor中的方法 */ @Deprecated public String executeCutAudio(String srcFile, float startS, float durationS) { if (fileExist(srcFile)) { List<String> cmdList = new ArrayList<String>(); String dstFile= LanSongFileUtil.createFileInBox(LanSongFileUtil.getFileSuffix(srcFile)); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-i"); cmdList.add(srcFile); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-y"); cmdList.add(dstFile); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstFile; }else{ LanSongFileUtil.deleteFile(dstFile); return null; } } else { return null; } } /** * 剪切mp4文件. * (包括视频文件中的音频部分和视频部分),即把mp4文件中的一段剪切成独立的一个视频文件, 比如把一个1分钟的视频,裁剪其中的10秒钟等. * <p> * 注意: 此方法裁剪不是精确裁剪,而是从视频的IDR帧开始裁剪的, 没有精确到您指定的那一帧的时间, 如果您指定的时间不是IDR帧上的时间,则退后到上一个IDR帧开始. * * @param videoFile 原视频文件 文件格式是mp4 * @param startS 开始裁剪位置,单位是秒, * @param durationS 需要裁剪的时长,单位秒,比如您可以从原视频的8.9秒出开始裁剪,裁剪2分钟,则这里的参数是120 * @return */ public String executeCutVideo(String videoFile, float startS, float durationS) { if (fileExist(videoFile)) { String dstFile= LanSongFileUtil.createMp4FileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-y"); cmdList.add(dstFile); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstFile; }else{ LanSongFileUtil.deleteFile(dstFile); return null; } } else { return null; } } /** * 精确裁剪 * @param videoFile 输入源视频的完整路径 * @param startS 开始裁剪时间点, 单位秒 * @param durationS 要裁剪的总长度,单位秒 * @return 裁剪后返回的目标视频 */ public String executeCutVideoExact(String videoFile, float startS, float durationS) { if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 精确裁剪的同时,缩放到指定位置,不同于上面的命令,这个可以设置宽度和高度. 其中宽度和高度是采用缩放来完成. * <p> * <p> * 采用的是软缩放的形式. * * @param videoFile * @param startS * @param durationS * @param width 要缩放到的宽度 建议是16的倍数 , * @param height 要缩放到的高度, 建议是16的倍数 * @return */ public String executeCutVideoExact(String videoFile, float startS, float durationS, int width, int height) { if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); String scalecmd = String.format(Locale.getDefault(), "scale=%d:%d", width, height); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); cmdList.add("-vf"); cmdList.add(scalecmd); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 获取视频的所有帧图片,并保存到指定路径. * 所有的帧会按照后缀名字加上_001.jpeg prefix_002.jpeg的顺序依次生成, 如果发现之前已经有同样格式的文件,则在原来数字后缀的基础上增加, 比如原来有prefix_516.jpeg;则这个方法执行从 * prefix_517.jpeg开始生成视频帧. * <p> * <p> * 如果您使用的是专业版,则建议用ExtractVideoFrameDemoActivity来获取视频图片, * 因为直接返回bitmap,不存到文件中,速度相对快很多 * <p> * 这条命令是把视频中的所有帧都提取成图片,适用于视频比较短的场合,比如一秒钟是25帧,视频总时长是10秒,则会提取250帧图片,保存到您指定的路径 * * @param videoFile * @param dstDir 目标文件夹绝对路径. * @param jpgPrefix 保存图片文件的前缀,可以是png或jpg * @return */ public int executeGetAllFrames(String videoFile, String dstDir, String jpgPrefix) { String dstPath = dstDir + jpgPrefix + "_%3d.jpeg"; if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-qscale:v"); cmdList.add("2"); cmdList.add(dstPath); cmdList.add("-y"); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } return executeVideoEditor(command); } else { return VIDEO_EDITOR_EXECUTE_FAILED; } } /** * 根据设定的采样,获取视频的几行图片. * 假如视频时长是30秒,想平均取5张图片,则sampleRate=5/30; * <p> * <p> * 如果您使用的是专业版本,则建议用ExtractVideoFrameDemoActivity来获取视频图片,因为直接返回bitmap,不存到文件中,速度相对快很多 * * @param videoFile * @param dstDir * @param jpgPrefix * @param sampeRate 一秒钟采样几张图片. 可以是小数. * @return */ public int executeGetSomeFrames(String videoFile, String dstDir, String jpgPrefix, float sampeRate) { String dstPath = dstDir + jpgPrefix + "_%3d.jpeg"; if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); // cmdList.add("-qscale:v"); // cmdList.add("2"); cmdList.add("-vsync"); cmdList.add("1"); cmdList.add("-r"); cmdList.add(String.valueOf(sampeRate)); // cmdList.add("-f"); // cmdList.add("image2"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } return executeVideoEditor(command); } else { return VIDEO_EDITOR_EXECUTE_FAILED; } } /** * 读取视频中的关键帧(IDR帧), 并把关键帧保存图片. 因是IDR帧, 在编码时没有起帧做参考,故提取的最快. * <p> * <p> * 如果您使用的是专业版本,则建议用ExtractVideoFrameDemoActivity来获取视频图片,因为直接返回bitmap,不存到文件中,速度相对快很多 * <p> * <p> * 经过我们SDK编码后的视频, 是一秒钟一个帧,如果您视频大小是30秒,则大约会提取30张图片. * * @param videoFile 视频文件 * @param dstDir 保持的文件夹 * @param jpgPrefix 文件前缀. * @return */ public int executeGetKeyFrames(String videoFile, String dstDir, String jpgPrefix) { String dstPath = dstDir + "/" + jpgPrefix + "_%3d.png"; if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-vf"); cmdList.add("select=eq(pict_type\\,I)"); cmdList.add("-vsync"); cmdList.add("vfr"); Log.i(TAG, " vsync is vfr"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } return executeVideoEditor(command); } else { return VIDEO_EDITOR_EXECUTE_FAILED; } } /** * 来自于网络, 没有全部测试. * 获取视频的缩略图 * 提供了一个统一的接口用于从一个输入媒体文件中取得帧和元数据。 * * @param path 视频的路径 * @param width 缩略图的宽 * @param height 缩略图的高 * @return 缩略图 */ public static Bitmap createVideoThumbnail(String path, int width, int height) { Bitmap bitmap = null; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); if (TextUtils.isEmpty(path)) { return null; } File file = new File(path); if (!file.exists()) { return null; } try { retriever.setDataSource(path); bitmap = retriever.getFrameAtTime(-1); //取得指定时间的Bitmap,即可以实现抓图(缩略图)功能 } catch (IllegalArgumentException ex) { // Assume this is a corrupt video file } catch (RuntimeException ex) { // Assume this is a corrupt video file. } finally { try { retriever.release(); } catch (RuntimeException ex) { // Ignore failures while cleaning up. } } if (bitmap == null) { return null; } bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); return bitmap; } /** * * 从视频的指定位置中获取一帧图片. 因为这个是精确提取视频的一帧, * 不建议作为提取缩略图来使用,用mediametadataRetriever最好. * * @param videoSrcPath 源视频的完整路径 * @param postionS 时间点,单位秒,类型float,可以有小数,比如从视频的2.35秒的地方获取一张图片。 * @return */ public String executeGetOneFrame(String videoSrcPath,float postionS) { if (fileExist(videoSrcPath)) { List<String> cmdList = new ArrayList<String>(); // String dstPng= LanSongFileUtil.createFileInBox("png"); cmdList.add("-i"); cmdList.add(videoSrcPath); cmdList.add("-ss"); cmdList.add(String.valueOf(postionS)); cmdList.add("-vframes"); cmdList.add("1"); cmdList.add("-y"); cmdList.add(dstPng); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPng; }else{ LanSongFileUtil.deleteFile(dstPng); return null; } } else { return null; } } /** * 从视频的指定位置中获取一帧图片,得到图片后,把图片缩放到指定的宽高. 因为这个是精确提取视频的一帧, 不建议作为提取缩略图来使用. * * @param videoSrcPath 源视频的完整路径 * @param postionS 时间点,单位秒,类型float,可以有小数,比如从视频的2.35秒的地方获取一张图片。 * @param pngWidth 得到目标图片后缩放的宽度. * @param pngHeight 得到目标图片后需要缩放的高度. * @return 得到目标图片的完整路径名. */ public String executeGetOneFrame(String videoSrcPath, float postionS, int pngWidth, int pngHeight) { if (fileExist(videoSrcPath)) { List<String> cmdList = new ArrayList<String>(); String dstPng= LanSongFileUtil.createFileInBox("png"); String resolution = String.valueOf(pngWidth); resolution += "x"; resolution += String.valueOf(pngHeight); cmdList.add("-i"); cmdList.add(videoSrcPath); cmdList.add("-ss"); cmdList.add(String.valueOf(postionS)); cmdList.add("-s"); cmdList.add(resolution); cmdList.add("-vframes"); cmdList.add("1"); cmdList.add("-y"); cmdList.add(dstPng); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPng; }else { LanSongFileUtil.deleteFile(dstPng); return null; } } return null; } /** * 请用VideoEditor中的方法; */ @Deprecated public String executeConvertMp3ToAAC(String mp3Path) { if (fileExist(mp3Path)) { String dstFile= LanSongFileUtil.createFileInBox("m4a"); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(mp3Path); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-y"); cmdList.add(dstFile); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstFile; }else{ LanSongFileUtil.deleteFile(dstFile); return null; } } else { return null; } } public String executeConvertMp3ToAAC(String mp3Path,float startS,float durationS) { if (fileExist(mp3Path)) { List<String> cmdList = new ArrayList<String>(); String dstPath= LanSongFileUtil.createM4AFileInBox(); cmdList.add("-i"); cmdList.add(mp3Path); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } return null; } public String executeConvertMp4toTs(String mp4Path) { if (fileExist(mp4Path)) { List<String> cmdList = new ArrayList<String>(); String dstTs = LanSongFileUtil.createFileInBox("ts"); cmdList.add("-i"); cmdList.add(mp4Path); cmdList.add("-c"); cmdList.add("copy"); cmdList.add("-bsf:v"); cmdList.add("h264_mp4toannexb"); cmdList.add("-f"); cmdList.add("mpegts"); cmdList.add("-y"); cmdList.add(dstTs); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret = executeVideoEditor(command); if (ret == 0) { return dstTs; } else { LanSongFileUtil.deleteFile(dstTs); return null; } } return null; } public String executeConvertTsToMp4(String[] tsArray) { if (LanSongFileUtil.filesExist(tsArray)) { String dstFile= LanSongFileUtil.createMp4FileInBox(); String concat = "concat:"; for (int i = 0; i < tsArray.length - 1; i++) { concat += tsArray[i]; concat += "|"; } concat += tsArray[tsArray.length - 1]; List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(concat); cmdList.add("-c"); cmdList.add("copy"); cmdList.add("-bsf:a"); cmdList.add("aac_adtstoasc"); cmdList.add("-y"); cmdList.add(dstFile); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstFile; }else{ LanSongFileUtil.deleteFile(dstFile); return null; } } else { return null; } } /** * 把分段录制的视频, 拼接在一起; * * 注意:此方法仅仅使用在分段录制的场合 * 注意:此方法仅仅使用在分段录制的场合 * 注意:此方法仅仅使用在分段录制的场合 * @param mp4Array */ public String executeConcatMP4(String[] mp4Array) { //第一步,先把所有的mp4转换为ts流 ArrayList<String> tsPathArray = new ArrayList<String>(); for (int i = 0; i < mp4Array.length; i++) { String segTs1 = executeConvertMp4toTs(mp4Array[i]); tsPathArray.add(segTs1); } //第二步: 把ts流拼接成mp4 String[] tsPaths = new String[tsPathArray.size()]; for (int i = 0; i < tsPathArray.size(); i++) { tsPaths[i] = (String) tsPathArray.get(i); } String dstVideo=executeConvertTsToMp4(tsPaths); //第三步:删除临时生成的ts文件. for (int i = 0; i < tsPathArray.size(); i++) { LanSongFileUtil.deleteFile(tsPathArray.get(i)); } return dstVideo; } public String executeConcatMP4(List<String> mp4Array) { //第一步,先把所有的mp4转换为ts流 ArrayList<String> tsPathArray = new ArrayList<String>(); for (int i = 0; i < mp4Array.size(); i++) { String segTs1 = executeConvertMp4toTs(mp4Array.get(i)); tsPathArray.add(segTs1); } //第二步: 把ts流拼接成mp4 String[] tsPaths = new String[tsPathArray.size()]; for (int i = 0; i < tsPathArray.size(); i++) { tsPaths[i] = (String) tsPathArray.get(i); } String dstVideo=executeConvertTsToMp4(tsPaths); //第三步:删除临时生成的ts文件. for (int i = 0; i < tsPathArray.size(); i++) { LanSongFileUtil.deleteFile(tsPathArray.get(i)); } return dstVideo; } /** * 不同来源的mp4文件进行拼接; * 拼接的所有视频分辨率必须一致; * * * @param videos 所有的视频 * @param ignorecheck 是否要忽略检测每个每个视频的分辨率, 如果您确信已经相等,则设为false; * @return 输出视频的路径 */ public String executeConcatDiffentMp4(ArrayList<String> videos,boolean ignorecheck) { if(videos!=null && videos.size()>1){ if(ignorecheck || checkVideoSizeSame(videos)){ String dstPath= LanSongFileUtil.createMp4FileInBox(); String filter = String.format(Locale.getDefault(), "concat=n=%d:v=1:a=1", videos.size()); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videos.get(0)); for (int i=1;i<videos.size();i++){ cmdList.add("-i"); cmdList.add(videos.get(i)); } cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-b:a"); cmdList.add("128000"); return executeAutoSwitch(cmdList); } } return null; } private boolean checkVideoSizeSame(ArrayList<String> videos){ int w=0; int h=0; for (String item: videos){ MediaInfo info=new MediaInfo(item); if(info.prepare()){ if(w ==0&& h==0){ w=info.getWidth(); h=info.getHeight(); }else if(info.getWidth()!=w || info.getHeight() !=h){ Log.e(TAG,"视频拼接中, 有个视频的分辨率不等于其他分辨率"); return false; } }else{ return false; } } return true; //返回正常; } /** * 裁剪视频画面 * * @param videoFile 需要裁剪的视频文件 * @param cropWidth 裁剪后的目标宽度 * @param cropHeight 裁剪后的目标高度 * @param x 视频画面开始的X坐标, 从画面的左上角开始是0.0坐标 * @param y 视频画面开始的Y坐标, * @return 处理后保存的路径,后缀需要是mp4 */ public String executeCropVideoFrame(String videoFile, int cropWidth, int cropHeight, int x, int y) { if (fileExist(videoFile)) { String filter = String.format(Locale.getDefault(), "crop=%d:%d:%d:%d", cropWidth, cropHeight, x, y); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } return null; } /** * 缩放视频画面 * * @param videoFile * @param scaleWidth * @param scaleHeight * @return */ public String executeScaleVideoFrame(String videoFile, int scaleWidth, int scaleHeight) { if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); scaleWidth=(scaleWidth/2)*2; scaleHeight=(scaleHeight/2)*2; String scalecmd = String.format(Locale.getDefault(), "scale=%d:%d", scaleWidth, scaleHeight); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-vf"); cmdList.add(scalecmd); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } return null; } /** * 缩放的同时增加logo水印. * * @param videoFile 原视频路径 * @param pngPath 增加图片路径 * @param scaleWidth 要缩放到的宽度 * @param scaleHeight 要缩放到的高度 * @param overX 图片的左上角 放到视频的 X坐标 * @param overY 图片的左上角 放到视频的 坐标 * @return */ public String executeScaleOverlay(String videoFile, String pngPath, int scaleWidth, int scaleHeight, int overX, int overY) { if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); String filter = String.format(Locale.getDefault(), "[0:v]scale=%d:%d [scale];[scale][1:v] overlay=%d:%d", scaleWidth, scaleHeight, overX, overY); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-i"); cmdList.add(pngPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 给视频增加图片 * * @param videoFile 原视频完整路径 * @param picturePath 图片完整路径 * @param overX 图片的左上角 放到视频的X坐标 * @param overY 图片的左上角 放到视频的X坐标 * @return 返回目标文件 */ public String executeOverLayVideoFrame(String videoFile, String picturePath, int overX, int overY) { String filter = String.format(Locale.getDefault(), "overlay=%d:%d", overX, overY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-i"); cmdList.add(picturePath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * 对视频画面进行裁剪,裁剪后叠加一个png类型的图片, * <p> * 等于把裁剪,叠加水印,压缩三条命令放在一次执行, 这样只解码一次,和只编码一次,极大的加快了处理速度. * * @param videoFile 原视频 * @param pngPath * @param cropX 画面裁剪的X坐标, 左上角为0:0 * @param cropY 画面裁剪的Y坐标 * @param cropWidth 画面裁剪宽度. 须小于等于源视频宽度 * @param cropHeight 画面裁剪高度, 须小于等于源视频高度 * @param overX 画面和png图片开始叠加的X坐标. * @param overY 画面和png图片开始叠加的Y坐标 * @return */ public String executeCropOverlay(String videoFile, String pngPath, int cropX, int cropY, int cropWidth, int cropHeight, int overX, int overY) { if (fileExist(videoFile)) { String filter = String.format(Locale.getDefault(), "[0:v]crop=%d:%d:%d:%d [crop];[crop][1:v] " + "overlay=%d:%d", cropWidth, cropHeight, cropX, cropY, overX, overY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-i"); cmdList.add(pngPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 画面裁剪. * * @param videoFile 输入文件完整路径 * @param cropX 画面裁剪的开始X坐标 * @param cropY 画面裁剪的开始Y坐标 * @param cropWidth 画面裁剪的宽度 * @param cropHeight 画面裁剪的高度. * @return */ public String executeCrop(String videoFile, int cropX, int cropY, int cropWidth, int cropHeight) { String filter = String.format(Locale.getDefault(), "crop=%d:%d:%d:%d", cropWidth, cropHeight, cropX, cropY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * 时长剪切的同时, 做画面裁剪. * * @param videoFile 输入文件完整路径 * @param startTimeS 开始时间,单位秒 * @param duationS 要剪切的时长; * @param cropX 画面裁剪的开始X坐标 * @param cropY 画面裁剪的开始Y坐标 * @param cropWidth 画面裁剪的宽度 * @param cropHeight 画面裁剪的高度. * @return */ public String executeCutCrop(String videoFile, float startTimeS, float duationS, int cropX, int cropY, int cropWidth, int cropHeight) { String filter = String.format(Locale.getDefault(), "crop=%d:%d:%d:%d", cropWidth, cropHeight, cropX, cropY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-ss"); cmdList.add(String.valueOf(startTimeS)); cmdList.add("-t"); cmdList.add(String.valueOf(duationS)); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * 同时执行 视频时长剪切, 画面裁剪和增加水印的功能. * * @param videoFile 源视频文件. * @param pngPath 增加的水印文件路径 * @param startTimeS 时长剪切的开始时间 * @param duationS 时长剪切的 总长度 * @param cropX 画面裁剪的 X坐标,(最左边坐标是0) * @param cropY 画面裁剪的Y坐标,(最上面坐标是0) * @param cropWidth 画面裁剪宽度 * @param cropHeight 画面裁剪高度 * @param overX 增加水印的X坐标 * @param overY 增加水印的Y坐标 * @return */ public String executeCutCropOverlay(String videoFile, String pngPath, float startTimeS, float duationS, int cropX, int cropY, int cropWidth, int cropHeight, int overX, int overY) { String filter = String.format(Locale.getDefault(), "[0:v]crop=%d:%d:%d:%d [crop];[crop][1:v] " + "overlay=%d:%d", cropWidth, cropHeight, cropX, cropY, overX, overY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-ss"); cmdList.add(String.valueOf(startTimeS)); cmdList.add("-t"); cmdList.add(String.valueOf(duationS)); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-i"); cmdList.add(pngPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * 把多张图片转换为视频 * 注意: 这里的多张图片必须在同一个文件夹下,并且命名需要有规律,比如名字是 r5r_001.jpeg r5r_002.jpeg, r5r_003.jpeg等 * 多张图片,需要统一的分辨率,如分辨率不同,则以第一张图片的分辨率为准,后面的分辨率自动缩放到第一张图片的分辨率的大小 * * @param picDir 保存图片的文件夹 * @param jpgprefix 图片的文件名有规律的前缀 * @param framerate 每秒钟需要显示几张图片 * @return */ public String executeConvertPictureToVideo(String picDir, String jpgprefix, float framerate) { String picSet = picDir + jpgprefix + "_%3d.jpeg"; List<String> cmdList = new ArrayList<String>(); cmdList.add("-framerate"); cmdList.add(String.valueOf(framerate)); cmdList.add("-i"); cmdList.add(picSet); cmdList.add("-r"); cmdList.add("25"); return executeAutoSwitch(cmdList); } /** * 把视频填充成指定大小的画面, 比视频的宽高 大的部分用黑色来填充. * * @param videoFile 源视频路径 * @param padWidth 填充成的目标宽度 , 参数需要是16的倍数 * @param padHeight 填充成的目标高度 , 参数需要是16的倍数 * @param padX 把视频画面放到填充区时的开始X坐标 * @param padY 把视频画面放到填充区时的开始Y坐标 * @return */ public String executePadVideo(String videoFile, int padWidth, int padHeight, int padX, int padY) { if (fileExist(videoFile)) { MediaInfo info = new MediaInfo(videoFile); if (info.prepare()) { int minWidth = info.vWidth + padX; int minHeight = info.vHeight + padY; if (minWidth > padWidth || minHeight > padHeight) { Log.e(TAG, "pad set position is error. min Width>pading width.or min height > padding height"); return null; //失败. } } else { Log.e(TAG, "media info prepare is error!!!"); return null; } //第二步: 开始padding. String filter = String.format(Locale.getDefault(), "pad=%d:%d:%d:%d:black", padWidth, padHeight, padX,padY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 给视频旋转角度, * * 注意这里 只是 旋转画面的的角度,而不会调整视频的宽高. * @param srcPath 需要旋转角度的原视频 * @param angle 角度 * @return */ public String executeRotateAngle(String srcPath, float angle) { if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "rotate=%f*(PI/180),format=yuv420p", angle); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-metadata:s:v"); cmdList.add("rotate=0"); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 设置多媒体文件中的 视频元数据的角度. * 一个多媒体文件中有很多种元数据, 包括音频轨道, 视频轨道, 各种元数据, 字幕,其他文字等信息, * 这里仅仅更改元数据中的视频播放角度, 当视频播放器播放该视频时, 会得到"要旋转多少度"播放的信息, * 这样在播放时就会旋转后再播放画面 * <p> * 此设置不改变音视频的各种参数, 仅仅是告诉播放器,"要旋转多少度"来播放而已. * 适用在拍摄的视频有90度和270的情况, 想更改这个角度参数的场合. * * @param srcPath 原视频 * @param angle 需要更改的角度 * @return */ public String executeSetVideoMetaAngle(String srcPath, int angle) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); String dstPath= LanSongFileUtil.createMp4FileInBox(); String filter = String.format(Locale.getDefault(), "rotate=%d", angle); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-c"); cmdList.add("copy"); cmdList.add("-metadata:s:v:0"); cmdList.add(filter); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else { LanSongFileUtil.deleteFile(dstPath); return null; } } else { return null; } } //--------------------------- /** * 叠加并调速; * @param srcPath * @param pngPath * @param overX * @param overY * @param speed 速度值,范围0--1; * @return */ public String executeOverLaySpeed(String srcPath, String pngPath,int overX,int overY, float speed){ if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "[0:v][1:v]overlay=%d:%d[overlay];[overlay]setpts=%f*PTS[v];[0:a]atempo=%f[a]",overX,overY, 1 / speed, speed); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-i"); cmdList.add(pngPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-map"); cmdList.add("[v]"); cmdList.add("-map"); cmdList.add("[a]"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 调整视频的播放速度 * 范围0.5--2.0; * 0.5:放慢一倍;2:加快一倍 * @param srcPath 源视频 * @return */ public String executeAdjustVideoSpeed(String srcPath, float speed){ if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "[0:v]setpts=%f*PTS[v];[0:a]atempo=%f[a]", 1 / speed, speed); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-map"); cmdList.add("[v]"); cmdList.add("-map"); cmdList.add("[a]"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 调整视频的播放速度, * 可以把视频加快速度,或放慢速度。适用在希望缩短视频中不重要的部分的场景,比如走路等 * * @param srcPath 源视频 * @param speed 源视频中 画面和音频同时改变的倍数,比如放慢一倍,则这里是0.5;加快一倍,这里是2;建议速度在0.5--2.0之间。 * @return */ public String executeAdjustVideoSpeed2(String srcPath, float speed) { if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "[0:v]setpts=%f*PTS[v]", 1 / speed); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-map"); cmdList.add("[v]"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频水平镜像,即把视频左半部分镜像显示在右半部分 * 【此方法用到编解码】 * * @param srcPath 源视频路径 * @return */ public String executeVideoMirrorH(String srcPath) { if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];" + "[left][right] hstack"); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频垂直镜像,即把视频上半部分镜像显示在下半部分 * * @param srcPath 源视频路径 * @return */ public String executeVideoMirrorV(String srcPath) { if (fileExist(srcPath)) { String filter = String.format(Locale.getDefault(), "crop=iw:ih/2:0:0,split[top][tmp];[tmp]vflip[bottom];" + "[top][bottom] vstack"); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频垂直方向反转 * @param srcPath 原视频 * @return */ public String executeVideoRotateVertically(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("vflip"); cmdList.add("-c:a"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频水平方向反转 * * @param srcPath 原视频 * @return */ public String executeVideoRotateHorizontally(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("hflip"); cmdList.add("-c:a"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频顺时针旋转90度 * * @param srcPath 原视频 * @return */ public String executeVideoRotate90Clockwise(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("transpose=1"); cmdList.add("-c:a"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频逆时针旋转90度,也可以认为是顺时针旋转270度. * * @param srcPath 原视频 * @return */ public String executeVideoRotate90CounterClockwise(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("transpose=2"); cmdList.add("-c:a"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 视频倒序;比如正常的视频画面是一个人从左边走到右边,倒序后,人从右边倒退到左边,即视频画面发生了倒序 * <p> * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * <p> * 如您的视频过大, 则可能导致:Failed to inject frame into filter network: Out of memory;这个是正常的.因为已超过APP可使用的内容范围, 内存不足. * * @param srcPath 原视频 * @return */ public String executeVideoReverse(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("reverse"); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 音频倒序,和视频倒序类似,把原来正常的声音,处理成从后向前的声音。 适合在搞怪的一些场合。 * <p> * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * * @param srcPath 源文件完整路径. 原文件可以是mp4的视频, 也可以是mp3或m4a的音频文件 * @return */ public String executeAudioReverse(String srcPath) { if (fileExist(srcPath)) { String dstPath= LanSongFileUtil.createM4AFileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-af"); cmdList.add("areverse"); cmdList.add("-c:v"); cmdList.add("copy"); cmdList.add("-y"); cmdList.add("drawpadDstPath"); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return dstPath; }else{ LanSongFileUtil.deleteFile(dstPath); return null; } } else { return null; } } /** * 把一个mp4文件中的音频部分和视频都倒序播放。 * <p> * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * 注意:此处理会占用大量的内存,建议视频最好是480x480的分辨率, 并且不要过长,尽量在15秒内 * 如您的视频过大, 则可能导致:Failed to inject frame into filter network: Out of memory;这个是正常的.因为已超过APP可使用的内容范围, 内存不足. * * @param srcPath 原mp4文件 * @return */ public String executeAVReverse(String srcPath) { if (fileExist(srcPath)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vf"); cmdList.add("reverse"); cmdList.add("-af"); cmdList.add("areverse"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 不建议使用,因为需要字体,比较麻烦, 建议把文字转图片,然后叠加. * @param videoPath * @param fontPath * @param text * @return */ public String testVideoAddText(String videoPath,String fontPath,String text) { List<String> cmdList = new ArrayList<String>(); //"drawtext=fontfile=/system/fonts/DroidSansFallback.ttf: text='杭州蓝松科技001abc'"); String filter="drawtext=fontfile="+fontPath+": text= '"+text+ "'"; cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoPath); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * 删除视频中的logo,比如一般在视频的左上角或右上角有视频logo信息,类似"优酷","抖音"等; * 这里把指定位置的图像删除掉; * * @param video 原视频 * @param startX 开始横坐标 * @param startY 开始的横坐标 * @param w 删除的宽度 * @param h 删除的高度 * @return */ public String executeDeleteLogo(String video,int startX,int startY,int w,int h){ if (fileExist(video)) { String filter = String.format(Locale.getDefault(), "delogo=x=%d:y=%d:w=%d:h=%d",startX, startY,w,h); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(video); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 删除视频中的最多4处有logo的信息. * * 4处logo, 是从1--4; * * 如果只用3处logo,则把startX4=0 startY4=0,width4=0,height4=0; * 如果只用到2处理,则把 3,4 设置为0; * 如果要设置编码后的码率,则用 setEncodeBitRate * @param video 输入视频, * @param startX1 第一处X坐标开始坐标 * @param startY1 第一处Y坐标开始坐标 * @param width1 第一处的宽度 * @param height1 第一处的高度 * @param startX2 * @param startY2 * @param width2 * @param height2 * @param startX3 * @param startY3 * @param width3 * @param height3 * @param startX4 * @param startY4 * @param width4 * @param height4 * @return */ public String executeDeleteLogo(String video,int startX1,int startY1,int width1,int height1, int startX2,int startY2,int width2,int height2, int startX3,int startY3,int width3,int height3, int startX4,int startY4,int width4,int height4){ if (fileExist(video)) { String filter = String.format(Locale.getDefault(), "delogo=x=%d:y=%d:w=%d:h=%d [d1]", startX1,startY1,width1,height1); if(startX2>=0 && startY2>=0 && width2>0 && height2>0){ String filter2 = String.format(Locale.getDefault(), ";[d1]delogo=x=%d:y=%d:w=%d:h=%d [d2]", startX2,startY2,width2,height2); filter+=filter2; } if(startX3>=0 && startY3>=0 && width3>0 && height3>0){ String filter3 = String.format(Locale.getDefault(), ";[d2]delogo=x=%d:y=%d:w=%d:h=%d [d3]", startX3,startY3,width3,height3); filter+=filter3; } if(startX4>=0 && startY4>=0 && width4>0 && height4>0){ String filter4 = String.format(Locale.getDefault(), ";[d3]delogo=x=%d:y=%d:w=%d:h=%d", startX4,startY4,width4,height4); filter+=filter4; } List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(video); cmdList.add("-vf"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } //----------增加压缩帧率 /** * 对视频调整帧率, 码率 * @param video * @param framerate 帧率 * @param bitrate 码率 * @return */ public String executeAdjustFrameRate(String video,float framerate,int bitrate){ if (fileExist(video)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(video); cmdList.add("-r"); cmdList.add(String.valueOf(framerate)); cmdList.add("-acodec"); cmdList.add("copy"); encodeBitRate=bitrate; return executeAutoSwitch(cmdList); } else { return null; } } /** *时长剪切的同时, 做画面裁剪,并调整帧率 * * @param videoFile 输入文件完整路径 * @param startTimeS 开始时间,单位秒 * @param duationS 要剪切的时长; * @param cropX 画面裁剪的开始X坐标 * @param cropY 画面裁剪的开始Y坐标 * @param cropWidth 画面裁剪的宽度 * @param cropHeight 画面裁剪的高度. * @param framerate 要调整的视频帧率, 建议15--30; * @return */ public String executeCutCropAdjustFps(String videoFile, float startTimeS, float duationS, int cropX, int cropY, int cropWidth, int cropHeight,float framerate) { String filter = String.format(Locale.getDefault(), "crop=%d:%d:%d:%d", cropWidth, cropHeight, cropX, cropY); List<String> cmdList = new ArrayList<String>(); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-ss"); cmdList.add(String.valueOf(startTimeS)); cmdList.add("-t"); cmdList.add(String.valueOf(duationS)); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-r"); cmdList.add(String.valueOf(framerate)); cmdList.add("-filter_complex"); cmdList.add(filter); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } /** * * @param input * @param interval 提取帧的间隔; 1秒种提取多少帧; * @param scaleW 提取帧的缩放的宽高, 如果为0, 则不缩放 * @param scaleH * @param dstBmp 目标帧序列; 格式:lansonggif_%5d.jpg * @return */ public boolean executeExtractFrame(String input,float interval, int scaleW, int scaleH,String dstBmp) { if (fileExist(input)) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(input); cmdList.add("-vsync"); cmdList.add("1"); cmdList.add("-qscale:v"); cmdList.add("2"); cmdList.add("-r"); cmdList.add(String.valueOf(interval)); if(scaleW>0 && scaleH>0){ cmdList.add("-s"); cmdList.add(String.format(Locale.getDefault(),"%dx%d",scaleW,scaleH)); } cmdList.add("-f"); cmdList.add("image2"); cmdList.add("-y"); cmdList.add(dstBmp); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return true; }else{ return false; } } else { return false; } } public String executeConvertBmpToGif(String bmpPaths,float framerate) { String gifPath= LanSongFileUtil.createGIFFileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-f"); cmdList.add("image2"); cmdList.add("-framerate"); cmdList.add(String.valueOf(framerate)); cmdList.add("-i"); cmdList.add(bmpPaths); cmdList.add("-y"); cmdList.add(gifPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret= executeVideoEditor(command); if(ret==0){ return gifPath; }else{ LanSongFileUtil.deleteFile(gifPath); return null; } } /** * 把视频转换为 gif * @param videoInput 输入视频 * @param inteval 对输入的视频取帧间隔. * @param scaleW 取帧的同时是否要缩放, =0为不缩放 * @param scaleH * @param frameRate 在编码成gif文件的时候的帧率; * @return */ public String executeConvertToGif(String videoInput, float inteval,int scaleW,int scaleH,float frameRate) { // ffmpeg -i d1.mp4 -r 1 -f image2 foo-%03d.jpeg // ffmpeg -f image2 -framerate 5 -i foo-%03d.jpeg c.gif String subfix="jpeg"; LanSongFileUtil.deleteNameFiles("lansonggif",subfix); String bmpPaths= LanSongFileUtil.getCreateFileDir(""); bmpPaths+="/lansonggif_%05d."+subfix; if(executeExtractFrame(videoInput,inteval,scaleW,scaleH,bmpPaths)){ String ret=executeConvertBmpToGif(bmpPaths,frameRate); LanSongFileUtil.deleteNameFiles("lansonggif",subfix); return ret; } return null; } /** * 在视频的指定时间范围内增加一张图片,图片从左上角00开始叠加到视频的上面 * * 比如给视频的第一帧增加一张图片,时间范围是:0.0 --0.03; * 注意:如果你用这个给视频增加一张封面的话, 增加好后, 分享到QQ或微信或放到mac系统上, 显示的缩略图不一定是第一帧的画面. * * LSNEW: 增加在指定时间叠加图片 * @param srcPath 视频的完整路径 * @param picPath 图片的完整的路径, 增加后,会从上左上角覆盖视频的第一帧 * @param startTimeS 开始时间,单位秒.float类型,可以有小数 * @param endTimeS 结束时间,单位秒. * @return */ public String executeAddPitureAtTime(String srcPath,String picPath,float startTimeS,float endTimeS) { if (fileExist(srcPath) && fileExist(picPath)) { List<String> cmdList = new ArrayList<String>(); String filter = String.format(Locale.getDefault(), "[0:v][1:v] overlay=0:0:enable='between(t,%f,%f)'", startTimeS, endTimeS); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-i"); cmdList.add(picPath); cmdList.add("-filter_complex"); cmdList.add(String.valueOf(filter)); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 在视频的指定位置,指定时间内叠加一张图片 * * @param srcPath 源视频的完整路径 * @param picPath 图片的完整路径,png/ jpg * @param x 图片的左上角要叠加到源视频的X坐标哪里, 左上角为0,0 * @param y * @param startTimeS 时间范围,开始时间,单位秒 * @param endTimeS 时间范围, 结束时间, 单位秒. * @return */ public String executeAddPitureAtXYTime(String srcPath,String picPath,int x,int y,float startTimeS,float endTimeS) { if (fileExist(srcPath) && fileExist(picPath)) { List<String> cmdList = new ArrayList<String>(); String filter = String.format(Locale.getDefault(), "[0:v][1:v] overlay=%d:%d:enable='between(t,%f,%f)'", x,y,startTimeS, endTimeS); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-i"); cmdList.add(picPath); cmdList.add("-filter_complex"); cmdList.add(String.valueOf(filter)); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } /** * 给Mp4文件中增加一些描述文字. * * 比如您可以把一些对该视频的操作信息, 配置信息,服务器的说明信息等放到视频里面,和视频一起传输, * 注意:这个文字信息是携带到mp4文件中, 不会增加到每帧上. * * 这里是写入. 我们有另外的读取 * LSNEW : * @param srcPath 原视频的完整路径 * @param text 要携带的描述文字 * @return 增加后的目标文件. */ public String executeAddTextToMp4(String srcPath,String text) { // ffmpeg -i d1.mp4 -metadata description="LanSon\"g \"Text" // -acodec copy -vcodec copy t1.mp4 if(fileExist(srcPath) && text!=null) { String retPath = LanSongFileUtil.createMp4FileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-metadata"); cmdList.add("description="); cmdList.add(text); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-y"); cmdList.add(retPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } int ret = executeVideoEditor(command); if (ret == 0) { return retPath; } else { LanSongFileUtil.deleteFile(retPath); return null; } }else{ return null; } } /** * 从视频中获取该视频的描述信息 * @param srcPath * @return */ public String executeGetTextFromMp4(String srcPath) { //LSTODO. 暂时预留. return null; } /** * 获取lansosdk的建议码率; * 这个码率不是唯一的, 仅仅是我们建议这样设置, 如果您对码率理解很清楚或有一定的压缩要求,则完全可以不用我们的建议,自行设置. * * @param wxh 宽度和高度的乘积; * @return */ public static int getSuggestBitRate(int wxh) { if (wxh <= 480 * 480) { return 1000 * 1024; } else if (wxh <= 640 * 480) { return 1500 * 1024; } else if (wxh <= 800 * 480) { return 1800 * 1024; } else if (wxh <= 960 * 544) { return 2000 * 1024; } else if (wxh <= 1280 * 720) { return 2500 * 1024; } else if (wxh <= 1920 * 1088) { return 3000 * 1024; } else { return 3500 * 1024; } } public static int checkSuggestBitRate(int wxh, int bitrate) { int sugg = getSuggestBitRate(wxh); return bitrate < sugg ? sugg : bitrate; //如果设置过来的码率小于建议码率,则返回建议码率,不然返回设置码率 } /** * 用在编码方法中; */ private MediaInfo _inputInfo=null; /** * 编码执行, 如果您有特殊的需求, 可以重载这个方法; * @param cmdList * @return */ public String executeAutoSwitch(List<String> cmdList) { int ret=0; int bitrate=0; boolean useSoftWareEncoder=false; if(encodeBitRate>0){ bitrate=encodeBitRate; } String dstPath= LanSongFileUtil.createMp4FileInBox(); if(isForceSoftWareDecoder || checkSoftDecoder()){ for(int i=0;i<cmdList.size();i++){ String cmd=cmdList.get(i); if("lansoh264_dec".equals(cmd)){ if(i>0){ cmdList.remove(i-1); cmdList.remove(i-1); } break; } } } // if(isForceHWEncoder==false && noCheck16Multi==false){ //暂时不加. // for(int i=0;i<cmdList.size();i++){ // String cmd=cmdList.get(i); // if("-i".equals(cmd) && i>0){ // 找到第一个输入项; // String videoPath=cmdList.get(i+1); // _inputInfo=new MediaInfo(videoPath); // if(_inputInfo.prepare() && _inputInfo.vFrameRate>0){ // if(_inputInfo.getWidth()%16 !=0 || _inputInfo.getHeight()%16 !=0){ // Log.e(TAG,"您输入的视频分辨率宽度或高度不是16的倍数, 默认切换为软编码"); // useSoftWareEncoder=true; // } // }else{ // _inputInfo=null; // } // } // } // } if(isForceHWEncoder){ Log.d(TAG,"开始处理:硬解码+ 硬编码...."); ret=executeWithEncoder(cmdList, bitrate, dstPath, true); }else if(isForceSoftWareEncoder || useSoftWareEncoder || checkSoftEncoder()) { Log.d(TAG,"开始处理:硬解码+ 软编码...."); ret = executeWithEncoder(cmdList, bitrate, dstPath, false); }else{ Log.d(TAG,"开始处理:硬解码+ 硬编码...."); ret=executeWithEncoder(cmdList, bitrate, dstPath, true); if(ret!=0){ Log.d(TAG,"开始处理:硬解码+ 软编码...."); ret=executeWithEncoder(cmdList, bitrate, dstPath, false); } } if(ret!=0) { for(int i=0;i<cmdList.size();i++){ String cmd=cmdList.get(i); if("lansoh264_dec".equals(cmd)){ if(i>0){ cmdList.remove(i-1); cmdList.remove(i-1); } break; } } sendEncoderEnchange(); Log.d(TAG,"开始处理:软解码+ 软编码...."); ret=executeWithEncoder(cmdList, bitrate, dstPath, false); } if(ret!=0){ if(lanSongLogCollector !=null){ lanSongLogCollector.start(); } Log.e("LanSoJni","编码失败, 开始搜集信息...use software decoder and encoder"); //再次执行一遍, 读取错误信息; ret=executeWithEncoder(cmdList, bitrate, dstPath, false); if(lanSongLogCollector !=null && lanSongLogCollector.isRunning()){ lanSongLogCollector.stop(); } LanSongFileUtil.deleteFile(dstPath); return null; }else{ return dstPath; } } /** * 增加编码器,并开始执行; * @param cmdList * @param bitrate * @param dstPath * @param isHWEnc 是否使用硬件编码器; 如果强制了,则以强制为准; * @return */ public int executeWithEncoder(List<String> cmdList,int bitrate, String dstPath, boolean isHWEnc) { List<String> cmdList2 = new ArrayList<String>(); for(String item: cmdList){ cmdList2.add(item); } cmdList2.add("-vcodec"); if(isHWEnc){ cmdList2.add("lansoh264_enc"); cmdList2.add("-pix_fmt"); if(isQiLinSoc()){ cmdList2.add("nv21"); setForceColorFormat(21); //LSTODO, 没有全面测试... }else{ cmdList2.add("yuv420p"); } cmdList2.add("-b:v"); cmdList2.add(String.valueOf(bitrate)); }else{ cmdList2.add("libx264"); cmdList2.add("-bf"); cmdList2.add("0"); cmdList2.add("-pix_fmt"); cmdList2.add("yuv420p"); cmdList2.add("-g"); cmdList2.add("30"); if(bitrate==0){ if(_inputInfo!=null){ bitrate=getSuggestBitRate(_inputInfo.vWidth * _inputInfo.vHeight); }else{ bitrate=(int)(2.5f*1024*1024); } } cmdList2.add("-b:v"); cmdList2.add(String.valueOf(bitrate)); } cmdList2.add("-y"); cmdList2.add(dstPath); String[] command = new String[cmdList2.size()]; for (int i = 0; i < cmdList2.size(); i++) { command[i] = (String) cmdList2.get(i); } int ret=executeVideoEditor(command); return ret; } /** * 检测是否需要软编码; * @return */ public boolean checkSoftEncoder() { for(String item: qilinCpulist){ if(item.contains(Build.MODEL) && isSupportNV21ColorFormat()==false){ isForceSoftWareEncoder=true; return true; } } if(Build.MODEL!=null && isSupportNV21ColorFormat()==false) { if (Build.MODEL.contains("-AL00") || Build.MODEL.contains("-CL00")) { isForceSoftWareEncoder = true; return true; } } return false; } //是否是麒麟处理器. public static boolean isQiLinSoc() { for(String item: qilinCpulist){ if(item.contains(Build.MODEL)){ return true; } } if(Build.MODEL!=null) { if (Build.MODEL.contains("-AL00") || Build.MODEL.contains("-CL00")) { return true; } } return false; } /** * 强制软编码器; * @return */ public boolean checkSoftDecoder() { for(String item: useSoftDecoderlist){ if(item.equalsIgnoreCase(Build.MODEL)){ return true; }else if(item.contains(Build.MODEL)){ return true; } } return false; } /** * 当数据不是16的倍数的时候,把他调整成16的倍数, 以最近的16倍数为准; * 举例如下: * 16, 17, 18, 19,20,21,22,23 ==>16; * 24,25,26,27,28,29,30,31,32==>32; * * * 如果是18,19这样接近16,则等于16, 等于缩小了原有的画面, * 如果是25,28这样接近32,则等于32, 等于稍微拉伸了原来的画面, * 因为最多缩小或拉伸8个像素, 还不至于画面严重变形,而又兼容编码器的要求,故可以这样做. * * @return */ public static int make16Closest(int value) { if (value < 16) { return value; } else { value += 8; int val2 = value / 16; val2 *= 16; return val2; } } /** * 把数据变成16的倍数, 以大于等于16倍数的为准; * 比如: * 16-->返回16; * 17---31-->返回32; * * @param value * @return */ public static int make16Next(int value) { if(value%16==0){ return value; }else{ return ((int)(value/16.0f +1)*16) ; } } private static int checkCPUName() { String str1 = "/proc/cpuinfo"; String str2 = ""; try { FileReader fr = new FileReader(str1); BufferedReader localBufferedReader = new BufferedReader(fr, 8192); str2 = localBufferedReader.readLine(); while (str2 != null) { // Log.i("testCPU","->"+str2+"<-"); str2 = localBufferedReader.readLine(); if(str2.contains("SDM845")){ //845的平台; } } localBufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } return 0; } private static final String MIME_TYPE_AVC = "video/avc"; // H.264 Advanced private static boolean isSupportNV21=false; /** * 是否支持NV21的编码; * @return */ public boolean isSupportNV21ColorFormat() { if(isSupportNV21){ return true; } MediaCodecInfo codecInfo = selectCodec(MIME_TYPE_AVC); if (codecInfo == null) { return false; } isSupportNV21=selectColorFormat(codecInfo, MIME_TYPE_AVC); return isSupportNV21; } private static MediaCodecInfo selectCodec(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); if (!codecInfo.isEncoder()) { continue; } String[] types = codecInfo.getSupportedTypes(); for (int j = 0; j < types.length; j++) { if (types[j].equalsIgnoreCase(mimeType)) { return codecInfo; } } } return null; } private static boolean selectColorFormat(MediaCodecInfo codecInfo, String mimeType) { MediaCodecInfo.CodecCapabilities capabilities = codecInfo .getCapabilitiesForType(mimeType); for (int i = 0; i < capabilities.colorFormats.length; i++) { int colorFormat = capabilities.colorFormats[i]; if(colorFormat==21){ return true; } } return false; } /** * 精确裁剪的同时,缩放到指定位置,不同于上面的命令,这个可以设置宽度和高度. 其中宽度和高度是采用缩放来完成. * <p> * <p> * 采用的是软缩放的形式. * * @param videoFile * @param startS * @param durationS * @param width 要缩放到的宽度 建议是16的倍数 , * @param height 要缩放到的高度, 建议是16的倍数 * @param framerate 帧率 * @param bitrate 码率 * @return */ public String executeCutVideoExactTest(String videoFile, float startS, float durationS, int width, int height, float framerate, int bitrate) { if (fileExist(videoFile)) { List<String> cmdList = new ArrayList<String>(); String scalecmd = String.format(Locale.getDefault(), "scale=%d:%d", width, height); cmdList.add("-vcodec"); cmdList.add("lansoh264_dec"); cmdList.add("-i"); cmdList.add(videoFile); cmdList.add("-ss"); cmdList.add(String.valueOf(startS)); cmdList.add("-t"); cmdList.add(String.valueOf(durationS)); //---测试调整码率、帧率+裁剪 cmdList.add("-r"); cmdList.add(String.valueOf(framerate)); encodeBitRate = bitrate; //---测试调整码率、帧率+裁剪 cmdList.add("-vf"); cmdList.add(scalecmd); cmdList.add("-acodec"); cmdList.add("copy"); return executeAutoSwitch(cmdList); } else { return null; } } public String testGetAudioM4a(String srcPath) { String dstPath= LanSongFileUtil.createM4AFileInBox(); List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(srcPath); cmdList.add("-vn"); cmdList.add("-acodec"); cmdList.add("libfaac"); cmdList.add("-b:a"); cmdList.add("64000"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } if(executeVideoEditor(command)==0){ return dstPath; }else{ LSLog.e("executeGetAudioTrack error"); return null; } } public int testVideoMergeAudio(String video, String audio,String dstPath) { List<String> cmdList = new ArrayList<String>(); cmdList.add("-i"); cmdList.add(video); cmdList.add("-i"); cmdList.add(audio); cmdList.add("-map"); cmdList.add("0:v"); cmdList.add("-map"); cmdList.add("1:a"); cmdList.add("-vcodec"); cmdList.add("copy"); cmdList.add("-acodec"); cmdList.add("copy"); cmdList.add("-absf"); cmdList.add("aac_adtstoasc"); cmdList.add("-y"); cmdList.add(dstPath); String[] command = new String[cmdList.size()]; for (int i = 0; i < cmdList.size(); i++) { command[i] = (String) cmdList.get(i); } VideoEditor editor = new VideoEditor(); return editor.executeVideoEditor(command); } }