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