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