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 <inttypes.h> 20 #include <sys/types.h> 21 #include <sys/stat.h> 22 #include <fcntl.h> 23 #include <utils/Log.h> 24 25 #include <binder/ProcessState.h> 26 #include <media/IMediaHTTPService.h> 27 #include <media/stagefright/foundation/ABuffer.h> 28 #include <media/stagefright/foundation/ADebug.h> 29 #include <media/stagefright/foundation/ALooper.h> 30 #include <media/stagefright/foundation/AMessage.h> 31 #include <media/stagefright/foundation/AString.h> 32 #include <media/stagefright/MediaCodec.h> 33 #include <media/stagefright/MediaDefs.h> 34 #include <media/stagefright/MediaMuxer.h> 35 #include <media/stagefright/MetaData.h> 36 #include <media/stagefright/NuMediaExtractor.h> 37 38 static void usage(const char *me) { 39 fprintf(stderr, "usage: %s [-a] [-v] [-s <trim start time>]" 40 " [-e <trim end time>] [-o <output file>]" 41 " <input video file>\n", me); 42 fprintf(stderr, " -h help\n"); 43 fprintf(stderr, " -a use audio\n"); 44 fprintf(stderr, " -v use video\n"); 45 fprintf(stderr, " -w mux into WebM container (default is MP4)\n"); 46 fprintf(stderr, " -s Time in milli-seconds when the trim should start\n"); 47 fprintf(stderr, " -e Time in milli-seconds when the trim should end\n"); 48 fprintf(stderr, " -o output file name. Default is /sdcard/muxeroutput.mp4\n"); 49 50 exit(1); 51 } 52 53 using namespace android; 54 55 static int muxing( 56 const char *path, 57 bool useAudio, 58 bool useVideo, 59 const char *outputFileName, 60 bool enableTrim, 61 int trimStartTimeMs, 62 int trimEndTimeMs, 63 int rotationDegrees, 64 MediaMuxer::OutputFormat container = MediaMuxer::OUTPUT_FORMAT_MPEG_4) { 65 sp<NuMediaExtractor> extractor = new NuMediaExtractor; 66 if (extractor->setDataSource(NULL /* httpService */, path) != OK) { 67 fprintf(stderr, "unable to instantiate extractor. %s\n", path); 68 return 1; 69 } 70 71 if (outputFileName == NULL) { 72 outputFileName = "/sdcard/muxeroutput.mp4"; 73 } 74 75 ALOGV("input file %s, output file %s", path, outputFileName); 76 ALOGV("useAudio %d, useVideo %d", useAudio, useVideo); 77 78 int fd = open(outputFileName, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); 79 80 if (fd < 0) { 81 ALOGE("couldn't open file"); 82 return fd; 83 } 84 sp<MediaMuxer> muxer = new MediaMuxer(fd, container); 85 close(fd); 86 87 size_t trackCount = extractor->countTracks(); 88 // Map the extractor's track index to the muxer's track index. 89 KeyedVector<size_t, ssize_t> trackIndexMap; 90 size_t bufferSize = 1 * 1024 * 1024; // default buffer size is 1MB. 91 92 bool haveAudio = false; 93 bool haveVideo = false; 94 95 int64_t trimStartTimeUs = trimStartTimeMs * 1000; 96 int64_t trimEndTimeUs = trimEndTimeMs * 1000; 97 bool trimStarted = false; 98 int64_t trimOffsetTimeUs = 0; 99 100 for (size_t i = 0; i < trackCount; ++i) { 101 sp<AMessage> format; 102 status_t err = extractor->getTrackFormat(i, &format); 103 CHECK_EQ(err, (status_t)OK); 104 ALOGV("extractor getTrackFormat: %s", format->debugString().c_str()); 105 106 AString mime; 107 CHECK(format->findString("mime", &mime)); 108 109 bool isAudio = !strncasecmp(mime.c_str(), "audio/", 6); 110 bool isVideo = !strncasecmp(mime.c_str(), "video/", 6); 111 112 if (useAudio && !haveAudio && isAudio) { 113 haveAudio = true; 114 } else if (useVideo && !haveVideo && isVideo) { 115 haveVideo = true; 116 } else { 117 continue; 118 } 119 120 if (isVideo) { 121 int width , height; 122 CHECK(format->findInt32("width", &width)); 123 CHECK(format->findInt32("height", &height)); 124 bufferSize = width * height * 4; // Assuming it is maximally 4BPP 125 } 126 127 int64_t duration; 128 CHECK(format->findInt64("durationUs", &duration)); 129 130 // Since we got the duration now, correct the start time. 131 if (enableTrim) { 132 if (trimStartTimeUs > duration) { 133 fprintf(stderr, "Warning: trimStartTimeUs > duration," 134 " reset to 0\n"); 135 trimStartTimeUs = 0; 136 } 137 } 138 139 ALOGV("selecting track %zu", i); 140 141 err = extractor->selectTrack(i); 142 CHECK_EQ(err, (status_t)OK); 143 144 ssize_t newTrackIndex = muxer->addTrack(format); 145 if (newTrackIndex < 0) { 146 fprintf(stderr, "%s track (%zu) unsupported by muxer\n", 147 isAudio ? "audio" : "video", 148 i); 149 } else { 150 trackIndexMap.add(i, newTrackIndex); 151 } 152 } 153 154 int64_t muxerStartTimeUs = ALooper::GetNowUs(); 155 156 bool sawInputEOS = false; 157 158 size_t trackIndex = -1; 159 sp<ABuffer> newBuffer = new ABuffer(bufferSize); 160 161 muxer->setOrientationHint(rotationDegrees); 162 muxer->start(); 163 164 while (!sawInputEOS) { 165 status_t err = extractor->getSampleTrackIndex(&trackIndex); 166 if (err != OK) { 167 ALOGV("saw input eos, err %d", err); 168 sawInputEOS = true; 169 break; 170 } else if (trackIndexMap.indexOfKey(trackIndex) < 0) { 171 // ALOGV("skipping input from unsupported track %zu", trackIndex); 172 extractor->advance(); 173 continue; 174 } else { 175 // ALOGV("reading sample from track index %zu\n", trackIndex); 176 err = extractor->readSampleData(newBuffer); 177 CHECK_EQ(err, (status_t)OK); 178 179 int64_t timeUs; 180 err = extractor->getSampleTime(&timeUs); 181 CHECK_EQ(err, (status_t)OK); 182 183 sp<MetaData> meta; 184 err = extractor->getSampleMeta(&meta); 185 CHECK_EQ(err, (status_t)OK); 186 187 uint32_t sampleFlags = 0; 188 int32_t val; 189 if (meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) { 190 // We only support BUFFER_FLAG_SYNCFRAME in the flag for now. 191 sampleFlags |= MediaCodec::BUFFER_FLAG_SYNCFRAME; 192 193 // We turn on trimming at the sync frame. 194 if (enableTrim && timeUs > trimStartTimeUs && 195 timeUs <= trimEndTimeUs) { 196 if (trimStarted == false) { 197 trimOffsetTimeUs = timeUs; 198 } 199 trimStarted = true; 200 } 201 } 202 // Trim can end at any non-sync frame. 203 if (enableTrim && timeUs > trimEndTimeUs) { 204 trimStarted = false; 205 } 206 207 if (!enableTrim || (enableTrim && trimStarted)) { 208 err = muxer->writeSampleData(newBuffer, 209 trackIndexMap.valueFor(trackIndex), 210 timeUs - trimOffsetTimeUs, sampleFlags); 211 } 212 213 extractor->advance(); 214 } 215 } 216 217 muxer->stop(); 218 newBuffer.clear(); 219 trackIndexMap.clear(); 220 221 int64_t elapsedTimeUs = ALooper::GetNowUs() - muxerStartTimeUs; 222 fprintf(stderr, "SUCCESS: muxer generate the video in %" PRId64 " ms\n", 223 elapsedTimeUs / 1000); 224 225 return 0; 226 } 227 228 int main(int argc, char **argv) { 229 const char *me = argv[0]; 230 231 bool useAudio = false; 232 bool useVideo = false; 233 char *outputFileName = NULL; 234 int trimStartTimeMs = -1; 235 int trimEndTimeMs = -1; 236 int rotationDegrees = 0; 237 // When trimStartTimeMs and trimEndTimeMs seems valid, we turn this switch 238 // to true. 239 bool enableTrim = false; 240 MediaMuxer::OutputFormat container = MediaMuxer::OUTPUT_FORMAT_MPEG_4; 241 242 int res; 243 while ((res = getopt(argc, argv, "h?avo:s:e:r:w")) >= 0) { 244 switch (res) { 245 case 'a': 246 { 247 useAudio = true; 248 break; 249 } 250 251 case 'v': 252 { 253 useVideo = true; 254 break; 255 } 256 257 case 'w': 258 { 259 container = MediaMuxer::OUTPUT_FORMAT_WEBM; 260 break; 261 } 262 263 case 'o': 264 { 265 outputFileName = optarg; 266 break; 267 } 268 269 case 's': 270 { 271 trimStartTimeMs = atoi(optarg); 272 break; 273 } 274 275 case 'e': 276 { 277 trimEndTimeMs = atoi(optarg); 278 break; 279 } 280 281 case 'r': 282 { 283 rotationDegrees = atoi(optarg); 284 break; 285 } 286 287 case '?': 288 case 'h': 289 default: 290 { 291 usage(me); 292 } 293 } 294 } 295 296 argc -= optind; 297 argv += optind; 298 299 if (argc != 1) { 300 usage(me); 301 } 302 303 if (trimStartTimeMs < 0 || trimEndTimeMs < 0) { 304 // If no input on either 's' or 'e', or they are obviously wrong input, 305 // then turn off trimming. 306 ALOGV("Trimming is disabled, copying the whole length video."); 307 enableTrim = false; 308 } else if (trimStartTimeMs > trimEndTimeMs) { 309 fprintf(stderr, "ERROR: start time is bigger\n"); 310 return 1; 311 } else { 312 enableTrim = true; 313 } 314 315 if (!useAudio && !useVideo) { 316 fprintf(stderr, "ERROR: Missing both -a and -v, no track to mux then.\n"); 317 return 1; 318 } 319 ProcessState::self()->startThreadPool(); 320 321 sp<ALooper> looper = new ALooper; 322 looper->start(); 323 324 int result = muxing(argv[0], useAudio, useVideo, outputFileName, 325 enableTrim, trimStartTimeMs, trimEndTimeMs, rotationDegrees, container); 326 327 looper->stop(); 328 329 return result; 330 } 331