transcoder: fix rotation handling
Transfer rotation degree from source format to track format
for MediaSampleWriter, and setOrietationHint on the muxer.
During transcoding using encoder/decoder, clear the rotation
degree so that the codec is guaranted to operate non-rotated.
bug: 154734285
bug: 159649977
bug: 152091443
test: unit testing (updated test with rotation)
Change-Id: I7e829ef777b1954d894411e8591a5e31ff36e275
diff --git a/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4 b/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4
new file mode 100644
index 0000000..df42a15
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4
Binary files differ
diff --git a/media/libmediatranscoding/tests/assets/push_assets.sh b/media/libmediatranscoding/tests/assets/push_assets.sh
index 2fcb233..8c85e58 100755
--- a/media/libmediatranscoding/tests/assets/push_assets.sh
+++ b/media/libmediatranscoding/tests/assets/push_assets.sh
@@ -21,5 +21,5 @@
#TODO(hkuang): Check if the destination folder already exists. If so, skip the copying.
echo "Copying files to device"
-adb push $ANDROID_BUILD_TOP/frameworks/av/media/libmediatranscoding/tests/assets /data/local/tmp/TranscodingTestAssets
+adb push $ANDROID_BUILD_TOP/frameworks/av/media/libmediatranscoding/tests/assets/* /data/local/tmp/TranscodingTestAssets
echo "Copy done"
diff --git a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
index aaa2adb..0fdb0b7 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
@@ -26,7 +26,19 @@
class DefaultMuxer : public MediaSampleWriterMuxerInterface {
public:
// MediaSampleWriterMuxerInterface
- ssize_t addTrack(const AMediaFormat* trackFormat) override {
+ ssize_t addTrack(AMediaFormat* trackFormat) override {
+ // If the track format has rotation, need to call AMediaMuxer_setOrientationHint
+ // to set the rotation. Muxer doesn't take rotation specified on the track.
+ const char* mime;
+ if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime) &&
+ strncmp(mime, "video/", 6) == 0) {
+ int32_t rotation;
+ if (AMediaFormat_getInt32(trackFormat, AMEDIAFORMAT_KEY_ROTATION, &rotation) &&
+ (rotation != 0)) {
+ AMediaMuxer_setOrientationHint(mMuxer, rotation);
+ }
+ }
+
return AMediaMuxer_addTrack(mMuxer, trackFormat);
}
media_status_t start() override { return AMediaMuxer_start(mMuxer); }
@@ -213,4 +225,4 @@
return AMEDIA_OK;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 96e2e61..4df3296 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -139,6 +139,10 @@
}
AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatSurface);
+ // Always encode without rotation. The rotation degree will be transferred directly to
+ // MediaSampleWriter track format, and MediaSampleWriter will call AMediaMuxer_setOrientationHint.
+ AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_ROTATION, 0);
+
mDestinationFormat = std::shared_ptr<AMediaFormat>(encoderFormat, &AMediaFormat_delete);
// Create and configure the encoder.
@@ -339,25 +343,35 @@
// at container level. This is supposed to override any SAR settings in the bitstream,
// thus should always be transferred to the container of the transcoded file.
int32_t sarWidth, sarHeight;
- if (AMediaFormat_getInt32(mDestinationFormat.get(), AMEDIAFORMAT_KEY_SAR_WIDTH, &sarWidth) &&
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_SAR_WIDTH, &sarWidth) &&
(sarWidth > 0) &&
- AMediaFormat_getInt32(mDestinationFormat.get(), AMEDIAFORMAT_KEY_SAR_HEIGHT, &sarHeight) &&
+ AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_SAR_HEIGHT, &sarHeight) &&
(sarHeight > 0)) {
AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_SAR_WIDTH, sarWidth);
AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_SAR_HEIGHT, sarHeight);
}
// Transfer DAR settings.
int32_t displayWidth, displayHeight;
- if (AMediaFormat_getInt32(mDestinationFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_WIDTH,
- &displayWidth) &&
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_WIDTH, &displayWidth) &&
(displayWidth > 0) &&
- AMediaFormat_getInt32(mDestinationFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
+ AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
&displayHeight) &&
(displayHeight > 0)) {
AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_DISPLAY_WIDTH, displayWidth);
AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, displayHeight);
}
+ // Transfer rotation settings.
+ // Note that muxer itself doesn't take rotation from the track format. It requires
+ // AMediaMuxer_setOrientationHint to set the rotation. Here we pass the rotation to
+ // MediaSampleWriter using the track format. MediaSampleWriter will then call
+ // AMediaMuxer_setOrientationHint as needed.
+ int32_t rotation;
+ if (AMediaFormat_getInt32(mSourceFormat.get(), AMEDIAFORMAT_KEY_ROTATION, &rotation) &&
+ (rotation != 0)) {
+ AMediaFormat_setInt32(formatCopy, AMEDIAFORMAT_KEY_ROTATION, rotation);
+ }
+
// TODO: transfer other fields as required.
mActualOutputFormat = std::shared_ptr<AMediaFormat>(formatCopy, &AMediaFormat_delete);
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
index 4d0264b..da83167 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
@@ -41,7 +41,7 @@
* @param trackFormat Format of the new track.
* @return A non-negative track index on success, or a negative number on failure.
*/
- virtual ssize_t addTrack(const AMediaFormat* trackFormat) = 0;
+ virtual ssize_t addTrack(AMediaFormat* trackFormat) = 0;
/** Starts the muxer. */
virtual media_status_t start() = 0;
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
index 9ffb32b..98813e7 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
@@ -58,7 +58,7 @@
class TestMuxer : public MediaSampleWriterMuxerInterface {
public:
// MuxerInterface
- ssize_t addTrack(const AMediaFormat* trackFormat) override {
+ ssize_t addTrack(AMediaFormat* trackFormat) override {
mEventQueue.push_back(AddTrack(trackFormat));
return mTrackCount++;
}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index 4d9386a..71d3a4e 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -67,7 +67,7 @@
void initSampleReader() {
const char* sourcePath =
- "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+ "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
const int sourceFd = open(sourcePath, O_RDONLY);
ASSERT_GT(sourceFd, 0);
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index 6bf6562..67f1ca1 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -40,7 +40,7 @@
struct FormatVerifierEntry {
const char* key;
- bool (*equal)(const char* key, AMediaFormat* src, AMediaFormat* dst);
+ std::function<bool(const char*, AMediaFormat*, AMediaFormat*)> equal;
};
static const FormatVerifierEntry kFieldsToPreserve[] = {
@@ -92,11 +92,6 @@
bool mFinished = false;
};
-static const char* SOURCE_PATH_AVC =
- "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
-static const char* SOURCE_PATH_HEVC =
- "/data/local/tmp/TranscodingTestAssets/jets_hevc_1280x720_20Mbps.mp4";
-
// 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.
@@ -161,6 +156,44 @@
return mCallbacks->mStatus;
}
+ void testTranscodeVideo(const char* srcPath, const char* destPath, const char* dstMime) {
+ const int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
+
+ EXPECT_EQ(
+ transcodeHelper(
+ srcPath, destPath,
+ [dstMime](AMediaFormat* sourceFormat) {
+ AMediaFormat* format = nullptr;
+ const char* mime = nullptr;
+ AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+ if (strncmp(mime, "video/", 6) == 0) {
+ format = AMediaFormat_new();
+ AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+
+ if (dstMime != nullptr) {
+ AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, dstMime);
+ }
+ }
+ return format;
+ }),
+ AMEDIA_OK);
+
+ if (dstMime != nullptr) {
+ std::vector<FormatVerifierEntry> extraVerifiers = {
+ {AMEDIAFORMAT_KEY_MIME,
+ [dstMime](const char* key, AMediaFormat* src __unused, AMediaFormat* dst) {
+ const char* mime = nullptr;
+ AMediaFormat_getString(dst, key, &mime);
+ return !strcmp(mime, dstMime);
+ }},
+ };
+ verifyOutputFormat(destPath, &extraVerifiers);
+ } else {
+ verifyOutputFormat(destPath);
+ }
+ }
+
void verifyOutputFormat(const char* destPath,
const std::vector<FormatVerifierEntry>* extraVerifiers = nullptr) {
int dstFd = open(destPath, O_RDONLY);
@@ -194,7 +227,8 @@
for (int i = 0; i < (sizeof(kFieldsToPreserve) / sizeof(kFieldsToPreserve[0])); ++i) {
EXPECT_TRUE(kFieldsToPreserve[i].equal(kFieldsToPreserve[i].key,
- mSourceVideoFormat.get(), videoFormat.get()));
+ mSourceVideoFormat.get(), videoFormat.get()))
+ << "Failed at key " << kFieldsToPreserve[i].key;
}
if (extraVerifiers != nullptr) {
@@ -212,76 +246,31 @@
};
TEST_F(MediaTranscoderTests, TestPassthrough) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
- EXPECT_EQ(transcodeHelper(SOURCE_PATH_AVC, destPath, [](AMediaFormat*) { return nullptr; }),
- AMEDIA_OK);
+ EXPECT_EQ(transcodeHelper(srcPath, destPath, [](AMediaFormat*) { return nullptr; }), AMEDIA_OK);
verifyOutputFormat(destPath);
}
-TEST_F(MediaTranscoderTests, TestBasicVideoTranscodeAvcToAvc) {
- const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscodeAvcToAvc.MP4";
- const int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
-
- EXPECT_EQ(transcodeHelper(
- SOURCE_PATH_AVC, destPath,
- [](AMediaFormat* sourceFormat) {
- AMediaFormat* format = nullptr;
- const char* mime = nullptr;
- AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
-
- if (strncmp(mime, "video/", 6) == 0) {
- format = AMediaFormat_new();
- AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
- }
- return format;
- }),
- AMEDIA_OK);
-
- std::vector<FormatVerifierEntry> extraVerifiers = {
- {AMEDIAFORMAT_KEY_MIME,
- [](const char* key, AMediaFormat* src __unused, AMediaFormat* dst) {
- const char* mime = nullptr;
- AMediaFormat_getString(dst, key, &mime);
- return !strcmp(mime, AMEDIA_MIMETYPE_VIDEO_AVC);
- }},
- };
-
- verifyOutputFormat(destPath, &extraVerifiers);
+TEST_F(MediaTranscoderTests, TestVideoTranscode_AvcToAvc_Basic) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_AvcToAvc_Basic.MP4";
+ testTranscodeVideo(srcPath, destPath, nullptr /*dstMime*/);
}
-TEST_F(MediaTranscoderTests, TestBasicVideoTranscodeHevcToAvc) {
- const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscodeHevcToAvc.MP4";
+TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Basic) {
+ const char* srcPath = "/data/local/tmp/TranscodingTestAssets/jets_hevc_1280x720_20Mbps.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Basic.MP4";
+ testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
+}
- EXPECT_EQ(transcodeHelper(
- SOURCE_PATH_HEVC, 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);
- AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME,
- AMEDIA_MIMETYPE_VIDEO_AVC);
- }
- return format;
- }),
- AMEDIA_OK);
-
- std::vector<FormatVerifierEntry> extraVerifiers = {
- {AMEDIAFORMAT_KEY_MIME,
- [](const char* key, AMediaFormat* src __unused, AMediaFormat* dst) {
- const char* mime = nullptr;
- AMediaFormat_getString(dst, key, &mime);
- return !strcmp(mime, AMEDIA_MIMETYPE_VIDEO_AVC);
- }},
- };
-
- verifyOutputFormat(destPath, &extraVerifiers);
+TEST_F(MediaTranscoderTests, TestVideoTranscode_HevcToAvc_Rotation) {
+ const char* srcPath =
+ "/data/local/tmp/TranscodingTestAssets/desk_hevc_1920x1080_aac_48KHz_rot90.mp4";
+ const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode_HevcToAvc_Rotation.MP4";
+ testTranscodeVideo(srcPath, destPath, AMEDIA_MIMETYPE_VIDEO_AVC);
}
} // namespace android