blob: 351d80bbe4bb0be7fd5083dcfdc410666927cdb7 [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
Linus Nilssonfdb3e332020-09-18 17:11:41 -070064 virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
65 std::unique_lock lock(mMutex);
66 mFinished = true;
67 mCondition.notify_all();
68 }
69
Linus Nilsson8a96cfc2020-09-29 12:31:15 -070070 virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
71 media_status_t status) override {
72 std::unique_lock lock(mMutex);
73 mFinished = true;
74 mStatus = status;
75 mCondition.notify_all();
76 }
77
78 void waitForTranscodingFinished() {
79 std::unique_lock lock(mMutex);
80 while (!mFinished) {
81 mCondition.wait(lock);
82 }
83 }
84
85 media_status_t mStatus = AMEDIA_OK;
86
87private:
88 std::mutex mMutex;
89 std::condition_variable mCondition;
90 bool mFinished = false;
91};
92
93/**
94 * MockSampleReader holds a ringbuffer of the first samples in the provided source track. Samples
95 * are returned to the caller from the ringbuffer in a round-robin fashion with increasing
96 * timestamps. The number of samples returned before EOS matches the number of frames in the source
97 * track.
98 */
99class MockSampleReader : public MediaSampleReader {
100public:
101 static std::shared_ptr<MediaSampleReader> createFromFd(int fd, size_t offset, size_t size) {
102 AMediaExtractor* extractor = AMediaExtractor_new();
103 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
104 if (status != AMEDIA_OK) return nullptr;
105
106 auto sampleReader = std::shared_ptr<MockSampleReader>(new MockSampleReader(extractor));
107 return sampleReader;
108 }
109
110 AMediaFormat* getFileFormat() override { return AMediaExtractor_getFileFormat(mExtractor); }
111
112 size_t getTrackCount() const override { return AMediaExtractor_getTrackCount(mExtractor); }
113
114 AMediaFormat* getTrackFormat(int trackIndex) override {
115 return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
116 }
117
118 media_status_t selectTrack(int trackIndex) override {
119 if (mSelectedTrack >= 0) return AMEDIA_ERROR_UNSUPPORTED;
120 mSelectedTrack = trackIndex;
121
122 media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
123 if (status != AMEDIA_OK) return status;
124
125 // Get the sample count.
126 AMediaFormat* format = getTrackFormat(trackIndex);
127 const bool haveSampleCount =
128 AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_COUNT, &mSampleCount);
129 AMediaFormat_delete(format);
130
131 if (!haveSampleCount) {
132 LOG(ERROR) << "No sample count in track format.";
133 return AMEDIA_ERROR_UNSUPPORTED;
134 }
135
136 // Buffer samples.
137 const int32_t targetBufferCount = 60;
138 std::unique_ptr<uint8_t[]> buffer;
139 MediaSampleInfo info;
140 while (true) {
141 info.presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
142 info.flags = AMediaExtractor_getSampleFlags(mExtractor);
143 info.size = AMediaExtractor_getSampleSize(mExtractor);
144
145 // Finish buffering after either reading all the samples in the track or after
146 // completing the GOP satisfying the target count.
147 if (mSamples.size() == mSampleCount ||
148 (mSamples.size() >= targetBufferCount && info.flags & SAMPLE_FLAG_SYNC_SAMPLE)) {
149 break;
150 }
151
152 buffer.reset(new uint8_t[info.size]);
153
154 ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer.get(), info.size);
155 if (bytesRead != info.size) {
156 return AMEDIA_ERROR_UNKNOWN;
157 }
158
159 mSamples.emplace_back(std::move(buffer), info);
160
161 AMediaExtractor_advance(mExtractor);
162 }
163
164 mFirstPtsUs = mSamples[0].second.presentationTimeUs;
165 mPtsDiff = mSamples[1].second.presentationTimeUs - mSamples[0].second.presentationTimeUs;
166
167 return AMEDIA_OK;
168 }
169
170 media_status_t setEnforceSequentialAccess(bool enforce __unused) override { return AMEDIA_OK; }
171
172 media_status_t getEstimatedBitrateForTrack(int trackIndex __unused,
173 int32_t* bitrate __unused) override {
174 return AMEDIA_ERROR_UNSUPPORTED;
175 }
176
177 media_status_t getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) override {
178 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
179
180 if (mCurrentSampleIndex >= mSampleCount) {
181 info->presentationTimeUs = 0;
182 info->size = 0;
183 info->flags = SAMPLE_FLAG_END_OF_STREAM;
184 return AMEDIA_ERROR_END_OF_STREAM;
185 }
186
187 *info = mSamples[mCurrentSampleIndex % mSamples.size()].second;
188 info->presentationTimeUs = mFirstPtsUs + mCurrentSampleIndex * mPtsDiff;
189 return AMEDIA_OK;
190 }
191
192 media_status_t readSampleDataForTrack(int trackIndex, uint8_t* buffer,
193 size_t bufferSize) override {
194 if (trackIndex != mSelectedTrack) return AMEDIA_ERROR_INVALID_PARAMETER;
195
196 if (mCurrentSampleIndex >= mSampleCount) return AMEDIA_ERROR_END_OF_STREAM;
197
198 auto& p = mSamples[mCurrentSampleIndex % mSamples.size()];
199
200 if (bufferSize < p.second.size) return AMEDIA_ERROR_INVALID_PARAMETER;
Linus Nilssond98ea3b2020-10-15 09:27:18 -0700201 memcpy(buffer, p.first.get(), p.second.size);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700202
203 advanceTrack(trackIndex);
204 return AMEDIA_OK;
205 }
206
207 void advanceTrack(int trackIndex) {
208 if (trackIndex != mSelectedTrack) return;
209 ++mCurrentSampleIndex;
210 }
211
212 virtual ~MockSampleReader() override { AMediaExtractor_delete(mExtractor); }
213
214private:
215 MockSampleReader(AMediaExtractor* extractor) : mExtractor(extractor) {}
216 AMediaExtractor* mExtractor = nullptr;
217 int32_t mSampleCount = 0;
218 std::vector<std::pair<std::unique_ptr<uint8_t[]>, MediaSampleInfo>> mSamples;
219 int mSelectedTrack = -1;
220 int32_t mCurrentSampleIndex = 0;
221 int64_t mFirstPtsUs = 0;
222 int64_t mPtsDiff = 0;
223};
224
225static std::shared_ptr<AMediaFormat> GetDefaultTrackFormat(MediaType mediaType,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700226 AMediaFormat* sourceFormat) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700227 // Default video config.
228 static constexpr int32_t kVideoBitRate = 20 * 1000 * 1000; // 20 mbps
229 static constexpr float kVideoFrameRate = 30.0f; // 30 fps
230
231 AMediaFormat* format = nullptr;
232
233 if (mediaType == kVideo) {
234 format = AMediaFormat_new();
235 AMediaFormat_copy(format, sourceFormat);
236 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
237 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kVideoBitRate);
238 AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, kVideoFrameRate);
239 }
240 // nothing for audio.
241
242 return std::shared_ptr<AMediaFormat>(format, &AMediaFormat_delete);
243}
244
245/** Gets a MediaSampleReader for the source file */
246static std::shared_ptr<MediaSampleReader> GetSampleReader(const std::string& srcFileName,
247 bool mock) {
248 // Asset directory
249 static const std::string kAssetDirectory = "/data/local/tmp/TranscodingBenchmark/";
250
251 int srcFd = 0;
252 std::string srcPath = kAssetDirectory + srcFileName;
253
254 if ((srcFd = open(srcPath.c_str(), O_RDONLY)) < 0) {
255 return nullptr;
256 }
257
258 const size_t fileSize = lseek(srcFd, 0, SEEK_END);
259 lseek(srcFd, 0, SEEK_SET);
260
261 std::shared_ptr<MediaSampleReader> sampleReader;
262
263 if (mock) {
264 sampleReader = MockSampleReader::createFromFd(srcFd, 0 /* offset */, fileSize);
265 } else {
266 sampleReader = MediaSampleReaderNDK::createFromFd(srcFd, 0 /* offset */, fileSize);
267 }
268
269 if (srcFd > 0) close(srcFd);
270 return sampleReader;
271}
272
273/**
274 * Configures a MediaTrackTranscoder with an empty sample consumer so that the samples are returned
275 * to the transcoder immediately.
276 */
277static void ConfigureEmptySampleConsumer(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
278 uint32_t& sampleCount) {
279 transcoder->setSampleConsumer([&sampleCount](const std::shared_ptr<MediaSample>& sample) {
280 if (!(sample->info.flags & SAMPLE_FLAG_CODEC_CONFIG) && sample->info.size > 0) {
281 ++sampleCount;
282 }
283 });
284}
285
286/**
Linus Nilsson82a6a292020-10-09 11:19:29 -0700287 * Callback to edit track format for transcoding.
288 * @param dstFormat The default track format for the track type.
289 */
290using TrackFormatEditCallback = std::function<void(AMediaFormat* dstFormat)>;
291
292/**
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700293 * Configures a MediaTrackTranscoder with the provided MediaSampleReader, reading from the first
294 * track that matches the specified media type.
295 */
296static bool ConfigureSampleReader(const std::shared_ptr<MediaTrackTranscoder>& transcoder,
297 const std::shared_ptr<MediaSampleReader>& sampleReader,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700298 MediaType mediaType,
299 const TrackFormatEditCallback& formatEditor) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700300 int srcTrackIndex = -1;
301 std::shared_ptr<AMediaFormat> srcTrackFormat = nullptr;
302
303 for (int trackIndex = 0; trackIndex < sampleReader->getTrackCount(); ++trackIndex) {
304 AMediaFormat* trackFormat = sampleReader->getTrackFormat(trackIndex);
305
306 const char* mime = nullptr;
307 AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &mime);
308
309 if ((mediaType == kVideo && strncmp(mime, "video/", 6) == 0) ||
310 (mediaType == kAudio && strncmp(mime, "audio/", 6) == 0)) {
311 srcTrackIndex = trackIndex;
312 srcTrackFormat = std::shared_ptr<AMediaFormat>(trackFormat, &AMediaFormat_delete);
313 break;
314 }
315 AMediaFormat_delete(trackFormat);
316 }
317
318 if (srcTrackIndex == -1) {
319 LOG(ERROR) << "No matching source track found";
320 return false;
321 }
322
323 media_status_t status = sampleReader->selectTrack(srcTrackIndex);
324 if (status != AMEDIA_OK) {
325 LOG(ERROR) << "Unable to select track";
326 return false;
327 }
328
Linus Nilsson82a6a292020-10-09 11:19:29 -0700329 auto destinationFormat = GetDefaultTrackFormat(mediaType, srcTrackFormat.get());
330 if (formatEditor != nullptr) {
331 formatEditor(destinationFormat.get());
332 }
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700333 status = transcoder->configure(sampleReader, srcTrackIndex, destinationFormat);
334 if (status != AMEDIA_OK) {
335 LOG(ERROR) << "transcoder configure returned " << status;
336 return false;
337 }
338
339 return true;
340}
341
342static void BenchmarkTranscoder(benchmark::State& state, const std::string& srcFileName,
Linus Nilsson82a6a292020-10-09 11:19:29 -0700343 bool mockReader, MediaType mediaType,
344 const TrackFormatEditCallback& formatEditor = nullptr) {
345 static pthread_once_t once = PTHREAD_ONCE_INIT;
346 pthread_once(&once, ABinderProcess_startThreadPool);
347
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700348 for (auto _ : state) {
349 std::shared_ptr<TrackTranscoderCallbacks> callbacks =
350 std::make_shared<TrackTranscoderCallbacks>();
351 std::shared_ptr<MediaTrackTranscoder> transcoder;
352
353 if (mediaType == kVideo) {
354 transcoder = VideoTrackTranscoder::create(callbacks);
355 } else {
356 transcoder = std::make_shared<PassthroughTrackTranscoder>(callbacks);
357 }
358
359 std::shared_ptr<MediaSampleReader> sampleReader = GetSampleReader(srcFileName, mockReader);
360 if (sampleReader == nullptr) {
361 state.SkipWithError("Unable to create sample reader");
362 return;
363 }
364
Linus Nilsson82a6a292020-10-09 11:19:29 -0700365 if (!ConfigureSampleReader(transcoder, sampleReader, mediaType, formatEditor)) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700366 state.SkipWithError("Unable to configure the transcoder");
367 return;
368 }
369
370 uint32_t sampleCount = 0;
371 ConfigureEmptySampleConsumer(transcoder, sampleCount);
372
373 if (!transcoder->start()) {
374 state.SkipWithError("Unable to start the transcoder");
375 return;
376 }
377
378 callbacks->waitForTranscodingFinished();
379 transcoder->stop();
380
381 if (callbacks->mStatus != AMEDIA_OK) {
382 state.SkipWithError("Transcoder failed with error");
383 return;
384 }
385
386 LOG(DEBUG) << "Number of samples received: " << sampleCount;
387 state.counters["FrameRate"] = benchmark::Counter(sampleCount, benchmark::Counter::kIsRate);
388 }
389}
390
Linus Nilsson82a6a292020-10-09 11:19:29 -0700391static void BenchmarkTranscoderWithOperatingRate(benchmark::State& state,
392 const std::string& srcFile, bool mockReader,
393 MediaType mediaType) {
394 TrackFormatEditCallback editor;
395 const int32_t operatingRate = state.range(0);
396 const int32_t priority = state.range(1);
397
398 if (operatingRate >= 0 && priority >= 0) {
399 editor = [operatingRate, priority](AMediaFormat* format) {
400 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_OPERATING_RATE, operatingRate);
401 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_PRIORITY, priority);
402 };
403 }
404 BenchmarkTranscoder(state, srcFile, mockReader, mediaType, editor);
405}
406
Linus Nilsson6bf46532020-10-07 11:58:02 -0700407//-------------------------------- AVC to AVC Benchmarks -------------------------------------------
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700408
Linus Nilsson82a6a292020-10-09 11:19:29 -0700409static void BM_VideoTranscode_AVC2AVC(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700410 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700411 BenchmarkTranscoderWithOperatingRate(state, srcFile, false /* mockReader */, kVideo);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700412}
413
Linus Nilsson82a6a292020-10-09 11:19:29 -0700414static void BM_VideoTranscode_AVC2AVC_NoExtractor(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700415 const char* srcFile = "video_1920x1080_3648frame_h264_22Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700416 BenchmarkTranscoderWithOperatingRate(state, srcFile, true /* mockReader */, kVideo);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700417}
418
419//-------------------------------- HEVC to AVC Benchmarks ------------------------------------------
420
Linus Nilsson82a6a292020-10-09 11:19:29 -0700421static void BM_VideoTranscode_HEVC2AVC(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700422 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700423 BenchmarkTranscoderWithOperatingRate(state, srcFile, false /* mockReader */, kVideo);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700424}
425
Linus Nilsson82a6a292020-10-09 11:19:29 -0700426static void BM_VideoTranscode_HEVC2AVC_NoExtractor(benchmark::State& state) {
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700427 const char* srcFile = "video_1920x1080_3863frame_hevc_4Mbps_30fps_aac.mp4";
Linus Nilsson82a6a292020-10-09 11:19:29 -0700428 BenchmarkTranscoderWithOperatingRate(state, srcFile, true /* mockReader */, kVideo);
Linus Nilsson6bf46532020-10-07 11:58:02 -0700429}
430
431//-------------------------------- Benchmark Registration ------------------------------------------
432
433// Benchmark registration wrapper for transcoding.
434#define TRANSCODER_BENCHMARK(func) \
435 BENCHMARK(func)->UseRealTime()->MeasureProcessCPUTime()->Unit(benchmark::kMillisecond)
436
Linus Nilsson82a6a292020-10-09 11:19:29 -0700437// Benchmark registration for testing different operating rate and priority combinations.
438#define TRANSCODER_OPERATING_RATE_BENCHMARK(func) \
439 TRANSCODER_BENCHMARK(func) \
440 ->Args({-1, -1}) /* <-- Use default */ \
441 ->Args({240, 0}) \
442 ->Args({INT32_MAX, 0}) \
443 ->Args({240, 1}) \
444 ->Args({INT32_MAX, 1})
Linus Nilsson6bf46532020-10-07 11:58:02 -0700445
Linus Nilsson82a6a292020-10-09 11:19:29 -0700446TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_AVC2AVC);
447TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_AVC2AVC_NoExtractor);
448
449TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_HEVC2AVC);
450TRANSCODER_OPERATING_RATE_BENCHMARK(BM_VideoTranscode_HEVC2AVC_NoExtractor);
Linus Nilsson8a96cfc2020-09-29 12:31:15 -0700451
452BENCHMARK_MAIN();