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