MediaAppender+MediaMuxer:AppendFunction, new APIs

Added test cases to test appending data to MPEG4 files and two new
APIs, GetTrackFormat and GetTrackCount in NDK MediaMuxer.
MPEG4 test files with only audio, only video and one audio and one
video are added as inputs to the tests.
New formats can be added to the test just by adding suitable test files.

Bug: 154734325

Test: atest android.mediav2.cts.MuxerTest
      -- all the new tests are passing.
Change-Id: I60063f6f25e08d67d8dcffe64a58ec3b0c43968c
diff --git a/tests/media/jni/NativeExtractorUnitTest.cpp b/tests/media/jni/NativeExtractorUnitTest.cpp
index 2abc515..1c7792a 100644
--- a/tests/media/jni/NativeExtractorUnitTest.cpp
+++ b/tests/media/jni/NativeExtractorUnitTest.cpp
@@ -318,6 +318,82 @@
     return static_cast<jboolean>(isPass);
 }
 
+static jboolean nativeTestVideoSampleFileOffsetByGetSampleFormat(JNIEnv* env, jobject,
+                                                             jstring jsrcPath) {
+    int64_t video_sample_offsets[] = {6522, 95521, 118719, 126219, 137578};
+    bool isPass = true;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    AMediaExtractor* extractor = AMediaExtractor_new();
+    AMediaFormat* format = AMediaFormat_new();
+    FILE* srcFp = fopen(csrcPath, "rbe");
+    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
+        if (AMEDIA_OK == AMediaExtractor_selectTrack(extractor, 0 /* video */)) {
+            for(int i = 0; i < sizeof(video_sample_offsets)/sizeof(int64_t); ++i) {
+                if (AMEDIA_OK == AMediaExtractor_getSampleFormat(extractor, format)) {
+                    ALOGV("AMediaFormat_toString:%s", AMediaFormat_toString(format));
+                    int64_t offset = 0;
+                    if (AMediaFormat_getInt64(format, "sample-file-offset", &offset)) {
+                        if (offset != video_sample_offsets[i]) {
+                            ALOGD("offset:%lld, video_sample_offsets[%d]:%lld",
+                                        (long long)offset, i, (long long)video_sample_offsets[i]);
+                            isPass = false;
+                            break;
+                        }
+                    } else {
+                        ALOGD("error: sample-file-offset not found");
+                        isPass = false;
+                        break;
+                    }
+                }
+                AMediaExtractor_advance(extractor);
+            }
+        }
+    }
+    AMediaExtractor_delete(extractor);
+    AMediaFormat_delete(format);
+    if (srcFp) fclose(srcFp);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestAudioSampleFileOffsetByGetSampleFormat(JNIEnv* env, jobject,
+                                                             jstring jsrcPath) {
+    int64_t audio_sample_offsets[] = {186125, 186682, 187286, 187944, 188551};
+    bool isPass = true;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    AMediaExtractor* extractor = AMediaExtractor_new();
+    AMediaFormat* format = AMediaFormat_new();
+    FILE* srcFp = fopen(csrcPath, "rbe");
+    if (AMEDIA_OK == setExtractorDataSource(extractor, srcFp)) {
+        if (AMEDIA_OK == AMediaExtractor_selectTrack(extractor, 1 /* audio */)) {
+            for(int i = 0; i < sizeof(audio_sample_offsets)/sizeof(int64_t); ++i) {
+                if (AMEDIA_OK == AMediaExtractor_getSampleFormat(extractor, format)) {
+                    ALOGV("AMediaFormat_toString:%s", AMediaFormat_toString(format));
+                    int64_t offset = 0;
+                    if (AMediaFormat_getInt64(format, "sample-file-offset", &offset)) {
+                        if (offset != audio_sample_offsets[i]) {
+                            ALOGD("offset:%lld, audio_sample_offsets[%d]:%lld",
+                                        (long long)offset, i, (long long)audio_sample_offsets[i]);
+                            isPass = false;
+                            break;
+                        }
+                    } else {
+                        ALOGE("error: sample-file-offset not found");
+                        isPass = false;
+                        break;
+                    }
+                }
+                AMediaExtractor_advance(extractor);
+            }
+        }
+    }
+    AMediaExtractor_delete(extractor);
+    AMediaFormat_delete(format);
+    if (srcFp) fclose(srcFp);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
 static jboolean nativeTestGetSampleTrackIndexBeforeSetDataSource(JNIEnv*, jobject) {
     AMediaExtractor* extractor = AMediaExtractor_new();
     bool isPass = AMediaExtractor_getSampleTrackIndex(extractor) == -1;
@@ -483,6 +559,10 @@
              (void*)nativeTestReadSampleDataBeforeSelectTrack},
             {"nativeTestIfNullLocationIsRejectedBySetDataSource", "()Z",
              (void*)nativeTestIfNullLocationIsRejectedBySetDataSource},
+            {"nativeTestVideoSampleFileOffsetByGetSampleFormat", "(Ljava/lang/String;)Z",
+             (void*)nativeTestVideoSampleFileOffsetByGetSampleFormat},
+            {"nativeTestAudioSampleFileOffsetByGetSampleFormat", "(Ljava/lang/String;)Z",
+             (void*)nativeTestAudioSampleFileOffsetByGetSampleFormat},
     };
     jclass c = env->FindClass("android/mediav2/cts/ExtractorUnitTest$TestApiNative");
     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
