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