• 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 "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