diff options
Diffstat (limited to 'media/libstagefright/webm')
-rw-r--r-- | media/libstagefright/webm/Android.mk | 23 | ||||
-rw-r--r-- | media/libstagefright/webm/EbmlUtil.cpp | 108 | ||||
-rw-r--r-- | media/libstagefright/webm/EbmlUtil.h | 50 | ||||
-rw-r--r-- | media/libstagefright/webm/LinkedBlockingQueue.h | 79 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmConstants.h | 133 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmElement.cpp | 367 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmElement.h | 127 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmFrame.cpp | 83 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmFrame.h | 46 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmFrameThread.cpp | 399 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmFrameThread.h | 160 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmWriter.cpp | 551 | ||||
-rw-r--r-- | media/libstagefright/webm/WebmWriter.h | 130 |
13 files changed, 2256 insertions, 0 deletions
diff --git a/media/libstagefright/webm/Android.mk b/media/libstagefright/webm/Android.mk new file mode 100644 index 0000000..7081463 --- /dev/null +++ b/media/libstagefright/webm/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_CPPFLAGS += -D__STDINT_LIMITS \ + -Werror + +LOCAL_SRC_FILES:= EbmlUtil.cpp \ + WebmElement.cpp \ + WebmFrame.cpp \ + WebmFrameThread.cpp \ + WebmWriter.cpp + + +LOCAL_C_INCLUDES += $(TOP)/frameworks/av/include + +LOCAL_SHARED_LIBRARIES += libstagefright_foundation \ + libstagefright \ + libutils \ + liblog + +LOCAL_MODULE:= libstagefright_webm + +include $(BUILD_STATIC_LIBRARY) diff --git a/media/libstagefright/webm/EbmlUtil.cpp b/media/libstagefright/webm/EbmlUtil.cpp new file mode 100644 index 0000000..449fec6 --- /dev/null +++ b/media/libstagefright/webm/EbmlUtil.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> + +namespace { + +// Table for Seal's algorithm for Number of Trailing Zeros. Hacker's Delight +// online, Figure 5-18 (http://www.hackersdelight.org/revisions.pdf) +// The entries whose value is -1 are never referenced. +int NTZ_TABLE[] = { + 32, 0, 1, 12, 2, 6, -1, 13, 3, -1, 7, -1, -1, -1, -1, 14, + 10, 4, -1, -1, 8, -1, -1, 25, -1, -1, -1, -1, -1, 21, 27, 15, + 31, 11, 5, -1, -1, -1, -1, -1, 9, -1, -1, 24, -1, -1, 20, 26, + 30, -1, -1, -1, -1, 23, -1, 19, 29, -1, 22, 18, 28, 17, 16, -1 +}; + +int numberOfTrailingZeros32(int32_t i) { + uint32_t u = (i & -i) * 0x0450FBAF; + return NTZ_TABLE[(u) >> 26]; +} + +uint64_t highestOneBit(uint64_t n) { + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + n |= (n >> 32); + return n - (n >> 1); +} + +uint64_t _powerOf2(uint64_t u) { + uint64_t powerOf2 = highestOneBit(u); + return powerOf2 ? powerOf2 : 1; +} + +// Based on Long.numberOfTrailingZeros in Long.java +int numberOfTrailingZeros(uint64_t u) { + int32_t low = u; + return low !=0 ? numberOfTrailingZeros32(low) + : 32 + numberOfTrailingZeros32((int32_t) (u >> 32)); +} +} + +namespace webm { + +// Encode the id and/or size of an EBML element bytes by setting a leading length descriptor bit: +// +// 1xxxxxxx - 1-byte values +// 01xxxxxx xxxxxxxx - +// 001xxxxx xxxxxxxx xxxxxxxx - +// 0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx - ... +// 00001xxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 000001xx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 0000001x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 00000001 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - 8-byte values +// +// This function uses the least the number of bytes possible. +uint64_t encodeUnsigned(uint64_t u) { + uint64_t powerOf2 = _powerOf2(u); + if (u + 1 == powerOf2 << 1) + powerOf2 <<= 1; + int shiftWidth = (7 + numberOfTrailingZeros(powerOf2)) / 7 * 7; + long lengthDescriptor = 1 << shiftWidth; + return lengthDescriptor | u; +} + +// Like above but pads the input value with leading zeros up to the specified width. The length +// descriptor is calculated based on width. +uint64_t encodeUnsigned(uint64_t u, int width) { + int shiftWidth = 7 * width; + uint64_t lengthDescriptor = 1; + lengthDescriptor <<= shiftWidth; + return lengthDescriptor | u; +} + +// Calculate the length of an EBML coded id or size from its length descriptor. +int sizeOf(uint64_t u) { + uint64_t powerOf2 = _powerOf2(u); + int unsignedLength = numberOfTrailingZeros(powerOf2) / 8 + 1; + return unsignedLength; +} + +// Serialize an EBML coded id or size in big-endian order. +int serializeCodedUnsigned(uint64_t u, uint8_t* bary) { + int unsignedLength = sizeOf(u); + for (int i = unsignedLength - 1; i >= 0; i--) { + bary[i] = u & 0xff; + u >>= 8; + } + return unsignedLength; +} + +} diff --git a/media/libstagefright/webm/EbmlUtil.h b/media/libstagefright/webm/EbmlUtil.h new file mode 100644 index 0000000..eb9c37c --- /dev/null +++ b/media/libstagefright/webm/EbmlUtil.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EBMLUTIL_H_ +#define EBMLUTIL_H_ + +#include <stdint.h> + +namespace webm { + +// Encode the id and/or size of an EBML element bytes by setting a leading length descriptor bit: +// +// 1xxxxxxx - 1-byte values +// 01xxxxxx xxxxxxxx - +// 001xxxxx xxxxxxxx xxxxxxxx - +// 0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx - ... +// 00001xxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 000001xx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 0000001x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - +// 00000001 xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx - 8-byte values +// +// This function uses the least the number of bytes possible. +uint64_t encodeUnsigned(uint64_t u); + +// Like above but pads the input value with leading zeros up to the specified width. The length +// descriptor is calculated based on width. +uint64_t encodeUnsigned(uint64_t u, int width); + +// Serialize an EBML coded id or size in big-endian order. +int serializeCodedUnsigned(uint64_t u, uint8_t* bary); + +// Calculate the length of an EBML coded id or size from its length descriptor. +int sizeOf(uint64_t u); + +} + +#endif /* EBMLUTIL_H_ */ diff --git a/media/libstagefright/webm/LinkedBlockingQueue.h b/media/libstagefright/webm/LinkedBlockingQueue.h new file mode 100644 index 0000000..0b6a9a1 --- /dev/null +++ b/media/libstagefright/webm/LinkedBlockingQueue.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LINKEDBLOCKINGQUEUE_H_ +#define LINKEDBLOCKINGQUEUE_H_ + +#include <utils/List.h> +#include <utils/Mutex.h> +#include <utils/Condition.h> + +namespace android { + +template<typename T> +class LinkedBlockingQueue { + List<T> mList; + Mutex mLock; + Condition mContentAvailableCondition; + + T front(bool remove) { + Mutex::Autolock autolock(mLock); + while (mList.empty()) { + mContentAvailableCondition.wait(mLock); + } + T e = *(mList.begin()); + if (remove) { + mList.erase(mList.begin()); + } + return e; + } + + DISALLOW_EVIL_CONSTRUCTORS(LinkedBlockingQueue); + +public: + LinkedBlockingQueue() { + } + + ~LinkedBlockingQueue() { + } + + bool empty() { + Mutex::Autolock autolock(mLock); + return mList.empty(); + } + + void clear() { + Mutex::Autolock autolock(mLock); + mList.clear(); + } + + T peek() { + return front(false); + } + + T take() { + return front(true); + } + + void push(T e) { + Mutex::Autolock autolock(mLock); + mList.push_back(e); + mContentAvailableCondition.signal(); + } +}; + +} /* namespace android */ +#endif /* LINKEDBLOCKINGQUEUE_H_ */ diff --git a/media/libstagefright/webm/WebmConstants.h b/media/libstagefright/webm/WebmConstants.h new file mode 100644 index 0000000..c53f458 --- /dev/null +++ b/media/libstagefright/webm/WebmConstants.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBMCONSTANTS_H_ +#define WEBMCONSTANTS_H_ + +#include <stdint.h> + +namespace webm { + +const int kMinEbmlVoidSize = 2; +const int64_t kMaxMetaSeekSize = 64; +const int64_t kMkvUnknownLength = 0x01ffffffffffffffl; + +// EBML element id's from http://matroska.org/technical/specs/index.html +enum Mkv { + kMkvEbml = 0x1A45DFA3, + kMkvEbmlVersion = 0x4286, + kMkvEbmlReadVersion = 0x42F7, + kMkvEbmlMaxIdlength = 0x42F2, + kMkvEbmlMaxSizeLength = 0x42F3, + kMkvDocType = 0x4282, + kMkvDocTypeVersion = 0x4287, + kMkvDocTypeReadVersion = 0x4285, + kMkvVoid = 0xEC, + kMkvSignatureSlot = 0x1B538667, + kMkvSignatureAlgo = 0x7E8A, + kMkvSignatureHash = 0x7E9A, + kMkvSignaturePublicKey = 0x7EA5, + kMkvSignature = 0x7EB5, + kMkvSignatureElements = 0x7E5B, + kMkvSignatureElementList = 0x7E7B, + kMkvSignedElement = 0x6532, + kMkvSegment = 0x18538067, + kMkvSeekHead = 0x114D9B74, + kMkvSeek = 0x4DBB, + kMkvSeekId = 0x53AB, + kMkvSeekPosition = 0x53AC, + kMkvInfo = 0x1549A966, + kMkvTimecodeScale = 0x2AD7B1, + kMkvSegmentDuration = 0x4489, + kMkvDateUtc = 0x4461, + kMkvMuxingApp = 0x4D80, + kMkvWritingApp = 0x5741, + kMkvCluster = 0x1F43B675, + kMkvTimecode = 0xE7, + kMkvPrevSize = 0xAB, + kMkvBlockGroup = 0xA0, + kMkvBlock = 0xA1, + kMkvBlockAdditions = 0x75A1, + kMkvBlockMore = 0xA6, + kMkvBlockAddId = 0xEE, + kMkvBlockAdditional = 0xA5, + kMkvBlockDuration = 0x9B, + kMkvReferenceBlock = 0xFB, + kMkvLaceNumber = 0xCC, + kMkvSimpleBlock = 0xA3, + kMkvTracks = 0x1654AE6B, + kMkvTrackEntry = 0xAE, + kMkvTrackNumber = 0xD7, + kMkvTrackUid = 0x73C5, + kMkvTrackType = 0x83, + kMkvFlagEnabled = 0xB9, + kMkvFlagDefault = 0x88, + kMkvFlagForced = 0x55AA, + kMkvFlagLacing = 0x9C, + kMkvDefaultDuration = 0x23E383, + kMkvMaxBlockAdditionId = 0x55EE, + kMkvName = 0x536E, + kMkvLanguage = 0x22B59C, + kMkvCodecId = 0x86, + kMkvCodecPrivate = 0x63A2, + kMkvCodecName = 0x258688, + kMkvVideo = 0xE0, + kMkvFlagInterlaced = 0x9A, + kMkvStereoMode = 0x53B8, + kMkvAlphaMode = 0x53C0, + kMkvPixelWidth = 0xB0, + kMkvPixelHeight = 0xBA, + kMkvPixelCropBottom = 0x54AA, + kMkvPixelCropTop = 0x54BB, + kMkvPixelCropLeft = 0x54CC, + kMkvPixelCropRight = 0x54DD, + kMkvDisplayWidth = 0x54B0, + kMkvDisplayHeight = 0x54BA, + kMkvDisplayUnit = 0x54B2, + kMkvAspectRatioType = 0x54B3, + kMkvFrameRate = 0x2383E3, + kMkvAudio = 0xE1, + kMkvSamplingFrequency = 0xB5, + kMkvOutputSamplingFrequency = 0x78B5, + kMkvChannels = 0x9F, + kMkvBitDepth = 0x6264, + kMkvCues = 0x1C53BB6B, + kMkvCuePoint = 0xBB, + kMkvCueTime = 0xB3, + kMkvCueTrackPositions = 0xB7, + kMkvCueTrack = 0xF7, + kMkvCueClusterPosition = 0xF1, + kMkvCueBlockNumber = 0x5378 +}; + +enum TrackTypes { + kInvalidType = -1, + kVideoType = 0x1, + kAudioType = 0x2, + kComplexType = 0x3, + kLogoType = 0x10, + kSubtitleType = 0x11, + kButtonsType = 0x12, + kControlType = 0x20 +}; + +enum TrackNum { + kVideoTrackNum = 0x1, + kAudioTrackNum = 0x2 +}; +} + +#endif /* WEBMCONSTANTS_H_ */ diff --git a/media/libstagefright/webm/WebmElement.cpp b/media/libstagefright/webm/WebmElement.cpp new file mode 100644 index 0000000..a008cab --- /dev/null +++ b/media/libstagefright/webm/WebmElement.cpp @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#define LOG_TAG "WebmElement" + +#include "EbmlUtil.h" +#include "WebmElement.h" +#include "WebmConstants.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <utils/Log.h> + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> + +using namespace android; +using namespace webm; + +namespace { + +int64_t voidSize(int64_t totalSize) { + if (totalSize < 2) { + return -1; + } + if (totalSize < 9) { + return totalSize - 2; + } + return totalSize - 9; +} + +uint64_t childrenSum(const List<sp<WebmElement> >& children) { + uint64_t total = 0; + for (List<sp<WebmElement> >::const_iterator it = children.begin(); + it != children.end(); ++it) { + total += (*it)->totalSize(); + } + return total; +} + +void populateCommonTrackEntries( + int num, + uint64_t uid, + bool lacing, + const char *lang, + const char *codec, + TrackTypes type, + List<sp<WebmElement> > &ls) { + ls.push_back(new WebmUnsigned(kMkvTrackNumber, num)); + ls.push_back(new WebmUnsigned(kMkvTrackUid, uid)); + ls.push_back(new WebmUnsigned(kMkvFlagLacing, lacing)); + ls.push_back(new WebmString(kMkvLanguage, lang)); + ls.push_back(new WebmString(kMkvCodecId, codec)); + ls.push_back(new WebmUnsigned(kMkvTrackType, type)); +} +} + +namespace android { + +WebmElement::WebmElement(uint64_t id, uint64_t size) + : mId(id), mSize(size) { +} + +WebmElement::~WebmElement() { +} + +int WebmElement::serializePayloadSize(uint8_t *buf) { + return serializeCodedUnsigned(encodeUnsigned(mSize), buf); +} + +uint64_t WebmElement::serializeInto(uint8_t *buf) { + uint8_t *cur = buf; + int head = serializeCodedUnsigned(mId, cur); + cur += head; + int neck = serializePayloadSize(cur); + cur += neck; + serializePayload(cur); + cur += mSize; + return cur - buf; +} + +uint64_t WebmElement::totalSize() { + uint8_t buf[8]; + //............... + sizeOf(encodeUnsigned(size)) + return sizeOf(mId) + serializePayloadSize(buf) + mSize; +} + +uint8_t *WebmElement::serialize(uint64_t& size) { + size = totalSize(); + uint8_t *buf = new uint8_t[size]; + serializeInto(buf); + return buf; +} + +int WebmElement::write(int fd, uint64_t& size) { + uint8_t buf[8]; + size = totalSize(); + off64_t off = ::lseek64(fd, (size - 1), SEEK_CUR) - (size - 1); + ::write(fd, buf, 1); // extend file + + off64_t curOff = off + size; + off64_t alignedOff = off & ~(::sysconf(_SC_PAGE_SIZE) - 1); + off64_t mapSize = curOff - alignedOff; + off64_t pageOff = off - alignedOff; + void *dst = ::mmap64(NULL, mapSize, PROT_WRITE, MAP_SHARED, fd, alignedOff); + if (dst == MAP_FAILED) { + ALOGE("mmap64 failed; errno = %d", errno); + ALOGE("fd %d; flags: %o", fd, ::fcntl(fd, F_GETFL, 0)); + return errno; + } else { + serializeInto((uint8_t*) dst + pageOff); + ::msync(dst, mapSize, MS_SYNC); + return ::munmap(dst, mapSize); + } +} + +//================================================================================================= + +WebmUnsigned::WebmUnsigned(uint64_t id, uint64_t value) + : WebmElement(id, sizeOf(value)), mValue(value) { +} + +void WebmUnsigned::serializePayload(uint8_t *buf) { + serializeCodedUnsigned(mValue, buf); +} + +//================================================================================================= + +WebmFloat::WebmFloat(uint64_t id, double value) + : WebmElement(id, sizeof(double)), mValue(value) { +} + +WebmFloat::WebmFloat(uint64_t id, float value) + : WebmElement(id, sizeof(float)), mValue(value) { +} + +void WebmFloat::serializePayload(uint8_t *buf) { + uint64_t data; + if (mSize == sizeof(float)) { + float f = mValue; + data = *reinterpret_cast<const uint32_t*>(&f); + } else { + data = *reinterpret_cast<const uint64_t*>(&mValue); + } + for (int i = mSize - 1; i >= 0; --i) { + buf[i] = data & 0xff; + data >>= 8; + } +} + +//================================================================================================= + +WebmBinary::WebmBinary(uint64_t id, const sp<ABuffer> &ref) + : WebmElement(id, ref->size()), mRef(ref) { +} + +void WebmBinary::serializePayload(uint8_t *buf) { + memcpy(buf, mRef->data(), mRef->size()); +} + +//================================================================================================= + +WebmString::WebmString(uint64_t id, const char *str) + : WebmElement(id, strlen(str)), mStr(str) { +} + +void WebmString::serializePayload(uint8_t *buf) { + memcpy(buf, mStr, strlen(mStr)); +} + +//================================================================================================= + +WebmSimpleBlock::WebmSimpleBlock( + int trackNum, + int16_t relTimecode, + bool key, + const sp<ABuffer>& orig) + // ............................ trackNum*1 + timecode*2 + flags*1 + // ^^^ + // Only the least significant byte of trackNum is encoded + : WebmElement(kMkvSimpleBlock, orig->size() + 4), + mTrackNum(trackNum), + mRelTimecode(relTimecode), + mKey(key), + mRef(orig) { +} + +void WebmSimpleBlock::serializePayload(uint8_t *buf) { + serializeCodedUnsigned(encodeUnsigned(mTrackNum), buf); + buf[1] = (mRelTimecode & 0xff00) >> 8; + buf[2] = mRelTimecode & 0xff; + buf[3] = mKey ? 0x80 : 0; + memcpy(buf + 4, mRef->data(), mSize - 4); +} + +//================================================================================================= + +EbmlVoid::EbmlVoid(uint64_t totalSize) + : WebmElement(kMkvVoid, voidSize(totalSize)), + mSizeWidth(totalSize - sizeOf(kMkvVoid) - voidSize(totalSize)) { + CHECK_GE(voidSize(totalSize), 0); +} + +int EbmlVoid::serializePayloadSize(uint8_t *buf) { + return serializeCodedUnsigned(encodeUnsigned(mSize, mSizeWidth), buf); +} + +void EbmlVoid::serializePayload(uint8_t *buf) { + ::memset(buf, 0, mSize); + return; +} + +//================================================================================================= + +WebmMaster::WebmMaster(uint64_t id, const List<sp<WebmElement> >& children) + : WebmElement(id, childrenSum(children)), mChildren(children) { +} + +WebmMaster::WebmMaster(uint64_t id) + : WebmElement(id, 0) { +} + +int WebmMaster::serializePayloadSize(uint8_t *buf) { + if (mSize == 0){ + return serializeCodedUnsigned(kMkvUnknownLength, buf); + } + return WebmElement::serializePayloadSize(buf); +} + +void WebmMaster::serializePayload(uint8_t *buf) { + uint64_t off = 0; + for (List<sp<WebmElement> >::const_iterator it = mChildren.begin(); it != mChildren.end(); + ++it) { + sp<WebmElement> child = (*it); + child->serializeInto(buf + off); + off += child->totalSize(); + } +} + +//================================================================================================= + +sp<WebmElement> WebmElement::CuePointEntry(uint64_t time, int track, uint64_t off) { + List<sp<WebmElement> > cuePointEntryFields; + cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueTrack, track)); + cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueClusterPosition, off)); + WebmElement *cueTrackPositions = new WebmMaster(kMkvCueTrackPositions, cuePointEntryFields); + + cuePointEntryFields.clear(); + cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueTime, time)); + cuePointEntryFields.push_back(cueTrackPositions); + return new WebmMaster(kMkvCuePoint, cuePointEntryFields); +} + +sp<WebmElement> WebmElement::SeekEntry(uint64_t id, uint64_t off) { + List<sp<WebmElement> > seekEntryFields; + seekEntryFields.push_back(new WebmUnsigned(kMkvSeekId, id)); + seekEntryFields.push_back(new WebmUnsigned(kMkvSeekPosition, off)); + return new WebmMaster(kMkvSeek, seekEntryFields); +} + +sp<WebmElement> WebmElement::EbmlHeader( + int ver, + int readVer, + int maxIdLen, + int maxSizeLen, + int docVer, + int docReadVer) { + List<sp<WebmElement> > headerFields; + headerFields.push_back(new WebmUnsigned(kMkvEbmlVersion, ver)); + headerFields.push_back(new WebmUnsigned(kMkvEbmlReadVersion, readVer)); + headerFields.push_back(new WebmUnsigned(kMkvEbmlMaxIdlength, maxIdLen)); + headerFields.push_back(new WebmUnsigned(kMkvEbmlMaxSizeLength, maxSizeLen)); + headerFields.push_back(new WebmString(kMkvDocType, "webm")); + headerFields.push_back(new WebmUnsigned(kMkvDocTypeVersion, docVer)); + headerFields.push_back(new WebmUnsigned(kMkvDocTypeReadVersion, docReadVer)); + return new WebmMaster(kMkvEbml, headerFields); +} + +sp<WebmElement> WebmElement::SegmentInfo(uint64_t scale, double dur) { + List<sp<WebmElement> > segmentInfo; + // place duration first; easier to patch + segmentInfo.push_back(new WebmFloat(kMkvSegmentDuration, dur)); + segmentInfo.push_back(new WebmUnsigned(kMkvTimecodeScale, scale)); + segmentInfo.push_back(new WebmString(kMkvMuxingApp, "android")); + segmentInfo.push_back(new WebmString(kMkvWritingApp, "android")); + return new WebmMaster(kMkvInfo, segmentInfo); +} + +sp<WebmElement> WebmElement::AudioTrackEntry( + int chans, + double rate, + const sp<ABuffer> &buf, + int bps, + uint64_t uid, + bool lacing, + const char *lang) { + if (uid == 0) { + uid = kAudioTrackNum; + } + + List<sp<WebmElement> > trackEntryFields; + populateCommonTrackEntries( + kAudioTrackNum, + uid, + lacing, + lang, + "A_VORBIS", + kAudioType, + trackEntryFields); + + List<sp<WebmElement> > audioInfo; + audioInfo.push_back(new WebmUnsigned(kMkvChannels, chans)); + audioInfo.push_back(new WebmFloat(kMkvSamplingFrequency, rate)); + if (bps) { + WebmElement *bitDepth = new WebmUnsigned(kMkvBitDepth, bps); + audioInfo.push_back(bitDepth); + } + + trackEntryFields.push_back(new WebmMaster(kMkvAudio, audioInfo)); + trackEntryFields.push_back(new WebmBinary(kMkvCodecPrivate, buf)); + return new WebmMaster(kMkvTrackEntry, trackEntryFields); +} + +sp<WebmElement> WebmElement::VideoTrackEntry( + uint64_t width, + uint64_t height, + uint64_t uid, + bool lacing, + const char *lang) { + if (uid == 0) { + uid = kVideoTrackNum; + } + + List<sp<WebmElement> > trackEntryFields; + populateCommonTrackEntries( + kVideoTrackNum, + uid, + lacing, + lang, + "V_VP8", + kVideoType, + trackEntryFields); + + List<sp<WebmElement> > videoInfo; + videoInfo.push_back(new WebmUnsigned(kMkvPixelWidth, width)); + videoInfo.push_back(new WebmUnsigned(kMkvPixelHeight, height)); + + trackEntryFields.push_back(new WebmMaster(kMkvVideo, videoInfo)); + return new WebmMaster(kMkvTrackEntry, trackEntryFields); +} +} /* namespace android */ diff --git a/media/libstagefright/webm/WebmElement.h b/media/libstagefright/webm/WebmElement.h new file mode 100644 index 0000000..f19933e --- /dev/null +++ b/media/libstagefright/webm/WebmElement.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBMELEMENT_H_ +#define WEBMELEMENT_H_ + +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <utils/List.h> + +namespace android { + +struct WebmElement : public LightRefBase<WebmElement> { + const uint64_t mId, mSize; + + WebmElement(uint64_t id, uint64_t size); + virtual ~WebmElement(); + + virtual int serializePayloadSize(uint8_t *buf); + virtual void serializePayload(uint8_t *buf)=0; + uint64_t totalSize(); + uint64_t serializeInto(uint8_t *buf); + uint8_t *serialize(uint64_t& size); + int write(int fd, uint64_t& size); + + static sp<WebmElement> EbmlHeader( + int ver = 1, + int readVer = 1, + int maxIdLen = 4, + int maxSizeLen = 8, + int docVer = 2, + int docReadVer = 2); + + static sp<WebmElement> SegmentInfo(uint64_t scale = 1000000, double dur = 0); + + static sp<WebmElement> AudioTrackEntry( + int chans, + double rate, + const sp<ABuffer> &buf, + int bps = 0, + uint64_t uid = 0, + bool lacing = false, + const char *lang = "und"); + + static sp<WebmElement> VideoTrackEntry( + uint64_t width, + uint64_t height, + uint64_t uid = 0, + bool lacing = false, + const char *lang = "und"); + + static sp<WebmElement> SeekEntry(uint64_t id, uint64_t off); + static sp<WebmElement> CuePointEntry(uint64_t time, int track, uint64_t off); + static sp<WebmElement> SimpleBlock( + int trackNum, + int16_t timecode, + bool key, + const uint8_t *data, + uint64_t dataSize); +}; + +struct WebmUnsigned : public WebmElement { + WebmUnsigned(uint64_t id, uint64_t value); + const uint64_t mValue; + void serializePayload(uint8_t *buf); +}; + +struct WebmFloat : public WebmElement { + const double mValue; + WebmFloat(uint64_t id, float value); + WebmFloat(uint64_t id, double value); + void serializePayload(uint8_t *buf); +}; + +struct WebmBinary : public WebmElement { + const sp<ABuffer> mRef; + WebmBinary(uint64_t id, const sp<ABuffer> &ref); + void serializePayload(uint8_t *buf); +}; + +struct WebmString : public WebmElement { + const char *const mStr; + WebmString(uint64_t id, const char *str); + void serializePayload(uint8_t *buf); +}; + +struct WebmSimpleBlock : public WebmElement { + const int mTrackNum; + const int16_t mRelTimecode; + const bool mKey; + const sp<ABuffer> mRef; + + WebmSimpleBlock(int trackNum, int16_t timecode, bool key, const sp<ABuffer>& orig); + void serializePayload(uint8_t *buf); +}; + +struct EbmlVoid : public WebmElement { + const uint64_t mSizeWidth; + EbmlVoid(uint64_t totalSize); + int serializePayloadSize(uint8_t *buf); + void serializePayload(uint8_t *buf); +}; + +struct WebmMaster : public WebmElement { + const List<sp<WebmElement> > mChildren; + WebmMaster(uint64_t id); + WebmMaster(uint64_t id, const List<sp<WebmElement> > &children); + int serializePayloadSize(uint8_t *buf); + void serializePayload(uint8_t *buf); +}; + +} /* namespace android */ +#endif /* WEBMELEMENT_H_ */ diff --git a/media/libstagefright/webm/WebmFrame.cpp b/media/libstagefright/webm/WebmFrame.cpp new file mode 100644 index 0000000..e5134ed --- /dev/null +++ b/media/libstagefright/webm/WebmFrame.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "WebmFrame" + +#include "WebmFrame.h" +#include "WebmConstants.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <unistd.h> + +using namespace android; +using namespace webm; + +namespace { +sp<ABuffer> toABuffer(MediaBuffer *mbuf) { + sp<ABuffer> abuf = new ABuffer(mbuf->range_length()); + memcpy(abuf->data(), (uint8_t*) mbuf->data() + mbuf->range_offset(), mbuf->range_length()); + return abuf; +} +} + +namespace android { + +const sp<WebmFrame> WebmFrame::EOS = new WebmFrame(); + +WebmFrame::WebmFrame() + : mType(kInvalidType), + mKey(false), + mAbsTimecode(UINT64_MAX), + mData(new ABuffer(0)), + mEos(true) { +} + +WebmFrame::WebmFrame(int type, bool key, uint64_t absTimecode, MediaBuffer *mbuf) + : mType(type), + mKey(key), + mAbsTimecode(absTimecode), + mData(toABuffer(mbuf)), + mEos(false) { +} + +sp<WebmElement> WebmFrame::SimpleBlock(uint64_t baseTimecode) const { + return new WebmSimpleBlock( + mType == kVideoType ? kVideoTrackNum : kAudioTrackNum, + mAbsTimecode - baseTimecode, + mKey, + mData); +} + +bool WebmFrame::operator<(const WebmFrame &other) const { + if (this->mEos) { + return false; + } + if (other.mEos) { + return true; + } + if (this->mAbsTimecode == other.mAbsTimecode) { + if (this->mType == kAudioType && other.mType == kVideoType) { + return true; + } + if (this->mType == kVideoType && other.mType == kAudioType) { + return false; + } + return false; + } + return this->mAbsTimecode < other.mAbsTimecode; +} +} /* namespace android */ diff --git a/media/libstagefright/webm/WebmFrame.h b/media/libstagefright/webm/WebmFrame.h new file mode 100644 index 0000000..4f0b055 --- /dev/null +++ b/media/libstagefright/webm/WebmFrame.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBMFRAME_H_ +#define WEBMFRAME_H_ + +#include "WebmElement.h" + +namespace android { + +struct WebmFrame : LightRefBase<WebmFrame> { +public: + const int mType; + const bool mKey; + const uint64_t mAbsTimecode; + const sp<ABuffer> mData; + const bool mEos; + + WebmFrame(); + WebmFrame(int type, bool key, uint64_t absTimecode, MediaBuffer *buf); + ~WebmFrame() {} + + sp<WebmElement> SimpleBlock(uint64_t baseTimecode) const; + + bool operator<(const WebmFrame &other) const; + + static const sp<WebmFrame> EOS; +private: + DISALLOW_EVIL_CONSTRUCTORS(WebmFrame); +}; + +} /* namespace android */ +#endif /* WEBMFRAME_H_ */ diff --git a/media/libstagefright/webm/WebmFrameThread.cpp b/media/libstagefright/webm/WebmFrameThread.cpp new file mode 100644 index 0000000..a4b8a42 --- /dev/null +++ b/media/libstagefright/webm/WebmFrameThread.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "WebmFrameThread" + +#include "WebmConstants.h" +#include "WebmFrameThread.h" + +#include <media/stagefright/MetaData.h> +#include <media/stagefright/foundation/ADebug.h> + +#include <utils/Log.h> +#include <inttypes.h> + +using namespace webm; + +namespace android { + +void *WebmFrameThread::wrap(void *arg) { + WebmFrameThread *worker = reinterpret_cast<WebmFrameThread*>(arg); + worker->run(); + return NULL; +} + +status_t WebmFrameThread::start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&mThread, &attr, WebmFrameThread::wrap, this); + pthread_attr_destroy(&attr); + return OK; +} + +status_t WebmFrameThread::stop() { + void *status; + pthread_join(mThread, &status); + return (status_t)(intptr_t)status; +} + +//================================================================================================= + +WebmFrameSourceThread::WebmFrameSourceThread( + int type, + LinkedBlockingQueue<const sp<WebmFrame> >& sink) + : mType(type), mSink(sink) { +} + +//================================================================================================= + +WebmFrameSinkThread::WebmFrameSinkThread( + const int& fd, + const uint64_t& off, + sp<WebmFrameSourceThread> videoThread, + sp<WebmFrameSourceThread> audioThread, + List<sp<WebmElement> >& cues) + : mFd(fd), + mSegmentDataStart(off), + mVideoFrames(videoThread->mSink), + mAudioFrames(audioThread->mSink), + mCues(cues), + mDone(true) { +} + +WebmFrameSinkThread::WebmFrameSinkThread( + const int& fd, + const uint64_t& off, + LinkedBlockingQueue<const sp<WebmFrame> >& videoSource, + LinkedBlockingQueue<const sp<WebmFrame> >& audioSource, + List<sp<WebmElement> >& cues) + : mFd(fd), + mSegmentDataStart(off), + mVideoFrames(videoSource), + mAudioFrames(audioSource), + mCues(cues), + mDone(true) { +} + +// Initializes a webm cluster with its starting timecode. +// +// frames: +// sequence of input audio/video frames received from the source. +// +// clusterTimecodeL: +// the starting timecode of the cluster; this is the timecode of the first +// frame since frames are ordered by timestamp. +// +// children: +// list to hold child elements in a webm cluster (start timecode and +// simple blocks). +// +// static +void WebmFrameSinkThread::initCluster( + List<const sp<WebmFrame> >& frames, + uint64_t& clusterTimecodeL, + List<sp<WebmElement> >& children) { + CHECK(!frames.empty() && children.empty()); + + const sp<WebmFrame> f = *(frames.begin()); + clusterTimecodeL = f->mAbsTimecode; + WebmUnsigned *clusterTimecode = new WebmUnsigned(kMkvTimecode, clusterTimecodeL); + children.clear(); + children.push_back(clusterTimecode); +} + +void WebmFrameSinkThread::writeCluster(List<sp<WebmElement> >& children) { + // children must contain at least one simpleblock and its timecode + CHECK_GE(children.size(), 2); + + uint64_t size; + sp<WebmElement> cluster = new WebmMaster(kMkvCluster, children); + cluster->write(mFd, size); + children.clear(); +} + +// Write out (possibly multiple) webm cluster(s) from frames split on video key frames. +// +// last: +// current flush is triggered by EOS instead of a second outstanding video key frame. +void WebmFrameSinkThread::flushFrames(List<const sp<WebmFrame> >& frames, bool last) { + if (frames.empty()) { + return; + } + + uint64_t clusterTimecodeL; + List<sp<WebmElement> > children; + initCluster(frames, clusterTimecodeL, children); + + uint64_t cueTime = clusterTimecodeL; + off_t fpos = ::lseek(mFd, 0, SEEK_CUR); + size_t n = frames.size(); + if (!last) { + // If we are not flushing the last sequence of outstanding frames, flushFrames + // must have been called right after we have pushed a second outstanding video key + // frame (the last frame), which belongs to the next cluster; also hold back on + // flushing the second to last frame before we check its type. A audio frame + // should precede the aforementioned video key frame in the next sequence, a video + // frame should be the last frame in the current (to-be-flushed) sequence. + CHECK_GE(n, 2); + n -= 2; + } + + for (size_t i = 0; i < n; i++) { + const sp<WebmFrame> f = *(frames.begin()); + if (f->mType == kVideoType && f->mKey) { + cueTime = f->mAbsTimecode; + } + + if (f->mAbsTimecode - clusterTimecodeL > INT16_MAX) { + writeCluster(children); + initCluster(frames, clusterTimecodeL, children); + } + + frames.erase(frames.begin()); + children.push_back(f->SimpleBlock(clusterTimecodeL)); + } + + // equivalent to last==false + if (!frames.empty()) { + // decide whether to write out the second to last frame. + const sp<WebmFrame> secondLastFrame = *(frames.begin()); + if (secondLastFrame->mType == kVideoType) { + frames.erase(frames.begin()); + children.push_back(secondLastFrame->SimpleBlock(clusterTimecodeL)); + } + } + + writeCluster(children); + sp<WebmElement> cuePoint = WebmElement::CuePointEntry(cueTime, 1, fpos - mSegmentDataStart); + mCues.push_back(cuePoint); +} + +status_t WebmFrameSinkThread::start() { + mDone = false; + return WebmFrameThread::start(); +} + +status_t WebmFrameSinkThread::stop() { + mDone = true; + mVideoFrames.push(WebmFrame::EOS); + mAudioFrames.push(WebmFrame::EOS); + return WebmFrameThread::stop(); +} + +void WebmFrameSinkThread::run() { + int numVideoKeyFrames = 0; + List<const sp<WebmFrame> > outstandingFrames; + while (!mDone) { + ALOGV("wait v frame"); + const sp<WebmFrame> videoFrame = mVideoFrames.peek(); + ALOGV("v frame: %p", videoFrame.get()); + + ALOGV("wait a frame"); + const sp<WebmFrame> audioFrame = mAudioFrames.peek(); + ALOGV("a frame: %p", audioFrame.get()); + + if (videoFrame->mEos && audioFrame->mEos) { + break; + } + + if (*audioFrame < *videoFrame) { + ALOGV("take a frame"); + mAudioFrames.take(); + outstandingFrames.push_back(audioFrame); + } else { + ALOGV("take v frame"); + mVideoFrames.take(); + outstandingFrames.push_back(videoFrame); + if (videoFrame->mKey) + numVideoKeyFrames++; + } + + if (numVideoKeyFrames == 2) { + flushFrames(outstandingFrames, /* last = */ false); + numVideoKeyFrames--; + } + } + ALOGV("flushing last cluster (size %zu)", outstandingFrames.size()); + flushFrames(outstandingFrames, /* last = */ true); + mDone = true; +} + +//================================================================================================= + +static const int64_t kInitialDelayTimeUs = 700000LL; + +void WebmFrameMediaSourceThread::clearFlags() { + mDone = false; + mPaused = false; + mResumed = false; + mStarted = false; + mReachedEOS = false; +} + +WebmFrameMediaSourceThread::WebmFrameMediaSourceThread( + const sp<MediaSource>& source, + int type, + LinkedBlockingQueue<const sp<WebmFrame> >& sink, + uint64_t timeCodeScale, + int64_t startTimeRealUs, + int32_t startTimeOffsetMs, + int numTracks, + bool realTimeRecording) + : WebmFrameSourceThread(type, sink), + mSource(source), + mTimeCodeScale(timeCodeScale), + mTrackDurationUs(0) { + clearFlags(); + mStartTimeUs = startTimeRealUs; + if (realTimeRecording && numTracks > 1) { + /* + * Copied from MPEG4Writer + * + * This extra delay of accepting incoming audio/video signals + * helps to align a/v start time at the beginning of a recording + * session, and it also helps eliminate the "recording" sound for + * camcorder applications. + * + * If client does not set the start time offset, we fall back to + * use the default initial delay value. + */ + int64_t startTimeOffsetUs = startTimeOffsetMs * 1000LL; + if (startTimeOffsetUs < 0) { // Start time offset was not set + startTimeOffsetUs = kInitialDelayTimeUs; + } + mStartTimeUs += startTimeOffsetUs; + ALOGI("Start time offset: %" PRId64 " us", startTimeOffsetUs); + } +} + +status_t WebmFrameMediaSourceThread::start() { + sp<MetaData> meta = new MetaData; + meta->setInt64(kKeyTime, mStartTimeUs); + status_t err = mSource->start(meta.get()); + if (err != OK) { + mDone = true; + mReachedEOS = true; + return err; + } else { + mStarted = true; + return WebmFrameThread::start(); + } +} + +status_t WebmFrameMediaSourceThread::resume() { + if (!mDone && mPaused) { + mPaused = false; + mResumed = true; + } + return OK; +} + +status_t WebmFrameMediaSourceThread::pause() { + if (mStarted) { + mPaused = true; + } + return OK; +} + +status_t WebmFrameMediaSourceThread::stop() { + if (mStarted) { + mStarted = false; + mDone = true; + mSource->stop(); + return WebmFrameThread::stop(); + } + return OK; +} + +void WebmFrameMediaSourceThread::run() { + int32_t count = 0; + int64_t timestampUs = 0xdeadbeef; + int64_t lastTimestampUs = 0; // Previous sample time stamp + int64_t lastDurationUs = 0; // Previous sample duration + int64_t previousPausedDurationUs = 0; + + const uint64_t kUninitialized = 0xffffffffffffffffL; + mStartTimeUs = kUninitialized; + + status_t err = OK; + MediaBuffer *buffer; + while (!mDone && (err = mSource->read(&buffer, NULL)) == OK) { + if (buffer->range_length() == 0) { + buffer->release(); + buffer = NULL; + continue; + } + + sp<MetaData> md = buffer->meta_data(); + CHECK(md->findInt64(kKeyTime, ×tampUs)); + if (mStartTimeUs == kUninitialized) { + mStartTimeUs = timestampUs; + } + timestampUs -= mStartTimeUs; + + if (mPaused && !mResumed) { + lastDurationUs = timestampUs - lastTimestampUs; + lastTimestampUs = timestampUs; + buffer->release(); + buffer = NULL; + continue; + } + ++count; + + // adjust time-stamps after pause/resume + if (mResumed) { + int64_t durExcludingEarlierPausesUs = timestampUs - previousPausedDurationUs; + CHECK_GE(durExcludingEarlierPausesUs, 0ll); + int64_t pausedDurationUs = durExcludingEarlierPausesUs - mTrackDurationUs; + CHECK_GE(pausedDurationUs, lastDurationUs); + previousPausedDurationUs += pausedDurationUs - lastDurationUs; + mResumed = false; + } + timestampUs -= previousPausedDurationUs; + CHECK_GE(timestampUs, 0ll); + + int32_t isSync = false; + md->findInt32(kKeyIsSyncFrame, &isSync); + const sp<WebmFrame> f = new WebmFrame( + mType, + isSync, + timestampUs * 1000 / mTimeCodeScale, + buffer); + mSink.push(f); + + ALOGV( + "%s %s frame at %" PRId64 " size %zu\n", + mType == kVideoType ? "video" : "audio", + isSync ? "I" : "P", + timestampUs * 1000 / mTimeCodeScale, + buffer->range_length()); + + buffer->release(); + buffer = NULL; + + if (timestampUs > mTrackDurationUs) { + mTrackDurationUs = timestampUs; + } + lastDurationUs = timestampUs - lastTimestampUs; + lastTimestampUs = timestampUs; + } + + mTrackDurationUs += lastDurationUs; + mSink.push(WebmFrame::EOS); +} +} diff --git a/media/libstagefright/webm/WebmFrameThread.h b/media/libstagefright/webm/WebmFrameThread.h new file mode 100644 index 0000000..d65d9b7 --- /dev/null +++ b/media/libstagefright/webm/WebmFrameThread.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBMFRAMETHREAD_H_ +#define WEBMFRAMETHREAD_H_ + +#include "WebmFrame.h" +#include "LinkedBlockingQueue.h" + +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaSource.h> + +#include <utils/List.h> +#include <utils/Errors.h> + +#include <pthread.h> + +namespace android { + +class WebmFrameThread : public LightRefBase<WebmFrameThread> { +public: + virtual void run() = 0; + virtual bool running() { return false; } + virtual status_t start(); + virtual status_t pause() { return OK; } + virtual status_t resume() { return OK; } + virtual status_t stop(); + virtual ~WebmFrameThread() { stop(); } + static void *wrap(void *arg); + +protected: + WebmFrameThread() + : mThread(0) { + } + +private: + pthread_t mThread; + DISALLOW_EVIL_CONSTRUCTORS(WebmFrameThread); +}; + +//================================================================================================= + +class WebmFrameSourceThread; +class WebmFrameSinkThread : public WebmFrameThread { +public: + WebmFrameSinkThread( + const int& fd, + const uint64_t& off, + sp<WebmFrameSourceThread> videoThread, + sp<WebmFrameSourceThread> audioThread, + List<sp<WebmElement> >& cues); + + WebmFrameSinkThread( + const int& fd, + const uint64_t& off, + LinkedBlockingQueue<const sp<WebmFrame> >& videoSource, + LinkedBlockingQueue<const sp<WebmFrame> >& audioSource, + List<sp<WebmElement> >& cues); + + void run(); + bool running() { + return !mDone; + } + status_t start(); + status_t stop(); + +private: + const int& mFd; + const uint64_t& mSegmentDataStart; + LinkedBlockingQueue<const sp<WebmFrame> >& mVideoFrames; + LinkedBlockingQueue<const sp<WebmFrame> >& mAudioFrames; + List<sp<WebmElement> >& mCues; + + volatile bool mDone; + + static void initCluster( + List<const sp<WebmFrame> >& frames, + uint64_t& clusterTimecodeL, + List<sp<WebmElement> >& children); + void writeCluster(List<sp<WebmElement> >& children); + void flushFrames(List<const sp<WebmFrame> >& frames, bool last); +}; + +//================================================================================================= + +class WebmFrameSourceThread : public WebmFrameThread { +public: + WebmFrameSourceThread(int type, LinkedBlockingQueue<const sp<WebmFrame> >& sink); + virtual int64_t getDurationUs() = 0; +protected: + const int mType; + LinkedBlockingQueue<const sp<WebmFrame> >& mSink; + + friend class WebmFrameSinkThread; +}; + +//================================================================================================= + +class WebmFrameEmptySourceThread : public WebmFrameSourceThread { +public: + WebmFrameEmptySourceThread(int type, LinkedBlockingQueue<const sp<WebmFrame> >& sink) + : WebmFrameSourceThread(type, sink) { + } + void run() { mSink.push(WebmFrame::EOS); } + int64_t getDurationUs() { return 0; } +}; + +//================================================================================================= + +class WebmFrameMediaSourceThread: public WebmFrameSourceThread { +public: + WebmFrameMediaSourceThread( + const sp<MediaSource>& source, + int type, + LinkedBlockingQueue<const sp<WebmFrame> >& sink, + uint64_t timeCodeScale, + int64_t startTimeRealUs, + int32_t startTimeOffsetMs, + int numPeers, + bool realTimeRecording); + + void run(); + status_t start(); + status_t resume(); + status_t pause(); + status_t stop(); + int64_t getDurationUs() { + return mTrackDurationUs; + } + +private: + const sp<MediaSource> mSource; + const uint64_t mTimeCodeScale; + uint64_t mStartTimeUs; + + volatile bool mDone; + volatile bool mPaused; + volatile bool mResumed; + volatile bool mStarted; + volatile bool mReachedEOS; + int64_t mTrackDurationUs; + + void clearFlags(); +}; +} /* namespace android */ + +#endif /* WEBMFRAMETHREAD_H_ */ diff --git a/media/libstagefright/webm/WebmWriter.cpp b/media/libstagefright/webm/WebmWriter.cpp new file mode 100644 index 0000000..03cf92a --- /dev/null +++ b/media/libstagefright/webm/WebmWriter.cpp @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #define LOG_NDEBUG 0 +#define LOG_TAG "WebmWriter" + +#include "EbmlUtil.h" +#include "WebmWriter.h" + +#include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/foundation/ADebug.h> + +#include <utils/Errors.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <inttypes.h> + +using namespace webm; + +namespace { +size_t XiphLaceCodeLen(size_t size) { + return size / 0xff + 1; +} + +size_t XiphLaceEnc(uint8_t *buf, size_t size) { + size_t i; + for (i = 0; size >= 0xff; ++i, size -= 0xff) { + buf[i] = 0xff; + } + buf[i++] = size; + return i; +} +} + +namespace android { + +static const int64_t kMinStreamableFileSizeInBytes = 5 * 1024 * 1024; + +WebmWriter::WebmWriter(int fd) + : mFd(dup(fd)), + mInitCheck(mFd < 0 ? NO_INIT : OK), + mTimeCodeScale(1000000), + mStartTimestampUs(0), + mStartTimeOffsetMs(0), + mSegmentOffset(0), + mSegmentDataStart(0), + mInfoOffset(0), + mInfoSize(0), + mTracksOffset(0), + mCuesOffset(0), + mPaused(false), + mStarted(false), + mIsFileSizeLimitExplicitlyRequested(false), + mIsRealTimeRecording(false), + mStreamableFile(true), + mEstimatedCuesSize(0) { + mStreams[kAudioIndex] = WebmStream(kAudioType, "Audio", &WebmWriter::audioTrack); + mStreams[kVideoIndex] = WebmStream(kVideoType, "Video", &WebmWriter::videoTrack); + mSinkThread = new WebmFrameSinkThread( + mFd, + mSegmentDataStart, + mStreams[kVideoIndex].mSink, + mStreams[kAudioIndex].mSink, + mCuePoints); +} + +WebmWriter::WebmWriter(const char *filename) + : mInitCheck(NO_INIT), + mTimeCodeScale(1000000), + mStartTimestampUs(0), + mStartTimeOffsetMs(0), + mSegmentOffset(0), + mSegmentDataStart(0), + mInfoOffset(0), + mInfoSize(0), + mTracksOffset(0), + mCuesOffset(0), + mPaused(false), + mStarted(false), + mIsFileSizeLimitExplicitlyRequested(false), + mIsRealTimeRecording(false), + mStreamableFile(true), + mEstimatedCuesSize(0) { + mFd = open(filename, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); + if (mFd >= 0) { + ALOGV("fd %d; flags: %o", mFd, fcntl(mFd, F_GETFL, 0)); + mInitCheck = OK; + } + mStreams[kAudioIndex] = WebmStream(kAudioType, "Audio", &WebmWriter::audioTrack); + mStreams[kVideoIndex] = WebmStream(kVideoType, "Video", &WebmWriter::videoTrack); + mSinkThread = new WebmFrameSinkThread( + mFd, + mSegmentDataStart, + mStreams[kVideoIndex].mSink, + mStreams[kAudioIndex].mSink, + mCuePoints); +} + +// static +sp<WebmElement> WebmWriter::videoTrack(const sp<MetaData>& md) { + int32_t width, height; + CHECK(md->findInt32(kKeyWidth, &width)); + CHECK(md->findInt32(kKeyHeight, &height)); + return WebmElement::VideoTrackEntry(width, height); +} + +// static +sp<WebmElement> WebmWriter::audioTrack(const sp<MetaData>& md) { + int32_t nChannels, samplerate; + uint32_t type; + const void *headerData1; + const char headerData2[] = { 3, 'v', 'o', 'r', 'b', 'i', 's', 7, 0, 0, 0, + 'a', 'n', 'd', 'r', 'o', 'i', 'd', 0, 0, 0, 0, 1 }; + const void *headerData3; + size_t headerSize1, headerSize2 = sizeof(headerData2), headerSize3; + + CHECK(md->findInt32(kKeyChannelCount, &nChannels)); + CHECK(md->findInt32(kKeySampleRate, &samplerate)); + CHECK(md->findData(kKeyVorbisInfo, &type, &headerData1, &headerSize1)); + CHECK(md->findData(kKeyVorbisBooks, &type, &headerData3, &headerSize3)); + + size_t codecPrivateSize = 1; + codecPrivateSize += XiphLaceCodeLen(headerSize1); + codecPrivateSize += XiphLaceCodeLen(headerSize2); + codecPrivateSize += headerSize1 + headerSize2 + headerSize3; + + off_t off = 0; + sp<ABuffer> codecPrivateBuf = new ABuffer(codecPrivateSize); + uint8_t *codecPrivateData = codecPrivateBuf->data(); + codecPrivateData[off++] = 2; + + off += XiphLaceEnc(codecPrivateData + off, headerSize1); + off += XiphLaceEnc(codecPrivateData + off, headerSize2); + + memcpy(codecPrivateData + off, headerData1, headerSize1); + off += headerSize1; + memcpy(codecPrivateData + off, headerData2, headerSize2); + off += headerSize2; + memcpy(codecPrivateData + off, headerData3, headerSize3); + + sp<WebmElement> entry = WebmElement::AudioTrackEntry( + nChannels, + samplerate, + codecPrivateBuf); + return entry; +} + +size_t WebmWriter::numTracks() { + Mutex::Autolock autolock(mLock); + + size_t numTracks = 0; + for (size_t i = 0; i < kMaxStreams; ++i) { + if (mStreams[i].mTrackEntry != NULL) { + numTracks++; + } + } + + return numTracks; +} + +uint64_t WebmWriter::estimateCuesSize(int32_t bitRate) { + // This implementation is based on estimateMoovBoxSize in MPEG4Writer. + // + // Statistical analysis shows that metadata usually accounts + // for a small portion of the total file size, usually < 0.6%. + + // The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2, + // where 1MB is the common file size limit for MMS application. + // The default MAX _MOOV_BOX_SIZE value is based on about 3 + // minute video recording with a bit rate about 3 Mbps, because + // statistics also show that most of the video captured are going + // to be less than 3 minutes. + + // If the estimation is wrong, we will pay the price of wasting + // some reserved space. This should not happen so often statistically. + static const int32_t factor = 2; + static const int64_t MIN_CUES_SIZE = 3 * 1024; // 3 KB + static const int64_t MAX_CUES_SIZE = (180 * 3000000 * 6LL / 8000); + int64_t size = MIN_CUES_SIZE; + + // Max file size limit is set + if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) { + size = mMaxFileSizeLimitBytes * 6 / 1000; + } + + // Max file duration limit is set + if (mMaxFileDurationLimitUs != 0) { + if (bitRate > 0) { + int64_t size2 = ((mMaxFileDurationLimitUs * bitRate * 6) / 1000 / 8000000); + if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) { + // When both file size and duration limits are set, + // we use the smaller limit of the two. + if (size > size2) { + size = size2; + } + } else { + // Only max file duration limit is set + size = size2; + } + } + } + + if (size < MIN_CUES_SIZE) { + size = MIN_CUES_SIZE; + } + + // Any long duration recording will be probably end up with + // non-streamable webm file. + if (size > MAX_CUES_SIZE) { + size = MAX_CUES_SIZE; + } + + ALOGV("limits: %" PRId64 "/%" PRId64 " bytes/us," + " bit rate: %d bps and the estimated cues size %" PRId64 " bytes", + mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size); + return factor * size; +} + +void WebmWriter::initStream(size_t idx) { + if (mStreams[idx].mThread != NULL) { + return; + } + if (mStreams[idx].mSource == NULL) { + ALOGV("adding dummy source ... "); + mStreams[idx].mThread = new WebmFrameEmptySourceThread( + mStreams[idx].mType, mStreams[idx].mSink); + } else { + ALOGV("adding source %p", mStreams[idx].mSource.get()); + mStreams[idx].mThread = new WebmFrameMediaSourceThread( + mStreams[idx].mSource, + mStreams[idx].mType, + mStreams[idx].mSink, + mTimeCodeScale, + mStartTimestampUs, + mStartTimeOffsetMs, + numTracks(), + mIsRealTimeRecording); + } +} + +void WebmWriter::release() { + close(mFd); + mFd = -1; + mInitCheck = NO_INIT; + mStarted = false; +} + +status_t WebmWriter::reset() { + if (mInitCheck != OK) { + return OK; + } else { + if (!mStarted) { + release(); + return OK; + } + } + + status_t err = OK; + int64_t maxDurationUs = 0; + int64_t minDurationUs = 0x7fffffffffffffffLL; + for (int i = 0; i < kMaxStreams; ++i) { + if (mStreams[i].mThread == NULL) { + continue; + } + + status_t status = mStreams[i].mThread->stop(); + if (err == OK && status != OK) { + err = status; + } + + int64_t durationUs = mStreams[i].mThread->getDurationUs(); + if (durationUs > maxDurationUs) { + maxDurationUs = durationUs; + } + if (durationUs < minDurationUs) { + minDurationUs = durationUs; + } + } + + if (numTracks() > 1) { + ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us", minDurationUs, maxDurationUs); + } + + mSinkThread->stop(); + + // Do not write out movie header on error. + if (err != OK) { + release(); + return err; + } + + sp<WebmElement> cues = new WebmMaster(kMkvCues, mCuePoints); + uint64_t cuesSize = cues->totalSize(); + // TRICKY Even when the cues do fit in the space we reserved, if they do not fit + // perfectly, we still need to check if there is enough "extra space" to write an + // EBML void element. + if (cuesSize != mEstimatedCuesSize && cuesSize > mEstimatedCuesSize - kMinEbmlVoidSize) { + mCuesOffset = ::lseek(mFd, 0, SEEK_CUR); + cues->write(mFd, cuesSize); + } else { + uint64_t spaceSize; + ::lseek(mFd, mCuesOffset, SEEK_SET); + cues->write(mFd, cuesSize); + sp<WebmElement> space = new EbmlVoid(mEstimatedCuesSize - cuesSize); + space->write(mFd, spaceSize); + } + + mCuePoints.clear(); + mStreams[kVideoIndex].mSink.clear(); + mStreams[kAudioIndex].mSink.clear(); + + uint8_t bary[sizeof(uint64_t)]; + uint64_t totalSize = ::lseek(mFd, 0, SEEK_END); + uint64_t segmentSize = totalSize - mSegmentDataStart; + ::lseek(mFd, mSegmentOffset + sizeOf(kMkvSegment), SEEK_SET); + uint64_t segmentSizeCoded = encodeUnsigned(segmentSize, sizeOf(kMkvUnknownLength)); + serializeCodedUnsigned(segmentSizeCoded, bary); + ::write(mFd, bary, sizeOf(kMkvUnknownLength)); + + uint64_t size; + uint64_t durationOffset = mInfoOffset + sizeOf(kMkvInfo) + sizeOf(mInfoSize) + + sizeOf(kMkvSegmentDuration) + sizeOf(sizeof(double)); + sp<WebmElement> duration = new WebmFloat( + kMkvSegmentDuration, + (double) (maxDurationUs * 1000 / mTimeCodeScale)); + duration->serializePayload(bary); + ::lseek(mFd, durationOffset, SEEK_SET); + ::write(mFd, bary, sizeof(double)); + + List<sp<WebmElement> > seekEntries; + seekEntries.push_back(WebmElement::SeekEntry(kMkvInfo, mInfoOffset - mSegmentDataStart)); + seekEntries.push_back(WebmElement::SeekEntry(kMkvTracks, mTracksOffset - mSegmentDataStart)); + seekEntries.push_back(WebmElement::SeekEntry(kMkvCues, mCuesOffset - mSegmentDataStart)); + sp<WebmElement> seekHead = new WebmMaster(kMkvSeekHead, seekEntries); + + uint64_t metaSeekSize; + ::lseek(mFd, mSegmentDataStart, SEEK_SET); + seekHead->write(mFd, metaSeekSize); + + uint64_t spaceSize; + sp<WebmElement> space = new EbmlVoid(kMaxMetaSeekSize - metaSeekSize); + space->write(mFd, spaceSize); + + release(); + return err; +} + +status_t WebmWriter::addSource(const sp<MediaSource> &source) { + Mutex::Autolock l(mLock); + if (mStarted) { + ALOGE("Attempt to add source AFTER recording is started"); + return UNKNOWN_ERROR; + } + + // At most 2 tracks can be supported. + if (mStreams[kVideoIndex].mTrackEntry != NULL + && mStreams[kAudioIndex].mTrackEntry != NULL) { + ALOGE("Too many tracks (2) to add"); + return ERROR_UNSUPPORTED; + } + + CHECK(source != NULL); + + // A track of type other than video or audio is not supported. + const char *mime; + source->getFormat()->findCString(kKeyMIMEType, &mime); + const char *vp8 = MEDIA_MIMETYPE_VIDEO_VP8; + const char *vorbis = MEDIA_MIMETYPE_AUDIO_VORBIS; + + size_t streamIndex; + if (!strncasecmp(mime, vp8, strlen(vp8))) { + streamIndex = kVideoIndex; + } else if (!strncasecmp(mime, vorbis, strlen(vorbis))) { + streamIndex = kAudioIndex; + } else { + ALOGE("Track (%s) other than %s or %s is not supported", mime, vp8, vorbis); + return ERROR_UNSUPPORTED; + } + + // No more than one video or one audio track is supported. + if (mStreams[streamIndex].mTrackEntry != NULL) { + ALOGE("%s track already exists", mStreams[streamIndex].mName); + return ERROR_UNSUPPORTED; + } + + // This is the first track of either audio or video. + // Go ahead to add the track. + mStreams[streamIndex].mSource = source; + mStreams[streamIndex].mTrackEntry = mStreams[streamIndex].mMakeTrack(source->getFormat()); + + return OK; +} + +status_t WebmWriter::start(MetaData *params) { + if (mInitCheck != OK) { + return UNKNOWN_ERROR; + } + + if (mStreams[kVideoIndex].mTrackEntry == NULL + && mStreams[kAudioIndex].mTrackEntry == NULL) { + ALOGE("No source added"); + return INVALID_OPERATION; + } + + if (mMaxFileSizeLimitBytes != 0) { + mIsFileSizeLimitExplicitlyRequested = true; + } + + if (params) { + int32_t isRealTimeRecording; + params->findInt32(kKeyRealTimeRecording, &isRealTimeRecording); + mIsRealTimeRecording = isRealTimeRecording; + } + + if (mStarted) { + if (mPaused) { + mPaused = false; + mStreams[kAudioIndex].mThread->resume(); + mStreams[kVideoIndex].mThread->resume(); + } + return OK; + } + + if (params) { + int32_t tcsl; + if (params->findInt32(kKeyTimeScale, &tcsl)) { + mTimeCodeScale = tcsl; + } + } + CHECK_GT(mTimeCodeScale, 0); + ALOGV("movie time scale: %" PRIu64, mTimeCodeScale); + + /* + * When the requested file size limit is small, the priority + * is to meet the file size limit requirement, rather than + * to make the file streamable. mStreamableFile does not tell + * whether the actual recorded file is streamable or not. + */ + mStreamableFile = (!mMaxFileSizeLimitBytes) + || (mMaxFileSizeLimitBytes >= kMinStreamableFileSizeInBytes); + + /* + * Write various metadata. + */ + sp<WebmElement> ebml, segment, info, seekHead, tracks, cues; + ebml = WebmElement::EbmlHeader(); + segment = new WebmMaster(kMkvSegment); + seekHead = new EbmlVoid(kMaxMetaSeekSize); + info = WebmElement::SegmentInfo(mTimeCodeScale, 0); + + List<sp<WebmElement> > children; + for (size_t i = 0; i < kMaxStreams; ++i) { + if (mStreams[i].mTrackEntry != NULL) { + children.push_back(mStreams[i].mTrackEntry); + } + } + tracks = new WebmMaster(kMkvTracks, children); + + if (!mStreamableFile) { + cues = NULL; + } else { + int32_t bitRate = -1; + if (params) { + params->findInt32(kKeyBitRate, &bitRate); + } + mEstimatedCuesSize = estimateCuesSize(bitRate); + CHECK_GE(mEstimatedCuesSize, 8); + cues = new EbmlVoid(mEstimatedCuesSize); + } + + sp<WebmElement> elems[] = { ebml, segment, seekHead, info, tracks, cues }; + size_t nElems = sizeof(elems) / sizeof(elems[0]); + uint64_t offsets[nElems]; + uint64_t sizes[nElems]; + for (uint32_t i = 0; i < nElems; i++) { + WebmElement *e = elems[i].get(); + if (!e) { + continue; + } + + uint64_t size; + offsets[i] = ::lseek(mFd, 0, SEEK_CUR); + sizes[i] = e->mSize; + e->write(mFd, size); + } + + mSegmentOffset = offsets[1]; + mSegmentDataStart = offsets[2]; + mInfoOffset = offsets[3]; + mInfoSize = sizes[3]; + mTracksOffset = offsets[4]; + mCuesOffset = offsets[5]; + + // start threads + if (params) { + params->findInt64(kKeyTime, &mStartTimestampUs); + } + + initStream(kAudioIndex); + initStream(kVideoIndex); + + mStreams[kAudioIndex].mThread->start(); + mStreams[kVideoIndex].mThread->start(); + mSinkThread->start(); + + mStarted = true; + return OK; +} + +status_t WebmWriter::pause() { + if (mInitCheck != OK) { + return OK; + } + mPaused = true; + status_t err = OK; + for (int i = 0; i < kMaxStreams; ++i) { + if (mStreams[i].mThread == NULL) { + continue; + } + status_t status = mStreams[i].mThread->pause(); + if (status != OK) { + err = status; + } + } + return err; +} + +status_t WebmWriter::stop() { + return reset(); +} + +bool WebmWriter::reachedEOS() { + return !mSinkThread->running(); +} +} /* namespace android */ diff --git a/media/libstagefright/webm/WebmWriter.h b/media/libstagefright/webm/WebmWriter.h new file mode 100644 index 0000000..36b6965 --- /dev/null +++ b/media/libstagefright/webm/WebmWriter.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBMWRITER_H_ +#define WEBMWRITER_H_ + +#include "WebmConstants.h" +#include "WebmFrameThread.h" +#include "LinkedBlockingQueue.h" + +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MediaWriter.h> + +#include <utils/Errors.h> +#include <utils/Mutex.h> +#include <utils/StrongPointer.h> + +#include <stdint.h> + +using namespace webm; + +namespace android { + +class WebmWriter : public MediaWriter { +public: + WebmWriter(int fd); + WebmWriter(const char *filename); + ~WebmWriter() { reset(); } + + + virtual status_t addSource(const sp<MediaSource> &source); + virtual status_t start(MetaData *param = NULL); + virtual status_t stop(); + virtual status_t pause(); + virtual bool reachedEOS(); + + virtual void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; } + virtual int32_t getStartTimeOffsetMs() const { return mStartTimeOffsetMs; } + +private: + int mFd; + status_t mInitCheck; + + uint64_t mTimeCodeScale; + int64_t mStartTimestampUs; + int32_t mStartTimeOffsetMs; + + uint64_t mSegmentOffset; + uint64_t mSegmentDataStart; + uint64_t mInfoOffset; + uint64_t mInfoSize; + uint64_t mTracksOffset; + uint64_t mCuesOffset; + + bool mPaused; + bool mStarted; + bool mIsFileSizeLimitExplicitlyRequested; + bool mIsRealTimeRecording; + bool mStreamableFile; + uint64_t mEstimatedCuesSize; + + Mutex mLock; + List<sp<WebmElement> > mCuePoints; + + enum { + kAudioIndex = 0, + kVideoIndex = 1, + kMaxStreams = 2, + }; + + struct WebmStream { + int mType; + const char *mName; + sp<WebmElement> (*mMakeTrack)(const sp<MetaData>&); + + sp<MediaSource> mSource; + sp<WebmElement> mTrackEntry; + sp<WebmFrameSourceThread> mThread; + LinkedBlockingQueue<const sp<WebmFrame> > mSink; + + WebmStream() + : mType(kInvalidType), + mName("Invalid"), + mMakeTrack(NULL) { + } + + WebmStream(int type, const char *name, sp<WebmElement> (*makeTrack)(const sp<MetaData>&)) + : mType(type), + mName(name), + mMakeTrack(makeTrack) { + } + + WebmStream &operator=(const WebmStream &other) { + mType = other.mType; + mName = other.mName; + mMakeTrack = other.mMakeTrack; + return *this; + } + }; + WebmStream mStreams[kMaxStreams]; + + sp<WebmFrameSinkThread> mSinkThread; + + size_t numTracks(); + uint64_t estimateCuesSize(int32_t bitRate); + void initStream(size_t idx); + void release(); + status_t reset(); + + static sp<WebmElement> videoTrack(const sp<MetaData>& md); + static sp<WebmElement> audioTrack(const sp<MetaData>& md); + + DISALLOW_EVIL_CONSTRUCTORS(WebmWriter); +}; + +} /* namespace android */ +#endif /* WEBMWRITER_H_ */ |