blob: 94bdcb3f881fd82c91a2c8febcc370baec74e6a1 [file] [log] [blame]
Linus Nilsson8a96cfc2020-09-29 12:31:15 -07001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Native media track transcoder benchmark tests.
19 *
20 * How to run the benchmark:
21 *
22 * 1. Download the media assets from http://go/transcodingbenchmark and push the directory
23 * ("TranscodingBenchmark") to /data/local/tmp.
24 *
25 * 2. Compile the benchmark and sync to device:
26 * $ mm -j72 && adb sync
27 *
28 * 3. Run:
29 * $ adb shell /data/nativetest64/MediaTrackTranscoderBenchmark/MediaTrackTranscoderBenchmark
30 */
31
32// #define LOG_NDEBUG 0
33#define LOG_TAG "MediaTrackTranscoderBenchmark"
34
35#include <android-base/logging.h>
36#include <benchmark/benchmark.h>
37#include <fcntl.h>
38#include <media/MediaSampleReader.h>
39#include <media/MediaSampleReaderNDK.h>
40#include <media/MediaTrackTranscoder.h>
41#include <media/MediaTrackTranscoderCallback.h>
42#include <media/NdkCommon.h>
43#include <media/PassthroughTrackTranscoder.h>
44#include <media/VideoTrackTranscoder.h>
45
46using namespace android;
47
48typedef enum {
49 kVideo,
50 kAudio,
51} MediaType;
52
53class TrackTranscoderCallbacks : public MediaTrackTranscoderCallback {
54public:
55 virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder __unused) override {}
56
57 virtual void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) override {
58 std::unique_lock lock(mMutex);
59 mFinished = true;
60 mCondition.notify_all();
61 }
62
63 virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
64 media_status_t status) override {
65 std::unique_lock lock(mMutex);
66 mFinished = true;
67 mStatus = status;
68 mCondition.notify_all();
69 }
70
71 void waitForTranscodingFinished() {
72 std::unique_lock lock(mMutex);
73 while (!mFinished) {
74 mCondition.wait(lock);
75 }
76 }
77
78 media_status_t mStatus = AMEDIA_OK;
79
80private:
81 std::mutex mMutex;
82 std::condition_variable mCondition;
83 bool mFinished = false;
84};
85
86/**
87 * MockSampleReader holds a ringbuffer of the first samples in the provided source track. Samples
88 * are returned to the caller from the ringbuffer in a round-robin fashion with increasing
89 * timestamps. The number of samples returned before EOS matches the number of frames in the source
90 * track.
91 */
92class MockSampleReader : public MediaSampleReader {
93public:
94 static std::shared_ptr<MediaSampleReader> createFromFd(int fd, size_t offset, size_t size) {
95 AMediaExtractor* extractor = AMediaExtractor_new();
96 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
97 if (status != AMEDIA_OK) return nullptr;
98
99 auto sampleReader = std::shared_ptr<MockSampleReader>(new MockSampleReader(extractor));
100 return sampleReader;
101 }
102
103 AMediaFormat* getFileFormat() override { return AMediaExtractor_getFileFormat(mExtractor); }
104
105 size_t getTrackCount() const override { return AMediaExtractor_getTrackCount(mExtractor); }
106
107 AMediaFormat* getTrackFormat(int trackIndex) override {
108 return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
109 }
110
111 media_status_t selectTrack(int trackIndex) override {
112 if (mSelectedTrack >= 0) return AMEDIA_ERROR_UNSUPPORTED;
113 mSelectedTrack = trackIndex;
114
115 media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
116 if (status != AMEDIA_OK) return status;
117
118 // Get the sample count.
119 AMediaFormat* format = getTrackFormat(trackIndex);
120 const bool haveSampleCount =
121 AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_COUNT, &mSampleCount);
122 AMediaFormat_delete(format);
123
124 if (!haveSampleCount) {
125 LOG(ERROR) << "No sample count in track format.";
126 return AMEDIA_ERROR_UNSUPPORTED;
127 }
128
129 // Buffer samples.
130 const int32_t targetBufferCount = 60;
131 std::unique_ptr<uint8_t[]> buffer;
132 MediaSampleInfo info;
133 while (true) {
134 info.presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
135 info.flags = AMediaExtractor_getSampleFlags(mExtractor);
136 info.size = AMediaExtractor_getSampleSize(mExtractor);
137
138 // Finish buffering after either reading all the samples in the track or after
139 // completing the GOP satisfying the target count.
140 if (mSamples.size() == mSampleCount ||
141 (mSamples.size() >= targetBufferCount && info.flags & SAMPLE_FLAG_SYNC_SAMPLE)) {
142 break;
143 }
144
145 buffer.reset(new uint8_t[info.size]);
146
147 ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer.get(), info.size);
148 if (bytesRead != info.size) {
149 return AMEDIA_ERROR_UNKNOWN;
150 }
151
152 mSamples.emplace_back(std::move(buffer), info);
153
154 AMediaExtractor_advance(mExtractor);
155 }
156
157 mFirstPtsUs = mSamples[0].second.presentationTimeUs;
158 mPtsDiff = mSamples[1].second.presentationTimeUs - mSamples[0].second.presentationTimeUs;
159
160 return AMEDIA_OK;
161 }
162
163 media_status_t setEnforceSequentialAccess(bool enforce __unused) override { return AMEDIA_OK; }
164
165 media_status_t getEstimatedBitrateForTrack(int trackIndex __unused,
166 int32_t* bitrate __unused) override {
167 return AMEDIA_ERROR_UNSUPPORTED;
168 }
169
170 media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) override {
171 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
172
173 if (mCurrentSampleIndex >= mSampleCount) {
174 info->presentationTimeUs = 0;
175 info->size = 0;
176 info->flags = SAMPLE_FLAG_END_OF_STREAM;
177 return AMEDIA_ERROR_END_OF_STREAM;
178 }
179
180 *info = mSamples[mCurrentSampleIndex % mSamples.size()].second;
181 info->presentationTimeUs = mFirstPtsUs + mCurrentSampleIndex * mPtsDiff;
182 return AMEDIA_OK;
183 }
184
185 media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
186 size_t bufferSize) override {
187 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
188
189 if (mCurrentSampleIndex >= mSampleCount) return AMEDIA_ERROR_END_OF_STREAM;
190
191 auto& p = mSamples[mCurrentSampleIndex % mSamples.size()];
192
193 if (bufferSize < p.second.size) return AMEDIA_ERROR_INVALID_PARAMETER;
194 memcpy(buffer, p.first.get(), bufferSize);
195
196 advanceTrack(trackIndex);
197 return AMEDIA_OK;
198 }
199
200 void advanceTrack(int trackIndex) {
201 if (trackIndex != mSelectedTrack) return;
202 ++mCurrentSampleIndex;
203 }
204
205 virtual ~MockSampleReader() override { AMediaExtractor_delete(mExtractor); }
206
207private:
208 MockSampleReader(AMediaExtractor* extractor) : mExtractor(extractor) {}
209 AMediaExtractor* mExtractor = nullptr;
210 int32_t mSampleCount = 0;
211 std::vector<std::pair<std::unique_ptr<uint8_t[]>, MediaSampleInfo>> mSamples;
212 int mSelectedTrack = -1;
213 int32_t mCurrentSampleIndex = 0;
214 int64_t mFirstPtsUs = 0;
215 int64_t mPtsDiff = 0;
216};
217
218static std::shared_ptr<AMediaFormat> GetDefaultTrackFormat(MediaType mediaType,
Linus Nilsson6bf46532020-10-07 11:58:02 -0700219 AMediaFormat* sourceFormat,
220 bool maxOperatingRate) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700221 // Default video config.
222 static constexpr int32_t kVideoBitRate = 20 * 1000 * 1000; // 20 mbps
223 static constexpr float kVideoFrameRate = 30.0f; // 30 fps
224
225 AMediaFormat* format = nullptr;
226
227 if (mediaType == kVideo) {
228 format = AMediaFormat_new();
229 AMediaFormat_copy(format, sourceFormat);
230 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
231 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kVideoBitRate);
232 AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, kVideoFrameRate);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700233
234 if (maxOperatingRate) {
235 AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_OPERATING_RATE, INT32_MAX);
236 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_PRIORITY, 1);
237 }
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700238 }
239 // nothing for audio.
240
241 return std::shared_ptr<AMediaFormat>(format, &AMediaFormat_delete);
242}
243
244/** Gets a MediaSampleReader for the source file */
245static std::shared_ptr<MediaSampleReader> GetSampleReader(const std::string& srcFileName,
246 bool mock) {
247 // Asset directory
248 static const std::string kAssetDirectory = "/data/local/tmp/TranscodingBenchmark/";
249
250 int srcFd = 0;
251 std::string srcPath = kAssetDirectory + srcFileName;
252
253 if ((srcFd = open(srcPath.c_str(), O_RDONLY)) < 0) {
254 return nullptr;
255 }
256
257 const size_t fileSize = lseek(srcFd, 0, SEEK_END);
258 lseek(srcFd, 0, SEEK_SET);
259
260 std::shared_ptr<MediaSampleReader> sampleReader;
261
262 if (mock) {
263 sampleReader = MockSampleReader::createFromFd(srcFd, 0 /* offset */, fileSize);
264 } else {
265 sampleReader = MediaSampleReaderNDK::createFromFd(srcFd, 0 /* offset */, fileSize);
266 }
267
268 if (srcFd > 0) close(srcFd);
269 return sampleReader;
270}
271
272/**
273 * Configures a MediaTrackTranscoder with an empty sample consumer so that the samples are returned
274 * to the transcoder immediately.
275 */
276static void ConfigureEmptySampleConsumer(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
277 uint32_t& sampleCount) {
278 transcoder->setSampleConsumer([&sampleCount](const std::shared_ptr<MediaSample>& sample) {
279 if (!(sample->info.flags & SAMPLE_FLAG_CODEC_CONFIG) && sample->info.size > 0) {
280 ++sampleCount;
281 }
282 });
283}
284
285/**
286 * Configures a MediaTrackTranscoder with the provided MediaSampleReader, reading from the first
287 * track that matches the specified media type.
288 */
289static bool ConfigureSampleReader(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
290 const std::shared_ptr<MediaSampleReader>& sampleReader,
Linus Nilsson6bf46532020-10-07 11:58:02 -0700291 MediaType mediaType, bool maxOperatingRate) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700292 int srcTrackIndex = -1;
293 std::shared_ptr<AMediaFormat> srcTrackFormat = nullptr;
294
295 for (int trackIndex = 0; trackIndex < sampleReader->getTrackCount(); ++trackIndex) {
296 AMediaFormat* trackFormat = sampleReader->getTrackFormat(trackIndex);
297
298 const char* mime = nullptr;
299 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
300
301 if ((mediaType == kVideo && strncmp(mime, "video/", 6) == 0) ||
302 (mediaType == kAudio && strncmp(mime, "audio/", 6) == 0)) {
303 srcTrackIndex = trackIndex;
304 srcTrackFormat = std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete);
305 break;
306 }
307 AMediaFormat_delete(trackFormat);
308 }
309
310 if (srcTrackIndex == -1) {
311 LOG(ERROR) << "No matching source track found";
312 return false;
313 }
314
315 media_status_t status = sampleReader->selectTrack(srcTrackIndex);
316 if (status != AMEDIA_OK) {
317 LOG(ERROR) << "Unable to select track";
318 return false;
319 }
320
Linus Nilsson6bf46532020-10-07 11:58:02 -0700321 auto destinationFormat =
322 GetDefaultTrackFormat(mediaType, srcTrackFormat.get(), maxOperatingRate);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700323 status = transcoder->configure(sampleReader, srcTrackIndex, destinationFormat);
324 if (status != AMEDIA_OK) {
325 LOG(ERROR) << "transcoder configure returned " << status;
326 return false;
327 }
328
329 return true;
330}
331
332static void BenchmarkTranscoder(benchmark::State& state, const std::string& srcFileName,
Linus Nilsson6bf46532020-10-07 11:58:02 -0700333 bool mockReader, MediaType mediaType, bool maxOperatingRate) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700334 for (auto _ : state) {
335 std::shared_ptr<TrackTranscoderCallbacks> callbacks =
336 std::make_shared<TrackTranscoderCallbacks>();
337 std::shared_ptr<MediaTrackTranscoder> transcoder;
338
339 if (mediaType == kVideo) {
340 transcoder = VideoTrackTranscoder::create(callbacks);
341 } else {
342 transcoder = std::make_shared<PassthroughTrackTranscoder>(callbacks);
343 }
344
345 std::shared_ptr<MediaSampleReader> sampleReader = GetSampleReader(srcFileName, mockReader);
346 if (sampleReader == nullptr) {
347 state.SkipWithError("Unable to create sample reader");
348 return;
349 }
350
Linus Nilsson6bf46532020-10-07 11:58:02 -0700351 if (!ConfigureSampleReader(transcoder, sampleReader, mediaType, maxOperatingRate)) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700352 state.SkipWithError("Unable to configure the transcoder");
353 return;
354 }
355
356 uint32_t sampleCount = 0;
357 ConfigureEmptySampleConsumer(transcoder, sampleCount);
358
359 if (!transcoder->start()) {
360 state.SkipWithError("Unable to start the transcoder");
361 return;
362 }
363
364 callbacks->waitForTranscodingFinished();
365 transcoder->stop();
366
367 if (callbacks->mStatus != AMEDIA_OK) {
368 state.SkipWithError("Transcoder failed with error");
369 return;
370 }
371
372 LOG(DEBUG) << "Number of samples received: " << sampleCount;
373 state.counters["FrameRate"] = benchmark::Counter(sampleCount, benchmark::Counter::kIsRate);
374 }
375}
376
Linus Nilsson6bf46532020-10-07 11:58:02 -0700377//-------------------------------- AVC to AVC Benchmarks -------------------------------------------
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700378
379static void BM_VideoTranscode_AVC2AVC_NoMuxer(benchmark::State& state) {
380 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson6bf46532020-10-07 11:58:02 -0700381 BenchmarkTranscoder(state, srcFile, false /* mockReader */, kVideo,
382 false /* maxOperatingRate */);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700383}
384
385static void BM_VideoTranscode_AVC2AVC_NoMuxer_NoExtractor(benchmark::State& state) {
386 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson6bf46532020-10-07 11:58:02 -0700387 BenchmarkTranscoder(state, srcFile, true /* mockReader */, kVideo,
388 false /* maxOperatingRate */);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700389}
390
Linus Nilsson6bf46532020-10-07 11:58:02 -0700391static void BM_VideoTranscode_AVC2AVC_NoMuxer_MaxOperatingRate(benchmark::State& state) {
392 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
393 BenchmarkTranscoder(state, srcFile, false /* mockReader */, kVideo,
394 true /* maxOperatingRate */);
395}
396
397static void BM_VideoTranscode_AVC2AVC_NoMuxer_NoExtractor_MaxOperatingRate(
398 benchmark::State& state) {
399 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
400 BenchmarkTranscoder(state, srcFile, true /* mockReader */, kVideo, true /* maxOperatingRate */);
401}
402
403//-------------------------------- HEVC to AVC Benchmarks ------------------------------------------
404
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700405static void BM_VideoTranscode_HEVC2AVC_NoMuxer(benchmark::State& state) {
406 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson6bf46532020-10-07 11:58:02 -0700407 BenchmarkTranscoder(state, srcFile, false /* mockReader */, kVideo,
408 false /* maxOperatingRate */);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700409}
410
411static void BM_VideoTranscode_HEVC2AVC_NoMuxer_NoExtractor(benchmark::State& state) {
412 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson6bf46532020-10-07 11:58:02 -0700413 BenchmarkTranscoder(state, srcFile, true /* mockReader */, kVideo,
414 false /* maxOperatingRate */);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700415}
416
Linus Nilsson6bf46532020-10-07 11:58:02 -0700417static void BM_VideoTranscode_HEVC2AVC_NoMuxer_MaxOperatingRate(benchmark::State& state) {
418 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
419 BenchmarkTranscoder(state, srcFile, false /* mockReader */, kVideo,
420 true /* maxOperatingRate */);
421}
422
423static void BM_VideoTranscode_HEVC2AVC_NoMuxer_NoExtractor_MaxOperatingRate(
424 benchmark::State& state) {
425 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
426 BenchmarkTranscoder(state, srcFile, true /* mockReader */, kVideo, true /* maxOperatingRate */);
427}
428
429//-------------------------------- Benchmark Registration ------------------------------------------
430
431// Benchmark registration wrapper for transcoding.
432#define TRANSCODER_BENCHMARK(func) \
433 BENCHMARK(func)->UseRealTime()->MeasureProcessCPUTime()->Unit(benchmark::kMillisecond)
434
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700435TRANSCODER_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoMuxer);
436TRANSCODER_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoMuxer_NoExtractor);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700437TRANSCODER_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoMuxer_MaxOperatingRate);
438TRANSCODER_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoMuxer_NoExtractor_MaxOperatingRate);
439
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700440TRANSCODER_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoMuxer);
441TRANSCODER_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoMuxer_NoExtractor);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700442TRANSCODER_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoMuxer_MaxOperatingRate);
443TRANSCODER_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoMuxer_NoExtractor_MaxOperatingRate);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700444
445BENCHMARK_MAIN();