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 "MediaAppender"
19
20 #include <media/stagefright/MediaAppender.h>
21 #include <media/stagefright/MediaCodec.h>
22 #include <media/stagefright/foundation/ABuffer.h>
23 #include <utils/Log.h>
24 // TODO : check if this works for NDK apps without JVM
25 // #include <media/ndk/NdkJavaVMHelperPriv.h>
26
27 namespace android {
28
29 struct MediaAppender::sampleDataInfo {
30 size_t size;
31 int64_t time;
32 size_t exTrackIndex;
33 sp<MetaData> meta;
34 };
35
create(int fd,AppendMode mode)36 sp<MediaAppender> MediaAppender::create(int fd, AppendMode mode) {
37 if (fd < 0) {
38 ALOGE("invalid file descriptor");
39 return nullptr;
40 }
41 if (!(mode >= APPEND_MODE_FIRST && mode <= APPEND_MODE_LAST)) {
42 ALOGE("invalid mode %d", mode);
43 return nullptr;
44 }
45 sp<MediaAppender> ma = new (std::nothrow) MediaAppender(fd, mode);
46 if (ma->init() != OK) {
47 return nullptr;
48 }
49 return ma;
50 }
51
52 // TODO: inject mediamuxer and mediaextractor objects.
53 // TODO: @format is not required as an input if we can sniff the file and find the format of
54 // the existing content.
55 // TODO: Code it to the interface(MediaAppender), and have a separate MediaAppender NDK
MediaAppender(int fd,AppendMode mode)56 MediaAppender::MediaAppender(int fd, AppendMode mode)
57 : mFd(fd),
58 mMode(mode),
59 // TODO : check if this works for NDK apps without JVM
60 // mExtractor(new NuMediaExtractor(NdkJavaVMHelper::getJNIEnv() != nullptr
61 // ? NuMediaExtractor::EntryPoint::NDK_WITH_JVM
62 // : NuMediaExtractor::EntryPoint::NDK_NO_JVM)),
63 mExtractor(new (std::nothrow) NuMediaExtractor(NuMediaExtractor::EntryPoint::NDK_WITH_JVM)),
64 mTrackCount(0),
65 mState(UNINITIALIZED) {
66 ALOGV("MediaAppender::MediaAppender mode:%d", mode);
67 }
68
init()69 status_t MediaAppender::init() {
70 std::scoped_lock lock(mMutex);
71 ALOGV("MediaAppender::init");
72 status_t status = mExtractor->setDataSource(mFd, 0, lseek(mFd, 0, SEEK_END));
73 if (status != OK) {
74 ALOGE("extractor_setDataSource failed, status :%d", status);
75 return status;
76 }
77
78 if (strcmp("MPEG4Extractor", mExtractor->getName()) == 0) {
79 mFormat = MediaMuxer::OUTPUT_FORMAT_MPEG_4;
80 } else {
81 ALOGE("Unsupported format, extractor name:%s", mExtractor->getName());
82 return ERROR_UNSUPPORTED;
83 }
84
85 mTrackCount = mExtractor->countTracks();
86 ALOGV("mTrackCount:%zu", mTrackCount);
87 if (mTrackCount == 0) {
88 ALOGE("no tracks are present");
89 return ERROR_MALFORMED;
90 }
91 size_t exTrackIndex = 0;
92 ssize_t audioTrackIndex = -1, videoTrackIndex = -1;
93 bool audioSyncSampleTimeSet = false;
94
95 while (exTrackIndex < mTrackCount) {
96 sp<AMessage> fmt;
97 status = mExtractor->getTrackFormat(exTrackIndex, &fmt, 0);
98 if (status != OK) {
99 ALOGE("getTrackFormat failed for trackIndex:%zu, status:%d", exTrackIndex, status);
100 return status;
101 }
102 AString mime;
103 if (fmt->findString("mime", &mime)) {
104 if (!strncasecmp(mime.c_str(), "video/", 6)) {
105 ALOGV("VideoTrack");
106 if (videoTrackIndex != -1) {
107 ALOGE("Not more than one video track is supported");
108 return ERROR_UNSUPPORTED;
109 }
110 videoTrackIndex = exTrackIndex;
111 } else if (!strncasecmp(mime.c_str(), "audio/", 6)) {
112 ALOGV("AudioTrack");
113 if (audioTrackIndex != -1) {
114 ALOGE("Not more than one audio track is supported");
115 }
116 audioTrackIndex = exTrackIndex;
117 } else {
118 ALOGV("Neither Video nor Audio track");
119 }
120 }
121 mFmtIndexMap.emplace(exTrackIndex, fmt);
122 mSampleCountVect.emplace_back(0);
123 mMaxTimestampVect.emplace_back(0);
124 mLastSyncSampleTimeVect.emplace_back(0);
125 status = mExtractor->selectTrack(exTrackIndex);
126 if (status != OK) {
127 ALOGE("selectTrack failed for trackIndex:%zu, status:%d", exTrackIndex, status);
128 return status;
129 }
130 ++exTrackIndex;
131 }
132
133 ALOGV("AudioTrackIndex:%zu, VideoTrackIndex:%zu", audioTrackIndex, videoTrackIndex);
134
135 do {
136 sampleDataInfo tmpSDI;
137 // TODO: read info into members of the struct sampleDataInfo directly
138 size_t sampleSize;
139 status = mExtractor->getSampleSize(&sampleSize);
140 if (status != OK) {
141 ALOGE("getSampleSize failed, status:%d", status);
142 return status;
143 }
144 mSampleSizeVect.emplace_back(sampleSize);
145 tmpSDI.size = sampleSize;
146 int64_t sampleTime = 0;
147 status = mExtractor->getSampleTime(&sampleTime);
148 if (status != OK) {
149 ALOGE("getSampleTime failed, status:%d", status);
150 return status;
151 }
152 mSampleTimeVect.emplace_back(sampleTime);
153 tmpSDI.time = sampleTime;
154 status = mExtractor->getSampleTrackIndex(&exTrackIndex);
155 if (status != OK) {
156 ALOGE("getSampleTrackIndex failed, status:%d", status);
157 return status;
158 }
159 mSampleIndexVect.emplace_back(exTrackIndex);
160 tmpSDI.exTrackIndex = exTrackIndex;
161 ++mSampleCountVect[exTrackIndex];
162 mMaxTimestampVect[exTrackIndex] = std::max(mMaxTimestampVect[exTrackIndex], sampleTime);
163 sp<MetaData> sampleMeta;
164 status = mExtractor->getSampleMeta(&sampleMeta);
165 if (status != OK) {
166 ALOGE("getSampleMeta failed, status:%d", status);
167 return status;
168 }
169 mSampleMetaVect.emplace_back(sampleMeta);
170 int32_t val = 0;
171 if (sampleMeta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
172 mLastSyncSampleTimeVect[exTrackIndex] = sampleTime;
173 }
174 tmpSDI.meta = sampleMeta;
175 mSDI.emplace_back(tmpSDI);
176 } while (mExtractor->advance() == OK);
177
178 mExtractor.clear();
179
180 std::sort(mSDI.begin(), mSDI.end(), [](sampleDataInfo& a, sampleDataInfo& b) {
181 int64_t aOffset, bOffset;
182 a.meta->findInt64(kKeySampleFileOffset, &aOffset);
183 b.meta->findInt64(kKeySampleFileOffset, &bOffset);
184 return aOffset < bOffset;
185 });
186 for (int64_t syncSampleTime : mLastSyncSampleTimeVect) {
187 ALOGV("before ignoring frames, mLastSyncSampleTimeVect:%lld", (long long)syncSampleTime);
188 }
189 ALOGV("mMode:%u", mMode);
190 if (mMode == APPEND_MODE_IGNORE_LAST_VIDEO_GOP && videoTrackIndex != -1 ) {
191 ALOGV("Video track is present");
192 bool lastVideoIframe = false;
193 size_t lastVideoIframeOffset = 0;
194 int64_t lastVideoSampleTime = -1;
195 for (auto rItr = mSDI.rbegin(); rItr != mSDI.rend(); ++rItr) {
196 if (rItr->exTrackIndex != videoTrackIndex) {
197 continue;
198 }
199 if (lastVideoSampleTime == -1) {
200 lastVideoSampleTime = rItr->time;
201 }
202 int64_t offset = 0;
203 if (!rItr->meta->findInt64(kKeySampleFileOffset, &offset) || offset == 0) {
204 ALOGE("Missing offset");
205 return ERROR_MALFORMED;
206 }
207 ALOGV("offset:%lld", (long long)offset);
208 int32_t val = 0;
209 if (rItr->meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
210 ALOGV("sampleTime:%lld", (long long)rItr->time);
211 ALOGV("lastVideoSampleTime:%lld", (long long)lastVideoSampleTime);
212 if (lastVideoIframe == false && (lastVideoSampleTime - rItr->time) >
213 1000000/* Track interleaving duration in MPEG4Writer*/) {
214 ALOGV("lastVideoIframe got chosen");
215 lastVideoIframe = true;
216 mLastSyncSampleTimeVect[videoTrackIndex] = rItr->time;
217 lastVideoIframeOffset = offset;
218 ALOGV("lastVideoIframeOffset:%lld", (long long)offset);
219 break;
220 }
221 }
222 }
223 if (lastVideoIframe == false) {
224 ALOGV("Need to rewrite all samples");
225 mLastSyncSampleTimeVect[videoTrackIndex] = 0;
226 lastVideoIframeOffset = 0;
227 }
228 unsigned int framesIgnoredCount = 0;
229 for (auto itr = mSDI.begin(); itr != mSDI.end();) {
230 int64_t offset = 0;
231 ALOGV("trackIndex:%zu, %" PRId64 "", itr->exTrackIndex, itr->time);
232 if (itr->meta->findInt64(kKeySampleFileOffset, &offset) &&
233 offset >= lastVideoIframeOffset) {
234 ALOGV("offset:%lld", (long long)offset);
235 if (!audioSyncSampleTimeSet && audioTrackIndex != -1 &&
236 audioTrackIndex == itr->exTrackIndex) {
237 mLastSyncSampleTimeVect[audioTrackIndex] = itr->time;
238 audioSyncSampleTimeSet = true;
239 }
240 itr = mSDI.erase(itr);
241 ++framesIgnoredCount;
242 } else {
243 ++itr;
244 }
245 }
246 ALOGV("framesIgnoredCount:%u", framesIgnoredCount);
247 }
248
249 if (mMode == APPEND_MODE_IGNORE_LAST_VIDEO_GOP && videoTrackIndex == -1 &&
250 audioTrackIndex != -1) {
251 ALOGV("Only AudioTrack is present");
252 for (auto rItr = mSDI.rbegin(); rItr != mSDI.rend(); ++rItr) {
253 int32_t val = 0;
254 if (rItr->meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
255 mLastSyncSampleTimeVect[audioTrackIndex] = rItr->time;
256 break;
257 }
258 }
259 unsigned int framesIgnoredCount = 0;
260 for (auto itr = mSDI.begin(); itr != mSDI.end();) {
261 if (itr->time >= mLastSyncSampleTimeVect[audioTrackIndex]) {
262 itr = mSDI.erase(itr);
263 ++framesIgnoredCount;
264 } else {
265 ++itr;
266 }
267 }
268 ALOGV("framesIgnoredCount :%u", framesIgnoredCount);
269 }
270
271 for (size_t i = 0; i < mLastSyncSampleTimeVect.size(); ++i) {
272 ALOGV("mLastSyncSampleTimeVect[%zu]:%lld", i, (long long)mLastSyncSampleTimeVect[i]);
273 mFmtIndexMap[i]->setInt64(
274 "sample-time-before-append" /*AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND*/,
275 mLastSyncSampleTimeVect[i]);
276 }
277 for (size_t i = 0; i < mMaxTimestampVect.size(); ++i) {
278 ALOGV("mMaxTimestamp[%zu]:%lld", i, (long long)mMaxTimestampVect[i]);
279 }
280 for (size_t i = 0; i < mSampleCountVect.size(); ++i) {
281 ALOGV("SampleCountVect[%zu]:%zu", i, mSampleCountVect[i]);
282 }
283 mState = INITIALIZED;
284 return OK;
285 }
286
~MediaAppender()287 MediaAppender::~MediaAppender() {
288 ALOGV("MediaAppender::~MediaAppender");
289 mMuxer.clear();
290 mExtractor.clear();
291 }
292
start()293 status_t MediaAppender::start() {
294 std::scoped_lock lock(mMutex);
295 ALOGV("MediaAppender::start");
296 if (mState != INITIALIZED) {
297 ALOGE("MediaAppender::start() is called in invalid state %d", mState);
298 return INVALID_OPERATION;
299 }
300 mMuxer = new (std::nothrow) MediaMuxer(mFd, mFormat);
301 for (const auto& n : mFmtIndexMap) {
302 ssize_t muxIndex = mMuxer->addTrack(n.second);
303 if (muxIndex < 0) {
304 ALOGE("addTrack failed");
305 return UNKNOWN_ERROR;
306 }
307 mTrackIndexMap.emplace(n.first, muxIndex);
308 }
309 ALOGV("trackIndexmap size:%zu", mTrackIndexMap.size());
310
311 status_t status = mMuxer->start();
312 if (status != OK) {
313 ALOGE("muxer start failed:%d", status);
314 return status;
315 }
316
317 ALOGV("Sorting samples based on their offsets");
318 for (int i = 0; i < mSDI.size(); ++i) {
319 ALOGV("i:%d", i + 1);
320 /* TODO : Allocate a single allocation of the max size, and reuse it across ABuffers if
321 * using new ABuffer(void *, size_t).
322 */
323 sp<ABuffer> data = new (std::nothrow) ABuffer(mSDI[i].size);
324 if (data == nullptr) {
325 ALOGE("memory allocation failed");
326 return NO_MEMORY;
327 }
328 data->setRange(0, mSDI[i].size);
329 int32_t val = 0;
330 int sampleFlags = 0;
331 if (mSDI[i].meta->findInt32(kKeyIsSyncFrame, &val) && val != 0) {
332 sampleFlags |= MediaCodec::BUFFER_FLAG_SYNCFRAME;
333 }
334
335 int64_t val64;
336 if (mSDI[i].meta->findInt64(kKeySampleFileOffset, &val64)) {
337 ALOGV("SampleFileOffset Found :%zu:%lld:%lld", mSDI[i].exTrackIndex,
338 (long long)mSampleCountVect[mSDI[i].exTrackIndex], (long long)val64);
339 sp<AMessage> bufMeta = data->meta();
340 bufMeta->setInt64("sample-file-offset" /*AMEDIAFORMAT_KEY_SAMPLE_TIME_BEFORE_APPEND*/,
341 val64);
342 }
343 if (mSDI[i].meta->findInt64(kKeyLastSampleIndexInChunk, &val64)) {
344 ALOGV("kKeyLastSampleIndexInChunk Found %lld:%lld",
345 (long long)mSampleCountVect[mSDI[i].exTrackIndex], (long long)val64);
346 sp<AMessage> bufMeta = data->meta();
347 bufMeta->setInt64(
348 "last-sample-index-in-chunk" /*AMEDIAFORMAT_KEY_LAST_SAMPLE_INDEX_IN_CHUNK*/,
349 val64);
350 }
351 status = mMuxer->writeSampleData(data, mTrackIndexMap[mSDI[i].exTrackIndex], mSDI[i].time,
352 sampleFlags);
353 if (status != OK) {
354 ALOGE("muxer writeSampleData failed:%d", status);
355 return status;
356 }
357 }
358 mState = STARTED;
359 return OK;
360 }
361
stop()362 status_t MediaAppender::stop() {
363 std::scoped_lock lock(mMutex);
364 ALOGV("MediaAppender::stop");
365 if (mState == STARTED) {
366 status_t status = mMuxer->stop();
367 if (status != OK) {
368 mState = ERROR;
369 } else {
370 mState = STOPPED;
371 }
372 return status;
373 } else {
374 ALOGE("stop() is called in invalid state %d", mState);
375 return INVALID_OPERATION;
376 }
377 }
378
getTrackCount()379 ssize_t MediaAppender::getTrackCount() {
380 std::scoped_lock lock(mMutex);
381 ALOGV("MediaAppender::getTrackCount");
382 if (mState != INITIALIZED && mState != STARTED) {
383 ALOGE("getTrackCount() is called in invalid state %d", mState);
384 return -1;
385 }
386 return mTrackCount;
387 }
388
getTrackFormat(size_t idx)389 sp<AMessage> MediaAppender::getTrackFormat(size_t idx) {
390 std::scoped_lock lock(mMutex);
391 ALOGV("MediaAppender::getTrackFormat");
392 if (mState != INITIALIZED && mState != STARTED) {
393 ALOGE("getTrackFormat() is called in invalid state %d", mState);
394 return nullptr;
395 }
396 if (idx < 0 || idx >= mTrackCount) {
397 ALOGE("getTrackFormat() idx is out of range");
398 return nullptr;
399 }
400 return mFmtIndexMap[idx];
401 }
402
writeSampleData(const sp<ABuffer> & buffer,size_t trackIndex,int64_t timeUs,uint32_t flags)403 status_t MediaAppender::writeSampleData(const sp<ABuffer>& buffer, size_t trackIndex,
404 int64_t timeUs, uint32_t flags) {
405 std::scoped_lock lock(mMutex);
406 ALOGV("writeSampleData:trackIndex:%zu, time:%" PRId64 "", trackIndex, timeUs);
407 return mMuxer->writeSampleData(buffer, trackIndex, timeUs, flags);
408 }
409
setOrientationHint(int degrees)410 status_t MediaAppender::setOrientationHint([[maybe_unused]] int degrees) {
411 ALOGE("setOrientationHint not supported. Has to be called prior to start on initial muxer");
412 return ERROR_UNSUPPORTED;
413 };
414
setLocation(int latit,int longit)415 status_t MediaAppender::setLocation([[maybe_unused]] int latit, [[maybe_unused]] int longit) {
416 ALOGE("setLocation not supported. Has to be called prior to start on initial muxer");
417 return ERROR_UNSUPPORTED;
418 }
419
addTrack(const sp<AMessage> & format)420 ssize_t MediaAppender::addTrack([[maybe_unused]] const sp<AMessage> &format) {
421 ALOGE("addTrack not supported");
422 return ERROR_UNSUPPORTED;
423 }
424
425 } // namespace android
426