Files
UnrealEngine/Engine/Source/Runtime/AVEncoder/Private/Decoders/vdecmpeg4/M4Decoder.cpp
2025-05-18 13:04:45 +08:00

1158 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "M4Decoder.h"
#include "vdecmpeg4.h"
#include "M4Bitstream.h"
#include "M4Image.h"
#include "M4Prediction.h"
#include "M4MotionVectorMgr.h"
#include "M4idct.h"
#include "M4InvQuant.h"
#include "M4MemOps.h"
namespace vdecmpeg4
{
#define DEFAULT_STREAM_BUFFER_BYTES 2048
#define M4RSHIFT(a,b) ((a) > 0 ? ((a) + (1<<((b)-1)))>>(b) : ((a) + (1<<((b)-1))-1)>>(b))
#define M4SIGN(X) (((X)>0)?1:-1)
#define M4ABS(X) (((X)>0)?(X):-(X))
const int32 M4Decoder::mDequantTable[4] = { -1, -2, 1, 2 };
const uint32 M4Decoder::mRoundtab[16] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2 };
const uint8 M4Decoder::mIntraMacroBlocks[8] = { 0, 0, 0, 1, 1, 0, 0, 0 };
// ----------------------------------------------------------------------------
/**
* Static creation
*
* @param memHandler
*
* @return
*/
M4Decoder* M4Decoder::create(M4MemHandler& memHandler)
{
return new(memHandler) M4Decoder(memHandler);
}
// ----------------------------------------------------------------------------
/**
* Static destruction
*
* @param pDecoder
* @param memHandler
*/
void M4Decoder::destroy(M4Decoder*& pDecoder, M4MemHandler& memHandler)
{
if (pDecoder)
{
pDecoder->~M4Decoder();
memHandler.free(pDecoder);
pDecoder = nullptr;
}
}
void M4Decoder::VIDPrintf(const char* pFormat, ...)
{
if (mCbPrintf)
{
char buffer[512];
va_list args;
va_start(args, pFormat);
vsnprintf(buffer, 512, pFormat, args);
va_end(args);
buffer[511] = '\0';
mCbPrintf(buffer);
}
}
// ----------------------------------------------------------------------------
/**
* Constructor
*
* @param memHandler
*/
M4Decoder::M4Decoder(M4MemHandler& memHandler)
: mDecoderFlags(0)
, mIPMacroblocks(0)
, mBMacroblocks(0)
, mMotVecMgr(0)
, mpStreamIO(nullptr)
, mpStreamEvents(nullptr)
, mMemSys(memHandler)
{
static_assert(sizeof(M4_MB) == 256, "Size mismatch");
mCurrent = mTempImage[0] = mTempImage[1] = mReference[0] = mReference[1] = 0;
mpVidImage = nullptr;
mNumVidImages = 0;
// Important: Now need todo allocs inside bitstream
mBitstream.setMemoryHook(mMemSys);
#ifdef _M4_ENABLE_BMP_OUT
mBaseName[0] = '\0';
#endif
}
// ----------------------------------------------------------------------------
/**
* Destructor
*/
M4Decoder::~M4Decoder()
{
mXCommand.Exit();
freeStuff();
}
// ----------------------------------------------------------------------------
/**
* Just release image buffer stuff
*/
void M4Decoder::freeBuffers()
{
for(uint32 i=0; i<2; ++i)
{
if (mReference[i])
{
mReference[i]->RefRemove();
mReference[i] = nullptr;
}
}
mCurrent = nullptr;
if (mpVidImage)
{
for(uint32 i=0; i<mNumVidImages; ++i)
{
mpVidImage[i]->RefRemove();
}
mMemSys.free(mpVidImage);
mpVidImage = nullptr;
}
M4Image::destroy(mTempImage[0], this);
M4Image::destroy(mTempImage[1], this);
mMemSys.free(mIPMacroblocks);
mIPMacroblocks = nullptr;
mMemSys.free(mBMacroblocks);
mBMacroblocks = nullptr;
}
// ----------------------------------------------------------------------------
/**
* Release all buffers
*/
void M4Decoder::freeStuff()
{
freeBuffers();
mBitstreamCache.Exit(mMemSys);
M4MotionVectorMgr::destroy(mMotVecMgr, mMemSys);
}
// ----------------------------------------------------------------------------
/**
* Handle init code executed just once
*
* @param setup width/height in setup struct can be 0
*
* @return none
*/
VIDError M4Decoder::init(const VIDDecoderSetup* setup)
{
M4CHECK(setup);
VIDError error = VID_OK;
mFrameCounter = 0;
mWidth = 0;
mHeight = 0;
mDecoderFlags = setup->flags;
// Create at least 3 buffers here
mNumVidImages = mDecoderFlags & VID_DECODER_VID_BUFFERS ? setup->numOfVidBuffers : 3;
if (mNumVidImages < 3)
{
return VID_ERROR_SETUP_NUMBER_OF_VID_BUFFERS_INVALID;
}
// Initialize the 'protocol' parser stuff
error = mBitstreamParser.init(this, &mBitstream);
if (error == VID_OK)
{
error = mBitstreamCache.Init(mMemSys);
if (error == VID_OK)
{
error = VID_ERROR_OUT_OF_MEMORY;
mMotVecMgr = M4MotionVectorMgr::create(this, mMemSys);
if (mMotVecMgr)
{
error = mXCommand.Init(this);
if (error == VID_OK)
{
return VID_OK;
}
}
}
}
// Error handling...
freeStuff();
return error;
}
// ----------------------------------------------------------------------------
/**
* Handle buffer allocation of frame size changes
*
* @param width new width
* @param height new height
*
* @return VIDError code
*/
VIDError M4Decoder::initBuffers(int16 width, int16 height)
{
// Check minimum size
M4CHECK(width>=32 && height>=32);
// Check for multiple of 16.
M4CHECK((width &0xf)==0);
M4CHECK((height&0xf)==0);
// Check if we really have a change in the dimension.
if (mpVidImage != nullptr && width == mWidth && height == mHeight)
{
// Try to check if stream was changed, but buffers are not reallocated
if (mBitstreamParser.GetLastVopTime() > 0.0)
{
// We assume that this is an 'inbetween' VOL and actually do not need to change state (is this correct?)
return VID_OK;
}
else
{
// Frame buffer size is valid, but need to reset what else?
return VID_OK;
}
}
mWidth = width;
mHeight = height;
// Release any old stuff
freeBuffers();
// Create at least 2 (non-B frames) or 3 (B-frames) buffers here
mpVidImage = (M4Image**)mMemSys.malloc(sizeof(M4Image*) * mNumVidImages);
for(uint32 i=0; i < mNumVidImages; ++i)
{
mpVidImage[i] = M4Image::create(this, mWidth, mHeight);
if (!mpVidImage[i])
{
freeBuffers();
return VID_ERROR_OUT_OF_MEMORY;
}
}
// Images are created with a ref count of 1
mCurrent = mpVidImage[0];
mReference[0] = mpVidImage[1];
mReference[0]->RefAdd(); // fake: image is referenced by decoder
mReference[1] = mpVidImage[2];
mReference[1]->RefAdd(); // fake: image is referenced by decoder
mBitstreamParser.initFrame(mWidth, mHeight);
// convert input size to 16x16 macroblock size
mMBWidth = (uint16)((mWidth +15)>>4);
mMBHeight = (uint16)((mHeight+15)>>4);
mIPMacroblocks = (M4_MB*)mMemSys.malloc(mMBWidth * mMBHeight * sizeof(M4_MB));
if (!mIPMacroblocks)
{
freeBuffers();
return VID_ERROR_OUT_OF_MEMORY;
}
mTempImage[0] = M4Image::create(this, mWidth, mHeight); // interpolate mode B-frame
mTempImage[1] = M4Image::create(this, mWidth, mHeight); // additional output buffer
mBMacroblocks = (M4_MB*)mMemSys.malloc(mMBWidth * mMBHeight * sizeof(M4_MB));
if (!mTempImage[0] || !mTempImage[1] || !mBMacroblocks)
{
freeBuffers();
return VID_ERROR_OUT_OF_MEMORY;
}
mCurrent = nullptr;
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Allocate a buffer for a new frame
*
* @return
*/
M4Image* M4Decoder::AllocVidFrame()
{
for(uint32 i=0; i<mNumVidImages; ++i)
{
M4CHECKF(mpVidImage[i]->RefGet() >= 1, TEXT("Refcount error! Someone decremented twice %d"), mpVidImage[i]->RefGet());
M4CHECKF(mpVidImage[i]->RefGet() <= 3, TEXT("Refcount error! Someone incremented too often (todo!) %d"), mpVidImage[i]->RefGet());
}
for(uint32 i=0; i<mNumVidImages; ++i)
{
// Free images are having a ref count of 1 for meaning: "just allocated and pointed to by mpVidImage"
if (mpVidImage[i]->RefGet() == 1)
{
return mpVidImage[i];
}
}
return nullptr;
}
// ----------------------------------------------------------------------------
/**
* Attach stream to decoder.
*
* Ownership is not transferred
*
* @param pStreamIO
* @param pEvents
*
* @return VIDError code
*/
VIDError M4Decoder::StreamSet(VIDStreamIO* pStreamIO, VIDStreamEvents* pEvents)
{
mpStreamIO = pStreamIO;
// Attach stream to stream object
if (mpStreamIO)
{
mBitstream.init(mpStreamIO, DEFAULT_STREAM_BUFFER_BYTES);
mBitstreamParser.reset();
}
mpStreamEvents = pEvents;
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Handle decoding via stream interface
*
* @param result
*
* @return VIDError code
*/
VIDError M4Decoder::StreamDecode(float /*time*/, const VIDImage** result)
{
if (mpStreamIO == nullptr && mBitstream.getBaseAddr() == nullptr)
{
return VID_ERROR_STREAM_NOT_SET;
}
*result = nullptr;
VIDImageInfo* pImageInfo;
VIDError error = VID_OK;
M4Image* pReturnedImage; // frame returned to the user
M4PictureType type;
// Try to alloc a buffer where to put the final frame
// This is only a check meaning that the buffer is NOT marked as 'reserved' here
M4Image* pFinishedImage = nullptr;
if (mpVidImage)
{
pFinishedImage = AllocVidFrame();
if (pFinishedImage == nullptr)
{
return VID_ERROR_DECODE_NO_VID_BUFFER_AVAILABLE;
}
}
do
{
// Parse mpeg4-es skipping any non-coded vop
do
{
error = mBitstreamParser.parseMPEG4ES(type);
}
while(error == VID_ERROR_STREAM_VOP_NOT_CODED);
// Check if there was any other error decoding the header
if (error != VID_OK)
{
return error;
}
// VOL processing
if (type == M4PIC_VOL)
{
// Handle information callback to user
if (mpStreamEvents)
{
mpStreamEvents->FoundVideoObjectLayer(mBitstreamParser.GetVOLInfo());
}
error = initBuffers(mBitstreamParser.GetWidth(), mBitstreamParser.GetHeight());
if (error != VID_OK)
{
return error;
}
pFinishedImage = AllocVidFrame();
if (pFinishedImage == nullptr)
{
return VID_ERROR_DECODE_NO_VID_BUFFER_AVAILABLE;
}
}
}
while(type == M4PIC_VOL); // scan again if type as a VOL...
// VOP processing
if (type < M4PIC_I_VOP || type > M4PIC_S_VOP)
{
return VID_ERROR_DECODE_INVALID_VOP;
}
// Set location where we put the output image
M4CHECK(pFinishedImage);
mCurrent = pFinishedImage;
pFinishedImage = nullptr;
// Check if we have allocated additional buffers
mXCommand.FrameBegin(mCurrent, &mBitstreamParser.mHeaderInfo, mReference);
if (type == M4PIC_P_VOP)
{
error = pFrame(false);
}
else if (type == M4PIC_I_VOP)
{
error = iFrame();
}
else if (type == M4PIC_B_VOP)
{
error = bFrame();
}
else
{
error = pFrame(true); // S(GMC)-VOP
}
if (error != VID_OK)
{
return error;
}
pImageInfo = &mCurrent->mImageInfo;
pImageInfo->mFrameNumber = mFrameCounter;
mCurrent->mImage.time = mBitstreamParser.GetLastVopTime();
// swap images for next decoder call
// if we have a b frame, we don't SWAP buffers. We reuse mCurrent for the next frame
if (type != M4PIC_B_VOP)
{
// In B-Frame supporting mode we need to 'delay' one frame, because we
// get two P-frames for constructing a possible following B-frame.
*result = &mReference[0]->mImage;
// This image is handed out to user
mReference[0]->RefAdd();
// This image is used sometimes later as reference frame
mCurrent->RefAdd();
// This image is not used anymore by the decoder (but perhaps by the user?)
mReference[1]->RefRemove();
mReference[1] = mReference[0];
mReference[0] = mCurrent;
}
else
{
// Special case for "single consecutive b-frame" mode
*result = &mCurrent->mImage;
// Image is handed out to user
mCurrent->RefAdd();
}
mCurrent = nullptr;
mFrameCounter++;
mXCommand.FrameEnd();
// Update stats
pImageInfo->mFrameBytes = mBitstream.totalBitsGet()>>3;
pImageInfo->mMacroblockInfo = mXCommand.GetMacroblockInfo();
// Finally, check if this actuall was a valid
pReturnedImage = (M4Image*)(*result)->_private;
M4CHECK(pReturnedImage);
if (pReturnedImage->mImageInfo.mFrameType == VID_IMAGEINFO_FRAMETYPE_UNKNOWN)
{
pReturnedImage->RefRemove();
return VID_ERROR_STREAM_UNDERFLOW;
}
else
{
// Debugging: Check if we want to write a bmp image from the created image
#ifdef _M4_ENABLE_BMP_OUT
if (mBaseName[0] && pReturnedImage)
{
// FrameCount is already increment here. So, we just use previous 'count'
pReturnedImage->saveBMP(mBaseName, mFrameCounter-1);
}
#endif
return VID_OK;
}
}
// ----------------------------------------------------------------------------
/**
* Do some stuff if seeking happens.
*
* WARNING: Need to revise interface if seeking to some arbitraty position
*
* @return
*/
VIDError M4Decoder::StreamSeekNotify()
{
if (mpStreamIO)
{
mBitstream.init(mpStreamIO, DEFAULT_STREAM_BUFFER_BYTES);
}
mBitstreamParser.reset();
// Need to invalidate all frames if initialized...
if (mpVidImage)
{
// Mark all frames as invalid, because we do a seek now.
for(uint32 i=0; i < mNumVidImages; ++i)
{
mpVidImage[i]->mImageInfo.mFrameType = VID_IMAGEINFO_FRAMETYPE_UNKNOWN;
mpVidImage[i]->Black();
}
freeBuffers();
}
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Decode I-Frame
*
* @return
*/
VIDError M4Decoder::iFrame()
{
mCurrent->mImageInfo.mFrameType = VID_IMAGEINFO_FRAMETYPE_I;
uint16 quant = mBitstreamParser.mHeaderInfo.mQuant;
M4_MB* mb = mIPMacroblocks;
for(int32 y=0; y<mMBHeight; ++y)
{
for(int32 x=0; x<mMBWidth; ++x, ++mb)
{
// get macroblock TYPE **AND** the coded block pattern for chrominance .
// It is always included for coded macroblocks.
mb->mFlags = 0;
int32 mbcbpc = mBitstreamParser.getCbpCIntra();
mb->mMode = (uint8)(mbcbpc & 7);
mb->mModeIntra = mIntraMacroBlocks[mb->mMode];
M4CHECK(mb->mMode != M4_MBMODE_STUFFING);
if (mb->mMode == M4_MBMODE_STUFFING)
{
VIDPrintf("M4Decoder::iFrame(): Stuffing not supported!\n");
return VID_ERROR_DECODE_STUFFING_NOT_SUPPORTED;
}
uint32 cbpc = (uint32)(mbcbpc >> 4);
// This is a 1-bit flag which when set to '1' indicates that either the first row or
// the first column of ac coefficients are differentially coded for intra coded macroblocks.
bool useACPredition = !!mBitstream.getBit();
// This variable length code represents a pattern of non-transparent luminance blocks
// with at least one non intra DC transform coefficient, in a macroblock.
mb->mCbp = (uint8)((mBitstreamParser.getCbpy(true) << 2) | cbpc);
if (mb->mMode == M4_MBMODE_INTRA_Q)
{
// This is a 2-bit code which specifies the change in the quantizer, for I- and P-VOPs!
quant += (int16)mDequantTable[mBitstream.getBits(2)];
if (quant > 31)
{
quant = 31;
}
else if (quant < 1)
{
quant = 1;
}
}
mb->mQuant = (uint8)quant;
mbIntra(mb, x, y, useACPredition, mb->mCbp, (uint8)quant);
// Resync marker?
if (mBitstream.showBitsByteAligned(17) == 1 && mBitstream.validStuffingBits())
{
VIDError err = mBitstreamParser.videoPacketHeader();
if (err != VID_OK)
{
return err;
}
// The current assumption is that the next macroblock is also the one we want to handle in our loop.
M4CHECK(mBitstreamParser.mResyncMacroblockNumber == y * mMBWidth + x + 1);
}
/*
else if (mBitstream.showBitsByteAligned(23) == 0)
{
// Next aligned 23 bits are all 0 which indicates a startcode. The frame is either complete or not.
// For now we assume it is.
M4CHECK(y == mMBHeight-1 && x == mMBWidth - 1);
// If it is not we can still bail out here with some indication of this.
mBitstream.nextStartCode();
return VID_OK;
}
*/
}
}
mBitstream.nextStartCode();
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Decode P-Frame
*
* @param gmc
*
* @return
*/
VIDError M4Decoder::pFrame(bool gmc)
{
mCurrent->mImageInfo.mFrameType = gmc ? VID_IMAGEINFO_FRAMETYPE_S : VID_IMAGEINFO_FRAMETYPE_P;
// init motion vector manager for this frame
mMotVecMgr->init();
// Update/create border padding area of image
mXCommand.XCreatePadding(&mReference[0]->mImage);
uint8 quant = (uint8)mBitstreamParser.mHeaderInfo.mQuant;
M4_MB* mb = mIPMacroblocks;
for(int32 mby=0; mby<mMBHeight; ++mby)
{
for(int32 mbx=0; mbx<mMBWidth; ++mbx, ++mb)
{
mb->mFlags = 0;
// check for flag 'not coded'
// 'not coded' means that we do not have ANY info about this mb in this frame
if (mBitstream.getBit()) // not_coded
{
// this case: not_coded == 1
if (!gmc)
{
mb->mMode = M4_MBMODE_NOT_CODED_PVOP;
mb->mModeIntra = 0;
// copy this macroblock's data directly from previous VOP
M4MotionVectorClear(mb);
mXCommand.XCopyMB(mbx, mby);
}
else
{
return VID_ERROR_DECODE_GMC_NOT_ENABLED;
}
}
else
{
int32 mbcbpc = mBitstreamParser.getCbpCInter();
mb->mMode = (uint8)(mbcbpc & 7);
mb->mModeIntra = mIntraMacroBlocks[mb->mMode];
M4CHECKF(mb->mMode != M4_MBMODE_STUFFING, TEXT("*** M4Decoder::pFrame: STUFFING currently not supported!\n"));
if (mb->mMode == M4_MBMODE_STUFFING)
{
VIDPrintf("M4Decoder::pFrame(): Stuffing not supported!\n");
return VID_ERROR_DECODE_STUFFING_NOT_SUPPORTED;
}
uint32 cbpc = (uint32)(mbcbpc >> 4);
// handle this macroblock as INTRA
if (mb->mModeIntra)
{
bool useACPredition = !!mBitstream.getBit();
// get 'coded block pattern' for luminance
uint32 cbpy = mBitstreamParser.getCbpy(true);
mb->mCbp = (uint8)((cbpy << 2) | cbpc);
if (mb->mMode == M4_MBMODE_INTRA_Q)
{
quant += (int8)mDequantTable[mBitstream.getBits(2)];
if (quant > 31)
{
quant = 31;
}
else if (quant < 1)
{
quant = 1;
}
}
mb->mQuant = quant;
mbIntra(mb, mbx, mby, useACPredition, mb->mCbp , quant);
}
else
{
// handle this macroblock as INTER (incl. INTER-GMC)
uint32 mcsel = gmc && ( mb->mMode == M4_MBMODE_INTER || mb->mMode == M4_MBMODE_INTER_Q ) ? mBitstream.getBit() : 0; // mcsel
uint32 cbpy = mBitstreamParser.getCbpy(false);
mb->mCbp = (uint8)((cbpy << 2) | cbpc);
// change quantiser value
if (mb->mMode == M4_MBMODE_INTER_Q)
{
quant += (int8)mDequantTable[mBitstream.getBits(2)];
if (quant > 31)
{
quant = 31;
}
else if (quant < 1)
{
quant = 1;
}
}
mb->mQuant = quant;
if (!mcsel || mb->mMode == M4_MBMODE_INTER4V /* new: this overwrites mcsel */)
{
mMotVecMgr->readMvForward(mb);
mMotVecMgr->predictAddForward(mb, mbx, mby);
mbInter(mb, mbx, mby, mb->mCbp, MV_PREDICTION_P_FORWARD, 0);
}
else
{
return VID_ERROR_DECODE_GMC_NOT_ENABLED;
}
}
}
// Resync marker?
if (mBitstream.showBitsByteAligned(16 + mBitstreamParser.mFcodeForward) == 1 && mBitstream.validStuffingBits())
{
VIDError err = mBitstreamParser.videoPacketHeader();
if (err != VID_OK)
{
return err;
}
// The current assumption is that the next macroblock is also the one we want to handle in our loop.
M4CHECK(mBitstreamParser.mResyncMacroblockNumber == mby * mMBWidth + mbx + 1);
}
/*
else if (mBitstream.showBitsByteAligned(23) == 0)
{
// Next aligned 23 bits are all 0 which indicates a startcode. The frame is either complete or not.
// For now we assume it is.
M4CHECK(mby == mMBHeight-1 && mbx == mMBWidth - 1);
// If it is not we can still bail out here with some indication of this.
mBitstream.nextStartCode();
return VID_OK;
}
*/
} // mbx loop
} // mby loop
mBitstream.nextStartCode();
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Decode B-Frame
*
* @return
*/
VIDError M4Decoder::bFrame()
{
mCurrent->mImageInfo.mFrameType = VID_IMAGEINFO_FRAMETYPE_B;
// Init motion vector manager for this frame
mMotVecMgr->init();
// Update/create border padding area of image
// In a multiprocessor environment, this is a NOP, because padding happens
// in a separate thread as soon as the image is returned to the user...
mXCommand.XCreatePadding(&mReference[0]->mImage);
bool hasMotionVec;
uint16 mbLastIdx = 0;
M4_MB* mbLast = mIPMacroblocks;
M4_MB* mb = mBMacroblocks;
uint8 quant = (uint8)mBitstreamParser.mHeaderInfo.mQuant;
int32 TRD = (int32)mBitstreamParser.mTimePP;
int32 TRB = (int32)mBitstreamParser.mTimeBP;
M4_VECTOR mMvForwardPred;
M4_VECTOR mMvBackwardPred;
for(int32 mby=0; mby<mMBHeight; ++mby)
{
// Init motion vector predictor
mMvForwardPred = mMvBackwardPred = M4MotionVectorMgr::MV_ZERO;
for(int32 mbx=0; mbx<mMBWidth; ++mbx, ++mb, ++mbLast, ++mbLastIdx)
{
M4_VECTOR deltaMv = M4MotionVectorMgr::MV_ZERO;
mb->mMode = M4_MBMODE_INTER; // default to this mode
mb->mModeIntra = 0;
// If the co-located macroblock in the most recently decoded I- or P-VOP is skipped...
if (mbLast->mMode == M4_MBMODE_NOT_CODED_PVOP)
{
// If the co-located macroblock in the most recently decoded I- or P-VOP is skipped,
// the current B-macroblock is treated as the forward mode with the zero motion vector (MVFx,MVFy)
mb->mFlags = M4_MBFLAG_MVTYPE_FORWARD;
mb->mCbp = 0;
mb->mQuant = mbLast->mQuant;
M4MotionVectorClear(mb);
mbInter(mb, mbx, mby, mb->mCbp, MV_PREDICTION_NONE, 1);
continue;
}
if (mBitstream.getBit()) // modb
{
// If modb == 1 the current B macroblock is reconstructed by using the direct mode with zero delta vector.
hasMotionVec = false;
mb->mFlags = M4_MBFLAG_MVTYPE_DIRECT;
mb->mCbp = 0;
deltaMv = M4MotionVectorMgr::MV_ZERO;
}
else
{
hasMotionVec = true;
// here mb_type is present, but we must check for cbpb
uint8 modb1 = (uint8)mBitstream.getBit();
// Get b macroblock type: 00, 01, 10, 11
mb->mFlags = mBitstreamParser.getMBType() | M4_MBFLAG_HAS_MOTION_VECTOR;
mb->mCbp = (uint8)(modb1 ? 0 : mBitstream.getBits(6));
if ( ((mb->mFlags & M4_MBFLAG_MVTYPE_MASK) != M4_MBFLAG_MVTYPE_DIRECT) && !modb1 )
{
quant += (int8)mBitstreamParser.getQuantiserChange();
if (quant > 31)
{
quant = 31;
}
else if (quant < 1)
{
quant = 1;
}
}
}
// store current quant value in this macroblock
mb->mQuant = quant;
// --------------------------------------------------------------------------------------------------------
switch(mb->mFlags & M4_MBFLAG_MVTYPE_MASK)
{
case M4_MBFLAG_MVTYPE_DIRECT:
{
if (hasMotionVec)
{
mMotVecMgr->readMv(deltaMv, 1);
mMotVecMgr->addMv(deltaMv, 1, deltaMv, M4MotionVectorMgr::MV_ZERO);
}
// progressive direct mode
for(uint32 i = 0; i < 4; i++)
{
mb->mFMv[i].x = TRB * mbLast->mFMv[i].x / TRD + deltaMv.x;
mb->mFMv[i].y = TRB * mbLast->mFMv[i].y / TRD + deltaMv.y;
mb->mBMv[i].x = deltaMv.x == 0
? (TRB - TRD) * mbLast->mFMv[i].x / TRD
: mb->mFMv[i].x - mbLast->mFMv[i].x;
mb->mBMv[i].y = deltaMv.y == 0
? (TRB - TRD) * mbLast->mFMv[i].y / TRD
: mb->mFMv[i].y - mbLast->mFMv[i].y;
}
mb->mMode = M4_MBMODE_INTER4V;
mb->mModeIntra = 0;
mbInterpolate(1, 0, mb, mbx, mby, mb->mCbp, mbLastIdx);
break;
}
case M4_MBFLAG_MVTYPE_INTERPOLATE:
{
mMotVecMgr->readMv(mb->mFMv[0], mBitstreamParser.mFcodeForward);
mMotVecMgr->addMv(mb->mFMv[0], mBitstreamParser.mFcodeForward, mb->mFMv[0], mMvForwardPred);
mMvForwardPred = mb->mFMv[1] = mb->mFMv[2] = mb->mFMv[3] = mb->mFMv[0];
mMotVecMgr->readMv(mb->mBMv[0], mBitstreamParser.mFcodeBackward);
mMotVecMgr->addMv(mb->mBMv[0], mBitstreamParser.mFcodeBackward, mb->mBMv[0], mMvBackwardPred);
mMvBackwardPred = mb->mBMv[1] = mb->mBMv[2] = mb->mBMv[3] = mb->mBMv[0];
mbInterpolate(1, 0, mb, mbx, mby, mb->mCbp);
break;
}
case M4_MBFLAG_MVTYPE_BACKWARD:
{
mMotVecMgr->readMv(mb->mFMv[0], mBitstreamParser.mFcodeBackward);
mMotVecMgr->addMv(mb->mFMv[0], mBitstreamParser.mFcodeBackward, mb->mFMv[0], mMvBackwardPred);
mMvBackwardPred = mb->mFMv[1] = mb->mFMv[2] = mb->mFMv[3] = mb->mFMv[0];
mbInter(mb, mbx, mby, mb->mCbp, MV_PREDICTION_B_BACKWARD, 0); // calc from 'future' I- or P-VOP
break;
}
case M4_MBFLAG_MVTYPE_FORWARD:
{
mMotVecMgr->readMv(mb->mFMv[0], mBitstreamParser.mFcodeForward);
mMotVecMgr->addMv(mb->mFMv[0], mBitstreamParser.mFcodeForward, mb->mFMv[0], mMvForwardPred);
mMvForwardPred = mb->mFMv[1] = mb->mFMv[2] = mb->mFMv[3] = mb->mFMv[0];
mbInter(mb, mbx, mby, mb->mCbp, MV_PREDICTION_B_FORWARD, 1); // calc from 'past' I- or P-VOP
break;
}
default:
{
M4CHECK(false && "Invalid B-frame type\n");
break;
}
}
#if 0
// Resync marker?
uint32 resyncMarkerLen = max(15 + mBitstreamParser.mFcodeForward + mBitstreamParser.mFcodeBackward, 17) + 1;
if (mBitstream.showBitsByteAligned(resyncMarkerLen == 1 && mBitstream.validStuffingBits())
{
VIDError err = mBitstreamParser.videoPacketHeader();
if (err != VID_OK)
{
return err;
}
// The current assumption is that the next macroblock is also the one we want to handle in our loop.
M4CHECK(mBitstreamParser.mResyncMacroblockNumber == mby * mMBWidth + mbx + 1);
}
/*
else if (mBitstream.showBitsByteAligned(23) == 0)
{
// Next aligned 23 bits are all 0 which indicates a startcode. The frame is either complete or not.
// For now we assume it is.
M4CHECK(mby == mMBHeight-1 && mbx == mMBWidth - 1);
// If it is not we can still bail out here with some indication of this.
mBitstream.nextStartCode();
return VID_OK;
}
*/
#endif
}
}
mBitstream.nextStartCode();
return VID_OK;
}
// ----------------------------------------------------------------------------
/**
* Decode an INTRA macroblock
*
* @param mb
* @param mbx
* @param mby
* @param useACPredition
* @param cbp
* @param quant
*/
void M4Decoder::mbIntra(M4_MB* mb, int32 mbx, int32 mby, bool useACPredition, uint32 cbp, uint8 quant)
{
M4CHECK(mb->mMode == M4_MBMODE_INTRA || mb->mMode == M4_MBMODE_INTRA_Q);
M4CHECK(mb->mModeIntra);
// Get a new cache entry where to store our bitstream data
M4BitstreamCacheEntry& cacheEntry = mBitstreamCache.Alloc();
int16* pDctBitstream = cacheEntry.mDctFromBitstream;
uint16* iDcScaler = cacheEntry.mDcScaler;
// no motion vector for this macroblock
M4MotionVectorClear(mb);
int16 predictionValues[8];
uint32 dctOffset = 0;
uint32 cbpMask = 32;
for(uint32 i=0; i<6; ++i, dctOffset += 64, cbpMask >>=1)
{
iDcScaler[i] = mBitstreamParser.getDCScaler(mb->mQuant, i < 4);
M4PredictionInit(mIPMacroblocks, mbx, mby, mMBWidth, i, mb->mQuant, iDcScaler[i], predictionValues);
if (!useACPredition)
{
mb->mACPredDirections[i] = 0;
}
uint32 startCoeff = 0;
if (quant < mBitstreamParser.mIntraDCThreshold)
{
int32 dcSize = mBitstreamParser.getDCSize(i < 4);
int32 dcDif = dcSize ? mBitstreamParser.getDCDiff((uint32) dcSize) : 0;
if (dcSize > 8)
{
mBitstream.skip(1); // skip marker bit
}
pDctBitstream[dctOffset] = (int16)dcDif; // DC coeff set (= read from stream)
startCoeff = 1; // start at first AC component
}
if (cbp & cbpMask) // coded
{
mBitstreamParser.decodeIntraBlock(pDctBitstream+dctOffset, mb->mACPredDirections[i], startCoeff);
}
M4PredictionAdd(mb, pDctBitstream+dctOffset, i, iDcScaler[i], predictionValues);
}
mXCommand.XUpdateIntraMB(mb, mbx, mby, &cacheEntry);
}
// ----------------------------------------------------------------------------
/**
* Decode an INTER macroblock
*
* @param mb
* @param mbx
* @param mby
* @param cbp
* @param mvPrediction
* @param refImgNo
*/
void M4Decoder::mbInter(M4_MB* mb, int32 mbx, int32 mby, uint32 cbp, MV_PREDICTION mvPrediction, uint32 refImgNo)
{
M4CHECK(refImgNo < 2);
M4CHECK(mb->mMode == M4_MBMODE_INTER || mb->mMode == M4_MBMODE_INTER_Q || mb->mMode == M4_MBMODE_INTER4V );
M4BitstreamCacheEntry* pCacheEntry = nullptr;
// process coded macroblocks if we have some
if (cbp)
{
pCacheEntry = &mBitstreamCache.Alloc();
int16* pDctBitstream = pCacheEntry->mDctFromBitstream;
for(uint32 i=0; i<6; ++i)
{
// check if any block is coded inside stream
if (cbp & (1 << (5-i)))
{
uint32 dctOffset = i<<6;
mBitstreamParser.decodeInterBlock(pDctBitstream+dctOffset);
}
}
}
mXCommand.XUpdateInterMB(mb, mbx, mby, pCacheEntry, mvPrediction, refImgNo);
}
// ----------------------------------------------------------------------------
/**
* Decode an INTER macroblock for B frames using interpolation
*
* @param refImgForward
* @param refImgBackward
* @param mb
* @param mbx
* @param mby
* @param cbp
* @param mbLastIdx
*/
void M4Decoder::mbInterpolate(uint32 refImgForward, uint32 refImgBackward, M4_MB* mb, int32 mbx, int32 mby, uint32 cbp, uint16 mbLastIdx)
{
M4BitstreamCacheEntry* pCacheEntry = nullptr;
if (cbp)
{
pCacheEntry = &mBitstreamCache.Alloc();
int16* pDctBitstream = pCacheEntry->mDctFromBitstream;
for(uint32 i=0; i<6; ++i)
{
// check if any block is coded inside stream
if (cbp & (1 << (5-i)))
{
uint32 dctOffset = i<<6;
mBitstreamParser.decodeInterBlock(pDctBitstream+dctOffset);
}
}
}
mXCommand.XInterpolateMB(mb, mbx, mby, pCacheEntry, refImgForward, refImgBackward, mbLastIdx);
}
#ifdef _M4_ENABLE_BMP_OUT
// ----------------------------------------------------------------------------
/**
* Handle settings for BMP output
*
* @param pFileBaseName
*/
void M4Decoder::SetVideoOutToBMP(const char* pFileBaseName)
{
if (pFileBaseName)
{
strncpy(mBaseName, pFileBaseName, sizeof(mBaseName));
mBaseName[sizeof(mBaseName)-1] = '\0';
}
else
{
mBaseName[0] = '\0';
}
}
#endif
}