Transcoder: Added MediaTranscoder and unit test.
MediaTranscoder is the API for the native transcoding library.
Test: Unit tests.
Bug: 156003955, 152091443, 155918341
Change-Id: I24b52d174db0faecea8f331ef6d8a3dc4e473c4e
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index 0b7ddbb..843d047 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -22,6 +22,7 @@
"MediaSampleReaderNDK.cpp",
"MediaSampleWriter.cpp",
"MediaTrackTranscoder.cpp",
+ "MediaTranscoder.cpp",
"PassthroughTrackTranscoder.cpp",
"VideoTrackTranscoder.cpp",
],
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 1673b5b..10c0c6c 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -101,4 +101,8 @@
return false;
}
-} // namespace android
\ No newline at end of file
+std::shared_ptr<MediaSampleQueue> MediaTrackTranscoder::getOutputQueue() const {
+ return mOutputQueue;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
new file mode 100644
index 0000000..f2f7810
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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 "MediaTranscoder"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTranscoder.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+#include <unistd.h>
+
+namespace android {
+
+#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName) \
+ static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
+ _type value; \
+ if (AMediaFormat_get##_typeName(from, key, &value)) { \
+ AMediaFormat_set##_typeName(to, key, value); \
+ } \
+ }
+
+DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
+
+static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
+ if (base == nullptr || overlay == nullptr) {
+ LOG(ERROR) << "Cannot merge null formats";
+ return nullptr;
+ }
+
+ AMediaFormat* format = AMediaFormat_new();
+ if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
+ AMediaFormat_delete(format);
+ return nullptr;
+ }
+
+ // Note: AMediaFormat does not expose a function for appending values from another format or for
+ // iterating over all values and keys in a format. Instead we define a static list of known keys
+ // along with their value types and copy the ones that are present. A better solution would be
+ // to either implement required functions in NDK or to parse the overlay format's string
+ // representation and copy all existing keys.
+ static const struct {
+ const char* key;
+ void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
+ } kSupportedConfigs[] = {
+ {AMEDIAFORMAT_KEY_MIME, copyString},
+ {AMEDIAFORMAT_KEY_DURATION, copyInt64},
+ {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
+ {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
+ {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
+ {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
+ {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
+ {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
+ {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
+ {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
+ };
+
+ for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
+ kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
+ }
+
+ return format;
+}
+
+void MediaTranscoder::sendCallback(media_status_t status) {
+ 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 then
+ // automatically delete the output file.
+ const bool deleteOutputFile = status != AMEDIA_OK;
+ std::thread asyncCancelThread{
+ [self = shared_from_this(), deleteOutputFile] { self->cancel(deleteOutputFile); }};
+ asyncCancelThread.detach();
+ }
+}
+
+void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+}
+
+void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
+ LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
+ sendCallback(status);
+}
+
+void MediaTranscoder::onSampleWriterFinished(media_status_t status) {
+ LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
+ sendCallback(status);
+}
+
+std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
+ const std::shared_ptr<CallbackInterface>& callbacks,
+ const std::shared_ptr<Parcel>& pausedState) {
+ if (pausedState != nullptr) {
+ LOG(ERROR) << "Initializing from paused state is currently not supported.";
+ return nullptr;
+ } else if (callbacks == nullptr) {
+ LOG(ERROR) << "Callbacks cannot be null";
+ return nullptr;
+ }
+
+ return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
+}
+
+media_status_t MediaTranscoder::configureSource(const char* path) {
+ if (path == nullptr) {
+ LOG(ERROR) << "Source path cannot be null";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ const int fd = open(path, O_RDONLY);
+ if (fd <= 0) {
+ LOG(ERROR) << "Unable to open source path: " << path;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ const size_t fileSize = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+
+ mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
+ close(fd);
+
+ if (mSampleReader == nullptr) {
+ LOG(ERROR) << "Unable to parse source file: " << path;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ const size_t trackCount = mSampleReader->getTrackCount();
+ for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+ AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
+ if (trackFormat == nullptr) {
+ LOG(ERROR) << "Track #" << trackIndex << " has no format";
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
+ }
+
+ return AMEDIA_OK;
+}
+
+std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
+ // Return a deep copy of the formats to avoid the caller modifying our internal formats.
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
+ for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
+ AMediaFormat* copy = AMediaFormat_new();
+ AMediaFormat_copy(copy, sourceFormat.get());
+ trackFormats.emplace_back(copy, &AMediaFormat_delete);
+ }
+ return trackFormats;
+}
+
+media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
+ if (mSampleReader == nullptr) {
+ LOG(ERROR) << "Source must be configured before tracks";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ } else if (trackIndex >= mSourceTrackFormats.size()) {
+ LOG(ERROR) << "Track index " << trackIndex
+ << " is out of bounds. Track count: " << mSourceTrackFormats.size();
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ std::unique_ptr<MediaTrackTranscoder> transcoder = nullptr;
+ std::shared_ptr<AMediaFormat> format = nullptr;
+
+ if (trackFormat == nullptr) {
+ transcoder = std::make_unique<PassthroughTrackTranscoder>(shared_from_this());
+ } else {
+ const char* srcMime = nullptr;
+ if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
+ &srcMime)) {
+ LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
+ return AMEDIA_ERROR_MALFORMED;
+ }
+
+ if (strncmp(srcMime, "video/", 6) != 0) {
+ LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
+ "track #"
+ << trackIndex << " with mime " << srcMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+
+ const char* dstMime = nullptr;
+ if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
+ if (strncmp(dstMime, "video/", 6) != 0) {
+ LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
+ << srcMime << " to " << dstMime;
+ return AMEDIA_ERROR_UNSUPPORTED;
+ }
+ }
+
+ transcoder = std::make_unique<VideoTrackTranscoder>(shared_from_this());
+
+ AMediaFormat* mergedFormat =
+ mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
+ if (mergedFormat == nullptr) {
+ LOG(ERROR) << "Unable to merge source and destination formats";
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
+ }
+
+ media_status_t status = transcoder->configure(mSampleReader, trackIndex, format);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
+ << status;
+ return status;
+ }
+
+ mTrackTranscoders.emplace_back(std::move(transcoder));
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::configureDestination(const char* path) {
+ if (path == nullptr || strlen(path) < 1) {
+ LOG(ERROR) << "Invalid destination path: " << path;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ } else if (mSampleWriter != nullptr) {
+ LOG(ERROR) << "Destination is already configured.";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ }
+
+ // Write-only, create file if non-existent, don't overwrite existing file.
+ static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+ // User R+W permission.
+ static constexpr int kFileMode = S_IRUSR | S_IWUSR;
+
+ const int fd = open(path, kOpenFlags, kFileMode);
+ if (fd < 0) {
+ LOG(ERROR) << "Unable to open destination file \"" << path << "\" for writing: " << fd;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ mDestinationPath = std::string(path);
+
+ mSampleWriter = std::make_unique<MediaSampleWriter>();
+ const bool initOk = mSampleWriter->init(
+ fd, std::bind(&MediaTranscoder::onSampleWriterFinished, this, std::placeholders::_1));
+ close(fd);
+
+ if (!initOk) {
+ LOG(ERROR) << "Unable to initialize sample writer with destination path " << path;
+ mSampleWriter.reset();
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::start() {
+ if (mTrackTranscoders.size() < 1) {
+ LOG(ERROR) << "Unable to start, no tracks are configured.";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ } else if (mSampleWriter == nullptr) {
+ LOG(ERROR) << "Unable to start, destination is not configured";
+ return AMEDIA_ERROR_INVALID_OPERATION;
+ }
+
+ // Add tracks to the writer.
+ for (auto& transcoder : mTrackTranscoders) {
+ const bool ok = mSampleWriter->addTrack(transcoder->getOutputQueue(),
+ transcoder->getOutputFormat());
+ if (!ok) {
+ LOG(ERROR) << "Unable to add track to sample writer.";
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+ }
+
+ bool started = mSampleWriter->start();
+ if (!started) {
+ LOG(ERROR) << "Unable to start sample writer.";
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+
+ // Start transcoders
+ for (auto& transcoder : mTrackTranscoders) {
+ started = transcoder->start();
+ if (!started) {
+ LOG(ERROR) << "Unable to start track transcoder.";
+ cancel(true);
+ return AMEDIA_ERROR_UNKNOWN;
+ }
+ }
+ return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcelable>* pausedState) {
+ (void)pausedState;
+ LOG(ERROR) << "Pause is not currently supported";
+ return AMEDIA_ERROR_UNSUPPORTED;
+}
+
+media_status_t MediaTranscoder::resume() {
+ LOG(ERROR) << "Resume is not currently supported";
+ return AMEDIA_ERROR_UNSUPPORTED;
+}
+
+media_status_t MediaTranscoder::cancel(bool deleteDestinationFile) {
+ bool expected = false;
+ if (!mCancelled.compare_exchange_strong(expected, true)) {
+ // Already cancelled.
+ return AMEDIA_OK;
+ }
+
+ mSampleWriter->stop();
+ for (auto& transcoder : mTrackTranscoders) {
+ transcoder->stop();
+ }
+
+ // TODO(chz): file deletion should be done by upper level from the content URI.
+ if (deleteDestinationFile && !mDestinationPath.empty()) {
+ int error = unlink(mDestinationPath.c_str());
+ if (error) {
+ LOG(ERROR) << "Unable to delete destination file " << mDestinationPath.c_str() << ": "
+ << error;
+ return AMEDIA_ERROR_IO;
+ }
+ }
+ return AMEDIA_OK;
+}
+
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 4404bbb..7806208 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -134,7 +134,7 @@
}
sample->info = info;
- if (mOutputQueue.enqueue(sample)) {
+ if (mOutputQueue->enqueue(sample)) {
LOG(ERROR) << "Output queue aborted";
return AMEDIA_ERROR_IO;
}
@@ -153,4 +153,7 @@
mBufferPool->abort();
}
+std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
+ return mSourceFormat;
+}
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 311e9be..3818545 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -30,6 +30,11 @@
static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
"Sample flag mismatch: PARTIAL_FRAME");
+// Color format defined by surface. (See MediaCodecInfo.CodecCapabilities#COLOR_FormatSurface.)
+static constexpr int32_t kColorFormatSurface = 0x7f000789;
+// Default key frame interval in seconds.
+static constexpr float kDefaultKeyFrameIntervalSeconds = 1.0f;
+
template <typename T>
void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
{
@@ -113,11 +118,24 @@
media_status_t status = AMEDIA_OK;
if (destinationFormat == nullptr) {
- LOG(ERROR) << "Destination format is null";
+ LOG(ERROR) << "Destination format is null, use passthrough transcoder";
return AMEDIA_ERROR_INVALID_PARAMETER;
}
- mDestinationFormat = destinationFormat;
+ AMediaFormat* encoderFormat = AMediaFormat_new();
+ if (!encoderFormat || AMediaFormat_copy(encoderFormat, destinationFormat.get()) != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to copy destination format";
+ return AMEDIA_ERROR_INVALID_PARAMETER;
+ }
+
+ float tmp;
+ if (!AMediaFormat_getFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &tmp)) {
+ AMediaFormat_setFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+ kDefaultKeyFrameIntervalSeconds);
+ }
+ AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatSurface);
+
+ mDestinationFormat = std::shared_ptr<AMediaFormat>(encoderFormat, &AMediaFormat_delete);
// Create and configure the encoder.
const char* destinationMime = nullptr;
@@ -276,7 +294,7 @@
sample->info.flags = bufferInfo.flags;
sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
- const bool aborted = mOutputQueue.enqueue(sample);
+ const bool aborted = mOutputQueue->enqueue(sample);
if (aborted) {
LOG(ERROR) << "Output sample queue was aborted. Stopping transcode.";
mStatus = AMEDIA_ERROR_IO; // TODO: Define custom error codes?
@@ -321,7 +339,8 @@
}
AMediaCodec_stop(mDecoder);
- AMediaCodec_stop(mEncoder.get());
+ // TODO: Stop invalidates all buffers. Stop encoder when last buffer is released.
+ // AMediaCodec_stop(mEncoder.get());
return mStatus;
}
@@ -330,4 +349,8 @@
mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
}
+std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
+ return mDestinationFormat;
+}
+
} // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index 235766c..a71db67 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -40,14 +40,14 @@
* successfully.
* @param transcoder The MediaTrackTranscoder that finished the transcoding.
*/
- virtual void onTrackFinished(MediaTrackTranscoder* transcoder);
+ virtual void onTrackFinished(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.
*/
- virtual void onTrackError(MediaTrackTranscoder* transcoder, media_status_t status);
+ virtual void onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status);
protected:
virtual ~MediaTrackTranscoderCallback() = default;
@@ -96,15 +96,24 @@
bool stop();
/**
- * Sample output queue.
- * TODO(b/155918341) Move to protected.
+ * Retrieves the track transcoder's output sample queue.
+ * @return The output sample queue.
*/
- MediaSampleQueue mOutputQueue = {};
+ std::shared_ptr<MediaSampleQueue> getOutputQueue() const;
+
+ /**
+ * Retrieves the track transcoder's final output format. The output is available after the
+ * track transcoder has been successfully configured.
+ * @return The track output format.
+ */
+ virtual std::shared_ptr<AMediaFormat> getOutputFormat() const = 0;
+
+ virtual ~MediaTrackTranscoder() = default;
protected:
MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
- : mTranscoderCallback(transcoderCallback){};
- virtual ~MediaTrackTranscoder() = default;
+ : mOutputQueue(std::make_shared<MediaSampleQueue>()),
+ mTranscoderCallback(transcoderCallback){};
// configureDestinationFormat needs to be implemented by subclasses, and gets called on an
// external thread before start.
@@ -119,6 +128,7 @@
// be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
virtual void abortTranscodeLoop() = 0;
+ std::shared_ptr<MediaSampleQueue> mOutputQueue;
std::shared_ptr<MediaSampleReader> mMediaSampleReader;
int mTrackIndex;
std::shared_ptr<AMediaFormat> mSourceFormat;
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
new file mode 100644
index 0000000..2d18eea
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODER_H
+#define ANDROID_MEDIA_TRANSCODER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <media/MediaSampleReader.h>
+#include <media/MediaSampleWriter.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+
+#include <atomic>
+#include <memory>
+#include <unordered_set>
+
+namespace android {
+
+class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
+ public MediaTrackTranscoderCallback {
+public:
+ /** Callbacks from transcoder to client. */
+ class CallbackInterface {
+ public:
+ /** Transcoder finished successfully. */
+ virtual void onFinished(const MediaTranscoder* transcoder) = 0;
+
+ /** Transcoder encountered an unrecoverable error. */
+ virtual void onError(const MediaTranscoder* transcoder, media_status_t error) = 0;
+
+ /** Transcoder progress update reported in percent from 0 to 100. */
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder, int32_t progress) = 0;
+
+ /**
+ * Transcoder lost codec resources and paused operations. The client can resume transcoding
+ * again when resources are available by either:
+ * 1) Calling resume on the same MediaTranscoder instance.
+ * 2) Creating a new MediaTranscoding instance with the paused state and then calling
+ * resume.
+ */
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
+ const std::shared_ptr<const Parcelable>& pausedState) = 0;
+
+ virtual ~CallbackInterface() = default;
+ };
+
+ /**
+ * Creates a new MediaTranscoder instance. If the supplied paused state is valid, the transcoder
+ * will be initialized with the paused state and be ready to be resumed right away. It is not
+ * possible to change any configurations on a paused transcoder.
+ */
+ static std::shared_ptr<MediaTranscoder> create(
+ const std::shared_ptr<CallbackInterface>& callbacks,
+ const std::shared_ptr<Parcel>& pausedState = nullptr);
+
+ /** Configures source from path. */
+ media_status_t configureSource(const char* path);
+
+ /** Gets the media formats of all tracks in the file. */
+ std::vector<std::shared_ptr<AMediaFormat>> getTrackFormats() const;
+
+ /**
+ * Configures transcoding of a track. Tracks that are not configured will not present in the
+ * final transcoded file, i.e. tracks will be dropped by default. Passing nullptr for
+ * trackFormat means the track will be copied unchanged ("passthrough") to the destination.
+ * Track configurations must be done after the source has been configured.
+ * Note: trackFormat is not modified but cannot be const.
+ */
+ media_status_t configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat);
+
+ /** Configures destination from path. */
+ media_status_t configureDestination(const char* path);
+
+ /** Starts transcoding. No configurations can be made once the transcoder has started. */
+ 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.
+ */
+ media_status_t pause(std::shared_ptr<const Parcelable>* pausedState);
+
+ /** Resumes a paused transcoding. */
+ media_status_t resume();
+
+ /** Cancels the transcoding. Once canceled the transcoding can not be restarted. returns error
+ * if file could not be deleted. */
+ media_status_t cancel(bool deleteDestinationFile = true);
+
+ virtual ~MediaTranscoder() = default;
+
+private:
+ MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
+ : mCallbacks(callbacks),
+ mSampleReader(nullptr),
+ mSampleWriter(nullptr),
+ mSourceTrackFormats(),
+ mTrackTranscoders() {}
+
+ // MediaTrackTranscoderCallback
+ virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+ virtual void onTrackError(const MediaTrackTranscoder* transcoder,
+ media_status_t status) override;
+ // ~MediaTrackTranscoderCallback
+ void onSampleWriterFinished(media_status_t status);
+ void sendCallback(media_status_t status);
+
+ std::shared_ptr<CallbackInterface> mCallbacks;
+ std::shared_ptr<MediaSampleReader> mSampleReader;
+ std::unique_ptr<MediaSampleWriter> mSampleWriter;
+ std::vector<std::shared_ptr<AMediaFormat>> mSourceTrackFormats;
+ std::vector<std::unique_ptr<MediaTrackTranscoder>> mTrackTranscoders;
+
+ std::string mDestinationPath;
+ std::atomic_bool mCallbackSent = false;
+ std::atomic_bool mCancelled = false;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index 42feb85..b9491ed 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -90,6 +90,7 @@
void abortTranscodeLoop() override;
media_status_t configureDestinationFormat(
const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+ std::shared_ptr<AMediaFormat> getOutputFormat() const override;
// ~MediaTrackTranscoder
std::shared_ptr<BufferPool> mBufferPool;
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index 7d93d60..c47e4b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -61,6 +61,7 @@
void abortTranscodeLoop() override;
media_status_t configureDestinationFormat(
const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+ std::shared_ptr<AMediaFormat> getOutputFormat() const override;
// ~MediaTrackTranscoder
// Enqueues an input sample with the decoder.
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index 926110885..4160c30 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -74,3 +74,10 @@
defaults: ["testdefaults"],
srcs: ["MediaSampleWriterTests.cpp"],
}
+
+// MediaTranscoder unit test
+cc_test {
+ name: "MediaTranscoderTests",
+ defaults: ["testdefaults"],
+ srcs: ["MediaTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index c5b181d..4d9386a 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -60,6 +60,7 @@
break;
}
ASSERT_NE(mTranscoder, nullptr);
+ mTranscoderOutputQueue = mTranscoder->getOutputQueue();
initSampleReader();
}
@@ -120,7 +121,7 @@
std::shared_ptr<MediaSample> sample;
bool aborted = false;
do {
- aborted = mTranscoder->mOutputQueue.dequeue(&sample);
+ aborted = mTranscoderOutputQueue->dequeue(&sample);
} while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
mQueueWasAborted = aborted;
mGotEndOfStream =
@@ -142,6 +143,7 @@
protected:
std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+ std::shared_ptr<MediaSampleQueue> mTranscoderOutputQueue;
std::shared_ptr<TestCallback> mCallback;
std::shared_ptr<MediaSampleReader> mMediaSampleReader;
@@ -242,7 +244,7 @@
EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
AMEDIA_OK);
ASSERT_TRUE(mTranscoder->start());
- mTranscoder->mOutputQueue.abort();
+ mTranscoderOutputQueue->abort();
drainOutputSampleQueue();
EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
EXPECT_TRUE(mTranscoder->stop());
@@ -259,7 +261,7 @@
ASSERT_TRUE(mTranscoder->start());
std::shared_ptr<MediaSample> sample;
- EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+ EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
drainOutputSampleQueue();
EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
@@ -269,6 +271,7 @@
EXPECT_TRUE(mGotEndOfStream);
mTranscoder.reset();
+ mTranscoderOutputQueue.reset();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
sample.reset();
}
@@ -280,7 +283,7 @@
ASSERT_TRUE(mTranscoder->start());
std::shared_ptr<MediaSample> sample;
- EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+ EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
EXPECT_TRUE(mTranscoder->stop());
std::this_thread::sleep_for(std::chrono::milliseconds(20));
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
new file mode 100644
index 0000000..c4a67bb
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoderTests"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/MediaTranscoder.h>
+
+namespace android {
+
+class TestCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+ virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_FALSE(mFinished);
+ mFinished = true;
+ mCondition.notify_all();
+ }
+
+ virtual void onError(const MediaTranscoder* transcoder __unused,
+ media_status_t error) override {
+ std::unique_lock<std::mutex> lock(mMutex);
+ EXPECT_NE(error, AMEDIA_OK);
+ EXPECT_FALSE(mFinished);
+ mFinished = true;
+ mStatus = error;
+ mCondition.notify_all();
+ }
+
+ virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+ int32_t progress __unused) override {}
+
+ virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+ const std::shared_ptr<const Parcelable>& pausedState
+ __unused) override {}
+
+ void waitForTranscodingFinished() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mFinished) {
+ mCondition.wait(lock);
+ }
+ }
+
+ media_status_t mStatus = AMEDIA_OK;
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ bool mFinished = false;
+};
+
+static const char* SOURCE_PATH =
+ "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+class MediaTranscoderTests : public ::testing::Test {
+public:
+ MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests created"; }
+ ~MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests destroyed"; }
+
+ void SetUp() override {
+ LOG(DEBUG) << "MediaTranscoderTests set up";
+ mCallbacks = std::make_shared<TestCallbacks>();
+ }
+
+ void TearDown() override {
+ LOG(DEBUG) << "MediaTranscoderTests tear down";
+ mCallbacks.reset();
+ }
+
+ void deleteFile(const char* path) { unlink(path); }
+
+ using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
+ media_status_t transcodeHelper(const char* destPath,
+ FormatConfigurationCallback formatCallback) {
+ auto transcoder = MediaTranscoder::create(mCallbacks, nullptr);
+ EXPECT_NE(transcoder, nullptr);
+
+ EXPECT_EQ(transcoder->configureSource(SOURCE_PATH), AMEDIA_OK);
+
+ std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+ EXPECT_GT(trackFormats.size(), 0);
+
+ for (int i = 0; i < trackFormats.size(); ++i) {
+ AMediaFormat* format = formatCallback(trackFormats[i].get());
+ EXPECT_EQ(transcoder->configureTrackFormat(i, format), AMEDIA_OK);
+ if (format != nullptr) {
+ AMediaFormat_delete(format);
+ }
+ }
+ deleteFile(destPath);
+ EXPECT_EQ(transcoder->configureDestination(destPath), AMEDIA_OK);
+
+ media_status_t startStatus = transcoder->start();
+ EXPECT_EQ(startStatus, AMEDIA_OK);
+ if (startStatus == AMEDIA_OK) {
+ mCallbacks->waitForTranscodingFinished();
+ }
+
+ return mCallbacks->mStatus;
+ }
+
+ std::shared_ptr<TestCallbacks> mCallbacks;
+};
+
+TEST_F(MediaTranscoderTests, TestPassthrough) {
+ const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
+
+ EXPECT_EQ(transcodeHelper(destPath, [](AMediaFormat*) { return nullptr; }), AMEDIA_OK);
+
+ // TODO: Validate output file
+}
+
+TEST_F(MediaTranscoderTests, TestBasicVideoTranscode) {
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode.MP4";
+
+ EXPECT_EQ(transcodeHelper(
+ destPath,
+ [](AMediaFormat* sourceFormat) {
+ AMediaFormat* format = nullptr;
+ const char* mime = nullptr;
+ AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ const int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
+ format = AMediaFormat_new();
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+ }
+ return format;
+ }),
+ AMEDIA_OK);
+
+ // TODO: Validate output file
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 7a92a37..316793a 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -166,7 +166,8 @@
// Pull transcoder's output samples and compare against input checksums.
uint64_t sampleCount = 0;
std::shared_ptr<MediaSample> sample;
- while (!transcoder.mOutputQueue.dequeue(&sample)) {
+ std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+ while (!outputQueue->dequeue(&sample)) {
ASSERT_NE(sample, nullptr);
if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 6b9131c..79c227b 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -56,13 +56,13 @@
~TestCallback() = default;
// MediaTrackTranscoderCallback
- void onTrackFinished(MediaTrackTranscoder* transcoder __unused) {
+ void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) {
std::unique_lock<std::mutex> lock(mMutex);
mTranscodingFinished = true;
mCv.notify_all();
}
- void onTrackError(MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+ void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
std::unique_lock<std::mutex> lock(mMutex);
mTranscodingFinished = true;
mStatus = status;
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 3cec1a1..6b1f640 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -100,10 +100,11 @@
EXPECT_EQ(transcoder.configure(mMediaSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
ASSERT_TRUE(transcoder.start());
- std::thread sampleConsumerThread{[&transcoder] {
+ std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+ std::thread sampleConsumerThread{[&outputQueue] {
uint64_t sampleCount = 0;
std::shared_ptr<MediaSample> sample;
- while (!transcoder.mOutputQueue.dequeue(&sample)) {
+ while (!outputQueue->dequeue(&sample)) {
ASSERT_NE(sample, nullptr);
const uint32_t flags = sample->info.flags;
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
index 70e2111..01beeee 100755
--- a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -39,3 +39,6 @@
echo "testing MediaSampleWriter"
adb shell /data/nativetest64/MediaSampleWriterTests/MediaSampleWriterTests
+
+echo "testing MediaTranscoder"
+adb shell /data/nativetest64/MediaTranscoderTests/MediaTranscoderTests