Home | History | Annotate | Download | only in stagefright
      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