blob: 6a00a10216b4aaa15ebfb4650a30e053334b066f [file] [log] [blame]
Linus Nilsson478df7e2020-01-29 15:34:24 -08001/*
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// #define LOG_NDEBUG 0
18#define LOG_TAG "MediaSampleReader"
19
20#include <android-base/logging.h>
21#include <media/MediaSampleReaderNDK.h>
22
23#include <algorithm>
Linus Nilsson800793f2020-07-31 16:16:38 -070024#include <cmath>
Linus Nilsson478df7e2020-01-29 15:34:24 -080025#include <vector>
26
27namespace android {
28
Linus Nilsson0da327a2020-01-31 16:22:18 -080029// Check that the extractor sample flags have the expected NDK meaning.
30static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
31 "Sample flag mismatch: SYNC_SAMPLE");
32
Linus Nilsson478df7e2020-01-29 15:34:24 -080033// static
34std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
35 size_t size) {
36 AMediaExtractor* extractor = AMediaExtractor_new();
37 if (extractor == nullptr) {
38 LOG(ERROR) << "Unable to allocate AMediaExtractor";
39 return nullptr;
40 }
41
42 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
43 if (status != AMEDIA_OK) {
44 LOG(ERROR) << "AMediaExtractor_setDataSourceFd returned error: " << status;
45 AMediaExtractor_delete(extractor);
46 return nullptr;
47 }
48
49 auto sampleReader = std::shared_ptr<MediaSampleReaderNDK>(new MediaSampleReaderNDK(extractor));
50 status = sampleReader->init();
51 if (status != AMEDIA_OK) {
52 LOG(ERROR) << "MediaSampleReaderNDK::init returned error: " << status;
53 return nullptr;
54 }
55
56 return sampleReader;
57}
58
59MediaSampleReaderNDK::MediaSampleReaderNDK(AMediaExtractor* extractor)
60 : mExtractor(extractor), mTrackCount(AMediaExtractor_getTrackCount(mExtractor)) {
61 if (mTrackCount > 0) {
62 mTrackCursors.resize(mTrackCount);
63 mTrackCursors.resize(mTrackCount);
64 }
65}
66
67media_status_t MediaSampleReaderNDK::init() {
68 for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
69 media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
70 if (status != AMEDIA_OK) {
71 LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
72 return status;
73 }
74 }
75
76 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
77 if (mExtractorTrackIndex >= 0) {
78 mTrackCursors[mExtractorTrackIndex].current.set(mExtractorSampleIndex,
79 AMediaExtractor_getSampleTime(mExtractor));
80 } else if (mTrackCount > 0) {
81 // The extractor track index is only allowed to be invalid if there are no tracks.
82 LOG(ERROR) << "Track index " << mExtractorTrackIndex << " is invalid for track count "
83 << mTrackCount;
84 return AMEDIA_ERROR_MALFORMED;
85 }
86
87 return AMEDIA_OK;
88}
89
90MediaSampleReaderNDK::~MediaSampleReaderNDK() {
91 if (mExtractor != nullptr) {
92 AMediaExtractor_delete(mExtractor);
93 }
94}
95
96bool MediaSampleReaderNDK::advanceExtractor_l() {
97 // Reset the "next" sample time whenever the extractor advances past a sample that is current,
98 // to ensure that "next" is appropriately updated when the extractor advances over the next
99 // sample of that track.
100 if (mTrackCursors[mExtractorTrackIndex].current.isSet &&
101 mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex) {
102 mTrackCursors[mExtractorTrackIndex].next.reset();
103 }
104
105 if (!AMediaExtractor_advance(mExtractor)) {
106 return false;
107 }
108
109 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
110 mExtractorSampleIndex++;
111
112 SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
113 if (mExtractorSampleIndex > cursor.previous.index) {
114 if (!cursor.current.isSet) {
115 cursor.current.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
116 } else if (!cursor.next.isSet && mExtractorSampleIndex > cursor.current.index) {
117 cursor.next.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
118 }
119 }
120 return true;
121}
122
123media_status_t MediaSampleReaderNDK::seekExtractorBackwards_l(int64_t targetTimeUs,
124 int targetTrackIndex,
125 uint64_t targetSampleIndex) {
126 if (targetSampleIndex > mExtractorSampleIndex) {
127 LOG(ERROR) << "Error: Forward seek is not supported";
128 return AMEDIA_ERROR_UNSUPPORTED;
129 }
130
131 // AMediaExtractor supports reading negative timestamps but does not support seeking to them.
132 const int64_t seekToTimeUs = std::max(targetTimeUs, (int64_t)0);
133 media_status_t status =
134 AMediaExtractor_seekTo(mExtractor, seekToTimeUs, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
135 if (status != AMEDIA_OK) {
136 LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
137 return status;
138 }
139 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
140 int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
141
142 while (sampleTimeUs != targetTimeUs || mExtractorTrackIndex != targetTrackIndex) {
143 if (!AMediaExtractor_advance(mExtractor)) {
144 return AMEDIA_ERROR_END_OF_STREAM;
145 }
146 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
147 sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
148 }
149 mExtractorSampleIndex = targetSampleIndex;
150 return AMEDIA_OK;
151}
152
153void MediaSampleReaderNDK::advanceTrack(int trackIndex) {
154 std::scoped_lock lock(mExtractorMutex);
155
156 if (trackIndex < 0 || trackIndex >= mTrackCount) {
157 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
158 return;
159 }
160
161 // Note: Positioning the extractor before advancing the track is needed for two reasons:
162 // 1. To enable multiple advances without explicitly letting the extractor catch up.
163 // 2. To prevent the extractor from being farther than "next".
164 (void)positionExtractorForTrack_l(trackIndex);
165
166 SampleCursor& cursor = mTrackCursors[trackIndex];
167 cursor.previous = cursor.current;
168 cursor.current = cursor.next;
169 cursor.next.reset();
170}
171
172media_status_t MediaSampleReaderNDK::positionExtractorForTrack_l(int trackIndex) {
173 media_status_t status = AMEDIA_OK;
174 const SampleCursor& cursor = mTrackCursors[trackIndex];
175
176 // Seek backwards if the extractor is ahead of the current time.
177 if (cursor.current.isSet && mExtractorSampleIndex > cursor.current.index) {
178 status = seekExtractorBackwards_l(cursor.current.timeStampUs, trackIndex,
179 cursor.current.index);
180 if (status != AMEDIA_OK) return status;
181 }
182
183 // Advance until extractor points to the current sample.
184 while (!(cursor.current.isSet && cursor.current.index == mExtractorSampleIndex)) {
185 if (!advanceExtractor_l()) {
186 return AMEDIA_ERROR_END_OF_STREAM;
187 }
188 }
189
190 return AMEDIA_OK;
191}
192
Linus Nilsson800793f2020-07-31 16:16:38 -0700193media_status_t MediaSampleReaderNDK::getEstimatedBitrateForTrack(int trackIndex, int32_t* bitrate) {
194 std::scoped_lock lock(mExtractorMutex);
195 media_status_t status = AMEDIA_OK;
196
197 if (trackIndex < 0 || trackIndex >= mTrackCount) {
198 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
199 return AMEDIA_ERROR_INVALID_PARAMETER;
200 } else if (bitrate == nullptr) {
201 LOG(ERROR) << "bitrate pointer is NULL.";
202 return AMEDIA_ERROR_INVALID_PARAMETER;
203 }
204
205 // Rewind the extractor and sample from the beginning of the file.
206 if (mExtractorSampleIndex > 0) {
207 status = AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
208 if (status != AMEDIA_OK) {
209 LOG(ERROR) << "Unable to reset extractor: " << status;
210 return status;
211 }
212
213 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
214 mExtractorSampleIndex = 0;
215 }
216
217 // Sample the track.
218 static constexpr int64_t kSamplingDurationUs = 10 * 1000 * 1000; // 10 seconds
219 size_t lastSampleSize = 0;
220 size_t totalSampleSize = 0;
221 int64_t firstSampleTimeUs = 0;
222 int64_t lastSampleTimeUs = 0;
223
224 do {
225 if (mExtractorTrackIndex == trackIndex) {
226 lastSampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
227 if (totalSampleSize == 0) {
228 firstSampleTimeUs = lastSampleTimeUs;
229 }
230
231 lastSampleSize = AMediaExtractor_getSampleSize(mExtractor);
232 totalSampleSize += lastSampleSize;
233 }
234 } while ((lastSampleTimeUs - firstSampleTimeUs) < kSamplingDurationUs && advanceExtractor_l());
235
236 int64_t durationUs = 0;
237 const int64_t sampledDurationUs = lastSampleTimeUs - firstSampleTimeUs;
238
239 if (sampledDurationUs < kSamplingDurationUs) {
240 // Track is shorter than the sampling duration so use the full track duration to get better
241 // accuracy (i.e. don't skip the last sample).
242 AMediaFormat* trackFormat = getTrackFormat(trackIndex);
243 if (!AMediaFormat_getInt64(trackFormat, AMEDIAFORMAT_KEY_DURATION, &durationUs)) {
244 durationUs = 0;
245 }
246 AMediaFormat_delete(trackFormat);
247 }
248
249 if (durationUs == 0) {
250 // The sampled duration does not account for the last sample's duration so its size should
251 // not be included either.
252 totalSampleSize -= lastSampleSize;
253 durationUs = sampledDurationUs;
254 }
255
256 if (totalSampleSize == 0 || durationUs <= 0) {
257 LOG(ERROR) << "Unable to estimate track bitrate";
258 return AMEDIA_ERROR_MALFORMED;
259 }
260
261 *bitrate = roundf((float)totalSampleSize * 8 * 1000000 / durationUs);
262 return AMEDIA_OK;
263}
264
Linus Nilsson478df7e2020-01-29 15:34:24 -0800265media_status_t MediaSampleReaderNDK::getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) {
266 std::scoped_lock lock(mExtractorMutex);
267
268 if (trackIndex < 0 || trackIndex >= mTrackCount) {
269 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
270 return AMEDIA_ERROR_INVALID_PARAMETER;
271 } else if (info == nullptr) {
272 LOG(ERROR) << "MediaSampleInfo pointer is NULL.";
273 return AMEDIA_ERROR_INVALID_PARAMETER;
274 }
275
276 media_status_t status = positionExtractorForTrack_l(trackIndex);
277 if (status == AMEDIA_OK) {
278 info->presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
279 info->flags = AMediaExtractor_getSampleFlags(mExtractor);
280 info->size = AMediaExtractor_getSampleSize(mExtractor);
281 } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
282 info->presentationTimeUs = 0;
283 info->flags = SAMPLE_FLAG_END_OF_STREAM;
284 info->size = 0;
285 }
286
287 return status;
288}
289
290media_status_t MediaSampleReaderNDK::readSampleDataForTrack(int trackIndex, uint8_t* buffer,
291 size_t bufferSize) {
292 std::scoped_lock lock(mExtractorMutex);
293
294 if (trackIndex < 0 || trackIndex >= mTrackCount) {
295 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
296 return AMEDIA_ERROR_INVALID_PARAMETER;
297 } else if (buffer == nullptr) {
298 LOG(ERROR) << "buffer pointer is NULL";
299 return AMEDIA_ERROR_INVALID_PARAMETER;
300 }
301
302 media_status_t status = positionExtractorForTrack_l(trackIndex);
303 if (status != AMEDIA_OK) return status;
304
305 ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
306 if (bufferSize < sampleSize) {
307 LOG(ERROR) << "Buffer is too small for sample, " << bufferSize << " vs " << sampleSize;
308 return AMEDIA_ERROR_INVALID_PARAMETER;
309 }
310
311 ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer, bufferSize);
312 if (bytesRead < sampleSize) {
313 LOG(ERROR) << "Unable to read full sample, " << bytesRead << " vs " << sampleSize;
314 return AMEDIA_ERROR_INVALID_PARAMETER;
315 }
316
317 return AMEDIA_OK;
318}
319
320AMediaFormat* MediaSampleReaderNDK::getFileFormat() {
321 return AMediaExtractor_getFileFormat(mExtractor);
322}
323
324size_t MediaSampleReaderNDK::getTrackCount() const {
325 return mTrackCount;
326}
327
328AMediaFormat* MediaSampleReaderNDK::getTrackFormat(int trackIndex) {
329 if (trackIndex < 0 || trackIndex >= mTrackCount) {
330 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
331 return AMediaFormat_new();
332 }
333
334 return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
335}
336
337} // namespace android