• 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 // #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