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