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