diff --git a/tests/media/jni/NativeMuxerTest.cpp b/tests/media/jni/NativeMuxerTest.cpp
index b9eae64..0d4f392 100644
--- a/tests/media/jni/NativeMuxerTest.cpp
+++ b/tests/media/jni/NativeMuxerTest.cpp
@@ -25,6 +25,7 @@
 #include <jni.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <dlfcn.h>
 
 #include <cmath>
 #include <cstring>
@@ -34,6 +35,9 @@
 
 #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,
@@ -58,12 +62,36 @@
 
     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);
@@ -180,7 +208,7 @@
         }
         offset += bufferInfo->size;
     }
-
+    ALOGV("frameCount:%d", frameCount);
     AMediaExtractor_delete(extractor);
     fclose(ifp);
 }
@@ -213,11 +241,154 @@
     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;
@@ -261,7 +432,7 @@
     return (AMediaMuxer_stop(muxer) == AMEDIA_OK);
 }
 
-// returns true if 'this' stream is a subset of 'o'. That is all tracks in current media
+// 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;
@@ -280,22 +451,32 @@
             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()) {
+                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;
                         }
                     }
@@ -711,12 +892,453 @@
     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));
@@ -751,6 +1373,27 @@
     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*) {
@@ -760,6 +1403,7 @@
     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;
 }
diff --git a/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
index 8cc987e..188afc4 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
@@ -963,5 +963,17 @@
             assertTrue(nativeTestIfNullLocationIsRejectedBySetDataSource());
         }
         private native boolean nativeTestIfNullLocationIsRejectedBySetDataSource();
+
+        @Test
+        public void testVideoSampleFileOffsetByGetSampleFormat() {
+            assertTrue(nativeTestVideoSampleFileOffsetByGetSampleFormat(mInpPrefix + mInpMedia));
+        }
+        private native boolean nativeTestVideoSampleFileOffsetByGetSampleFormat(String srcPath);
+
+        @Test
+        public void testAudioSampleFileOffsetByGetSampleFormat() {
+            assertTrue(nativeTestAudioSampleFileOffsetByGetSampleFormat(mInpPrefix + mInpMedia));
+        }
+        private native boolean nativeTestAudioSampleFileOffsetByGetSampleFormat(String srcPath);
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index cecc5db..6e32dac 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -21,6 +21,7 @@
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaMuxer;
+import android.os.Build;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
@@ -427,6 +428,7 @@
         private String mSrcFile;
         private String mInpPath;
         private String mOutPath;
+        private int mTrackCount;
         private static final float annapurnaLat = 28.59f;
         private static final float annapurnaLong = 83.82f;
         private static final float TOLERANCE = 0.0002f;
