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 // #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>
24 #include <cmath>
25
26 namespace android {
27
28 // Check that the extractor sample flags have the expected NDK meaning.
29 static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
30 "Sample flag mismatch: SYNC_SAMPLE");
31
32 // static
createFromFd(int fd,size_t offset,size_t size)33 std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
34 size_t size) {
35 AMediaExtractor* extractor = AMediaExtractor_new();
36 if (extractor == nullptr) {
37 LOG(ERROR) << "Unable to allocate AMediaExtractor";
38 return nullptr;
39 }
40
41 media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
42 if (status != AMEDIA_OK) {
43 LOG(ERROR) << "AMediaExtractor_setDataSourceFd returned error: " << status;
44 AMediaExtractor_delete(extractor);
45 return nullptr;
46 }
47
48 auto sampleReader = std::shared_ptr<MediaSampleReaderNDK>(new MediaSampleReaderNDK(extractor));
49 return sampleReader;
50 }
51
MediaSampleReaderNDK(AMediaExtractor * extractor)52 MediaSampleReaderNDK::MediaSampleReaderNDK(AMediaExtractor* extractor)
53 : mExtractor(extractor), mTrackCount(AMediaExtractor_getTrackCount(mExtractor)) {
54 if (mTrackCount > 0) {
55 mTrackCursors.resize(mTrackCount);
56 }
57 }
58
~MediaSampleReaderNDK()59 MediaSampleReaderNDK::~MediaSampleReaderNDK() {
60 if (mExtractor != nullptr) {
61 AMediaExtractor_delete(mExtractor);
62 }
63 }
64
advanceTrack_l(int trackIndex)65 void MediaSampleReaderNDK::advanceTrack_l(int trackIndex) {
66 if (!mEnforceSequentialAccess) {
67 // Note: Positioning the extractor before advancing the track is needed for two reasons:
68 // 1. To enable multiple advances without explicitly letting the extractor catch up.
69 // 2. To prevent the extractor from being farther than "next".
70 (void)moveToTrack_l(trackIndex);
71 }
72
73 SampleCursor& cursor = mTrackCursors[trackIndex];
74 cursor.previous = cursor.current;
75 cursor.current = cursor.next;
76 cursor.next.reset();
77
78 if (mEnforceSequentialAccess && trackIndex == mExtractorTrackIndex) {
79 while (advanceExtractor_l()) {
80 SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
81 if (cursor.current.isSet && cursor.current.index == mExtractorSampleIndex) {
82 if (mExtractorTrackIndex != trackIndex) {
83 mTrackSignals[mExtractorTrackIndex].notify_all();
84 }
85 break;
86 }
87 }
88 }
89 return;
90 }
91
advanceExtractor_l()92 bool MediaSampleReaderNDK::advanceExtractor_l() {
93 // Reset the "next" sample time whenever the extractor advances past a sample that is current,
94 // to ensure that "next" is appropriately updated when the extractor advances over the next
95 // sample of that track.
96 if (mTrackCursors[mExtractorTrackIndex].current.isSet &&
97 mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex) {
98 mTrackCursors[mExtractorTrackIndex].next.reset();
99 }
100
101 // Update the extractor's sample index even if this track reaches EOS, so that the other tracks
102 // are not given an incorrect extractor position.
103 mExtractorSampleIndex++;
104 if (!AMediaExtractor_advance(mExtractor)) {
105 LOG(DEBUG) << " EOS in advanceExtractor_l";
106 mEosReached = true;
107 for (auto it = mTrackSignals.begin(); it != mTrackSignals.end(); ++it) {
108 it->second.notify_all();
109 }
110 return false;
111 }
112
113 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
114
115 SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
116 if (mExtractorSampleIndex > cursor.previous.index) {
117 if (!cursor.current.isSet) {
118 cursor.current.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
119 } else if (!cursor.next.isSet && mExtractorSampleIndex > cursor.current.index) {
120 cursor.next.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
121 }
122 }
123
124 return true;
125 }
126
seekExtractorBackwards_l(int64_t targetTimeUs,int targetTrackIndex,uint64_t targetSampleIndex)127 media_status_t MediaSampleReaderNDK::seekExtractorBackwards_l(int64_t targetTimeUs,
128 int targetTrackIndex,
129 uint64_t targetSampleIndex) {
130 if (targetSampleIndex > mExtractorSampleIndex) {
131 LOG(ERROR) << "Error: Forward seek is not supported";
132 return AMEDIA_ERROR_UNSUPPORTED;
133 }
134
135 // AMediaExtractor supports reading negative timestamps but does not support seeking to them.
136 const int64_t seekToTimeUs = std::max(targetTimeUs, (int64_t)0);
137 media_status_t status =
138 AMediaExtractor_seekTo(mExtractor, seekToTimeUs, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
139 if (status != AMEDIA_OK) {
140 LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
141 return status;
142 }
143
144 mEosReached = false;
145 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
146 int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
147
148 while (sampleTimeUs != targetTimeUs || mExtractorTrackIndex != targetTrackIndex) {
149 if (!AMediaExtractor_advance(mExtractor)) {
150 return AMEDIA_ERROR_END_OF_STREAM;
151 }
152 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
153 sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
154 }
155 mExtractorSampleIndex = targetSampleIndex;
156 return AMEDIA_OK;
157 }
158
moveToSample_l(SamplePosition & pos,int trackIndex)159 media_status_t MediaSampleReaderNDK::moveToSample_l(SamplePosition& pos, int trackIndex) {
160 // Seek backwards if the extractor is ahead of the sample.
161 if (pos.isSet && mExtractorSampleIndex > pos.index) {
162 media_status_t status = seekExtractorBackwards_l(pos.timeStampUs, trackIndex, pos.index);
163 if (status != AMEDIA_OK) return status;
164 }
165
166 // Advance until extractor points to the sample.
167 while (!(pos.isSet && pos.index == mExtractorSampleIndex)) {
168 if (!advanceExtractor_l()) {
169 return AMEDIA_ERROR_END_OF_STREAM;
170 }
171 }
172
173 return AMEDIA_OK;
174 }
175
moveToTrack_l(int trackIndex)176 media_status_t MediaSampleReaderNDK::moveToTrack_l(int trackIndex) {
177 return moveToSample_l(mTrackCursors[trackIndex].current, trackIndex);
178 }
179
waitForTrack_l(int trackIndex,std::unique_lock<std::mutex> & lockHeld)180 media_status_t MediaSampleReaderNDK::waitForTrack_l(int trackIndex,
181 std::unique_lock<std::mutex>& lockHeld) {
182 while (trackIndex != mExtractorTrackIndex && !mEosReached && mEnforceSequentialAccess) {
183 mTrackSignals[trackIndex].wait(lockHeld);
184 }
185
186 if (mEosReached) {
187 return AMEDIA_ERROR_END_OF_STREAM;
188 }
189
190 if (!mEnforceSequentialAccess) {
191 return moveToTrack_l(trackIndex);
192 }
193
194 return AMEDIA_OK;
195 }
196
primeExtractorForTrack_l(int trackIndex,std::unique_lock<std::mutex> & lockHeld)197 media_status_t MediaSampleReaderNDK::primeExtractorForTrack_l(
198 int trackIndex, std::unique_lock<std::mutex>& lockHeld) {
199 if (mExtractorTrackIndex < 0) {
200 mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
201 if (mExtractorTrackIndex < 0) {
202 return AMEDIA_ERROR_END_OF_STREAM;
203 }
204 mTrackCursors[mExtractorTrackIndex].current.set(mExtractorSampleIndex,
205 AMediaExtractor_getSampleTime(mExtractor));
206 }
207
208 if (mEnforceSequentialAccess) {
209 return waitForTrack_l(trackIndex, lockHeld);
210 } else {
211 return moveToTrack_l(trackIndex);
212 }
213 }
214
selectTrack(int trackIndex)215 media_status_t MediaSampleReaderNDK::selectTrack(int trackIndex) {
216 std::scoped_lock lock(mExtractorMutex);
217
218 if (trackIndex < 0 || trackIndex >= mTrackCount) {
219 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
220 return AMEDIA_ERROR_INVALID_PARAMETER;
221 } else if (mTrackSignals.find(trackIndex) != mTrackSignals.end()) {
222 LOG(ERROR) << "TrackIndex " << trackIndex << " already selected";
223 return AMEDIA_ERROR_INVALID_PARAMETER;
224 } else if (mExtractorTrackIndex >= 0) {
225 LOG(ERROR) << "Tracks must be selected before sample reading begins.";
226 return AMEDIA_ERROR_UNSUPPORTED;
227 }
228
229 media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
230 if (status != AMEDIA_OK) {
231 LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
232 return status;
233 }
234
235 mTrackSignals.emplace(std::piecewise_construct, std::forward_as_tuple(trackIndex),
236 std::forward_as_tuple());
237 return AMEDIA_OK;
238 }
239
unselectTrack(int trackIndex)240 media_status_t MediaSampleReaderNDK::unselectTrack(int trackIndex) {
241 std::scoped_lock lock(mExtractorMutex);
242
243 if (trackIndex < 0 || trackIndex >= mTrackCount) {
244 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
245 return AMEDIA_ERROR_INVALID_PARAMETER;
246 } else if (mExtractorTrackIndex >= 0) {
247 LOG(ERROR) << "unselectTrack must be called before sample reading begins.";
248 return AMEDIA_ERROR_UNSUPPORTED;
249 }
250
251 auto it = mTrackSignals.find(trackIndex);
252 if (it == mTrackSignals.end()) {
253 LOG(ERROR) << "TrackIndex " << trackIndex << " is not selected";
254 return AMEDIA_ERROR_INVALID_PARAMETER;
255 }
256 mTrackSignals.erase(it);
257
258 media_status_t status = AMediaExtractor_unselectTrack(mExtractor, trackIndex);
259 if (status != AMEDIA_OK) {
260 LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
261 return status;
262 }
263
264 return AMEDIA_OK;
265 }
266
setEnforceSequentialAccess(bool enforce)267 media_status_t MediaSampleReaderNDK::setEnforceSequentialAccess(bool enforce) {
268 LOG(DEBUG) << "setEnforceSequentialAccess( " << enforce << " )";
269
270 std::scoped_lock lock(mExtractorMutex);
271
272 if (mEnforceSequentialAccess && !enforce) {
273 // If switching from enforcing to not enforcing sequential access there may be threads
274 // waiting that needs to be woken up.
275 for (auto it = mTrackSignals.begin(); it != mTrackSignals.end(); ++it) {
276 it->second.notify_all();
277 }
278 } else if (!mEnforceSequentialAccess && enforce && mExtractorTrackIndex >= 0) {
279 // If switching from not enforcing to enforcing sequential access the extractor needs to be
280 // positioned for the track farthest behind so that it won't get stuck waiting.
281 struct {
282 SamplePosition* pos = nullptr;
283 int trackIndex = -1;
284 } earliestSample;
285
286 for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
287 SamplePosition& lastKnownTrackPosition = mTrackCursors[trackIndex].current.isSet
288 ? mTrackCursors[trackIndex].current
289 : mTrackCursors[trackIndex].previous;
290
291 if (lastKnownTrackPosition.isSet) {
292 if (earliestSample.pos == nullptr ||
293 earliestSample.pos->index > lastKnownTrackPosition.index) {
294 earliestSample.pos = &lastKnownTrackPosition;
295 earliestSample.trackIndex = trackIndex;
296 }
297 }
298 }
299
300 if (earliestSample.pos == nullptr) {
301 LOG(ERROR) << "No known sample position found";
302 return AMEDIA_ERROR_UNKNOWN;
303 }
304
305 media_status_t status = moveToSample_l(*earliestSample.pos, earliestSample.trackIndex);
306 if (status != AMEDIA_OK) return status;
307
308 while (!(mTrackCursors[mExtractorTrackIndex].current.isSet &&
309 mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex)) {
310 if (!advanceExtractor_l()) {
311 return AMEDIA_ERROR_END_OF_STREAM;
312 }
313 }
314 }
315
316 mEnforceSequentialAccess = enforce;
317 return AMEDIA_OK;
318 }
319
getEstimatedBitrateForTrack(int trackIndex,int32_t * bitrate)320 media_status_t MediaSampleReaderNDK::getEstimatedBitrateForTrack(int trackIndex, int32_t* bitrate) {
321 std::scoped_lock lock(mExtractorMutex);
322 media_status_t status = AMEDIA_OK;
323
324 if (mTrackSignals.find(trackIndex) == mTrackSignals.end()) {
325 LOG(ERROR) << "Track is not selected.";
326 return AMEDIA_ERROR_INVALID_PARAMETER;
327 } else if (bitrate == nullptr) {
328 LOG(ERROR) << "bitrate pointer is NULL.";
329 return AMEDIA_ERROR_INVALID_PARAMETER;
330 } else if (mExtractorTrackIndex >= 0) {
331 LOG(ERROR) << "getEstimatedBitrateForTrack must be called before sample reading begins.";
332 return AMEDIA_ERROR_UNSUPPORTED;
333 }
334
335 // Sample the track.
336 static constexpr int64_t kSamplingDurationUs = 10 * 1000 * 1000; // 10 seconds
337 size_t lastSampleSize = 0;
338 size_t totalSampleSize = 0;
339 int64_t firstSampleTimeUs = 0;
340 int64_t lastSampleTimeUs = 0;
341
342 do {
343 if (AMediaExtractor_getSampleTrackIndex(mExtractor) == trackIndex) {
344 lastSampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
345 if (totalSampleSize == 0) {
346 firstSampleTimeUs = lastSampleTimeUs;
347 }
348
349 lastSampleSize = AMediaExtractor_getSampleSize(mExtractor);
350 totalSampleSize += lastSampleSize;
351 }
352 } while ((lastSampleTimeUs - firstSampleTimeUs) < kSamplingDurationUs &&
353 AMediaExtractor_advance(mExtractor));
354
355 // Reset the extractor to the beginning.
356 status = AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
357 if (status != AMEDIA_OK) {
358 LOG(ERROR) << "Unable to reset extractor: " << status;
359 return status;
360 }
361
362 int64_t durationUs = 0;
363 const int64_t sampledDurationUs = lastSampleTimeUs - firstSampleTimeUs;
364
365 if (sampledDurationUs < kSamplingDurationUs) {
366 // Track is shorter than the sampling duration so use the full track duration to get better
367 // accuracy (i.e. don't skip the last sample).
368 AMediaFormat* trackFormat = getTrackFormat(trackIndex);
369 if (!AMediaFormat_getInt64(trackFormat, AMEDIAFORMAT_KEY_DURATION, &durationUs)) {
370 durationUs = 0;
371 }
372 AMediaFormat_delete(trackFormat);
373 }
374
375 if (durationUs == 0) {
376 // The sampled duration does not account for the last sample's duration so its size should
377 // not be included either.
378 totalSampleSize -= lastSampleSize;
379 durationUs = sampledDurationUs;
380 }
381
382 if (totalSampleSize == 0 || durationUs <= 0) {
383 LOG(ERROR) << "Unable to estimate track bitrate";
384 return AMEDIA_ERROR_MALFORMED;
385 }
386
387 *bitrate = roundf((float)totalSampleSize * 8 * 1000000 / durationUs);
388 return AMEDIA_OK;
389 }
390
getSampleInfoForTrack(int trackIndex,MediaSampleInfo * info)391 media_status_t MediaSampleReaderNDK::getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) {
392 std::unique_lock<std::mutex> lock(mExtractorMutex);
393
394 if (mTrackSignals.find(trackIndex) == mTrackSignals.end()) {
395 LOG(ERROR) << "Track not selected.";
396 return AMEDIA_ERROR_INVALID_PARAMETER;
397 } else if (info == nullptr) {
398 LOG(ERROR) << "MediaSampleInfo pointer is NULL.";
399 return AMEDIA_ERROR_INVALID_PARAMETER;
400 }
401
402 media_status_t status = primeExtractorForTrack_l(trackIndex, lock);
403 if (status == AMEDIA_OK) {
404 info->presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
405 info->flags = AMediaExtractor_getSampleFlags(mExtractor);
406 info->size = AMediaExtractor_getSampleSize(mExtractor);
407 } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
408 info->presentationTimeUs = 0;
409 info->flags = SAMPLE_FLAG_END_OF_STREAM;
410 info->size = 0;
411 LOG(DEBUG) << " getSampleInfoForTrack #" << trackIndex << ": End Of Stream";
412 } else {
413 LOG(ERROR) << " getSampleInfoForTrack #" << trackIndex << ": Error " << status;
414 }
415
416 return status;
417 }
418
readSampleDataForTrack(int trackIndex,uint8_t * buffer,size_t bufferSize)419 media_status_t MediaSampleReaderNDK::readSampleDataForTrack(int trackIndex, uint8_t* buffer,
420 size_t bufferSize) {
421 std::unique_lock<std::mutex> lock(mExtractorMutex);
422
423 if (mTrackSignals.find(trackIndex) == mTrackSignals.end()) {
424 LOG(ERROR) << "Track not selected.";
425 return AMEDIA_ERROR_INVALID_PARAMETER;
426 } else if (buffer == nullptr) {
427 LOG(ERROR) << "buffer pointer is NULL";
428 return AMEDIA_ERROR_INVALID_PARAMETER;
429 }
430
431 media_status_t status = primeExtractorForTrack_l(trackIndex, lock);
432 if (status != AMEDIA_OK) {
433 return status;
434 }
435
436 ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
437 if (bufferSize < sampleSize) {
438 LOG(ERROR) << "Buffer is too small for sample, " << bufferSize << " vs " << sampleSize;
439 return AMEDIA_ERROR_INVALID_PARAMETER;
440 }
441
442 ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer, bufferSize);
443 if (bytesRead < sampleSize) {
444 LOG(ERROR) << "Unable to read full sample, " << bytesRead << " vs " << sampleSize;
445 return AMEDIA_ERROR_INVALID_PARAMETER;
446 }
447
448 advanceTrack_l(trackIndex);
449
450 return AMEDIA_OK;
451 }
452
advanceTrack(int trackIndex)453 void MediaSampleReaderNDK::advanceTrack(int trackIndex) {
454 std::scoped_lock lock(mExtractorMutex);
455
456 if (mTrackSignals.find(trackIndex) != mTrackSignals.end()) {
457 advanceTrack_l(trackIndex);
458 } else {
459 LOG(ERROR) << "Trying to advance a track that is not selected (#" << trackIndex << ")";
460 }
461 }
462
getFileFormat()463 AMediaFormat* MediaSampleReaderNDK::getFileFormat() {
464 return AMediaExtractor_getFileFormat(mExtractor);
465 }
466
getTrackCount() const467 size_t MediaSampleReaderNDK::getTrackCount() const {
468 return mTrackCount;
469 }
470
getTrackFormat(int trackIndex)471 AMediaFormat* MediaSampleReaderNDK::getTrackFormat(int trackIndex) {
472 if (trackIndex < 0 || trackIndex >= mTrackCount) {
473 LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
474 return AMediaFormat_new();
475 }
476
477 return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
478 }
479
480 } // namespace android
481