FFmpegUtil.java 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. package com.cooleshow.ffmpegcmd.util;
  2. import android.content.Context;
  3. import com.cooleshow.ffmpegcmd.model.VideoLayout;
  4. import java.util.List;
  5. import java.util.Locale;
  6. /**
  7. * ffmpeg tool: assemble the complete command
  8. * Created by frank on 2018/1/23.
  9. */
  10. public class FFmpegUtil {
  11. private static String[] insert(String[] cmd, int position, String inputPath) {
  12. return insert(cmd, position, inputPath, null);
  13. }
  14. /**
  15. * insert inputPath and outputPath into target array
  16. */
  17. private static String[] insert(String[] cmd, int position, String inputPath, String outputPath) {
  18. if (cmd == null || inputPath == null || position < 2) {
  19. return cmd;
  20. }
  21. int len = (outputPath != null ? (cmd.length + 2) : (cmd.length + 1));
  22. String[] result = new String[len];
  23. System.arraycopy(cmd, 0, result, 0, position);
  24. result[position] = inputPath;
  25. System.arraycopy(cmd, position, result, position + 1, cmd.length - position);
  26. if (outputPath != null) {
  27. result[result.length - 1] = outputPath;
  28. }
  29. return result;
  30. }
  31. public static String[] insert(String[] cmd, int position1, String inputPath1,
  32. int position2, String inputPath2, String outputPath) {
  33. if (cmd == null || inputPath1 == null || position1 < 2 || inputPath2 == null || position2 < 4) {
  34. return cmd;
  35. }
  36. int len = (outputPath != null ? (cmd.length + 3) : (cmd.length + 2));
  37. String[] result = new String[len];
  38. System.arraycopy(cmd, 0, result, 0, position1);
  39. result[position1] = inputPath1;
  40. System.arraycopy(cmd, position1, result, position1 + 1, position2 - position1 - 1);
  41. result[position2] = inputPath2;
  42. System.arraycopy(cmd, position2 - 1, result, position2 + 1, cmd.length - (position2 - 1));
  43. if (outputPath != null) {
  44. result[result.length - 1] = outputPath;
  45. }
  46. return result;
  47. }
  48. /**
  49. * transform audio, according to your assigning the output format
  50. *
  51. * @param inputPath input file
  52. * @param outputPath output file
  53. * @return transform success or not
  54. */
  55. public static String[] transformAudio(String inputPath, String outputPath) {
  56. String[] result = new String[4];
  57. result[0] = "ffmpeg";
  58. result[1] = "-i";
  59. result[2] = inputPath;
  60. result[3] = outputPath;
  61. return result;
  62. }
  63. public static String[] transformAudio(String inputPath, String acodec, String outputPath) {
  64. String transformAudioCmd = "ffmpeg -i -acodec %s -ac 2 -ar 44100";
  65. transformAudioCmd = String.format(transformAudioCmd, acodec);
  66. return insert(transformAudioCmd.split(" "), 2, inputPath, outputPath);
  67. }
  68. /**
  69. * cut audio, you could assign the startTime and duration which you want to
  70. *
  71. * @param inputPath input file
  72. * @param startTime start time in the audio(unit is second)
  73. * @param duration start time(unit is second)
  74. * @param outputPath output file
  75. * @return cut success or not
  76. */
  77. public static String[] cutAudio(String inputPath, float startTime, float duration, String outputPath) {
  78. String cutAudioCmd = "ffmpeg -i -ss %f -t %f -vn";
  79. cutAudioCmd = String.format(Locale.getDefault(), cutAudioCmd, startTime, duration);
  80. return insert(cutAudioCmd.split(" "), 2, inputPath, outputPath);
  81. }
  82. public static String[] getAudioDuration(String input) {
  83. // String cmd = "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 %s";
  84. String cmd = "ffmpeg -i %s -hide_banner -loglevel grep Duration | cut -d ' ' -f 4 | sed 's/,//'";
  85. // String cmd = "ffmpeg -i %s";
  86. cmd = String.format(Locale.getDefault(), cmd, input);
  87. return cmd.split(" ");
  88. }
  89. /**
  90. * concat all the audio together
  91. *
  92. * @param fileList list of files
  93. * @param outputPath output file
  94. * @return concat success or not
  95. */
  96. public static String[] concatAudio(List<String> fileList, String outputPath) {
  97. if (fileList == null || fileList.size() == 0) {
  98. return null;
  99. }
  100. StringBuilder concatBuilder = new StringBuilder();
  101. concatBuilder.append("concat:");
  102. for (String file : fileList) {
  103. concatBuilder.append(file).append("|");
  104. }
  105. String concatStr = concatBuilder.substring(0, concatBuilder.length() - 1);
  106. String concatAudioCmd = "ffmpeg -i -acodec copy"; // "ffmpeg -i -acodec libmp3lame"
  107. return insert(concatAudioCmd.split(" "), 2, concatStr, outputPath);
  108. }
  109. /**
  110. * mix multiple audio inputs into a single output
  111. */
  112. public static String[] mixAudio(String inputPath, String mixPath, String outputPath) {
  113. //mixing formula: value = sample1 + sample2 - ((sample1 * sample2) >> 0x10)
  114. return mixAudio(inputPath, mixPath, 0, 0, false, outputPath);
  115. }
  116. public static String[] mixAudio(String inputPath, String mixPath, int weight1, int weight2,
  117. boolean disableThumb, String outputPath) {
  118. //duration: first shortest longest
  119. //weight: adjust weight(volume) of each audio stream
  120. //disableThumb:(-vn)if not disable, it may cause incorrect mixing
  121. int len = 8;
  122. String amix = "amix=inputs=2:duration=shortest";
  123. if (disableThumb) len += 1;
  124. String[] mixAudioCmd = new String[len];
  125. mixAudioCmd[0] = "ffmpeg";
  126. mixAudioCmd[1] = "-i";
  127. mixAudioCmd[2] = inputPath;
  128. mixAudioCmd[3] = "-i";
  129. mixAudioCmd[4] = mixPath;
  130. mixAudioCmd[5] = "-filter_complex";
  131. if (weight1 > 0 && weight2 > 0) {
  132. String weight = ":weights=" + "'" + weight1 + " " + weight2 + "'";
  133. amix += weight;
  134. }
  135. mixAudioCmd[6] = amix;
  136. if (disableThumb) mixAudioCmd[7] = "-vn";
  137. mixAudioCmd[len - 1] = outputPath;
  138. return mixAudioCmd;
  139. }
  140. public static String[] mixAudio(String inputPath, String mixPath, int weight1, int weight2, float volume1, float volume2,
  141. boolean disableThumb, String outputPath, int delay, int delay2) {
  142. //duration: first shortest longest
  143. //weight: adjust weight(volume) of each audio stream
  144. //disableThumb:(-vn)if not disable, it may cause incorrect mixing
  145. int len = 8;
  146. // String amix = "[1]adelay=%d|%d[b];[0][b]amix=inputs=2:duration=shortest";
  147. // String amix = "";
  148. // if (isShortestAligning) {
  149. // amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=shortest";
  150. // } else {
  151. // amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=longest";
  152. // }
  153. String amix = "[0]volume=%f,adelay=%d|%d[a];[1]volume=%f,adelay=%d|%d[b];[a][b]amix=inputs=2:duration=first";
  154. amix = String.format(Locale.getDefault(), amix, volume1, delay, delay, volume2, delay2, delay2);
  155. if (disableThumb) len += 1;
  156. String[] mixAudioCmd = new String[len];
  157. mixAudioCmd[0] = "ffmpeg";
  158. mixAudioCmd[1] = "-i";
  159. mixAudioCmd[2] = inputPath;
  160. mixAudioCmd[3] = "-i";
  161. mixAudioCmd[4] = mixPath;
  162. mixAudioCmd[5] = "-filter_complex";
  163. if (weight1 > 0 && weight2 > 0) {
  164. String weight = ":weights=" + "'" + weight1 + " " + weight2 + "'";
  165. amix += weight;
  166. }
  167. mixAudioCmd[6] = amix;
  168. if (disableThumb) mixAudioCmd[7] = "-vn";
  169. mixAudioCmd[len - 1] = outputPath;
  170. return mixAudioCmd;
  171. }
  172. /**
  173. * merge multiple audio streams into a single multi-channel stream
  174. */
  175. public static String[] mergeAudio(String inputPath, String mergePath, String outputPath) {
  176. String mergeCmd = "ffmpeg -i %s -i %s -filter_complex [0:a][1:a]amerge=inputs=2[aout] -map [aout] %s";
  177. mergeCmd = String.format(mergeCmd, inputPath, mergePath, outputPath);
  178. return mergeCmd.split(" ");
  179. }
  180. /**
  181. * Set echo and delay effect
  182. *
  183. * @param inputPath input file
  184. * @param delay delay to play
  185. * @param outputPath output file
  186. */
  187. public static String[] audioEcho(String inputPath, int delay, String outputPath) {
  188. // in_gain (0, 1], Default is 0.6
  189. // out_gain (0, 1], Default is 0.3
  190. // delays (0 - 90000]. Default is 1000
  191. // decays (0 - 1.0]. Default is 0.5
  192. String echoCmd = "ffmpeg -i -af aecho=0.8:0.8:%d:0.5";
  193. echoCmd = String.format(Locale.getDefault(), echoCmd, delay);
  194. return insert(echoCmd.split(" "), 2, inputPath, outputPath);
  195. }
  196. /**
  197. * Set tremolo effect, sinusoidal amplitude modulation
  198. *
  199. * @param inputPath input file
  200. * @param frequency frequency
  201. * @param depth depth
  202. * @param outputPath output file
  203. */
  204. public static String[] audioTremolo(String inputPath, int frequency, float depth, String outputPath) {
  205. // frequency [0.1, 20000.0], Default is 5
  206. // depth (0, 1], Default is 0.5
  207. String tremoloCmd = "ffmpeg -i -af tremolo=%d:%f";
  208. tremoloCmd = String.format(Locale.getDefault(), tremoloCmd, frequency, depth);
  209. return insert(tremoloCmd.split(" "), 2, inputPath, outputPath);
  210. }
  211. /**
  212. * Denoise audio samples with FFT
  213. *
  214. * @param inputPath input file
  215. * @param outputPath output file
  216. */
  217. public static String[] audioDenoise(String inputPath, String outputPath) {
  218. // nr: noise reduction in dB, [0.01 to 97], Default value is 12 dB
  219. // nf: noise floor in dB, [-80 to -20], Default value is -50 dB
  220. // nt: noise type {w:white noise v:vinyl noise s:shellac noise}
  221. String fftDenoiseCmd = "ffmpeg -i -af afftdn";
  222. return insert(fftDenoiseCmd.split(" "), 2, inputPath, outputPath);
  223. }
  224. /**
  225. * Detect silence of a chunk of audio
  226. *
  227. * @param inputPath input file
  228. */
  229. public static String[] audioSilenceDetect(String inputPath) {
  230. // silence_start: 268.978
  231. // silence_end: 271.048 | silence_duration: 2.06975
  232. String silenceCmd = "ffmpeg -i -af silencedetect=noise=0.0001 -f null -";
  233. return insert(silenceCmd.split(" "), 2, inputPath);
  234. }
  235. /**
  236. * Change volume of a chunk of audio
  237. *
  238. * @param inputPath input file
  239. * @param volume volume
  240. * @param outputPath output file
  241. */
  242. public static String[] audioVolume(String inputPath, float volume, String outputPath) {
  243. // output_volume = volume * input_volume
  244. String volumeCmd = "ffmpeg -i -af volume=%f";
  245. volumeCmd = String.format(Locale.getDefault(), volumeCmd, volume);
  246. return insert(volumeCmd.split(" "), 2, inputPath, outputPath);
  247. }
  248. /**
  249. * Apply 18 band of equalizer into audio
  250. *
  251. * @param inputPath input file
  252. * @param bandList bandList
  253. * @param outputPath output file
  254. */
  255. public static String[] audioEqualizer(String inputPath, List<String> bandList, String outputPath) {
  256. // unit: Hz gain:0-20
  257. /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  258. | 1b | 2b | 3b | 4b | 5b | 6b | 7b | 8b | 9b |
  259. | 65 | 92 | 131 | 185 | 262 | 370 | 523 | 740 | 1047 |
  260. |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  261. | 10b | 11b | 12b | 13b | 14b | 15b | 16b | 17b | 18b |
  262. | 1480 | 2093 | 2960 | 4186 | 5920 | 8372 | 11840 | 16744 | 20000 |
  263. |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
  264. String bands = String.join(":", bandList);
  265. String equalizerCmd = "ffmpeg -i -af superequalizer=%s -y";
  266. equalizerCmd = String.format(Locale.getDefault(), equalizerCmd, bands);
  267. return insert(equalizerCmd.split(" "), 2, inputPath, outputPath);
  268. }
  269. /**
  270. * mux audio and video together
  271. *
  272. * @param videoFile the file of pure video
  273. * @param audioFile the file of pure audio
  274. * @param copy copy codec
  275. * @param muxFile output file
  276. * @return mux success or not
  277. */
  278. public static String[] mediaMux(String videoFile, String audioFile, boolean copy, String muxFile) {
  279. String mediaMuxCmd;
  280. if (copy) {
  281. mediaMuxCmd = "ffmpeg -i %s -i %s -codec copy -shortest -y %s";
  282. } else {
  283. mediaMuxCmd = "ffmpeg -i %s -i %s -shortest -y %s";
  284. }
  285. mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, videoFile, audioFile, muxFile);
  286. return mediaMuxCmd.split(" ");
  287. }
  288. /**
  289. * mux audio and video together
  290. *
  291. * @param videoFile the file of pure video
  292. * @param audioFile the file of pure audio
  293. * @param copy copy codec
  294. * @param muxFile output file
  295. * @return mux success or not
  296. */
  297. public static String[] mediaMux2(String videoFile, String audioFile, boolean copy, String muxFile, float videoDelay) {
  298. String mediaMuxCmd;
  299. if (copy) {
  300. mediaMuxCmd = "ffmpeg -itsoffset %f -i %s -itsoffset 0 -i %s -codec copy -shortest -y %s";
  301. } else {
  302. mediaMuxCmd = "ffmpeg -itsoffset %f -i %s -itsoffset 0 -i %s -shortest -y %s";
  303. }
  304. mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, videoDelay, videoFile, audioFile, muxFile);
  305. return mediaMuxCmd.split(" ");
  306. }
  307. public static String[] getVideoFrame(String input, String output) {
  308. // String mediaMuxCmd = "ffmpeg -i %s -vf fps=1/1 %simg%03d.jpg";
  309. String mediaMuxCmd = "ffmpeg -i %s -vf fps=1/1 %s";
  310. mediaMuxCmd = String.format(Locale.getDefault(), mediaMuxCmd, input, output);
  311. return mediaMuxCmd.split(" ");
  312. }
  313. /**
  314. * extract audio from media file
  315. *
  316. * @param inputPath input file
  317. * @param outputPath output file
  318. * @return demux audio success or not
  319. */
  320. public static String[] extractAudio(String inputPath, String outputPath) {
  321. // -vn: disable video
  322. // multi audio track: ffmpeg -i input.mp4 -map 0:1 -vn output.mp3
  323. String extractAudioCmd = "ffmpeg -i -vn";
  324. return insert(extractAudioCmd.split(" "), 2, inputPath, outputPath);
  325. }
  326. /**
  327. * extract pure video from media file
  328. *
  329. * @param inputPath input file
  330. * @param outputPath output file
  331. * @return demux video success or not
  332. */
  333. public static String[] extractVideo(String inputPath, String outputPath) {
  334. //-an: disable audio
  335. String extractVideoCmd = "ffmpeg -i -vcodec copy -an";
  336. return insert(extractVideoCmd.split(" "), 2, inputPath, outputPath);
  337. }
  338. /**
  339. * transform video, according to your assigning the output format
  340. *
  341. * @param inputPath input file
  342. * @param outputPath output file
  343. * @return transform video success or not
  344. */
  345. public static String[] transformVideo(String inputPath, String outputPath) {
  346. //just copy codec
  347. // String transformVideoCmd = "ffmpeg -i %s -vcodec copy -acodec copy %s";
  348. // assign the frameRate, bitRate and resolution
  349. // String transformVideoCmd = "ffmpeg -i %s -r 25 -b 200 -s 1080x720 %s";
  350. // assign the encoder
  351. // ffmpeg -i %s -vcodec libx264 -acodec libmp3lame %s
  352. String transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec libmp3lame";
  353. return insert(transformVideoCmd.split(" "), 2, inputPath, outputPath);
  354. }
  355. /**
  356. * Using FFmpeg to transform video, with re-encode
  357. *
  358. * @param inputPath the source file
  359. * @param outputPath target file
  360. * @return transform video success or not
  361. */
  362. public static String[] transformVideoWithEncode(String inputPath, String outputPath) {
  363. return transformVideoWithEncode(inputPath, 0, 0, outputPath);
  364. }
  365. /**
  366. * Using FFmpeg to transform video, with re-encode and specific resolution
  367. *
  368. * @param inputPath the source file
  369. * @param width the width of video
  370. * @param height the height of video
  371. * @param outputPath target file
  372. * @return transform video success or not
  373. */
  374. public static String[] transformVideoWithEncode(String inputPath, int width, int height, String outputPath) {
  375. String transformVideoCmd;
  376. if (width > 0 && height > 0) {
  377. String scale = "-vf scale=" + width + ":" + height;
  378. transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec aac " + scale;
  379. } else {
  380. transformVideoCmd = "ffmpeg -i -vcodec libx264 -acodec aac";
  381. }
  382. return insert(transformVideoCmd.split(" "), 2, inputPath, outputPath);
  383. }
  384. /**
  385. * joint every single video together
  386. *
  387. * @param fileListPath the path file list
  388. * @param outputPath output path
  389. * @return joint video success or not
  390. */
  391. public static String[] jointVideo(String fileListPath, String outputPath) {
  392. // ffmpeg -f concat -safe 0 -i %s -c copy %s
  393. String jointVideoCmd = "ffmpeg -f concat -safe 0 -i -c copy -y";
  394. return insert(jointVideoCmd.split(" "), 6, fileListPath, outputPath);
  395. }
  396. /**
  397. * cut video, you could assign the startTime and duration which you want to
  398. *
  399. * @param inputPath input file
  400. * @param startTime startTime in the video(unit is second)
  401. * @param duration duration
  402. * @param outputPath output file
  403. * @return cut video success or not
  404. */
  405. public static String[] cutVideo(String inputPath, float startTime, float duration, String outputPath) {
  406. // -map 0 -codec copy (copy all tracks)
  407. // -map 0:v -vcodec copy (copy track of video)
  408. // -map 0:a -acodec copy (copy all tracks of audio)
  409. // -map 0:s -scodec copy (copy all tracks of subtitle)
  410. // ffmpeg -ss %f -accurate_seek -t %f -i %s -map 0 -codec copy -avoid_negative_ts 1 %s
  411. String cutVideoCmd = "ffmpeg -ss %f -accurate_seek -t %f -i -map 0 -codec copy -avoid_negative_ts 1";
  412. cutVideoCmd = String.format(Locale.getDefault(), cutVideoCmd, startTime, duration);
  413. return insert(cutVideoCmd.split(" "), 7, inputPath, outputPath);
  414. }
  415. /**
  416. * screenshot from video, you could assign the specific time
  417. *
  418. * @param inputPath input file
  419. * @param offset which time you want to shot
  420. * @param outputPath output file
  421. * @return screenshot success or not
  422. */
  423. public static String[] screenShot(String inputPath, float offset, String outputPath) {
  424. // ffmpeg -ss %f -i %s -f image2 -vframes 1 -an %s
  425. String screenShotCmd = "ffmpeg -ss %f -i -f image2 -vframes 1 -an";
  426. screenShotCmd = String.format(Locale.getDefault(), screenShotCmd, offset);
  427. return insert(screenShotCmd.split(" "), 4, inputPath, outputPath);
  428. }
  429. private static String obtainOverlay(int offsetX, int offsetY, int location) {
  430. switch (location) {
  431. case 2:
  432. return "overlay='(main_w-overlay_w)-" + offsetX + ":" + offsetY + "'";
  433. case 3:
  434. return "overlay='" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'";
  435. case 4:
  436. return "overlay='(main_w-overlay_w)-" + offsetX + ":(main_h-overlay_h)-" + offsetY + "'";
  437. case 1:
  438. default:
  439. // move from to right
  440. return "overlay=(10+t*20):" + offsetY;
  441. }
  442. }
  443. /**
  444. * add watermark with image to video, you could assign the location and bitRate
  445. *
  446. * @param inputPath input file
  447. * @param imgPath the path of the image
  448. * @param location the location in the video(1:top left 2:top right 3:bottom left 4:bottom right)
  449. * @param bitRate bitRate
  450. * @param offsetXY the offset of x and y in the video
  451. * @param outputPath output file
  452. * @return add watermark success or not
  453. */
  454. public static String[] addWaterMarkImg(String inputPath, String imgPath, int location, int bitRate,
  455. int offsetXY, String outputPath) {
  456. String mBitRate = bitRate + "k";
  457. String overlay = obtainOverlay(offsetXY, offsetXY, location);
  458. String waterMarkCmd = "ffmpeg -i -i -b:v %s -filter_complex %s -preset:v superfast -y";
  459. waterMarkCmd = String.format(waterMarkCmd, mBitRate, overlay);
  460. return insert(waterMarkCmd.split(" "), 2, inputPath, 4, imgPath, outputPath);
  461. }
  462. /**
  463. * add watermark with gif to video, you could assign the location and bitRate
  464. *
  465. * @param inputPath input file
  466. * @param imgPath the path of the gif
  467. * @param location the location in the video(1:top left 2:top right 3:bottom left 4:bottom right)
  468. * @param bitRate bitRate
  469. * @param offsetXY the offset of x and y in the video
  470. * @param outputPath output file
  471. * @return add watermark success or not
  472. */
  473. public static String[] addWaterMarkGif(Context context, String inputPath, String imgPath, int location, int bitRate,
  474. int offsetXY, String outputPath) {
  475. String mBitRate = bitRate + "k";
  476. int offset = ScreenUtil.INSTANCE.dp2px(context, offsetXY);
  477. String overlay = obtainOverlay(offset, offset, location) + ":shortest=1";
  478. String waterMarkCmd = "ffmpeg -i -ignore_loop 0 -i -b:v %s -filter_complex %s -preset:v superfast";
  479. waterMarkCmd = String.format(waterMarkCmd, mBitRate, overlay);
  480. return insert(waterMarkCmd.split(" "), 2, inputPath, 6, imgPath, outputPath);
  481. }
  482. /**
  483. * Remove watermark from video: Suppress logo by a simple interpolation of the surrounding pixels.
  484. * On the other hand, it can be used to mosaic video
  485. *
  486. * @return delogo cmd
  487. */
  488. public static String[] removeLogo(String inputPath, int x, int y, int width, int height, String outputPath) {
  489. // ffmpeg -i in.mp4 -filter_complex delogo=x=%d:y=%d:w=%d:h=%d out.mp4
  490. String delogoCmd = "ffmpeg -i -filter_complex delogo=x=%d:y=%d:w=%d:h=%d";
  491. delogoCmd = String.format(Locale.getDefault(), delogoCmd, x, y, width, height);
  492. return insert(delogoCmd.split(" "), 2, inputPath, outputPath);
  493. }
  494. /**
  495. * generate a palette for gif
  496. *
  497. * @param inputPath input file
  498. * @param frameRate frameRate of the gif
  499. * @param startTime startTime in the video
  500. * @param duration duration, how long you want to
  501. * @param width width
  502. * @param outputPath output file
  503. * @return generate palette success or not
  504. */
  505. public static String[] generatePalette(String inputPath, int startTime, int duration,
  506. int frameRate, int width, String outputPath) {
  507. String paletteCmd = "ffmpeg -ss %d -accurate_seek -t %d -i -vf fps=%d,scale=%d:-1:flags=lanczos,palettegen";
  508. paletteCmd = String.format(Locale.getDefault(), paletteCmd, startTime,
  509. duration, frameRate, width);
  510. return insert(paletteCmd.split(" "), 7, inputPath, outputPath);
  511. }
  512. /**
  513. * convert video into gif with palette
  514. *
  515. * @param inputPath input file
  516. * @param palette the palette which will apply to gif
  517. * @param startTime startTime in the video
  518. * @param duration duration, how long you want to
  519. * @param frameRate frameRate of the gif
  520. * @param width width
  521. * @param outputPath output gif
  522. * @return convert gif success or not
  523. */
  524. public static String[] generateGifByPalette(String inputPath, String palette, int startTime, int duration,
  525. int frameRate, int width, String outputPath) {
  526. String paletteGifCmd = "ffmpeg -ss %d -accurate_seek -t %d -i -i -lavfi fps=%d,scale=%d:-1:flags=lanczos[x];[x][1:v]" +
  527. "paletteuse=dither=bayer:bayer_scale=3";
  528. paletteGifCmd = String.format(Locale.getDefault(), paletteGifCmd, startTime,
  529. duration, frameRate, width);
  530. return insert(paletteGifCmd.split(" "), 7, inputPath, 9, palette, outputPath);
  531. }
  532. /**
  533. * convert s series of pictures into video
  534. *
  535. * @param inputPath input file
  536. * @param frameRate frameRate
  537. * @param outputPath output file
  538. * @return convert success or not
  539. */
  540. public static String[] pictureToVideo(String inputPath, int frameRate, String outputPath) {
  541. //-f: stand for format
  542. String combineVideo = "ffmpeg -f image2 -r %d -i %simg#d.jpg -vcodec mpeg4 %s";
  543. combineVideo = String.format(Locale.getDefault(), combineVideo, frameRate, inputPath, outputPath);
  544. combineVideo = combineVideo.replace("#", "%");
  545. return combineVideo.split(" ");
  546. }
  547. /**
  548. * convert resolution
  549. *
  550. * @param inputPath input file
  551. * @param resolution resolution
  552. * @param outputPath output file
  553. * @return convert success or not
  554. */
  555. public static String[] convertResolution(String inputPath, String resolution, String outputPath) {
  556. String convertCmd = "ffmpeg -i -s %s -pix_fmt yuv420p";
  557. convertCmd = String.format(Locale.getDefault(), convertCmd, resolution);
  558. return insert(convertCmd.split(" "), 2, inputPath, outputPath);
  559. }
  560. /**
  561. * encode audio, you could assign the sampleRate and channel
  562. *
  563. * @param inputPath pcm raw audio
  564. * @param outputPath output file
  565. * @param sampleRate sampleRate
  566. * @param channel sound channel: mono channel is 1, stereo channel is 2
  567. * @return encode audio success or not
  568. */
  569. public static String[] encodeAudio(String inputPath, String outputPath, int sampleRate, int channel) {
  570. String encodeAudioCmd = "ffmpeg -f s16le -ar %d -ac %d -i";
  571. encodeAudioCmd = String.format(Locale.getDefault(), encodeAudioCmd, sampleRate, channel);
  572. return insert(encodeAudioCmd.split(" "), 8, inputPath, outputPath);
  573. }
  574. /**
  575. * join multi videos together
  576. *
  577. * @param input1 input one
  578. * @param input2 input two
  579. * @param videoLayout the layout of video, which could be horizontal or vertical
  580. * @param outputPath output file
  581. * @return join success or not
  582. */
  583. public static String[] multiVideo(String input1, String input2, String outputPath, int videoLayout) {
  584. String multiVideo = "ffmpeg -i -i -filter_complex hstack";//hstack: horizontal
  585. if (videoLayout == VideoLayout.LAYOUT_VERTICAL) {//vstack: vertical
  586. multiVideo = multiVideo.replace("hstack", "vstack");
  587. }
  588. return insert(multiVideo.split(" "), 2, input1, 4, input2, outputPath);
  589. }
  590. /**
  591. * reverse video
  592. *
  593. * @param inputPath input file
  594. * @param outputPath output file
  595. * @return reverse success or not
  596. */
  597. public static String[] reverseVideo(String inputPath, String outputPath) {
  598. //-vf reverse: only video reverse, -an: disable audio
  599. //tip: reverse will cost a lot of time, only short video are recommended
  600. String reverseVideo = "ffmpeg -i -vf reverse -an";
  601. return insert(reverseVideo.split(" "), 2, inputPath, outputPath);
  602. }
  603. /**
  604. * noise reduction with video
  605. *
  606. * @param inputPath input file
  607. * @param outputPath output file
  608. * @return noise reduction success or not
  609. */
  610. public static String[] denoiseVideo(String inputPath, String outputPath) {
  611. String denoiseVideo = "ffmpeg -i -nr 500";
  612. return insert(denoiseVideo.split(" "), 2, inputPath, outputPath);
  613. }
  614. /**
  615. * covert video into a series of pictures
  616. *
  617. * @param inputPath input file
  618. * @param startTime startTime in the video
  619. * @param duration duration, how long you want
  620. * @param frameRate frameRate
  621. * @param outputPath output file
  622. * @return convert success or not
  623. */
  624. public static String[] videoToImage(String inputPath, int startTime, int duration, int frameRate, String outputPath) {
  625. return videoToImageWithScale(inputPath, startTime, duration, frameRate, 0, outputPath);
  626. }
  627. public static String[] videoToImageWithScale(String inputPath, int startTime, int duration,
  628. int frameRate, int width, String outputPath) {
  629. String toImage;
  630. if (width > 0) {
  631. toImage = "ffmpeg -ss %d -accurate_seek -t %d -i %s -an -vf fps=%d,scale=%d:-1 %s";
  632. toImage = String.format(Locale.CHINESE, toImage, startTime, duration, inputPath, frameRate, width, outputPath);
  633. } else {
  634. toImage = "ffmpeg -ss %d -accurate_seek -t %d -i %s -an -r %d %s";
  635. toImage = String.format(Locale.CHINESE, toImage, startTime, duration, inputPath, frameRate, outputPath);
  636. }
  637. toImage = toImage + "%3d.png";
  638. return toImage.split(" ");
  639. }
  640. /**
  641. * convert videos into picture-in-picture mode
  642. *
  643. * @param inputPath1 input one
  644. * @param inputPath2 input two
  645. * @param outputPath output file
  646. * @param x x coordinate point
  647. * @param y y coordinate point
  648. * @return convert success or not
  649. */
  650. public static String[] picInPicVideo(String inputPath1, String inputPath2, int x, int y, String outputPath) {
  651. String pipVideo = "ffmpeg -i -i -filter_complex overlay=%d:%d";
  652. pipVideo = String.format(Locale.getDefault(), pipVideo, x, y);
  653. return insert(pipVideo.split(" "), 2, inputPath1, 4, inputPath2, outputPath);
  654. }
  655. /**
  656. * convert videos into picture-in-picture mode
  657. *
  658. * @param whichCorner 0: top-left; 1: top-right; 2: bottom-left; 3: bottom-right
  659. * @return convert success or not
  660. */
  661. public static String[] picInPicVideoInCorner(String inputPath1, String inputPath2, int whichCorner, String outputPath) {
  662. String pipCmd = "ffmpeg -i -i -filter_complex ";
  663. switch (whichCorner) {
  664. case 1:
  665. pipCmd += "overlay=W-w";
  666. break;
  667. case 2:
  668. pipCmd += "overlay=0:H-h";
  669. break;
  670. case 3:
  671. pipCmd += "overlay=W-w:H-h";
  672. break;
  673. case 0:
  674. default:
  675. pipCmd += "overlay";
  676. break;
  677. }
  678. return insert(pipCmd.split(" "), 2, inputPath1, 4, inputPath2, outputPath);
  679. }
  680. /**
  681. * move moov box in the front of mdat box, when moox box is behind mdat box(only mp4)
  682. *
  683. * @param inputPath inputFile
  684. * @param outputPath outputFile
  685. * @return move success or not
  686. */
  687. public static String[] moveMoovAhead(String inputPath, String outputPath) {
  688. String moovCmd = "ffmpeg -i -movflags faststart -acodec copy -vcodec copy";
  689. return insert(moovCmd.split(" "), 2, inputPath, outputPath);
  690. }
  691. /**
  692. * Change Video from RGB to gray(black & white)
  693. *
  694. * @param inputPath inputPath
  695. * @param outputPath outputPath
  696. * @return grayCmd
  697. */
  698. public static String[] toGrayVideo(String inputPath, String outputPath) {
  699. String grayCmd = "ffmpeg -i -vf lutyuv='u=128:v=128'";
  700. return insert(grayCmd.split(" "), 2, inputPath, outputPath);
  701. }
  702. /**
  703. * Photo zoom to video
  704. *
  705. * @param inputPath inputPath
  706. * @param position position
  707. * 0:center
  708. * 1:left to right
  709. * 2:right to left
  710. * 3:down to up
  711. * 4:up to down
  712. * @param outputPath outputPath
  713. * @return zoomCmd
  714. */
  715. public static String[] photoZoomToVideo(String inputPath, int position, String outputPath) {
  716. String zoomCmd = "ffmpeg -i -vf ";
  717. switch (position) {
  718. case 1:
  719. zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw/zoom)/2,x-0.5)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=180";
  720. break;
  721. case 2:
  722. zoomCmd += "zoompan='1.3':x='if(lte(on,-1),(iw-iw/zoom)/2,x+0.5)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=180";
  723. break;
  724. case 3:
  725. zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,-1),(ih-ih/zoom)/2,y+0.5)':d=180";
  726. break;
  727. case 4:
  728. zoomCmd += "zoompan='1.3':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,1),(ih/zoom)/2,y-0.5)':d=180";
  729. break;
  730. case 0:
  731. default:
  732. zoomCmd += "zoompan=z='min(zoom+0.0015,1.5)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=180";
  733. break;
  734. }
  735. return insert(zoomCmd.split(" "), 2, inputPath, outputPath);
  736. }
  737. /**
  738. * using FFprobe to parse the media format
  739. *
  740. * @param inputPath inputFile
  741. * @return probe success or not
  742. */
  743. public static String[] probeFormat(String inputPath) {
  744. // ffprobe -i hello.mp4 -show_streams -show_format -print_format json"
  745. String ffprobeCmd = "ffprobe -i -show_streams -show_format -print_format json";
  746. return insert(ffprobeCmd.split(" "), 2, inputPath);
  747. }
  748. /**
  749. * Changing the speed of playing, speed range at 0.5-2 in audio-video mode.
  750. * However, in pure video mode, the speed range at 0.25-4
  751. *
  752. * @param inputPath the inputFile of normal speed
  753. * @param outputPath the outputFile which you want to change speed
  754. * @param speed speed of playing
  755. * @param pureVideo whether pure video or not, default false
  756. * @return change speed success or not
  757. */
  758. public static String[] changeSpeed(String inputPath, String outputPath, float speed, boolean pureVideo) {
  759. //audio atempo: 0.5--2
  760. //video pts:0.25--4
  761. if (pureVideo) {
  762. if (speed > 4 || speed < 0.25) {
  763. throw new IllegalArgumentException("speed range is 0.25--4");
  764. }
  765. } else {
  766. if (speed > 2 || speed < 0.5) {
  767. throw new IllegalArgumentException("speed range is 0.5--2");
  768. }
  769. }
  770. float ptsFactor = 1 / speed;
  771. String speedCmd;
  772. if (pureVideo) {
  773. speedCmd = "ffmpeg -i -filter_complex [0:v]setpts=%.2f*PTS[v] -map [v]";
  774. speedCmd = String.format(Locale.getDefault(), speedCmd, ptsFactor);
  775. } else {
  776. speedCmd = "ffmpeg -i -filter_complex [0:v]setpts=%.2f*PTS[v];[0:a]atempo=%.2f[a] -map [v] -map [a]";
  777. speedCmd = String.format(Locale.getDefault(), speedCmd, ptsFactor, speed);
  778. }
  779. return insert(speedCmd.split(" "), 2, inputPath, outputPath);
  780. }
  781. /**
  782. * Changing the speed of playing, speed range at 0.5-2 in audio.
  783. *
  784. * @param inputPath the inputFile of normal speed
  785. * @param outputPath the outputFile which you want to change speed
  786. * @param speed speed of playing
  787. * @return change speed success or not
  788. */
  789. public static String[] changeAudioSpeed(String inputPath, String outputPath, float speed) {
  790. // atempo range [0.5, 100.0]
  791. if (speed > 100 || speed < 0.5) {
  792. throw new IllegalArgumentException("audio speed range is from 0.5 to 100");
  793. }
  794. String speedCmd = "ffmpeg -i -filter_complex atempo=%.2f -c:a copy -b:a 192k";
  795. speedCmd = String.format(Locale.getDefault(), speedCmd, speed);
  796. return insert(speedCmd.split(" "), 2, inputPath, outputPath);
  797. }
  798. public static String[] changeAudioSpeed2(String inputPath, String outputPath, float speed) {
  799. // atempo range [0.5, 100.0]
  800. if (speed > 100) {
  801. throw new IllegalArgumentException("audio speed range is from 0.5 to 100");
  802. }
  803. if (speed < 0.5) {
  804. String s = buildAtempoFilters(speed);
  805. String command = "ffmpeg -i -filter:a %s -b:a 192k";
  806. String format = String.format(Locale.getDefault(), command, s);
  807. return insert(format.split(" "), 2, inputPath, outputPath);
  808. } else {
  809. String speedCmd = "ffmpeg -i -filter:a atempo=%.2f -b:a 192k";
  810. speedCmd = String.format(Locale.getDefault(), speedCmd, speed);
  811. return insert(speedCmd.split(" "), 2, inputPath, outputPath);
  812. }
  813. }
  814. public static String[] changeAudioSpeed3(String inputPath, String outputPath, float speed) {
  815. if (speed > 100.0F) {
  816. throw new IllegalArgumentException("audio speed range is from 0.5 to 100");
  817. } else {
  818. String speedCmd;
  819. if ((double)speed < 0.5) {
  820. speedCmd = buildAtempoFilters((double)speed);
  821. String command = "ffmpeg -i -filter:a %s -b:a 192k -ar 44100 -ac 2 -acodec pcm_s16le";
  822. String format = String.format(Locale.getDefault(), command, speedCmd);
  823. return insert(format.split(" "), 2, inputPath, outputPath);
  824. } else {
  825. speedCmd = "ffmpeg -i -filter:a atempo=%.2f -b:a 192k -ar 44100 -ac 2 -acodec pcm_s16le";
  826. speedCmd = String.format(Locale.getDefault(), speedCmd, speed);
  827. return insert(speedCmd.split(" "), 2, inputPath, outputPath);
  828. }
  829. }
  830. }
  831. private static String buildAtempoFilters(double speed) {
  832. StringBuilder filters = new StringBuilder();
  833. double tempSpeed = speed;
  834. // 分解速度调整,FFmpeg的atempo值只能在0.5到2.0之间
  835. while (tempSpeed < 0.5) {
  836. filters.append("atempo=0.5,");
  837. tempSpeed *= 2;
  838. }
  839. filters.append(String.format("atempo=%.2f", tempSpeed));
  840. return filters.toString();
  841. }
  842. /**
  843. * Insert the picture into the header of video, which as a thumbnail
  844. *
  845. * @param inputPath inputFile
  846. * @param picturePath the path of thumbnail
  847. * @param outputPath targetFile
  848. * @return command of inserting picture
  849. */
  850. public static String[] insertPicIntoVideo(String inputPath, String picturePath, String outputPath) {
  851. String insertPicCmd = "ffmpeg -i -i -map 0 -map 1 -c copy -c:v:1 png -disposition:v:1 attached_pic";
  852. return insert(insertPicCmd.split(" "), 2, inputPath, 4, picturePath, outputPath);
  853. }
  854. public static String[] addSubtitleIntoVideo(String inputPath, String subtitlePath, String outputPath) {
  855. String subtitleCmd = "ffmpeg -i -i -map 0:v -map 0:a -map 1:s -c copy";
  856. return insert(subtitleCmd.split(" "), 2, inputPath, 4, subtitlePath, outputPath);
  857. }
  858. /**
  859. * Using one input file to push multi streams.
  860. * After publish the streams, you could use VLC to play it
  861. * Note: if stream is rtmp protocol, need to start your rtmp server
  862. * Note: if stream is http protocol, need to start your http server
  863. *
  864. * @param inputPath inputFile
  865. * @param duration how long of inputFile you want to publish
  866. * @param streamUrl1 the url of stream1
  867. * @param streamUrl2 the url of stream2
  868. * @return command of build flv index
  869. */
  870. public static String[] pushMultiStreams(String inputPath, int duration, String streamUrl1, String streamUrl2) {
  871. //ffmpeg -i what.mp4 -vcodec libx264 -acodec aac -t 60 -f flv
  872. //"tee:rtmp://192.168.1.102/live/stream1|rtmp://192.168.1.102/live/stream2"
  873. String format = "flv";
  874. if (streamUrl1.startsWith("rtmp://")) {//rtmp protocol
  875. format = "flv";
  876. } else if (streamUrl1.startsWith("http://")) {//http protocol
  877. format = "mpegts";
  878. }
  879. String pushStreams = "ffmpeg -i %s -vcodec libx264 -acodec aac -t %d -f %s \"tee:%s|%s\"";
  880. pushStreams = String.format(Locale.getDefault(), pushStreams, inputPath, duration, format, streamUrl1, streamUrl2);
  881. return pushStreams.split(" ");
  882. }
  883. public static String[] rotateVideo(String inputPath, int rotateDegree, String outputPath) {
  884. String rotateCmd = "ffmpeg -i -c copy -metadata:s:v:0 rotate=%d";
  885. rotateCmd = String.format(Locale.getDefault(), rotateCmd, rotateDegree);
  886. return insert(rotateCmd.split(" "), 2, inputPath, outputPath);
  887. }
  888. /**
  889. * Trim one or more segments from a single video
  890. *
  891. * @param inputPath the path of input file
  892. * @param start1 start time of the first segment
  893. * @param end1 end time of the first segment
  894. * @param start2 start time of the second segment
  895. * @param end2 end time of the second segment
  896. * @param outputPath the path of output file
  897. * @return the command of trim video
  898. */
  899. public static String[] trimVideo(String inputPath, int start1, int end1, int start2, int end2, String outputPath) {
  900. String trimCmd = "ffmpeg -i -filter_complex " +
  901. "[0:v]trim=start=%d:end=%d,setpts=PTS-STARTPTS[v0];" +
  902. "[0:a]atrim=start=%d:end=%d,asetpts=PTS-STARTPTS[a0];" +
  903. "[0:v]trim=start=%d:end=%d,setpts=PTS-STARTPTS[v1];" +
  904. "[0:a]atrim=start=%d:end=%d,asetpts=PTS-STARTPTS[a1];" +
  905. "[v0][a0][v1][a1]concat=n=2:v=1:a=1[out] -map [out]";
  906. trimCmd = String.format(Locale.getDefault(), trimCmd, start1, end1, start1, end1, start2, end2, start2, end2);
  907. return insert(trimCmd.split(" "), 2, inputPath, outputPath);
  908. }
  909. public static String[] showAudioWaveform(String inputPath, String resolution, int splitChannels, String outputPath) {
  910. String waveformCmd = "ffmpeg -i -filter_complex showwavespic=s=%s:split_channels=%d";
  911. waveformCmd = String.format(Locale.getDefault(), waveformCmd, resolution, splitChannels);
  912. return insert(waveformCmd.split(" "), 2, inputPath, outputPath);
  913. }
  914. /**
  915. * Convert video into stereo3d mode(VR video)
  916. *
  917. * @param inputPath inputPath
  918. * @param outputPath outputPath
  919. * @return the command of stereo3d
  920. */
  921. public static String[] videoStereo3D(String inputPath, String outputPath) {
  922. /* *
  923. * sbsl: side by side parallel (left eye left, right eye right)
  924. * sbsr: side by side crosseye (right eye left, left eye right)
  925. * abl : above-below (left eye above, right eye below)
  926. * al : alternating frames (left eye first, right eye second)
  927. * irl : interleaved rows (left eye has top row, right eye next)
  928. * icl : interleaved columns, left eye first
  929. */
  930. String stereo3dCmd = "ffmpeg -i -filter_complex stereo3d=sbsl:arbg";
  931. return insert(stereo3dCmd.split(" "), 2, inputPath, outputPath);
  932. }
  933. public static String[] videoTransition(String inputPath1, int width, int height, int offset, String inputPath2, String outputPath) {
  934. String transitionCmd = "ffmpeg -i -i -filter_complex " +
  935. "[0]settb=AVTB,fps=24000/1001[v0];[1]settb=AVTB,fps=24000/1001,scale=%d:%d[v1];" +
  936. "[v0][v1]xfade=transition=radial:duration=1:offset=%d";
  937. transitionCmd = String.format(Locale.getDefault(), transitionCmd, width, height, offset);
  938. return insert(transitionCmd.split(" "), 2, inputPath1, 4, inputPath2, outputPath);
  939. }
  940. }