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 <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