1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// #define LOG_NDEBUG 0
18#define LOG_TAG "WebmElement"
19
20#include "EbmlUtil.h"
21#include "WebmElement.h"
22#include "WebmConstants.h"
23
24#include <media/stagefright/foundation/ADebug.h>
25#include <media/stagefright/foundation/ColorUtils.h>
26#include <media/stagefright/MetaData.h>
27#include <utils/Log.h>
28
29#include <string.h>
30#include <unistd.h>
31#include <errno.h>
32#include <fcntl.h>
33#include <sys/mman.h>
34
35using namespace android;
36using namespace webm;
37
38namespace {
39
40int64_t voidSize(int64_t totalSize) {
41    if (totalSize < 2) {
42        return -1;
43    }
44    if (totalSize < 9) {
45        return totalSize - 2;
46    }
47    return totalSize - 9;
48}
49
50uint64_t childrenSum(const List<sp<WebmElement> >& children) {
51    uint64_t total = 0;
52    for (List<sp<WebmElement> >::const_iterator it = children.begin();
53            it != children.end(); ++it) {
54        total += (*it)->totalSize();
55    }
56    return total;
57}
58
59void populateCommonTrackEntries(
60        int num,
61        uint64_t uid,
62        bool lacing,
63        const char *lang,
64        const char *codec,
65        TrackTypes type,
66        List<sp<WebmElement> > &ls) {
67    ls.push_back(new WebmUnsigned(kMkvTrackNumber, num));
68    ls.push_back(new WebmUnsigned(kMkvTrackUid, uid));
69    ls.push_back(new WebmUnsigned(kMkvFlagLacing, lacing));
70    ls.push_back(new WebmString(kMkvLanguage, lang));
71    ls.push_back(new WebmString(kMkvCodecId, codec));
72    ls.push_back(new WebmUnsigned(kMkvTrackType, type));
73}
74}
75
76namespace android {
77
78WebmElement::WebmElement(uint64_t id, uint64_t size)
79    : mId(id), mSize(size) {
80}
81
82WebmElement::~WebmElement() {
83}
84
85int WebmElement::serializePayloadSize(uint8_t *buf) {
86    return serializeCodedUnsigned(encodeUnsigned(mSize), buf);
87}
88
89uint64_t WebmElement::serializeInto(uint8_t *buf) {
90    uint8_t *cur = buf;
91    int head = serializeCodedUnsigned(mId, cur);
92    cur += head;
93    int neck = serializePayloadSize(cur);
94    cur += neck;
95    serializePayload(cur);
96    cur += mSize;
97    return cur - buf;
98}
99
100uint64_t WebmElement::totalSize() {
101    uint8_t buf[8];
102    //............... + sizeOf(encodeUnsigned(size))
103    return sizeOf(mId) + serializePayloadSize(buf) + mSize;
104}
105
106uint8_t *WebmElement::serialize(uint64_t& size) {
107    size = totalSize();
108    uint8_t *buf = new uint8_t[size];
109    serializeInto(buf);
110    return buf;
111}
112
113int WebmElement::write(int fd, uint64_t& size) {
114    uint8_t buf[8];
115    size = totalSize();
116    off64_t off = ::lseek64(fd, (size - 1), SEEK_CUR) - (size - 1);
117    ::write(fd, buf, 1); // extend file
118
119    off64_t curOff = off + size;
120    off64_t alignedOff = off & ~(::sysconf(_SC_PAGE_SIZE) - 1);
121    off64_t mapSize = curOff - alignedOff;
122    off64_t pageOff = off - alignedOff;
123    void *dst = ::mmap64(NULL, mapSize, PROT_WRITE, MAP_SHARED, fd, alignedOff);
124    if (dst == MAP_FAILED) {
125        ALOGE("mmap64 failed; errno = %d", errno);
126        ALOGE("fd %d; flags: %o", fd, ::fcntl(fd, F_GETFL, 0));
127        return errno;
128    } else {
129        serializeInto((uint8_t*) dst + pageOff);
130        ::msync(dst, mapSize, MS_SYNC);
131        return ::munmap(dst, mapSize);
132    }
133}
134
135//=================================================================================================
136
137WebmUnsigned::WebmUnsigned(uint64_t id, uint64_t value)
138    : WebmElement(id, sizeOf(value)), mValue(value) {
139}
140
141void WebmUnsigned::serializePayload(uint8_t *buf) {
142    serializeCodedUnsigned(mValue, buf);
143}
144
145//=================================================================================================
146
147WebmFloat::WebmFloat(uint64_t id, double value)
148    : WebmElement(id, sizeof(double)), mValue(value) {
149}
150
151WebmFloat::WebmFloat(uint64_t id, float value)
152    : WebmElement(id, sizeof(float)), mValue(value) {
153}
154
155void WebmFloat::serializePayload(uint8_t *buf) {
156    uint64_t data;
157    if (mSize == sizeof(float)) {
158        float f = mValue;
159        data = *reinterpret_cast<const uint32_t*>(&f);
160    } else {
161        data = *reinterpret_cast<const uint64_t*>(&mValue);
162    }
163    for (int i = mSize - 1; i >= 0; --i) {
164        buf[i] = data & 0xff;
165        data >>= 8;
166    }
167}
168
169//=================================================================================================
170
171WebmBinary::WebmBinary(uint64_t id, const sp<ABuffer> &ref)
172    : WebmElement(id, ref->size()), mRef(ref) {
173}
174
175void WebmBinary::serializePayload(uint8_t *buf) {
176    memcpy(buf, mRef->data(), mRef->size());
177}
178
179//=================================================================================================
180
181WebmString::WebmString(uint64_t id, const char *str)
182    : WebmElement(id, strlen(str)), mStr(str) {
183}
184
185void WebmString::serializePayload(uint8_t *buf) {
186    memcpy(buf, mStr, strlen(mStr));
187}
188
189//=================================================================================================
190
191WebmSimpleBlock::WebmSimpleBlock(
192        int trackNum,
193        int16_t relTimecode,
194        bool key,
195        const sp<ABuffer>& orig)
196    // ............................ trackNum*1 + timecode*2 + flags*1
197    //                                ^^^
198    // Only the least significant byte of trackNum is encoded
199    : WebmElement(kMkvSimpleBlock, orig->size() + 4),
200      mTrackNum(trackNum),
201      mRelTimecode(relTimecode),
202      mKey(key),
203      mRef(orig) {
204}
205
206void WebmSimpleBlock::serializePayload(uint8_t *buf) {
207    serializeCodedUnsigned(encodeUnsigned(mTrackNum), buf);
208    buf[1] = (mRelTimecode & 0xff00) >> 8;
209    buf[2] = mRelTimecode & 0xff;
210    buf[3] = mKey ? 0x80 : 0;
211    memcpy(buf + 4, mRef->data(), mSize - 4);
212}
213
214//=================================================================================================
215
216EbmlVoid::EbmlVoid(uint64_t totalSize)
217    : WebmElement(kMkvVoid, voidSize(totalSize)),
218      mSizeWidth(totalSize - sizeOf(kMkvVoid) - voidSize(totalSize)) {
219    CHECK_GE(voidSize(totalSize), 0);
220}
221
222int EbmlVoid::serializePayloadSize(uint8_t *buf) {
223    return serializeCodedUnsigned(encodeUnsigned(mSize, mSizeWidth), buf);
224}
225
226void EbmlVoid::serializePayload(uint8_t *buf) {
227    ::memset(buf, 0, mSize);
228    return;
229}
230
231//=================================================================================================
232
233WebmMaster::WebmMaster(uint64_t id, const List<sp<WebmElement> >& children)
234    : WebmElement(id, childrenSum(children)), mChildren(children) {
235}
236
237WebmMaster::WebmMaster(uint64_t id)
238    : WebmElement(id, 0) {
239}
240
241int WebmMaster::serializePayloadSize(uint8_t *buf) {
242    if (mSize == 0){
243        return serializeCodedUnsigned(kMkvUnknownLength, buf);
244    }
245    return WebmElement::serializePayloadSize(buf);
246}
247
248void WebmMaster::serializePayload(uint8_t *buf) {
249    uint64_t off = 0;
250    for (List<sp<WebmElement> >::const_iterator it = mChildren.begin(); it != mChildren.end();
251            ++it) {
252        sp<WebmElement> child = (*it);
253        child->serializeInto(buf + off);
254        off += child->totalSize();
255    }
256}
257
258//=================================================================================================
259
260sp<WebmElement> WebmElement::CuePointEntry(uint64_t time, int track, uint64_t off) {
261    List<sp<WebmElement> > cuePointEntryFields;
262    cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueTrack, track));
263    cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueClusterPosition, off));
264    WebmElement *cueTrackPositions = new WebmMaster(kMkvCueTrackPositions, cuePointEntryFields);
265
266    cuePointEntryFields.clear();
267    cuePointEntryFields.push_back(new WebmUnsigned(kMkvCueTime, time));
268    cuePointEntryFields.push_back(cueTrackPositions);
269    return new WebmMaster(kMkvCuePoint, cuePointEntryFields);
270}
271
272sp<WebmElement> WebmElement::SeekEntry(uint64_t id, uint64_t off) {
273    List<sp<WebmElement> > seekEntryFields;
274    seekEntryFields.push_back(new WebmUnsigned(kMkvSeekId, id));
275    seekEntryFields.push_back(new WebmUnsigned(kMkvSeekPosition, off));
276    return new WebmMaster(kMkvSeek, seekEntryFields);
277}
278
279sp<WebmElement> WebmElement::EbmlHeader(
280        int ver,
281        int readVer,
282        int maxIdLen,
283        int maxSizeLen,
284        int docVer,
285        int docReadVer) {
286    List<sp<WebmElement> > headerFields;
287    headerFields.push_back(new WebmUnsigned(kMkvEbmlVersion, ver));
288    headerFields.push_back(new WebmUnsigned(kMkvEbmlReadVersion, readVer));
289    headerFields.push_back(new WebmUnsigned(kMkvEbmlMaxIdlength, maxIdLen));
290    headerFields.push_back(new WebmUnsigned(kMkvEbmlMaxSizeLength, maxSizeLen));
291    headerFields.push_back(new WebmString(kMkvDocType, "webm"));
292    headerFields.push_back(new WebmUnsigned(kMkvDocTypeVersion, docVer));
293    headerFields.push_back(new WebmUnsigned(kMkvDocTypeReadVersion, docReadVer));
294    return new WebmMaster(kMkvEbml, headerFields);
295}
296
297sp<WebmElement> WebmElement::SegmentInfo(uint64_t scale, double dur) {
298    List<sp<WebmElement> > segmentInfo;
299    // place duration first; easier to patch
300    segmentInfo.push_back(new WebmFloat(kMkvSegmentDuration, dur));
301    segmentInfo.push_back(new WebmUnsigned(kMkvTimecodeScale, scale));
302    segmentInfo.push_back(new WebmString(kMkvMuxingApp, "android"));
303    segmentInfo.push_back(new WebmString(kMkvWritingApp, "android"));
304    return new WebmMaster(kMkvInfo, segmentInfo);
305}
306
307sp<WebmElement> WebmElement::AudioTrackEntry(
308        int chans,
309        double rate,
310        const sp<ABuffer> &buf,
311        int bps,
312        uint64_t uid,
313        bool lacing,
314        const char *lang) {
315    if (uid == 0) {
316        uid = kAudioTrackNum;
317    }
318
319    List<sp<WebmElement> > trackEntryFields;
320    populateCommonTrackEntries(
321            kAudioTrackNum,
322            uid,
323            lacing,
324            lang,
325            "A_VORBIS",
326            kAudioType,
327            trackEntryFields);
328
329    List<sp<WebmElement> > audioInfo;
330    audioInfo.push_back(new WebmUnsigned(kMkvChannels, chans));
331    audioInfo.push_back(new WebmFloat(kMkvSamplingFrequency, rate));
332    if (bps) {
333        WebmElement *bitDepth = new WebmUnsigned(kMkvBitDepth, bps);
334        audioInfo.push_back(bitDepth);
335    }
336
337    trackEntryFields.push_back(new WebmMaster(kMkvAudio, audioInfo));
338    trackEntryFields.push_back(new WebmBinary(kMkvCodecPrivate, buf));
339    return new WebmMaster(kMkvTrackEntry, trackEntryFields);
340}
341
342sp<WebmElement> WebmElement::VideoTrackEntry(
343        const char *codec,
344        uint64_t width,
345        uint64_t height,
346        const sp<MetaData> &meta,
347        uint64_t uid,
348        bool lacing,
349        const char *lang) {
350    if (uid == 0) {
351        uid = kVideoTrackNum;
352    }
353
354    List<sp<WebmElement> > trackEntryFields;
355    populateCommonTrackEntries(
356            kVideoTrackNum,
357            uid,
358            lacing,
359            lang,
360            codec,
361            kVideoType,
362            trackEntryFields);
363
364    // CSD
365    uint32_t type;
366    const void *data;
367    size_t size;
368    if (meta->findData(kKeyVp9CodecPrivate, &type, &data, &size)) {
369        sp<ABuffer> buf = new ABuffer((void *)data, size); // note: buf does not own data
370        trackEntryFields.push_back(new WebmBinary(kMkvCodecPrivate, buf));
371    }
372
373    List<sp<WebmElement> > videoInfo;
374    videoInfo.push_back(new WebmUnsigned(kMkvPixelWidth, width));
375    videoInfo.push_back(new WebmUnsigned(kMkvPixelHeight, height));
376
377    // Color aspects
378    {
379        List<sp<WebmElement> > colorInfo;
380
381        ColorAspects aspects;
382        aspects.mPrimaries = ColorAspects::PrimariesUnspecified;
383        aspects.mTransfer = ColorAspects::TransferUnspecified;
384        aspects.mMatrixCoeffs = ColorAspects::MatrixUnspecified;
385        aspects.mRange = ColorAspects::RangeUnspecified;
386        bool havePrimaries = meta->findInt32(kKeyColorPrimaries, (int32_t*)&aspects.mPrimaries);
387        bool haveTransfer = meta->findInt32(kKeyTransferFunction, (int32_t*)&aspects.mTransfer);
388        bool haveCoeffs = meta->findInt32(kKeyColorMatrix, (int32_t*)&aspects.mMatrixCoeffs);
389        bool haveRange = meta->findInt32(kKeyColorRange, (int32_t*)&aspects.mRange);
390
391        int32_t primaries, transfer, coeffs;
392        bool fullRange;
393        ColorUtils::convertCodecColorAspectsToIsoAspects(
394                aspects, &primaries, &transfer, &coeffs, &fullRange);
395        if (havePrimaries) {
396            colorInfo.push_back(new WebmUnsigned(kMkvPrimaries, primaries));
397        }
398        if (haveTransfer) {
399            colorInfo.push_back(new WebmUnsigned(kMkvTransferCharacteristics, transfer));
400        }
401        if (haveCoeffs) {
402            colorInfo.push_back(new WebmUnsigned(kMkvMatrixCoefficients, coeffs));
403        }
404        if (haveRange) {
405            colorInfo.push_back(new WebmUnsigned(kMkvRange, fullRange ? 2 : 1));
406        }
407
408        // Also add HDR static info, some of which goes to MasteringMetadata element
409
410        const HDRStaticInfo *info;
411        uint32_t type;
412        const void *data;
413        size_t size;
414        if (meta->findData(kKeyHdrStaticInfo, &type, &data, &size)
415                && type == 'hdrS' && size == sizeof(*info)) {
416            info = (const HDRStaticInfo*)data;
417            if (info->mID == HDRStaticInfo::kType1) {
418                List<sp<WebmElement> > masteringInfo;
419
420                // convert HDRStaticInfo values to matroska equivalent values for each non-0 group
421                if (info->sType1.mMaxFrameAverageLightLevel) {
422                    colorInfo.push_back(new WebmUnsigned(
423                            kMkvMaxFALL, info->sType1.mMaxFrameAverageLightLevel));
424                }
425                if (info->sType1.mMaxContentLightLevel) {
426                    colorInfo.push_back(new WebmUnsigned(
427                            kMkvMaxCLL, info->sType1.mMaxContentLightLevel));
428                }
429                if (info->sType1.mMinDisplayLuminance) {
430                    // HDRStaticInfo Type1 stores min luminance scaled 10000:1
431                    masteringInfo.push_back(new WebmFloat(
432                            kMkvLuminanceMin, info->sType1.mMinDisplayLuminance * 0.0001));
433                }
434                if (info->sType1.mMaxDisplayLuminance) {
435                    masteringInfo.push_back(new WebmFloat(
436                            kMkvLuminanceMax, (float)info->sType1.mMaxDisplayLuminance));
437                }
438                // HDRStaticInfo Type1 stores primaries scaled 50000:1
439                if (info->sType1.mW.x || info->sType1.mW.y) {
440                    masteringInfo.push_back(new WebmFloat(
441                            kMkvWhitePointChromaticityX, info->sType1.mW.x * 0.00002));
442                    masteringInfo.push_back(new WebmFloat(
443                            kMkvWhitePointChromaticityY, info->sType1.mW.y * 0.00002));
444                }
445                if (info->sType1.mR.x || info->sType1.mR.y || info->sType1.mG.x
446                        || info->sType1.mG.y || info->sType1.mB.x || info->sType1.mB.y) {
447                    masteringInfo.push_back(new WebmFloat(
448                            kMkvPrimaryRChromaticityX, info->sType1.mR.x * 0.00002));
449                    masteringInfo.push_back(new WebmFloat(
450                            kMkvPrimaryRChromaticityY, info->sType1.mR.y * 0.00002));
451                    masteringInfo.push_back(new WebmFloat(
452                            kMkvPrimaryGChromaticityX, info->sType1.mG.x * 0.00002));
453                    masteringInfo.push_back(new WebmFloat(
454                            kMkvPrimaryGChromaticityY, info->sType1.mG.y * 0.00002));
455                    masteringInfo.push_back(new WebmFloat(
456                            kMkvPrimaryBChromaticityX, info->sType1.mB.x * 0.00002));
457                    masteringInfo.push_back(new WebmFloat(
458                            kMkvPrimaryBChromaticityY, info->sType1.mB.y * 0.00002));
459                }
460                if (masteringInfo.size()) {
461                    colorInfo.push_back(new WebmMaster(kMkvMasteringMetadata, masteringInfo));
462                }
463            }
464        }
465        if (colorInfo.size()) {
466            videoInfo.push_back(new WebmMaster(kMkvColour, colorInfo));
467        }
468    }
469
470    trackEntryFields.push_back(new WebmMaster(kMkvVideo, videoInfo));
471    return new WebmMaster(kMkvTrackEntry, trackEntryFields);
472}
473} /* namespace android */
474