• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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