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/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
index 53d567e..d2f6c40 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -181,6 +181,11 @@
if (mEosReached) {
return AMEDIA_ERROR_END_OF_STREAM;
}
+
+ if (!mEnforceSequentialAccess) {
+ return moveToTrack_l(trackIndex);
+ }
+
return AMEDIA_OK;
}
diff --git a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
index afa5021..389b941 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
@@ -79,7 +79,7 @@
MediaSampleWriter::~MediaSampleWriter() {
if (mState == STARTED) {
- stop(); // Join thread.
+ stop();
}
}
@@ -169,38 +169,41 @@
}
mState = STARTED;
- mThread = std::thread([this] {
- media_status_t status = writeSamples();
+ std::thread([this] {
+ bool wasStopped = false;
+ media_status_t status = writeSamples(&wasStopped);
if (auto callbacks = mCallbacks.lock()) {
- callbacks->onFinished(this, status);
+ if (wasStopped && status == AMEDIA_OK) {
+ callbacks->onStopped(this);
+ } else {
+ callbacks->onFinished(this, status);
+ }
}
- });
+ }).detach();
return true;
}
-bool MediaSampleWriter::stop() {
+void MediaSampleWriter::stop() {
{
std::scoped_lock lock(mMutex);
if (mState != STARTED) {
LOG(ERROR) << "Sample writer is not started.";
- return false;
+ return;
}
mState = STOPPED;
}
mSampleSignal.notify_all();
- mThread.join();
- return true;
}
-media_status_t MediaSampleWriter::writeSamples() {
+media_status_t MediaSampleWriter::writeSamples(bool* wasStopped) {
media_status_t muxerStatus = mMuxer->start();
if (muxerStatus != AMEDIA_OK) {
LOG(ERROR) << "Error starting muxer: " << muxerStatus;
return muxerStatus;
}
- media_status_t writeStatus = runWriterLoop();
+ media_status_t writeStatus = runWriterLoop(wasStopped);
if (writeStatus != AMEDIA_OK) {
LOG(ERROR) << "Error writing samples: " << writeStatus;
}
@@ -213,7 +216,7 @@
return writeStatus != AMEDIA_OK ? writeStatus : muxerStatus;
}
-media_status_t MediaSampleWriter::runWriterLoop() NO_THREAD_SAFETY_ANALYSIS {
+media_status_t MediaSampleWriter::runWriterLoop(bool* wasStopped) NO_THREAD_SAFETY_ANALYSIS {
AMediaCodecBufferInfo bufferInfo;
int32_t lastProgressUpdate = 0;
int trackEosCount = 0;
@@ -242,8 +245,9 @@
mSampleSignal.wait(lock);
}
- if (mState != STARTED) {
- return AMEDIA_ERROR_UNKNOWN; // TODO(lnilsson): Custom error code.
+ if (mState == STOPPED) {
+ *wasStopped = true;
+ return AMEDIA_OK;
}
auto& topEntry = mSampleQueue.top();
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 698594f..15f7427 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -69,41 +69,44 @@
LOG(ERROR) << "TrackTranscoder must be configured before started";
return false;
}
+ mState = STARTED;
- mTranscodingThread = std::thread([this] {
- media_status_t status = runTranscodeLoop();
+ std::thread([this] {
+ bool stopped = false;
+ media_status_t status = runTranscodeLoop(&stopped);
+
+ // Output an EOS sample if the transcoder was stopped.
+ if (stopped) {
+ auto sample = std::make_shared<MediaSample>();
+ sample->info.flags = SAMPLE_FLAG_END_OF_STREAM;
+ onOutputSampleAvailable(sample);
+ }
// Notify the client.
if (auto callbacks = mTranscoderCallback.lock()) {
- if (status != AMEDIA_OK) {
- callbacks->onTrackError(this, status);
- } else {
+ if (stopped) {
+ callbacks->onTrackStopped(this);
+ } else if (status == AMEDIA_OK) {
callbacks->onTrackFinished(this);
+ } else {
+ callbacks->onTrackError(this, status);
}
}
- });
+ }).detach();
- mState = STARTED;
return true;
}
-bool MediaTrackTranscoder::stop() {
+void MediaTrackTranscoder::stop(bool stopOnSyncSample) {
std::scoped_lock lock{mStateMutex};
- if (mState == STARTED) {
+ if (mState == STARTED || (mStopRequest == STOP_ON_SYNC && !stopOnSyncSample)) {
+ mStopRequest = stopOnSyncSample ? STOP_ON_SYNC : STOP_NOW;
abortTranscodeLoop();
- mMediaSampleReader->setEnforceSequentialAccess(false);
- mTranscodingThread.join();
- {
- std::scoped_lock lock{mSampleMutex};
- mSampleQueue.abort(); // Release any buffered samples.
- }
mState = STOPPED;
- return true;
+ } else {
+ LOG(WARNING) << "TrackTranscoder must be started before stopped";
}
-
- LOG(ERROR) << "TrackTranscoder must be started before stopped";
- return false;
}
void MediaTrackTranscoder::notifyTrackFormatAvailable() {
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
index d89b58f..dc49014 100644
--- a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -69,38 +69,67 @@
return format;
}
-void MediaTranscoder::sendCallback(media_status_t status) {
- // If the transcoder is already cancelled explicitly, don't send any error callbacks.
- // Tracks and sample writer will report errors for abort. However, currently we can't
- // tell it apart from real errors. Ideally we still want to report real errors back
- // to client, as there is a small chance that explicit abort and the real error come
- // at around the same time, we should report that if abort has a specific error code.
- // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
- // the same time of the abort, we should still report the finish back to the client.
- if (mCancelled && status != AMEDIA_OK) {
+void MediaTranscoder::onThreadFinished(const void* thread, media_status_t threadStatus,
+ bool threadStopped) {
+ LOG(DEBUG) << "Thread " << thread << " finished with status " << threadStatus << " stopped "
+ << threadStopped;
+
+ // Stop all threads if one reports an error.
+ if (threadStatus != AMEDIA_OK) {
+ requestStop(false /* stopOnSync */);
+ }
+
+ std::scoped_lock lock{mThreadStateMutex};
+
+ // Record the change.
+ mThreadStates[thread] = DONE;
+ if (threadStatus != AMEDIA_OK && mTranscoderStatus == AMEDIA_OK) {
+ mTranscoderStatus = threadStatus;
+ }
+
+ mTranscoderStopped |= threadStopped;
+
+ // Check if all threads are done. Note that if all transcoders have stopped but the sample
+ // writer has not yet started, it never will.
+ bool transcodersDone = true;
+ ThreadState sampleWriterState = PENDING;
+ for (const auto& it : mThreadStates) {
+ LOG(DEBUG) << " Thread " << it.first << " state" << it.second;
+ if (it.first == static_cast<const void*>(mSampleWriter.get())) {
+ sampleWriterState = it.second;
+ } else {
+ transcodersDone &= (it.second == DONE);
+ }
+ }
+ if (!transcodersDone || sampleWriterState == RUNNING) {
return;
}
- bool expected = false;
- if (mCallbackSent.compare_exchange_strong(expected, true)) {
- if (status == AMEDIA_OK) {
- mCallbacks->onFinished(this);
- } else {
- mCallbacks->onError(this, status);
- }
-
- // Transcoding is done and the callback to the client has been sent, so tear down the
- // pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
- // should clean up the file.
- std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
- asyncCancelThread.detach();
+ // All done. Send callback asynchronously and wake up threads waiting in cancel/pause.
+ mThreadsDone = true;
+ if (!mCallbackSent) {
+ std::thread asyncNotificationThread{[this, self = shared_from_this(),
+ status = mTranscoderStatus,
+ stopped = mTranscoderStopped] {
+ // If the transcoder was stopped that means a caller is waiting in stop or pause
+ // in which case we don't send a callback.
+ if (status != AMEDIA_OK) {
+ mCallbacks->onError(this, status);
+ } else if (!stopped) {
+ mCallbacks->onFinished(this);
+ }
+ mThreadsDoneSignal.notify_all();
+ }};
+ asyncNotificationThread.detach();
+ mCallbackSent = true;
}
}
void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
- LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " format available.";
std::scoped_lock lock{mTracksAddedMutex};
+ const void* sampleWriterPtr = static_cast<const void*>(mSampleWriter.get());
// Ignore duplicate format change.
if (mTracksAdded.count(transcoder) > 0) {
@@ -111,7 +140,7 @@
auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
if (consumer == nullptr) {
LOG(ERROR) << "Unable to add track to sample writer.";
- sendCallback(AMEDIA_ERROR_UNKNOWN);
+ onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
return;
}
@@ -119,34 +148,57 @@
mutableTranscoder->setSampleConsumer(consumer);
mTracksAdded.insert(transcoder);
+ bool errorStarting = false;
if (mTracksAdded.size() == mTrackTranscoders.size()) {
// Enable sequential access mode on the sample reader to achieve optimal read performance.
// This has to wait until all tracks have delivered their output formats and the sample
// writer is started. Otherwise the tracks will not get their output sample queues drained
// and the transcoder could hang due to one track running out of buffers and blocking the
// other tracks from reading source samples before they could output their formats.
- mSampleReader->setEnforceSequentialAccess(true);
- LOG(INFO) << "Starting sample writer.";
- bool started = mSampleWriter->start();
- if (!started) {
- LOG(ERROR) << "Unable to start sample writer.";
- sendCallback(AMEDIA_ERROR_UNKNOWN);
+
+ std::scoped_lock lock{mThreadStateMutex};
+ // Don't start the sample writer if a stop already has been requested.
+ if (!mSampleWriterStopped) {
+ if (!mCancelled) {
+ mSampleReader->setEnforceSequentialAccess(true);
+ }
+ LOG(DEBUG) << "Starting sample writer.";
+ errorStarting = !mSampleWriter->start();
+ if (!errorStarting) {
+ mThreadStates[sampleWriterPtr] = RUNNING;
+ }
}
}
+
+ if (errorStarting) {
+ LOG(ERROR) << "Unable to start sample writer.";
+ onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
+ }
}
void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+ onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, false /* stopped */);
+}
+
+void MediaTranscoder::onTrackStopped(const MediaTrackTranscoder* transcoder) {
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " stopped";
+ onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, true /* stopped */);
}
void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
LOG(ERROR) << "TrackTranscoder " << transcoder << " returned error " << status;
- sendCallback(status);
+ onThreadFinished(static_cast<const void*>(transcoder), status, false /* stopped */);
}
-void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
- LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
- sendCallback(status);
+void MediaTranscoder::onFinished(const MediaSampleWriter* writer, media_status_t status) {
+ LOG(status == AMEDIA_OK ? DEBUG : ERROR) << "Sample writer finished with status " << status;
+ onThreadFinished(static_cast<const void*>(writer), status, false /* stopped */);
+}
+
+void MediaTranscoder::onStopped(const MediaSampleWriter* writer) {
+ LOG(DEBUG) << "Sample writer " << writer << " stopped";
+ onThreadFinished(static_cast<const void*>(writer), AMEDIA_OK, true /* stopped */);
}
void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
@@ -276,6 +328,9 @@
return status;
}
+ std::scoped_lock lock{mThreadStateMutex};
+ mThreadStates[static_cast<const void*>(transcoder.get())] = PENDING;
+
mTrackTranscoders.emplace_back(std::move(transcoder));
return AMEDIA_OK;
}
@@ -300,6 +355,8 @@
return AMEDIA_ERROR_UNKNOWN;
}
+ std::scoped_lock lock{mThreadStateMutex};
+ mThreadStates[static_cast<const void*>(mSampleWriter.get())] = PENDING;
return AMEDIA_OK;
}
@@ -313,21 +370,75 @@
}
// Start transcoders
- for (auto& transcoder : mTrackTranscoders) {
- bool started = transcoder->start();
- if (!started) {
- LOG(ERROR) << "Unable to start track transcoder.";
- cancel();
- return AMEDIA_ERROR_UNKNOWN;
+ bool started = true;
+ {
+ std::scoped_lock lock{mThreadStateMutex};
+ for (auto& transcoder : mTrackTranscoders) {
+ if (!(started = transcoder->start())) {
+ break;
+ }
+ mThreadStates[static_cast<const void*>(transcoder.get())] = RUNNING;
}
}
+ if (!started) {
+ LOG(ERROR) << "Unable to start track transcoder.";
+ cancel();
+ return AMEDIA_ERROR_UNKNOWN;
+ }
return AMEDIA_OK;
}
+media_status_t MediaTranscoder::requestStop(bool stopOnSync) {
+ std::scoped_lock lock{mThreadStateMutex};
+ if (mCancelled) {
+ LOG(DEBUG) << "MediaTranscoder already cancelled";
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ if (!stopOnSync) {
+ mSampleWriterStopped = true;
+ mSampleWriter->stop();
+ }
+
+ mSampleReader->setEnforceSequentialAccess(false);
+ for (auto& transcoder : mTrackTranscoders) {
+ transcoder->stop(stopOnSync);
+ }
+
+ mCancelled = true;
+ return AMEDIA_OK;
+}
+
+void MediaTranscoder::waitForThreads() NO_THREAD_SAFETY_ANALYSIS {
+ std::unique_lock lock{mThreadStateMutex};
+ while (!mThreadsDone) {
+ mThreadsDoneSignal.wait(lock);
+ }
+}
+
media_status_t MediaTranscoder::pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState) {
+ media_status_t status = requestStop(true /* stopOnSync */);
+ if (status != AMEDIA_OK) {
+ return status;
+ }
+
+ waitForThreads();
+
// TODO: write internal states to parcel.
*pausedState = std::shared_ptr<::ndk::ScopedAParcel>(new ::ndk::ScopedAParcel());
- return cancel();
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::cancel() {
+ media_status_t status = requestStop(false /* stopOnSync */);
+ if (status != AMEDIA_OK) {
+ return status;
+ }
+
+ waitForThreads();
+
+ // TODO: Release transcoders?
+ return AMEDIA_OK;
}
media_status_t MediaTranscoder::resume() {
@@ -335,20 +446,4 @@
return start();
}
-media_status_t MediaTranscoder::cancel() {
- bool expected = false;
- if (!mCancelled.compare_exchange_strong(expected, true)) {
- // Already cancelled.
- return AMEDIA_OK;
- }
-
- mSampleWriter->stop();
- mSampleReader->setEnforceSequentialAccess(false);
- for (auto& transcoder : mTrackTranscoders) {
- transcoder->stop();
- }
-
- return AMEDIA_OK;
-}
-
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 35b1d33..c55e244 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -93,9 +93,10 @@
return AMEDIA_OK;
}
-media_status_t PassthroughTrackTranscoder::runTranscodeLoop() {
+media_status_t PassthroughTrackTranscoder::runTranscodeLoop(bool* stopped) {
MediaSampleInfo info;
std::shared_ptr<MediaSample> sample;
+ bool eosReached = false;
// Notify the track format as soon as we start. It's same as the source format.
notifyTrackFormatAvailable();
@@ -106,18 +107,18 @@
};
// Move samples until EOS is reached or transcoding is stopped.
- while (!mStopRequested && !mEosFromSource) {
+ while (mStopRequest != STOP_NOW && !eosReached) {
media_status_t status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &info);
if (status == AMEDIA_OK) {
uint8_t* buffer = mBufferPool->getBufferWithSize(info.size);
if (buffer == nullptr) {
- if (mStopRequested) {
+ if (mStopRequest == STOP_NOW) {
break;
}
LOG(ERROR) << "Unable to get buffer from pool";
- return AMEDIA_ERROR_IO; // TODO: Custom error codes?
+ return AMEDIA_ERROR_UNKNOWN;
}
sample = MediaSample::createWithReleaseCallback(
@@ -131,7 +132,7 @@
} else if (status == AMEDIA_ERROR_END_OF_STREAM) {
sample = std::make_shared<MediaSample>();
- mEosFromSource = true;
+ eosReached = true;
} else {
LOG(ERROR) << "Unable to get next sample info. Aborting transcode.";
return status;
@@ -139,17 +140,22 @@
sample->info = info;
onOutputSampleAvailable(sample);
+
+ if (mStopRequest == STOP_ON_SYNC && info.flags & SAMPLE_FLAG_SYNC_SAMPLE) {
+ break;
+ }
}
- if (mStopRequested && !mEosFromSource) {
- return AMEDIA_ERROR_UNKNOWN; // TODO: Custom error codes?
+ if (mStopRequest != NONE && !eosReached) {
+ *stopped = true;
}
return AMEDIA_OK;
}
void PassthroughTrackTranscoder::abortTranscodeLoop() {
- mStopRequested = true;
- mBufferPool->abort();
+ if (mStopRequest == STOP_NOW) {
+ mBufferPool->abort();
+ }
}
std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 4cf54f1..08ae9aa 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -156,11 +156,7 @@
static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
if (auto transcoder = wrapper->getTranscoder()) {
transcoder->mCodecMessageQueue.push(
- [transcoder, error] {
- transcoder->mStatus = error;
- transcoder->mStopRequested = true;
- },
- true);
+ [transcoder, error] { transcoder->mStatus = error; }, true);
}
}
};
@@ -404,6 +400,8 @@
sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
onOutputSampleAvailable(sample);
+
+ mLastSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
} else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder->getCodec());
LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
@@ -483,7 +481,7 @@
notifyTrackFormatAvailable();
}
-media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+media_status_t VideoTrackTranscoder::runTranscodeLoop(bool* stopped) {
androidSetThreadPriority(0 /* tid (0 = current) */, ANDROID_PRIORITY_VIDEO);
// Push start decoder and encoder as two messages, so that these are subject to the
@@ -507,25 +505,31 @@
});
// Process codec events until EOS is reached, transcoding is stopped or an error occurs.
- while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+ while (mStopRequest != STOP_NOW && !mEosFromEncoder && mStatus == AMEDIA_OK) {
std::function<void()> message = mCodecMessageQueue.pop();
message();
+
+ if (mStopRequest == STOP_ON_SYNC && mLastSampleWasSync) {
+ break;
+ }
}
mCodecMessageQueue.abort();
AMediaCodec_stop(mDecoder);
- // Return error if transcoding was stopped before it finished.
- if (mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
- mStatus = AMEDIA_ERROR_UNKNOWN; // TODO: Define custom error codes?
+ // Signal if transcoding was stopped before it finished.
+ if (mStopRequest != NONE && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+ *stopped = true;
}
return mStatus;
}
void VideoTrackTranscoder::abortTranscodeLoop() {
- // Push abort message to the front of the codec event queue.
- mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+ if (mStopRequest == STOP_NOW) {
+ // Wake up transcoder thread.
+ mCodecMessageQueue.push([] {}, true /* front */);
+ }
}
std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
index aee0ed6..351d80b 100644
--- a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
+++ b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
@@ -61,6 +61,12 @@
mCondition.notify_all();
}
+ virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+ std::unique_lock lock(mMutex);
+ mFinished = true;
+ mCondition.notify_all();
+ }
+
virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
media_status_t status) override {
std::unique_lock lock(mMutex);
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
index f762556..080f2b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
@@ -84,6 +84,9 @@
*/
virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) = 0;
+ /** Sample writer was stopped before it was finished. */
+ virtual void onStopped(const MediaSampleWriter* writer) = 0;
+
/** Sample writer progress update in percent. */
virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) = 0;
@@ -129,15 +132,14 @@
bool start();
/**
- * Stops the sample writer. If the sample writer is not yet finished its operation will be
- * aborted and an error value will be returned to the client in the callback supplied to
- * {@link #start}. If the sample writer has already finished and the client callback has fired
- * the writer has already automatically stopped and there is no need to call stop manually. Once
- * the sample writer has been stopped it cannot be restarted.
- * @return True if the sample writer was successfully stopped on this call. False if the sample
- * writer was already stopped or was never started.
+ * Stops the sample writer. If the sample writer is not yet finished, its operation will be
+ * aborted and the onStopped callback will fire. If the sample writer has already finished and
+ * the onFinished callback has fired the writer has already automatically stopped and there is
+ * no need to call stop manually. Once the sample writer has been stopped it cannot be
+ * restarted. This method is asynchronous and will not wait for the sample writer to stop before
+ * returning.
*/
- bool stop();
+ void stop();
/** Destructor. */
~MediaSampleWriter();
@@ -186,7 +188,6 @@
std::mutex mMutex; // Protects sample queue and state.
std::condition_variable mSampleSignal;
- std::thread mThread;
std::unordered_map<size_t, TrackRecord> mTracks;
std::priority_queue<SampleEntry, std::vector<SampleEntry>, SampleComparator> mSampleQueue
GUARDED_BY(mMutex);
@@ -200,8 +201,8 @@
MediaSampleWriter() : mState(UNINITIALIZED){};
void addSampleToTrack(size_t trackIndex, const std::shared_ptr<MediaSample>& sample);
- media_status_t writeSamples();
- media_status_t runWriterLoop();
+ media_status_t writeSamples(bool* wasStopped);
+ media_status_t runWriterLoop(bool* wasStopped);
};
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index c5e161c..724b919 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -62,18 +62,21 @@
const std::shared_ptr<AMediaFormat>& destinationFormat);
/**
- * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
- * {@link #stop}, even after completing successfully. Start should only be called once.
+ * Starts the track transcoder. After the track transcoder is successfully started it will run
+ * until a callback signals that transcoding has ended. Start should only be called once.
* @return True if the track transcoder started, or false if it had already been started.
*/
bool start();
/**
* Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
- * again. It is safe to call stop multiple times.
- * @return True if the track transcoder stopped, or false if it was already stopped.
+ * again. It is safe to call stop multiple times. Stop is an asynchronous operation. Once the
+ * track transcoder has stopped the onTrackStopped callback will get called, unless the
+ * transcoding finished or encountered an error before it could be stopped in which case the
+ * callbacks corresponding to those events will be called instead.
+ * @param stopOnSyncSample Request the transcoder to stop after emitting a sync sample.
*/
- bool stop();
+ void stop(bool stopOnSyncSample = false);
/**
* Set the sample consumer function. The MediaTrackTranscoder will deliver transcoded samples to
@@ -100,7 +103,9 @@
// Called by subclasses when the actual track format becomes available.
void notifyTrackFormatAvailable();
- // Called by subclasses when a transcoded sample is available.
+ // Called by subclasses when a transcoded sample is available. Samples must not hold a strong
+ // reference to the track transcoder in order to avoid retain cycles through the track
+ // transcoder's sample queue.
void onOutputSampleAvailable(const std::shared_ptr<MediaSample>& sample);
// configureDestinationFormat needs to be implemented by subclasses, and gets called on an
@@ -110,7 +115,7 @@
// runTranscodeLoop needs to be implemented by subclasses, and gets called on
// MediaTrackTranscoder's internal thread when the track transcoder is started.
- virtual media_status_t runTranscodeLoop() = 0;
+ virtual media_status_t runTranscodeLoop(bool* stopped) = 0;
// abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
// be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
@@ -120,13 +125,20 @@
int mTrackIndex;
std::shared_ptr<AMediaFormat> mSourceFormat;
+ enum StopRequest {
+ NONE,
+ STOP_NOW,
+ STOP_ON_SYNC,
+ };
+ std::atomic<StopRequest> mStopRequest = NONE;
+
private:
std::mutex mSampleMutex;
+ // SampleQueue for buffering output samples before a sample consumer has been set.
MediaSampleQueue mSampleQueue GUARDED_BY(mSampleMutex);
MediaSampleWriter::MediaSampleConsumerFunction mSampleConsumer GUARDED_BY(mSampleMutex);
const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
std::mutex mStateMutex;
- std::thread mTranscodingThread GUARDED_BY(mStateMutex);
enum {
UNINITIALIZED,
CONFIGURED,
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
index 654171e..7b62d46 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
@@ -39,6 +39,12 @@
virtual void onTrackFinished(const MediaTrackTranscoder* transcoder);
/**
+ * Called when the MediaTrackTranscoder instance was explicitly stopped before it was finished.
+ * @param transcoder The MediaTrackTranscoder that was stopped.
+ */
+ virtual void onTrackStopped(const MediaTrackTranscoder* transcoder);
+
+ /**
* Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
* @param transcoder The MediaTrackTranscoder that encountered the error.
* @param status The non-zero error code describing the encountered error.
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
index 555cfce..b995e6d 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -94,23 +94,25 @@
media_status_t start();
/**
- * Pauses transcoding. The transcoder's paused state is returned through pausedState. The
- * paused state is only needed for resuming transcoding with a new MediaTranscoder instance. The
- * caller can resume transcoding with the current MediaTranscoder instance at any time by
- * calling resume(). It is not required to cancel a paused transcoder. The paused state is
- * independent and the caller can always initialize a new transcoder instance with the same
- * paused state. If the caller wishes to abandon a paused transcoder's operation they can
- * release the transcoder instance, clear the paused state and delete the partial destination
- * file. The caller can optionally call cancel to let the transcoder clean up the partial
- * destination file.
+ * Pauses transcoding and finalizes the partial transcoded file to disk. Pause is a synchronous
+ * operation and will wait until all internal components are done. Once this method returns it
+ * is safe to release the transcoder instance. No callback will be called if the transcoder was
+ * paused successfully. But if the transcoding finishes or encountered an error during pause,
+ * the corresponding callback will be called.
*/
media_status_t pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState);
/** Resumes a paused transcoding. */
media_status_t resume();
- /** Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
- * will be responsible for cleaning up the abandoned file. */
+ /**
+ * Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
+ * will be responsible for cleaning up the abandoned file. Cancel is a synchronous operation and
+ * will wait until all internal components are done. Once this method returns it is safe to
+ * release the transcoder instance. Normally no callback will be called when the transcoder is
+ * cancelled. But if the transcoding finishes or encountered an error during cancel, the
+ * corresponding callback will be called.
+ */
media_status_t cancel();
virtual ~MediaTranscoder() = default;
@@ -121,17 +123,20 @@
// MediaTrackTranscoderCallback
virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) override;
virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+ virtual void onTrackStopped(const MediaTrackTranscoder* transcoder) override;
virtual void onTrackError(const MediaTrackTranscoder* transcoder,
media_status_t status) override;
// ~MediaTrackTranscoderCallback
// MediaSampleWriter::CallbackInterface
virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) override;
+ virtual void onStopped(const MediaSampleWriter* writer) override;
virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) override;
// ~MediaSampleWriter::CallbackInterface
- void onSampleWriterFinished(media_status_t status);
- void sendCallback(media_status_t status);
+ void onThreadFinished(const void* thread, media_status_t threadStatus, bool threadStopped);
+ media_status_t requestStop(bool stopOnSync);
+ void waitForThreads();
std::shared_ptr<CallbackInterface> mCallbacks;
std::shared_ptr<MediaSampleReader> mSampleReader;
@@ -141,7 +146,20 @@
std::mutex mTracksAddedMutex;
std::unordered_set<const MediaTrackTranscoder*> mTracksAdded GUARDED_BY(mTracksAddedMutex);
- std::atomic_bool mCallbackSent = false;
+ enum ThreadState {
+ PENDING = 0, // Not yet started.
+ RUNNING, // Currently running.
+ DONE, // Done running (can be finished, stopped or error).
+ };
+ std::mutex mThreadStateMutex;
+ std::condition_variable mThreadsDoneSignal;
+ std::unordered_map<const void*, ThreadState> mThreadStates GUARDED_BY(mThreadStateMutex);
+ media_status_t mTranscoderStatus GUARDED_BY(mThreadStateMutex) = AMEDIA_OK;
+ bool mTranscoderStopped GUARDED_BY(mThreadStateMutex) = false;
+ bool mThreadsDone GUARDED_BY(mThreadStateMutex) = false;
+ bool mCallbackSent GUARDED_BY(mThreadStateMutex) = false;
+ bool mSampleWriterStopped GUARDED_BY(mThreadStateMutex) = false;
+
std::atomic_bool mCancelled = false;
};
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index b9491ed..c074831 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -86,7 +86,7 @@
};
// MediaTrackTranscoder
- media_status_t runTranscodeLoop() override;
+ media_status_t runTranscodeLoop(bool* stopped) override;
void abortTranscodeLoop() override;
media_status_t configureDestinationFormat(
const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -94,8 +94,6 @@
// ~MediaTrackTranscoder
std::shared_ptr<BufferPool> mBufferPool;
- bool mEosFromSource = false;
- std::atomic_bool mStopRequested = false;
};
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index d000d7f..267755d 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -65,7 +65,7 @@
: MediaTrackTranscoder(transcoderCallback){};
// MediaTrackTranscoder
- media_status_t runTranscodeLoop() override;
+ media_status_t runTranscodeLoop(bool* stopped) override;
void abortTranscodeLoop() override;
media_status_t configureDestinationFormat(
const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -89,7 +89,7 @@
ANativeWindow* mSurface = nullptr;
bool mEosFromSource = false;
bool mEosFromEncoder = false;
- bool mStopRequested = false;
+ bool mLastSampleWasSync = false;
media_status_t mStatus = AMEDIA_OK;
MediaSampleInfo mSampleInfo;
BlockingQueue<std::function<void()>> mCodecMessageQueue;
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