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