From 6df48bfe8cccdfec58f5f94be3cf3a2c64697e56 Mon Sep 17 00:00:00 2001 From: ztenghui Date: Thu, 7 Feb 2013 15:12:10 -0800 Subject: Add a test utility for MediaMuxer. This test/utility copy samples from one video and mux into another video. It support trimming, cutting audio or video track. It can run simply as command line like: adb shell muxer -a -v -s 1000 -e 8000 "/sdcard/DCIM/Camera/VID_*.mp4" bug:7991013 Change-Id: I8a2eeff3cabd001b6b2a7062d991dd076edbf22e --- cmds/stagefright/Android.mk | 23 ++++ cmds/stagefright/muxer.cpp | 295 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 cmds/stagefright/muxer.cpp (limited to 'cmds') diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index a59186a..d583e65 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -165,3 +165,26 @@ LOCAL_MODULE:= codec include $(BUILD_EXECUTABLE) +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + muxer.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright liblog libutils libbinder libstagefright_foundation \ + libmedia libgui libcutils libui libc + +LOCAL_C_INCLUDES:= \ + frameworks/av/media/libstagefright \ + $(TOP)/frameworks/native/include/media/openmax + +LOCAL_CFLAGS += -Wno-multichar + +LOCAL_MODULE_TAGS := debug + +LOCAL_MODULE:= muxer + +include $(BUILD_EXECUTABLE) + diff --git a/cmds/stagefright/muxer.cpp b/cmds/stagefright/muxer.cpp new file mode 100644 index 0000000..1b127c7 --- /dev/null +++ b/cmds/stagefright/muxer.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "muxer" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(const char *me) { + fprintf(stderr, "usage: %s [-a] [-v] [-s ]" + " [-e ] [-o ]" + " \n", me); + fprintf(stderr, " -h help\n"); + fprintf(stderr, " -a use audio\n"); + fprintf(stderr, " -v use video\n"); + fprintf(stderr, " -s Time in milli-seconds when the trim should start\n"); + fprintf(stderr, " -e Time in milli-seconds when the trim should end\n"); + fprintf(stderr, " -o output file name. Default is /sdcard/muxeroutput.mp4\n"); + + exit(1); +} + +using namespace android; + +static int muxing( + const android::sp &looper, + const char *path, + bool useAudio, + bool useVideo, + const char *outputFileName, + bool enableTrim, + int trimStartTimeMs, + int trimEndTimeMs) { + sp extractor = new NuMediaExtractor; + if (extractor->setDataSource(path) != OK) { + fprintf(stderr, "unable to instantiate extractor. %s\n", path); + return 1; + } + + if (outputFileName == NULL) { + outputFileName = "/sdcard/muxeroutput.mp4"; + } + + ALOGV("input file %s, output file %s", path, outputFileName); + ALOGV("useAudio %d, useVideo %d", useAudio, useVideo); + + sp muxer = new MediaMuxer(outputFileName); + + size_t trackCount = extractor->countTracks(); + // Map the extractor's track index to the muxer's track index. + KeyedVector trackIndexMap; + size_t bufferSize = 1 * 1024 * 1024; // default buffer size is 1MB. + + bool haveAudio = false; + bool haveVideo = false; + + int64_t trimStartTimeUs = trimStartTimeMs * 1000; + int64_t trimEndTimeUs = trimEndTimeMs * 1000; + bool trimStarted = false; + int64_t trimOffsetTimeUs = 0; + + for (size_t i = 0; i < trackCount; ++i) { + sp format; + status_t err = extractor->getTrackFormat(i, &format); + CHECK_EQ(err, (status_t)OK); + ALOGV("extractor getTrackFormat: %s", format->debugString().c_str()); + + AString mime; + CHECK(format->findString("mime", &mime)); + + bool isAudio = !strncasecmp(mime.c_str(), "audio/", 6); + bool isVideo = !strncasecmp(mime.c_str(), "video/", 6); + + if (useAudio && !haveAudio && isAudio) { + haveAudio = true; + } else if (useVideo && !haveVideo && isVideo) { + haveVideo = true; + } else { + continue; + } + + if (isVideo) { + int width , height; + CHECK(format->findInt32("width", &width)); + CHECK(format->findInt32("height", &height)); + bufferSize = width * height * 4; // Assuming it is maximally 4BPP + } + + int64_t duration; + CHECK(format->findInt64("durationUs", &duration)); + + // Since we got the duration now, correct the start time. + if (enableTrim) { + if (trimStartTimeUs > duration) { + fprintf(stderr, "Warning: trimStartTimeUs > duration," + " reset to 0\n"); + trimStartTimeUs = 0; + } + } + + ALOGV("selecting track %d", i); + + err = extractor->selectTrack(i); + CHECK_EQ(err, (status_t)OK); + + ssize_t newTrackIndex = muxer->addTrack(format); + CHECK_GE(newTrackIndex, 0); + trackIndexMap.add(i, newTrackIndex); + } + + int64_t muxerStartTimeUs = ALooper::GetNowUs(); + + bool sawInputEOS = false; + + size_t trackIndex = -1; + sp newBuffer = new ABuffer(bufferSize); + + muxer->start(); + + while (!sawInputEOS) { + status_t err = extractor->getSampleTrackIndex(&trackIndex); + if (err != OK) { + ALOGV("saw input eos, err %d", err); + sawInputEOS = true; + break; + } else { + err = extractor->readSampleData(newBuffer); + CHECK_EQ(err, (status_t)OK); + + int64_t timeUs; + err = extractor->getSampleTime(&timeUs); + CHECK_EQ(err, (status_t)OK); + + sp meta; + err = extractor->getSampleMeta(&meta); + CHECK_EQ(err, (status_t)OK); + + uint32_t sampleFlags = 0; + int32_t val; + if (meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) { + // We only support BUFFER_FLAG_SYNCFRAME in the flag for now. + sampleFlags |= MediaCodec::BUFFER_FLAG_SYNCFRAME; + + // We turn on trimming at the sync frame. + if (enableTrim && timeUs > trimStartTimeUs && + timeUs <= trimEndTimeUs) { + if (trimStarted == false) { + trimOffsetTimeUs = timeUs; + } + trimStarted = true; + } + } + // Trim can end at any non-sync frame. + if (enableTrim && timeUs > trimEndTimeUs) { + trimStarted = false; + } + + if (!enableTrim || (enableTrim && trimStarted)) { + err = muxer->writeSampleData(newBuffer, + trackIndexMap.valueFor(trackIndex), + timeUs - trimOffsetTimeUs, sampleFlags); + } + + extractor->advance(); + } + } + + muxer->stop(); + newBuffer.clear(); + trackIndexMap.clear(); + + int64_t elapsedTimeUs = ALooper::GetNowUs() - muxerStartTimeUs; + fprintf(stderr, "SUCCESS: muxer generate the video in %lld ms\n", + elapsedTimeUs / 1000); + + return 0; +} + +int main(int argc, char **argv) { + const char *me = argv[0]; + + bool useAudio = false; + bool useVideo = false; + char *outputFileName = NULL; + int trimStartTimeMs = -1; + int trimEndTimeMs = -1; + // When trimStartTimeMs and trimEndTimeMs seems valid, we turn this switch + // to true. + bool enableTrim = false; + + int res; + while ((res = getopt(argc, argv, "h?avo:s:e:")) >= 0) { + switch (res) { + case 'a': + { + useAudio = true; + break; + } + + case 'v': + { + useVideo = true; + break; + } + + case 'o': + { + outputFileName = optarg; + break; + } + + case 's': + { + trimStartTimeMs = atoi(optarg); + break; + } + + case 'e': + { + trimEndTimeMs = atoi(optarg); + break; + } + + case '?': + case 'h': + default: + { + usage(me); + } + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + usage(me); + } + + if (trimStartTimeMs < 0 || trimEndTimeMs < 0) { + // If no input on either 's' or 'e', or they are obviously wrong input, + // then turn off trimming. + ALOGV("Trimming is disabled, copying the whole length video."); + enableTrim = false; + } else if (trimStartTimeMs > trimEndTimeMs) { + fprintf(stderr, "ERROR: start time is bigger\n"); + return 1; + } else { + enableTrim = true; + } + + if (!useAudio && !useVideo) { + fprintf(stderr, "ERROR: Missing both -a and -v, no track to mux then.\n"); + return 1; + } + ProcessState::self()->startThreadPool(); + + // Make sure setDataSource() works. + DataSource::RegisterDefaultSniffers(); + + sp looper = new ALooper; + looper->start(); + + int result = muxing(looper, argv[0], useAudio, useVideo, outputFileName, + enableTrim, trimStartTimeMs, trimEndTimeMs); + + looper->stop(); + + return result; +} -- cgit v1.1