| /* |
| * Copyright (C) 2019 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 "NativeMuxerTest" |
| #include <log/log.h> |
| |
| #include <NdkMediaExtractor.h> |
| #include <NdkMediaFormat.h> |
| #include <NdkMediaMuxer.h> |
| #include <fcntl.h> |
| #include <jni.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <dlfcn.h> |
| |
| #include <cmath> |
| #include <cstring> |
| #include <fstream> |
| #include <map> |
| #include <vector> |
| |
| #include "NativeMediaCommon.h" |
| |
| // TODO: replace __ANDROID_API_FUTURE__with 31 when it's official |
| #define __TRANSCODING_MIN_API__ __ANDROID_API_FUTURE__ |
| |
| /** |
| * MuxerNativeTestHelper breaks a media file to elements that a muxer can use to rebuild its clone. |
| * While testing muxer, if the test doesn't use MediaCodecs class to generate elementary stream, |
| * but uses MediaExtractor, this class will be handy |
| */ |
| class MuxerNativeTestHelper { |
| public: |
| explicit MuxerNativeTestHelper(const char* srcPath, const char* mime = nullptr, |
| int frameLimit = -1) |
| : mSrcPath(srcPath), mMime(mime), mTrackCount(0), mBuffer(nullptr) { |
| mFrameLimit = frameLimit < 0 ? INT_MAX : frameLimit; |
| splitMediaToMuxerParameters(); |
| } |
| |
| ~MuxerNativeTestHelper() { |
| for (auto format : mFormat) AMediaFormat_delete(format); |
| delete[] mBuffer; |
| for (const auto& buffInfoTrack : mBufferInfo) { |
| for (auto info : buffInfoTrack) delete info; |
| } |
| } |
| |
| int getTrackCount() { return mTrackCount; } |
| |
| size_t getSampleCount(size_t trackId) { |
| if (trackId >= mTrackCount) { |
| return 0; |
| } |
| return mBufferInfo[(mInpIndexMap.at(trackId))].size(); |
| } |
| |
| AMediaFormat* getTrackFormat(size_t trackId) { |
| if (trackId >= mTrackCount) { |
| return nullptr; |
| } |
| return mFormat[trackId]; |
| } |
| |
| bool registerTrack(AMediaMuxer* muxer); |
| |
| bool insertSampleData(AMediaMuxer* muxer); |
| |
| bool writeAFewSamplesData(AMediaMuxer* muxer, uint32_t fromIndex, uint32_t toIndex); |
| |
| bool writeAFewSamplesDataFromTime(AMediaMuxer* muxer, int64_t *fromTime, |
| uint32_t numSamples, bool lastSplit); |
| |
| bool muxMedia(AMediaMuxer* muxer); |
| |
| bool appendMedia(AMediaMuxer *muxer, uint32_t fromIndex, uint32_t toIndex); |
| |
| bool appendMediaFromTime(AMediaMuxer *muxer, int64_t *appendFromTime, |
| uint32_t numSamples, bool lastSplit); |
| |
| bool combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that, const int* repeater); |
| |
| bool isSubsetOf(MuxerNativeTestHelper* that); |
| |
| void offsetTimeStamp(int64_t tsAudioOffsetUs, int64_t tsVideoOffsetUs, int sampleOffset); |
| |
| private: |
| void splitMediaToMuxerParameters(); |
| |
| static const int STTS_TOLERANCE_US = 100; |
| const char* mSrcPath; |
| const char* mMime; |
| int mTrackCount; |
| std::vector<AMediaFormat*> mFormat; |
| uint8_t* mBuffer; |
| std::vector<std::vector<AMediaCodecBufferInfo*>> mBufferInfo; |
| std::map<int, int> mInpIndexMap; |
| std::vector<int> mTrackIdxOrder; |
| int mFrameLimit; |
| // combineMedias() uses local version of this variable |
| std::map<int, int> mOutIndexMap; |
| }; |
| |
| void MuxerNativeTestHelper::splitMediaToMuxerParameters() { |
| FILE* ifp = fopen(mSrcPath, "rbe"); |
| int fd; |
| int fileSize; |
| if (ifp) { |
| struct stat buf {}; |
| stat(mSrcPath, &buf); |
| fileSize = buf.st_size; |
| fd = fileno(ifp); |
| } else { |
| return; |
| } |
| AMediaExtractor* extractor = AMediaExtractor_new(); |
| if (extractor == nullptr) { |
| fclose(ifp); |
| return; |
| } |
| // Set up MediaExtractor to read from the source. |
| media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, fileSize); |
| if (AMEDIA_OK != status) { |
| AMediaExtractor_delete(extractor); |
| fclose(ifp); |
| return; |
| } |
| |
| // Set up MediaFormat |
| int index = 0; |
| for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) { |
| AMediaExtractor_selectTrack(extractor, trackID); |
| AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID); |
| if (mMime == nullptr) { |
| mTrackCount++; |
| mFormat.push_back(format); |
| mInpIndexMap[trackID] = index++; |
| } else { |
| const char* mime; |
| bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime); |
| if (hasKey && !strcmp(mime, mMime)) { |
| mTrackCount++; |
| mFormat.push_back(format); |
| mInpIndexMap[trackID] = index; |
| break; |
| } else { |
| AMediaFormat_delete(format); |
| AMediaExtractor_unselectTrack(extractor, trackID); |
| } |
| } |
| } |
| |
| if (mTrackCount <= 0) { |
| AMediaExtractor_delete(extractor); |
| fclose(ifp); |
| return; |
| } |
| |
| // Set up location for elementary stream |
| int bufferSize = ((fileSize + 127) >> 7) << 7; |
| // Ideally, Sum of return values of extractor.readSampleData(...) should not exceed |
| // source file size. But in case of Vorbis, aosp extractor appends an additional 4 bytes to |
| // the data at every readSampleData() call. bufferSize <<= 1 empirically large enough to |
| // hold the excess 4 bytes per read call |
| bufferSize <<= 1; |
| mBuffer = new uint8_t[bufferSize]; |
| if (mBuffer == nullptr) { |
| mTrackCount = 0; |
| AMediaExtractor_delete(extractor); |
| fclose(ifp); |
| return; |
| } |
| |
| // Let MediaExtractor do its thing |
| bool sawEOS = false; |
| int frameCount = 0; |
| int offset = 0; |
| mBufferInfo.resize(mTrackCount); |
| while (!sawEOS && frameCount < mFrameLimit) { |
| auto* bufferInfo = new AMediaCodecBufferInfo(); |
| bufferInfo->offset = offset; |
| bufferInfo->size = |
| AMediaExtractor_readSampleData(extractor, mBuffer + offset, (bufferSize - offset)); |
| if (bufferInfo->size < 0) { |
| sawEOS = true; |
| } else { |
| bufferInfo->presentationTimeUs = AMediaExtractor_getSampleTime(extractor); |
| bufferInfo->flags = AMediaExtractor_getSampleFlags(extractor); |
| int trackID = AMediaExtractor_getSampleTrackIndex(extractor); |
| mTrackIdxOrder.push_back(trackID); |
| mBufferInfo[(mInpIndexMap.at(trackID))].push_back(bufferInfo); |
| AMediaExtractor_advance(extractor); |
| frameCount++; |
| } |
| offset += bufferInfo->size; |
| } |
| ALOGV("frameCount:%d", frameCount); |
| AMediaExtractor_delete(extractor); |
| fclose(ifp); |
| } |
| |
| bool MuxerNativeTestHelper::registerTrack(AMediaMuxer* muxer) { |
| for (int trackID = 0; trackID < mTrackCount; trackID++) { |
| int dstIndex = AMediaMuxer_addTrack(muxer, mFormat[trackID]); |
| if (dstIndex < 0) return false; |
| mOutIndexMap[trackID] = dstIndex; |
| } |
| return true; |
| } |
| |
| bool MuxerNativeTestHelper::insertSampleData(AMediaMuxer* muxer) { |
| // write all registered tracks in interleaved order |
| int* frameCount = new int[mTrackCount]{0}; |
| for (int trackID : mTrackIdxOrder) { |
| int index = mInpIndexMap.at(trackID); |
| AMediaCodecBufferInfo* info = mBufferInfo[index][frameCount[index]]; |
| if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(index), mBuffer, info) != |
| AMEDIA_OK) { |
| delete[] frameCount; |
| return false; |
| } |
| ALOGV("Track: %d Timestamp: %" PRId64 "", trackID, info->presentationTimeUs); |
| frameCount[index]++; |
| } |
| delete[] frameCount; |
| ALOGV("Total track samples %zu", mTrackIdxOrder.size()); |
| return true; |
| } |
| |
| bool MuxerNativeTestHelper::writeAFewSamplesData(AMediaMuxer* muxer, uint32_t fromIndex, |
| uint32_t toIndex) { |
| ALOGV("fromIndex:%u, toIndex:%u", fromIndex, toIndex); |
| // write all registered tracks in interleaved order |
| ALOGV("mTrackIdxOrder.size:%zu", mTrackIdxOrder.size()); |
| if (fromIndex > toIndex || toIndex >= mTrackIdxOrder.size()) { |
| ALOGE("wrong index"); |
| return false; |
| } |
| int* frameCount = new int[mTrackCount]{0}; |
| for (int i = 0; i < fromIndex; ++i) { |
| ++frameCount[mInpIndexMap.at(mTrackIdxOrder[i])]; |
| } |
| ALOGV("Initial samples skipped:"); |
| for (int i = 0; i < mTrackCount; ++i) { |
| ALOGV("i:%d:%d", i, frameCount[i]); |
| } |
| for (int i = fromIndex; i <= toIndex; ++i) { |
| int trackID = mTrackIdxOrder[i]; |
| int trackIndex = mInpIndexMap.at(trackID); |
| ALOGV("trackID:%d, trackIndex:%d, frameCount:%d", trackID, trackIndex, |
| frameCount[trackIndex]); |
| AMediaCodecBufferInfo* info = mBufferInfo[trackIndex][frameCount[trackIndex]]; |
| ALOGV("Got info offset:%d, size:%d", info->offset, info->size); |
| if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(trackIndex), mBuffer, info) != |
| AMEDIA_OK) { |
| delete[] frameCount; |
| return false; |
| } |
| ALOGV("Track: %d Timestamp: %" PRId64 "", trackID, info->presentationTimeUs); |
| ++frameCount[trackIndex]; |
| } |
| ALOGV("Last sample counts:"); |
| for (int i = 0; i < mTrackCount; ++i) { |
| ALOGV("i:%d", frameCount[i]); |
| } |
| delete[] frameCount; |
| return true; |
| } |
| |
| bool MuxerNativeTestHelper::writeAFewSamplesDataFromTime(AMediaMuxer* muxer, int64_t *fromTime, |
| uint32_t numSamples, bool lastSplit) { |
| for(int tc = 0; tc < mTrackCount; ++tc) { |
| ALOGV("fromTime[%d]:%lld", tc, (long long)fromTime[tc]); |
| } |
| ALOGV("numSamples:%u", numSamples); |
| uint32_t samplesWritten = 0; |
| uint32_t i = 0; |
| int* frameCount = new int[mTrackCount]{0}; |
| do { |
| int trackID = mTrackIdxOrder[i++]; |
| int trackIndex = mInpIndexMap.at(trackID); |
| ALOGV("trackID:%d, trackIndex:%d, frameCount:%d", trackID, trackIndex, |
| frameCount[trackIndex]); |
| AMediaCodecBufferInfo* info = mBufferInfo[trackIndex][frameCount[trackIndex]]; |
| ++frameCount[trackIndex]; |
| ALOGV("Got info offset:%d, size:%d, PTS:%" PRId64 "", info->offset, info->size, |
| info->presentationTimeUs); |
| if (info->presentationTimeUs < fromTime[trackID]) { |
| ALOGV("skipped"); |
| continue; |
| } |
| if (AMediaMuxer_writeSampleData(muxer, mOutIndexMap.at(trackIndex), mBuffer, info) != |
| AMEDIA_OK) { |
| delete[] frameCount; |
| return false; |
| } else { |
| ++samplesWritten; |
| } |
| } while ((lastSplit) ? (i < mTrackIdxOrder.size()) : ((samplesWritten < numSamples) && |
| (i < mTrackIdxOrder.size()))); |
| ALOGV("samplesWritten:%u", samplesWritten); |
| |
| delete[] frameCount; |
| return true; |
| } |
| |
| |
| bool MuxerNativeTestHelper::muxMedia(AMediaMuxer* muxer) { |
| return (registerTrack(muxer) && (AMediaMuxer_start(muxer) == AMEDIA_OK) && |
| insertSampleData(muxer) && (AMediaMuxer_stop(muxer) == AMEDIA_OK)); |
| } |
| |
| bool MuxerNativeTestHelper::appendMedia(AMediaMuxer *muxer, uint32_t fromIndex, uint32_t toIndex) { |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| ALOGV("fromIndex:%u, toIndex:%u", fromIndex, toIndex); |
| if (fromIndex == 0) { |
| registerTrack(muxer); |
| } else { |
| size_t trackCount = AMediaMuxer_getTrackCount(muxer); |
| ALOGV("appendMedia:trackCount:%zu", trackCount); |
| for(size_t i = 0; i < trackCount; ++i) { |
| ALOGV("track i:%zu", i); |
| ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i))); |
| ALOGV("%s", AMediaFormat_toString(mFormat[i])); |
| for(size_t j = 0; j < mFormat.size(); ++j) { |
| const char* thatMime = nullptr; |
| AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i), |
| AMEDIAFORMAT_KEY_MIME, &thatMime); |
| const char* thisMime = nullptr; |
| AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMime); |
| ALOGV("strlen(thisMime)%zu", strlen(thisMime)); |
| if (strcmp(thatMime, thisMime) == 0) { |
| ALOGV("appendMedia:i:%zu, j:%zu", i, j); |
| mOutIndexMap[j]=i; |
| } |
| } |
| } |
| } |
| AMediaMuxer_start(muxer); |
| bool res = writeAFewSamplesData(muxer, fromIndex, toIndex); |
| AMediaMuxer_stop(muxer); |
| return res; |
| } else { |
| return false; |
| } |
| } |
| |
| bool MuxerNativeTestHelper::appendMediaFromTime(AMediaMuxer *muxer, int64_t *appendFromTime, |
| uint32_t numSamples, bool lastSplit) { |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| size_t trackCount = AMediaMuxer_getTrackCount(muxer); |
| ALOGV("appendMediaFromTime:trackCount:%zu", trackCount); |
| for(size_t i = 0; i < trackCount; ++i) { |
| ALOGV("track i:%zu", i); |
| ALOGV("%s", AMediaFormat_toString(AMediaMuxer_getTrackFormat(muxer, i))); |
| ALOGV("%s", AMediaFormat_toString(mFormat[i])); |
| for(size_t j = 0; j < mFormat.size(); ++j) { |
| const char* thatMime = nullptr; |
| AMediaFormat_getString(AMediaMuxer_getTrackFormat(muxer, i), |
| AMEDIAFORMAT_KEY_MIME, &thatMime); |
| const char* thisMime = nullptr; |
| AMediaFormat_getString(mFormat[j], AMEDIAFORMAT_KEY_MIME, &thisMime); |
| ALOGV("strlen(thisMime)%zu", strlen(thisMime)); |
| if (strcmp(thatMime, thisMime) == 0) { |
| ALOGV("appendMediaFromTime:i:%zu, j:%zu", i, j); |
| mOutIndexMap[j]=i; |
| } |
| } |
| } |
| AMediaMuxer_start(muxer); |
| bool res = writeAFewSamplesDataFromTime(muxer, appendFromTime, numSamples, lastSplit); |
| AMediaMuxer_stop(muxer); |
| return res; |
| } else { |
| return false; |
| } |
| } |
| bool MuxerNativeTestHelper::combineMedias(AMediaMuxer* muxer, MuxerNativeTestHelper* that, |
| const int* repeater) { |
| if (that == nullptr) return false; |
| if (repeater == nullptr) return false; |
| |
| // register tracks |
| int totalTracksToAdd = repeater[0] * this->mTrackCount + repeater[1] * that->mTrackCount; |
| int outIndexMap[totalTracksToAdd]; |
| MuxerNativeTestHelper* group[2]{this, that}; |
| for (int k = 0, idx = 0; k < 2; k++) { |
| for (int j = 0; j < repeater[k]; j++) { |
| for (AMediaFormat* format : group[k]->mFormat) { |
| int dstIndex = AMediaMuxer_addTrack(muxer, format); |
| if (dstIndex < 0) return false; |
| outIndexMap[idx++] = dstIndex; |
| } |
| } |
| } |
| // start |
| if (AMediaMuxer_start(muxer) != AMEDIA_OK) return false; |
| // write sample data |
| // write all registered tracks in planar order viz all samples of a track A then all |
| // samples of track B, ... |
| for (int k = 0, idx = 0; k < 2; k++) { |
| for (int j = 0; j < repeater[k]; j++) { |
| for (int i = 0; i < group[k]->mTrackCount; i++) { |
| for (int p = 0; p < group[k]->mBufferInfo[i].size(); p++) { |
| AMediaCodecBufferInfo* info = group[k]->mBufferInfo[i][p]; |
| if (AMediaMuxer_writeSampleData(muxer, outIndexMap[idx], group[k]->mBuffer, |
| info) != AMEDIA_OK) { |
| return false; |
| } |
| ALOGV("Track: %d Timestamp: %" PRId64 "", outIndexMap[idx], |
| info->presentationTimeUs); |
| } |
| idx++; |
| } |
| } |
| } |
| // stop |
| return (AMediaMuxer_stop(muxer) == AMEDIA_OK); |
| } |
| |
| // returns true if 'this' stream is a subset of 'that'. That is all tracks in current media |
| // stream are present in ref media stream |
| bool MuxerNativeTestHelper::isSubsetOf(MuxerNativeTestHelper* that) { |
| if (this == that) return true; |
| if (that == nullptr) return false; |
| |
| for (int i = 0; i < mTrackCount; i++) { |
| AMediaFormat* thisFormat = mFormat[i]; |
| const char* thisMime = nullptr; |
| AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMime); |
| int tolerance = !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0; |
| tolerance += 1; // rounding error |
| int j = 0; |
| for (; j < that->mTrackCount; j++) { |
| AMediaFormat* thatFormat = that->mFormat[j]; |
| const char* thatMime = nullptr; |
| AMediaFormat_getString(thatFormat, AMEDIAFORMAT_KEY_MIME, &thatMime); |
| if (thisMime != nullptr && thatMime != nullptr && !strcmp(thisMime, thatMime)) { |
| if (!isFormatSimilar(thisFormat, thatFormat)) continue; |
| if (mBufferInfo[i].size() <= that->mBufferInfo[j].size()) { |
| int tolerance = |
| !strncmp(thisMime, "video/", strlen("video/")) ? STTS_TOLERANCE_US : 0; |
| tolerance += 1; // rounding error |
| int k = 0; |
| for (; k < mBufferInfo[i].size(); k++) { |
| ALOGV("k:%d", k); |
| AMediaCodecBufferInfo* thisInfo = mBufferInfo[i][k]; |
| AMediaCodecBufferInfo* thatInfo = that->mBufferInfo[j][k]; |
| if (thisInfo->flags != thatInfo->flags) { |
| ALOGD("flags this:%u, that:%u", thisInfo->flags, thatInfo->flags); |
| break; |
| } |
| if (thisInfo->size != thatInfo->size) { |
| ALOGD("size this:%d, that:%d", thisInfo->size, thatInfo->size); |
| break; |
| } else if (memcmp(mBuffer + thisInfo->offset, |
| that->mBuffer + thatInfo->offset, thisInfo->size)) { |
| ALOGD("memcmp failed"); |
| break; |
| } |
| if (abs(thisInfo->presentationTimeUs - thatInfo->presentationTimeUs) > |
| tolerance) { |
| ALOGD("time this:%lld, that:%lld", |
| (long long)thisInfo->presentationTimeUs, |
| (long long)thatInfo->presentationTimeUs); |
| break; |
| } |
| } |
| if (k == mBufferInfo[i].size()) break; |
| } |
| } |
| } |
| if (j == that->mTrackCount) { |
| AMediaFormat_getString(thisFormat, AMEDIAFORMAT_KEY_MIME, &thisMime); |
| ALOGV("For mime %s, Couldn't find a match", thisMime); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void MuxerNativeTestHelper::offsetTimeStamp(int64_t tsAudioOffsetUs, int64_t tsVideoOffsetUs, |
| int sampleOffset) { |
| // offset pts of samples from index sampleOffset till the end by tsOffset for each audio and |
| // video track |
| for (int trackID = 0; trackID < mTrackCount; trackID++) { |
| int64_t tsOffsetUs = 0; |
| const char* thisMime = nullptr; |
| AMediaFormat_getString(mFormat[trackID], AMEDIAFORMAT_KEY_MIME, &thisMime); |
| if (thisMime != nullptr) { |
| if (strncmp(thisMime, "video/", strlen("video/")) == 0) { |
| tsOffsetUs = tsVideoOffsetUs; |
| } else if (strncmp(thisMime, "audio/", strlen("audio/")) == 0) { |
| tsOffsetUs = tsAudioOffsetUs; |
| } |
| for (int i = sampleOffset; i < mBufferInfo[trackID].size(); i++) { |
| AMediaCodecBufferInfo *info = mBufferInfo[trackID][i]; |
| info->presentationTimeUs += tsOffsetUs; |
| } |
| } |
| } |
| } |
| |
| static bool isCodecContainerPairValid(MuxerFormat format, const char* mime) { |
| static const std::map<MuxerFormat, std::vector<const char*>> codecListforType = { |
| {OUTPUT_FORMAT_MPEG_4, |
| {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC, |
| AMEDIA_MIMETYPE_VIDEO_HEVC, AMEDIA_MIMETYPE_AUDIO_AAC}}, |
| {OUTPUT_FORMAT_WEBM, |
| {AMEDIA_MIMETYPE_VIDEO_VP8, AMEDIA_MIMETYPE_VIDEO_VP9, AMEDIA_MIMETYPE_AUDIO_VORBIS, |
| AMEDIA_MIMETYPE_AUDIO_OPUS}}, |
| {OUTPUT_FORMAT_THREE_GPP, |
| {AMEDIA_MIMETYPE_VIDEO_MPEG4, AMEDIA_MIMETYPE_VIDEO_H263, AMEDIA_MIMETYPE_VIDEO_AVC, |
| AMEDIA_MIMETYPE_AUDIO_AAC, AMEDIA_MIMETYPE_AUDIO_AMR_NB, |
| AMEDIA_MIMETYPE_AUDIO_AMR_WB}}, |
| {OUTPUT_FORMAT_OGG, {AMEDIA_MIMETYPE_AUDIO_OPUS}}, |
| }; |
| |
| if (format == OUTPUT_FORMAT_MPEG_4 && |
| strncmp(mime, "application/", strlen("application/")) == 0) |
| return true; |
| |
| auto it = codecListforType.find(format); |
| if (it != codecListforType.end()) |
| for (auto it2 : it->second) |
| if (strcmp(it2, mime) == 0) return true; |
| |
| return false; |
| } |
| |
| static jboolean nativeTestSetLocation(JNIEnv* env, jobject, jint jformat, jstring jsrcPath, |
| jstring jdstPath) { |
| bool isPass = true; |
| bool isGeoDataSupported; |
| const float atlanticLat = 14.59f; |
| const float atlanticLong = 28.67f; |
| const float tooFarNorth = 90.5f; |
| const float tooFarWest = -180.5f; |
| const float tooFarSouth = -90.5f; |
| const float tooFarEast = 180.5f; |
| const float annapurnaLat = 28.59f; |
| const float annapurnaLong = 83.82f; |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| FILE* ofp = fopen(cdstPath, "wbe+"); |
| if (ofp) { |
| AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat); |
| media_status_t status = AMediaMuxer_setLocation(muxer, tooFarNorth, atlanticLong); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, atlanticLong); |
| } |
| status = AMediaMuxer_setLocation(muxer, tooFarSouth, atlanticLong); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarSouth, atlanticLong); |
| } |
| status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarWest); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarWest); |
| } |
| status = AMediaMuxer_setLocation(muxer, atlanticLat, tooFarEast); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds on bad args: (%f, %f)", atlanticLat, tooFarEast); |
| } |
| status = AMediaMuxer_setLocation(muxer, tooFarNorth, tooFarWest); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds on bad args: (%f, %f)", tooFarNorth, tooFarWest); |
| } |
| status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong); |
| isGeoDataSupported = (status == AMEDIA_OK); |
| if (isGeoDataSupported) { |
| status = AMediaMuxer_setLocation(muxer, annapurnaLat, annapurnaLong); |
| if (status != AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation fails on args: (%f, %f)", annapurnaLat, annapurnaLong); |
| } |
| } else { |
| isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 && |
| (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP); |
| } |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) { |
| status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds after starting the muxer"); |
| } |
| if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) { |
| status = AMediaMuxer_setLocation(muxer, atlanticLat, atlanticLong); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setLocation succeeds after stopping the muxer"); |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to writeSampleData or stop muxer"); |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to addTrack or start muxer"); |
| } |
| delete mediaInfo; |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestSetOrientationHint(JNIEnv* env, jobject, jint jformat, jstring jsrcPath, |
| jstring jdstPath) { |
| bool isPass = true; |
| bool isOrientationSupported; |
| const int badRotation[] = {360, 45, -90}; |
| const int oldRotation = 90; |
| const int currRotation = 180; |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| FILE* ofp = fopen(cdstPath, "wbe+"); |
| if (ofp) { |
| AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat); |
| media_status_t status; |
| for (int degrees : badRotation) { |
| status = AMediaMuxer_setOrientationHint(muxer, degrees); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setOrientationHint succeeds on bad args: %d", degrees); |
| } |
| } |
| status = AMediaMuxer_setOrientationHint(muxer, oldRotation); |
| isOrientationSupported = (status == AMEDIA_OK); |
| if (isOrientationSupported) { |
| status = AMediaMuxer_setOrientationHint(muxer, currRotation); |
| if (status != AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setOrientationHint fails on args: %d", currRotation); |
| } |
| } else { |
| isPass &= ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4 && |
| (MuxerFormat)jformat != OUTPUT_FORMAT_THREE_GPP); |
| } |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| if (mediaInfo->registerTrack(muxer) && AMediaMuxer_start(muxer) == AMEDIA_OK) { |
| status = AMediaMuxer_setOrientationHint(muxer, currRotation); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setOrientationHint succeeds after starting the muxer"); |
| } |
| if (mediaInfo->insertSampleData(muxer) && AMediaMuxer_stop(muxer) == AMEDIA_OK) { |
| status = AMediaMuxer_setOrientationHint(muxer, currRotation); |
| if (status == AMEDIA_OK) { |
| isPass = false; |
| ALOGE("setOrientationHint succeeds after stopping the muxer"); |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to writeSampleData or stop muxer"); |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to addTrack or start muxer"); |
| } |
| delete mediaInfo; |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestMultiTrack(JNIEnv* env, jobject, jint jformat, jstring jsrcPathA, |
| jstring jsrcPathB, jstring jrefPath, jstring jdstPath) { |
| bool isPass = true; |
| const char* csrcPathA = env->GetStringUTFChars(jsrcPathA, nullptr); |
| const char* csrcPathB = env->GetStringUTFChars(jsrcPathB, nullptr); |
| auto* mediaInfoA = new MuxerNativeTestHelper(csrcPathA); |
| auto* mediaInfoB = new MuxerNativeTestHelper(csrcPathB); |
| if (mediaInfoA->getTrackCount() == 1 && mediaInfoB->getTrackCount() == 1) { |
| const char* crefPath = env->GetStringUTFChars(jrefPath, nullptr); |
| // number of times to repeat {mSrcFileA, mSrcFileB} in Output |
| // values should be in sync with testMultiTrack |
| static const int numTracks[][2] = {{1, 1}, {2, 0}, {0, 2}, {1, 2}, {2, 1}, {2, 2}}; |
| // prepare reference |
| FILE* rfp = fopen(crefPath, "wbe+"); |
| if (rfp) { |
| AMediaMuxer* muxer = AMediaMuxer_new(fileno(rfp), (OutputFormat)jformat); |
| bool muxStatus = mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[0]); |
| AMediaMuxer_delete(muxer); |
| fclose(rfp); |
| if (muxStatus) { |
| auto* refInfo = new MuxerNativeTestHelper(crefPath); |
| if (!mediaInfoA->isSubsetOf(refInfo) || !mediaInfoB->isSubsetOf(refInfo)) { |
| isPass = false; |
| ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B " |
| "failed", csrcPathA, csrcPathB, jformat); |
| } else { |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| for (int i = 1; i < sizeof(numTracks) / sizeof(numTracks[0]) && isPass; i++) { |
| FILE* ofp = fopen(cdstPath, "wbe+"); |
| if (ofp) { |
| muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat); |
| bool status = |
| mediaInfoA->combineMedias(muxer, mediaInfoB, numTracks[i]); |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| if (status) { |
| auto* dstInfo = new MuxerNativeTestHelper(cdstPath); |
| if (!dstInfo->isSubsetOf(refInfo)) { |
| isPass = false; |
| ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing " |
| "src A: %d, src B: %d failed", csrcPathA, csrcPathB, |
| jformat, numTracks[i][0], numTracks[i][1]); |
| } |
| delete dstInfo; |
| } else { |
| if ((MuxerFormat)jformat != OUTPUT_FORMAT_MPEG_4) { |
| isPass = false; |
| ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing " |
| "src A: %d, src B: %d failed", csrcPathA, csrcPathB, |
| jformat, numTracks[i][0], numTracks[i][1]); |
| } |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| } |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| } |
| delete refInfo; |
| } else { |
| if ((MuxerFormat)jformat != OUTPUT_FORMAT_OGG) { |
| isPass = false; |
| ALOGE("testMultiTrack: inputs: %s %s, fmt: %d, error ! muxing src A and src B " |
| "failed", csrcPathA, csrcPathB, jformat); |
| } |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open reference output file %s", crefPath); |
| } |
| env->ReleaseStringUTFChars(jrefPath, crefPath); |
| } else { |
| isPass = false; |
| if (mediaInfoA->getTrackCount() != 1) { |
| ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathA, 1, |
| mediaInfoA->getTrackCount()); |
| } |
| if (mediaInfoB->getTrackCount() != 1) { |
| ALOGE("error: file %s, track count exp/rec - %d/%d", csrcPathB, 1, |
| mediaInfoB->getTrackCount()); |
| } |
| } |
| env->ReleaseStringUTFChars(jsrcPathA, csrcPathA); |
| env->ReleaseStringUTFChars(jsrcPathB, csrcPathB); |
| delete mediaInfoA; |
| delete mediaInfoB; |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestOffsetPts(JNIEnv* env, jobject, jint format, jstring jsrcPath, |
| jstring jdstPath, jintArray joffsetIndices) { |
| bool isPass = true; |
| // values should be in sync with testOffsetPresentationTime |
| static const int64_t OFFSET_TS_AUDIO_US[4] = {-23220LL, 0LL, 200000LL, 400000LL}; |
| static const int64_t OFFSET_TS_VIDEO_US[3] = {0LL, 200000LL, 400000LL}; |
| jsize len = env->GetArrayLength(joffsetIndices); |
| jint* coffsetIndices = env->GetIntArrayElements(joffsetIndices, nullptr); |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| if (mediaInfo->getTrackCount() != 0) { |
| for (int64_t audioOffsetUs : OFFSET_TS_AUDIO_US) { |
| for (int64_t videoOffsetUs : OFFSET_TS_VIDEO_US) { |
| for (int i = 0; i < len; i++) { |
| mediaInfo->offsetTimeStamp(audioOffsetUs, videoOffsetUs, coffsetIndices[i]); |
| } |
| FILE *ofp = fopen(cdstPath, "wbe+"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat) format); |
| mediaInfo->muxMedia(muxer); |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| auto *outInfo = new MuxerNativeTestHelper(cdstPath); |
| isPass = mediaInfo->isSubsetOf(outInfo); |
| if (!isPass) { |
| ALOGE("Validation failed after adding timestamp offsets audio: %lld," |
| " video: %lld", (long long) audioOffsetUs, (long long) videoOffsetUs); |
| } |
| delete outInfo; |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| for (int i = len - 1; i >= 0; i--) { |
| mediaInfo->offsetTimeStamp(-audioOffsetUs, -videoOffsetUs, coffsetIndices[i]); |
| } |
| } |
| } |
| } else { |
| isPass = false; |
| ALOGE("no valid track found in input file %s", csrcPath); |
| } |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| env->ReleaseIntArrayElements(joffsetIndices, coffsetIndices, 0); |
| delete mediaInfo; |
| return static_cast<jboolean>(isPass); |
| } |
| |
| static jboolean nativeTestSimpleMux(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath, |
| jstring jmime, jstring jselector) { |
| bool isPass = true; |
| const char* cmime = env->GetStringUTFChars(jmime, nullptr); |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cselector = env->GetStringUTFChars(jselector, nullptr); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath, cmime); |
| static const std::map<MuxerFormat, const char*> formatStringPair = { |
| {OUTPUT_FORMAT_MPEG_4, "mp4"}, |
| {OUTPUT_FORMAT_WEBM, "webm"}, |
| {OUTPUT_FORMAT_THREE_GPP, "3gp"}, |
| {OUTPUT_FORMAT_HEIF, "heif"}, |
| {OUTPUT_FORMAT_OGG, "ogg"}}; |
| if (mediaInfo->getTrackCount() == 1) { |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| for (int fmt = OUTPUT_FORMAT_START; fmt <= OUTPUT_FORMAT_LIST_END && isPass; fmt++) { |
| auto it = formatStringPair.find((MuxerFormat)fmt); |
| if (it == formatStringPair.end() || strstr(cselector, it->second) == nullptr) { |
| continue; |
| } |
| if (fmt == OUTPUT_FORMAT_WEBM) continue; // TODO(b/146923551) |
| FILE* ofp = fopen(cdstPath, "wbe+"); |
| if (ofp) { |
| AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)fmt); |
| bool muxStatus = mediaInfo->muxMedia(muxer); |
| bool result = true; |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| if (muxStatus) { |
| auto* outInfo = new MuxerNativeTestHelper(cdstPath, cmime); |
| result = mediaInfo->isSubsetOf(outInfo); |
| delete outInfo; |
| } |
| if ((muxStatus && !result) || |
| (!muxStatus && isCodecContainerPairValid((MuxerFormat)fmt, cmime))) { |
| isPass = false; |
| ALOGE("error: file %s, mime %s, output != clone(input) for format %d", csrcPath, |
| cmime, fmt); |
| } |
| } else { |
| isPass = false; |
| ALOGE("error: file %s, mime %s, failed to open output file %s", csrcPath, cmime, |
| cdstPath); |
| } |
| } |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| } else { |
| isPass = false; |
| ALOGE("error: file %s, mime %s, track count exp/rec - %d/%d", csrcPath, cmime, 1, |
| mediaInfo->getTrackCount()); |
| } |
| env->ReleaseStringUTFChars(jselector, cselector); |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| env->ReleaseStringUTFChars(jmime, cmime); |
| delete mediaInfo; |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* Check whether AMediaMuxer_getTrackCount works as expected. |
| */ |
| static jboolean nativeTestGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath, |
| jint jformat, jint jtrackCount) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| FILE* ofp = fopen(cdstPath, "w+"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)jformat); |
| if (muxer) { |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| if (!mediaInfo->registerTrack(muxer)) { |
| isPass = false; |
| ALOGE("register track failed"); |
| } |
| if (AMediaMuxer_getTrackCount(muxer) != jtrackCount) { |
| isPass = false; |
| ALOGE("track counts are not equal"); |
| } |
| delete mediaInfo; |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("file open error: file %s", csrcPath); |
| } |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* Check whether AMediaMuxer_getTrackCount works as expected when the file is opened in |
| * append mode. |
| */ |
| static jboolean nativeTestAppendGetTrackCount(JNIEnv* env, jobject, jstring jsrcPath, |
| jint jtrackCount) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP; |
| mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) { |
| ALOGV("mode:%u", mode); |
| FILE* ofp = fopen(csrcPath, "r"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode); |
| if (muxer) { |
| ssize_t trackCount = AMediaMuxer_getTrackCount(muxer); |
| if ( trackCount != jtrackCount) { |
| isPass = false; |
| ALOGE("trackcounts are not equal, trackCount:%ld vs jtrackCount:%d", |
| (long)trackCount, jtrackCount); |
| } |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| } else { |
| isPass = false; |
| ALOGE("file open error: file %s", csrcPath); |
| } |
| } |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* Checks whether AMediaMuxer_getTrackFormat works as expected in muxer mode. |
| */ |
| static jboolean nativeTestGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath, jstring jdstPath, |
| jint joutFormat) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| FILE* ofp = fopen(cdstPath, "w+"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat); |
| if (muxer) { |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| if (!mediaInfo->registerTrack(muxer)) { |
| isPass = false; |
| ALOGE("register track failed"); |
| } |
| for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) { |
| if (!isFormatSimilar(mediaInfo->getTrackFormat(i), |
| AMediaMuxer_getTrackFormat(muxer, i))) { |
| isPass = false; |
| ALOGE("track formats are not similar"); |
| } |
| } |
| delete mediaInfo; |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("file open error: file %s", csrcPath); |
| } |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* Checks whether AMediaMuxer_getTrackFormat works as expected when the file is opened in |
| * append mode. |
| */ |
| static jboolean nativeTestAppendGetTrackFormat(JNIEnv* env, jobject, jstring jsrcPath) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP; |
| mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) { |
| ALOGV("mode:%u", mode); |
| FILE* ofp = fopen(csrcPath, "r"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode); |
| if (muxer) { |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) { |
| if (!isFormatSimilar(mediaInfo->getTrackFormat(i), |
| AMediaMuxer_getTrackFormat(muxer, i))) { |
| isPass = false; |
| ALOGE("track formats are not similar"); |
| } |
| } |
| delete mediaInfo; |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("file open error: file %s", csrcPath); |
| } |
| } |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| } |
| else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* |
| * Checks if appending media data to the end of existing media data in a file works good. |
| * Mode : AMEDIAMUXER_APPEND_TO_EXISTING_DATA. Splits the contents of source file equally |
| * starting from one and increasing the number of splits by one for every iteration. Starts |
| * with writing first split into a new file and appends the rest of the contents split by split. |
| */ |
| static jboolean nativeTestSimpleAppend(JNIEnv* env, jobject, jint joutFormat, jstring jsrcPath, |
| jstring jdstPath) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| ALOGV("csrcPath:%s", csrcPath); |
| ALOGV("cdstPath:%s", cdstPath); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| for (int numSplits = 1; numSplits <= 5; ++numSplits) { |
| ALOGV("numSplits:%d", numSplits); |
| size_t totalSampleCount = 0; |
| AMediaMuxer *muxer = nullptr; |
| // Start by writing first split into a new file. |
| FILE* ofp = fopen(cdstPath, "w+"); |
| if (ofp) { |
| muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat); |
| if (muxer) { |
| for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) { |
| ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i)); |
| totalSampleCount += mediaInfo->getSampleCount(i); |
| } |
| mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1); |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| // Check if the contents in the new file is as same as in the source file. |
| if (isPass) { |
| auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr); |
| isPass = mediaInfoDest->isSubsetOf(mediaInfo); |
| delete mediaInfoDest; |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| |
| // Append rest of the contents from the source file to the new file split by split. |
| int curSplit = 1; |
| while (curSplit < numSplits && isPass) { |
| ofp = fopen(cdstPath, "r+"); |
| if (ofp) { |
| muxer = AMediaMuxer_append(fileno(ofp), AMEDIAMUXER_APPEND_TO_EXISTING_DATA); |
| if (muxer) { |
| ssize_t trackCount = AMediaMuxer_getTrackCount(muxer); |
| if (trackCount > 0) { |
| decltype(trackCount) tc = 0; |
| while(tc < trackCount) { |
| AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc); |
| int64_t val = 0; |
| if (AMediaFormat_getInt64(format, |
| AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) { |
| ALOGV("sample-time-before-append:%lld", (long long)val); |
| } |
| ++tc; |
| } |
| mediaInfo->appendMedia(muxer, totalSampleCount*curSplit/numSplits, |
| totalSampleCount*(curSplit+1)/numSplits-1); |
| } else { |
| isPass = false; |
| ALOGE("no tracks in the file"); |
| } |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| if (isPass) { |
| auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr); |
| isPass = mediaInfoDest->isSubsetOf(mediaInfo); |
| delete mediaInfoDest; |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| ++curSplit; |
| } |
| } |
| delete mediaInfo; |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* Checks if opening a file to append data and closing it without actually appending data |
| * works good in all append modes. |
| */ |
| static jboolean nativeTestNoSamples(JNIEnv* env, jobject, jint joutFormat, jstring jinPath, |
| jstring joutPath) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* cinPath = env->GetStringUTFChars(jinPath, nullptr); |
| const char* coutPath = env->GetStringUTFChars(joutPath, nullptr); |
| ALOGV("cinPath:%s", cinPath); |
| ALOGV("coutPath:%s", coutPath); |
| auto* mediaInfo = new MuxerNativeTestHelper(cinPath); |
| for (unsigned int mode = AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP; |
| mode <= AMEDIAMUXER_APPEND_TO_EXISTING_DATA; ++mode) { |
| if (mediaInfo->getTrackCount() != 0) { |
| // Create a new file and write media data to it. |
| FILE *ofp = fopen(coutPath, "wbe+"); |
| if (ofp) { |
| AMediaMuxer *muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat) joutFormat); |
| mediaInfo->muxMedia(muxer); |
| AMediaMuxer_delete(muxer); |
| fclose(ofp); |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", coutPath); |
| } |
| } else { |
| isPass = false; |
| ALOGE("no tracks in input file"); |
| } |
| ALOGV("after file close"); |
| FILE* ofp = fopen(coutPath, "r+"); |
| if (ofp) { |
| ALOGV("create append muxer"); |
| // Open the new file in one of the append modes and close it without writing data. |
| AMediaMuxer *muxer = AMediaMuxer_append(fileno(ofp), (AppendMode)mode); |
| if (muxer) { |
| AMediaMuxer_start(muxer); |
| AMediaMuxer_stop(muxer); |
| ALOGV("delete append muxer"); |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file to append %s", coutPath); |
| } |
| // Check if contents written in the new file match with contents in the original file. |
| auto* mediaInfoOut = new MuxerNativeTestHelper(coutPath, nullptr); |
| isPass = mediaInfoOut->isSubsetOf(mediaInfo); |
| delete mediaInfoOut; |
| } |
| delete mediaInfo; |
| env->ReleaseStringUTFChars(jinPath, cinPath); |
| env->ReleaseStringUTFChars(joutPath, coutPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| /* |
| * Checks if appending media data in AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP mode works good. |
| * Splits the contents of source file equally starting from one and increasing the number of |
| * splits by one for every iteration. Starts with writing first split into a new file and |
| * appends the rest of the contents split by split. |
| */ |
| static jboolean nativeTestIgnoreLastGOPAppend(JNIEnv* env, jobject, jint joutFormat, |
| jstring jsrcPath, jstring jdstPath) { |
| bool isPass = true; |
| if (__builtin_available(android __TRANSCODING_MIN_API__, *)) { |
| const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr); |
| const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr); |
| ALOGV("csrcPath:%s", csrcPath); |
| ALOGV("cdstPath:%s", cdstPath); |
| auto* mediaInfo = new MuxerNativeTestHelper(csrcPath); |
| for (int numSplits = 1; numSplits <= 5 && isPass; ++numSplits) { |
| ALOGV("numSplits:%d", numSplits); |
| size_t totalSampleCount = 0; |
| size_t totalSamplesWritten = 0; |
| AMediaMuxer *muxer = nullptr; |
| FILE* ofp = fopen(cdstPath, "w+"); |
| if (ofp) { |
| // Start by writing first split into a new file. |
| muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)joutFormat); |
| if (muxer) { |
| for(int i = 0 ; i < mediaInfo->getTrackCount(); ++i ) { |
| ALOGV("getSampleCount:%d:%zu", i, mediaInfo->getSampleCount(i)); |
| totalSampleCount += mediaInfo->getSampleCount(i); |
| } |
| mediaInfo->appendMedia(muxer, 0, (totalSampleCount/numSplits)-1); |
| totalSamplesWritten += (totalSampleCount/numSplits); |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("Failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| if (isPass) { |
| // Check if the contents in the new file is as same as in the source file. |
| auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr); |
| isPass = mediaInfoDest->isSubsetOf(mediaInfo); |
| delete mediaInfoDest; |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| |
| // Append rest of the contents from the source file to the new file split by split. |
| int curSplit = 1; |
| while (curSplit < numSplits && isPass) { |
| ofp = fopen(cdstPath, "r+"); |
| if (ofp) { |
| muxer = AMediaMuxer_append(fileno(ofp), |
| AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP); |
| if (muxer) { |
| auto trackCount = AMediaMuxer_getTrackCount(muxer); |
| if (trackCount > 0) { |
| decltype(trackCount) tc = 0; |
| int64_t* appendFromTime = new int64_t[trackCount]{0}; |
| while(tc < trackCount) { |
| AMediaFormat* format = AMediaMuxer_getTrackFormat(muxer, tc); |
| int64_t val = 0; |
| if (AMediaFormat_getInt64(format, |
| AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND, &val)) { |
| ALOGV("sample-time-before-append:%lld", (long long)val); |
| appendFromTime[tc] = val; |
| } |
| ++tc; |
| } |
| bool lastSplit = (curSplit == numSplits-1) ? true : false; |
| mediaInfo->appendMediaFromTime(muxer, appendFromTime, |
| totalSampleCount/numSplits + ((curSplit-1) * 30), lastSplit); |
| delete[] appendFromTime; |
| } else { |
| isPass = false; |
| ALOGE("no tracks in the file"); |
| } |
| AMediaMuxer_delete(muxer); |
| } else { |
| isPass = false; |
| ALOGE("failed to create muxer"); |
| } |
| fclose(ofp); |
| ofp = nullptr; |
| if (isPass) { |
| auto* mediaInfoDest = new MuxerNativeTestHelper(cdstPath, nullptr); |
| isPass = mediaInfoDest->isSubsetOf(mediaInfo); |
| delete mediaInfoDest; |
| } |
| } else { |
| isPass = false; |
| ALOGE("failed to open output file %s", cdstPath); |
| } |
| ++curSplit; |
| } |
| } |
| delete mediaInfo; |
| env->ReleaseStringUTFChars(jdstPath, cdstPath); |
| env->ReleaseStringUTFChars(jsrcPath, csrcPath); |
| } else { |
| isPass = false; |
| } |
| return static_cast<jboolean>(isPass); |
| } |
| |
| int registerAndroidMediaV2CtsMuxerTestApi(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestSetOrientationHint", "(ILjava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestSetOrientationHint}, |
| {"nativeTestSetLocation", "(ILjava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestSetLocation}, |
| {"nativeTestGetTrackCount", "(Ljava/lang/String;Ljava/lang/String;II)Z", |
| (void*)nativeTestGetTrackCount}, |
| {"nativeTestGetTrackFormat", "(Ljava/lang/String;Ljava/lang/String;I)Z", |
| (void*)nativeTestGetTrackFormat}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestApi"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| int registerAndroidMediaV2CtsMuxerTestMultiTrack(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestMultiTrack", |
| "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestMultiTrack}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestMultiTrack"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| int registerAndroidMediaV2CtsMuxerTestOffsetPts(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestOffsetPts", "(ILjava/lang/String;Ljava/lang/String;[I)Z", |
| (void*)nativeTestOffsetPts}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestOffsetPts"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| int registerAndroidMediaV2CtsMuxerTestSimpleMux(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestSimpleMux", |
| "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestSimpleMux}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleMux"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| int registerAndroidMediaV2CtsMuxerTestSimpleAppend(JNIEnv* env) { |
| const JNINativeMethod methodTable[] = { |
| {"nativeTestSimpleAppend", |
| "(ILjava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestSimpleAppend}, |
| {"nativeTestAppendGetTrackCount", |
| "(Ljava/lang/String;I)Z", |
| (void*)nativeTestAppendGetTrackCount}, |
| {"nativeTestAppendGetTrackFormat", "(Ljava/lang/String;)Z", |
| (void*)nativeTestAppendGetTrackFormat}, |
| {"nativeTestNoSamples", |
| "(ILjava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestNoSamples}, |
| {"nativeTestIgnoreLastGOPAppend", |
| "(ILjava/lang/String;Ljava/lang/String;)Z", |
| (void*)nativeTestIgnoreLastGOPAppend}, |
| }; |
| jclass c = env->FindClass("android/mediav2/cts/MuxerTest$TestSimpleAppend"); |
| return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod)); |
| } |
| |
| extern int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env); |
| |
| extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { |
| JNIEnv* env; |
| if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerTestApi(env) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerTestSimpleAppend(env) != JNI_OK) return JNI_ERR; |
| if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR; |
| return JNI_VERSION_1_6; |
| } |