@@ -447,23 +449,26 @@
             new File(mOutPath).delete();
         }
 
-        @Parameterized.Parameters(name = "{index}({2})")
+        @Parameterized.Parameters(name = "{index}({3})")
         public static Collection<Object[]> input() {
             return Arrays.asList(new Object[][]{
                     {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, "bbb_cif_768kbps_30fps_avc.mp4",
-                            "mp4"},
+                            1, "mp4"},
                     {MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, "bbb_cif_768kbps_30fps_vp9.mkv",
-                            "webm"},
+                            1, "webm"},
                     {MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP, "bbb_cif_768kbps_30fps_h263.mp4",
-                            "3gpp"},
+                            1, "3gpp"},
                     {MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG, "bbb_stereo_48kHz_192kbps_opus.ogg",
-                            "ogg"},
+                            1, "ogg"},
+                    {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", 2, "mp4"},
             });
         }
 
-        public TestApi(int outFormat, String srcFile, String testName) {
+        public TestApi(int outFormat, String srcFile, int trackCount, String testName) {
             mOutFormat = outFormat;
             mSrcFile = srcFile;
+            mTrackCount = trackCount;
         }
 
         private native boolean nativeTestSetLocation(int format, String srcPath, String outPath);
@@ -471,6 +476,12 @@
         private native boolean nativeTestSetOrientationHint(int format, String srcPath,
                 String outPath);
 
+        private native boolean nativeTestGetTrackCount(String srcPath, String outPath,
+                int outFormat, int trackCount);
+
+        private native boolean nativeTestGetTrackFormat(String srcPath, String outPath,
+                int outFormat);
+
         private void verifyLocationInFile(String fileName) {
             if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 &&
                     mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return;
@@ -697,6 +708,18 @@
             assertTrue(nativeTestSetOrientationHint(mOutFormat, mInpPath, mOutPath));
             verifyOrientation(mOutPath);
         }
+
+        @Test
+        public void testGetTrackCountNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestGetTrackCount(mInpPath, mOutPath, mOutFormat, mTrackCount));
+        }
+
+        @Test
+        public void testGetTrackFormatNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestGetTrackFormat(mInpPath, mOutPath, mOutFormat));
+        }
     }
 
     /**
@@ -949,6 +972,99 @@
     }
 
     /**
+     * Tests whether appending audio and/or video data to an existing media file works in all
+     * supported append modes.
+     */
+    @LargeTest
+    @RunWith(Parameterized.class)
+    public static class TestSimpleAppend {
+        private static final String LOG_TAG = MuxerTestHelper.class.getSimpleName();
+        private String mSrcFile;
+        private String mInpPath;
+        private String mOutPath;
+        private int mOutFormat;
+        private int mTrackCount;
+
+        static {
+            System.loadLibrary("ctsmediav2muxer_jni");
+        }
+
+        @Before
+        public void prologue() throws IOException {
+            mInpPath = WorkDir.getMediaDirString() + mSrcFile;
+            mOutPath = File.createTempFile("tmp", ".out").getAbsolutePath();
+        }
+
+        @After
+        public void epilogue() {
+            new File(mOutPath).delete();
+        }
+
+        @Parameterized.Parameters(name = "{index}({3})")
+        public static Collection<Object[]> input() {
+            return Arrays.asList(new Object[][]{
+                    {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                            "bbb_stereo_48kHz_128kbps_aac.mp4", 1, "mp4"},
+                    {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                            "bbb_1920x1080_avc_high_l42.mp4", 1, "mp4"},
+                    {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", 2, "mp4"},
+                    {MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                            "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", 2, "mp4"},
+            });
+        }
+
+        public TestSimpleAppend(int outFormat, String srcFile, int trackCount, String testName) {
+            mOutFormat = outFormat;
+            mSrcFile = srcFile;
+            mTrackCount = trackCount;
+        }
+
+        private native boolean nativeTestSimpleAppend(int outFormat, String srcPath,
+                String outPath);
+
+        private native boolean nativeTestAppendGetTrackCount(String srcPath, int trackCount);
+
+        private native boolean nativeTestNoSamples(int outFormat, String srcPath, String outPath);
+
+        private native boolean nativeTestIgnoreLastGOPAppend(int outFormat, String srcPath,
+                String outPath);
+
+        private native boolean nativeTestAppendGetTrackFormat(String srcPath);
+
+        @Test
+        public void testSimpleAppendNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestSimpleAppend(mOutFormat, mInpPath, mOutPath));
+        }
+
+        @Test
+        public void testAppendGetTrackCountNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestAppendGetTrackCount(mInpPath, mTrackCount));
+        }
+
+        @Test
+        public void testAppendNoSamplesNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestNoSamples(mOutFormat, mInpPath, mOutPath));
+        }
+
+        @Test
+        public void testIgnoreLastGOPAppend() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestIgnoreLastGOPAppend(mOutFormat, mInpPath, mOutPath));
+        }
+
+        @Test
+        public void testAppendGetTrackFormatNative() {
+            Assume.assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.R);
+            assertTrue(nativeTestAppendGetTrackFormat(mInpPath));
+        }
+    }
+
+
+    /**
      * Audio, Video Codecs support a variety of file-types/container formats. For example,
      * AAC-LC supports MPEG4, 3GPP. Vorbis supports OGG and WEBM. H.263 supports 3GPP and WEBM.
      * This test takes the output of a codec and muxes it in to all possible container formats.
@@ -966,7 +1082,7 @@
             System.loadLibrary("ctsmediav2muxer_jni");
         }
 
-        public TestSimpleMux(String mime, String srcFile) {
+        public TestSimpleMux(String mime, String srcFile, String testName) {
             mMime = mime;
             mSrcFile = srcFile;
         }
@@ -993,38 +1109,43 @@
         private native boolean nativeTestSimpleMux(String srcPath, String outPath, String mime,
                 String selector);
 
-        @Parameterized.Parameters(name = "{index}({0})")
+        private native boolean nativeTestSimpleAppend(String srcPath, String outPath, String mime,
+                                                      String selector);
+
+        @Parameterized.Parameters(name = "{index}({2})")
         public static Collection<Object[]> input() {
             return Arrays.asList(new Object[][]{
                     // Video Codecs
                     {MediaFormat.MIMETYPE_VIDEO_H263,
-                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp"},
+                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", "h263"},
                     {MediaFormat.MIMETYPE_VIDEO_AVC,
-                            "bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4"},
+                            "bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4", "avc"},
                     {MediaFormat.MIMETYPE_VIDEO_HEVC,
-                            "bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_opus.mp4"},
+                            "bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_opus.mp4", "hevc"},
                     {MediaFormat.MIMETYPE_VIDEO_MPEG4,
-                            "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp"},
+                            "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", "mpeg4"},
                     {MediaFormat.MIMETYPE_VIDEO_VP8,
-                            "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm"},
+                            "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm", "vp8"},
                     {MediaFormat.MIMETYPE_VIDEO_VP9,
-                            "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm"},
+                            "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm", "vp9"},
                     // Audio Codecs
                     {MediaFormat.MIMETYPE_AUDIO_AAC,
-                            "bbb_stereo_48kHz_128kbps_aac.mp4"},
+                            "bbb_stereo_48kHz_128kbps_aac.mp4", "aac"},
                     {MediaFormat.MIMETYPE_AUDIO_AMR_NB,
-                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp"},
+                            "bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp", "amrnb"},
                     {MediaFormat.MIMETYPE_AUDIO_AMR_WB,
-                            "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp"},
+                            "bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp", "amrwb"},
                     {MediaFormat.MIMETYPE_AUDIO_OPUS,
-                            "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm"},
+                            "bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm", "opus"},
                     {MediaFormat.MIMETYPE_AUDIO_VORBIS,
-                            "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm"},
+                            "bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm", "vorbis"},
                     // Metadata
                     {"application/gyro",
-                            "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp"},
+                            "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp",
+                            "gyro-non-compliant"},
                     {"application/gyro",
-                            "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp"},
+                            "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp",
+                            "gyro-compliant"},
             });
         }