1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //#define LOG_NDEBUG 0 18 #define LOG_TAG "muxer" 19 #include <utils/Log.h> 20 21 #include <binder/ProcessState.h> 22 #include <media/stagefright/foundation/ABuffer.h> 23 #include <media/stagefright/foundation/ADebug.h> 24 #include <media/stagefright/foundation/ALooper.h> 25 #include <media/stagefright/foundation/AMessage.h> 26 #include <media/stagefright/foundation/AString.h> 27 #include <media/stagefright/DataSource.h> 28 #include <media/stagefright/MediaCodec.h> 29 #include <media/stagefright/MediaDefs.h> 30 #include <media/stagefright/MediaMuxer.h> 31 #include <media/stagefright/MetaData.h> 32 #include <media/stagefright/NuMediaExtractor.h> 33 34 static void usage(const char *me) { 35 fprintf(stderr, "usage: %s [-a] [-v] [-s <trim start time>]" 36 " [-e <trim end time>] [-o <output file>]" 37 " <input video file>\n", me); 38 fprintf(stderr, " -h help\n"); 39 fprintf(stderr, " -a use audio\n"); 40 fprintf(stderr, " -v use video\n"); 41 fprintf(stderr, " -s Time in milli-seconds when the trim should start\n"); 42 fprintf(stderr, " -e Time in milli-seconds when the trim should end\n"); 43 fprintf(stderr, " -o output file name. Default is /sdcard/muxeroutput.mp4\n"); 44 45 exit(1); 46 } 47 48 using namespace android; 49 50 static int muxing( 51 const android::sp<android::ALooper> &looper, 52 const char *path, 53 bool useAudio, 54 bool useVideo, 55 const char *outputFileName, 56 bool enableTrim, 57 int trimStartTimeMs, 58 int trimEndTimeMs, 59 int rotationDegrees) { 60 sp<NuMediaExtractor> extractor = new NuMediaExtractor; 61 if (extractor->setDataSource(path) != OK) { 62 fprintf(stderr, "unable to instantiate extractor. %s\n", path); 63 return 1; 64 } 65 66 if (outputFileName == NULL) { 67 outputFileName = "/sdcard/muxeroutput.mp4"; 68 } 69 70 ALOGV("input file %s, output file %s", path, outputFileName); 71 ALOGV("useAudio %d, useVideo %d", useAudio, useVideo); 72 73 sp<MediaMuxer> muxer = new MediaMuxer(outputFileName, 74 MediaMuxer::OUTPUT_FORMAT_MPEG_4); 75 76 size_t trackCount = extractor->countTracks(); 77 // Map the extractor's track index to the muxer's track index. 78 KeyedVector<size_t, ssize_t> trackIndexMap; 79 size_t bufferSize = 1 * 1024 * 1024; // default buffer size is 1MB. 80 81 bool haveAudio = false; 82 bool haveVideo = false; 83 84 int64_t trimStartTimeUs = trimStartTimeMs * 1000; 85 int64_t trimEndTimeUs = trimEndTimeMs * 1000; 86 bool trimStarted = false; 87 int64_t trimOffsetTimeUs = 0; 88 89 for (size_t i = 0; i < trackCount; ++i) { 90 sp<AMessage> format; 91 status_t err = extractor->getTrackFormat(i, &format); 92 CHECK_EQ(err, (status_t)OK); 93 ALOGV("extractor getTrackFormat: %s", format->debugString().c_str()); 94 95 AString mime; 96 CHECK(format->findString("mime", &mime)); 97 98 bool isAudio = !strncasecmp(mime.c_str(), "audio/", 6); 99 bool isVideo = !strncasecmp(mime.c_str(), "video/", 6); 100 101 if (useAudio && !haveAudio && isAudio) { 102 haveAudio = true; 103 } else if (useVideo && !haveVideo && isVideo) { 104 haveVideo = true; 105 } else { 106 continue; 107 } 108 109 if (isVideo) { 110 int width , height; 111 CHECK(format->findInt32("width", &width)); 112 CHECK(format->findInt32("height", &height)); 113 bufferSize = width * height * 4; // Assuming it is maximally 4BPP 114 } 115 116 int64_t duration; 117 CHECK(format->findInt64("durationUs", &duration)); 118 119 // Since we got the duration now, correct the start time. 120 if (enableTrim) { 121 if (trimStartTimeUs > duration) { 122 fprintf(stderr, "Warning: trimStartTimeUs > duration," 123 " reset to 0\n"); 124 trimStartTimeUs = 0; 125 } 126 } 127 128 ALOGV("selecting track %d", i); 129 130 err = extractor->selectTrack(i); 131 CHECK_EQ(err, (status_t)OK); 132 133 ssize_t newTrackIndex = muxer->addTrack(format); 134 CHECK_GE(newTrackIndex, 0); 135 trackIndexMap.add(i, newTrackIndex); 136 } 137 138 int64_t muxerStartTimeUs = ALooper::GetNowUs(); 139 140 bool sawInputEOS = false; 141 142 size_t trackIndex = -1; 143 sp<ABuffer> newBuffer = new ABuffer(bufferSize); 144 145 muxer->setOrientationHint(rotationDegrees); 146 muxer->start(); 147 148 while (!sawInputEOS) { 149 status_t err = extractor->getSampleTrackIndex(&trackIndex); 150 if (err != OK) { 151 ALOGV("saw input eos, err %d", err); 152 sawInputEOS = true; 153 break; 154 } else { 155 err = extractor->readSampleData(newBuffer); 156 CHECK_EQ(err, (status_t)OK); 157 158 int64_t timeUs; 159 err = extractor->getSampleTime(&timeUs); 160 CHECK_EQ(err, (status_t)OK); 161 162 sp<MetaData> meta; 163 err = extractor->getSampleMeta(&meta); 164 CHECK_EQ(err, (status_t)OK); 165 166 uint32_t sampleFlags = 0; 167 int32_t val; 168 if (meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) { 169 // We only support BUFFER_FLAG_SYNCFRAME in the flag for now. 170 sampleFlags |= MediaCodec::BUFFER_FLAG_SYNCFRAME; 171 172 // We turn on trimming at the sync frame. 173 if (enableTrim && timeUs > trimStartTimeUs && 174 timeUs <= trimEndTimeUs) { 175 if (trimStarted == false) { 176 trimOffsetTimeUs = timeUs; 177 } 178 trimStarted = true; 179 } 180 } 181 // Trim can end at any non-sync frame. 182 if (enableTrim && timeUs > trimEndTimeUs) { 183 trimStarted = false; 184 } 185 186 if (!enableTrim || (enableTrim && trimStarted)) { 187 err = muxer->writeSampleData(newBuffer, 188 trackIndexMap.valueFor(trackIndex), 189 timeUs - trimOffsetTimeUs, sampleFlags); 190 } 191 192 extractor->advance(); 193 } 194 } 195 196 muxer->stop(); 197 newBuffer.clear(); 198 trackIndexMap.clear(); 199 200 int64_t elapsedTimeUs = ALooper::GetNowUs() - muxerStartTimeUs; 201 fprintf(stderr, "SUCCESS: muxer generate the video in %lld ms\n", 202 elapsedTimeUs / 1000); 203 204 return 0; 205 } 206 207 int main(int argc, char **argv) { 208 const char *me = argv[0]; 209 210 bool useAudio = false; 211 bool useVideo = false; 212 char *outputFileName = NULL; 213 int trimStartTimeMs = -1; 214 int trimEndTimeMs = -1; 215 int rotationDegrees = 0; 216 // When trimStartTimeMs and trimEndTimeMs seems valid, we turn this switch 217 // to true. 218 bool enableTrim = false; 219 220 int res; 221 while ((res = getopt(argc, argv, "h?avo:s:e:r:")) >= 0) { 222 switch (res) { 223 case 'a': 224 { 225 useAudio = true; 226 break; 227 } 228 229 case 'v': 230 { 231 useVideo = true; 232 break; 233 } 234 235 case 'o': 236 { 237 outputFileName = optarg; 238 break; 239 } 240 241 case 's': 242 { 243 trimStartTimeMs = atoi(optarg); 244 break; 245 } 246 247 case 'e': 248 { 249 trimEndTimeMs = atoi(optarg); 250 break; 251 } 252 253 case 'r': 254 { 255 rotationDegrees = atoi(optarg); 256 break; 257 } 258 259 case '?': 260 case 'h': 261 default: 262 { 263 usage(me); 264 } 265 } 266 } 267 268 argc -= optind; 269 argv += optind; 270 271 if (argc != 1) { 272 usage(me); 273 } 274 275 if (trimStartTimeMs < 0 || trimEndTimeMs < 0) { 276 // If no input on either 's' or 'e', or they are obviously wrong input, 277 // then turn off trimming. 278 ALOGV("Trimming is disabled, copying the whole length video."); 279 enableTrim = false; 280 } else if (trimStartTimeMs > trimEndTimeMs) { 281 fprintf(stderr, "ERROR: start time is bigger\n"); 282 return 1; 283 } else { 284 enableTrim = true; 285 } 286 287 if (!useAudio && !useVideo) { 288 fprintf(stderr, "ERROR: Missing both -a and -v, no track to mux then.\n"); 289 return 1; 290 } 291 ProcessState::self()->startThreadPool(); 292 293 // Make sure setDataSource() works. 294 DataSource::RegisterDefaultSniffers(); 295 296 sp<ALooper> looper = new ALooper; 297 looper->start(); 298 299 int result = muxing(looper, argv[0], useAudio, useVideo, outputFileName, 300 enableTrim, trimStartTimeMs, trimEndTimeMs, rotationDegrees); 301 302 looper->stop(); 303 304 return result; 305 } 306