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 #include <media/stagefright/foundation/hexdump.h>
27 #include <media/stagefright/foundation/OpusHeader.h>
28 
29 #include <utils/Errors.h>
30 
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <sys/stat.h>
34 #include <inttypes.h>
35 
36 using namespace webm;
37 
38 namespace {
XiphLaceCodeLen(size_t size)39 size_t XiphLaceCodeLen(size_t size) {
40     return size / 0xff + 1;
41 }
42 
XiphLaceEnc(uint8_t * buf,size_t size)43 size_t XiphLaceEnc(uint8_t *buf, size_t size) {
44     size_t i;
45     for (i = 0; size >= 0xff; ++i, size -= 0xff) {
46         buf[i] = 0xff;
47     }
48     buf[i++] = size;
49     return i;
50 }
51 }
52 
53 namespace android {
54 
55 static const int64_t kMinStreamableFileSizeInBytes = 5 * 1024 * 1024;
56 
isFdOpenModeValid(int fd)57 bool WebmWriter::isFdOpenModeValid(int fd) {
58     // check for invalid file descriptor.
59     if (!MediaWriter::isFdOpenModeValid(fd)) {
60         return false;
61     }
62     int flags = fcntl(fd, F_GETFL);
63     if ((flags & O_RDWR) == 0) {
64         ALOGE("File must be in read-write mode for webm writer");
65         return false;
66     }
67     return true;
68 }
69 
isSampleMetadataValid(size_t trackIndex,int64_t timeUs)70 bool WebmWriter::isSampleMetadataValid(size_t trackIndex, int64_t timeUs) {
71     int64_t prevTimeUs = 0;
72     if (mLastTimestampUsByTrackIndex.find(trackIndex) != mLastTimestampUsByTrackIndex.end()) {
73         prevTimeUs = mLastTimestampUsByTrackIndex[trackIndex];
74     }
75     // WebM has monotonically increasing timestamps
76     if (timeUs < 0 || timeUs < prevTimeUs) {
77         return false;
78     }
79     int64_t lastDurationUs = timeUs - prevTimeUs;
80     // Ensure that the timeUs value does not overflow,
81     // when adding lastDurationUs in the WebmFrameMediaSourceThread.
82     if (timeUs > (INT64_MAX / 1000) - lastDurationUs) {
83         return false;
84     }
85     mLastTimestampUsByTrackIndex[trackIndex] = timeUs;
86     return true;
87 }
88 
WebmWriter(int fd)89 WebmWriter::WebmWriter(int fd)
90     : mFd(dup(fd)),
91       mInitCheck(mFd < 0 ? NO_INIT : OK),
92       mTimeCodeScale(1000000),
93       mStartTimestampUs(0),
94       mStartTimeOffsetMs(0),
95       mSegmentOffset(0),
96       mSegmentDataStart(0),
97       mInfoOffset(0),
98       mInfoSize(0),
99       mTracksOffset(0),
100       mCuesOffset(0),
101       mPaused(false),
102       mStarted(false),
103       mIsFileSizeLimitExplicitlyRequested(false),
104       mIsRealTimeRecording(false),
105       mStreamableFile(true),
106       mEstimatedCuesSize(0) {
107     mStreams[kAudioIndex] = WebmStream(kAudioType, "Audio", &WebmWriter::audioTrack);
108     mStreams[kVideoIndex] = WebmStream(kVideoType, "Video", &WebmWriter::videoTrack);
109     mSinkThread = new WebmFrameSinkThread(
110             mFd,
111             mSegmentDataStart,
112             mStreams[kVideoIndex].mSink,
113             mStreams[kAudioIndex].mSink,
114             mCuePoints);
115 }
116 
117 // static
videoTrack(const sp<MetaData> & md)118 sp<WebmElement> WebmWriter::videoTrack(const sp<MetaData>& md) {
119     int32_t width, height;
120     const char *mimeType;
121     if (!md->findInt32(kKeyWidth, &width)
122             || !md->findInt32(kKeyHeight, &height)
123             || !md->findCString(kKeyMIMEType, &mimeType)) {
124         ALOGE("Missing format keys for video track");
125         md->dumpToLog();
126         return NULL;
127     }
128     const char *codec;
129     if (!strncasecmp(
130             mimeType,
131             MEDIA_MIMETYPE_VIDEO_VP8,
132             strlen(MEDIA_MIMETYPE_VIDEO_VP8))) {
133         codec = "V_VP8";
134     } else if (!strncasecmp(
135             mimeType,
136             MEDIA_MIMETYPE_VIDEO_VP9,
137             strlen(MEDIA_MIMETYPE_VIDEO_VP9))) {
138         codec = "V_VP9";
139     } else {
140         ALOGE("Unsupported codec: %s", mimeType);
141         return NULL;
142     }
143     return WebmElement::VideoTrackEntry(codec, width, height, md);
144 }
145 
146 // static
audioTrack(const sp<MetaData> & md)147 sp<WebmElement> WebmWriter::audioTrack(const sp<MetaData>& md) {
148     int32_t nChannels, samplerate;
149     const char* mimeType;
150 
151     if (!md->findInt32(kKeyChannelCount, &nChannels)
152         || !md->findInt32(kKeySampleRate, &samplerate)
153         || !md->findCString(kKeyMIMEType, &mimeType)) {
154         ALOGE("Missing format keys for audio track");
155         md->dumpToLog();
156         return NULL;
157     }
158 
159     int32_t bitsPerSample = 0;
160     if (!md->findInt32(kKeyBitsPerSample, &bitsPerSample)) {
161         ALOGV("kKeyBitsPerSample not available");
162     }
163 
164     if (!strncasecmp(mimeType, MEDIA_MIMETYPE_AUDIO_OPUS, strlen(MEDIA_MIMETYPE_AUDIO_OPUS))) {
165         // Opus in WebM is a well-known, yet under-documented, format. The codec private data
166         // of the track is an Opus Ogg header (https://tools.ietf.org/html/rfc7845#section-5.1)
167         // The name of the track isn't standardized, its value should be "A_OPUS".
168         OpusHeader header;
169         header.channels = nChannels;
170         header.num_streams = nChannels;
171         header.num_coupled = 0;
172         // - Channel mapping family (8 bits unsigned)
173         //  --  0 = one stream: mono or L,R stereo
174         //  --  1 = channels in vorbis spec order: mono or L,R stereo or ... or FL,C,FR,RL,RR,LFE, ...
175         //  --  2..254 = reserved (treat as 255)
176         //  --  255 = no defined channel meaning
177         //
178         //  our implementation encodes:  0, 1, or 255
179         header.channel_mapping = ((nChannels > 8) ? 255 : (nChannels > 2));
180         header.gain_db = 0;
181         header.skip_samples = 0;
182 
183         // headers are 21-bytes + something driven by channel count
184         // expect numbers in the low 30's here. WriteOpusHeader() will tell us
185         // if things are bad.
186         unsigned char header_data[100];
187         int headerSize = WriteOpusHeader(header, samplerate, (uint8_t*)header_data,
188                                             sizeof(header_data));
189 
190         if (headerSize < 0) {
191             // didn't fill out that header for some reason
192             ALOGE("failed to generate OPUS header");
193             return NULL;
194         }
195 
196         size_t codecPrivateSize = 0;
197         codecPrivateSize += headerSize;
198 
199         off_t off = 0;
200         sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
201         uint8_t* codecPrivateData = codecPrivateBuf->data();
202 
203         memcpy(codecPrivateData + off, (uint8_t*)header_data, headerSize);
204         sp<WebmElement> entry = WebmElement::AudioTrackEntry("A_OPUS", nChannels, samplerate,
205                                                              codecPrivateBuf, bitsPerSample);
206         return entry;
207     } else if (!strncasecmp(mimeType,
208                             MEDIA_MIMETYPE_AUDIO_VORBIS,
209                             strlen(MEDIA_MIMETYPE_AUDIO_VORBIS))) {
210         uint32_t type;
211         const void *headerData1;
212         const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0,
213                 'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 };
214         const void *headerData3;
215         size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3;
216 
217         if (!md->findData(kKeyOpaqueCSD0, &type, &headerData1, &headerSize1)
218             || !md->findData(kKeyOpaqueCSD1, &type, &headerData3, &headerSize3)) {
219             ALOGE("Missing header format keys for vorbis track");
220             md->dumpToLog();
221             return NULL;
222         }
223 
224         size_t codecPrivateSize = 1;
225         codecPrivateSize += XiphLaceCodeLen(headerSize1);
226         codecPrivateSize += XiphLaceCodeLen(headerSize2);
227         codecPrivateSize += headerSize1 + headerSize2 + headerSize3;
228 
229         off_t off = 0;
230         sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize);
231         uint8_t *codecPrivateData = codecPrivateBuf->data();
232         codecPrivateData[off++] = 2;
233 
234         off += XiphLaceEnc(codecPrivateData + off, headerSize1);
235         off += XiphLaceEnc(codecPrivateData + off, headerSize2);
236 
237         memcpy(codecPrivateData + off, headerData1, headerSize1);
238         off += headerSize1;
239         memcpy(codecPrivateData + off, headerData2, headerSize2);
240         off += headerSize2;
241         memcpy(codecPrivateData + off, headerData3, headerSize3);
242 
243         sp<WebmElement> entry = WebmElement::AudioTrackEntry("A_VORBIS", nChannels, samplerate,
244                                                              codecPrivateBuf, bitsPerSample);
245         return entry;
246     } else {
247         ALOGE("Track (%s) is not a supported audio format", mimeType);
248         return NULL;
249     }
250 }
251 
numTracks()252 size_t WebmWriter::numTracks() {
253     Mutex::Autolock autolock(mLock);
254 
255     size_t numTracks = 0;
256     for (size_t i = 0; i < kMaxStreams; ++i) {
257         if (mStreams[i].mTrackEntry != NULL) {
258             numTracks++;
259         }
260     }
261 
262     return numTracks;
263 }
264 
estimateCuesSize(int32_t bitRate)265 uint64_t WebmWriter::estimateCuesSize(int32_t bitRate) {
266     // This implementation is based on estimateMoovBoxSize in MPEG4Writer.
267     //
268     // Statistical analysis shows that metadata usually accounts
269     // for a small portion of the total file size, usually < 0.6%.
270 
271     // The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2,
272     // where 1MB is the common file size limit for MMS application.
273     // The default MAX _MOOV_BOX_SIZE value is based on about 3
274     // minute video recording with a bit rate about 3 Mbps, because
275     // statistics also show that most of the video captured are going
276     // to be less than 3 minutes.
277 
278     // If the estimation is wrong, we will pay the price of wasting
279     // some reserved space. This should not happen so often statistically.
280     static const int32_t factor = 2;
281     static const int64_t MIN_CUES_SIZE = 3 * 1024;  // 3 KB
282     static const int64_t MAX_CUES_SIZE = (180 * 3000000 * 6LL / 8000);
283     int64_t size = MIN_CUES_SIZE;
284 
285     // Max file size limit is set
286     if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
287         size = mMaxFileSizeLimitBytes * 6 / 1000;
288     }
289 
290     // Max file duration limit is set
291     if (mMaxFileDurationLimitUs != 0) {
292         if (bitRate > 0) {
293             int64_t size2 = ((mMaxFileDurationLimitUs * bitRate * 6) / 1000 / 8000000);
294             if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
295                 // When both file size and duration limits are set,
296                 // we use the smaller limit of the two.
297                 if (size > size2) {
298                     size = size2;
299                 }
300             } else {
301                 // Only max file duration limit is set
302                 size = size2;
303             }
304         }
305     }
306 
307     if (size < MIN_CUES_SIZE) {
308         size = MIN_CUES_SIZE;
309     }
310 
311     // Any long duration recording will be probably end up with
312     // non-streamable webm file.
313     if (size > MAX_CUES_SIZE) {
314         size = MAX_CUES_SIZE;
315     }
316 
317     ALOGV("limits: %" PRId64 "/%" PRId64 " bytes/us,"
318             " bit rate: %d bps and the estimated cues size %" PRId64 " bytes",
319             mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
320     return factor * size;
321 }
322 
initStream(size_t idx)323 void WebmWriter::initStream(size_t idx) {
324     if (mStreams[idx].mThread != NULL) {
325         return;
326     }
327     if (mStreams[idx].mSource == NULL) {
328         ALOGV("adding dummy source ... ");
329         mStreams[idx].mThread = new WebmFrameEmptySourceThread(
330                 mStreams[idx].mType, mStreams[idx].mSink);
331     } else {
332         ALOGV("adding source %p", mStreams[idx].mSource.get());
333         mStreams[idx].mThread = new WebmFrameMediaSourceThread(
334                 mStreams[idx].mSource,
335                 mStreams[idx].mType,
336                 mStreams[idx].mSink,
337                 mTimeCodeScale,
338                 mStartTimestampUs,
339                 mStartTimeOffsetMs,
340                 numTracks(),
341                 mIsRealTimeRecording);
342     }
343 }
344 
release()345 void WebmWriter::release() {
346     close(mFd);
347     mFd = -1;
348     mInitCheck = NO_INIT;
349     mStarted = false;
350     for (size_t ix = 0; ix < kMaxStreams; ++ix) {
351         mStreams[ix].mTrackEntry.clear();
352         mStreams[ix].mSource.clear();
353     }
354     mStreamsInOrder.clear();
355 }
356 
reset()357 status_t WebmWriter::reset() {
358     if (mInitCheck != OK) {
359         return OK;
360     } else {
361         if (!mStarted) {
362             release();
363             return OK;
364         }
365     }
366 
367     status_t err = OK;
368     int64_t maxDurationUs = 0;
369     int64_t minDurationUs = 0x7fffffffffffffffLL;
370     for (int i = 0; i < kMaxStreams; ++i) {
371         if (mStreams[i].mThread == NULL) {
372             continue;
373         }
374 
375         status_t status = mStreams[i].mThread->stop();
376         if (err == OK && status != OK) {
377             err = status;
378         }
379 
380         int64_t durationUs = mStreams[i].mThread->getDurationUs();
381         if (durationUs > maxDurationUs) {
382             maxDurationUs = durationUs;
383         }
384         if (durationUs < minDurationUs) {
385             minDurationUs = durationUs;
386         }
387 
388         mStreams[i].mThread.clear();
389     }
390 
391     if (numTracks() > 1) {
392         ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us", minDurationUs, maxDurationUs);
393     }
394 
395     mSinkThread->stop();
396 
397     // Do not write out movie header on error.
398     if (err != OK) {
399         release();
400         return err;
401     }
402 
403     sp<WebmElement> cues = new WebmMaster(kMkvCues, mCuePoints);
404     uint64_t cuesSize = cues->totalSize();
405     // TRICKY Even when the cues do fit in the space we reserved, if they do not fit
406     // perfectly, we still need to check if there is enough "extra space" to write an
407     // EBML void element.
408     if (cuesSize != mEstimatedCuesSize && cuesSize > mEstimatedCuesSize - kMinEbmlVoidSize) {
409         mCuesOffset = ::lseek(mFd, 0, SEEK_CUR);
410         cues->write(mFd, cuesSize);
411     } else {
412         uint64_t spaceSize;
413         ::lseek(mFd, mCuesOffset, SEEK_SET);
414         cues->write(mFd, cuesSize);
415         sp<WebmElement> space = new EbmlVoid(mEstimatedCuesSize - cuesSize);
416         space->write(mFd, spaceSize);
417     }
418 
419     mCuePoints.clear();
420     mStreams[kVideoIndex].mSink.clear();
421     mStreams[kAudioIndex].mSink.clear();
422 
423     uint8_t bary[sizeof(uint64_t)];
424     uint64_t totalSize = ::lseek(mFd, 0, SEEK_END);
425     uint64_t segmentSize = totalSize - mSegmentDataStart;
426     ::lseek(mFd, mSegmentOffset + sizeOf(kMkvSegment), SEEK_SET);
427     uint64_t segmentSizeCoded = encodeUnsigned(segmentSize, sizeOf(kMkvUnknownLength));
428     serializeCodedUnsigned(segmentSizeCoded, bary);
429     ::write(mFd, bary, sizeOf(kMkvUnknownLength));
430 
431     uint64_t durationOffset = mInfoOffset + sizeOf(kMkvInfo) + sizeOf(mInfoSize)
432         + sizeOf(kMkvSegmentDuration) + sizeOf(sizeof(double));
433     sp<WebmElement> duration = new WebmFloat(
434             kMkvSegmentDuration,
435             (double) (maxDurationUs * 1000 / mTimeCodeScale));
436     duration->serializePayload(bary);
437     ::lseek(mFd, durationOffset, SEEK_SET);
438     ::write(mFd, bary, sizeof(double));
439 
440     List<sp<WebmElement> > seekEntries;
441     seekEntries.push_back(WebmElement::SeekEntry(kMkvInfo, mInfoOffset - mSegmentDataStart));
442     seekEntries.push_back(WebmElement::SeekEntry(kMkvTracks, mTracksOffset - mSegmentDataStart));
443     seekEntries.push_back(WebmElement::SeekEntry(kMkvCues, mCuesOffset - mSegmentDataStart));
444     sp<WebmElement> seekHead = new WebmMaster(kMkvSeekHead, seekEntries);
445 
446     uint64_t metaSeekSize;
447     ::lseek(mFd, mSegmentDataStart, SEEK_SET);
448     seekHead->write(mFd, metaSeekSize);
449 
450     uint64_t spaceSize;
451     sp<WebmElement> space = new EbmlVoid(kMaxMetaSeekSize - metaSeekSize);
452     space->write(mFd, spaceSize);
453 
454     release();
455     return err;
456 }
457 
addSource(const sp<MediaSource> & source)458 status_t WebmWriter::addSource(const sp<MediaSource> &source) {
459     Mutex::Autolock l(mLock);
460     if (mStarted) {
461         ALOGE("Attempt to add source AFTER recording is started");
462         return UNKNOWN_ERROR;
463     }
464 
465     // At most 2 tracks can be supported.
466     if (mStreams[kVideoIndex].mTrackEntry != NULL
467             && mStreams[kAudioIndex].mTrackEntry != NULL) {
468         ALOGE("Too many tracks (2) to add");
469         return ERROR_UNSUPPORTED;
470     }
471 
472     CHECK(source != NULL);
473 
474     // A track of type other than video or audio is not supported.
475     const char *mime;
476     source->getFormat()->findCString(kKeyMIMEType, &mime);
477     const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8;
478     const char *vp9 = MEDIA_MIMETYPE_VIDEO_VP9;
479     const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS;
480     const char* opus = MEDIA_MIMETYPE_AUDIO_OPUS;
481 
482     size_t streamIndex;
483     if (!strncasecmp(mime, vp8, strlen(vp8)) ||
484         !strncasecmp(mime, vp9, strlen(vp9))) {
485         streamIndex = kVideoIndex;
486     } else if (!strncasecmp(mime, vorbis, strlen(vorbis)) ||
487                !strncasecmp(mime, opus, strlen(opus))) {
488         streamIndex = kAudioIndex;
489     } else {
490         ALOGE("Track (%s) other than %s, %s, %s, or %s is not supported",
491               mime, vp8, vp9, vorbis, opus);
492         return ERROR_UNSUPPORTED;
493     }
494 
495     // No more than one video or one audio track is supported.
496     if (mStreams[streamIndex].mTrackEntry != NULL) {
497         ALOGE("%s track already exists", mStreams[streamIndex].mName);
498         return ERROR_UNSUPPORTED;
499     }
500 
501     // This is the first track of either audio or video.
502     // Go ahead to add the track.
503     mStreams[streamIndex].mSource = source;
504     mStreams[streamIndex].mTrackEntry = mStreams[streamIndex].mMakeTrack(source->getFormat());
505     if (mStreams[streamIndex].mTrackEntry == NULL) {
506         mStreams[streamIndex].mSource.clear();
507         return BAD_VALUE;
508     }
509     mStreamsInOrder.push_back(mStreams[streamIndex].mTrackEntry);
510 
511     return OK;
512 }
513 
start(MetaData * params)514 status_t WebmWriter::start(MetaData *params) {
515     if (mInitCheck != OK) {
516         return UNKNOWN_ERROR;
517     }
518 
519     if (mStreams[kVideoIndex].mTrackEntry == NULL
520             && mStreams[kAudioIndex].mTrackEntry == NULL) {
521         ALOGE("No source added");
522         return INVALID_OPERATION;
523     }
524 
525     if (mMaxFileSizeLimitBytes != 0) {
526         mIsFileSizeLimitExplicitlyRequested = true;
527     }
528 
529     if (params) {
530         int32_t isRealTimeRecording;
531         params->findInt32(kKeyRealTimeRecording, &isRealTimeRecording);
532         mIsRealTimeRecording = isRealTimeRecording;
533     }
534 
535     if (mStarted) {
536         if (mPaused) {
537             mPaused = false;
538             mStreams[kAudioIndex].mThread->resume();
539             mStreams[kVideoIndex].mThread->resume();
540         }
541         return OK;
542     }
543 
544     if (params) {
545         int32_t tcsl;
546         if (params->findInt32(kKeyTimeScale, &tcsl)) {
547             mTimeCodeScale = tcsl;
548         }
549     }
550     if (mTimeCodeScale == 0) {
551         ALOGE("movie time scale is 0");
552         return BAD_VALUE;
553     }
554     ALOGV("movie time scale: %" PRIu64, mTimeCodeScale);
555 
556     /*
557      * When the requested file size limit is small, the priority
558      * is to meet the file size limit requirement, rather than
559      * to make the file streamable. mStreamableFile does not tell
560      * whether the actual recorded file is streamable or not.
561      */
562     mStreamableFile = (!mMaxFileSizeLimitBytes)
563         || (mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes);
564 
565     /*
566      * Write various metadata.
567      */
568     sp<WebmElement> ebml, segment, info, seekHead, tracks, cues;
569     ebml = WebmElement::EbmlHeader();
570     segment = new WebmMaster(kMkvSegment);
571     seekHead = new EbmlVoid(kMaxMetaSeekSize);
572     info = WebmElement::SegmentInfo(mTimeCodeScale, 0);
573 
574     List<sp<WebmElement> > children;
575     for (size_t i = 0; i < mStreamsInOrder.size(); ++i) {
576         children.push_back(mStreamsInOrder[i]);
577     }
578     tracks = new WebmMaster(kMkvTracks, children);
579 
580     if (!mStreamableFile) {
581         cues = NULL;
582     } else {
583         int32_t bitRate = -1;
584         if (params) {
585             params->findInt32(kKeyBitRate, &bitRate);
586         }
587         mEstimatedCuesSize = estimateCuesSize(bitRate);
588         CHECK_GE(mEstimatedCuesSize, 8u);
589         cues = new EbmlVoid(mEstimatedCuesSize);
590     }
591 
592     sp<WebmElement> elems[] = { ebml, segment, seekHead, info, tracks, cues };
593     static const size_t nElems = sizeof(elems) / sizeof(elems[0]);
594     uint64_t offsets[nElems];
595     uint64_t sizes[nElems];
596     for (uint32_t i = 0; i < nElems; i++) {
597         WebmElement *e = elems[i].get();
598         if (!e) {
599             continue;
600         }
601 
602         uint64_t size;
603         offsets[i] = ::lseek(mFd, 0, SEEK_CUR);
604         sizes[i] = e->mSize;
605         e->write(mFd, size);
606     }
607 
608     mSegmentOffset = offsets[1];
609     mSegmentDataStart = offsets[2];
610     mInfoOffset = offsets[3];
611     mInfoSize = sizes[3];
612     mTracksOffset = offsets[4];
613     mCuesOffset = offsets[5];
614 
615     // start threads
616     if (params) {
617         params->findInt64(kKeyTime, &mStartTimestampUs);
618     }
619 
620     initStream(kAudioIndex);
621     initStream(kVideoIndex);
622 
623     mStreams[kAudioIndex].mThread->start();
624     mStreams[kVideoIndex].mThread->start();
625     mSinkThread->start();
626 
627     mStarted = true;
628     return OK;
629 }
630 
pause()631 status_t WebmWriter::pause() {
632     if (mInitCheck != OK) {
633         return OK;
634     }
635     mPaused = true;
636     status_t err = OK;
637     for (int i = 0; i < kMaxStreams; ++i) {
638         if (mStreams[i].mThread == NULL) {
639             continue;
640         }
641         status_t status = mStreams[i].mThread->pause();
642         if (status != OK) {
643             err = status;
644         }
645     }
646     return err;
647 }
648 
stop()649 status_t WebmWriter::stop() {
650     return reset();
651 }
652 
reachedEOS()653 bool WebmWriter::reachedEOS() {
654     return !mSinkThread->running();
655 }
656 } /* namespace android */
657