summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Shih <robertshih@google.com>2014-02-07 16:59:35 -0800
committerRobert Shih <robertshih@google.com>2014-03-06 14:27:56 -0800
commit343947abc8b7c126f966fd32a0b18bff6c2cecd1 (patch)
tree211261c7f1985676176ce01891ba0529300aef55
parent7a0662afeba50b78f43ce24539230cace30c3cfd (diff)
downloadframeworks_av-343947abc8b7c126f966fd32a0b18bff6c2cecd1.zip
frameworks_av-343947abc8b7c126f966fd32a0b18bff6c2cecd1.tar.gz
frameworks_av-343947abc8b7c126f966fd32a0b18bff6c2cecd1.tar.bz2
Stagefright: added WebM muxer.
Change-Id: I50bbf9c6f089b205d5ecef1371bfdd2028b3b358
-rw-r--r--include/media/stagefright/MediaMuxer.h6
-rw-r--r--media/libstagefright/Android.mk1
-rw-r--r--media/libstagefright/MediaMuxer.cpp27
-rw-r--r--media/libstagefright/webm/Android.mk23
-rw-r--r--media/libstagefright/webm/EbmlUtil.cpp108
-rw-r--r--media/libstagefright/webm/EbmlUtil.h50
-rw-r--r--media/libstagefright/webm/LinkedBlockingQueue.h79
-rw-r--r--media/libstagefright/webm/WebmConstants.h133
-rw-r--r--media/libstagefright/webm/WebmElement.cpp367
-rw-r--r--media/libstagefright/webm/WebmElement.h127
-rw-r--r--media/libstagefright/webm/WebmFrame.cpp83
-rw-r--r--media/libstagefright/webm/WebmFrame.h46
-rw-r--r--media/libstagefright/webm/WebmFrameThread.cpp399
-rw-r--r--media/libstagefright/webm/WebmFrameThread.h160
-rw-r--r--media/libstagefright/webm/WebmWriter.cpp551
-rw-r--r--media/libstagefright/webm/WebmWriter.h130
16 files changed, 2284 insertions, 6 deletions
diff --git a/include/media/stagefright/MediaMuxer.h b/include/media/stagefright/MediaMuxer.h
index ff6a66e..bbe4303 100644
--- a/include/media/stagefright/MediaMuxer.h
+++ b/include/media/stagefright/MediaMuxer.h
@@ -30,7 +30,7 @@ struct MediaAdapter;
struct MediaBuffer;
struct MediaSource;
struct MetaData;
-struct MPEG4Writer;
+struct MediaWriter;
// MediaMuxer is used to mux multiple tracks into a video. Currently, we only
// support a mp4 file as the output.
@@ -44,6 +44,7 @@ public:
// OutputFormat is updated.
enum OutputFormat {
OUTPUT_FORMAT_MPEG_4 = 0,
+ OUTPUT_FORMAT_WEBM = 1,
OUTPUT_FORMAT_LIST_END // must be last - used to validate format type
};
@@ -115,7 +116,8 @@ public:
int64_t timeUs, uint32_t flags) ;
private:
- sp<MPEG4Writer> mWriter;
+ const OutputFormat mFormat;
+ sp<MediaWriter> mWriter;
Vector< sp<MediaAdapter> > mTrackList; // Each track has its MediaAdapter.
sp<MetaData> mFileMeta; // Metadata for the whole file.
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 0fd1e69..714b5e0 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -97,6 +97,7 @@ LOCAL_STATIC_LIBRARIES := \
libstagefright_color_conversion \
libstagefright_aacenc \
libstagefright_matroska \
+ libstagefright_webm \
libstagefright_timedtext \
libvpx \
libwebm \
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index d87e910..90335ee 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -16,6 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaMuxer"
+
+#include "webm/WebmWriter.h"
+
#include <utils/Log.h>
#include <media/stagefright/MediaMuxer.h>
@@ -36,19 +39,30 @@
namespace android {
MediaMuxer::MediaMuxer(const char *path, OutputFormat format)
- : mState(UNINITIALIZED) {
+ : mFormat(format),
+ mState(UNINITIALIZED) {
if (format == OUTPUT_FORMAT_MPEG_4) {
mWriter = new MPEG4Writer(path);
+ } else if (format == OUTPUT_FORMAT_WEBM) {
+ mWriter = new WebmWriter(path);
+ }
+
+ if (mWriter != NULL) {
mFileMeta = new MetaData;
mState = INITIALIZED;
}
-
}
MediaMuxer::MediaMuxer(int fd, OutputFormat format)
- : mState(UNINITIALIZED) {
+ : mFormat(format),
+ mState(UNINITIALIZED) {
if (format == OUTPUT_FORMAT_MPEG_4) {
mWriter = new MPEG4Writer(fd);
+ } else if (format == OUTPUT_FORMAT_WEBM) {
+ mWriter = new WebmWriter(fd);
+ }
+
+ if (mWriter != NULL) {
mFileMeta = new MetaData;
mState = INITIALIZED;
}
@@ -109,8 +123,13 @@ status_t MediaMuxer::setLocation(int latitude, int longitude) {
ALOGE("setLocation() must be called before start().");
return INVALID_OPERATION;
}
+ if (mFormat != OUTPUT_FORMAT_MPEG_4) {
+ ALOGE("setLocation() is only supported for .mp4 output.");
+ return INVALID_OPERATION;
+ }
+
ALOGV("Setting location: latitude = %d, longitude = %d", latitude, longitude);
- return mWriter->setGeoData(latitude, longitude);
+ return static_cast<MPEG4Writer*>(mWriter.get())->setGeoData(latitude, longitude);
}
status_t MediaMuxer::start() {
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..c978966
--- /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 ((int) dst == -1) {
+ 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..5addd3c
--- /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) 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, &timestampUs));
+ 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..529dec8
--- /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(); }
+
+
+ status_t addSource(const sp<MediaSource> &source);
+ status_t start(MetaData *param = NULL);
+ status_t stop();
+ status_t pause();
+ bool reachedEOS();
+
+ void setStartTimeOffsetMs(int ms) { mStartTimeOffsetMs = ms; }
+ 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_ */