• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
83 // static
videoTrack(const sp<MetaData> & md)84 sp<WebmElement> WebmWriter::videoTrack(const sp<MetaData>& md) {
85     int32_t width, height;
86     const char *mimeType;
87     if (!md->findInt32(kKeyWidth, &width)
88             || !md->findInt32(kKeyHeight, &height)
89             || !md->findCString(kKeyMIMEType, &mimeType)) {
90         ALOGE("Missing format keys for video track");
91         md->dumpToLog();
92         return NULL;
93     }
94     const char *codec;
95     if (!strncasecmp(
96             mimeType,
97             MEDIA_MIMETYPE_VIDEO_VP8,
98             strlen(MEDIA_MIMETYPE_VIDEO_VP8))) {
99         codec = "V_VP8";
100     } else if (!strncasecmp(
101             mimeType,
102             MEDIA_MIMETYPE_VIDEO_VP9,
103             strlen(MEDIA_MIMETYPE_VIDEO_VP9))) {
104         codec = "V_VP9";
105     } else {
106         ALOGE("Unsupported codec: %s", mimeType);
107         return NULL;
108     }
109     return WebmElement::VideoTrackEntry(codec, width, height, md);
110 }
111 
112 // static
audioTrack(const sp<MetaData> & md)113 sp<WebmElement> WebmWriter::audioTrack(const sp<MetaData>& md) {
114     int32_t nChannels, samplerate;
115     uint32_t type;
116     const void *headerData1;
117     const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0,
118             'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 };
119     const void *headerData3;
120     size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3;
121 
122     if (!md->findInt32(kKeyChannelCount, &nChannels)
123             || !md->findInt32(kKeySampleRate, &samplerate)
124             || !md->findData(kKeyVorbisInfo, &type, &headerData1, &headerSize1)
125             || !md->findData(kKeyVorbisBooks, &type, &headerData3, &headerSize3)) {
126         ALOGE("Missing format keys for audio track");
127         md->dumpToLog();
128         return NULL;
129     }
130 
131     size_t codecPrivateSize = 1;
132     codecPrivateSize += XiphLaceCodeLen(headerSize1);
133     codecPrivateSize += XiphLaceCodeLen(headerSize2);
134     codecPrivateSize += headerSize1 + headerSize2 + headerSize3;
135 
136     off_t off = 0;
137     sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
138     uint8_t *codecPrivateData = codecPrivateBuf->data();
139     codecPrivateData[off++] = 2;
140 
141     off += XiphLaceEnc(codecPrivateData + off, headerSize1);
142     off += XiphLaceEnc(codecPrivateData + off, headerSize2);
143 
144     memcpy(codecPrivateData + off, headerData1, headerSize1);
145     off += headerSize1;
146     memcpy(codecPrivateData + off, headerData2, headerSize2);
147     off += headerSize2;
148     memcpy(codecPrivateData + off, headerData3, headerSize3);
149 
150     sp<WebmElement> entry = WebmElement::AudioTrackEntry(
151             nChannels,
152             samplerate,
153             codecPrivateBuf);
154     return entry;
155 }
156 
numTracks()157 size_t WebmWriter::numTracks() {
158     Mutex::Autolock autolock(mLock);
159 
160     size_t numTracks = 0;
161     for (size_t i = 0; i < kMaxStreams; ++i) {
162         if (mStreams[i].mTrackEntry != NULL) {
163             numTracks++;
164         }
165     }
166 
167     return numTracks;
168 }
169 
estimateCuesSize(int32_t bitRate)170 uint64_t WebmWriter::estimateCuesSize(int32_t bitRate) {
171     // This implementation is based on estimateMoovBoxSize in MPEG4Writer.
172     //
173     // Statistical analysis shows that metadata usually accounts
174     // for a small portion of the total file size, usually < 0.6%.
175 
176     // The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2,
177     // where 1MB is the common file size limit for MMS application.
178     // The default MAX _MOOV_BOX_SIZE value is based on about 3
179     // minute video recording with a bit rate about 3 Mbps, because
180     // statistics also show that most of the video captured are going
181     // to be less than 3 minutes.
182 
183     // If the estimation is wrong, we will pay the price of wasting
184     // some reserved space. This should not happen so often statistically.
185     static const int32_t factor = 2;
186     static const int64_t MIN_CUES_SIZE = 3 * 1024;  // 3 KB
187     static const int64_t MAX_CUES_SIZE = (180 * 3000000 * 6LL / 8000);
188     int64_t size = MIN_CUES_SIZE;
189 
190     // Max file size limit is set
191     if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
192         size = mMaxFileSizeLimitBytes * 6 / 1000;
193     }
194 
195     // Max file duration limit is set
196     if (mMaxFileDurationLimitUs != 0) {
197         if (bitRate > 0) {
198             int64_t size2 = ((mMaxFileDurationLimitUs * bitRate * 6) / 1000 / 8000000);
199             if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
200                 // When both file size and duration limits are set,
201                 // we use the smaller limit of the two.
202                 if (size > size2) {
203                     size = size2;
204                 }
205             } else {
206                 // Only max file duration limit is set
207                 size = size2;
208             }
209         }
210     }
211 
212     if (size < MIN_CUES_SIZE) {
213         size = MIN_CUES_SIZE;
214     }
215 
216     // Any long duration recording will be probably end up with
217     // non-streamable webm file.
218     if (size > MAX_CUES_SIZE) {
219         size = MAX_CUES_SIZE;
220     }
221 
222     ALOGV("limits: %" PRId64 "/%" PRId64 " bytes/us,"
223             " bit rate: %d bps and the estimated cues size %" PRId64 " bytes",
224             mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
225     return factor * size;
226 }
227 
initStream(size_t idx)228 void WebmWriter::initStream(size_t idx) {
229     if (mStreams[idx].mThread != NULL) {
230         return;
231     }
232     if (mStreams[idx].mSource == NULL) {
233         ALOGV("adding dummy source ... ");
234         mStreams[idx].mThread = new WebmFrameEmptySourceThread(
235                 mStreams[idx].mType, mStreams[idx].mSink);
236     } else {
237         ALOGV("adding source %p", mStreams[idx].mSource.get());
238         mStreams[idx].mThread = new WebmFrameMediaSourceThread(
239                 mStreams[idx].mSource,
240                 mStreams[idx].mType,
241                 mStreams[idx].mSink,
242                 mTimeCodeScale,
243                 mStartTimestampUs,
244                 mStartTimeOffsetMs,
245                 numTracks(),
246                 mIsRealTimeRecording);
247     }
248 }
249 
release()250 void WebmWriter::release() {
251     close(mFd);
252     mFd = -1;
253     mInitCheck = NO_INIT;
254     mStarted = false;
255     for (size_t ix = 0; ix < kMaxStreams; ++ix) {
256         mStreams[ix].mTrackEntry.clear();
257         mStreams[ix].mSource.clear();
258     }
259     mStreamsInOrder.clear();
260 }
261 
reset()262 status_t WebmWriter::reset() {
263     if (mInitCheck != OK) {
264         return OK;
265     } else {
266         if (!mStarted) {
267             release();
268             return OK;
269         }
270     }
271 
272     status_t err = OK;
273     int64_t maxDurationUs = 0;
274     int64_t minDurationUs = 0x7fffffffffffffffLL;
275     for (int i = 0; i < kMaxStreams; ++i) {
276         if (mStreams[i].mThread == NULL) {
277             continue;
278         }
279 
280         status_t status = mStreams[i].mThread->stop();
281         if (err == OK && status != OK) {
282             err = status;
283         }
284 
285         int64_t durationUs = mStreams[i].mThread->getDurationUs();
286         if (durationUs > maxDurationUs) {
287             maxDurationUs = durationUs;
288         }
289         if (durationUs < minDurationUs) {
290             minDurationUs = durationUs;
291         }
292 
293         mStreams[i].mThread.clear();
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 durationOffset = mInfoOffset + sizeOf(kMkvInfo) + sizeOf(mInfoSize)
337         + sizeOf(kMkvSegmentDuration) + sizeOf(sizeof(double));
338     sp<WebmElement> duration = new WebmFloat(
339             kMkvSegmentDuration,
340             (double) (maxDurationUs * 1000 / mTimeCodeScale));
341     duration->serializePayload(bary);
342     ::lseek(mFd, durationOffset, SEEK_SET);
343     ::write(mFd, bary, sizeof(double));
344 
345     List<sp<WebmElement> > seekEntries;
346     seekEntries.push_back(WebmElement::SeekEntry(kMkvInfo, mInfoOffset - mSegmentDataStart));
347     seekEntries.push_back(WebmElement::SeekEntry(kMkvTracks, mTracksOffset - mSegmentDataStart));
348     seekEntries.push_back(WebmElement::SeekEntry(kMkvCues, mCuesOffset - mSegmentDataStart));
349     sp<WebmElement> seekHead = new WebmMaster(kMkvSeekHead, seekEntries);
350 
351     uint64_t metaSeekSize;
352     ::lseek(mFd, mSegmentDataStart, SEEK_SET);
353     seekHead->write(mFd, metaSeekSize);
354 
355     uint64_t spaceSize;
356     sp<WebmElement> space = new EbmlVoid(kMaxMetaSeekSize - metaSeekSize);
357     space->write(mFd, spaceSize);
358 
359     release();
360     return err;
361 }
362 
addSource(const sp<IMediaSource> & source)363 status_t WebmWriter::addSource(const sp<IMediaSource> &source) {
364     Mutex::Autolock l(mLock);
365     if (mStarted) {
366         ALOGE("Attempt to add source AFTER recording is started");
367         return UNKNOWN_ERROR;
368     }
369 
370     // At most 2 tracks can be supported.
371     if (mStreams[kVideoIndex].mTrackEntry != NULL
372             && mStreams[kAudioIndex].mTrackEntry != NULL) {
373         ALOGE("Too many tracks (2) to add");
374         return ERROR_UNSUPPORTED;
375     }
376 
377     CHECK(source != NULL);
378 
379     // A track of type other than video or audio is not supported.
380     const char *mime;
381     source->getFormat()->findCString(kKeyMIMEType, &mime);
382     const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8;
383     const char *vp9 = MEDIA_MIMETYPE_VIDEO_VP9;
384     const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS;
385 
386     size_t streamIndex;
387     if (!strncasecmp(mime, vp8, strlen(vp8)) ||
388         !strncasecmp(mime, vp9, strlen(vp9))) {
389         streamIndex = kVideoIndex;
390     } else if (!strncasecmp(mime, vorbis, strlen(vorbis))) {
391         streamIndex = kAudioIndex;
392     } else {
393         ALOGE("Track (%s) other than %s, %s or %s is not supported",
394               mime, vp8, vp9, vorbis);
395         return ERROR_UNSUPPORTED;
396     }
397 
398     // No more than one video or one audio track is supported.
399     if (mStreams[streamIndex].mTrackEntry != NULL) {
400         ALOGE("%s track already exists", mStreams[streamIndex].mName);
401         return ERROR_UNSUPPORTED;
402     }
403 
404     // This is the first track of either audio or video.
405     // Go ahead to add the track.
406     mStreams[streamIndex].mSource = source;
407     mStreams[streamIndex].mTrackEntry = mStreams[streamIndex].mMakeTrack(source->getFormat());
408     if (mStreams[streamIndex].mTrackEntry == NULL) {
409         mStreams[streamIndex].mSource.clear();
410         return BAD_VALUE;
411     }
412     mStreamsInOrder.push_back(mStreams[streamIndex].mTrackEntry);
413 
414     return OK;
415 }
416 
start(MetaData * params)417 status_t WebmWriter::start(MetaData *params) {
418     if (mInitCheck != OK) {
419         return UNKNOWN_ERROR;
420     }
421 
422     if (mStreams[kVideoIndex].mTrackEntry == NULL
423             && mStreams[kAudioIndex].mTrackEntry == NULL) {
424         ALOGE("No source added");
425         return INVALID_OPERATION;
426     }
427 
428     if (mMaxFileSizeLimitBytes != 0) {
429         mIsFileSizeLimitExplicitlyRequested = true;
430     }
431 
432     if (params) {
433         int32_t isRealTimeRecording;
434         params->findInt32(kKeyRealTimeRecording, &isRealTimeRecording);
435         mIsRealTimeRecording = isRealTimeRecording;
436     }
437 
438     if (mStarted) {
439         if (mPaused) {
440             mPaused = false;
441             mStreams[kAudioIndex].mThread->resume();
442             mStreams[kVideoIndex].mThread->resume();
443         }
444         return OK;
445     }
446 
447     if (params) {
448         int32_t tcsl;
449         if (params->findInt32(kKeyTimeScale, &tcsl)) {
450             mTimeCodeScale = tcsl;
451         }
452     }
453     if (mTimeCodeScale == 0) {
454         ALOGE("movie time scale is 0");
455         return BAD_VALUE;
456     }
457     ALOGV("movie time scale: %" PRIu64, mTimeCodeScale);
458 
459     /*
460      * When the requested file size limit is small, the priority
461      * is to meet the file size limit requirement, rather than
462      * to make the file streamable. mStreamableFile does not tell
463      * whether the actual recorded file is streamable or not.
464      */
465     mStreamableFile = (!mMaxFileSizeLimitBytes)
466         || (mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes);
467 
468     /*
469      * Write various metadata.
470      */
471     sp<WebmElement> ebml, segment, info, seekHead, tracks, cues;
472     ebml = WebmElement::EbmlHeader();
473     segment = new WebmMaster(kMkvSegment);
474     seekHead = new EbmlVoid(kMaxMetaSeekSize);
475     info = WebmElement::SegmentInfo(mTimeCodeScale, 0);
476 
477     List<sp<WebmElement> > children;
478     for (size_t i = 0; i < mStreamsInOrder.size(); ++i) {
479         children.push_back(mStreamsInOrder[i]);
480     }
481     tracks = new WebmMaster(kMkvTracks, children);
482 
483     if (!mStreamableFile) {
484         cues = NULL;
485     } else {
486         int32_t bitRate = -1;
487         if (params) {
488             params->findInt32(kKeyBitRate, &bitRate);
489         }
490         mEstimatedCuesSize = estimateCuesSize(bitRate);
491         CHECK_GE(mEstimatedCuesSize, 8);
492         cues = new EbmlVoid(mEstimatedCuesSize);
493     }
494 
495     sp<WebmElement> elems[] = { ebml, segment, seekHead, info, tracks, cues };
496     static const size_t nElems = sizeof(elems) / sizeof(elems[0]);
497     uint64_t offsets[nElems];
498     uint64_t sizes[nElems];
499     for (uint32_t i = 0; i < nElems; i++) {
500         WebmElement *e = elems[i].get();
501         if (!e) {
502             continue;
503         }
504 
505         uint64_t size;
506         offsets[i] = ::lseek(mFd, 0, SEEK_CUR);
507         sizes[i] = e->mSize;
508         e->write(mFd, size);
509     }
510 
511     mSegmentOffset = offsets[1];
512     mSegmentDataStart = offsets[2];
513     mInfoOffset = offsets[3];
514     mInfoSize = sizes[3];
515     mTracksOffset = offsets[4];
516     mCuesOffset = offsets[5];
517 
518     // start threads
519     if (params) {
520         params->findInt64(kKeyTime, &mStartTimestampUs);
521     }
522 
523     initStream(kAudioIndex);
524     initStream(kVideoIndex);
525 
526     mStreams[kAudioIndex].mThread->start();
527     mStreams[kVideoIndex].mThread->start();
528     mSinkThread->start();
529 
530     mStarted = true;
531     return OK;
532 }
533 
pause()534 status_t WebmWriter::pause() {
535     if (mInitCheck != OK) {
536         return OK;
537     }
538     mPaused = true;
539     status_t err = OK;
540     for (int i = 0; i < kMaxStreams; ++i) {
541         if (mStreams[i].mThread == NULL) {
542             continue;
543         }
544         status_t status = mStreams[i].mThread->pause();
545         if (status != OK) {
546             err = status;
547         }
548     }
549     return err;
550 }
551 
stop()552 status_t WebmWriter::stop() {
553     return reset();
554 }
555 
reachedEOS()556 bool WebmWriter::reachedEOS() {
557     return !mSinkThread->running();
558 }
559 } /* namespace android */
560