/*
* Copyright (c) 2009-2011 Intel Corporation. All rights reserved.
*
* 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 "VideoDecoderAVCSecure.h"
#include "VideoDecoderTrace.h"
#include <string.h>
#define STARTCODE_00 0x00
#define STARTCODE_01 0x01
#define STARTCODE_PREFIX_LEN 3
#define NALU_TYPE_MASK 0x1F
// mask for little endian, to mast the second and fourth bytes in the byte stream
#define STARTCODE_MASK0 0xFF000000 //0x00FF0000
#define STARTCODE_MASK1 0x0000FF00 //0x000000FF
typedef enum {
NAL_UNIT_TYPE_unspecified0 = 0,
NAL_UNIT_TYPE_SLICE,
NAL_UNIT_TYPE_DPA,
NAL_UNIT_TYPE_DPB,
NAL_UNIT_TYPE_DPC,
NAL_UNIT_TYPE_IDR,
NAL_UNIT_TYPE_SEI,
NAL_UNIT_TYPE_SPS,
NAL_UNIT_TYPE_PPS,
NAL_UNIT_TYPE_Acc_unit_delimiter,
NAL_UNIT_TYPE_EOSeq,
NAL_UNIT_TYPE_EOstream,
NAL_UNIT_TYPE_filler_data,
NAL_UNIT_TYPE_SPS_extension,
NAL_UNIT_TYPE_Reserved14,
NAL_UNIT_TYPE_Reserved15,
NAL_UNIT_TYPE_Reserved16,
NAL_UNIT_TYPE_Reserved17,
NAL_UNIT_TYPE_Reserved18,
NAL_UNIT_TYPE_ACP,
NAL_UNIT_TYPE_Reserved20,
NAL_UNIT_TYPE_Reserved21,
NAL_UNIT_TYPE_Reserved22,
NAL_UNIT_TYPE_Reserved23,
NAL_UNIT_TYPE_unspecified24,
} NAL_UNIT_TYPE;
#ifndef min
#define min(X, Y) ((X) <(Y) ? (X) : (Y))
#endif
static const uint8_t startcodePrefix[STARTCODE_PREFIX_LEN] = {0x00, 0x00, 0x01};
VideoDecoderAVCSecure::VideoDecoderAVCSecure(const char *mimeType)
: VideoDecoderAVC(mimeType),
mNaluHeaderBuffer(NULL),
mInputBuffer(NULL) {
memset(&mMetadata, 0, sizeof(NaluMetadata));
memset(&mByteStream, 0, sizeof(NaluByteStream));
}
VideoDecoderAVCSecure::~VideoDecoderAVCSecure() {
}
Decode_Status VideoDecoderAVCSecure::start(VideoConfigBuffer *buffer) {
Decode_Status status = VideoDecoderAVC::start(buffer);
if (status != DECODE_SUCCESS) {
return status;
}
mMetadata.naluInfo = new NaluInfo [MAX_NALU_NUMBER];
mByteStream.byteStream = new uint8_t [MAX_NALU_HEADER_BUFFER];
mNaluHeaderBuffer = new uint8_t [MAX_NALU_HEADER_BUFFER];
if (mMetadata.naluInfo == NULL ||
mByteStream.byteStream == NULL ||
mNaluHeaderBuffer == NULL) {
ETRACE("Failed to allocate memory.");
// TODO: release all allocated memory
return DECODE_MEMORY_FAIL;
}
return status;
}
void VideoDecoderAVCSecure::stop(void) {
VideoDecoderAVC::stop();
if (mMetadata.naluInfo) {
delete [] mMetadata.naluInfo;
mMetadata.naluInfo = NULL;
}
if (mByteStream.byteStream) {
delete [] mByteStream.byteStream;
mByteStream.byteStream = NULL;
}
if (mNaluHeaderBuffer) {
delete [] mNaluHeaderBuffer;
mNaluHeaderBuffer = NULL;
}
}
Decode_Status VideoDecoderAVCSecure::decode(VideoDecodeBuffer *buffer) {
Decode_Status status;
int32_t sizeAccumulated = 0;
int32_t sizeLeft = 0;
uint8_t *pByteStream = NULL;
NaluInfo *pNaluInfo = mMetadata.naluInfo;
if (buffer->flag & IS_SECURE_DATA) {
// NALU headers are appended to encrypted video bitstream
// |...encrypted video bitstream (16 bytes aligned)...| 4 bytes of header size |...NALU headers..|
pByteStream = buffer->data + buffer->size + 4;
sizeLeft = *(int32_t *)(buffer->data + buffer->size);
VTRACE("%s sizeLeft: %d buffer->size: %#x", __func__, sizeLeft, buffer->size);
mInputBuffer = buffer->data;
} else {
status = parseAnnexBStream(buffer->data, buffer->size, &mByteStream);
CHECK_STATUS("parseAnnexBStream");
pByteStream = mByteStream.byteStream;
sizeLeft = mByteStream.streamPos;
mInputBuffer = buffer->data;
}
if (sizeLeft < 4) {
ETRACE("Not enough data to read number of NALU.");
return DECODE_INVALID_DATA;
}
// read number of NALU
memcpy(&(mMetadata.naluNumber), pByteStream, sizeof(int32_t));
pByteStream += 4;
sizeLeft -= 4;
if (mMetadata.naluNumber == 0) {
WTRACE("Number of NALU is ZERO!");
return DECODE_SUCCESS;
}
for (int32_t i = 0; i < mMetadata.naluNumber; i++) {
if (sizeLeft < 12) {
ETRACE("Not enough data to parse NALU offset, size, header length for NALU %d, left = %d", i, sizeLeft);
return DECODE_INVALID_DATA;
}
sizeLeft -= 12;
// read NALU offset
memcpy(&(pNaluInfo->naluOffset), pByteStream, sizeof(int32_t));
pByteStream += 4;
// read NALU size
memcpy(&(pNaluInfo->naluLen), pByteStream, sizeof(int32_t));
pByteStream += 4;
// read NALU header length
memcpy(&(pNaluInfo->naluHeaderLen), pByteStream, sizeof(int32_t));
pByteStream += 4;
if (sizeLeft < pNaluInfo->naluHeaderLen) {
ETRACE("Not enough data to copy NALU header for %d, left = %d, header len = %d", i, sizeLeft, pNaluInfo->naluHeaderLen);
return DECODE_INVALID_DATA;
}
sizeLeft -= pNaluInfo->naluHeaderLen;
if (pNaluInfo->naluHeaderLen) {
// copy start code prefix to buffer
memcpy(mNaluHeaderBuffer + sizeAccumulated,
startcodePrefix,
STARTCODE_PREFIX_LEN);
sizeAccumulated += STARTCODE_PREFIX_LEN;
// copy NALU header
memcpy(mNaluHeaderBuffer + sizeAccumulated, pByteStream, pNaluInfo->naluHeaderLen);
pByteStream += pNaluInfo->naluHeaderLen;
sizeAccumulated += pNaluInfo->naluHeaderLen;
} else {
WTRACE("header len is zero for NALU %d", i);
}
// for next NALU
pNaluInfo++;
}
buffer->data = mNaluHeaderBuffer;
buffer->size = sizeAccumulated;
return VideoDecoderAVC::decode(buffer);
}
Decode_Status VideoDecoderAVCSecure::decodeSlice(vbp_data_h264 *data, uint32_t picIndex, uint32_t sliceIndex) {
Decode_Status status;
VAStatus vaStatus;
uint32_t bufferIDCount = 0;
// maximum 4 buffers to render a slice: picture parameter, IQMatrix, slice parameter, slice data
VABufferID bufferIDs[4];
vbp_picture_data_h264 *picData = &(data->pic_data[picIndex]);
vbp_slice_data_h264 *sliceData = &(picData->slc_data[sliceIndex]);
VAPictureParameterBufferH264 *picParam = picData->pic_parms;
VASliceParameterBufferH264 *sliceParam = &(sliceData->slc_parms);
if (sliceParam->first_mb_in_slice == 0 || mDecodingFrame == false) {
// either condition indicates start of a new frame
if (sliceParam->first_mb_in_slice != 0) {
WTRACE("The first slice is lost.");
// TODO: handle the first slice lost
}
if (mDecodingFrame) {
// interlace content, complete decoding the first field
vaStatus = vaEndPicture(mVADisplay, mVAContext);
CHECK_VA_STATUS("vaEndPicture");
// for interlace content, top field may be valid only after the second field is parsed
mAcquiredBuffer->pictureOrder= picParam->CurrPic.TopFieldOrderCnt;
}
// Check there is no reference frame loss before decoding a frame
// Update the reference frames and surface IDs for DPB and current frame
status = updateDPB(picParam);
CHECK_STATUS("updateDPB");
//We have to provide a hacked DPB rather than complete DPB for libva as workaround
status = updateReferenceFrames(picData);
CHECK_STATUS("updateReferenceFrames");
vaStatus = vaBeginPicture(mVADisplay, mVAContext, mAcquiredBuffer->renderBuffer.surface);
CHECK_VA_STATUS("vaBeginPicture");
// start decoding a frame
mDecodingFrame = true;
vaStatus = vaCreateBuffer(
mVADisplay,
mVAContext,
VAPictureParameterBufferType,
sizeof(VAPictureParameterBufferH264),
1,
picParam,
&bufferIDs[bufferIDCount]);
CHECK_VA_STATUS("vaCreatePictureParameterBuffer");
bufferIDCount++;
vaStatus = vaCreateBuffer(
mVADisplay,
mVAContext,
VAIQMatrixBufferType,
sizeof(VAIQMatrixBufferH264),
1,
data->IQ_matrix_buf,
&bufferIDs[bufferIDCount]);
CHECK_VA_STATUS("vaCreateIQMatrixBuffer");
bufferIDCount++;
}
status = setReference(sliceParam);
CHECK_STATUS("setReference");
// find which naluinfo is correlated to current slice
int naluIndex = 0;
uint32_t accumulatedHeaderLen = 0;
uint32_t headerLen = 0;
for (; naluIndex < mMetadata.naluNumber; naluIndex++) {
headerLen = mMetadata.naluInfo[naluIndex].naluHeaderLen;
if (headerLen == 0) {
WTRACE("lenght of current NAL unit is 0.");
continue;
}
accumulatedHeaderLen += STARTCODE_PREFIX_LEN;
if (accumulatedHeaderLen + headerLen > sliceData->slice_offset) {
break;
}
accumulatedHeaderLen += headerLen;
}
if (sliceData->slice_offset != accumulatedHeaderLen) {
WTRACE("unexpected slice offset %d, accumulatedHeaderLen = %d", sliceData->slice_offset, accumulatedHeaderLen);
}
sliceParam->slice_data_size = mMetadata.naluInfo[naluIndex].naluLen;
uint32_t sliceOffset = mMetadata.naluInfo[naluIndex].naluOffset;
uint32_t slice_offset_shift = sliceOffset % 16;
sliceParam->slice_data_offset += slice_offset_shift;
sliceData->slice_size = (sliceParam->slice_data_size + slice_offset_shift + 0xF) & ~0xF;
vaStatus = vaCreateBuffer(
mVADisplay,
mVAContext,
VASliceParameterBufferType,
sizeof(VASliceParameterBufferH264),
1,
sliceParam,
&bufferIDs[bufferIDCount]);
CHECK_VA_STATUS("vaCreateSliceParameterBuffer");
bufferIDCount++;
// sliceData->slice_offset - accumulatedHeaderLen is the absolute offset to start codes of current NAL unit
// offset points to first byte of NAL unit
if (mInputBuffer != NULL) {
vaStatus = vaCreateBuffer(
mVADisplay,
mVAContext,
VASliceDataBufferType,
sliceData->slice_size, //Slice size
1, // num_elements
mInputBuffer + sliceOffset - slice_offset_shift,
&bufferIDs[bufferIDCount]);
} else {
vaStatus = vaCreateBuffer(
mVADisplay,
mVAContext,
VAProtectedSliceDataBufferType,
sliceData->slice_size, //size
1, //num_elements
(uint8_t*)sliceOffset, // IMR offset
&bufferIDs[bufferIDCount]);
}
CHECK_VA_STATUS("vaCreateSliceDataBuffer");
bufferIDCount++;
vaStatus = vaRenderPicture(
mVADisplay,
mVAContext,
bufferIDs,
bufferIDCount);
CHECK_VA_STATUS("vaRenderPicture");
return DECODE_SUCCESS;
}
// Parse byte string pattern "0x000001" (3 bytes) in the current buffer.
// Returns offset of position following the pattern in the buffer if pattern is found or -1 if not found.
int32_t VideoDecoderAVCSecure::findNalUnitOffset(uint8_t *stream, int32_t offset, int32_t length) {
uint8_t *ptr;
uint32_t left = 0, data = 0, phase = 0;
uint8_t mask1 = 0, mask2 = 0;
/* Meaning of phase:
0: initial status, "0x000001" bytes are not found so far;
1: one "0x00" byte is found;
2: two or more consecutive "0x00" bytes" are found;
3: "0x000001" patten is found ;
4: if there is one more byte after "0x000001";
*/
left = length;
ptr = (uint8_t *) (stream + offset);
phase = 0;
// parse until there is more data and start code not found
while ((left > 0) && (phase < 3)) {
// Check if the address is 32-bit aligned & phase=0, if thats the case we can check 4 bytes instead of one byte at a time.
if (((((uint32_t)ptr) & 0x3) == 0) && (phase == 0)) {
while (left > 3) {
data = *((uint32_t *)ptr);
mask1 = (STARTCODE_00 != (data & STARTCODE_MASK0));
mask2 = (STARTCODE_00 != (data & STARTCODE_MASK1));
// If second byte and fourth byte are not zero's then we cannot have a start code here,
// as we need two consecutive zero bytes for a start code pattern.
if (mask1 && mask2) {
// skip 4 bytes and start over
ptr += 4;
left -=4;
continue;
} else {
break;
}
}
}
// At this point either data is not on a 32-bit boundary or phase > 0 so we look at one byte at a time
if (left > 0) {
if (*ptr == STARTCODE_00) {
phase++;
if (phase > 2) {
// more than 2 consecutive '0x00' bytes is found
phase = 2;
}
} else if ((*ptr == STARTCODE_01) && (phase == 2)) {
// start code is found
phase = 3;
} else {
// reset lookup
phase = 0;
}
ptr++;
left--;
}
}
if ((left > 0) && (phase == 3)) {
phase = 4;
// return offset of position following the pattern in the buffer which matches "0x000001" byte string
return (int32_t)(ptr - stream);
}
return -1;
}
Decode_Status VideoDecoderAVCSecure::copyNaluHeader(uint8_t *stream, NaluByteStream *naluStream) {
uint8_t naluType;
int32_t naluHeaderLen;
naluType = *(uint8_t *)(stream + naluStream->naluOffset);
naluType &= NALU_TYPE_MASK;
// first update nalu header length based on nalu type
if (naluType >= NAL_UNIT_TYPE_SLICE && naluType <= NAL_UNIT_TYPE_IDR) {
// coded slice, return only up to MAX_SLICE_HEADER_SIZE bytes
naluHeaderLen = min(naluStream->naluLen, MAX_SLICE_HEADER_SIZE);
} else if (naluType >= NAL_UNIT_TYPE_SEI && naluType <= NAL_UNIT_TYPE_PPS) {
//sps, pps, sei, etc, return the entire NAL unit in clear
naluHeaderLen = naluStream->naluLen;
} else {
return DECODE_FRAME_DROPPED;
}
memcpy(naluStream->byteStream + naluStream->streamPos, &(naluStream->naluOffset), sizeof(int32_t));
naluStream->streamPos += 4;
memcpy(naluStream->byteStream + naluStream->streamPos, &(naluStream->naluLen), sizeof(int32_t));
naluStream->streamPos += 4;
memcpy(naluStream->byteStream + naluStream->streamPos, &naluHeaderLen, sizeof(int32_t));
naluStream->streamPos += 4;
if (naluHeaderLen) {
memcpy(naluStream->byteStream + naluStream->streamPos, (uint8_t*)(stream + naluStream->naluOffset), naluHeaderLen);
naluStream->streamPos += naluHeaderLen;
}
return DECODE_SUCCESS;
}
// parse start-code prefixed stream, also knowns as Annex B byte stream, commonly used in AVI, ES, MPEG2 TS container
Decode_Status VideoDecoderAVCSecure::parseAnnexBStream(uint8_t *stream, int32_t length, NaluByteStream *naluStream) {
int32_t naluOffset, offset, left;
NaluInfo *info;
uint32_t ret = DECODE_SUCCESS;
naluOffset = 0;
offset = 0;
left = length;
// leave 4 bytes to copy nalu count
naluStream->streamPos = 4;
naluStream->naluCount = 0;
memset(naluStream->byteStream, 0, MAX_NALU_HEADER_BUFFER);
for (; ;) {
naluOffset = findNalUnitOffset(stream, offset, left);
if (naluOffset == -1) {
break;
}
if (naluStream->naluCount == 0) {
naluStream->naluOffset = naluOffset;
} else {
naluStream->naluLen = naluOffset - naluStream->naluOffset - STARTCODE_PREFIX_LEN;
ret = copyNaluHeader(stream, naluStream);
if (ret != DECODE_SUCCESS && ret != DECODE_FRAME_DROPPED) {
LOGW("copyNaluHeader returned %d", ret);
return ret;
}
// starting position for next NALU
naluStream->naluOffset = naluOffset;
}
if (ret == DECODE_SUCCESS) {
naluStream->naluCount++;
}
// update next lookup position and length
offset = naluOffset + 1; // skip one byte of NAL unit type
left = length - offset;
}
if (naluStream->naluCount > 0) {
naluStream->naluLen = length - naluStream->naluOffset;
memcpy(naluStream->byteStream, &(naluStream->naluCount), sizeof(int32_t));
// ignore return value, either DECODE_SUCCESS or DECODE_FRAME_DROPPED
copyNaluHeader(stream, naluStream);
return DECODE_SUCCESS;
}
LOGW("number of valid NALU is 0!");
return DECODE_SUCCESS;
}