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