Transcoder: Add support for pausing transcoding on a sync frame.
- Added support for stopping transcoders on a sync frame.
- Refactored MediaTrackTranscoders and MediaSampleWriter to stop()
asynchronously.
- Fixed callback and error handling logic in MediaTranscoder.
- Added tests for pause and stopping on sync frame.
Bug: 162886306
Test: Unit tests.
Change-Id: If689a10dfee198c674c4c13b865a7c56a901e075
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
index 46f3e9b..0a41b00 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
@@ -179,8 +179,6 @@
class TestCallbacks : public MediaSampleWriter::CallbackInterface {
public:
- TestCallbacks(bool expectSuccess = true) : mExpectSuccess(expectSuccess) {}
-
bool hasFinished() {
std::unique_lock<std::mutex> lock(mMutex);
return mFinished;
@@ -191,12 +189,15 @@
media_status_t status) override {
std::unique_lock<std::mutex> lock(mMutex);
EXPECT_FALSE(mFinished);
- if (mExpectSuccess) {
- EXPECT_EQ(status, AMEDIA_OK);
- } else {
- EXPECT_NE(status, AMEDIA_OK);
- }
mFinished = true;
+ mStatus = status;
+ mCondition.notify_all();
+ }
+
+ virtual void onStopped(const MediaSampleWriter* writer __unused) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_FALSE(mFinished);
+ mStopped = true;
mCondition.notify_all();
}
@@ -213,18 +214,20 @@
void waitForWritingFinished() {
std::unique_lock<std::mutex> lock(mMutex);
- while (!mFinished) {
+ while (!mFinished && !mStopped) {
mCondition.wait(lock);
}
}
uint32_t getProgressUpdateCount() const { return mProgressUpdateCount; }
+ bool wasStopped() const { return mStopped; }
private:
std::mutex mMutex;
std::condition_variable mCondition;
bool mFinished = false;
- bool mExpectSuccess;
+ bool mStopped = false;
+ media_status_t mStatus = AMEDIA_OK;
int32_t mLastProgress = -1;
uint32_t mProgressUpdateCount = 0;
};
@@ -316,8 +319,7 @@
TEST_F(MediaSampleWriterTests, TestDoubleStartStop) {
std::shared_ptr<MediaSampleWriter> writer = MediaSampleWriter::Create();
- std::shared_ptr<TestCallbacks> callbacks =
- std::make_shared<TestCallbacks>(false /* expectSuccess */);
+ std::shared_ptr<TestCallbacks> callbacks = std::make_shared<TestCallbacks>();
EXPECT_TRUE(writer->init(mTestMuxer, callbacks));
const TestMediaSource& mediaSource = getMediaSource();
@@ -327,9 +329,10 @@
ASSERT_TRUE(writer->start());
EXPECT_FALSE(writer->start());
- EXPECT_TRUE(writer->stop());
- EXPECT_TRUE(callbacks->hasFinished());
- EXPECT_FALSE(writer->stop());
+ writer->stop();
+ writer->stop();
+ callbacks->waitForWritingFinished();
+ EXPECT_TRUE(callbacks->wasStopped());
}
TEST_F(MediaSampleWriterTests, TestStopWithoutStart) {
@@ -340,7 +343,7 @@
EXPECT_NE(writer->addTrack(mediaSource.mTrackFormats[0]), nullptr);
EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
- EXPECT_FALSE(writer->stop());
+ writer->stop();
EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
}
@@ -468,7 +471,6 @@
}
EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
- EXPECT_TRUE(writer->stop());
EXPECT_TRUE(mTestCallbacks->hasFinished());
}
@@ -541,7 +543,6 @@
// Wait for writer.
mTestCallbacks->waitForWritingFinished();
- EXPECT_TRUE(writer->stop());
// Compare output file with source.
mediaSource.reset();
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index 83f0a4a..21f0b86 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -61,13 +61,10 @@
}
ASSERT_NE(mTranscoder, nullptr);
- initSampleReader();
+ initSampleReader("/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4");
}
- void initSampleReader() {
- const char* sourcePath =
- "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
-
+ void initSampleReader(const char* sourcePath) {
const int sourceFd = open(sourcePath, O_RDONLY);
ASSERT_GT(sourceFd, 0);
@@ -157,16 +154,23 @@
ASSERT_TRUE(mTranscoder->start());
drainOutputSamples();
EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
- EXPECT_TRUE(mTranscoder->stop());
+ EXPECT_TRUE(mCallback->transcodingFinished());
EXPECT_TRUE(mGotEndOfStream);
}
TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
LOG(DEBUG) << "Testing StopNormalOperation";
+
+ // Use a longer test asset to make sure that transcoding can be stopped.
+ initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
AMEDIA_OK);
EXPECT_TRUE(mTranscoder->start());
- EXPECT_TRUE(mTranscoder->stop());
+ mCallback->waitUntilTrackFormatAvailable();
+ mTranscoder->stop();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(mCallback->transcodingWasStopped());
}
TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
@@ -178,17 +182,23 @@
LOG(DEBUG) << "Testing StopWithoutStart";
EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
AMEDIA_OK);
- EXPECT_FALSE(mTranscoder->stop());
+ mTranscoder->stop();
}
TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
LOG(DEBUG) << "Testing DoubleStartStop";
+
+ // Use a longer test asset to make sure that transcoding can be stopped.
+ initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
AMEDIA_OK);
EXPECT_TRUE(mTranscoder->start());
EXPECT_FALSE(mTranscoder->start());
- EXPECT_TRUE(mTranscoder->stop());
- EXPECT_FALSE(mTranscoder->stop());
+ mTranscoder->stop();
+ mTranscoder->stop();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+ EXPECT_TRUE(mCallback->transcodingWasStopped());
}
TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
@@ -212,7 +222,8 @@
EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
AMEDIA_OK);
EXPECT_TRUE(mTranscoder->start());
- EXPECT_TRUE(mTranscoder->stop());
+ mTranscoder->stop();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
EXPECT_FALSE(mTranscoder->start());
}
@@ -223,7 +234,7 @@
ASSERT_TRUE(mTranscoder->start());
drainOutputSamples();
EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
- EXPECT_TRUE(mTranscoder->stop());
+ mTranscoder->stop();
EXPECT_FALSE(mTranscoder->start());
EXPECT_TRUE(mGotEndOfStream);
}
@@ -235,7 +246,7 @@
ASSERT_TRUE(mTranscoder->start());
drainOutputSamples(1 /* numSamplesToSave */);
EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
- EXPECT_TRUE(mTranscoder->stop());
+ mTranscoder->stop();
EXPECT_TRUE(mGotEndOfStream);
mTranscoder.reset();
@@ -251,7 +262,8 @@
ASSERT_TRUE(mTranscoder->start());
drainOutputSamples(1 /* numSamplesToSave */);
mSamplesSavedSemaphore.wait();
- EXPECT_TRUE(mTranscoder->stop());
+ mTranscoder->stop();
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
std::this_thread::sleep_for(std::chrono::milliseconds(20));
mSavedSamples.clear();
@@ -272,6 +284,44 @@
AMEDIA_OK);
}
+TEST_P(MediaTrackTranscoderTests, StopOnSync) {
+ LOG(DEBUG) << "Testing StopOnSync";
+
+ // Use a longer test asset to make sure there is a GOP to finish.
+ initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
+ EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+ AMEDIA_OK);
+
+ bool lastSampleWasEos = false;
+ bool lastRealSampleWasSync = false;
+ OneShotSemaphore samplesReceivedSemaphore;
+ uint32_t sampleCount = 0;
+
+ mTranscoder->setSampleConsumer([&](const std::shared_ptr<MediaSample>& sample) {
+ ASSERT_NE(sample, nullptr);
+
+ if ((lastSampleWasEos = sample->info.flags & SAMPLE_FLAG_END_OF_STREAM)) {
+ samplesReceivedSemaphore.signal();
+ return;
+ }
+ lastRealSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
+
+ if (++sampleCount >= 10) { // Wait for a few samples before stopping.
+ samplesReceivedSemaphore.signal();
+ }
+ });
+
+ ASSERT_TRUE(mTranscoder->start());
+ samplesReceivedSemaphore.wait();
+ mTranscoder->stop(true /* stopOnSync */);
+ EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+
+ EXPECT_TRUE(lastSampleWasEos);
+ EXPECT_TRUE(lastRealSampleWasSync);
+ EXPECT_TRUE(mCallback->transcodingWasStopped());
+}
+
}; // namespace android
using namespace android;
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index 1bf2d8c..2487e08 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -99,11 +99,11 @@
}
}
media_status_t mStatus = AMEDIA_OK;
+ bool mFinished = false;
private:
std::mutex mMutex;
std::condition_variable mCondition;
- bool mFinished = false;
bool mProgressMade = false;
};
@@ -145,6 +145,8 @@
kRunToCompletion,
kCancelAfterProgress,
kCancelAfterStart,
+ kPauseAfterProgress,
+ kPauseAfterStart,
} TranscodeExecutionControl;
using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
@@ -181,7 +183,10 @@
media_status_t startStatus = transcoder->start();
EXPECT_EQ(startStatus, AMEDIA_OK);
+
if (startStatus == AMEDIA_OK) {
+ std::shared_ptr<ndk::ScopedAParcel> pausedState;
+
switch (executionControl) {
case kCancelAfterProgress:
mCallbacks->waitForProgressMade();
@@ -189,6 +194,12 @@
case kCancelAfterStart:
transcoder->cancel();
break;
+ case kPauseAfterProgress:
+ mCallbacks->waitForProgressMade();
+ FALLTHROUGH_INTENDED;
+ case kPauseAfterStart:
+ transcoder->pause(&pausedState);
+ break;
case kRunToCompletion:
default:
mCallbacks->waitForTranscodingFinished();
@@ -360,9 +371,10 @@
const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
- for (int i = 0; i < 32; ++i) {
+ for (int i = 0; i < 20; ++i) {
EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterProgress),
AMEDIA_OK);
+ EXPECT_FALSE(mCallbacks->mFinished);
mCallbacks = std::make_shared<TestCallbacks>();
}
}
@@ -371,9 +383,34 @@
const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
- for (int i = 0; i < 32; ++i) {
+ for (int i = 0; i < 20; ++i) {
EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterStart),
AMEDIA_OK);
+ EXPECT_FALSE(mCallbacks->mFinished);
+ mCallbacks = std::make_shared<TestCallbacks>();
+ }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterProgress) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+ for (int i = 0; i < 20; ++i) {
+ EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterProgress),
+ AMEDIA_OK);
+ EXPECT_FALSE(mCallbacks->mFinished);
+ mCallbacks = std::make_shared<TestCallbacks>();
+ }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterStart) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+ for (int i = 0; i < 20; ++i) {
+ EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterStart),
+ AMEDIA_OK);
+ EXPECT_FALSE(mCallbacks->mFinished);
mCallbacks = std::make_shared<TestCallbacks>();
}
}
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 9713e17..5071efd 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -183,7 +183,6 @@
callback->waitUntilFinished();
EXPECT_EQ(sampleCount, sampleChecksums.size());
- EXPECT_TRUE(transcoder.stop());
}
/** Class for testing PassthroughTrackTranscoder's built in buffer pool. */
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 8d05353..a782f71 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -33,20 +33,14 @@
AMediaFormat* sourceFormat, bool includeBitrate = true) {
// Default video destination format setup.
static constexpr float kFrameRate = 30.0f;
- static constexpr float kIFrameInterval = 30.0f;
static constexpr int32_t kBitRate = 2 * 1000 * 1000;
- static constexpr int32_t kColorFormatSurface = 0x7f000789;
AMediaFormat* destinationFormat = AMediaFormat_new();
AMediaFormat_copy(destinationFormat, sourceFormat);
AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
- AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
- kIFrameInterval);
if (includeBitrate) {
AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
}
- AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
- kColorFormatSurface);
return std::shared_ptr<AMediaFormat>(destinationFormat, &AMediaFormat_delete);
}
@@ -70,6 +64,13 @@
mTranscodingFinishedCondition.notify_all();
}
+ virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mTranscodingFinished = true;
+ mTranscodingStopped = true;
+ mTranscodingFinishedCondition.notify_all();
+ }
+
void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
std::unique_lock<std::mutex> lock(mMutex);
mTranscodingFinished = true;
@@ -93,12 +94,18 @@
}
}
+ bool transcodingWasStopped() const { return mTranscodingFinished && mTranscodingStopped; }
+ bool transcodingFinished() const {
+ return mTranscodingFinished && !mTranscodingStopped && mStatus == AMEDIA_OK;
+ }
+
private:
media_status_t mStatus = AMEDIA_OK;
std::mutex mMutex;
std::condition_variable mTranscodingFinishedCondition;
std::condition_variable mTrackFormatAvailableCondition;
bool mTranscodingFinished = false;
+ bool mTranscodingStopped = false;
bool mTrackFormatAvailable = false;
};
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 1b5bd13..4ede97f 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -135,7 +135,6 @@
});
EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
- EXPECT_TRUE(transcoder->stop());
}
TEST_F(VideoTrackTranscoderTests, PreserveBitrate) {
@@ -160,7 +159,8 @@
auto outputFormat = transcoder->getOutputFormat();
ASSERT_NE(outputFormat, nullptr);
- ASSERT_TRUE(transcoder->stop());
+ transcoder->stop();
+ EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
int32_t outBitrate;
EXPECT_TRUE(AMediaFormat_getInt32(outputFormat.get(), AMEDIAFORMAT_KEY_BIT_RATE, &outBitrate));
@@ -205,7 +205,8 @@
// Wait for the encoder to output samples before stopping and releasing the transcoder.
semaphore.wait();
- EXPECT_TRUE(transcoder->stop());
+ transcoder->stop();
+ EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
transcoder.reset();
// Return buffers to the codec so that it can resume processing, but keep one buffer to avoid