115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu/* 215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * Copyright (C) 2012 The Android Open Source Project 315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * 415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * Licensed under the Apache License, Version 2.0 (the "License"); 515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * you may not use this file except in compliance with the License. 615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * You may obtain a copy of the License at 715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * 815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * http://www.apache.org/licenses/LICENSE-2.0 915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * 1015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * Unless required by applicable law or agreed to in writing, software 1115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * distributed under the License is distributed on an "AS IS" BASIS, 1215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * See the License for the specific language governing permissions and 1415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu * limitations under the License. 1515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu */ 1615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 1715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu// Modified example based on mp4parser google code open source project. 1815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu// http://code.google.com/p/mp4parser/source/browse/trunk/examples/src/main/java/com/googlecode/mp4parser/ShortenExample.java 1915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 2015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhupackage com.android.gallery3d.app; 2115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 228b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.media.MediaCodec.BufferInfo; 238b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.media.MediaExtractor; 248b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.media.MediaFormat; 258b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.media.MediaMetadataRetriever; 268b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.media.MediaMuxer; 278b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport android.util.Log; 288b9de91b3c01664b24deda202acb5db8594db503ztenghui 298b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport com.android.gallery3d.common.ApiHelper; 30648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhuimport com.android.gallery3d.util.SaveVideoFileInfo; 3115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.coremedia.iso.IsoFile; 3215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.coremedia.iso.boxes.TimeToSampleBox; 3315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.googlecode.mp4parser.authoring.Movie; 3415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.googlecode.mp4parser.authoring.Track; 3515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; 3615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; 3715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport com.googlecode.mp4parser.authoring.tracks.CroppedTrack; 3815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 3915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.io.File; 408b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport java.io.FileNotFoundException; 4115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.io.FileOutputStream; 4215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.io.IOException; 4315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.io.RandomAccessFile; 448b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport java.nio.ByteBuffer; 4515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.nio.channels.FileChannel; 4615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.util.Arrays; 478b9de91b3c01664b24deda202acb5db8594db503ztenghuiimport java.util.HashMap; 4815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.util.LinkedList; 4915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhuimport java.util.List; 5015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 51648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhupublic class VideoUtils { 528b9de91b3c01664b24deda202acb5db8594db503ztenghui private static final String LOGTAG = "VideoUtils"; 538b9de91b3c01664b24deda202acb5db8594db503ztenghui private static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024; 548b9de91b3c01664b24deda202acb5db8594db503ztenghui 558b9de91b3c01664b24deda202acb5db8594db503ztenghui /** 568b9de91b3c01664b24deda202acb5db8594db503ztenghui * Remove the sound track. 578b9de91b3c01664b24deda202acb5db8594db503ztenghui */ 588b9de91b3c01664b24deda202acb5db8594db503ztenghui public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) 598b9de91b3c01664b24deda202acb5db8594db503ztenghui throws IOException { 608b9de91b3c01664b24deda202acb5db8594db503ztenghui if (ApiHelper.HAS_MEDIA_MUXER) { 618b9de91b3c01664b24deda202acb5db8594db503ztenghui genVideoUsingMuxer(filePath, dstFileInfo.mFile.getPath(), -1, -1, 628b9de91b3c01664b24deda202acb5db8594db503ztenghui false, true); 638b9de91b3c01664b24deda202acb5db8594db503ztenghui } else { 648b9de91b3c01664b24deda202acb5db8594db503ztenghui startMuteUsingMp4Parser(filePath, dstFileInfo); 658b9de91b3c01664b24deda202acb5db8594db503ztenghui } 668b9de91b3c01664b24deda202acb5db8594db503ztenghui } 6715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 688b9de91b3c01664b24deda202acb5db8594db503ztenghui /** 698b9de91b3c01664b24deda202acb5db8594db503ztenghui * Shortens/Crops tracks 708b9de91b3c01664b24deda202acb5db8594db503ztenghui */ 718b9de91b3c01664b24deda202acb5db8594db503ztenghui public static void startTrim(File src, File dst, int startMs, int endMs) 728b9de91b3c01664b24deda202acb5db8594db503ztenghui throws IOException { 738b9de91b3c01664b24deda202acb5db8594db503ztenghui if (ApiHelper.HAS_MEDIA_MUXER) { 748b9de91b3c01664b24deda202acb5db8594db503ztenghui genVideoUsingMuxer(src.getPath(), dst.getPath(), startMs, endMs, 758b9de91b3c01664b24deda202acb5db8594db503ztenghui true, true); 768b9de91b3c01664b24deda202acb5db8594db503ztenghui } else { 778b9de91b3c01664b24deda202acb5db8594db503ztenghui trimUsingMp4Parser(src, dst, startMs, endMs); 788b9de91b3c01664b24deda202acb5db8594db503ztenghui } 798b9de91b3c01664b24deda202acb5db8594db503ztenghui } 808b9de91b3c01664b24deda202acb5db8594db503ztenghui 818b9de91b3c01664b24deda202acb5db8594db503ztenghui private static void startMuteUsingMp4Parser(String filePath, 828b9de91b3c01664b24deda202acb5db8594db503ztenghui SaveVideoFileInfo dstFileInfo) throws FileNotFoundException, IOException { 83648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu File dst = dstFileInfo.mFile; 84648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu File src = new File(filePath); 85648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); 86648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu Movie movie = MovieCreator.build(randomAccessFile.getChannel()); 87648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 88648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu // remove all tracks we will create new tracks from the old 89648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu List<Track> tracks = movie.getTracks(); 90648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu movie.setTracks(new LinkedList<Track>()); 91648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 92648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu for (Track track : tracks) { 93648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu if (track.getHandler().equals("vide")) { 94648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu movie.addTrack(track); 95648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu } 96648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu } 97648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu writeMovieIntoFile(dst, movie); 98648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu randomAccessFile.close(); 99648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu } 100648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 101648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu private static void writeMovieIntoFile(File dst, Movie movie) 102648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu throws IOException { 103648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu if (!dst.exists()) { 104648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu dst.createNewFile(); 105648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu } 106648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 107648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu IsoFile out = new DefaultMp4Builder().build(movie); 108648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu FileOutputStream fos = new FileOutputStream(dst); 109648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu FileChannel fc = fos.getChannel(); 1108b9de91b3c01664b24deda202acb5db8594db503ztenghui out.getBox(fc); // This one build up the memory. 111648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 112648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu fc.close(); 113648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu fos.close(); 114648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu } 115648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu 116648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu /** 1178b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param srcPath the path of source video file. 1188b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param dstPath the path of destination video file. 1198b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param startMs starting time in milliseconds for trimming. Set to 1208b9de91b3c01664b24deda202acb5db8594db503ztenghui * negative if starting from beginning. 1218b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param endMs end time for trimming in milliseconds. Set to negative if 1228b9de91b3c01664b24deda202acb5db8594db503ztenghui * no trimming at the end. 1238b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param useAudio true if keep the audio track from the source. 1248b9de91b3c01664b24deda202acb5db8594db503ztenghui * @param useVideo true if keep the video track from the source. 1258b9de91b3c01664b24deda202acb5db8594db503ztenghui * @throws IOException 126648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu */ 1278b9de91b3c01664b24deda202acb5db8594db503ztenghui private static void genVideoUsingMuxer(String srcPath, String dstPath, 1288b9de91b3c01664b24deda202acb5db8594db503ztenghui int startMs, int endMs, boolean useAudio, boolean useVideo) 1298b9de91b3c01664b24deda202acb5db8594db503ztenghui throws IOException { 1308b9de91b3c01664b24deda202acb5db8594db503ztenghui // Set up MediaExtractor to read from the source. 1318b9de91b3c01664b24deda202acb5db8594db503ztenghui MediaExtractor extractor = new MediaExtractor(); 1328b9de91b3c01664b24deda202acb5db8594db503ztenghui extractor.setDataSource(srcPath); 1338b9de91b3c01664b24deda202acb5db8594db503ztenghui 1348b9de91b3c01664b24deda202acb5db8594db503ztenghui int trackCount = extractor.getTrackCount(); 1358b9de91b3c01664b24deda202acb5db8594db503ztenghui 1368b9de91b3c01664b24deda202acb5db8594db503ztenghui // Set up MediaMuxer for the destination. 1378b9de91b3c01664b24deda202acb5db8594db503ztenghui MediaMuxer muxer; 1388b9de91b3c01664b24deda202acb5db8594db503ztenghui muxer = new MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 1398b9de91b3c01664b24deda202acb5db8594db503ztenghui 1408b9de91b3c01664b24deda202acb5db8594db503ztenghui // Set up the tracks and retrieve the max buffer size for selected 1418b9de91b3c01664b24deda202acb5db8594db503ztenghui // tracks. 1428b9de91b3c01664b24deda202acb5db8594db503ztenghui HashMap<Integer, Integer> indexMap = new HashMap<Integer, 1438b9de91b3c01664b24deda202acb5db8594db503ztenghui Integer>(trackCount); 1448b9de91b3c01664b24deda202acb5db8594db503ztenghui int bufferSize = -1; 1458b9de91b3c01664b24deda202acb5db8594db503ztenghui for (int i = 0; i < trackCount; i++) { 1468b9de91b3c01664b24deda202acb5db8594db503ztenghui MediaFormat format = extractor.getTrackFormat(i); 1478b9de91b3c01664b24deda202acb5db8594db503ztenghui String mime = format.getString(MediaFormat.KEY_MIME); 1488b9de91b3c01664b24deda202acb5db8594db503ztenghui 1498b9de91b3c01664b24deda202acb5db8594db503ztenghui boolean selectCurrentTrack = false; 1508b9de91b3c01664b24deda202acb5db8594db503ztenghui 1518b9de91b3c01664b24deda202acb5db8594db503ztenghui if (mime.startsWith("audio/") && useAudio) { 1528b9de91b3c01664b24deda202acb5db8594db503ztenghui selectCurrentTrack = true; 1538b9de91b3c01664b24deda202acb5db8594db503ztenghui } else if (mime.startsWith("video/") && useVideo) { 1548b9de91b3c01664b24deda202acb5db8594db503ztenghui selectCurrentTrack = true; 1558b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1568b9de91b3c01664b24deda202acb5db8594db503ztenghui 1578b9de91b3c01664b24deda202acb5db8594db503ztenghui if (selectCurrentTrack) { 1588b9de91b3c01664b24deda202acb5db8594db503ztenghui extractor.selectTrack(i); 1598b9de91b3c01664b24deda202acb5db8594db503ztenghui int dstIndex = muxer.addTrack(format); 1608b9de91b3c01664b24deda202acb5db8594db503ztenghui indexMap.put(i, dstIndex); 1618b9de91b3c01664b24deda202acb5db8594db503ztenghui if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) { 1628b9de91b3c01664b24deda202acb5db8594db503ztenghui int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); 1638b9de91b3c01664b24deda202acb5db8594db503ztenghui bufferSize = newSize > bufferSize ? newSize : bufferSize; 1648b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1658b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1668b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1678b9de91b3c01664b24deda202acb5db8594db503ztenghui 1688b9de91b3c01664b24deda202acb5db8594db503ztenghui if (bufferSize < 0) { 1698b9de91b3c01664b24deda202acb5db8594db503ztenghui bufferSize = DEFAULT_BUFFER_SIZE; 1708b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1718b9de91b3c01664b24deda202acb5db8594db503ztenghui 1728b9de91b3c01664b24deda202acb5db8594db503ztenghui // Set up the orientation and starting time for extractor. 1738b9de91b3c01664b24deda202acb5db8594db503ztenghui MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever(); 1748b9de91b3c01664b24deda202acb5db8594db503ztenghui retrieverSrc.setDataSource(srcPath); 1758b9de91b3c01664b24deda202acb5db8594db503ztenghui String degreesString = retrieverSrc.extractMetadata( 1768b9de91b3c01664b24deda202acb5db8594db503ztenghui MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); 1778b9de91b3c01664b24deda202acb5db8594db503ztenghui if (degreesString != null) { 1788b9de91b3c01664b24deda202acb5db8594db503ztenghui int degrees = Integer.parseInt(degreesString); 1798b9de91b3c01664b24deda202acb5db8594db503ztenghui if (degrees >= 0) { 1808b9de91b3c01664b24deda202acb5db8594db503ztenghui muxer.setOrientationHint(degrees); 1818b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1828b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1838b9de91b3c01664b24deda202acb5db8594db503ztenghui 1848b9de91b3c01664b24deda202acb5db8594db503ztenghui if (startMs > 0) { 1858b9de91b3c01664b24deda202acb5db8594db503ztenghui extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC); 1868b9de91b3c01664b24deda202acb5db8594db503ztenghui } 1878b9de91b3c01664b24deda202acb5db8594db503ztenghui 1888b9de91b3c01664b24deda202acb5db8594db503ztenghui // Copy the samples from MediaExtractor to MediaMuxer. We will loop 1898b9de91b3c01664b24deda202acb5db8594db503ztenghui // for copying each sample and stop when we get to the end of the source 1908b9de91b3c01664b24deda202acb5db8594db503ztenghui // file or exceed the end time of the trimming. 1918b9de91b3c01664b24deda202acb5db8594db503ztenghui int offset = 0; 1928b9de91b3c01664b24deda202acb5db8594db503ztenghui int trackIndex = -1; 1938b9de91b3c01664b24deda202acb5db8594db503ztenghui ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize); 1948b9de91b3c01664b24deda202acb5db8594db503ztenghui BufferInfo bufferInfo = new BufferInfo(); 19571e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui try { 19671e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui muxer.start(); 19771e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui while (true) { 19871e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo.offset = offset; 19971e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo.size = extractor.readSampleData(dstBuf, offset); 20071e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui if (bufferInfo.size < 0) { 20171e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui Log.d(LOGTAG, "Saw input EOS."); 20271e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo.size = 0; 2038b9de91b3c01664b24deda202acb5db8594db503ztenghui break; 2048b9de91b3c01664b24deda202acb5db8594db503ztenghui } else { 20571e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo.presentationTimeUs = extractor.getSampleTime(); 20671e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui if (endMs > 0 && bufferInfo.presentationTimeUs > (endMs * 1000)) { 20771e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui Log.d(LOGTAG, "The current sample is over the trim end time."); 20871e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui break; 20971e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui } else { 21071e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo.flags = extractor.getSampleFlags(); 21171e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui trackIndex = extractor.getSampleTrackIndex(); 21271e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui 21371e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, 21471e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui bufferInfo); 21571e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui extractor.advance(); 21671e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui } 2178b9de91b3c01664b24deda202acb5db8594db503ztenghui } 2188b9de91b3c01664b24deda202acb5db8594db503ztenghui } 2198b9de91b3c01664b24deda202acb5db8594db503ztenghui 22071e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui muxer.stop(); 22171e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui } catch (IllegalStateException e) { 22271e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui // Swallow the exception due to malformed source. 22371e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui Log.w(LOGTAG, "The source video file is malformed"); 22471e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui } finally { 22571e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui muxer.release(); 22671e27598c6c7cbba1792fffceaf653946a3bd2e4ztenghui } 2278b9de91b3c01664b24deda202acb5db8594db503ztenghui return; 2288b9de91b3c01664b24deda202acb5db8594db503ztenghui } 2298b9de91b3c01664b24deda202acb5db8594db503ztenghui 2308b9de91b3c01664b24deda202acb5db8594db503ztenghui private static void trimUsingMp4Parser(File src, File dst, int startMs, int endMs) 2318b9de91b3c01664b24deda202acb5db8594db503ztenghui throws FileNotFoundException, IOException { 23215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); 23315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu Movie movie = MovieCreator.build(randomAccessFile.getChannel()); 23415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 23515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu // remove all tracks we will create new tracks from the old 23615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu List<Track> tracks = movie.getTracks(); 23715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu movie.setTracks(new LinkedList<Track>()); 23815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 2398b9de91b3c01664b24deda202acb5db8594db503ztenghui double startTime = startMs / 1000; 2408b9de91b3c01664b24deda202acb5db8594db503ztenghui double endTime = endMs / 1000; 24115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 24215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu boolean timeCorrected = false; 24315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 2448b9de91b3c01664b24deda202acb5db8594db503ztenghui // Here we try to find a track that has sync samples. Since we can only 2458b9de91b3c01664b24deda202acb5db8594db503ztenghui // start decoding at such a sample we SHOULD make sure that the start of 2468b9de91b3c01664b24deda202acb5db8594db503ztenghui // the new fragment is exactly such a frame. 24715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (Track track : tracks) { 24815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { 24915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (timeCorrected) { 2508b9de91b3c01664b24deda202acb5db8594db503ztenghui // This exception here could be a false positive in case we 2518b9de91b3c01664b24deda202acb5db8594db503ztenghui // have multiple tracks with sync samples at exactly the 2528b9de91b3c01664b24deda202acb5db8594db503ztenghui // same positions. E.g. a single movie containing multiple 2538b9de91b3c01664b24deda202acb5db8594db503ztenghui // qualities of the same video (Microsoft Smooth Streaming 2548b9de91b3c01664b24deda202acb5db8594db503ztenghui // file) 2558b9de91b3c01664b24deda202acb5db8594db503ztenghui throw new RuntimeException( 2568b9de91b3c01664b24deda202acb5db8594db503ztenghui "The startTime has already been corrected by" + 2578b9de91b3c01664b24deda202acb5db8594db503ztenghui " another track with SyncSample. Not Supported."); 25815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 25915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu startTime = correctTimeToSyncSample(track, startTime, false); 26015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu endTime = correctTimeToSyncSample(track, endTime, true); 26115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu timeCorrected = true; 26215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 26315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 26415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 26515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (Track track : tracks) { 26615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu long currentSample = 0; 26715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu double currentTime = 0; 26815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu long startSample = -1; 26915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu long endSample = -1; 27015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 27115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) { 27215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i); 27315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (int j = 0; j < entry.getCount(); j++) { 2748b9de91b3c01664b24deda202acb5db8594db503ztenghui // entry.getDelta() is the amount of time the current sample 2758b9de91b3c01664b24deda202acb5db8594db503ztenghui // covers. 27615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 27715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (currentTime <= startTime) { 27815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu // current sample is still before the new starttime 27915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu startSample = currentSample; 28015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 28115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (currentTime <= endTime) { 2828b9de91b3c01664b24deda202acb5db8594db503ztenghui // current sample is after the new start time and still 2838b9de91b3c01664b24deda202acb5db8594db503ztenghui // before the new endtime 28415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu endSample = currentSample; 28515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } else { 28615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu // current sample is after the end of the cropped video 28715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu break; 28815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 2898b9de91b3c01664b24deda202acb5db8594db503ztenghui currentTime += (double) entry.getDelta() 2908b9de91b3c01664b24deda202acb5db8594db503ztenghui / (double) track.getTrackMetaData().getTimescale(); 29115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu currentSample++; 29215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 29315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 29415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu movie.addTrack(new CroppedTrack(track, startSample, endSample)); 29515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 296648b339c74da2b863304ffc61c8528cc74c2afb3Teng-Hui Zhu writeMovieIntoFile(dst, movie); 29715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu randomAccessFile.close(); 29815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 29915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 3008b9de91b3c01664b24deda202acb5db8594db503ztenghui private static double correctTimeToSyncSample(Track track, double cutHere, 3018b9de91b3c01664b24deda202acb5db8594db503ztenghui boolean next) { 30215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu double[] timeOfSyncSamples = new double[track.getSyncSamples().length]; 30315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu long currentSample = 0; 30415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu double currentTime = 0; 30515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) { 30615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i); 30715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (int j = 0; j < entry.getCount(); j++) { 30815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) { 3098b9de91b3c01664b24deda202acb5db8594db503ztenghui // samples always start with 1 but we start with zero 3108b9de91b3c01664b24deda202acb5db8594db503ztenghui // therefore +1 3118b9de91b3c01664b24deda202acb5db8594db503ztenghui timeOfSyncSamples[Arrays.binarySearch( 3128b9de91b3c01664b24deda202acb5db8594db503ztenghui track.getSyncSamples(), currentSample + 1)] = currentTime; 31315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 3148b9de91b3c01664b24deda202acb5db8594db503ztenghui currentTime += (double) entry.getDelta() 3158b9de91b3c01664b24deda202acb5db8594db503ztenghui / (double) track.getTrackMetaData().getTimescale(); 31615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu currentSample++; 31715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 31815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 31915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu double previous = 0; 32015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu for (double timeOfSyncSample : timeOfSyncSamples) { 32115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (timeOfSyncSample > cutHere) { 32215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu if (next) { 32315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu return timeOfSyncSample; 32415ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } else { 32515ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu return previous; 32615ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 32715ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 32815ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu previous = timeOfSyncSample; 32915ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 33015ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu return timeOfSyncSamples[timeOfSyncSamples.length - 1]; 33115ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu } 33215ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu 33315ff1b1ca52bea348b8a490b5b5abe53fa43eaf2Teng-Hui Zhu} 334