blob: cca33e0810c8c48749277fc5c1500516e7639d9b [file] [log] [blame]
ztenghui6df48bf2013-02-07 15:12:10 -08001/*
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
34static 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
48using namespace android;
49
50static 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,
ztenghuie0fb5282013-03-12 15:43:56 -070058 int trimEndTimeMs,
59 int rotationDegrees) {
ztenghui6df48bf2013-02-07 15:12:10 -080060 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
ztenghui3db62df2013-02-22 14:32:59 -080073 sp<MediaMuxer> muxer = new MediaMuxer(outputFileName,
74 MediaMuxer::OUTPUT_FORMAT_MPEG_4);
ztenghui6df48bf2013-02-07 15:12:10 -080075
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
ztenghuie0fb5282013-03-12 15:43:56 -0700145 muxer->setOrientationHint(rotationDegrees);
ztenghui6df48bf2013-02-07 15:12:10 -0800146 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
207int 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;
ztenghuie0fb5282013-03-12 15:43:56 -0700215 int rotationDegrees = 0;
ztenghui6df48bf2013-02-07 15:12:10 -0800216 // When trimStartTimeMs and trimEndTimeMs seems valid, we turn this switch
217 // to true.
218 bool enableTrim = false;
219
220 int res;
ztenghuie0fb5282013-03-12 15:43:56 -0700221 while ((res = getopt(argc, argv, "h?avo:s:e:r:")) >= 0) {
ztenghui6df48bf2013-02-07 15:12:10 -0800222 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
ztenghuie0fb5282013-03-12 15:43:56 -0700253 case 'r':
254 {
255 rotationDegrees = atoi(optarg);
256 break;
257 }
258
ztenghui6df48bf2013-02-07 15:12:10 -0800259 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,
ztenghuie0fb5282013-03-12 15:43:56 -0700300 enableTrim, trimStartTimeMs, trimEndTimeMs, rotationDegrees);
ztenghui6df48bf2013-02-07 15:12:10 -0800301
302 looper->stop();
303
304 return result;
305}