WebmWriter.cpp revision 9c876499f869af4010b8fcdca2d9f316a8a91123
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 34using namespace webm; 35 36namespace { 37size_t XiphLaceCodeLen(size_t size) { 38 return size / 0xff + 1; 39} 40 41size_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 51namespace android { 52 53static const int64_t kMinStreamableFileSizeInBytes = 5 * 1024 * 1024; 54 55WebmWriter::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 84sp<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 113sp<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 157size_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 170uint64_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 228void 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 250void WebmWriter::release() { 251 close(mFd); 252 mFd = -1; 253 mInitCheck = NO_INIT; 254 mStarted = false; 255} 256 257status_t WebmWriter::reset() { 258 if (mInitCheck != OK) { 259 return OK; 260 } else { 261 if (!mStarted) { 262 release(); 263 return OK; 264 } 265 } 266 267 status_t err = OK; 268 int64_t maxDurationUs = 0; 269 int64_t minDurationUs = 0x7fffffffffffffffLL; 270 for (int i = 0; i < kMaxStreams; ++i) { 271 if (mStreams[i].mThread == NULL) { 272 continue; 273 } 274 275 status_t status = mStreams[i].mThread->stop(); 276 if (err == OK && status != OK) { 277 err = status; 278 } 279 280 int64_t durationUs = mStreams[i].mThread->getDurationUs(); 281 if (durationUs > maxDurationUs) { 282 maxDurationUs = durationUs; 283 } 284 if (durationUs < minDurationUs) { 285 minDurationUs = durationUs; 286 } 287 } 288 289 if (numTracks() > 1) { 290 ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us", minDurationUs, maxDurationUs); 291 } 292 293 mSinkThread->stop(); 294 295 // Do not write out movie header on error. 296 if (err != OK) { 297 release(); 298 return err; 299 } 300 301 sp<WebmElement> cues = new WebmMaster(kMkvCues, mCuePoints); 302 uint64_t cuesSize = cues->totalSize(); 303 // TRICKY Even when the cues do fit in the space we reserved, if they do not fit 304 // perfectly, we still need to check if there is enough "extra space" to write an 305 // EBML void element. 306 if (cuesSize != mEstimatedCuesSize && cuesSize > mEstimatedCuesSize - kMinEbmlVoidSize) { 307 mCuesOffset = ::lseek(mFd, 0, SEEK_CUR); 308 cues->write(mFd, cuesSize); 309 } else { 310 uint64_t spaceSize; 311 ::lseek(mFd, mCuesOffset, SEEK_SET); 312 cues->write(mFd, cuesSize); 313 sp<WebmElement> space = new EbmlVoid(mEstimatedCuesSize - cuesSize); 314 space->write(mFd, spaceSize); 315 } 316 317 mCuePoints.clear(); 318 mStreams[kVideoIndex].mSink.clear(); 319 mStreams[kAudioIndex].mSink.clear(); 320 321 uint8_t bary[sizeof(uint64_t)]; 322 uint64_t totalSize = ::lseek(mFd, 0, SEEK_END); 323 uint64_t segmentSize = totalSize - mSegmentDataStart; 324 ::lseek(mFd, mSegmentOffset + sizeOf(kMkvSegment), SEEK_SET); 325 uint64_t segmentSizeCoded = encodeUnsigned(segmentSize, sizeOf(kMkvUnknownLength)); 326 serializeCodedUnsigned(segmentSizeCoded, bary); 327 ::write(mFd, bary, sizeOf(kMkvUnknownLength)); 328 329 uint64_t durationOffset = mInfoOffset + sizeOf(kMkvInfo) + sizeOf(mInfoSize) 330 + sizeOf(kMkvSegmentDuration) + sizeOf(sizeof(double)); 331 sp<WebmElement> duration = new WebmFloat( 332 kMkvSegmentDuration, 333 (double) (maxDurationUs * 1000 / mTimeCodeScale)); 334 duration->serializePayload(bary); 335 ::lseek(mFd, durationOffset, SEEK_SET); 336 ::write(mFd, bary, sizeof(double)); 337 338 List<sp<WebmElement> > seekEntries; 339 seekEntries.push_back(WebmElement::SeekEntry(kMkvInfo, mInfoOffset - mSegmentDataStart)); 340 seekEntries.push_back(WebmElement::SeekEntry(kMkvTracks, mTracksOffset - mSegmentDataStart)); 341 seekEntries.push_back(WebmElement::SeekEntry(kMkvCues, mCuesOffset - mSegmentDataStart)); 342 sp<WebmElement> seekHead = new WebmMaster(kMkvSeekHead, seekEntries); 343 344 uint64_t metaSeekSize; 345 ::lseek(mFd, mSegmentDataStart, SEEK_SET); 346 seekHead->write(mFd, metaSeekSize); 347 348 uint64_t spaceSize; 349 sp<WebmElement> space = new EbmlVoid(kMaxMetaSeekSize - metaSeekSize); 350 space->write(mFd, spaceSize); 351 352 release(); 353 return err; 354} 355 356status_t WebmWriter::addSource(const sp<IMediaSource> &source) { 357 Mutex::Autolock l(mLock); 358 if (mStarted) { 359 ALOGE("Attempt to add source AFTER recording is started"); 360 return UNKNOWN_ERROR; 361 } 362 363 // At most 2 tracks can be supported. 364 if (mStreams[kVideoIndex].mTrackEntry != NULL 365 && mStreams[kAudioIndex].mTrackEntry != NULL) { 366 ALOGE("Too many tracks (2) to add"); 367 return ERROR_UNSUPPORTED; 368 } 369 370 CHECK(source != NULL); 371 372 // A track of type other than video or audio is not supported. 373 const char *mime; 374 source->getFormat()->findCString(kKeyMIMEType, &mime); 375 const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8; 376 const char *vp9 = MEDIA_MIMETYPE_VIDEO_VP9; 377 const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS; 378 379 size_t streamIndex; 380 if (!strncasecmp(mime, vp8, strlen(vp8)) || 381 !strncasecmp(mime, vp9, strlen(vp9))) { 382 streamIndex = kVideoIndex; 383 } else if (!strncasecmp(mime, vorbis, strlen(vorbis))) { 384 streamIndex = kAudioIndex; 385 } else { 386 ALOGE("Track (%s) other than %s, %s or %s is not supported", 387 mime, vp8, vp9, vorbis); 388 return ERROR_UNSUPPORTED; 389 } 390 391 // No more than one video or one audio track is supported. 392 if (mStreams[streamIndex].mTrackEntry != NULL) { 393 ALOGE("%s track already exists", mStreams[streamIndex].mName); 394 return ERROR_UNSUPPORTED; 395 } 396 397 // This is the first track of either audio or video. 398 // Go ahead to add the track. 399 mStreams[streamIndex].mSource = source; 400 mStreams[streamIndex].mTrackEntry = mStreams[streamIndex].mMakeTrack(source->getFormat()); 401 if (mStreams[streamIndex].mTrackEntry == NULL) { 402 return BAD_VALUE; 403 } 404 405 return OK; 406} 407 408status_t WebmWriter::start(MetaData *params) { 409 if (mInitCheck != OK) { 410 return UNKNOWN_ERROR; 411 } 412 413 if (mStreams[kVideoIndex].mTrackEntry == NULL 414 && mStreams[kAudioIndex].mTrackEntry == NULL) { 415 ALOGE("No source added"); 416 return INVALID_OPERATION; 417 } 418 419 if (mMaxFileSizeLimitBytes != 0) { 420 mIsFileSizeLimitExplicitlyRequested = true; 421 } 422 423 if (params) { 424 int32_t isRealTimeRecording; 425 params->findInt32(kKeyRealTimeRecording, &isRealTimeRecording); 426 mIsRealTimeRecording = isRealTimeRecording; 427 } 428 429 if (mStarted) { 430 if (mPaused) { 431 mPaused = false; 432 mStreams[kAudioIndex].mThread->resume(); 433 mStreams[kVideoIndex].mThread->resume(); 434 } 435 return OK; 436 } 437 438 if (params) { 439 int32_t tcsl; 440 if (params->findInt32(kKeyTimeScale, &tcsl)) { 441 mTimeCodeScale = tcsl; 442 } 443 } 444 if (mTimeCodeScale == 0) { 445 ALOGE("movie time scale is 0"); 446 return BAD_VALUE; 447 } 448 ALOGV("movie time scale: %" PRIu64, mTimeCodeScale); 449 450 /* 451 * When the requested file size limit is small, the priority 452 * is to meet the file size limit requirement, rather than 453 * to make the file streamable. mStreamableFile does not tell 454 * whether the actual recorded file is streamable or not. 455 */ 456 mStreamableFile = (!mMaxFileSizeLimitBytes) 457 || (mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes); 458 459 /* 460 * Write various metadata. 461 */ 462 sp<WebmElement> ebml, segment, info, seekHead, tracks, cues; 463 ebml = WebmElement::EbmlHeader(); 464 segment = new WebmMaster(kMkvSegment); 465 seekHead = new EbmlVoid(kMaxMetaSeekSize); 466 info = WebmElement::SegmentInfo(mTimeCodeScale, 0); 467 468 List<sp<WebmElement> > children; 469 for (size_t i = 0; i < kMaxStreams; ++i) { 470 if (mStreams[i].mTrackEntry != NULL) { 471 children.push_back(mStreams[i].mTrackEntry); 472 } 473 } 474 tracks = new WebmMaster(kMkvTracks, children); 475 476 if (!mStreamableFile) { 477 cues = NULL; 478 } else { 479 int32_t bitRate = -1; 480 if (params) { 481 params->findInt32(kKeyBitRate, &bitRate); 482 } 483 mEstimatedCuesSize = estimateCuesSize(bitRate); 484 CHECK_GE(mEstimatedCuesSize, 8); 485 cues = new EbmlVoid(mEstimatedCuesSize); 486 } 487 488 sp<WebmElement> elems[] = { ebml, segment, seekHead, info, tracks, cues }; 489 size_t nElems = sizeof(elems) / sizeof(elems[0]); 490 uint64_t offsets[nElems]; 491 uint64_t sizes[nElems]; 492 for (uint32_t i = 0; i < nElems; i++) { 493 WebmElement *e = elems[i].get(); 494 if (!e) { 495 continue; 496 } 497 498 uint64_t size; 499 offsets[i] = ::lseek(mFd, 0, SEEK_CUR); 500 sizes[i] = e->mSize; 501 e->write(mFd, size); 502 } 503 504 mSegmentOffset = offsets[1]; 505 mSegmentDataStart = offsets[2]; 506 mInfoOffset = offsets[3]; 507 mInfoSize = sizes[3]; 508 mTracksOffset = offsets[4]; 509 mCuesOffset = offsets[5]; 510 511 // start threads 512 if (params) { 513 params->findInt64(kKeyTime, &mStartTimestampUs); 514 } 515 516 initStream(kAudioIndex); 517 initStream(kVideoIndex); 518 519 mStreams[kAudioIndex].mThread->start(); 520 mStreams[kVideoIndex].mThread->start(); 521 mSinkThread->start(); 522 523 mStarted = true; 524 return OK; 525} 526 527status_t WebmWriter::pause() { 528 if (mInitCheck != OK) { 529 return OK; 530 } 531 mPaused = true; 532 status_t err = OK; 533 for (int i = 0; i < kMaxStreams; ++i) { 534 if (mStreams[i].mThread == NULL) { 535 continue; 536 } 537 status_t status = mStreams[i].mThread->pause(); 538 if (status != OK) { 539 err = status; 540 } 541 } 542 return err; 543} 544 545status_t WebmWriter::stop() { 546 return reset(); 547} 548 549bool WebmWriter::reachedEOS() { 550 return !mSinkThread->running(); 551} 552} /* namespace android */ 553