1 /*
2  * Copyright (C) 2014 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 "WebmWriter"
19 
20 #include "EbmlUtil.h"
21 #include "WebmWriter.h"
22 
23 #include <media/stagefright/MetaData.h>
24 #include <media/stagefright/MediaDefs.h>
25 #include <media/stagefright/foundation/ADebug.h>
26 
27 #include <utils/Errors.h>
28 
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/stat.h>
32 #include <inttypes.h>
33 
34 using namespace webm;
35 
36 namespace {
XiphLaceCodeLen(size_t size)37 size_t XiphLaceCodeLen(size_t size) {
38     return size / 0xff + 1;
39 }
40 
XiphLaceEnc(uint8_t * buf,size_t size)41 size_t XiphLaceEnc(uint8_t *buf, size_t size) {
42     size_t i;
43     for (i = 0; size >= 0xff; ++i, size -= 0xff) {
44         buf[i] = 0xff;
45     }
46     buf[i++] = size;
47     return i;
48 }
49 }
50 
51 namespace android {
52 
53 static const int64_t kMinStreamableFileSizeInBytes = 5 * 1024 * 1024;
54 
WebmWriter(int fd)55 WebmWriter::WebmWriter(int fd)
56     : mFd(dup(fd)),
57       mInitCheck(mFd < 0 ? NO_INIT : OK),
58       mTimeCodeScale(1000000),
59       mStartTimestampUs(0),
60       mStartTimeOffsetMs(0),
61       mSegmentOffset(0),
62       mSegmentDataStart(0),
63       mInfoOffset(0),
64       mInfoSize(0),
65       mTracksOffset(0),
66       mCuesOffset(0),
67       mPaused(false),
68       mStarted(false),
69       mIsFileSizeLimitExplicitlyRequested(false),
70       mIsRealTimeRecording(false),
71       mStreamableFile(true),
72       mEstimatedCuesSize(0) {
73     mStreams[kAudioIndex] = WebmStream(kAudioType, "Audio", &WebmWriter::audioTrack);
74     mStreams[kVideoIndex] = WebmStream(kVideoType, "Video", &WebmWriter::videoTrack);
75     mSinkThread = new WebmFrameSinkThread(
76             mFd,
77             mSegmentDataStart,
78             mStreams[kVideoIndex].mSink,
79             mStreams[kAudioIndex].mSink,
80             mCuePoints);
81 }
82 
WebmWriter(const char * filename)83 WebmWriter::WebmWriter(const char *filename)
84     : mInitCheck(NO_INIT),
85       mTimeCodeScale(1000000),
86       mStartTimestampUs(0),
87       mStartTimeOffsetMs(0),
88       mSegmentOffset(0),
89       mSegmentDataStart(0),
90       mInfoOffset(0),
91       mInfoSize(0),
92       mTracksOffset(0),
93       mCuesOffset(0),
94       mPaused(false),
95       mStarted(false),
96       mIsFileSizeLimitExplicitlyRequested(false),
97       mIsRealTimeRecording(false),
98       mStreamableFile(true),
99       mEstimatedCuesSize(0) {
100     mFd = open(filename, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
101     if (mFd >= 0) {
102         ALOGV("fd %d; flags: %o", mFd, fcntl(mFd, F_GETFL, 0));
103         mInitCheck = OK;
104     }
105     mStreams[kAudioIndex] = WebmStream(kAudioType, "Audio", &WebmWriter::audioTrack);
106     mStreams[kVideoIndex] = WebmStream(kVideoType, "Video", &WebmWriter::videoTrack);
107     mSinkThread = new WebmFrameSinkThread(
108             mFd,
109             mSegmentDataStart,
110             mStreams[kVideoIndex].mSink,
111             mStreams[kAudioIndex].mSink,
112             mCuePoints);
113 }
114 
115 // static
videoTrack(const sp<MetaData> & md)116 sp<WebmElement> WebmWriter::videoTrack(const sp<MetaData>& md) {
117     int32_t width, height;
118     CHECK(md->findInt32(kKeyWidth, &width));
119     CHECK(md->findInt32(kKeyHeight, &height));
120     return WebmElement::VideoTrackEntry(width, height);
121 }
122 
123 // static
audioTrack(const sp<MetaData> & md)124 sp<WebmElement> WebmWriter::audioTrack(const sp<MetaData>& md) {
125     int32_t nChannels, samplerate;
126     uint32_t type;
127     const void *headerData1;
128     const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0,
129             'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 };
130     const void *headerData3;
131     size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3;
132 
133     CHECK(md->findInt32(kKeyChannelCount, &nChannels));
134     CHECK(md->findInt32(kKeySampleRate, &samplerate));
135     CHECK(md->findData(kKeyVorbisInfo, &type, &headerData1, &headerSize1));
136     CHECK(md->findData(kKeyVorbisBooks, &type, &headerData3, &headerSize3));
137 
138     size_t codecPrivateSize = 1;
139     codecPrivateSize += XiphLaceCodeLen(headerSize1);
140     codecPrivateSize += XiphLaceCodeLen(headerSize2);
141     codecPrivateSize += headerSize1 + headerSize2 + headerSize3;
142 
143     off_t off = 0;
144     sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
145     uint8_t *codecPrivateData = codecPrivateBuf->data();
146     codecPrivateData[off++] = 2;
147 
148     off += XiphLaceEnc(codecPrivateData + off, headerSize1);
149     off += XiphLaceEnc(codecPrivateData + off, headerSize2);
150 
151     memcpy(codecPrivateData + off, headerData1, headerSize1);
152     off += headerSize1;
153     memcpy(codecPrivateData + off, headerData2, headerSize2);
154     off += headerSize2;
155     memcpy(codecPrivateData + off, headerData3, headerSize3);
156 
157     sp<WebmElement> entry = WebmElement::AudioTrackEntry(
158             nChannels,
159             samplerate,
160             codecPrivateBuf);
161     return entry;
162 }
163 
numTracks()164 size_t WebmWriter::numTracks() {
165     Mutex::Autolock autolock(mLock);
166 
167     size_t numTracks = 0;
168     for (size_t i = 0; i < kMaxStreams; ++i) {
169         if (mStreams[i].mTrackEntry != NULL) {
170             numTracks++;
171         }
172     }
173 
174     return numTracks;
175 }
176 
estimateCuesSize(int32_t bitRate)177 uint64_t WebmWriter::estimateCuesSize(int32_t bitRate) {
178     // This implementation is based on estimateMoovBoxSize in MPEG4Writer.
179     //
180     // Statistical analysis shows that metadata usually accounts
181     // for a small portion of the total file size, usually < 0.6%.
182 
183     // The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2,
184     // where 1MB is the common file size limit for MMS application.
185     // The default MAX _MOOV_BOX_SIZE value is based on about 3
186     // minute video recording with a bit rate about 3 Mbps, because
187     // statistics also show that most of the video captured are going
188     // to be less than 3 minutes.
189 
190     // If the estimation is wrong, we will pay the price of wasting
191     // some reserved space. This should not happen so often statistically.
192     static const int32_t factor = 2;
193     static const int64_t MIN_CUES_SIZE = 3 * 1024;  // 3 KB
194     static const int64_t MAX_CUES_SIZE = (180 * 3000000 * 6LL / 8000);
195     int64_t size = MIN_CUES_SIZE;
196 
197     // Max file size limit is set
198     if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
199         size = mMaxFileSizeLimitBytes * 6 / 1000;
200     }
201 
202     // Max file duration limit is set
203     if (mMaxFileDurationLimitUs != 0) {
204         if (bitRate > 0) {
205             int64_t size2 = ((mMaxFileDurationLimitUs * bitRate * 6) / 1000 / 8000000);
206             if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
207                 // When both file size and duration limits are set,
208                 // we use the smaller limit of the two.
209                 if (size > size2) {
210                     size = size2;
211                 }
212             } else {
213                 // Only max file duration limit is set
214                 size = size2;
215             }
216         }
217     }
218 
219     if (size < MIN_CUES_SIZE) {
220         size = MIN_CUES_SIZE;
221     }
222 
223     // Any long duration recording will be probably end up with
224     // non-streamable webm file.
225     if (size > MAX_CUES_SIZE) {
226         size = MAX_CUES_SIZE;
227     }
228 
229     ALOGV("limits: %" PRId64 "/%" PRId64 " bytes/us,"
230             " bit rate: %d bps and the estimated cues size %" PRId64 " bytes",
231             mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
232     return factor * size;
233 }
234 
initStream(size_t idx)235 void WebmWriter::initStream(size_t idx) {
236     if (mStreams[idx].mThread != NULL) {
237         return;
238     }
239     if (mStreams[idx].mSource == NULL) {
240         ALOGV("adding dummy source ... ");
241         mStreams[idx].mThread = new WebmFrameEmptySourceThread(
242                 mStreams[idx].mType, mStreams[idx].mSink);
243     } else {
244         ALOGV("adding source %p", mStreams[idx].mSource.get());
245         mStreams[idx].mThread = new WebmFrameMediaSourceThread(
246                 mStreams[idx].mSource,
247                 mStreams[idx].mType,
248                 mStreams[idx].mSink,
249                 mTimeCodeScale,
250                 mStartTimestampUs,
251                 mStartTimeOffsetMs,
252                 numTracks(),
253                 mIsRealTimeRecording);
254     }
255 }
256 
release()257 void WebmWriter::release() {
258     close(mFd);
259     mFd = -1;
260     mInitCheck = NO_INIT;
261     mStarted = false;
262 }
263 
reset()264 status_t WebmWriter::reset() {
265     if (mInitCheck != OK) {
266         return OK;
267     } else {
268         if (!mStarted) {
269             release();
270             return OK;
271         }
272     }
273 
274     status_t err = OK;
275     int64_t maxDurationUs = 0;
276     int64_t minDurationUs = 0x7fffffffffffffffLL;
277     for (int i = 0; i < kMaxStreams; ++i) {
278         if (mStreams[i].mThread == NULL) {
279             continue;
280         }
281 
282         status_t status = mStreams[i].mThread->stop();
283         if (err == OK && status != OK) {
284             err = status;
285         }
286 
287         int64_t durationUs = mStreams[i].mThread->getDurationUs();
288         if (durationUs > maxDurationUs) {
289             maxDurationUs = durationUs;
290         }
291         if (durationUs < minDurationUs) {
292             minDurationUs = durationUs;
293         }
294     }
295 
296     if (numTracks() > 1) {
297         ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us", minDurationUs, maxDurationUs);
298     }
299 
300     mSinkThread->stop();
301 
302     // Do not write out movie header on error.
303     if (err != OK) {
304         release();
305         return err;
306     }
307 
308     sp<WebmElement> cues = new WebmMaster(kMkvCues, mCuePoints);
309     uint64_t cuesSize = cues->totalSize();
310     // TRICKY Even when the cues do fit in the space we reserved, if they do not fit
311     // perfectly, we still need to check if there is enough "extra space" to write an
312     // EBML void element.
313     if (cuesSize != mEstimatedCuesSize && cuesSize > mEstimatedCuesSize - kMinEbmlVoidSize) {
314         mCuesOffset = ::lseek(mFd, 0, SEEK_CUR);
315         cues->write(mFd, cuesSize);
316     } else {
317         uint64_t spaceSize;
318         ::lseek(mFd, mCuesOffset, SEEK_SET);
319         cues->write(mFd, cuesSize);
320         sp<WebmElement> space = new EbmlVoid(mEstimatedCuesSize - cuesSize);
321         space->write(mFd, spaceSize);
322     }
323 
324     mCuePoints.clear();
325     mStreams[kVideoIndex].mSink.clear();
326     mStreams[kAudioIndex].mSink.clear();
327 
328     uint8_t bary[sizeof(uint64_t)];
329     uint64_t totalSize = ::lseek(mFd, 0, SEEK_END);
330     uint64_t segmentSize = totalSize - mSegmentDataStart;
331     ::lseek(mFd, mSegmentOffset + sizeOf(kMkvSegment), SEEK_SET);
332     uint64_t segmentSizeCoded = encodeUnsigned(segmentSize, sizeOf(kMkvUnknownLength));
333     serializeCodedUnsigned(segmentSizeCoded, bary);
334     ::write(mFd, bary, sizeOf(kMkvUnknownLength));
335 
336     uint64_t size;
337     uint64_t durationOffset = mInfoOffset + sizeOf(kMkvInfo) + sizeOf(mInfoSize)
338         + sizeOf(kMkvSegmentDuration) + sizeOf(sizeof(double));
339     sp<WebmElement> duration = new WebmFloat(
340             kMkvSegmentDuration,
341             (double) (maxDurationUs * 1000 / mTimeCodeScale));
342     duration->serializePayload(bary);
343     ::lseek(mFd, durationOffset, SEEK_SET);
344     ::write(mFd, bary, sizeof(double));
345 
346     List<sp<WebmElement> > seekEntries;
347     seekEntries.push_back(WebmElement::SeekEntry(kMkvInfo, mInfoOffset - mSegmentDataStart));
348     seekEntries.push_back(WebmElement::SeekEntry(kMkvTracks, mTracksOffset - mSegmentDataStart));
349     seekEntries.push_back(WebmElement::SeekEntry(kMkvCues, mCuesOffset - mSegmentDataStart));
350     sp<WebmElement> seekHead = new WebmMaster(kMkvSeekHead, seekEntries);
351 
352     uint64_t metaSeekSize;
353     ::lseek(mFd, mSegmentDataStart, SEEK_SET);
354     seekHead->write(mFd, metaSeekSize);
355 
356     uint64_t spaceSize;
357     sp<WebmElement> space = new EbmlVoid(kMaxMetaSeekSize - metaSeekSize);
358     space->write(mFd, spaceSize);
359 
360     release();
361     return err;
362 }
363 
addSource(const sp<MediaSource> & source)364 status_t WebmWriter::addSource(const sp<MediaSource> &source) {
365     Mutex::Autolock l(mLock);
366     if (mStarted) {
367         ALOGE("Attempt to add source AFTER recording is started");
368         return UNKNOWN_ERROR;
369     }
370 
371     // At most 2 tracks can be supported.
372     if (mStreams[kVideoIndex].mTrackEntry != NULL
373             && mStreams[kAudioIndex].mTrackEntry != NULL) {
374         ALOGE("Too many tracks (2) to add");
375         return ERROR_UNSUPPORTED;
376     }
377 
378     CHECK(source != NULL);
379 
380     // A track of type other than video or audio is not supported.
381     const char *mime;
382     source->getFormat()->findCString(kKeyMIMEType, &mime);
383     const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8;
384     const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS;
385 
386     size_t streamIndex;
387     if (!strncasecmp(mime, vp8, strlen(vp8))) {
388         streamIndex = kVideoIndex;
389     } else if (!strncasecmp(mime, vorbis, strlen(vorbis))) {
390         streamIndex = kAudioIndex;
391     } else {
392         ALOGE("Track (%s) other than %s or %s is not supported", mime, vp8, vorbis);
393         return ERROR_UNSUPPORTED;
394     }
395 
396     // No more than one video or one audio track is supported.
397     if (mStreams[streamIndex].mTrackEntry != NULL) {
398         ALOGE("%s track already exists", mStreams[streamIndex].mName);
399         return ERROR_UNSUPPORTED;
400     }
401 
402     // This is the first track of either audio or video.
403     // Go ahead to add the track.
404     mStreams[streamIndex].mSource = source;
405     mStreams[streamIndex].mTrackEntry = mStreams[streamIndex].mMakeTrack(source->getFormat());
406 
407     return OK;
408 }
409 
start(MetaData * params)410 status_t WebmWriter::start(MetaData *params) {
411     if (mInitCheck != OK) {
412         return UNKNOWN_ERROR;
413     }
414 
415     if (mStreams[kVideoIndex].mTrackEntry == NULL
416             && mStreams[kAudioIndex].mTrackEntry == NULL) {
417         ALOGE("No source added");
418         return INVALID_OPERATION;
419     }
420 
421     if (mMaxFileSizeLimitBytes != 0) {
422         mIsFileSizeLimitExplicitlyRequested = true;
423     }
424 
425     if (params) {
426         int32_t isRealTimeRecording;
427         params->findInt32(kKeyRealTimeRecording, &isRealTimeRecording);
428         mIsRealTimeRecording = isRealTimeRecording;
429     }
430 
431     if (mStarted) {
432         if (mPaused) {
433             mPaused = false;
434             mStreams[kAudioIndex].mThread->resume();
435             mStreams[kVideoIndex].mThread->resume();
436         }
437         return OK;
438     }
439 
440     if (params) {
441         int32_t tcsl;
442         if (params->findInt32(kKeyTimeScale, &tcsl)) {
443             mTimeCodeScale = tcsl;
444         }
445     }
446     CHECK_GT(mTimeCodeScale, 0);
447     ALOGV("movie time scale: %" PRIu64, mTimeCodeScale);
448 
449     /*
450      * When the requested file size limit is small, the priority
451      * is to meet the file size limit requirement, rather than
452      * to make the file streamable. mStreamableFile does not tell
453      * whether the actual recorded file is streamable or not.
454      */
455     mStreamableFile = (!mMaxFileSizeLimitBytes)
456         || (mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes);
457 
458     /*
459      * Write various metadata.
460      */
461     sp<WebmElement> ebml, segment, info, seekHead, tracks, cues;
462     ebml = WebmElement::EbmlHeader();
463     segment = new WebmMaster(kMkvSegment);
464     seekHead = new EbmlVoid(kMaxMetaSeekSize);
465     info = WebmElement::SegmentInfo(mTimeCodeScale, 0);
466 
467     List<sp<WebmElement> > children;
468     for (size_t i = 0; i < kMaxStreams; ++i) {
469         if (mStreams[i].mTrackEntry != NULL) {
470             children.push_back(mStreams[i].mTrackEntry);
471         }
472     }
473     tracks = new WebmMaster(kMkvTracks, children);
474 
475     if (!mStreamableFile) {
476         cues = NULL;
477     } else {
478         int32_t bitRate = -1;
479         if (params) {
480             params->findInt32(kKeyBitRate, &bitRate);
481         }
482         mEstimatedCuesSize = estimateCuesSize(bitRate);
483         CHECK_GE(mEstimatedCuesSize, 8);
484         cues = new EbmlVoid(mEstimatedCuesSize);
485     }
486 
487     sp<WebmElement> elems[] = { ebml, segment, seekHead, info, tracks, cues };
488     size_t nElems = sizeof(elems) / sizeof(elems[0]);
489     uint64_t offsets[nElems];
490     uint64_t sizes[nElems];
491     for (uint32_t i = 0; i < nElems; i++) {
492         WebmElement *e = elems[i].get();
493         if (!e) {
494             continue;
495         }
496 
497         uint64_t size;
498         offsets[i] = ::lseek(mFd, 0, SEEK_CUR);
499         sizes[i] = e->mSize;
500         e->write(mFd, size);
501     }
502 
503     mSegmentOffset = offsets[1];
504     mSegmentDataStart = offsets[2];
505     mInfoOffset = offsets[3];
506     mInfoSize = sizes[3];
507     mTracksOffset = offsets[4];
508     mCuesOffset = offsets[5];
509 
510     // start threads
511     if (params) {
512         params->findInt64(kKeyTime, &mStartTimestampUs);
513     }
514 
515     initStream(kAudioIndex);
516     initStream(kVideoIndex);
517 
518     mStreams[kAudioIndex].mThread->start();
519     mStreams[kVideoIndex].mThread->start();
520     mSinkThread->start();
521 
522     mStarted = true;
523     return OK;
524 }
525 
pause()526 status_t WebmWriter::pause() {
527     if (mInitCheck != OK) {
528         return OK;
529     }
530     mPaused = true;
531     status_t err = OK;
532     for (int i = 0; i < kMaxStreams; ++i) {
533         if (mStreams[i].mThread == NULL) {
534             continue;
535         }
536         status_t status = mStreams[i].mThread->pause();
537         if (status != OK) {
538             err = status;
539         }
540     }
541     return err;
542 }
543 
stop()544 status_t WebmWriter::stop() {
545     return reset();
546 }
547 
reachedEOS()548 bool WebmWriter::reachedEOS() {
549     return !mSinkThread->running();
550 }
551 } /* namespace android */
552