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