stagefright: add HEVC support to MediaRecorder
Bug: 22879917
Change-Id: I6c97b051467de44c506a8ff021321e5953a63fc3
diff --git a/media/libmedia/MediaProfiles.cpp b/media/libmedia/MediaProfiles.cpp
index 2a17696..ff0e52e 100644
--- a/media/libmedia/MediaProfiles.cpp
+++ b/media/libmedia/MediaProfiles.cpp
@@ -37,7 +37,8 @@
const MediaProfiles::NameToTagMap MediaProfiles::sVideoEncoderNameMap[] = {
{"h263", VIDEO_ENCODER_H263},
{"h264", VIDEO_ENCODER_H264},
- {"m4v", VIDEO_ENCODER_MPEG_4_SP}
+ {"m4v", VIDEO_ENCODER_MPEG_4_SP},
+ {"hevc", VIDEO_ENCODER_HEVC}
};
const MediaProfiles::NameToTagMap MediaProfiles::sAudioEncoderNameMap[] = {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 73d07a0..405df06 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1226,6 +1226,7 @@
(mVideoEncoder == VIDEO_ENCODER_H263 ? MEDIA_MIMETYPE_VIDEO_H263 :
mVideoEncoder == VIDEO_ENCODER_MPEG_4_SP ? MEDIA_MIMETYPE_VIDEO_MPEG4 :
mVideoEncoder == VIDEO_ENCODER_VP8 ? MEDIA_MIMETYPE_VIDEO_VP8 :
+ mVideoEncoder == VIDEO_ENCODER_HEVC ? MEDIA_MIMETYPE_VIDEO_HEVC :
mVideoEncoder == VIDEO_ENCODER_H264 ? MEDIA_MIMETYPE_VIDEO_AVC : ""),
false /* decoder */, true /* hwCodec */, &codecs);
@@ -1515,6 +1516,10 @@
format->setString("mime", MEDIA_MIMETYPE_VIDEO_VP8);
break;
+ case VIDEO_ENCODER_HEVC:
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_HEVC);
+ break;
+
default:
CHECK(!"Should not be here, unsupported video encoding.");
break;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index cb5cfe3..29f305c 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -3530,8 +3530,8 @@
hevcType.eProfile = static_cast<OMX_VIDEO_HEVCPROFILETYPE>(profile);
hevcType.eLevel = static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level);
}
-
- // TODO: Need OMX structure definition for setting iFrameInterval
+ // TODO: finer control?
+ hevcType.nKeyFrameInterval = setPFramesSpacing(iFrameInterval, frameRate);
err = mOMX->setParameter(
mNode, (OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType));
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 9a4a350..14a0c66 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -24,6 +24,7 @@
FLACExtractor.cpp \
FrameRenderTracker.cpp \
HTTPBase.cpp \
+ HevcUtils.cpp \
JPEGSource.cpp \
MP3Extractor.cpp \
MPEG2TSWriter.cpp \
diff --git a/media/libstagefright/HevcUtils.cpp b/media/libstagefright/HevcUtils.cpp
new file mode 100644
index 0000000..087c903
--- /dev/null
+++ b/media/libstagefright/HevcUtils.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2015 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 "HevcUtils"
+
+#include <cstring>
+
+#include "include/HevcUtils.h"
+#include "include/avc_utils.h"
+
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+static const uint8_t kHevcNalUnitTypes[5] = {
+ kHevcNalUnitTypeVps,
+ kHevcNalUnitTypeSps,
+ kHevcNalUnitTypePps,
+ kHevcNalUnitTypePrefixSei,
+ kHevcNalUnitTypeSuffixSei,
+};
+
+HevcParameterSets::HevcParameterSets() {
+}
+
+status_t HevcParameterSets::addNalUnit(const uint8_t* data, size_t size) {
+ uint8_t nalUnitType = (data[0] >> 1) & 0x3f;
+ status_t err = OK;
+ switch (nalUnitType) {
+ case 32: // VPS
+ err = parseVps(data + 2, size - 2);
+ break;
+ case 33: // SPS
+ err = parseSps(data + 2, size - 2);
+ break;
+ case 34: // PPS
+ err = parsePps(data + 2, size - 2);
+ break;
+ case 39: // Prefix SEI
+ case 40: // Suffix SEI
+ // Ignore
+ break;
+ default:
+ ALOGE("Unrecognized NAL unit type.");
+ return ERROR_MALFORMED;
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ sp<ABuffer> buffer = ABuffer::CreateAsCopy(data, size);
+ buffer->setInt32Data(nalUnitType);
+ mNalUnits.push(buffer);
+ return OK;
+}
+
+template <typename T>
+static bool findParam(uint32_t key, T *param,
+ KeyedVector<uint32_t, uint64_t> ¶ms) {
+ CHECK(param);
+ if (params.indexOfKey(key) < 0) {
+ return false;
+ }
+ *param = (T) params[key];
+ return true;
+}
+
+bool HevcParameterSets::findParam8(uint32_t key, uint8_t *param) {
+ return findParam(key, param, mParams);
+}
+
+bool HevcParameterSets::findParam16(uint32_t key, uint16_t *param) {
+ return findParam(key, param, mParams);
+}
+
+bool HevcParameterSets::findParam32(uint32_t key, uint32_t *param) {
+ return findParam(key, param, mParams);
+}
+
+bool HevcParameterSets::findParam64(uint32_t key, uint64_t *param) {
+ return findParam(key, param, mParams);
+}
+
+size_t HevcParameterSets::getNumNalUnitsOfType(uint8_t type) {
+ size_t num = 0;
+ for (size_t i = 0; i < mNalUnits.size(); ++i) {
+ if (getType(i) == type) {
+ ++num;
+ }
+ }
+ return num;
+}
+
+uint8_t HevcParameterSets::getType(size_t index) {
+ CHECK_LT(index, mNalUnits.size());
+ return mNalUnits[index]->int32Data();
+}
+
+size_t HevcParameterSets::getSize(size_t index) {
+ CHECK_LT(index, mNalUnits.size());
+ return mNalUnits[index]->size();
+}
+
+bool HevcParameterSets::write(size_t index, uint8_t* dest, size_t size) {
+ CHECK_LT(index, mNalUnits.size());
+ const sp<ABuffer>& nalUnit = mNalUnits[index];
+ if (size < nalUnit->size()) {
+ ALOGE("dest buffer size too small: %zu vs. %zu to be written",
+ size, nalUnit->size());
+ return false;
+ }
+ memcpy(dest, nalUnit->data(), nalUnit->size());
+ return true;
+}
+
+status_t HevcParameterSets::parseVps(const uint8_t* data, size_t size) {
+ // See Rec. ITU-T H.265 v3 (04/2015) Chapter 7.3.2.1 for reference
+ NALBitReader reader(data, size);
+ // Skip vps_video_parameter_set_id
+ reader.skipBits(4);
+ // Skip vps_base_layer_internal_flag
+ reader.skipBits(1);
+ // Skip vps_base_layer_available_flag
+ reader.skipBits(1);
+ // Skip vps_max_layers_minus_1
+ reader.skipBits(6);
+ // Skip vps_temporal_id_nesting_flags
+ reader.skipBits(1);
+ // Skip reserved
+ reader.skipBits(16);
+
+ mParams.add(kGeneralProfileSpace, reader.getBits(2));
+ mParams.add(kGeneralTierFlag, reader.getBits(1));
+ mParams.add(kGeneralProfileIdc, reader.getBits(5));
+ mParams.add(kGeneralProfileCompatibilityFlags, reader.getBits(32));
+ mParams.add(
+ kGeneralConstraintIndicatorFlags,
+ ((uint64_t)reader.getBits(16) << 32) | reader.getBits(32));
+ mParams.add(kGeneralLevelIdc, reader.getBits(8));
+ // 96 bits total for general profile.
+
+ return OK;
+}
+
+status_t HevcParameterSets::parseSps(const uint8_t* data, size_t size) {
+ // See Rec. ITU-T H.265 v3 (04/2015) Chapter 7.3.2.2 for reference
+ NALBitReader reader(data, size);
+ // Skip sps_video_parameter_set_id
+ reader.skipBits(4);
+ uint8_t maxSubLayersMinus1 = reader.getBits(3);
+ // Skip sps_temporal_id_nesting_flag;
+ reader.skipBits(1);
+ // Skip general profile
+ reader.skipBits(96);
+ if (maxSubLayersMinus1 > 0) {
+ bool subLayerProfilePresentFlag[8];
+ bool subLayerLevelPresentFlag[8];
+ for (int i = 0; i < maxSubLayersMinus1; ++i) {
+ subLayerProfilePresentFlag[i] = reader.getBits(1);
+ subLayerLevelPresentFlag[i] = reader.getBits(1);
+ }
+ // Skip reserved
+ reader.skipBits(2 * (8 - maxSubLayersMinus1));
+ for (int i = 0; i < maxSubLayersMinus1; ++i) {
+ if (subLayerProfilePresentFlag[i]) {
+ // Skip profile
+ reader.skipBits(88);
+ }
+ if (subLayerLevelPresentFlag[i]) {
+ // Skip sub_layer_level_idc[i]
+ reader.skipBits(8);
+ }
+ }
+ }
+ // Skip sps_seq_parameter_set_id
+ parseUE(&reader);
+ uint8_t chromaFormatIdc = parseUE(&reader);
+ mParams.add(kChromaFormatIdc, chromaFormatIdc);
+ if (chromaFormatIdc == 3) {
+ // Skip separate_colour_plane_flag
+ reader.skipBits(1);
+ }
+ // Skip pic_width_in_luma_samples
+ parseUE(&reader);
+ // Skip pic_height_in_luma_samples
+ parseUE(&reader);
+ if (reader.getBits(1) /* i.e. conformance_window_flag */) {
+ // Skip conf_win_left_offset
+ parseUE(&reader);
+ // Skip conf_win_right_offset
+ parseUE(&reader);
+ // Skip conf_win_top_offset
+ parseUE(&reader);
+ // Skip conf_win_bottom_offset
+ parseUE(&reader);
+ }
+ mParams.add(kBitDepthLumaMinus8, parseUE(&reader));
+ mParams.add(kBitDepthChromaMinus8, parseUE(&reader));
+
+ return OK;
+}
+
+status_t HevcParameterSets::parsePps(
+ const uint8_t* data __unused, size_t size __unused) {
+ return OK;
+}
+
+status_t HevcParameterSets::makeHvcc(uint8_t *hvcc, size_t *hvccSize,
+ size_t nalSizeLength) {
+ if (hvcc == NULL || hvccSize == NULL
+ || (nalSizeLength != 4 && nalSizeLength != 2)) {
+ return BAD_VALUE;
+ }
+ // ISO 14496-15: HEVC file format
+ size_t size = 23; // 23 bytes in the header
+ size_t numOfArrays = 0;
+ const size_t numNalUnits = getNumNalUnits();
+ for (size_t i = 0; i < ARRAY_SIZE(kHevcNalUnitTypes); ++i) {
+ uint8_t type = kHevcNalUnitTypes[i];
+ size_t numNalus = getNumNalUnitsOfType(type);
+ if (numNalus == 0) {
+ continue;
+ }
+ ++numOfArrays;
+ size += 3;
+ for (size_t j = 0; j < numNalUnits; ++j) {
+ if (getType(j) != type) {
+ continue;
+ }
+ size += 2 + getSize(j);
+ }
+ }
+ uint8_t generalProfileSpace, generalTierFlag, generalProfileIdc;
+ if (!findParam8(kGeneralProfileSpace, &generalProfileSpace)
+ || !findParam8(kGeneralTierFlag, &generalTierFlag)
+ || !findParam8(kGeneralProfileIdc, &generalProfileIdc)) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t compatibilityFlags;
+ uint64_t constraintIdcFlags;
+ if (!findParam32(kGeneralProfileCompatibilityFlags, &compatibilityFlags)
+ || !findParam64(kGeneralConstraintIndicatorFlags, &constraintIdcFlags)) {
+ return ERROR_MALFORMED;
+ }
+ uint8_t generalLevelIdc;
+ if (!findParam8(kGeneralLevelIdc, &generalLevelIdc)) {
+ return ERROR_MALFORMED;
+ }
+ uint8_t chromaFormatIdc, bitDepthLumaMinus8, bitDepthChromaMinus8;
+ if (!findParam8(kChromaFormatIdc, &chromaFormatIdc)
+ || !findParam8(kBitDepthLumaMinus8, &bitDepthLumaMinus8)
+ || !findParam8(kBitDepthChromaMinus8, &bitDepthChromaMinus8)) {
+ return ERROR_MALFORMED;
+ }
+ if (size > *hvccSize) {
+ return NO_MEMORY;
+ }
+ *hvccSize = size;
+
+ uint8_t *header = hvcc;
+ header[0] = 1;
+ header[1] = (kGeneralProfileSpace << 6) | (kGeneralTierFlag << 5) | kGeneralProfileIdc;
+ header[2] = (compatibilityFlags >> 24) & 0xff;
+ header[3] = (compatibilityFlags >> 16) & 0xff;
+ header[4] = (compatibilityFlags >> 8) & 0xff;
+ header[5] = compatibilityFlags & 0xff;
+ header[6] = (constraintIdcFlags >> 40) & 0xff;
+ header[7] = (constraintIdcFlags >> 32) & 0xff;
+ header[8] = (constraintIdcFlags >> 24) & 0xff;
+ header[9] = (constraintIdcFlags >> 16) & 0xff;
+ header[10] = (constraintIdcFlags >> 8) & 0xff;
+ header[11] = constraintIdcFlags & 0xff;
+ header[12] = generalLevelIdc;
+ // FIXME: parse min_spatial_segmentation_idc.
+ header[13] = 0xf0;
+ header[14] = 0;
+ // FIXME: derive parallelismType properly.
+ header[15] = 0xfc;
+ header[16] = 0xfc | chromaFormatIdc;
+ header[17] = 0xf8 | bitDepthLumaMinus8;
+ header[18] = 0xf8 | bitDepthChromaMinus8;
+ // FIXME: derive avgFrameRate
+ header[19] = 0;
+ header[20] = 0;
+ // constantFrameRate, numTemporalLayers, temporalIdNested all set to 0.
+ header[21] = nalSizeLength - 1;
+ header[22] = numOfArrays;
+ header += 23;
+ for (size_t i = 0; i < ARRAY_SIZE(kHevcNalUnitTypes); ++i) {
+ uint8_t type = kHevcNalUnitTypes[i];
+ size_t numNalus = getNumNalUnitsOfType(type);
+ if (numNalus == 0) {
+ continue;
+ }
+ // array_completeness set to 0.
+ header[0] = type;
+ header[1] = (numNalus >> 8) & 0xff;
+ header[2] = numNalus & 0xff;
+ header += 3;
+ for (size_t j = 0; j < numNalUnits; ++j) {
+ if (getType(j) != type) {
+ continue;
+ }
+ header[0] = (getSize(j) >> 8) & 0xff;
+ header[1] = getSize(j) & 0xff;
+ if (!write(j, header + 2, size - (header - (uint8_t *)hvcc))) {
+ return NO_MEMORY;
+ }
+ header += (2 + getSize(j));
+ }
+ }
+ CHECK_EQ(header - size, hvcc);
+
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index ea4a7ac..a5d9484 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -41,7 +41,7 @@
#include <cutils/properties.h>
#include "include/ESDS.h"
-
+#include "include/HevcUtils.h"
#ifndef __predict_false
#define __predict_false(exp) __builtin_expect((exp) != 0, 0)
@@ -70,6 +70,18 @@
#endif
static const char kMetaKey_CaptureFps[] = "com.android.capture.fps";
+static const uint8_t kMandatoryHevcNalUnitTypes[3] = {
+ kHevcNalUnitTypeVps,
+ kHevcNalUnitTypeSps,
+ kHevcNalUnitTypePps,
+};
+static const uint8_t kHevcNalUnitTypes[5] = {
+ kHevcNalUnitTypeVps,
+ kHevcNalUnitTypeSps,
+ kHevcNalUnitTypePps,
+ kHevcNalUnitTypePrefixSei,
+ kHevcNalUnitTypeSuffixSei,
+};
/* uncomment to include model and build in meta */
//#define SHOW_MODEL_BUILD 1
@@ -89,6 +101,7 @@
void writeTrackHeader(bool use32BitOffset = true);
void bufferChunk(int64_t timestampUs);
bool isAvc() const { return mIsAvc; }
+ bool isHevc() const { return mIsHevc; }
bool isAudio() const { return mIsAudio; }
bool isMPEG4() const { return mIsMPEG4; }
void addChunkOffset(off64_t offset);
@@ -234,6 +247,7 @@
volatile bool mResumed;
volatile bool mStarted;
bool mIsAvc;
+ bool mIsHevc;
bool mIsAudio;
bool mIsMPEG4;
int32_t mTrackId;
@@ -299,10 +313,17 @@
const uint8_t *parseParamSet(
const uint8_t *data, size_t length, int type, size_t *paramSetLen);
+ status_t copyCodecSpecificData(const uint8_t *data, size_t size, size_t minLength = 0);
+
status_t makeAVCCodecSpecificData(const uint8_t *data, size_t size);
status_t copyAVCCodecSpecificData(const uint8_t *data, size_t size);
status_t parseAVCCodecSpecificData(const uint8_t *data, size_t size);
+ status_t makeHEVCCodecSpecificData(const uint8_t *data, size_t size);
+ status_t copyHEVCCodecSpecificData(const uint8_t *data, size_t size);
+ status_t parseHEVCCodecSpecificData(
+ const uint8_t *data, size_t size, HevcParameterSets ¶mSets);
+
// Track authoring progress status
void trackProgressStatus(int64_t timeUs, status_t err = OK);
void initTrackingProgressStatus(MetaData *params);
@@ -340,6 +361,7 @@
void writeD263Box();
void writePaspBox();
void writeAvccBox();
+ void writeHvccBox();
void writeUrlBox();
void writeDrefBox();
void writeDinfBox();
@@ -463,6 +485,8 @@
return "s263";
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
return "avc1";
+ } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_HEVC, mime)) {
+ return "hvc1";
}
} else {
ALOGE("Track (%s) other than video or audio is not supported", mime);
@@ -1465,6 +1489,7 @@
const char *mime;
mMeta->findCString(kKeyMIMEType, &mime);
mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+ mIsHevc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
mIsAudio = !strncasecmp(mime, "audio/", 6);
mIsMPEG4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
@@ -1560,31 +1585,26 @@
const char *mime;
CHECK(mMeta->findCString(kKeyMIMEType, &mime));
+ uint32_t type;
+ const void *data = NULL;
+ size_t size = 0;
if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
- uint32_t type;
- const void *data;
- size_t size;
- if (mMeta->findData(kKeyAVCC, &type, &data, &size)) {
- mCodecSpecificData = malloc(size);
- mCodecSpecificDataSize = size;
- memcpy(mCodecSpecificData, data, size);
- mGotAllCodecSpecificData = true;
- }
+ mMeta->findData(kKeyAVCC, &type, &data, &size);
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
+ mMeta->findData(kKeyHVCC, &type, &data, &size);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
- uint32_t type;
- const void *data;
- size_t size;
if (mMeta->findData(kKeyESDS, &type, &data, &size)) {
ESDS esds(data, size);
- if (esds.getCodecSpecificInfo(&data, &size) == OK) {
- mCodecSpecificData = malloc(size);
- mCodecSpecificDataSize = size;
- memcpy(mCodecSpecificData, data, size);
- mGotAllCodecSpecificData = true;
+ if (esds.getCodecSpecificInfo(&data, &size) != OK) {
+ data = NULL;
+ size = 0;
}
}
}
+ if (data != NULL && copyCodecSpecificData((uint8_t *)data, size) == OK) {
+ mGotAllCodecSpecificData = true;
+ }
}
MPEG4Writer::Track::~Track() {
@@ -1661,7 +1681,7 @@
while (!chunk->mSamples.empty()) {
List<MediaBuffer *>::iterator it = chunk->mSamples.begin();
- off64_t offset = chunk->mTrack->isAvc()
+ off64_t offset = (chunk->mTrack->isAvc() || chunk->mTrack->isHevc())
? addLengthPrefixedSample_l(*it)
: addSample_l(*it);
@@ -1968,13 +1988,30 @@
// 2 bytes for each of the parameter set length field
// plus the 7 bytes for the header
- if (size < 4 + 7) {
+ return copyCodecSpecificData(data, size, 4 + 7);
+}
+
+status_t MPEG4Writer::Track::copyHEVCCodecSpecificData(
+ const uint8_t *data, size_t size) {
+ ALOGV("copyHEVCCodecSpecificData");
+
+ // Min length of HEVC CSD is 23. (ISO/IEC 14496-15:2014 Chapter 8.3.3.1.2)
+ return copyCodecSpecificData(data, size, 23);
+}
+
+status_t MPEG4Writer::Track::copyCodecSpecificData(
+ const uint8_t *data, size_t size, size_t minLength) {
+ if (size < minLength) {
ALOGE("Codec specific data length too short: %zu", size);
return ERROR_MALFORMED;
}
- mCodecSpecificDataSize = size;
mCodecSpecificData = malloc(size);
+ if (mCodecSpecificData == NULL) {
+ ALOGE("Failed allocating codec specific data");
+ return NO_MEMORY;
+ }
+ mCodecSpecificDataSize = size;
memcpy(mCodecSpecificData, data, size);
return OK;
}
@@ -2097,6 +2134,11 @@
// ISO 14496-15: AVC file format
mCodecSpecificDataSize += 7; // 7 more bytes in the header
mCodecSpecificData = malloc(mCodecSpecificDataSize);
+ if (mCodecSpecificData == NULL) {
+ mCodecSpecificDataSize = 0;
+ ALOGE("Failed allocating codec specific data");
+ return NO_MEMORY;
+ }
uint8_t *header = (uint8_t *)mCodecSpecificData;
header[0] = 1; // version
header[1] = mProfileIdc; // profile indication
@@ -2145,6 +2187,96 @@
return OK;
}
+
+status_t MPEG4Writer::Track::parseHEVCCodecSpecificData(
+ const uint8_t *data, size_t size, HevcParameterSets ¶mSets) {
+
+ ALOGV("parseHEVCCodecSpecificData");
+ const uint8_t *tmp = data;
+ const uint8_t *nextStartCode = data;
+ size_t bytesLeft = size;
+ while (bytesLeft > 4 && !memcmp("\x00\x00\x00\x01", tmp, 4)) {
+ nextStartCode = findNextStartCode(tmp + 4, bytesLeft - 4);
+ if (nextStartCode == NULL) {
+ return ERROR_MALFORMED;
+ }
+ status_t err = paramSets.addNalUnit(tmp + 4, (nextStartCode - tmp) - 4);
+ if (err != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ // Move on to find the next parameter set
+ bytesLeft -= nextStartCode - tmp;
+ tmp = nextStartCode;
+ }
+
+ size_t csdSize = 23;
+ const size_t numNalUnits = paramSets.getNumNalUnits();
+ for (size_t i = 0; i < ARRAY_SIZE(kMandatoryHevcNalUnitTypes); ++i) {
+ int type = kMandatoryHevcNalUnitTypes[i];
+ size_t numParamSets = paramSets.getNumNalUnitsOfType(type);
+ if (numParamSets == 0) {
+ ALOGE("Cound not find NAL unit of type %d", type);
+ return ERROR_MALFORMED;
+ }
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(kHevcNalUnitTypes); ++i) {
+ int type = kHevcNalUnitTypes[i];
+ size_t numParamSets = paramSets.getNumNalUnitsOfType(type);
+ if (numParamSets > 0xffff) {
+ ALOGE("Too many seq parameter sets (%zu) found", numParamSets);
+ return ERROR_MALFORMED;
+ }
+ csdSize += 3;
+ for (size_t j = 0; j < numNalUnits; ++j) {
+ if (paramSets.getType(j) != type) {
+ continue;
+ }
+ csdSize += 2 + paramSets.getSize(j);
+ }
+ }
+ mCodecSpecificDataSize = csdSize;
+ return OK;
+}
+
+status_t MPEG4Writer::Track::makeHEVCCodecSpecificData(
+ const uint8_t *data, size_t size) {
+
+ if (mCodecSpecificData != NULL) {
+ ALOGE("Already have codec specific data");
+ return ERROR_MALFORMED;
+ }
+
+ if (size < 4) {
+ ALOGE("Codec specific data length too short: %zu", size);
+ return ERROR_MALFORMED;
+ }
+
+ // Data is in the form of HEVCCodecSpecificData
+ if (memcmp("\x00\x00\x00\x01", data, 4)) {
+ return copyHEVCCodecSpecificData(data, size);
+ }
+
+ HevcParameterSets paramSets;
+ if (parseHEVCCodecSpecificData(data, size, paramSets) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ mCodecSpecificData = malloc(mCodecSpecificDataSize);
+ if (mCodecSpecificData == NULL) {
+ mCodecSpecificDataSize = 0;
+ ALOGE("Failed allocating codec specific data");
+ return NO_MEMORY;
+ }
+ status_t err = paramSets.makeHvcc((uint8_t *)mCodecSpecificData,
+ &mCodecSpecificDataSize, mOwner->useNalLengthFour() ? 5 : 2);
+ if (err != OK) {
+ return err;
+ }
+
+ return OK;
+}
+
/*
* Updates the drift time from the audio track so that
* the video track can get the updated drift time information
@@ -2228,13 +2360,15 @@
+ buffer->range_offset(),
buffer->range_length());
CHECK_EQ((status_t)OK, err);
- } else if (mIsMPEG4) {
- mCodecSpecificDataSize = buffer->range_length();
- mCodecSpecificData = malloc(mCodecSpecificDataSize);
- memcpy(mCodecSpecificData,
+ } else if (mIsHevc) {
+ status_t err = makeHEVCCodecSpecificData(
(const uint8_t *)buffer->data()
+ buffer->range_offset(),
- buffer->range_length());
+ buffer->range_length());
+ CHECK_EQ((status_t)OK, err);
+ } else if (mIsMPEG4) {
+ copyCodecSpecificData((const uint8_t *)buffer->data() + buffer->range_offset(),
+ buffer->range_length());
}
buffer->release();
@@ -2254,10 +2388,10 @@
buffer->release();
buffer = NULL;
- if (mIsAvc) StripStartcode(copy);
+ if (mIsAvc || mIsHevc) StripStartcode(copy);
size_t sampleSize = copy->range_length();
- if (mIsAvc) {
+ if (mIsAvc || mIsHevc) {
if (mOwner->useNalLengthFour()) {
sampleSize += 4;
} else {
@@ -2457,7 +2591,7 @@
trackProgressStatus(timestampUs);
}
if (!hasMultipleTracks) {
- off64_t offset = mIsAvc? mOwner->addLengthPrefixedSample_l(copy)
+ off64_t offset = (mIsAvc || mIsHevc) ? mOwner->addLengthPrefixedSample_l(copy)
: mOwner->addSample_l(copy);
uint32_t count = (mOwner->use32BitFileOffset()
@@ -2709,7 +2843,8 @@
CHECK(mMeta->findCString(kKeyMIMEType, &mime));
if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime) ||
!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime) ||
- !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
+ !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime) ||
+ !strcasecmp(MEDIA_MIMETYPE_VIDEO_HEVC, mime)) {
if (!mCodecSpecificData ||
mCodecSpecificDataSize <= 0) {
ALOGE("Missing codec specific data");
@@ -2815,6 +2950,8 @@
writeD263Box();
} else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
writeAvccBox();
+ } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_HEVC, mime)) {
+ writeHvccBox();
}
writePaspBox();
@@ -3070,6 +3207,20 @@
mOwner->endBox(); // avcC
}
+
+void MPEG4Writer::Track::writeHvccBox() {
+ CHECK(mCodecSpecificData);
+ CHECK_GE(mCodecSpecificDataSize, 5);
+
+ // Patch avcc's lengthSize field to match the number
+ // of bytes we use to indicate the size of a nal unit.
+ uint8_t *ptr = (uint8_t *)mCodecSpecificData;
+ ptr[21] = (ptr[21] & 0xfc) | (mOwner->useNalLengthFour() ? 3 : 1);
+ mOwner->beginBox("hvcC");
+ mOwner->write(mCodecSpecificData, mCodecSpecificDataSize);
+ mOwner->endBox(); // hvcC
+}
+
void MPEG4Writer::Track::writeD263Box() {
mOwner->beginBox("d263");
mOwner->writeInt32(0); // vendor
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 2a8b635..13ad03a 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -22,6 +22,7 @@
#include <sys/stat.h>
#include "include/ESDS.h"
+#include "include/HevcUtils.h"
#include <arpa/inet.h>
#include <cutils/properties.h>
@@ -575,6 +576,41 @@
}
+static size_t reassembleHVCC(const sp<ABuffer> &csd0, uint8_t *hvcc, size_t hvccSize, size_t nalSizeLength) {
+ HevcParameterSets paramSets;
+ uint8_t* data = csd0->data();
+ if (csd0->size() < 4) {
+ ALOGE("csd0 too small");
+ return 0;
+ }
+ if (memcmp(data, "\x00\x00\x00\x01", 4) != 0) {
+ ALOGE("csd0 doesn't start with a start code");
+ return 0;
+ }
+ size_t prevNalOffset = 4;
+ status_t err = OK;
+ for (size_t i = 1; i < csd0->size() - 4; ++i) {
+ if (memcmp(&data[i], "\x00\x00\x00\x01", 4) != 0) {
+ continue;
+ }
+ err = paramSets.addNalUnit(&data[prevNalOffset], i - prevNalOffset);
+ if (err != OK) {
+ return 0;
+ }
+ prevNalOffset = i + 4;
+ }
+ err = paramSets.addNalUnit(&data[prevNalOffset], csd0->size() - prevNalOffset);
+ if (err != OK) {
+ return 0;
+ }
+ size_t size = hvccSize;
+ err = paramSets.makeHvcc(hvcc, &size, nalSizeLength);
+ if (err != OK) {
+ return 0;
+ }
+ return size;
+}
+
void convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
AString mime;
if (msg->findString("mime", &mime)) {
@@ -693,6 +729,10 @@
// for transporting the CSD to muxers.
reassembleESDS(csd0, esds);
meta->setData(kKeyESDS, kKeyESDS, esds, sizeof(esds));
+ } else if (mime == MEDIA_MIMETYPE_VIDEO_HEVC) {
+ uint8_t hvcc[1024]; // that oughta be enough, right?
+ size_t outsize = reassembleHVCC(csd0, hvcc, 1024, 4);
+ meta->setData(kKeyHVCC, kKeyHVCC, hvcc, outsize);
}
}
diff --git a/media/libstagefright/include/HevcUtils.h b/media/libstagefright/include/HevcUtils.h
new file mode 100644
index 0000000..0d7bb2f
--- /dev/null
+++ b/media/libstagefright/include/HevcUtils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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 HEVC_UTILS_H_
+
+#define HEVC_UTILS_H_
+
+#include <stdint.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+enum {
+ kHevcNalUnitTypeVps = 32,
+ kHevcNalUnitTypeSps = 33,
+ kHevcNalUnitTypePps = 34,
+ kHevcNalUnitTypePrefixSei = 39,
+ kHevcNalUnitTypeSuffixSei = 40,
+};
+
+enum {
+ // uint8_t
+ kGeneralProfileSpace,
+ // uint8_t
+ kGeneralTierFlag,
+ // uint8_t
+ kGeneralProfileIdc,
+ // uint32_t
+ kGeneralProfileCompatibilityFlags,
+ // uint64_t
+ kGeneralConstraintIndicatorFlags,
+ // uint8_t
+ kGeneralLevelIdc,
+ // uint8_t
+ kChromaFormatIdc,
+ // uint8_t
+ kBitDepthLumaMinus8,
+ // uint8_t
+ kBitDepthChromaMinus8,
+};
+
+class HevcParameterSets {
+public:
+ HevcParameterSets();
+
+ status_t addNalUnit(const uint8_t* data, size_t size);
+
+ bool findParam8(uint32_t key, uint8_t *param);
+ bool findParam16(uint32_t key, uint16_t *param);
+ bool findParam32(uint32_t key, uint32_t *param);
+ bool findParam64(uint32_t key, uint64_t *param);
+
+ inline size_t getNumNalUnits() { return mNalUnits.size(); }
+ size_t getNumNalUnitsOfType(uint8_t type);
+ uint8_t getType(size_t index);
+ size_t getSize(size_t index);
+ // Note that this method does not write the start code.
+ bool write(size_t index, uint8_t* dest, size_t size);
+ status_t makeHvcc(uint8_t *hvcc, size_t *hvccSize, size_t nalSizeLength);
+
+private:
+ status_t parseVps(const uint8_t* data, size_t size);
+ status_t parseSps(const uint8_t* data, size_t size);
+ status_t parsePps(const uint8_t* data, size_t size);
+
+ KeyedVector<uint32_t, uint64_t> mParams;
+ Vector<sp<ABuffer>> mNalUnits;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HevcParameterSets);
+};
+
+} // namespace android
+
+#endif // HEVC_UTILS_H_