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