blob: aee0ed694a3bbab5c5c691737aa3cb4fa8b3fd9b [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>
Linus Nilsson82a6a292020-10-09 11:19:29 -070036#include <android/binder_process.h>
Linus Nilsson8a96cfc2020-09-29 12:31:15 -070037#include <benchmark/benchmark.h>
38#include <fcntl.h>
39#include <media/MediaSampleReader.h>
40#include <media/MediaSampleReaderNDK.h>
41#include <media/MediaTrackTranscoder.h>
42#include <media/MediaTrackTranscoderCallback.h>
43#include <media/NdkCommon.h>
44#include <media/PassthroughTrackTranscoder.h>
45#include <media/VideoTrackTranscoder.h>
46
47using namespace android;
48
49typedef enum {
50 kVideo,
51 kAudio,
52} MediaType;
53
54class TrackTranscoderCallbacks : public MediaTrackTranscoderCallback {
55public:
56 virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder __unused) override {}
57
58 virtual void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) override {
59 std::unique_lock lock(mMutex);
60 mFinished = true;
61 mCondition.notify_all();
62 }
63
64 virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
65 media_status_t status) override {
66 std::unique_lock lock(mMutex);
67 mFinished = true;
68 mStatus = status;
69 mCondition.notify_all();
70 }
71
72 void waitForTranscodingFinished() {
73 std::unique_lock lock(mMutex);
74 while (!mFinished) {
75 mCondition.wait(lock);
76 }
77 }
78
79 media_status_t mStatus = AMEDIA_OK;
80
81private:
82 std::mutex mMutex;
83 std::condition_variable mCondition;
84 bool mFinished = false;
85};
86
87/**
88 * MockSampleReader holds a ringbuffer of the first samples in the provided source track. Samples
89 * are returned to the caller from the ringbuffer in a round-robin fashion with increasing
90 * timestamps. The number of samples returned before EOS matches the number of frames in the source
91 * track.
92 */
93class MockSampleReader : public MediaSampleReader {
94public:
95 static std::shared_ptr<MediaSampleReader> createFromFd(int fd, size_t offset, size_t size) {
96 AMediaExtractor* extractor = AMediaExtractor_new();
97 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
98 if (status != AMEDIA_OK) return nullptr;
99
100 auto sampleReader = std::shared_ptr<MockSampleReader>(new MockSampleReader(extractor));
101 return sampleReader;
102 }
103
104 AMediaFormat* getFileFormat() override { return AMediaExtractor_getFileFormat(mExtractor); }
105
106 size_t getTrackCount() const override { return AMediaExtractor_getTrackCount(mExtractor); }
107
108 AMediaFormat* getTrackFormat(int trackIndex) override {
109 return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
110 }
111
112 media_status_t selectTrack(int trackIndex) override {
113 if (mSelectedTrack >= 0) return AMEDIA_ERROR_UNSUPPORTED;
114 mSelectedTrack = trackIndex;
115
116 media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
117 if (status != AMEDIA_OK) return status;
118
119 // Get the sample count.
120 AMediaFormat* format = getTrackFormat(trackIndex);
121 const bool haveSampleCount =
122 AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_COUNT, &mSampleCount);
123 AMediaFormat_delete(format);
124
125 if (!haveSampleCount) {
126 LOG(ERROR) << "No sample count in track format.";
127 return AMEDIA_ERROR_UNSUPPORTED;
128 }
129
130 // Buffer samples.
131 const int32_t targetBufferCount = 60;
132 std::unique_ptr<uint8_t[]> buffer;
133 MediaSampleInfo info;
134 while (true) {
135 info.presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
136 info.flags = AMediaExtractor_getSampleFlags(mExtractor);
137 info.size = AMediaExtractor_getSampleSize(mExtractor);
138
139 // Finish buffering after either reading all the samples in the track or after
140 // completing the GOP satisfying the target count.
141 if (mSamples.size() == mSampleCount ||
142 (mSamples.size() >= targetBufferCount && info.flags & SAMPLE_FLAG_SYNC_SAMPLE)) {
143 break;
144 }
145
146 buffer.reset(new uint8_t[info.size]);
147
148 ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer.get(), info.size);
149 if (bytesRead != info.size) {
150 return AMEDIA_ERROR_UNKNOWN;
151 }
152
153 mSamples.emplace_back(std::move(buffer), info);
154
155 AMediaExtractor_advance(mExtractor);
156 }
157
158 mFirstPtsUs = mSamples[0].second.presentationTimeUs;
159 mPtsDiff = mSamples[1].second.presentationTimeUs - mSamples[0].second.presentationTimeUs;
160
161 return AMEDIA_OK;
162 }
163
164 media_status_t setEnforceSequentialAccess(bool enforce __unused) override { return AMEDIA_OK; }
165
166 media_status_t getEstimatedBitrateForTrack(int trackIndex __unused,
167 int32_t* bitrate __unused) override {
168 return AMEDIA_ERROR_UNSUPPORTED;
169 }
170
171 media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) override {
172 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
173
174 if (mCurrentSampleIndex >= mSampleCount) {
175 info->presentationTimeUs = 0;
176 info->size = 0;
177 info->flags = SAMPLE_FLAG_END_OF_STREAM;
178 return AMEDIA_ERROR_END_OF_STREAM;
179 }
180
181 *info = mSamples[mCurrentSampleIndex % mSamples.size()].second;
182 info->presentationTimeUs = mFirstPtsUs + mCurrentSampleIndex * mPtsDiff;
183 return AMEDIA_OK;
184 }
185
186 media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
187 size_t bufferSize) override {
188 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
189
190 if (mCurrentSampleIndex >= mSampleCount) return AMEDIA_ERROR_END_OF_STREAM;
191
192 auto& p = mSamples[mCurrentSampleIndex % mSamples.size()];
193
194 if (bufferSize < p.second.size) return AMEDIA_ERROR_INVALID_PARAMETER;
Linus Nilssond98ea3b2020-10-15 09:27:18 -0700195 memcpy(buffer, p.first.get(), p.second.size);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700196
197 advanceTrack(trackIndex);
198 return AMEDIA_OK;
199 }
200
201 void advanceTrack(int trackIndex) {
202 if (trackIndex != mSelectedTrack) return;
203 ++mCurrentSampleIndex;
204 }
205
206 virtual ~MockSampleReader() override { AMediaExtractor_delete(mExtractor); }
207
208private:
209 MockSampleReader(AMediaExtractor* extractor) : mExtractor(extractor) {}
210 AMediaExtractor* mExtractor = nullptr;
211 int32_t mSampleCount = 0;
212 std::vector<std::pair<std::unique_ptr<uint8_t[]>, MediaSampleInfo>> mSamples;
213 int mSelectedTrack = -1;
214 int32_t mCurrentSampleIndex = 0;
215 int64_t mFirstPtsUs = 0;
216 int64_t mPtsDiff = 0;
217};
218
219static std::shared_ptr<AMediaFormat> GetDefaultTrackFormat(MediaType mediaType,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700220 AMediaFormat* sourceFormat) {
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);
233 }
234 // nothing for audio.
235
236 return std::shared_ptr<AMediaFormat>(format, &AMediaFormat_delete);
237}
238
239/** Gets a MediaSampleReader for the source file */
240static std::shared_ptr<MediaSampleReader> GetSampleReader(const std::string& srcFileName,
241 bool mock) {
242 // Asset directory
243 static const std::string kAssetDirectory = "/data/local/tmp/TranscodingBenchmark/";
244
245 int srcFd = 0;
246 std::string srcPath = kAssetDirectory + srcFileName;
247
248 if ((srcFd = open(srcPath.c_str(), O_RDONLY)) < 0) {
249 return nullptr;
250 }
251
252 const size_t fileSize = lseek(srcFd, 0, SEEK_END);
253 lseek(srcFd, 0, SEEK_SET);
254
255 std::shared_ptr<MediaSampleReader> sampleReader;
256
257 if (mock) {
258 sampleReader = MockSampleReader::createFromFd(srcFd, 0 /* offset */, fileSize);
259 } else {
260 sampleReader = MediaSampleReaderNDK::createFromFd(srcFd, 0 /* offset */, fileSize);
261 }
262
263 if (srcFd > 0) close(srcFd);
264 return sampleReader;
265}
266
267/**
268 * Configures a MediaTrackTranscoder with an empty sample consumer so that the samples are returned
269 * to the transcoder immediately.
270 */
271static void ConfigureEmptySampleConsumer(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
272 uint32_t& sampleCount) {
273 transcoder->setSampleConsumer([&sampleCount](const std::shared_ptr<MediaSample>& sample) {
274 if (!(sample->info.flags & SAMPLE_FLAG_CODEC_CONFIG) && sample->info.size > 0) {
275 ++sampleCount;
276 }
277 });
278}
279
280/**
Linus Nilsson82a6a292020-10-09 11:19:29 -0700281 * Callback to edit track format for transcoding.
282 * @param dstFormat The default track format for the track type.
283 */
284using TrackFormatEditCallback = std::function<void(AMediaFormat* dstFormat)>;
285
286/**
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700287 * Configures a MediaTrackTranscoder with the provided MediaSampleReader, reading from the first
288 * track that matches the specified media type.
289 */
290static bool ConfigureSampleReader(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
291 const std::shared_ptr<MediaSampleReader>& sampleReader,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700292 MediaType mediaType,
293 const TrackFormatEditCallback& formatEditor) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700294 int srcTrackIndex = -1;
295 std::shared_ptr<AMediaFormat> srcTrackFormat = nullptr;
296
297 for (int trackIndex = 0; trackIndex < sampleReader->getTrackCount(); ++trackIndex) {
298 AMediaFormat* trackFormat = sampleReader->getTrackFormat(trackIndex);
299
300 const char* mime = nullptr;
301 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
302
303 if ((mediaType == kVideo && strncmp(mime, "video/", 6) == 0) ||
304 (mediaType == kAudio && strncmp(mime, "audio/", 6) == 0)) {
305 srcTrackIndex = trackIndex;
306 srcTrackFormat = std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete);
307 break;
308 }
309 AMediaFormat_delete(trackFormat);
310 }
311
312 if (srcTrackIndex == -1) {
313 LOG(ERROR) << "No matching source track found";
314 return false;
315 }
316
317 media_status_t status = sampleReader->selectTrack(srcTrackIndex);
318 if (status != AMEDIA_OK) {
319 LOG(ERROR) << "Unable to select track";
320 return false;
321 }
322
Linus Nilsson82a6a292020-10-09 11:19:29 -0700323 auto destinationFormat = GetDefaultTrackFormat(mediaType, srcTrackFormat.get());
324 if (formatEditor != nullptr) {
325 formatEditor(destinationFormat.get());
326 }
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700327 status = transcoder->configure(sampleReader, srcTrackIndex, destinationFormat);
328 if (status != AMEDIA_OK) {
329 LOG(ERROR) << "transcoder configure returned " << status;
330 return false;
331 }
332
333 return true;
334}
335
336static void BenchmarkTranscoder(benchmark::State& state, const std::string& srcFileName,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700337 bool mockReader, MediaType mediaType,
338 const TrackFormatEditCallback& formatEditor = nullptr) {
339 static pthread_once_t once = PTHREAD_ONCE_INIT;
340 pthread_once(&once, ABinderProcess_startThreadPool);
341
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700342 for (auto _ : state) {
343 std::shared_ptr<TrackTranscoderCallbacks> callbacks =
344 std::make_shared<TrackTranscoderCallbacks>();
345 std::shared_ptr<MediaTrackTranscoder> transcoder;
346
347 if (mediaType == kVideo) {
348 transcoder = VideoTrackTranscoder::create(callbacks);
349 } else {
350 transcoder = std::make_shared<PassthroughTrackTranscoder>(callbacks);
351 }
352
353 std::shared_ptr<MediaSampleReader> sampleReader = GetSampleReader(srcFileName, mockReader);
354 if (sampleReader == nullptr) {
355 state.SkipWithError("Unable to create sample reader");
356 return;
357 }
358
Linus Nilsson82a6a292020-10-09 11:19:29 -0700359 if (!ConfigureSampleReader(transcoder, sampleReader, mediaType, formatEditor)) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700360 state.SkipWithError("Unable to configure the transcoder");
361 return;
362 }
363
364 uint32_t sampleCount = 0;
365 ConfigureEmptySampleConsumer(transcoder, sampleCount);
366
367 if (!transcoder->start()) {
368 state.SkipWithError("Unable to start the transcoder");
369 return;
370 }
371
372 callbacks->waitForTranscodingFinished();
373 transcoder->stop();
374
375 if (callbacks->mStatus != AMEDIA_OK) {
376 state.SkipWithError("Transcoder failed with error");
377 return;
378 }
379
380 LOG(DEBUG) << "Number of samples received: " << sampleCount;
381 state.counters["FrameRate"] = benchmark::Counter(sampleCount, benchmark::Counter::kIsRate);
382 }
383}
384
Linus Nilsson82a6a292020-10-09 11:19:29 -0700385static void BenchmarkTranscoderWithOperatingRate(benchmark::State& state,
386 const std::string& srcFile, bool mockReader,
387 MediaType mediaType) {
388 TrackFormatEditCallback editor;
389 const int32_t operatingRate = state.range(0);
390 const int32_t priority = state.range(1);
391
392 if (operatingRate >= 0 && priority >= 0) {
393 editor = [operatingRate, priority](AMediaFormat* format) {
394 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_OPERATING_RATE, operatingRate);
395 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_PRIORITY, priority);
396 };
397 }
398 BenchmarkTranscoder(state, srcFile, mockReader, mediaType, editor);
399}
400
Linus Nilsson6bf46532020-10-07 11:58:02 -0700401//-------------------------------- AVC to AVC Benchmarks -------------------------------------------
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700402
Linus Nilsson82a6a292020-10-09 11:19:29 -0700403static void BM_VideoTranscode_AVC2AVC(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700404 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700405 BenchmarkTranscoderWithOperatingRate(state, srcFile, false /* mockReader */, kVideo);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700406}
407
Linus Nilsson82a6a292020-10-09 11:19:29 -0700408static void BM_VideoTranscode_AVC2AVC_NoExtractor(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700409 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700410 BenchmarkTranscoderWithOperatingRate(state, srcFile, true /* mockReader */, kVideo);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700411}
412
413//-------------------------------- HEVC to AVC Benchmarks ------------------------------------------
414
Linus Nilsson82a6a292020-10-09 11:19:29 -0700415static void BM_VideoTranscode_HEVC2AVC(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700416 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700417 BenchmarkTranscoderWithOperatingRate(state, srcFile, false /* mockReader */, kVideo);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700418}
419
Linus Nilsson82a6a292020-10-09 11:19:29 -0700420static void BM_VideoTranscode_HEVC2AVC_NoExtractor(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700421 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700422 BenchmarkTranscoderWithOperatingRate(state, srcFile, true /* mockReader */, kVideo);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700423}
424
425//-------------------------------- Benchmark Registration ------------------------------------------
426
427// Benchmark registration wrapper for transcoding.
428#define TRANSCODER_BENCHMARK(func) \
429 BENCHMARK(func)->UseRealTime()->MeasureProcessCPUTime()->Unit(benchmark::kMillisecond)
430
Linus Nilsson82a6a292020-10-09 11:19:29 -0700431// Benchmark registration for testing different operating rate and priority combinations.
432#define TRANSCODER_OPERATING_RATE_BENCHMARK(func) \
433 TRANSCODER_BENCHMARK(func) \
434 ->Args({-1, -1}) /* <-- Use default */ \
435 ->Args({240, 0}) \
436 ->Args({INT32_MAX, 0}) \
437 ->Args({240, 1}) \
438 ->Args({INT32_MAX, 1})
Linus Nilsson6bf46532020-10-07 11:58:02 -0700439
Linus Nilsson82a6a292020-10-09 11:19:29 -0700440TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_AVC2AVC);
441TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoExtractor);
442
443TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_HEVC2AVC);
444TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoExtractor);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700445
446BENCHMARK_MAIN();