Merge "Fix broken links when generating NDK Audio reference document" into sc-dev
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 0f4681e..6385bac 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -6265,7 +6265,19 @@
if (isSyncSample) {
AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1);
}
-
+
+ AMediaFormat_setInt64(
+ meta, "sample-file-offset" /*AMEDIAFORMAT_KEY_SAMPLE_FILE_OFFSET*/,
+ offset);
+
+ if (mSampleTable != nullptr &&
+ mCurrentSampleIndex == mSampleTable->getLastSampleIndexInChunk()) {
+ AMediaFormat_setInt64(
+ meta,
+ "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ mSampleTable->getLastSampleIndexInChunk());
+ }
+
++mCurrentSampleIndex;
}
}
@@ -6415,6 +6427,17 @@
AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1);
}
+ AMediaFormat_setInt64(
+ meta, "sample-file-offset" /*AMEDIAFORMAT_KEY_SAMPLE_FILE_OFFSET*/, offset);
+
+ if (mSampleTable != nullptr &&
+ mCurrentSampleIndex == mSampleTable->getLastSampleIndexInChunk()) {
+ AMediaFormat_setInt64(
+ meta,
+ "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ mSampleTable->getLastSampleIndexInChunk());
+ }
+
++mCurrentSampleIndex;
*out = mBuffer;
diff --git a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
index 5d311fc..1bbe443 100644
--- a/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalCapture.cpp
@@ -268,7 +268,7 @@
if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
ALOGD("%s(): callback returned AAUDIO_CALLBACK_RESULT_STOP", __func__);
- result = systemStopFromCallback();
+ result = systemStopInternal();
break;
}
}
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index b81e5e4..3f17e6b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -301,7 +301,7 @@
}
} else if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
ALOGD("%s(): callback returned AAUDIO_CALLBACK_RESULT_STOP", __func__);
- result = systemStopFromCallback();
+ result = systemStopInternal();
break;
}
}
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 53523c5..e8f71be 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -143,13 +143,13 @@
}
aaudio_result_t AudioStream::systemStart() {
- std::lock_guard<std::mutex> lock(mStreamLock);
-
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
+ std::lock_guard<std::mutex> lock(mStreamLock);
+
switch (getState()) {
// Is this a good time to start?
case AAUDIO_STREAM_STATE_OPEN:
@@ -187,7 +187,6 @@
}
aaudio_result_t AudioStream::systemPause() {
- std::lock_guard<std::mutex> lock(mStreamLock);
if (!isPauseSupported()) {
return AAUDIO_ERROR_UNIMPLEMENTED;
@@ -198,6 +197,7 @@
return AAUDIO_ERROR_INVALID_STATE;
}
+ std::lock_guard<std::mutex> lock(mStreamLock);
switch (getState()) {
// Proceed with pausing.
case AAUDIO_STREAM_STATE_STARTING:
@@ -242,12 +242,12 @@
return AAUDIO_ERROR_UNIMPLEMENTED;
}
- std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("stream cannot be flushed from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+ std::lock_guard<std::mutex> lock(mStreamLock);
aaudio_result_t result = AAudio_isFlushAllowed(getState());
if (result != AAUDIO_OK) {
return result;
@@ -256,7 +256,7 @@
return requestFlush_l();
}
-aaudio_result_t AudioStream::systemStopFromCallback() {
+aaudio_result_t AudioStream::systemStopInternal() {
std::lock_guard<std::mutex> lock(mStreamLock);
aaudio_result_t result = safeStop_l();
if (result == AAUDIO_OK) {
@@ -267,17 +267,12 @@
}
aaudio_result_t AudioStream::systemStopFromApp() {
- std::lock_guard<std::mutex> lock(mStreamLock);
+ // This check can and should be done outside the lock.
if (collidesWithCallback()) {
ALOGE("stream cannot be stopped by calling from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
- aaudio_result_t result = safeStop_l();
- if (result == AAUDIO_OK) {
- // We only call this for logging in "dumpsys audio". So ignore return code.
- (void) mPlayerBase->stopWithStatus();
- }
- return result;
+ return systemStopInternal();
}
aaudio_result_t AudioStream::safeStop_l() {
@@ -316,12 +311,12 @@
}
aaudio_result_t AudioStream::safeRelease() {
- // This may get temporarily unlocked in the MMAP release() when joining callback threads.
- std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
+ // This may get temporarily unlocked in the MMAP release() when joining callback threads.
+ std::lock_guard<std::mutex> lock(mStreamLock);
if (getState() == AAUDIO_STREAM_STATE_CLOSING) { // already released?
return AAUDIO_OK;
}
@@ -329,17 +324,14 @@
}
aaudio_result_t AudioStream::safeReleaseClose() {
- // This get temporarily unlocked in the MMAP release() when joining callback threads.
- std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
- releaseCloseFinal_l();
- return AAUDIO_OK;
+ return safeReleaseCloseInternal();
}
-aaudio_result_t AudioStream::safeReleaseCloseFromCallback() {
+aaudio_result_t AudioStream::safeReleaseCloseInternal() {
// This get temporarily unlocked in the MMAP release() when joining callback threads.
std::lock_guard<std::mutex> lock(mStreamLock);
releaseCloseFinal_l();
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 333e665..abf62f3 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -408,7 +408,7 @@
/**
* This is called internally when an app callback returns AAUDIO_CALLBACK_RESULT_STOP.
*/
- aaudio_result_t systemStopFromCallback();
+ aaudio_result_t systemStopInternal();
/**
* Safely RELEASE a stream after taking mStreamLock and checking
@@ -424,7 +424,7 @@
*/
aaudio_result_t safeReleaseClose();
- aaudio_result_t safeReleaseCloseFromCallback();
+ aaudio_result_t safeReleaseCloseInternal();
protected:
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index fdaa2ab..60eb73a 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -124,7 +124,7 @@
__func__, callbackResult);
}
audioBuffer->size = 0;
- systemStopFromCallback();
+ systemStopInternal();
// Disable the callback just in case the system keeps trying to call us.
mCallbackEnabled.store(false);
}
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 62c9b46..f9eebd7 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -250,3 +250,16 @@
"libutils",
],
}
+
+
+cc_test {
+ name: "test_disconnect_race",
+ defaults: ["libaaudio_tests_defaults"],
+ srcs: ["test_disconnect_race.cpp"],
+ shared_libs: [
+ "libaaudio",
+ "libbinder",
+ "libcutils",
+ "libutils",
+ ],
+}
diff --git a/media/libaaudio/tests/test_disconnect_race.cpp b/media/libaaudio/tests/test_disconnect_race.cpp
new file mode 100644
index 0000000..6dbe165
--- /dev/null
+++ b/media/libaaudio/tests/test_disconnect_race.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * Test whether an error callback is joined before the close finishes.
+ *
+ * Start a stream with a callback.
+ * The callback just sleeps for a long time.
+ * While the callback is sleeping, close() the stream from the main thread.
+ * Then check to make sure the callback was joined before the close() returns.
+ *
+ * This can hang if there are deadlocks. So make sure you get a PASSED result.
+ */
+
+#include <atomic>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+
+// Sleep long enough that the foreground has a chance to call close.
+static constexpr int kCallbackSleepMillis = 1000;
+static constexpr int kPollSleepMillis = 100;
+
+static int sErrorCount = 0;
+
+#define MY_ASSERT_TRUE(statement) \
+ if (!(statement)) { \
+ printf("ERROR line:%d - " #statement "\n", __LINE__); \
+ sErrorCount++; \
+ return false; \
+ }
+
+#define MY_ASSERT_EQ(aa,bb) MY_ASSERT_TRUE(((aa) == (bb)))
+#define MY_ASSERT_NE(aa,bb) MY_ASSERT_TRUE(((aa) != (bb)))
+
+class AudioEngine {
+public:
+
+ // Check for a crash or late callback if we close without stopping.
+ bool checkCloseJoins(aaudio_direction_t direction,
+ aaudio_performance_mode_t perfMode,
+ bool callStopFromCallback) {
+ mCallStopFromCallback = callStopFromCallback;
+
+ if (!startStreamForStall(direction, perfMode)) return false;
+
+ printf("--------------------------------------------------------\n");
+ printf("%s() - direction = %d, perfMode = %d, callStop = %d\n",
+ __func__, direction, perfMode, callStopFromCallback);
+
+ // When the callback starts it will go to sleep.
+ if (!waitForCallbackToStart()) return false;
+
+ printf("call AAudioStream_close()\n");
+ MY_ASSERT_TRUE(!mCallbackFinished); // Still sleeping?
+ aaudio_result_t result = AAudioStream_close(mStream); // May hang here!
+ if (mCallbackStarted) {
+ MY_ASSERT_TRUE(mCallbackFinished);
+ }
+ MY_ASSERT_EQ(AAUDIO_OK, result);
+ printf("AAudioStream_close() returned %d\n", result);
+
+ MY_ASSERT_EQ(AAUDIO_ERROR_DISCONNECTED, mError.load());
+ if (mCallStopFromCallback) {
+ // Did calling stop() from callback fail? It should have.
+ MY_ASSERT_NE(AAUDIO_OK, mStopResult.load());
+ }
+
+ return true;
+ }
+
+private:
+ bool startStreamForStall(aaudio_direction_t direction,
+ aaudio_performance_mode_t perfMode) {
+ AAudioStreamBuilder* builder = nullptr;
+ aaudio_result_t result = AAUDIO_OK;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ result = AAudio_createStreamBuilder(&builder);
+ MY_ASSERT_EQ(AAUDIO_OK, result);
+
+ // Request stream properties.
+ AAudioStreamBuilder_setDirection(builder, direction);
+ AAudioStreamBuilder_setPerformanceMode(builder, perfMode);
+ AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
+ AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
+
+ // Create an AAudioStream using the Builder.
+ result = AAudioStreamBuilder_openStream(builder, &mStream);
+ AAudioStreamBuilder_delete(builder);
+ MY_ASSERT_EQ(AAUDIO_OK, result);
+
+ // Check to see what kind of stream we actually got.
+ int32_t deviceId = AAudioStream_getDeviceId(mStream);
+ aaudio_performance_mode_t
+ actualPerfMode = AAudioStream_getPerformanceMode(mStream);
+ printf("-------- opened: deviceId = %3d, perfMode = %d\n",
+ deviceId,
+ actualPerfMode);
+
+ // Start stream.
+ result = AAudioStream_requestStart(mStream);
+ MY_ASSERT_EQ(AAUDIO_OK, result);
+
+ return true;
+ }
+
+ bool waitForCallbackToStart() {
+ // Wait for callback to say it has been called.
+ int countDown = 10 * 1000 / kPollSleepMillis;
+ while (!mCallbackStarted && countDown > 0) {
+ if ((countDown % 5) == 0) {
+ printf("===== Please PLUG or UNPLUG headphones! ======= %d\n", countDown);
+ }
+ usleep(kPollSleepMillis * 1000);
+ countDown--;
+ }
+ MY_ASSERT_TRUE(countDown > 0);
+ MY_ASSERT_TRUE(mCallbackStarted);
+ return true;
+ }
+
+// Callback function that fills the audio output buffer.
+ static aaudio_data_callback_result_t s_myDataCallbackProc(
+ AAudioStream * /* stream */,
+ void * /* userData */,
+ void * /* audioData */,
+ int32_t /* numFrames */
+ ) {
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ }
+
+ static void s_myErrorCallbackProc(
+ AAudioStream * stream,
+ void *userData,
+ aaudio_result_t error) {
+ AudioEngine *engine = (AudioEngine *)userData;
+ engine->mError = error;
+ engine->mCallbackStarted = true;
+ usleep(kCallbackSleepMillis * 1000);
+ // it is illegal to call stop() from the callback. It should
+ // return an error and not hang.
+ if (engine->mCallStopFromCallback) {
+ engine->mStopResult = AAudioStream_requestStop(stream);
+ }
+ engine->mCallbackFinished = true;
+ }
+
+ AAudioStream* mStream = nullptr;
+
+ std::atomic<aaudio_result_t> mError{AAUDIO_OK}; // written by error callback
+ std::atomic<bool> mCallStopFromCallback{false};
+ std::atomic<bool> mCallbackStarted{false}; // written by error callback
+ std::atomic<bool> mCallbackFinished{false}; // written by error callback
+ std::atomic<aaudio_result_t> mStopResult{AAUDIO_OK};
+};
+
+int main(int, char **) {
+ // Parameters to test.
+ static aaudio_direction_t directions[] = {AAUDIO_DIRECTION_OUTPUT,
+ AAUDIO_DIRECTION_INPUT};
+ static aaudio_performance_mode_t perfModes[] =
+ {AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_PERFORMANCE_MODE_NONE};
+ static bool callStops[] = { false, true };
+
+ // Make printf print immediately so that debug info is not stuck
+ // in a buffer if we hang or crash.
+ setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+ printf("Test Disconnect Race V1.0\n");
+ printf("\n");
+
+ for (auto callStop : callStops) {
+ for (auto direction : directions) {
+ for (auto perfMode : perfModes) {
+ AudioEngine engine;
+ engine.checkCloseJoins(direction, perfMode, callStop);
+ }
+ }
+ }
+
+ printf("Error Count = %d, %s\n", sErrorCount,
+ ((sErrorCount == 0) ? "PASS" : "FAIL"));
+}
diff --git a/media/libeffects/lvm/lib/Bundle/src/LVM_Process.cpp b/media/libeffects/lvm/lib/Bundle/src/LVM_Process.cpp
index 20058a1..4eea04f 100644
--- a/media/libeffects/lvm/lib/Bundle/src/LVM_Process.cpp
+++ b/media/libeffects/lvm/lib/Bundle/src/LVM_Process.cpp
@@ -23,6 +23,7 @@
#include <system/audio.h>
#include "LVM_Private.h"
+#include "ScalarArithmetic.h"
#include "VectorArithmetic.h"
#include "LVM_Coeffs.h"
@@ -178,6 +179,9 @@
* Apply the filter
*/
pInstance->pTEBiquad->process(pProcessed, pProcessed, NrFrames);
+ for (auto i = 0; i < NrChannels * NrFrames; i++) {
+ pProcessed[i] = LVM_Clamp(pProcessed[i]);
+ }
}
/*
* Volume balance
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 52434b3..d6e36b9 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -274,6 +274,7 @@
"MPEG2TSWriter.cpp",
"MPEG4Writer.cpp",
"MediaAdapter.cpp",
+ "MediaAppender.cpp",
"MediaClock.cpp",
"MediaCodec.cpp",
"MediaCodecList.cpp",
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 76a5cab..5c39239 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -519,12 +519,12 @@
mSendNotify = false;
mWriteSeekErr = false;
mFallocateErr = false;
-
// Reset following variables for all the sessions and they will be
// initialized in start(MetaData *param).
mIsRealTimeRecording = true;
mUse4ByteNalLength = true;
mOffset = 0;
+ mMaxOffsetAppend = 0;
mPreAllocateFileEndOffset = 0;
mMdatOffset = 0;
mMdatEndOffset = 0;
@@ -992,6 +992,19 @@
seekOrPostError(mFd, mFreeBoxOffset, SEEK_SET);
writeInt32(mInMemoryCacheSize);
write("free", 4);
+ if (mInMemoryCacheSize >= 8) {
+ off64_t bufSize = mInMemoryCacheSize - 8;
+ char* zeroBuffer = new (std::nothrow) char[bufSize];
+ if (zeroBuffer) {
+ std::fill_n(zeroBuffer, bufSize, '0');
+ writeOrPostError(mFd, zeroBuffer, bufSize);
+ delete [] zeroBuffer;
+ } else {
+ ALOGW("freebox in file isn't initialized to 0");
+ }
+ } else {
+ ALOGW("freebox size is less than 8:%" PRId64, mInMemoryCacheSize);
+ }
mMdatOffset = mFreeBoxOffset + mInMemoryCacheSize;
} else {
mMdatOffset = mOffset;
@@ -1541,6 +1554,26 @@
MediaBuffer *buffer, bool usePrefix,
uint32_t tiffHdrOffset, size_t *bytesWritten) {
off64_t old_offset = mOffset;
+ int64_t offset;
+ ALOGV("buffer->range_length:%lld", (long long)buffer->range_length());
+ if (buffer->meta_data().findInt64(kKeySampleFileOffset, &offset)) {
+ ALOGV("offset:%lld, old_offset:%lld", (long long)offset, (long long)old_offset);
+ if (old_offset == offset) {
+ mOffset += buffer->range_length();
+ } else {
+ ALOGV("offset and old_offset are not equal! diff:%lld", (long long)offset - old_offset);
+ mOffset = offset + buffer->range_length();
+ // mOffset += buffer->range_length() + offset - old_offset;
+ }
+ *bytesWritten = buffer->range_length();
+ ALOGV("mOffset:%lld, mMaxOffsetAppend:%lld, bytesWritten:%lld", (long long)mOffset,
+ (long long)mMaxOffsetAppend, (long long)*bytesWritten);
+ mMaxOffsetAppend = std::max(mOffset, mMaxOffsetAppend);
+ seekOrPostError(mFd, mMaxOffsetAppend, SEEK_SET);
+ return offset;
+ }
+
+ ALOGV("mOffset:%lld, mMaxOffsetAppend:%lld", (long long)mOffset, (long long)mMaxOffsetAppend);
if (usePrefix) {
addMultipleLengthPrefixedSamples_l(buffer);
@@ -1557,6 +1590,10 @@
mOffset += buffer->range_length();
}
*bytesWritten = mOffset - old_offset;
+
+ ALOGV("mOffset:%lld, old_offset:%lld, bytesWritten:%lld", (long long)mOffset,
+ (long long)old_offset, (long long)*bytesWritten);
+
return old_offset;
}
@@ -1569,6 +1606,7 @@
(const uint8_t *)buffer->data() + buffer->range_offset();
if (!memcmp(ptr, "\x00\x00\x00\x01", 4)) {
+ ALOGV("stripping start code");
buffer->set_range(
buffer->range_offset() + 4, buffer->range_length() - 4);
}
@@ -1599,8 +1637,10 @@
}
void MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) {
+ ALOGV("alp:buffer->range_length:%lld", (long long)buffer->range_length());
size_t length = buffer->range_length();
if (mUse4ByteNalLength) {
+ ALOGV("mUse4ByteNalLength");
uint8_t x[4];
x[0] = length >> 24;
x[1] = (length >> 16) & 0xff;
@@ -1610,6 +1650,7 @@
writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(), length);
mOffset += length + 4;
} else {
+ ALOGV("mUse2ByteNalLength");
CHECK_LT(length, 65536u);
uint8_t x[2];
@@ -2762,6 +2803,9 @@
}
writeAllChunks();
+ ALOGV("threadFunc mOffset:%lld, mMaxOffsetAppend:%lld", (long long)mOffset,
+ (long long)mMaxOffsetAppend);
+ mOffset = std::max(mOffset, mMaxOffsetAppend);
}
status_t MPEG4Writer::startWriterThread() {
@@ -3323,6 +3367,7 @@
uint32_t lastSamplesPerChunk = 0;
int64_t lastSampleDurationUs = -1; // Duration calculated from EOS buffer and its timestamp
int64_t lastSampleDurationTicks = -1; // Timescale based ticks
+ int64_t sampleFileOffset = -1;
if (mIsAudio) {
prctl(PR_SET_NAME, (unsigned long)"MP4WtrAudTrkThread", 0, 0, 0);
@@ -3342,6 +3387,7 @@
MediaBufferBase *buffer;
const char *trackName = getTrackType();
while (!mDone && (err = mSource->read(&buffer)) == OK) {
+ ALOGV("read:buffer->range_length:%lld", (long long)buffer->range_length());
int32_t isEOS = false;
if (buffer->range_length() == 0) {
if (buffer->meta_data().findInt32(kKeyIsEndOfStream, &isEOS) && isEOS) {
@@ -3448,6 +3494,14 @@
continue;
}
}
+ if (!buffer->meta_data().findInt64(kKeySampleFileOffset, &sampleFileOffset)) {
+ sampleFileOffset = -1;
+ }
+ int64_t lastSample = -1;
+ if (!buffer->meta_data().findInt64(kKeyLastSampleIndexInChunk, &lastSample)) {
+ lastSample = -1;
+ }
+ ALOGV("sampleFileOffset:%lld", (long long)sampleFileOffset);
/*
* Reserve space in the file for the current sample + to be written MOOV box. If reservation
@@ -3455,7 +3509,7 @@
* write MOOV box successfully as space for the same was reserved in the prior call.
* Release the current buffer/sample here.
*/
- if (!mOwner->preAllocate(buffer->range_length())) {
+ if (sampleFileOffset == -1 && !mOwner->preAllocate(buffer->range_length())) {
buffer->release();
buffer = nullptr;
break;
@@ -3466,9 +3520,14 @@
// Make a deep copy of the MediaBuffer and Metadata and release
// the original as soon as we can
MediaBuffer *copy = new MediaBuffer(buffer->range_length());
- memcpy(copy->data(), (uint8_t *)buffer->data() + buffer->range_offset(),
- buffer->range_length());
+ if (sampleFileOffset != -1) {
+ copy->meta_data().setInt64(kKeySampleFileOffset, sampleFileOffset);
+ } else {
+ memcpy(copy->data(), (uint8_t*)buffer->data() + buffer->range_offset(),
+ buffer->range_length());
+ }
copy->set_range(0, buffer->range_length());
+
meta_data = new MetaData(buffer->meta_data());
buffer->release();
buffer = NULL;
@@ -3476,14 +3535,16 @@
copy->meta_data().setInt32(kKeyExifTiffOffset, tiffHdrOffset);
}
bool usePrefix = this->usePrefix() && !isExif;
-
- if (usePrefix) StripStartcode(copy);
-
+ if (sampleFileOffset == -1 && usePrefix) {
+ StripStartcode(copy);
+ }
size_t sampleSize = copy->range_length();
- if (usePrefix) {
+ if (sampleFileOffset == -1 && usePrefix) {
if (mOwner->useNalLengthFour()) {
+ ALOGV("nallength4");
sampleSize += 4;
} else {
+ ALOGV("nallength2");
sampleSize += 2;
}
}
@@ -3778,7 +3839,8 @@
chunkTimestampUs = timestampUs;
} else {
int64_t chunkDurationUs = timestampUs - chunkTimestampUs;
- if (chunkDurationUs > interleaveDurationUs) {
+ if (chunkDurationUs > interleaveDurationUs || lastSample > 1) {
+ ALOGV("lastSample:%lld", (long long)lastSample);
if (chunkDurationUs > mMaxChunkDurationUs) {
mMaxChunkDurationUs = chunkDurationUs;
}
@@ -5331,4 +5393,4 @@
endBox();
}
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/media/libstagefright/MediaAppender.cpp b/media/libstagefright/MediaAppender.cpp
new file mode 100644
index 0000000..5d80b30
--- /dev/null
+++ b/media/libstagefright/MediaAppender.cpp
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2020 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 "MediaAppender"
+
+#include <media/stagefright/MediaAppender.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <utils/Log.h>
+// TODO : check if this works for NDK apps without JVM
+// #include <media/ndk/NdkJavaVMHelperPriv.h>
+
+namespace android {
+
+struct MediaAppender::sampleDataInfo {
+ size_t size;
+ int64_t time;
+ size_t exTrackIndex;
+ sp<MetaData> meta;
+};
+
+sp<MediaAppender> MediaAppender::create(int fd, AppendMode mode) {
+ if (fd < 0) {
+ ALOGE("invalid file descriptor");
+ return nullptr;
+ }
+ if (!(mode >= APPEND_MODE_FIRST && mode <= APPEND_MODE_LAST)) {
+ ALOGE("invalid mode %d", mode);
+ return nullptr;
+ }
+ sp<MediaAppender> ma = new (std::nothrow) MediaAppender(fd, mode);
+ if (ma->init() != OK) {
+ return nullptr;
+ }
+ return ma;
+}
+
+// TODO: inject mediamuxer and mediaextractor objects.
+// TODO: @format is not required as an input if we can sniff the file and find the format of
+// the existing content.
+// TODO: Code it to the interface(MediaAppender), and have a separate MediaAppender NDK
+MediaAppender::MediaAppender(int fd, AppendMode mode)
+ : mFd(fd),
+ mMode(mode),
+ // TODO : check if this works for NDK apps without JVM
+ // mExtractor(new NuMediaExtractor(NdkJavaVMHelper::getJNIEnv() != nullptr
+ // ? NuMediaExtractor::EntryPoint::NDK_WITH_JVM
+ // : NuMediaExtractor::EntryPoint::NDK_NO_JVM)),
+ mExtractor(new (std::nothrow) NuMediaExtractor(NuMediaExtractor::EntryPoint::NDK_WITH_JVM)),
+ mTrackCount(0),
+ mState(UNINITIALIZED) {
+ ALOGV("MediaAppender::MediaAppender mode:%d", mode);
+ }
+
+status_t MediaAppender::init() {
+ std::scoped_lock lock(mMutex);
+ ALOGV("MediaAppender::init");
+ status_t status = mExtractor->setDataSource(mFd, 0, lseek(mFd, 0, SEEK_END));
+ if (status != OK) {
+ ALOGE("extractor_setDataSource failed, status :%d", status);
+ return status;
+ }
+
+ if (strcmp("MPEG4Extractor", mExtractor->getName()) == 0) {
+ mFormat = MediaMuxer::OUTPUT_FORMAT_MPEG_4;
+ } else {
+ ALOGE("Unsupported format, extractor name:%s", mExtractor->getName());
+ return ERROR_UNSUPPORTED;
+ }
+
+ mTrackCount = mExtractor->countTracks();
+ ALOGV("mTrackCount:%zu", mTrackCount);
+ if (mTrackCount == 0) {
+ ALOGE("no tracks are present");
+ return ERROR_MALFORMED;
+ }
+ size_t exTrackIndex = 0;
+ ssize_t audioTrackIndex = -1, videoTrackIndex = -1;
+ bool audioSyncSampleTimeSet = false;
+
+ while (exTrackIndex < mTrackCount) {
+ sp<AMessage> fmt;
+ status = mExtractor->getTrackFormat(exTrackIndex, &fmt, 0);
+ if (status != OK) {
+ ALOGE("getTrackFormat failed for trackIndex:%zu, status:%d", exTrackIndex, status);
+ return status;
+ }
+ AString mime;
+ if (fmt->findString("mime", &mime)) {
+ if (!strncasecmp(mime.c_str(), "video/", 6)) {
+ ALOGV("VideoTrack");
+ if (videoTrackIndex != -1) {
+ ALOGE("Not more than one video track is supported");
+ return ERROR_UNSUPPORTED;
+ }
+ videoTrackIndex = exTrackIndex;
+ } else if (!strncasecmp(mime.c_str(), "audio/", 6)) {
+ ALOGV("AudioTrack");
+ if (audioTrackIndex != -1) {
+ ALOGE("Not more than one audio track is supported");
+ }
+ audioTrackIndex = exTrackIndex;
+ } else {
+ ALOGV("Neither Video nor Audio track");
+ }
+ }
+ mFmtIndexMap.emplace(exTrackIndex, fmt);
+ mSampleCountVect.emplace_back(0);
+ mMaxTimestampVect.emplace_back(0);
+ mLastSyncSampleTimeVect.emplace_back(0);
+ status = mExtractor->selectTrack(exTrackIndex);
+ if (status != OK) {
+ ALOGE("selectTrack failed for trackIndex:%zu, status:%d", exTrackIndex, status);
+ return status;
+ }
+ ++exTrackIndex;
+ }
+
+ ALOGV("AudioTrackIndex:%zu, VideoTrackIndex:%zu", audioTrackIndex, videoTrackIndex);
+
+ do {
+ sampleDataInfo tmpSDI;
+ // TODO: read info into members of the struct sampleDataInfo directly
+ size_t sampleSize;
+ status = mExtractor->getSampleSize(&sampleSize);
+ if (status != OK) {
+ ALOGE("getSampleSize failed, status:%d", status);
+ return status;
+ }
+ mSampleSizeVect.emplace_back(sampleSize);
+ tmpSDI.size = sampleSize;
+ int64_t sampleTime = 0;
+ status = mExtractor->getSampleTime(&sampleTime);
+ if (status != OK) {
+ ALOGE("getSampleTime failed, status:%d", status);
+ return status;
+ }
+ mSampleTimeVect.emplace_back(sampleTime);
+ tmpSDI.time = sampleTime;
+ status = mExtractor->getSampleTrackIndex(&exTrackIndex);
+ if (status != OK) {
+ ALOGE("getSampleTrackIndex failed, status:%d", status);
+ return status;
+ }
+ mSampleIndexVect.emplace_back(exTrackIndex);
+ tmpSDI.exTrackIndex = exTrackIndex;
+ ++mSampleCountVect[exTrackIndex];
+ mMaxTimestampVect[exTrackIndex] = std::max(mMaxTimestampVect[exTrackIndex], sampleTime);
+ sp<MetaData> sampleMeta;
+ status = mExtractor->getSampleMeta(&sampleMeta);
+ if (status != OK) {
+ ALOGE("getSampleMeta failed, status:%d", status);
+ return status;
+ }
+ mSampleMetaVect.emplace_back(sampleMeta);
+ int32_t val = 0;
+ if (sampleMeta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
+ mLastSyncSampleTimeVect[exTrackIndex] = sampleTime;
+ }
+ tmpSDI.meta = sampleMeta;
+ mSDI.emplace_back(tmpSDI);
+ } while (mExtractor->advance() == OK);
+
+ mExtractor.clear();
+
+ std::sort(mSDI.begin(), mSDI.end(), [](sampleDataInfo& a, sampleDataInfo& b) {
+ int64_t aOffset, bOffset;
+ a.meta->findInt64(kKeySampleFileOffset, &aOffset);
+ b.meta->findInt64(kKeySampleFileOffset, &bOffset);
+ return aOffset < bOffset;
+ });
+ for (int64_t syncSampleTime : mLastSyncSampleTimeVect) {
+ ALOGV("before ignoring frames, mLastSyncSampleTimeVect:%lld", (long long)syncSampleTime);
+ }
+ ALOGV("mMode:%u", mMode);
+ if (mMode == APPEND_MODE_IGNORE_LAST_VIDEO_GOP && videoTrackIndex != -1 ) {
+ ALOGV("Video track is present");
+ bool lastVideoIframe = false;
+ size_t lastVideoIframeOffset = 0;
+ int64_t lastVideoSampleTime = -1;
+ for (auto rItr = mSDI.rbegin(); rItr != mSDI.rend(); ++rItr) {
+ if (rItr->exTrackIndex != videoTrackIndex) {
+ continue;
+ }
+ if (lastVideoSampleTime == -1) {
+ lastVideoSampleTime = rItr->time;
+ }
+ int64_t offset = 0;
+ if (!rItr->meta->findInt64(kKeySampleFileOffset, &offset) || offset == 0) {
+ ALOGE("Missing offset");
+ return ERROR_MALFORMED;
+ }
+ ALOGV("offset:%lld", (long long)offset);
+ int32_t val = 0;
+ if (rItr->meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
+ ALOGV("sampleTime:%lld", (long long)rItr->time);
+ ALOGV("lastVideoSampleTime:%lld", (long long)lastVideoSampleTime);
+ if (lastVideoIframe == false && (lastVideoSampleTime - rItr->time) >
+ 1000000/* Track interleaving duration in MPEG4Writer*/) {
+ ALOGV("lastVideoIframe got chosen");
+ lastVideoIframe = true;
+ mLastSyncSampleTimeVect[videoTrackIndex] = rItr->time;
+ lastVideoIframeOffset = offset;
+ ALOGV("lastVideoIframeOffset:%lld", (long long)offset);
+ break;
+ }
+ }
+ }
+ if (lastVideoIframe == false) {
+ ALOGV("Need to rewrite all samples");
+ mLastSyncSampleTimeVect[videoTrackIndex] = 0;
+ lastVideoIframeOffset = 0;
+ }
+ unsigned int framesIgnoredCount = 0;
+ for (auto itr = mSDI.begin(); itr != mSDI.end();) {
+ int64_t offset = 0;
+ ALOGV("trackIndex:%zu, %" PRId64 "", itr->exTrackIndex, itr->time);
+ if (itr->meta->findInt64(kKeySampleFileOffset, &offset) &&
+ offset >= lastVideoIframeOffset) {
+ ALOGV("offset:%lld", (long long)offset);
+ if (!audioSyncSampleTimeSet && audioTrackIndex != -1 &&
+ audioTrackIndex == itr->exTrackIndex) {
+ mLastSyncSampleTimeVect[audioTrackIndex] = itr->time;
+ audioSyncSampleTimeSet = true;
+ }
+ itr = mSDI.erase(itr);
+ ++framesIgnoredCount;
+ } else {
+ ++itr;
+ }
+ }
+ ALOGV("framesIgnoredCount:%u", framesIgnoredCount);
+ }
+
+ if (mMode == APPEND_MODE_IGNORE_LAST_VIDEO_GOP && videoTrackIndex == -1 &&
+ audioTrackIndex != -1) {
+ ALOGV("Only AudioTrack is present");
+ for (auto rItr = mSDI.rbegin(); rItr != mSDI.rend(); ++rItr) {
+ int32_t val = 0;
+ if (rItr->meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
+ mLastSyncSampleTimeVect[audioTrackIndex] = rItr->time;
+ break;
+ }
+ }
+ unsigned int framesIgnoredCount = 0;
+ for (auto itr = mSDI.begin(); itr != mSDI.end();) {
+ if (itr->time >= mLastSyncSampleTimeVect[audioTrackIndex]) {
+ itr = mSDI.erase(itr);
+ ++framesIgnoredCount;
+ } else {
+ ++itr;
+ }
+ }
+ ALOGV("framesIgnoredCount :%u", framesIgnoredCount);
+ }
+
+ for (size_t i = 0; i < mLastSyncSampleTimeVect.size(); ++i) {
+ ALOGV("mLastSyncSampleTimeVect[%zu]:%lld", i, (long long)mLastSyncSampleTimeVect[i]);
+ mFmtIndexMap[i]->setInt64(
+ "sample-time-before-append" /*AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND*/,
+ mLastSyncSampleTimeVect[i]);
+ }
+ for (size_t i = 0; i < mMaxTimestampVect.size(); ++i) {
+ ALOGV("mMaxTimestamp[%zu]:%lld", i, (long long)mMaxTimestampVect[i]);
+ }
+ for (size_t i = 0; i < mSampleCountVect.size(); ++i) {
+ ALOGV("SampleCountVect[%zu]:%zu", i, mSampleCountVect[i]);
+ }
+ mState = INITIALIZED;
+ return OK;
+}
+
+MediaAppender::~MediaAppender() {
+ ALOGV("MediaAppender::~MediaAppender");
+ mMuxer.clear();
+ mExtractor.clear();
+}
+
+status_t MediaAppender::start() {
+ std::scoped_lock lock(mMutex);
+ ALOGV("MediaAppender::start");
+ if (mState != INITIALIZED) {
+ ALOGE("MediaAppender::start() is called in invalid state %d", mState);
+ return INVALID_OPERATION;
+ }
+ mMuxer = new (std::nothrow) MediaMuxer(mFd, mFormat);
+ for (const auto& n : mFmtIndexMap) {
+ ssize_t muxIndex = mMuxer->addTrack(n.second);
+ if (muxIndex < 0) {
+ ALOGE("addTrack failed");
+ return UNKNOWN_ERROR;
+ }
+ mTrackIndexMap.emplace(n.first, muxIndex);
+ }
+ ALOGV("trackIndexmap size:%zu", mTrackIndexMap.size());
+
+ status_t status = mMuxer->start();
+ if (status != OK) {
+ ALOGE("muxer start failed:%d", status);
+ return status;
+ }
+
+ ALOGV("Sorting samples based on their offsets");
+ for (int i = 0; i < mSDI.size(); ++i) {
+ ALOGV("i:%d", i + 1);
+ /* TODO : Allocate a single allocation of the max size, and reuse it across ABuffers if
+ * using new ABuffer(void *, size_t).
+ */
+ sp<ABuffer> data = new (std::nothrow) ABuffer(mSDI[i].size);
+ if (data == nullptr) {
+ ALOGE("memory allocation failed");
+ return NO_MEMORY;
+ }
+ data->setRange(0, mSDI[i].size);
+ int32_t val = 0;
+ int sampleFlags = 0;
+ if (mSDI[i].meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
+ sampleFlags |= MediaCodec::BUFFER_FLAG_SYNCFRAME;
+ }
+
+ int64_t val64;
+ if (mSDI[i].meta->findInt64(kKeySampleFileOffset, &val64)) {
+ ALOGV("SampleFileOffset Found :%zu:%lld:%lld", mSDI[i].exTrackIndex,
+ (long long)mSampleCountVect[mSDI[i].exTrackIndex], (long long)val64);
+ sp<AMessage> bufMeta = data->meta();
+ bufMeta->setInt64("sample-file-offset" /*AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND*/,
+ val64);
+ }
+ if (mSDI[i].meta->findInt64(kKeyLastSampleIndexInChunk, &val64)) {
+ ALOGV("kKeyLastSampleIndexInChunk Found %lld:%lld",
+ (long long)mSampleCountVect[mSDI[i].exTrackIndex], (long long)val64);
+ sp<AMessage> bufMeta = data->meta();
+ bufMeta->setInt64(
+ "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ val64);
+ }
+ status = mMuxer->writeSampleData(data, mTrackIndexMap[mSDI[i].exTrackIndex], mSDI[i].time,
+ sampleFlags);
+ if (status != OK) {
+ ALOGE("muxer writeSampleData failed:%d", status);
+ return status;
+ }
+ }
+ mState = STARTED;
+ return OK;
+}
+
+status_t MediaAppender::stop() {
+ std::scoped_lock lock(mMutex);
+ ALOGV("MediaAppender::stop");
+ if (mState == STARTED) {
+ status_t status = mMuxer->stop();
+ if (status != OK) {
+ mState = ERROR;
+ } else {
+ mState = STOPPED;
+ }
+ return status;
+ } else {
+ ALOGE("stop() is called in invalid state %d", mState);
+ return INVALID_OPERATION;
+ }
+}
+
+ssize_t MediaAppender::getTrackCount() {
+ std::scoped_lock lock(mMutex);
+ ALOGV("MediaAppender::getTrackCount");
+ if (mState != INITIALIZED && mState != STARTED) {
+ ALOGE("getTrackCount() is called in invalid state %d", mState);
+ return -1;
+ }
+ return mTrackCount;
+}
+
+sp<AMessage> MediaAppender::getTrackFormat(size_t idx) {
+ std::scoped_lock lock(mMutex);
+ ALOGV("MediaAppender::getTrackFormat");
+ if (mState != INITIALIZED && mState != STARTED) {
+ ALOGE("getTrackFormat() is called in invalid state %d", mState);
+ return nullptr;
+ }
+ if (idx < 0 || idx >= mTrackCount) {
+ ALOGE("getTrackFormat() idx is out of range");
+ return nullptr;
+ }
+ return mFmtIndexMap[idx];
+}
+
+status_t MediaAppender::writeSampleData(const sp<ABuffer>& buffer, size_t trackIndex,
+ int64_t timeUs, uint32_t flags) {
+ std::scoped_lock lock(mMutex);
+ ALOGV("writeSampleData:trackIndex:%zu, time:%" PRId64 "", trackIndex, timeUs);
+ return mMuxer->writeSampleData(buffer, trackIndex, timeUs, flags);
+}
+
+status_t MediaAppender::setOrientationHint([[maybe_unused]] int degrees) {
+ ALOGE("setOrientationHint not supported. Has to be called prior to start on initial muxer");
+ return ERROR_UNSUPPORTED;
+};
+
+status_t MediaAppender::setLocation([[maybe_unused]] int latit, [[maybe_unused]] int longit) {
+ ALOGE("setLocation not supported. Has to be called prior to start on initial muxer");
+ return ERROR_UNSUPPORTED;
+}
+
+ssize_t MediaAppender::addTrack([[maybe_unused]] const sp<AMessage> &format) {
+ ALOGE("addTrack not supported");
+ return ERROR_UNSUPPORTED;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index c91386d..a946f71 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -76,6 +76,7 @@
mFileMeta.clear();
mWriter.clear();
mTrackList.clear();
+ mFormatList.clear();
}
ssize_t MediaMuxer::addTrack(const sp<AMessage> &format) {
@@ -109,6 +110,8 @@
ALOGW("addTrack() setCaptureRate failed :%d", result);
}
}
+
+ mFormatList.add(format);
return mTrackList.add(newTrack);
}
@@ -224,9 +227,42 @@
ALOGV("BUFFER_FLAG_EOS");
}
+ sp<AMessage> bufMeta = buffer->meta();
+ int64_t val64;
+ if (bufMeta->findInt64("sample-file-offset", &val64)) {
+ sampleMetaData.setInt64(kKeySampleFileOffset, val64);
+ }
+ if (bufMeta->findInt64(
+ "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ &val64)) {
+ sampleMetaData.setInt64(kKeyLastSampleIndexInChunk, val64);
+ }
+
sp<MediaAdapter> currentTrack = mTrackList[trackIndex];
// This pushBuffer will wait until the mediaBuffer is consumed.
return currentTrack->pushBuffer(mediaBuffer);
}
+ssize_t MediaMuxer::getTrackCount() {
+ Mutex::Autolock autoLock(mMuxerLock);
+ if (mState != INITIALIZED && mState != STARTED) {
+ ALOGE("getTrackCount() must be called either in INITIALIZED or STARTED state");
+ return -1;
+ }
+ return mTrackList.size();
+}
+
+sp<AMessage> MediaMuxer::getTrackFormat([[maybe_unused]] size_t idx) {
+ Mutex::Autolock autoLock(mMuxerLock);
+ if (mState != INITIALIZED && mState != STARTED) {
+ ALOGE("getTrackFormat() must be called either in INITIALIZED or STARTED state");
+ return nullptr;
+ }
+ if (idx < 0 || idx >= mFormatList.size()) {
+ ALOGE("getTrackFormat() idx is out of range");
+ return nullptr;
+ }
+ return mFormatList[idx];
+}
+
} // namespace android
diff --git a/media/libstagefright/MediaTrack.cpp b/media/libstagefright/MediaTrack.cpp
index 24ba38a..2447f5e 100644
--- a/media/libstagefright/MediaTrack.cpp
+++ b/media/libstagefright/MediaTrack.cpp
@@ -133,6 +133,14 @@
if (format->mFormat->findInt64("target-time", &val64)) {
meta.setInt64(kKeyTargetTime, val64);
}
+ if (format->mFormat->findInt64("sample-file-offset", &val64)) {
+ meta.setInt64(kKeySampleFileOffset, val64);
+ }
+ if (format->mFormat->findInt64(
+ "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ &val64)) {
+ meta.setInt64(kKeyLastSampleIndexInChunk, val64);
+ }
int32_t val32;
if (format->mFormat->findInt32("is-sync-frame", &val32)) {
meta.setInt32(kKeyIsSyncFrame, val32);
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index f2c7dd6..f0383b5 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -189,6 +189,11 @@
return err;
}
+const char* NuMediaExtractor::getName() const {
+ Mutex::Autolock autoLock(mLock);
+ return mImpl == nullptr ? nullptr : mImpl->name().string();
+}
+
static String8 arrayToString(const std::vector<uint8_t> &array) {
String8 result;
for (size_t i = 0; i < array.size(); i++) {
diff --git a/media/libstagefright/OWNERS b/media/libstagefright/OWNERS
index 819389d..0cc2294 100644
--- a/media/libstagefright/OWNERS
+++ b/media/libstagefright/OWNERS
@@ -4,4 +4,8 @@
lajos@google.com
marcone@google.com
taklee@google.com
-wonsik@google.com
\ No newline at end of file
+wonsik@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 5ede871..04a9b17 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -725,16 +725,19 @@
}
};
-static std::vector<std::pair<const char *, uint32_t>> int64Mappings {
+static std::vector<std::pair<const char*, uint32_t>> int64Mappings {
{
- { "exif-offset", kKeyExifOffset },
- { "exif-size", kKeyExifSize },
- { "xmp-offset", kKeyXmpOffset },
- { "xmp-size", kKeyXmpSize },
- { "target-time", kKeyTargetTime },
- { "thumbnail-time", kKeyThumbnailTime },
- { "timeUs", kKeyTime },
- { "durationUs", kKeyDuration },
+ { "exif-offset", kKeyExifOffset},
+ { "exif-size", kKeyExifSize},
+ { "xmp-offset", kKeyXmpOffset},
+ { "xmp-size", kKeyXmpSize},
+ { "target-time", kKeyTargetTime},
+ { "thumbnail-time", kKeyThumbnailTime},
+ { "timeUs", kKeyTime},
+ { "durationUs", kKeyDuration},
+ { "sample-file-offset", kKeySampleFileOffset},
+ { "last-sample-index-in-chunk", kKeyLastSampleIndexInChunk},
+ { "sample-time-before-append", kKeySampleTimeBeforeAppend},
}
};
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2582ed0..7f2728e 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -106,6 +106,7 @@
off64_t mOffset;
off64_t mPreAllocateFileEndOffset; //End of file offset during preallocation.
off64_t mMdatOffset;
+ off64_t mMaxOffsetAppend; // File offset written upto while appending.
off64_t mMdatEndOffset; // End offset of mdat atom.
uint8_t *mInMemoryCache;
off64_t mInMemoryCacheOffset;
diff --git a/media/libstagefright/include/media/stagefright/MediaAppender.h b/media/libstagefright/include/media/stagefright/MediaAppender.h
new file mode 100644
index 0000000..c2f6f10
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaAppender.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef ANDROID_MEDIA_APPENDER_H
+#define ANDROID_MEDIA_APPENDER_H
+
+#include <media/stagefright/MediaMuxer.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <stack>
+
+namespace android {
+
+struct MediaAppender : public MediaMuxerBase {
+public:
+ enum AppendMode {
+ APPEND_MODE_FIRST = 0,
+ APPEND_MODE_IGNORE_LAST_VIDEO_GOP = APPEND_MODE_FIRST,
+ APPEND_MODE_ADD_TO_EXISTING_DATA = 1,
+ APPEND_MODE_LAST = APPEND_MODE_ADD_TO_EXISTING_DATA,
+ };
+
+ static sp<MediaAppender> create(int fd, AppendMode mode);
+
+ virtual ~MediaAppender();
+
+ status_t init();
+
+ status_t start();
+
+ status_t stop();
+
+ status_t writeSampleData(const sp<ABuffer>& buffer, size_t trackIndex, int64_t timeUs,
+ uint32_t flags);
+
+ status_t setOrientationHint(int degrees);
+
+ status_t setLocation(int latitude, int longitude);
+
+ ssize_t addTrack(const sp<AMessage> &format);
+
+ ssize_t getTrackCount();
+
+ sp<AMessage> getTrackFormat(size_t idx);
+
+private:
+ MediaAppender(int fd, AppendMode mode);
+
+ int mFd;
+ MediaMuxer::OutputFormat mFormat;
+ AppendMode mMode;
+ sp<NuMediaExtractor> mExtractor;
+ sp<MediaMuxer> mMuxer;
+ size_t mTrackCount;
+ // Map track index given by extractor to the ones received from muxer.
+ std::map<size_t, ssize_t> mTrackIndexMap;
+ // Count of the samples in each track, indexed by extractor track ids.
+ std::vector<size_t> mSampleCountVect;
+ // Extractor track index of samples.
+ std::vector<size_t> mSampleIndexVect;
+ // Track format indexed by extractor track ids.
+ std::map<size_t, sp<AMessage>> mFmtIndexMap;
+ // Size of samples.
+ std::vector<size_t> mSampleSizeVect;
+ // Presentation time stamp of samples.
+ std::vector<int64_t> mSampleTimeVect;
+ // Timestamp of last sample of tracks.
+ std::vector<int64_t> mMaxTimestampVect;
+ // Metadata of samples.
+ std::vector<sp<MetaData>> mSampleMetaVect;
+ std::mutex mMutex;
+ // Timestamp of the last sync sample of tracks.
+ std::vector<int64_t> mLastSyncSampleTimeVect;
+
+ struct sampleDataInfo;
+ std::vector<sampleDataInfo> mSDI;
+
+ enum : int {
+ UNINITIALIZED,
+ INITIALIZED,
+ STARTED,
+ STOPPED,
+ ERROR,
+ } mState GUARDED_BY(mMutex);
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_APPENDER_H
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaMuxer.h b/media/libstagefright/include/media/stagefright/MediaMuxer.h
index a1b9465..e97a65e 100644
--- a/media/libstagefright/include/media/stagefright/MediaMuxer.h
+++ b/media/libstagefright/include/media/stagefright/MediaMuxer.h
@@ -22,7 +22,12 @@
#include <utils/Vector.h>
#include <utils/threads.h>
+#include <map>
+#include <mutex>
+#include <vector>
+
#include "media/stagefright/foundation/ABase.h"
+#include "MediaMuxerBase.h"
namespace android {
@@ -33,6 +38,7 @@
struct MediaSource;
class MetaData;
struct MediaWriter;
+struct NuMediaExtractor;
// MediaMuxer is used to mux multiple tracks into a video. Currently, we only
// support a mp4 file as the output.
@@ -40,19 +46,8 @@
// Constructor -> addTrack+ -> start -> writeSampleData+ -> stop
// If muxing operation need to be cancelled, the app is responsible for
// deleting the output file after stop.
-struct MediaMuxer : public RefBase {
+struct MediaMuxer : public MediaMuxerBase {
public:
- // Please update media/java/android/media/MediaMuxer.java if the
- // OutputFormat is updated.
- enum OutputFormat {
- OUTPUT_FORMAT_MPEG_4 = 0,
- OUTPUT_FORMAT_WEBM = 1,
- OUTPUT_FORMAT_THREE_GPP = 2,
- OUTPUT_FORMAT_HEIF = 3,
- OUTPUT_FORMAT_OGG = 4,
- OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
- };
-
// Construct the muxer with the file descriptor. Note that the MediaMuxer
// will close this file at stop().
MediaMuxer(int fd, OutputFormat format);
@@ -117,10 +112,25 @@
status_t writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,
int64_t timeUs, uint32_t flags) ;
+ /**
+ * Gets the number of tracks added successfully. Should be called in
+ * INITIALIZED(after constructor) or STARTED(after start()) state.
+ * @return the number of tracks or -1 in wrong state.
+ */
+ ssize_t getTrackCount();
+
+ /**
+ * Gets the format of the track by their index.
+ * @param idx : index of the track whose format is wanted.
+ * @return smart pointer to AMessage containing the format details.
+ */
+ sp<AMessage> getTrackFormat(size_t idx);
+
private:
const OutputFormat mFormat;
sp<MediaWriter> mWriter;
Vector< sp<MediaAdapter> > mTrackList; // Each track has its MediaAdapter.
+ Vector< sp<AMessage> > mFormatList; // Format of each track.
sp<MetaData> mFileMeta; // Metadata for the whole file.
Mutex mMuxerLock;
diff --git a/media/libstagefright/include/media/stagefright/MediaMuxerBase.h b/media/libstagefright/include/media/stagefright/MediaMuxerBase.h
new file mode 100644
index 0000000..f02d510
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaMuxerBase.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef MEDIA_MUXER_BASE_H_
+#define MEDIA_MUXER_BASE_H_
+
+#include <utils/RefBase.h>
+#include "media/stagefright/foundation/ABase.h"
+
+namespace android {
+
+struct ABuffer;
+struct AMessage;
+
+// MediaMuxer is used to mux multiple tracks into a video. Currently, we only
+// support a mp4 file as the output.
+// The expected calling order of the functions is:
+// Constructor -> addTrack+ -> start -> writeSampleData+ -> stop
+// If muxing operation need to be cancelled, the app is responsible for
+// deleting the output file after stop.
+struct MediaMuxerBase : public RefBase {
+public:
+ // Please update media/java/android/media/MediaMuxer.java if the
+ // OutputFormat is updated.
+ enum OutputFormat {
+ OUTPUT_FORMAT_MPEG_4 = 0,
+ OUTPUT_FORMAT_WEBM = 1,
+ OUTPUT_FORMAT_THREE_GPP = 2,
+ OUTPUT_FORMAT_HEIF = 3,
+ OUTPUT_FORMAT_OGG = 4,
+ OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
+ };
+
+ // Construct the muxer with the file descriptor. Note that the MediaMuxer
+ // will close this file at stop().
+ MediaMuxerBase() {};
+
+ virtual ~MediaMuxerBase() {};
+
+ /**
+ * Add a track with its format information. This should be
+ * called before start().
+ * @param format the track's format.
+ * @return the track's index or negative number if error.
+ */
+ virtual ssize_t addTrack(const sp<AMessage> &format) = 0;
+
+ /**
+ * Start muxing. Make sure all the tracks have been added before
+ * calling this.
+ */
+ virtual status_t start() = 0;
+
+ /**
+ * Set the orientation hint.
+ * @param degrees The rotation degrees. It has to be either 0,
+ * 90, 180 or 270.
+ * @return OK if no error.
+ */
+ virtual status_t setOrientationHint(int degrees) = 0;
+
+ /**
+ * Set the location.
+ * @param latitude The latitude in degree x 1000. Its value must be in the range
+ * [-900000, 900000].
+ * @param longitude The longitude in degree x 1000. Its value must be in the range
+ * [-1800000, 1800000].
+ * @return OK if no error.
+ */
+ virtual status_t setLocation(int latitude, int longitude) = 0;
+
+ /**
+ * Stop muxing.
+ * This method is a blocking call. Depending on how
+ * much data is bufferred internally, the time needed for stopping
+ * the muxer may be time consuming. UI thread is
+ * not recommended for launching this call.
+ * @return OK if no error.
+ */
+ virtual status_t stop() = 0;
+
+ /**
+ * Send a sample buffer for muxing.
+ * The buffer can be reused once this method returns. Typically,
+ * this function won't be blocked for very long, and thus there
+ * is no need to use a separate thread calling this method to
+ * push a buffer.
+ * @param buffer the incoming sample buffer.
+ * @param trackIndex the buffer's track index number.
+ * @param timeUs the buffer's time stamp.
+ * @param flags the only supported flag for now is
+ * MediaCodec::BUFFER_FLAG_SYNCFRAME.
+ * @return OK if no error.
+ */
+ virtual status_t writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,
+ int64_t timeUs, uint32_t flags) = 0 ;
+
+ /**
+ * Gets the number of tracks added successfully. Should be called in
+ * INITIALIZED(after constructor) or STARTED(after start()) state.
+ * @return the number of tracks or -1 in wrong state.
+ */
+ virtual ssize_t getTrackCount() = 0;
+
+ /**
+ * Gets the format of the track by their index.
+ * @param idx : index of the track whose format is wanted.
+ * @return smart pointer to AMessage containing the format details.
+ */
+ virtual sp<AMessage> getTrackFormat(size_t idx) = 0;
+
+private:
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaMuxerBase);
+};
+
+} // namespace android
+
+#endif // MEDIA_MUXER_BASE_H_
+
diff --git a/media/libstagefright/include/media/stagefright/MetaDataBase.h b/media/libstagefright/include/media/stagefright/MetaDataBase.h
index 940bd86..408872f 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -264,6 +264,11 @@
// Slow-motion markers
kKeySlowMotionMarkers = 'slmo', // raw data, byte array following spec for
// MediaFormat#KEY_SLOW_MOTION_MARKERS
+
+ kKeySampleFileOffset = 'sfof', // int64_t, sample's offset in a media file.
+ kKeyLastSampleIndexInChunk = 'lsic', //int64_t, index of last sample in a chunk.
+ kKeySampleTimeBeforeAppend = 'lsba', // int64_t, timestamp of last sample of a track.
+
};
enum {
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index d8f2b00..6aa7c0f 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -100,6 +100,10 @@
status_t getAudioPresentations(size_t trackIdx, AudioPresentationCollection *presentations);
+ status_t setPlaybackId(const String8& playbackId);
+
+ const char* getName() const;
+
protected:
virtual ~NuMediaExtractor();
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index 0c65e9e..07fc5de 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -419,6 +419,7 @@
EXPORT
media_status_t AMediaExtractor_getSampleFormat(AMediaExtractor *ex, AMediaFormat *fmt) {
+ ALOGV("AMediaExtractor_getSampleFormat");
if (fmt == NULL) {
return AMEDIA_ERROR_INVALID_PARAMETER;
}
@@ -428,6 +429,9 @@
if (err != OK) {
return translate_error(err);
}
+#ifdef LOG_NDEBUG
+ sampleMeta->dumpToLog();
+#endif
sp<AMessage> meta;
AMediaFormat_getFormat(fmt, &meta);
@@ -483,6 +487,19 @@
meta->setBuffer(AMEDIAFORMAT_KEY_AUDIO_PRESENTATION_INFO, audioPresentationsData);
}
+ int64_t val64;
+ if (sampleMeta->findInt64(kKeySampleFileOffset, &val64)) {
+ meta->setInt64("sample-file-offset", val64);
+ ALOGV("SampleFileOffset Found");
+ }
+ if (sampleMeta->findInt64(kKeyLastSampleIndexInChunk, &val64)) {
+ meta->setInt64("last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
+ val64);
+ ALOGV("kKeyLastSampleIndexInChunk Found");
+ }
+
+ ALOGV("AMediaFormat_toString:%s", AMediaFormat_toString(fmt));
+
return AMEDIA_OK;
}
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index 1773023..c1793ce 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -334,6 +334,7 @@
EXPORT const char* AMEDIAFORMAT_KEY_IS_SYNC_FRAME = "is-sync-frame";
EXPORT const char* AMEDIAFORMAT_KEY_I_FRAME_INTERVAL = "i-frame-interval";
EXPORT const char* AMEDIAFORMAT_KEY_LANGUAGE = "language";
+EXPORT const char* AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK = "last-sample-index-in-chunk";
EXPORT const char* AMEDIAFORMAT_KEY_LATENCY = "latency";
EXPORT const char* AMEDIAFORMAT_KEY_LEVEL = "level";
EXPORT const char* AMEDIAFORMAT_KEY_LOCATION = "location";
@@ -359,7 +360,9 @@
EXPORT const char* AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
EXPORT const char* AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
EXPORT const char* AMEDIAFORMAT_KEY_ROTATION = "rotation-degrees";
+EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_FILE_OFFSET = "sample-file-offset";
EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_RATE = "sample-rate";
+EXPORT const char* AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND = "sample-time-before-append";
EXPORT const char* AMEDIAFORMAT_KEY_SAR_HEIGHT = "sar-height";
EXPORT const char* AMEDIAFORMAT_KEY_SAR_WIDTH = "sar-width";
EXPORT const char* AMEDIAFORMAT_KEY_SEI = "sei";
diff --git a/media/ndk/NdkMediaMuxer.cpp b/media/ndk/NdkMediaMuxer.cpp
index d1992bf..1965e62 100644
--- a/media/ndk/NdkMediaMuxer.cpp
+++ b/media/ndk/NdkMediaMuxer.cpp
@@ -17,28 +17,24 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "NdkMediaMuxer"
-
-#include <media/NdkMediaMuxer.h>
+#include <android_util_Binder.h>
+#include <jni.h>
+#include <media/IMediaHTTPService.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaErrorPriv.h>
#include <media/NdkMediaFormatPriv.h>
-
-
-#include <utils/Log.h>
-#include <utils/StrongPointer.h>
+#include <media/NdkMediaMuxer.h>
+#include <media/stagefright/MediaAppender.h>
+#include <media/stagefright/MediaMuxer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MediaMuxer.h>
-#include <media/IMediaHTTPService.h>
-#include <android_util_Binder.h>
-
-#include <jni.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
using namespace android;
struct AMediaMuxer {
- sp<MediaMuxer> mImpl;
-
+ sp<MediaMuxerBase> mImpl;
};
extern "C" {
@@ -46,8 +42,15 @@
EXPORT
AMediaMuxer* AMediaMuxer_new(int fd, OutputFormat format) {
ALOGV("ctor");
- AMediaMuxer *mData = new AMediaMuxer();
- mData->mImpl = new MediaMuxer(fd, (android::MediaMuxer::OutputFormat)format);
+ AMediaMuxer *mData = new (std::nothrow) AMediaMuxer();
+ if (mData == nullptr) {
+ return nullptr;
+ }
+ mData->mImpl = new (std::nothrow) MediaMuxer(fd, (android::MediaMuxer::OutputFormat)format);
+ if (mData->mImpl == nullptr) {
+ delete mData;
+ return nullptr;
+ }
return mData;
}
@@ -94,6 +97,34 @@
muxer->mImpl->writeSampleData(buf, trackIdx, info->presentationTimeUs, info->flags));
}
+EXPORT
+AMediaMuxer* AMediaMuxer_append(int fd, AppendMode mode) {
+ ALOGV("append");
+ AMediaMuxer* mData = new (std::nothrow) AMediaMuxer();
+ if (mData == nullptr) {
+ return nullptr;
+ }
+ mData->mImpl = MediaAppender::create(fd, (android::MediaAppender::AppendMode)mode);
+ if (mData->mImpl == nullptr) {
+ delete mData;
+ return nullptr;
+ }
+ return mData;
+}
+
+EXPORT
+ssize_t AMediaMuxer_getTrackCount(AMediaMuxer* muxer) {
+ return muxer->mImpl->getTrackCount();
+}
+
+EXPORT
+AMediaFormat* AMediaMuxer_getTrackFormat(AMediaMuxer* muxer, size_t idx) {
+ sp<AMessage> format = muxer->mImpl->getTrackFormat(idx);
+ if (format != nullptr) {
+ return AMediaFormat_fromMsg(&format);
+ }
+ return nullptr;
+}
} // extern "C"
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index 476bbd9..fbd855d 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -307,6 +307,9 @@
extern const char* AMEDIAFORMAT_KEY_THUMBNAIL_CSD_AV1C __INTRODUCED_IN(31);
extern const char* AMEDIAFORMAT_KEY_XMP_OFFSET __INTRODUCED_IN(31);
extern const char* AMEDIAFORMAT_KEY_XMP_SIZE __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_KEY_SAMPLE_FILE_OFFSET __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK __INTRODUCED_IN(31);
+extern const char* AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND __INTRODUCED_IN(31);
extern const char* AMEDIAFORMAT_VIDEO_QP_B_MAX __INTRODUCED_IN(31);
extern const char* AMEDIAFORMAT_VIDEO_QP_B_MIN __INTRODUCED_IN(31);
diff --git a/media/ndk/include/media/NdkMediaMuxer.h b/media/ndk/include/media/NdkMediaMuxer.h
index 519e249..866ebfd 100644
--- a/media/ndk/include/media/NdkMediaMuxer.h
+++ b/media/ndk/include/media/NdkMediaMuxer.h
@@ -54,6 +54,17 @@
AMEDIAMUXER_OUTPUT_FORMAT_THREE_GPP = 2,
} OutputFormat;
+typedef enum {
+ /* Last group of pictures(GOP) of video track can be incomplete, so it would be safe to
+ * scrap that and rewrite. If both audio and video tracks are present in a file, then
+ * samples of audio track after last GOP of video would be scrapped too.
+ * If only audio track is present, then no sample would be discarded.
+ */
+ AMEDIAMUXER_APPEND_IGNORE_LAST_VIDEO_GOP = 0,
+ // Keep all existing samples as it is and append new samples after that only.
+ AMEDIAMUXER_APPEND_TO_EXISTING_DATA = 1,
+} AppendMode;
+
/**
* Create new media muxer.
*
@@ -138,6 +149,41 @@
size_t trackIdx, const uint8_t *data,
const AMediaCodecBufferInfo *info) __INTRODUCED_IN(21);
+/**
+ * Creates a new media muxer for appending data to an existing MPEG4 file.
+ * This is a synchronous API call and could take a while to return if the existing file is large.
+ * Works for only MPEG4 files that contain a) a single audio track, b) a single video track,
+ * c) a single audio and a single video track.
+ * @param(fd): needs to be opened with read and write permission. Does not take ownership of
+ * this fd i.e., caller is responsible for closing fd.
+ * @param(mode): AppendMode is an enum that specifies one of the modes of appending data.
+ * @return : Pointer to AMediaMuxer if the file(fd) has tracks already, otherwise, nullptr.
+ * {@link AMediaMuxer_delete} should be used to free the returned pointer.
+ *
+ * Available since API level 31.
+ */
+AMediaMuxer* AMediaMuxer_append(int fd, AppendMode mode) __INTRODUCED_IN(31);
+
+/**
+ * Returns the number of tracks added in the file passed to {@link AMediaMuxer_new} or
+ * the number of existing tracks in the file passed to {@link AMediaMuxer_append}.
+ * Should be called in INITIALIZED or STARTED state, otherwise returns -1.
+ *
+ * Available since API level 31.
+ */
+ssize_t AMediaMuxer_getTrackCount(AMediaMuxer*) __INTRODUCED_IN(31);
+
+/**
+ * Returns AMediaFormat of the added track with index idx in the file passed to
+ * {@link AMediaMuxer_new} or the AMediaFormat of the existing track with index idx
+ * in the file passed to {@link AMediaMuxer_append}.
+ * Should be called in INITIALIZED or STARTED state, otherwise returns nullptr.
+ * {@link AMediaFormat_delete} should be used to free the returned pointer.
+ *
+ * Available since API level 31.
+ */
+AMediaFormat* AMediaMuxer_getTrackFormat(AMediaMuxer* muxer, size_t idx) __INTRODUCED_IN(31);
+
__END_DECLS
#endif // _NDK_MEDIA_MUXER_H
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index eead681..7e9e57e 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -109,6 +109,7 @@
AMEDIAFORMAT_KEY_IS_SYNC_FRAME; # var introduced=29
AMEDIAFORMAT_KEY_I_FRAME_INTERVAL; # var introduced=21
AMEDIAFORMAT_KEY_LANGUAGE; # var introduced=21
+ AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK; # var introduced=31
AMEDIAFORMAT_KEY_LATENCY; # var introduced=28
AMEDIAFORMAT_KEY_LEVEL; # var introduced=28
AMEDIAFORMAT_KEY_LOCATION; # var introduced=29
@@ -134,6 +135,8 @@
AMEDIAFORMAT_KEY_PUSH_BLANK_BUFFERS_ON_STOP; # var introduced=21
AMEDIAFORMAT_KEY_REPEAT_PREVIOUS_FRAME_AFTER; # var introduced=21
AMEDIAFORMAT_KEY_ROTATION; # var introduced=28
+ AMEDIAFORMAT_KEY_SAMPLE_FILE_OFFSET; # var introduced=31
+ AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND; # var introduced=31
AMEDIAFORMAT_KEY_SAMPLE_RATE; # var introduced=21
AMEDIAFORMAT_KEY_SAR_HEIGHT; # var introduced=29
AMEDIAFORMAT_KEY_SAR_WIDTH; # var introduced=29
@@ -286,7 +289,10 @@
AMediaFormat_setString;
AMediaFormat_toString;
AMediaMuxer_addTrack;
+ AMediaMuxer_append; # introduced=31
AMediaMuxer_delete;
+ AMediaMuxer_getTrackCount; # introduced=31
+ AMediaMuxer_getTrackFormat; # introduced=31
AMediaMuxer_new;
AMediaMuxer_setLocation;
AMediaMuxer_setOrientationHint;
diff --git a/services/audiopolicy/service/Android.bp b/services/audiopolicy/service/Android.bp
index d5ba756..14be671 100644
--- a/services/audiopolicy/service/Android.bp
+++ b/services/audiopolicy/service/Android.bp
@@ -23,6 +23,7 @@
],
shared_libs: [
+ "libactivitymanager_aidl",
"libaudioclient",
"libaudioclient_aidl_conversion",
"libaudiofoundation",
@@ -67,6 +68,7 @@
],
export_shared_lib_headers: [
+ "libactivitymanager_aidl",
"libsensorprivacy",
"media_permission-aidl-cpp",
],
diff --git a/services/oboeservice/AAudioServiceEndpointShared.cpp b/services/oboeservice/AAudioServiceEndpointShared.cpp
index 501e8c0..0d453cf 100644
--- a/services/oboeservice/AAudioServiceEndpointShared.cpp
+++ b/services/oboeservice/AAudioServiceEndpointShared.cpp
@@ -111,7 +111,7 @@
if (!endpoint->isConnected()) {
ALOGD("%s() call safeReleaseCloseFromCallback()", __func__);
// Release and close under a lock with no check for callback collisions.
- endpoint->getStreamInternal()->safeReleaseCloseFromCallback();
+ endpoint->getStreamInternal()->safeReleaseCloseInternal();
}
return result;