mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-10-25 10:20:52 +00:00
3146 lines
64 KiB
C++
3146 lines
64 KiB
C++
/*
|
|
* Copyright 2011-2026 Branimir Karadzic. All rights reserved.
|
|
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
|
|
*/
|
|
|
|
#include "video_player.h"
|
|
|
|
#include <bx/allocator.h>
|
|
#include <bx/bx.h>
|
|
#include <bx/debug.h>
|
|
#include <bx/endian.h>
|
|
#include <bx/file.h>
|
|
#include <bx/math.h>
|
|
#include <bx/readerwriter.h>
|
|
#include <bx/sort.h>
|
|
#include <bx/timer.h>
|
|
|
|
#include <entry/entry.h>
|
|
|
|
#include <tinystl/vector.h>
|
|
namespace stl = tinystl;
|
|
|
|
BX_PRAGMA_DIAGNOSTIC_PUSH();
|
|
BX_PRAGMA_DIAGNOSTIC_IGNORED_MSVC(4127) // warning C4127: conditional expression is constant
|
|
BX_PRAGMA_DIAGNOSTIC_IGNORED_MSVC(4244) // warning C4244: '=': conversion, possible loss of data
|
|
BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wshadow")
|
|
#include "l-smash/lsmash.h"
|
|
BX_PRAGMA_DIAGNOSTIC_POP();
|
|
|
|
#include <h264/h264.h>
|
|
|
|
struct VideoPlayer::Impl
|
|
{
|
|
enum Container
|
|
{
|
|
Mp4,
|
|
AnnexB,
|
|
Obu,
|
|
Ivf,
|
|
};
|
|
|
|
Container container = Mp4;
|
|
bgfx::VideoCodec::Enum codec = bgfx::VideoCodec::Count;
|
|
uint8_t chroma = 0;
|
|
uint8_t bitDepth = 8;
|
|
uint8_t profile = 0;
|
|
uint8_t level = 0;
|
|
uint16_t width = 0;
|
|
uint16_t height = 0;
|
|
uint8_t maxDpbSlots = 16;
|
|
uint8_t maxActiveReferences = 4;
|
|
stl::vector<uint8_t> parameterSets;
|
|
|
|
lsmash_root_t* root = NULL;
|
|
lsmash_file_parameters_t fp = {};
|
|
bool fpOpened = false;
|
|
uint32_t trackId = 0;
|
|
uint32_t lengthSize = 4;
|
|
|
|
bx::FileReader reader;
|
|
bool readerOpened = false;
|
|
uint64_t fileSize = 0;
|
|
|
|
stl::vector<uint8_t> scratch;
|
|
|
|
struct Frame
|
|
{
|
|
uint64_t ptsUs;
|
|
uint64_t offset;
|
|
uint32_t size;
|
|
bool isKeyframe;
|
|
};
|
|
|
|
stl::vector<Frame> frames;
|
|
uint64_t totalBytes = 0;
|
|
|
|
~Impl()
|
|
{
|
|
if (NULL != root)
|
|
{
|
|
lsmash_destroy_root(root);
|
|
root = NULL;
|
|
}
|
|
|
|
if (fpOpened)
|
|
{
|
|
lsmash_close_file(&fp);
|
|
fpOpened = false;
|
|
}
|
|
|
|
if (readerOpened)
|
|
{
|
|
bx::close(&reader);
|
|
readerOpened = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace
|
|
{
|
|
typedef VideoPlayer::Impl Impl;
|
|
|
|
const uint32_t kMaxSubmitPerCall = 1;
|
|
const int64_t kLookaheadUs = 2 * 1000000;
|
|
const int64_t kCacheWindowUs = 2 * kLookaheadUs;
|
|
const uint32_t kMinCachedAuBytes = 16u << 20;
|
|
const float kRateStepFactor = 1.25892541f;
|
|
const float kRateMin = 0.1f;
|
|
const float kRateMax = 100.0f;
|
|
const int64_t kDefaultFrameUs = 1000000 / 30;
|
|
const uint32_t kProbeSize = 64;
|
|
const uint32_t kNalPeekSize = 1024;
|
|
const uint32_t kScanBufferSize = 1 << 20;
|
|
const uint32_t kBoxHeaderSize = 8;
|
|
const uint8_t kH264NalUnitTypeMask = 0x1f;
|
|
|
|
namespace hevc
|
|
{
|
|
enum NalUnitType
|
|
{
|
|
BLA_W_LP = 16,
|
|
BLA_N_LP = 18,
|
|
IDR_W_RADL = 19,
|
|
IDR_N_LP = 20,
|
|
CRA_NUT = 21,
|
|
RSV_IRAP_VCL23 = 23,
|
|
VPS_NUT = 32,
|
|
SPS_NUT = 33,
|
|
PPS_NUT = 34,
|
|
AUD_NUT = 35,
|
|
PREFIX_SEI_NUT = 39,
|
|
SUFFIX_SEI_NUT = 40,
|
|
};
|
|
|
|
const uint32_t kNalHeaderSize = 2;
|
|
const uint32_t kMaxSpsCount = 16;
|
|
const uint32_t kMaxPpsCount = 64;
|
|
const uint32_t kMaxSubLayers = 7;
|
|
const uint32_t kMaxStRefPicSets = 64;
|
|
|
|
inline uint8_t getNalUnitType(const uint8_t* _nal)
|
|
{
|
|
return (_nal[0] >> 1) & 0x3f;
|
|
}
|
|
|
|
inline bool isVcl(uint8_t _type)
|
|
{
|
|
return bx::isInRange(_type, 0, CRA_NUT);
|
|
}
|
|
|
|
inline bool isIrap(uint8_t _type)
|
|
{
|
|
return bx::isInRange(_type, BLA_W_LP, RSV_IRAP_VCL23);
|
|
}
|
|
|
|
inline bool isIdr(uint8_t _type)
|
|
{
|
|
return IDR_W_RADL == _type || IDR_N_LP == _type;
|
|
}
|
|
|
|
inline bool isIdrOrBla(uint8_t _type)
|
|
{
|
|
return bx::isInRange(_type, BLA_W_LP, IDR_N_LP);
|
|
}
|
|
|
|
inline bool isParameterSet(uint8_t _type)
|
|
{
|
|
return bx::isInRange(_type, VPS_NUT, PPS_NUT);
|
|
}
|
|
}
|
|
|
|
namespace av1
|
|
{
|
|
enum ObuType
|
|
{
|
|
OBU_SEQUENCE_HEADER = 1,
|
|
OBU_TEMPORAL_DELIMITER = 2,
|
|
OBU_FRAME_HEADER = 3,
|
|
OBU_FRAME = 6,
|
|
};
|
|
|
|
enum FrameType
|
|
{
|
|
KEY_FRAME = 0,
|
|
};
|
|
|
|
enum Profile
|
|
{
|
|
PROFILE_MAIN = 0,
|
|
PROFILE_HIGH = 1,
|
|
PROFILE_PROFESSIONAL = 2,
|
|
};
|
|
|
|
const uint32_t kObuTypeShift = 3;
|
|
const uint8_t kObuTypeMask = 0xf;
|
|
const uint8_t kObuExtensionFlag = 0x4;
|
|
const uint8_t kObuHasSizeField = 0x2;
|
|
const uint32_t kObuHeaderMaxSize = 16;
|
|
const uint32_t kMaxLeadingZeros = 32;
|
|
const uint8_t kLeb128ValueMask = 0x7f;
|
|
const uint8_t kLeb128ContinuationBit = 0x80;
|
|
const uint32_t kAv1CSize = 4;
|
|
}
|
|
|
|
namespace ivf
|
|
{
|
|
const uint32_t kHeaderSize = 32;
|
|
const uint32_t kFrameHeaderSize = 12;
|
|
}
|
|
|
|
template <typename Ty>
|
|
Ty readLe(const uint8_t* _data)
|
|
{
|
|
Ty value;
|
|
bx::memCopy(&value, _data, sizeof(value) );
|
|
return bx::toHostEndian(value, true);
|
|
}
|
|
|
|
template <typename Ty>
|
|
Ty readBe(const uint8_t* _data)
|
|
{
|
|
Ty value;
|
|
bx::memCopy(&value, _data, sizeof(value) );
|
|
return bx::toHostEndian(value, false);
|
|
}
|
|
|
|
bool isFourCC(const uint8_t* _data, const char* _fourcc)
|
|
{
|
|
return 0 == bx::memCmp(_data, _fourcc, 4);
|
|
}
|
|
|
|
void avccToAnnexB(stl::vector<uint8_t>& _out, const uint8_t* _in, uint32_t _size, uint32_t _lengthSize)
|
|
{
|
|
uint32_t pos = 0;
|
|
|
|
while (pos + _lengthSize <= _size)
|
|
{
|
|
uint32_t naluLen = 0;
|
|
|
|
for (uint32_t ii = 0; ii < _lengthSize; ++ii)
|
|
{
|
|
naluLen = (naluLen << 8) | _in[pos + ii];
|
|
}
|
|
|
|
pos += _lengthSize;
|
|
|
|
if (pos + naluLen > _size)
|
|
{
|
|
break;
|
|
}
|
|
|
|
_out.push_back(0);
|
|
_out.push_back(0);
|
|
_out.push_back(1);
|
|
_out.insert(_out.end(), _in + pos, _in + pos + naluLen);
|
|
|
|
pos += naluLen;
|
|
}
|
|
}
|
|
|
|
template <typename Fn>
|
|
void visitLengthPrefixedNals(const uint8_t* _data, uint32_t _size, uint32_t _lengthSize, Fn _visit)
|
|
{
|
|
uint32_t pos = 0;
|
|
|
|
while (pos + _lengthSize <= _size)
|
|
{
|
|
uint32_t naluLen = 0;
|
|
|
|
for (uint32_t ii = 0; ii < _lengthSize; ++ii)
|
|
{
|
|
naluLen = (naluLen << 8) | _data[pos + ii];
|
|
}
|
|
|
|
pos += _lengthSize;
|
|
|
|
if (pos + naluLen > _size)
|
|
{
|
|
break;
|
|
}
|
|
|
|
_visit(_data + pos, naluLen);
|
|
pos += naluLen;
|
|
}
|
|
}
|
|
|
|
uint32_t appendNalAsAnnexB(stl::vector<uint8_t>& _out, const uint8_t* _nal, uint32_t _nalLen)
|
|
{
|
|
if (NULL == _nal
|
|
|| 0 == _nalLen)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
_out.push_back(0);
|
|
_out.push_back(0);
|
|
_out.push_back(1);
|
|
_out.insert(_out.end(), _nal, _nal + _nalLen);
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t stripEmulationPrevention(uint8_t* _dst, const uint8_t* _src, uint32_t _size)
|
|
{
|
|
uint32_t dstIdx = 0;
|
|
uint32_t zeros = 0;
|
|
|
|
for (uint32_t ii = 0; ii < _size; ++ii)
|
|
{
|
|
if (zeros >= 2
|
|
&& 0x03 == _src[ii])
|
|
{
|
|
zeros = 0;
|
|
continue;
|
|
}
|
|
|
|
zeros = (0 == _src[ii]) ? zeros + 1 : 0;
|
|
_dst[dstIdx++] = _src[ii];
|
|
}
|
|
|
|
return dstIdx;
|
|
}
|
|
|
|
void stripEmulationPrevention(stl::vector<uint8_t>& _dst, const uint8_t* _src, uint32_t _size)
|
|
{
|
|
_dst.resize(_size);
|
|
_dst.resize(stripEmulationPrevention(_dst.data(), _src, _size) );
|
|
}
|
|
|
|
uint8_t chromaFromFormatIdc(uint32_t _chromaFormatIdc)
|
|
{
|
|
return 3 == _chromaFormatIdc ? 4
|
|
: 2 == _chromaFormatIdc ? 2
|
|
: 0
|
|
;
|
|
}
|
|
|
|
int32_t computePoc(
|
|
int32_t& _prevPocMsb
|
|
, int32_t& _prevPocLsb
|
|
, const h264::SPS& _sps
|
|
, const h264::SliceHeader& _sh
|
|
, const h264::NALHeader& _nal
|
|
, bool _isIdr
|
|
)
|
|
{
|
|
if (2 == _sps.pic_order_cnt_type)
|
|
{
|
|
const int32_t maxFrameNum = 1 << (_sps.log2_max_frame_num_minus4 + 4);
|
|
int32_t frameNumOffset;
|
|
int32_t poc;
|
|
|
|
if (_isIdr)
|
|
{
|
|
frameNumOffset = 0;
|
|
poc = 0;
|
|
}
|
|
else
|
|
{
|
|
frameNumOffset = _prevPocLsb > _sh.frame_num
|
|
? _prevPocMsb + maxFrameNum
|
|
: _prevPocMsb
|
|
;
|
|
|
|
poc = 2 * (frameNumOffset + _sh.frame_num);
|
|
|
|
if (0 == _nal.idc)
|
|
{
|
|
poc -= 1;
|
|
}
|
|
}
|
|
|
|
_prevPocMsb = frameNumOffset;
|
|
_prevPocLsb = _sh.frame_num;
|
|
|
|
return poc;
|
|
}
|
|
|
|
if (0 != _sps.pic_order_cnt_type)
|
|
{
|
|
return _sh.frame_num * 2;
|
|
}
|
|
|
|
const int32_t maxPocLsb = 1 << (_sps.log2_max_pic_order_cnt_lsb_minus4 + 4);
|
|
int32_t prevPocMsb = _isIdr ? 0 : _prevPocMsb;
|
|
int32_t prevPocLsb = _isIdr ? 0 : _prevPocLsb;
|
|
|
|
const int32_t pocLsb = _sh.pic_order_cnt_lsb;
|
|
int32_t pocMsb;
|
|
|
|
if (pocLsb < prevPocLsb
|
|
&& prevPocLsb - pocLsb >= maxPocLsb / 2)
|
|
{
|
|
pocMsb = prevPocMsb + maxPocLsb;
|
|
}
|
|
else if (pocLsb > prevPocLsb
|
|
&& pocLsb - prevPocLsb > maxPocLsb / 2)
|
|
{
|
|
pocMsb = prevPocMsb - maxPocLsb;
|
|
}
|
|
else
|
|
{
|
|
pocMsb = prevPocMsb;
|
|
}
|
|
|
|
if (0 != _nal.idc)
|
|
{
|
|
_prevPocMsb = pocMsb;
|
|
_prevPocLsb = pocLsb;
|
|
}
|
|
|
|
return pocMsb + pocLsb;
|
|
}
|
|
|
|
struct H264Tables
|
|
{
|
|
H264Tables()
|
|
: prevPocMsb(0)
|
|
, prevPocLsb(0)
|
|
{
|
|
sps.resize(h264::MAX_SPS_ID + 1);
|
|
pps.resize(h264::MAX_PPS_ID + 1);
|
|
bx::memSet(spsValid, 0, sizeof(spsValid) );
|
|
bx::memSet(ppsValid, 0, sizeof(ppsValid) );
|
|
}
|
|
|
|
stl::vector<h264::SPS> sps;
|
|
stl::vector<h264::PPS> pps;
|
|
bool spsValid[h264::MAX_SPS_ID + 1];
|
|
bool ppsValid[h264::MAX_PPS_ID + 1];
|
|
int32_t prevPocMsb;
|
|
int32_t prevPocLsb;
|
|
};
|
|
|
|
void noteH264Sps(Impl& _video, const h264::SPS& _sps, int64_t& _frameUs)
|
|
{
|
|
_video.profile = uint8_t(_sps.profile_idc);
|
|
_video.level = uint8_t(_sps.level_idc);
|
|
_video.chroma = chromaFromFormatIdc(_sps.chroma_format_idc);
|
|
_video.bitDepth = uint8_t(_sps.bit_depth_luma_minus8 + 8);
|
|
|
|
const uint32_t frameMbsOnly = 0 != _sps.frame_mbs_only_flag ? 1 : 0;
|
|
const uint32_t codedWidth = uint32_t(_sps.pic_width_in_mbs_minus1 + 1) * 16;
|
|
const uint32_t codedHeight = uint32_t(_sps.pic_height_in_map_units_minus1 + 1) * 16 * (2 - frameMbsOnly);
|
|
|
|
uint32_t width = codedWidth;
|
|
uint32_t height = codedHeight;
|
|
|
|
if (0 != _sps.frame_cropping_flag)
|
|
{
|
|
const uint32_t cropUnitX = 2;
|
|
const uint32_t cropUnitY = 2 * (2 - frameMbsOnly);
|
|
const uint32_t cropX = uint32_t(_sps.frame_crop_left_offset + _sps.frame_crop_right_offset) * cropUnitX;
|
|
const uint32_t cropY = uint32_t(_sps.frame_crop_top_offset + _sps.frame_crop_bottom_offset) * cropUnitY;
|
|
|
|
if (cropX < codedWidth
|
|
&& cropY < codedHeight)
|
|
{
|
|
width = codedWidth - cropX;
|
|
height = codedHeight - cropY;
|
|
}
|
|
}
|
|
|
|
_video.width = uint16_t(width);
|
|
_video.height = uint16_t(height);
|
|
|
|
if (0 != _sps.vui_parameters_present_flag
|
|
&& 0 != _sps.vui.timing_info_present_flag
|
|
&& 0 < _sps.vui.time_scale
|
|
&& 0 < _sps.vui.num_units_in_tick)
|
|
{
|
|
_frameUs = int64_t(2000000ull * uint64_t(_sps.vui.num_units_in_tick) / uint64_t(_sps.vui.time_scale) );
|
|
}
|
|
}
|
|
|
|
bool parseH264ParameterSet(H264Tables& _tables, Impl& _video, int64_t& _frameUs, const uint8_t* _rbsp, uint32_t _size)
|
|
{
|
|
h264::Bitstream bs;
|
|
bs.init(_rbsp, _size);
|
|
|
|
h264::NALHeader nal = {};
|
|
|
|
if (!h264::read_nal_header(&nal, &bs) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (h264::NAL_UNIT_TYPE_SPS == nal.type)
|
|
{
|
|
h264::SPS sps = {};
|
|
h264::read_sps(&sps, &bs);
|
|
|
|
if (sps.seq_parameter_set_id < 0
|
|
|| sps.seq_parameter_set_id > h264::MAX_SPS_ID)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_tables.sps[sps.seq_parameter_set_id] = sps;
|
|
_tables.spsValid[sps.seq_parameter_set_id] = true;
|
|
noteH264Sps(_video, sps, _frameUs);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (h264::NAL_UNIT_TYPE_PPS == nal.type)
|
|
{
|
|
h264::PPS pps = {};
|
|
h264::read_pps(&pps, &bs);
|
|
|
|
if (pps.pic_parameter_set_id < 0
|
|
|| pps.pic_parameter_set_id > h264::MAX_PPS_ID
|
|
|| pps.seq_parameter_set_id < 0
|
|
|| pps.seq_parameter_set_id > h264::MAX_SPS_ID)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_tables.pps[pps.pic_parameter_set_id] = pps;
|
|
_tables.ppsValid[pps.pic_parameter_set_id] = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool h264SlicePoc(H264Tables& _tables, const uint8_t* _rbsp, uint32_t _size, int32_t& _poc)
|
|
{
|
|
h264::Bitstream bs;
|
|
bs.init(_rbsp, _size);
|
|
|
|
h264::NALHeader nal = {};
|
|
|
|
if (!h264::read_nal_header(&nal, &bs) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
h264::Bitstream peek = bs;
|
|
peek.ue();
|
|
peek.ue();
|
|
const uint32_t ppsId = peek.ue();
|
|
|
|
if (ppsId > uint32_t(h264::MAX_PPS_ID)
|
|
|| !_tables.ppsValid[ppsId])
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t spsId = uint32_t(_tables.pps[ppsId].seq_parameter_set_id);
|
|
|
|
if (spsId > uint32_t(h264::MAX_SPS_ID)
|
|
|| !_tables.spsValid[spsId])
|
|
{
|
|
return false;
|
|
}
|
|
|
|
h264::SliceHeader sh = {};
|
|
h264::read_slice_header(&sh, &nal, _tables.pps.data(), _tables.sps.data(), &bs);
|
|
|
|
const bool isIdr = h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == nal.type;
|
|
_poc = computePoc(_tables.prevPocMsb, _tables.prevPocLsb, _tables.sps[spsId], sh, nal, isIdr);
|
|
|
|
return true;
|
|
}
|
|
|
|
struct HevcSps
|
|
{
|
|
bool valid;
|
|
bool separateColourPlane;
|
|
uint8_t chromaFormatIdc;
|
|
uint8_t bitDepth;
|
|
uint8_t profile;
|
|
uint8_t level;
|
|
uint16_t width;
|
|
uint16_t height;
|
|
uint32_t log2MaxPocLsb;
|
|
int64_t frameUs;
|
|
};
|
|
|
|
struct HevcPps
|
|
{
|
|
bool valid;
|
|
bool dependentSliceSegments;
|
|
bool outputFlagPresent;
|
|
uint32_t spsId;
|
|
uint32_t numExtraSliceHeaderBits;
|
|
};
|
|
|
|
struct HevcTables
|
|
{
|
|
HevcTables()
|
|
: prevPocMsb(0)
|
|
, prevPocLsb(0)
|
|
, firstPicture(true)
|
|
{
|
|
bx::memSet(sps, 0, sizeof(sps) );
|
|
bx::memSet(pps, 0, sizeof(pps) );
|
|
}
|
|
|
|
HevcSps sps[hevc::kMaxSpsCount];
|
|
HevcPps pps[hevc::kMaxPpsCount];
|
|
int32_t prevPocMsb;
|
|
int32_t prevPocLsb;
|
|
bool firstPicture;
|
|
};
|
|
|
|
void skipHevcProfileTierLevel(h264::Bitstream& _bs, uint32_t _maxSubLayersMinus1, uint8_t* _profile, uint8_t* _level)
|
|
{
|
|
_bs.u(2);
|
|
_bs.u(1);
|
|
const uint32_t profileIdc = _bs.u(5);
|
|
_bs.u(32);
|
|
_bs.u(32);
|
|
_bs.u(16);
|
|
const uint32_t levelIdc = _bs.u(8);
|
|
|
|
if (NULL != _profile)
|
|
{
|
|
*_profile = uint8_t(profileIdc);
|
|
*_level = uint8_t(levelIdc);
|
|
}
|
|
|
|
bool subLayerProfilePresent[hevc::kMaxSubLayers] = {};
|
|
bool subLayerLevelPresent[hevc::kMaxSubLayers] = {};
|
|
|
|
for (uint32_t ii = 0; ii < _maxSubLayersMinus1 && ii < hevc::kMaxSubLayers; ++ii)
|
|
{
|
|
subLayerProfilePresent[ii] = 0 != _bs.u1();
|
|
subLayerLevelPresent[ii] = 0 != _bs.u1();
|
|
}
|
|
|
|
if (0 < _maxSubLayersMinus1)
|
|
{
|
|
for (uint32_t ii = _maxSubLayersMinus1; ii < 8; ++ii)
|
|
{
|
|
_bs.u(2);
|
|
}
|
|
}
|
|
|
|
for (uint32_t ii = 0; ii < _maxSubLayersMinus1 && ii < hevc::kMaxSubLayers; ++ii)
|
|
{
|
|
if (subLayerProfilePresent[ii])
|
|
{
|
|
_bs.u(32);
|
|
_bs.u(32);
|
|
_bs.u(24);
|
|
}
|
|
|
|
if (subLayerLevelPresent[ii])
|
|
{
|
|
_bs.u(8);
|
|
}
|
|
}
|
|
}
|
|
|
|
void skipHevcScalingListData(h264::Bitstream& _bs)
|
|
{
|
|
for (uint32_t sizeId = 0; sizeId < 4; ++sizeId)
|
|
{
|
|
for (uint32_t matrixId = 0; matrixId < 6; matrixId += 3 == sizeId ? 3 : 1)
|
|
{
|
|
if (0 == _bs.u1() )
|
|
{
|
|
_bs.ue();
|
|
}
|
|
else
|
|
{
|
|
const uint32_t coefNum = bx::min<uint32_t>(64, 1u << (4 + (sizeId << 1) ) );
|
|
|
|
if (1 < sizeId)
|
|
{
|
|
_bs.se();
|
|
}
|
|
|
|
for (uint32_t ii = 0; ii < coefNum; ++ii)
|
|
{
|
|
_bs.se();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void skipHevcStRefPicSet(h264::Bitstream& _bs, uint32_t _idx, uint32_t* _numDeltaPocs)
|
|
{
|
|
if (0 != _idx
|
|
&& 0 != _bs.u1() )
|
|
{
|
|
_bs.u1();
|
|
_bs.ue();
|
|
|
|
const uint32_t refIdx = _idx - 1;
|
|
uint32_t count = 0;
|
|
|
|
for (uint32_t jj = 0; jj <= _numDeltaPocs[refIdx]; ++jj)
|
|
{
|
|
const bool used = 0 != _bs.u1();
|
|
const bool useDelta = used || 0 != _bs.u1();
|
|
count += useDelta ? 1 : 0;
|
|
}
|
|
|
|
_numDeltaPocs[_idx] = count;
|
|
return;
|
|
}
|
|
|
|
const uint32_t numNegative = _bs.ue();
|
|
const uint32_t numPositive = _bs.ue();
|
|
|
|
for (uint32_t ii = 0; ii < numNegative && ii < 64; ++ii)
|
|
{
|
|
_bs.ue();
|
|
_bs.u1();
|
|
}
|
|
|
|
for (uint32_t ii = 0; ii < numPositive && ii < 64; ++ii)
|
|
{
|
|
_bs.ue();
|
|
_bs.u1();
|
|
}
|
|
|
|
_numDeltaPocs[_idx] = bx::min<uint32_t>(numNegative + numPositive, 64);
|
|
}
|
|
|
|
bool parseHevcSps(const uint8_t* _rbsp, uint32_t _size, HevcSps& _out, uint32_t& _spsId)
|
|
{
|
|
h264::Bitstream bs;
|
|
bs.init(_rbsp + hevc::kNalHeaderSize, _size - hevc::kNalHeaderSize);
|
|
|
|
_out = {};
|
|
|
|
bs.u(4);
|
|
const uint32_t maxSubLayersMinus1 = bs.u(3);
|
|
bs.u1();
|
|
skipHevcProfileTierLevel(bs, maxSubLayersMinus1, &_out.profile, &_out.level);
|
|
|
|
_spsId = bs.ue();
|
|
|
|
const uint32_t chromaFormatIdc = bs.ue();
|
|
_out.chromaFormatIdc = uint8_t(chromaFormatIdc);
|
|
_out.separateColourPlane = 3 == chromaFormatIdc && 0 != bs.u1();
|
|
|
|
const uint32_t width = bs.ue();
|
|
const uint32_t height = bs.ue();
|
|
|
|
uint32_t cropX = 0;
|
|
uint32_t cropY = 0;
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
const uint32_t subWidthC = (1 == chromaFormatIdc || 2 == chromaFormatIdc) ? 2 : 1;
|
|
const uint32_t subHeightC = 1 == chromaFormatIdc ? 2 : 1;
|
|
const uint32_t left = bs.ue();
|
|
const uint32_t right = bs.ue();
|
|
const uint32_t top = bs.ue();
|
|
const uint32_t bottom = bs.ue();
|
|
cropX = (left + right) * subWidthC;
|
|
cropY = (top + bottom) * subHeightC;
|
|
}
|
|
|
|
_out.bitDepth = uint8_t(bs.ue() + 8);
|
|
bs.ue();
|
|
_out.log2MaxPocLsb = bs.ue() + 4;
|
|
|
|
const bool subLayerOrderingInfoPresent = 0 != bs.u1();
|
|
|
|
for (uint32_t ii = subLayerOrderingInfoPresent ? 0 : maxSubLayersMinus1; ii <= maxSubLayersMinus1; ++ii)
|
|
{
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
}
|
|
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
|
|
if (0 != bs.u1()
|
|
&& 0 != bs.u1() )
|
|
{
|
|
skipHevcScalingListData(bs);
|
|
}
|
|
|
|
bs.u1();
|
|
bs.u1();
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.u(4);
|
|
bs.u(4);
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.u1();
|
|
}
|
|
|
|
const uint32_t numStRefPicSets = bs.ue();
|
|
|
|
if (hevc::kMaxStRefPicSets < numStRefPicSets)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t numDeltaPocs[hevc::kMaxStRefPicSets] = {};
|
|
|
|
for (uint32_t ii = 0; ii < numStRefPicSets; ++ii)
|
|
{
|
|
skipHevcStRefPicSet(bs, ii, numDeltaPocs);
|
|
}
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
const uint32_t numLongTerm = bs.ue();
|
|
|
|
for (uint32_t ii = 0; ii < numLongTerm && ii < 32; ++ii)
|
|
{
|
|
bs.u(int(_out.log2MaxPocLsb) );
|
|
bs.u1();
|
|
}
|
|
}
|
|
|
|
bs.u1();
|
|
bs.u1();
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
if (0 != bs.u1() )
|
|
{
|
|
if (255 == bs.u(8) )
|
|
{
|
|
bs.u(16);
|
|
bs.u(16);
|
|
}
|
|
}
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.u1();
|
|
}
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.u(3);
|
|
bs.u1();
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.u(8);
|
|
bs.u(8);
|
|
bs.u(8);
|
|
}
|
|
}
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.ue();
|
|
bs.ue();
|
|
}
|
|
|
|
bs.u1();
|
|
bs.u1();
|
|
bs.u1();
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
bs.ue();
|
|
}
|
|
|
|
if (0 != bs.u1() )
|
|
{
|
|
const uint32_t numUnitsInTick = bs.u(32);
|
|
const uint32_t timeScale = bs.u(32);
|
|
|
|
if (0 != numUnitsInTick
|
|
&& 0 != timeScale)
|
|
{
|
|
_out.frameUs = int64_t(1000000ull * uint64_t(numUnitsInTick) / uint64_t(timeScale) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bs.eof()
|
|
|| 0 == width
|
|
|| 0 == height
|
|
|| cropX >= width
|
|
|| cropY >= height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_out.width = uint16_t(width - cropX);
|
|
_out.height = uint16_t(height - cropY);
|
|
_out.valid = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool parseHevcPps(const uint8_t* _rbsp, uint32_t _size, HevcPps& _out, uint32_t& _ppsId)
|
|
{
|
|
h264::Bitstream bs;
|
|
bs.init(_rbsp + hevc::kNalHeaderSize, _size - hevc::kNalHeaderSize);
|
|
|
|
_out = {};
|
|
|
|
_ppsId = bs.ue();
|
|
_out.spsId = bs.ue();
|
|
_out.dependentSliceSegments = 0 != bs.u1();
|
|
_out.outputFlagPresent = 0 != bs.u1();
|
|
_out.numExtraSliceHeaderBits = bs.u(3);
|
|
_out.valid = !bs.eof();
|
|
|
|
return _out.valid;
|
|
}
|
|
|
|
bool parseHevcParameterSet(HevcTables& _tables, const uint8_t* _rbsp, uint32_t _size)
|
|
{
|
|
if (_size <= hevc::kNalHeaderSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t type = hevc::getNalUnitType(_rbsp);
|
|
|
|
if (hevc::SPS_NUT == type)
|
|
{
|
|
HevcSps sps;
|
|
uint32_t id;
|
|
|
|
if (parseHevcSps(_rbsp, _size, sps, id)
|
|
&& id < hevc::kMaxSpsCount)
|
|
{
|
|
_tables.sps[id] = sps;
|
|
return true;
|
|
}
|
|
}
|
|
else if (hevc::PPS_NUT == type)
|
|
{
|
|
HevcPps pps;
|
|
uint32_t id;
|
|
|
|
if (parseHevcPps(_rbsp, _size, pps, id)
|
|
&& id < hevc::kMaxPpsCount)
|
|
{
|
|
_tables.pps[id] = pps;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool hevcSlicePoc(HevcTables& _tables, const uint8_t* _rbsp, uint32_t _size, int32_t& _poc)
|
|
{
|
|
const uint8_t type = hevc::getNalUnitType(_rbsp);
|
|
|
|
h264::Bitstream bs;
|
|
bs.init(_rbsp + hevc::kNalHeaderSize, _size - hevc::kNalHeaderSize);
|
|
|
|
bs.u1();
|
|
|
|
if (hevc::isIrap(type) )
|
|
{
|
|
bs.u1();
|
|
}
|
|
|
|
const uint32_t ppsId = bs.ue();
|
|
|
|
if (hevc::kMaxPpsCount <= ppsId
|
|
|| !_tables.pps[ppsId].valid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const HevcPps& pps = _tables.pps[ppsId];
|
|
|
|
if (hevc::kMaxSpsCount <= pps.spsId
|
|
|| !_tables.sps[pps.spsId].valid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const HevcSps& sps = _tables.sps[pps.spsId];
|
|
|
|
for (uint32_t ii = 0; ii < pps.numExtraSliceHeaderBits; ++ii)
|
|
{
|
|
bs.u1();
|
|
}
|
|
|
|
bs.ue();
|
|
|
|
if (pps.outputFlagPresent)
|
|
{
|
|
bs.u1();
|
|
}
|
|
|
|
if (sps.separateColourPlane)
|
|
{
|
|
bs.u(2);
|
|
}
|
|
|
|
const int32_t maxPocLsb = 1 << sps.log2MaxPocLsb;
|
|
const int32_t pocLsb = hevc::isIdr(type) ? 0 : int32_t(bs.u(int(sps.log2MaxPocLsb) ) );
|
|
|
|
int32_t pocMsb;
|
|
|
|
if (hevc::isIdrOrBla(type)
|
|
|| (hevc::CRA_NUT == type && _tables.firstPicture) )
|
|
{
|
|
pocMsb = 0;
|
|
}
|
|
else if (pocLsb < _tables.prevPocLsb
|
|
&& _tables.prevPocLsb - pocLsb >= maxPocLsb / 2)
|
|
{
|
|
pocMsb = _tables.prevPocMsb + maxPocLsb;
|
|
}
|
|
else if (pocLsb > _tables.prevPocLsb
|
|
&& pocLsb - _tables.prevPocLsb > maxPocLsb / 2)
|
|
{
|
|
pocMsb = _tables.prevPocMsb - maxPocLsb;
|
|
}
|
|
else
|
|
{
|
|
pocMsb = _tables.prevPocMsb;
|
|
}
|
|
|
|
_tables.prevPocMsb = pocMsb;
|
|
_tables.prevPocLsb = pocLsb;
|
|
_tables.firstPicture = false;
|
|
|
|
_poc = pocMsb + pocLsb;
|
|
|
|
return true;
|
|
}
|
|
|
|
struct Av1SequenceHeader
|
|
{
|
|
uint32_t maxWidth;
|
|
uint32_t maxHeight;
|
|
int64_t frameUs;
|
|
uint8_t profile;
|
|
uint8_t levelIdx;
|
|
uint8_t highBitdepth;
|
|
uint8_t twelveBit;
|
|
uint8_t monochrome;
|
|
uint8_t subsamplingX;
|
|
uint8_t subsamplingY;
|
|
uint8_t reducedStillPictureHeader;
|
|
};
|
|
|
|
uint32_t uvlc(h264::Bitstream& _bs)
|
|
{
|
|
uint32_t leadingZeros = 0;
|
|
|
|
while (leadingZeros < av1::kMaxLeadingZeros
|
|
&& 0 == _bs.u1() )
|
|
{
|
|
++leadingZeros;
|
|
}
|
|
|
|
if (av1::kMaxLeadingZeros <= leadingZeros)
|
|
{
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
return _bs.u(int(leadingZeros) ) + (1u << leadingZeros) - 1;
|
|
}
|
|
|
|
bool parseAv1SequenceHeader(const uint8_t* _data, uint32_t _size, Av1SequenceHeader& _out)
|
|
{
|
|
h264::Bitstream br;
|
|
br.init(_data, _size);
|
|
|
|
_out = {};
|
|
_out.profile = uint8_t(br.u(3) );
|
|
br.u1();
|
|
_out.reducedStillPictureHeader = uint8_t(br.u1() );
|
|
|
|
bool decoderModelInfoPresent = false;
|
|
uint32_t bufferDelayLength = 0;
|
|
|
|
if (_out.reducedStillPictureHeader)
|
|
{
|
|
_out.levelIdx = uint8_t(br.u(5) );
|
|
}
|
|
else
|
|
{
|
|
if (0 != br.u1() )
|
|
{
|
|
const uint32_t numUnitsInDisplayTick = br.u(32);
|
|
const uint32_t timeScale = br.u(32);
|
|
uint32_t ticksPerPicture = 1;
|
|
|
|
if (0 != br.u1() )
|
|
{
|
|
ticksPerPicture = uvlc(br) + 1;
|
|
}
|
|
|
|
if (0 != numUnitsInDisplayTick
|
|
&& 0 != timeScale)
|
|
{
|
|
_out.frameUs = int64_t(1000000ull * uint64_t(numUnitsInDisplayTick) * ticksPerPicture / uint64_t(timeScale) );
|
|
}
|
|
|
|
decoderModelInfoPresent = 0 != br.u1();
|
|
|
|
if (decoderModelInfoPresent)
|
|
{
|
|
bufferDelayLength = br.u(5) + 1;
|
|
br.u(32);
|
|
br.u(5);
|
|
br.u(5);
|
|
}
|
|
}
|
|
|
|
const bool initialDisplayDelayPresent = 0 != br.u1();
|
|
const uint32_t numOperatingPoints = br.u(5) + 1;
|
|
|
|
for (uint32_t ii = 0; ii < numOperatingPoints; ++ii)
|
|
{
|
|
br.u(12);
|
|
const uint32_t levelIdx = br.u(5);
|
|
|
|
if (7 < levelIdx)
|
|
{
|
|
br.u1();
|
|
}
|
|
|
|
if (0 == ii)
|
|
{
|
|
_out.levelIdx = uint8_t(levelIdx);
|
|
}
|
|
|
|
if (decoderModelInfoPresent
|
|
&& 0 != br.u1() )
|
|
{
|
|
br.u(int(bufferDelayLength) );
|
|
br.u(int(bufferDelayLength) );
|
|
br.u1();
|
|
}
|
|
|
|
if (initialDisplayDelayPresent
|
|
&& 0 != br.u1() )
|
|
{
|
|
br.u(4);
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32_t widthBits = br.u(4) + 1;
|
|
const uint32_t heightBits = br.u(4) + 1;
|
|
_out.maxWidth = br.u(int(widthBits) ) + 1;
|
|
_out.maxHeight = br.u(int(heightBits) ) + 1;
|
|
|
|
if (!_out.reducedStillPictureHeader
|
|
&& 0 != br.u1() )
|
|
{
|
|
br.u(4);
|
|
br.u(3);
|
|
}
|
|
|
|
br.u(3);
|
|
|
|
if (!_out.reducedStillPictureHeader)
|
|
{
|
|
br.u(4);
|
|
const bool enableOrderHint = 0 != br.u1();
|
|
|
|
if (enableOrderHint)
|
|
{
|
|
br.u(2);
|
|
}
|
|
|
|
const uint32_t forceScreenContentTools = 0 != br.u1() ? 2 : br.u1();
|
|
|
|
if (0 < forceScreenContentTools
|
|
&& 0 == br.u1() )
|
|
{
|
|
br.u1();
|
|
}
|
|
|
|
if (enableOrderHint)
|
|
{
|
|
br.u(3);
|
|
}
|
|
}
|
|
|
|
br.u(3);
|
|
|
|
_out.highBitdepth = uint8_t(br.u1() );
|
|
_out.twelveBit = av1::PROFILE_PROFESSIONAL == _out.profile && _out.highBitdepth ? uint8_t(br.u1() ) : 0;
|
|
_out.monochrome = av1::PROFILE_HIGH == _out.profile ? 0 : uint8_t(br.u1() );
|
|
|
|
uint32_t colorPrimaries = 2;
|
|
uint32_t transfer = 2;
|
|
uint32_t matrix = 2;
|
|
|
|
if (0 != br.u1() )
|
|
{
|
|
colorPrimaries = br.u(8);
|
|
transfer = br.u(8);
|
|
matrix = br.u(8);
|
|
}
|
|
|
|
if (_out.monochrome)
|
|
{
|
|
br.u1();
|
|
_out.subsamplingX = 1;
|
|
_out.subsamplingY = 1;
|
|
}
|
|
else if (1 == colorPrimaries && 13 == transfer && 0 == matrix)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
br.u1();
|
|
|
|
if (av1::PROFILE_MAIN == _out.profile)
|
|
{
|
|
_out.subsamplingX = 1;
|
|
_out.subsamplingY = 1;
|
|
}
|
|
else if (av1::PROFILE_PROFESSIONAL == _out.profile)
|
|
{
|
|
if (_out.twelveBit)
|
|
{
|
|
_out.subsamplingX = uint8_t(br.u1() );
|
|
_out.subsamplingY = _out.subsamplingX ? uint8_t(br.u1() ) : 0;
|
|
}
|
|
else
|
|
{
|
|
_out.subsamplingX = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return !br.eof()
|
|
&& 0 != _out.maxWidth
|
|
&& 0 != _out.maxHeight
|
|
;
|
|
}
|
|
|
|
void noteAv1SequenceHeader(Impl& _video, const Av1SequenceHeader& _seq)
|
|
{
|
|
_video.profile = _seq.profile;
|
|
_video.level = _seq.levelIdx;
|
|
_video.bitDepth = _seq.twelveBit ? 12 : _seq.highBitdepth ? 10 : 8;
|
|
_video.chroma = _seq.subsamplingX && _seq.subsamplingY ? 0
|
|
: _seq.subsamplingX ? 2
|
|
: 4
|
|
;
|
|
_video.width = uint16_t(_seq.maxWidth);
|
|
_video.height = uint16_t(_seq.maxHeight);
|
|
}
|
|
|
|
struct Obu
|
|
{
|
|
uint8_t type;
|
|
uint32_t headerSize;
|
|
uint32_t payloadSize;
|
|
bool hasSize;
|
|
};
|
|
|
|
bool readObuHeader(const uint8_t* _data, uint32_t _size, Obu& _out)
|
|
{
|
|
if (0 == _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t header = _data[0];
|
|
const bool hasExtension = 0 != (header & av1::kObuExtensionFlag);
|
|
|
|
_out.type = (header >> av1::kObuTypeShift) & av1::kObuTypeMask;
|
|
_out.hasSize = 0 != (header & av1::kObuHasSizeField);
|
|
_out.headerSize = 1 + (hasExtension ? 1 : 0);
|
|
|
|
if (0 != (header & 0x80)
|
|
|| _size < _out.headerSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_out.hasSize)
|
|
{
|
|
_out.payloadSize = _size - _out.headerSize;
|
|
return true;
|
|
}
|
|
|
|
uint32_t size = 0;
|
|
|
|
for (uint32_t shift = 0; shift < 32; shift += 7)
|
|
{
|
|
if (_out.headerSize >= _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t byte = _data[_out.headerSize++];
|
|
size |= uint32_t(byte & av1::kLeb128ValueMask) << shift;
|
|
|
|
if (0 == (byte & av1::kLeb128ContinuationBit) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
_out.payloadSize = bx::min(size, _size - _out.headerSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename Fn>
|
|
void visitObus(const uint8_t* _data, uint32_t _size, Fn _visit)
|
|
{
|
|
uint32_t pos = 0;
|
|
|
|
while (pos < _size)
|
|
{
|
|
Obu obu;
|
|
|
|
if (!readObuHeader(_data + pos, _size - pos, obu) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint32_t obuSize = obu.headerSize + obu.payloadSize;
|
|
_visit(obu.type, _data + pos, obuSize, _data + pos + obu.headerSize, obu.payloadSize);
|
|
pos += obuSize;
|
|
}
|
|
}
|
|
|
|
bool av1FrameIsKey(const uint8_t* _payload, uint32_t _size, bool _reducedStillPictureHeader)
|
|
{
|
|
if (0 == _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_reducedStillPictureHeader)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
h264::Bitstream br;
|
|
br.init(_payload, _size);
|
|
|
|
if (0 != br.u1() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t frameType = br.u(2);
|
|
const bool showFrame = 0 != br.u1();
|
|
|
|
return av1::KEY_FRAME == frameType && showFrame;
|
|
}
|
|
|
|
bool av1TemporalUnitIsKey(const uint8_t* _data, uint32_t _size, bool _reducedStillPictureHeader)
|
|
{
|
|
bool seen = false;
|
|
bool key = false;
|
|
|
|
visitObus(_data, _size, [&](uint8_t _type, const uint8_t*, uint32_t, const uint8_t* _payload, uint32_t _payloadSize)
|
|
{
|
|
if (!seen
|
|
&& (av1::OBU_FRAME_HEADER == _type || av1::OBU_FRAME == _type) )
|
|
{
|
|
seen = true;
|
|
key = av1FrameIsKey(_payload, _payloadSize, _reducedStillPictureHeader);
|
|
}
|
|
});
|
|
|
|
return key;
|
|
}
|
|
|
|
bool takeAv1SequenceHeader(Impl& _video, Av1SequenceHeader& _seq, const uint8_t* _data, uint32_t _size)
|
|
{
|
|
bool found = false;
|
|
|
|
visitObus(_data, _size, [&](uint8_t _type, const uint8_t* _obu, uint32_t _obuSize, const uint8_t* _payload, uint32_t _payloadSize)
|
|
{
|
|
if (!found
|
|
&& av1::OBU_SEQUENCE_HEADER == _type
|
|
&& parseAv1SequenceHeader(_payload, _payloadSize, _seq) )
|
|
{
|
|
found = true;
|
|
_video.parameterSets.clear();
|
|
_video.parameterSets.insert(_video.parameterSets.end(), _obu, _obu + _obuSize);
|
|
}
|
|
});
|
|
|
|
return found;
|
|
}
|
|
|
|
struct Probe
|
|
{
|
|
Impl::Container container;
|
|
bgfx::VideoCodec::Enum codec;
|
|
};
|
|
|
|
bool probeAnnexB(const uint8_t* _data, uint32_t _size, bgfx::VideoCodec::Enum& _codec)
|
|
{
|
|
uint32_t header = 0;
|
|
|
|
if (4 <= _size && 0 == _data[0] && 0 == _data[1] && 0 == _data[2] && 1 == _data[3])
|
|
{
|
|
header = 4;
|
|
}
|
|
else if (3 <= _size && 0 == _data[0] && 0 == _data[1] && 1 == _data[2])
|
|
{
|
|
header = 3;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_size < header + 2
|
|
|| 0 != (_data[header] & 0x80) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t hevcType = hevc::getNalUnitType(_data + header);
|
|
const uint32_t hevcLayerId = (uint32_t(_data[header] & 1) << 5) | (_data[header + 1] >> 3);
|
|
const uint32_t hevcTidPlus1 = _data[header + 1] & 7;
|
|
|
|
const bool hevcHead = false
|
|
|| hevc::isParameterSet(hevcType)
|
|
|| hevc::AUD_NUT == hevcType
|
|
|| hevc::PREFIX_SEI_NUT == hevcType
|
|
|| hevc::isIrap(hevcType)
|
|
;
|
|
|
|
if (hevcHead
|
|
&& 0 == hevcLayerId
|
|
&& 0 != hevcTidPlus1)
|
|
{
|
|
_codec = bgfx::VideoCodec::H265;
|
|
return true;
|
|
}
|
|
|
|
const uint8_t h264Type = _data[header] & kH264NalUnitTypeMask;
|
|
|
|
const bool h264Head = false
|
|
|| h264::NAL_UNIT_TYPE_SPS == h264Type
|
|
|| h264::NAL_UNIT_TYPE_PPS == h264Type
|
|
|| h264::NAL_UNIT_TYPE_AUD == h264Type
|
|
|| h264::NAL_UNIT_TYPE_SEI == h264Type
|
|
|| h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == h264Type
|
|
;
|
|
|
|
if (h264Head)
|
|
{
|
|
_codec = bgfx::VideoCodec::H264;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool probeObu(const uint8_t* _data, uint32_t _size)
|
|
{
|
|
Obu obu;
|
|
|
|
if (!readObuHeader(_data, _size, obu)
|
|
|| !obu.hasSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (av1::OBU_SEQUENCE_HEADER == obu.type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (av1::OBU_TEMPORAL_DELIMITER != obu.type
|
|
|| 0 != obu.payloadSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t next = obu.headerSize;
|
|
|
|
return readObuHeader(_data + next, _size - next, obu)
|
|
&& obu.hasSize
|
|
&& av1::OBU_SEQUENCE_HEADER == obu.type
|
|
;
|
|
}
|
|
|
|
bool probeBytes(const uint8_t* _data, uint32_t _size, Probe& _out)
|
|
{
|
|
if (12 <= _size)
|
|
{
|
|
const uint8_t* box = _data + 4;
|
|
|
|
if (isFourCC(box, "ftyp")
|
|
|| isFourCC(box, "moov")
|
|
|| isFourCC(box, "mdat")
|
|
|| isFourCC(box, "free")
|
|
|| isFourCC(box, "skip")
|
|
|| isFourCC(box, "wide") )
|
|
{
|
|
_out = { Impl::Mp4, bgfx::VideoCodec::Count };
|
|
return true;
|
|
}
|
|
|
|
if (isFourCC(_data, "DKIF")
|
|
&& isFourCC(_data + 8, "AV01") )
|
|
{
|
|
_out = { Impl::Ivf, bgfx::VideoCodec::AV1 };
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bgfx::VideoCodec::Enum codec;
|
|
|
|
if (probeAnnexB(_data, _size, codec) )
|
|
{
|
|
_out = { Impl::AnnexB, codec };
|
|
return true;
|
|
}
|
|
|
|
if (probeObu(_data, _size) )
|
|
{
|
|
_out = { Impl::Obu, bgfx::VideoCodec::AV1 };
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool probeFile(const bx::FilePath& _filePath, Probe& _out)
|
|
{
|
|
bx::FileReader reader;
|
|
bx::Error err;
|
|
|
|
if (!bx::open(&reader, _filePath, &err) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint8_t data[kProbeSize] = {};
|
|
const int32_t size = bx::read(&reader, data, sizeof(data), &err);
|
|
bx::close(&reader);
|
|
|
|
return 0 < size
|
|
&& probeBytes(data, uint32_t(size), _out)
|
|
;
|
|
}
|
|
|
|
class FileScanner
|
|
{
|
|
public:
|
|
FileScanner(bx::FileReaderI* _reader, uint64_t _size)
|
|
: m_reader(_reader)
|
|
, m_size(_size)
|
|
, m_bufferPos(0)
|
|
, m_bufferLen(0)
|
|
{
|
|
m_buffer.resize(kScanBufferSize);
|
|
}
|
|
|
|
const uint8_t* window(uint64_t _pos, uint32_t _minLen, uint32_t& _len)
|
|
{
|
|
if (_pos >= m_size)
|
|
{
|
|
_len = 0;
|
|
return NULL;
|
|
}
|
|
|
|
const uint64_t want = bx::min<uint64_t>(_minLen, m_size - _pos);
|
|
|
|
if (_pos < m_bufferPos
|
|
|| _pos + want > m_bufferPos + m_bufferLen)
|
|
{
|
|
refill(_pos);
|
|
}
|
|
|
|
_len = uint32_t(bx::min<uint64_t>(m_bufferPos + m_bufferLen - _pos, UINT32_MAX) );
|
|
|
|
return &m_buffer[size_t(_pos - m_bufferPos)];
|
|
}
|
|
|
|
uint64_t size() const
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
private:
|
|
void refill(uint64_t _pos)
|
|
{
|
|
bx::Error err;
|
|
bx::seek(m_reader, int64_t(_pos), bx::Whence::Begin);
|
|
const int32_t len = bx::read(m_reader, m_buffer.data(), int32_t(m_buffer.size() ), &err);
|
|
|
|
m_bufferPos = _pos;
|
|
m_bufferLen = 0 < len ? uint32_t(len) : 0;
|
|
}
|
|
|
|
bx::FileReaderI* m_reader;
|
|
uint64_t m_size;
|
|
uint64_t m_bufferPos;
|
|
uint32_t m_bufferLen;
|
|
stl::vector<uint8_t> m_buffer;
|
|
};
|
|
|
|
uint64_t findStartCode(FileScanner& _scanner, uint64_t _pos)
|
|
{
|
|
const uint64_t size = _scanner.size();
|
|
|
|
while (_pos + 3 <= size)
|
|
{
|
|
uint32_t len;
|
|
const uint8_t* data = _scanner.window(_pos, 3, len);
|
|
|
|
if (NULL == data
|
|
|| len < 3)
|
|
{
|
|
break;
|
|
}
|
|
|
|
uint32_t ii = 0;
|
|
|
|
for (; ii + 3 <= len; ++ii)
|
|
{
|
|
if (0 == data[ii]
|
|
&& 0 == data[ii+1]
|
|
&& 1 == data[ii+2])
|
|
{
|
|
const uint64_t sc = _pos + ii;
|
|
|
|
if (0 < ii)
|
|
{
|
|
return 0 == data[ii-1] ? sc - 1 : sc;
|
|
}
|
|
|
|
if (0 < sc)
|
|
{
|
|
uint32_t prevLen;
|
|
const uint8_t* prev = _scanner.window(sc - 1, 1, prevLen);
|
|
return NULL != prev && 0 == prev[0] ? sc - 1 : sc;
|
|
}
|
|
|
|
return sc;
|
|
}
|
|
}
|
|
|
|
_pos += ii;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
struct GopPicture
|
|
{
|
|
int32_t poc;
|
|
uint32_t frame;
|
|
};
|
|
|
|
int32_t compareGopPicture(const void* _lhs, const void* _rhs)
|
|
{
|
|
const GopPicture& lhs = *(const GopPicture*)_lhs;
|
|
const GopPicture& rhs = *(const GopPicture*)_rhs;
|
|
|
|
if (lhs.poc != rhs.poc)
|
|
{
|
|
return lhs.poc < rhs.poc ? -1 : 1;
|
|
}
|
|
|
|
return lhs.frame < rhs.frame ? -1 : lhs.frame > rhs.frame ? 1 : 0;
|
|
}
|
|
|
|
void finalizeGop(Impl& _video, stl::vector<GopPicture>& _gop, uint64_t& _displayBase, int64_t _frameUs)
|
|
{
|
|
if (_gop.empty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bx::quickSort(_gop.data(), uint32_t(_gop.size() ), sizeof(GopPicture), compareGopPicture);
|
|
|
|
for (uint32_t ii = 0; ii < uint32_t(_gop.size() ); ++ii)
|
|
{
|
|
_video.frames[_gop[ii].frame].ptsUs = (_displayBase + ii) * uint64_t(_frameUs);
|
|
}
|
|
|
|
_displayBase += _gop.size();
|
|
_gop.clear();
|
|
}
|
|
|
|
void closeFrame(Impl& _video, uint64_t _end)
|
|
{
|
|
if (!_video.frames.empty() )
|
|
{
|
|
Impl::Frame& last = _video.frames.back();
|
|
last.size = uint32_t(_end - last.offset);
|
|
_video.totalBytes += last.size;
|
|
}
|
|
}
|
|
|
|
bool indexAnnexB(Impl& _video)
|
|
{
|
|
const bool isHevc = bgfx::VideoCodec::H265 == _video.codec;
|
|
|
|
FileScanner scanner(&_video.reader, _video.fileSize);
|
|
|
|
H264Tables h264Tables;
|
|
HevcTables hevcTables;
|
|
|
|
int64_t frameUs = 0;
|
|
uint64_t displayBase = 0;
|
|
uint64_t unitBegin = UINT64_MAX;
|
|
uint64_t pos = findStartCode(scanner, 0);
|
|
|
|
stl::vector<GopPicture> gop;
|
|
stl::vector<uint8_t> rbsp;
|
|
rbsp.resize(kNalPeekSize);
|
|
|
|
while (pos < scanner.size() )
|
|
{
|
|
uint32_t len;
|
|
const uint8_t* sc = scanner.window(pos, 4, len);
|
|
|
|
if (NULL == sc
|
|
|| len < 3)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint32_t scLen = 1 == sc[2] ? 3 : 4;
|
|
const uint64_t header = pos + scLen;
|
|
const uint64_t next = findStartCode(scanner, header);
|
|
|
|
if (header >= next)
|
|
{
|
|
pos = next;
|
|
continue;
|
|
}
|
|
|
|
const uint32_t peek = uint32_t(bx::min<uint64_t>(next - header, kNalPeekSize) );
|
|
const uint8_t* nal = scanner.window(header, peek, len);
|
|
const uint32_t nalLen = bx::min(peek, len);
|
|
const uint32_t rbspSize = stripEmulationPrevention(rbsp.data(), nal, nalLen);
|
|
const uint32_t minSize = isHevc ? hevc::kNalHeaderSize + 1 : 2;
|
|
|
|
if (rbspSize < minSize)
|
|
{
|
|
pos = next;
|
|
continue;
|
|
}
|
|
|
|
bool parameterSet;
|
|
bool slice;
|
|
bool firstSlice;
|
|
bool keyframe;
|
|
|
|
if (isHevc)
|
|
{
|
|
const uint8_t type = hevc::getNalUnitType(rbsp.data() );
|
|
parameterSet = hevc::isParameterSet(type);
|
|
slice = hevc::isVcl(type);
|
|
firstSlice = 0 != (rbsp[hevc::kNalHeaderSize] & 0x80);
|
|
keyframe = hevc::isIrap(type);
|
|
}
|
|
else
|
|
{
|
|
const uint8_t type = rbsp[0] & kH264NalUnitTypeMask;
|
|
parameterSet = h264::NAL_UNIT_TYPE_SPS == type || h264::NAL_UNIT_TYPE_PPS == type;
|
|
slice = h264::NAL_UNIT_TYPE_CODED_SLICE_NON_IDR == type || h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == type;
|
|
firstSlice = 0 != (rbsp[1] & 0x80);
|
|
keyframe = h264::NAL_UNIT_TYPE_CODED_SLICE_IDR == type;
|
|
}
|
|
|
|
if (parameterSet)
|
|
{
|
|
if (isHevc)
|
|
{
|
|
parseHevcParameterSet(hevcTables, rbsp.data(), rbspSize);
|
|
}
|
|
else
|
|
{
|
|
parseH264ParameterSet(h264Tables, _video, frameUs, rbsp.data(), rbspSize);
|
|
}
|
|
|
|
if (_video.frames.empty()
|
|
&& UINT64_MAX == unitBegin)
|
|
{
|
|
appendNalAsAnnexB(_video.parameterSets, nal, nalLen);
|
|
}
|
|
else
|
|
{
|
|
unitBegin = bx::min(unitBegin, pos);
|
|
}
|
|
}
|
|
else if (slice && firstSlice)
|
|
{
|
|
const uint64_t start = UINT64_MAX != unitBegin ? unitBegin : pos;
|
|
closeFrame(_video, start);
|
|
|
|
int32_t poc = 0;
|
|
|
|
const bool haveOrder = isHevc
|
|
? hevcSlicePoc(hevcTables, rbsp.data(), rbspSize, poc)
|
|
: h264SlicePoc(h264Tables, rbsp.data(), rbspSize, poc)
|
|
;
|
|
|
|
const bool gopStart = isHevc
|
|
? hevc::isIdrOrBla(hevc::getNalUnitType(rbsp.data() ) )
|
|
: keyframe
|
|
;
|
|
|
|
if (gopStart)
|
|
{
|
|
finalizeGop(_video, gop, displayBase, 0 != frameUs ? frameUs : kDefaultFrameUs);
|
|
}
|
|
|
|
const uint32_t frameIdx = uint32_t(_video.frames.size() );
|
|
|
|
_video.frames.push_back(
|
|
{
|
|
.ptsUs = 0,
|
|
.offset = start,
|
|
.size = 0,
|
|
.isKeyframe = keyframe,
|
|
});
|
|
|
|
gop.push_back({ haveOrder ? poc : int32_t(frameIdx), frameIdx });
|
|
unitBegin = UINT64_MAX;
|
|
}
|
|
else if (!slice
|
|
&& !_video.frames.empty() )
|
|
{
|
|
unitBegin = bx::min(unitBegin, pos);
|
|
}
|
|
|
|
pos = next;
|
|
}
|
|
|
|
closeFrame(_video, scanner.size() );
|
|
|
|
if (0 == frameUs
|
|
&& isHevc)
|
|
{
|
|
for (uint32_t ii = 0; ii < hevc::kMaxSpsCount; ++ii)
|
|
{
|
|
if (hevcTables.sps[ii].valid)
|
|
{
|
|
const HevcSps& sps = hevcTables.sps[ii];
|
|
_video.profile = sps.profile;
|
|
_video.level = sps.level;
|
|
_video.chroma = chromaFromFormatIdc(sps.chromaFormatIdc);
|
|
_video.bitDepth = sps.bitDepth;
|
|
_video.width = sps.width;
|
|
_video.height = sps.height;
|
|
frameUs = sps.frameUs;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
finalizeGop(_video, gop, displayBase, 0 != frameUs ? frameUs : kDefaultFrameUs);
|
|
|
|
return !_video.frames.empty()
|
|
&& !_video.parameterSets.empty()
|
|
&& 0 != _video.width
|
|
&& 0 != _video.height
|
|
;
|
|
}
|
|
|
|
bool indexObu(Impl& _video)
|
|
{
|
|
FileScanner scanner(&_video.reader, _video.fileSize);
|
|
|
|
Av1SequenceHeader seq = {};
|
|
bool haveSeq = false;
|
|
bool keySeen = false;
|
|
uint64_t pos = 0;
|
|
|
|
while (pos < scanner.size() )
|
|
{
|
|
uint32_t len;
|
|
const uint8_t* data = scanner.window(pos, av1::kObuHeaderMaxSize, len);
|
|
Obu obu;
|
|
|
|
if (NULL == data
|
|
|| !readObuHeader(data, len, obu) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint64_t payload = pos + obu.headerSize;
|
|
const uint64_t next = obu.hasSize
|
|
? bx::min(payload + obu.payloadSize, scanner.size() )
|
|
: scanner.size()
|
|
;
|
|
|
|
if (av1::OBU_TEMPORAL_DELIMITER == obu.type
|
|
|| _video.frames.empty() )
|
|
{
|
|
closeFrame(_video, pos);
|
|
|
|
_video.frames.push_back(
|
|
{
|
|
.ptsUs = 0,
|
|
.offset = pos,
|
|
.size = 0,
|
|
.isKeyframe = false,
|
|
});
|
|
|
|
keySeen = false;
|
|
}
|
|
|
|
if (av1::OBU_SEQUENCE_HEADER == obu.type
|
|
&& !haveSeq)
|
|
{
|
|
const uint32_t obuSize = uint32_t(next - pos);
|
|
const uint8_t* whole = scanner.window(pos, obuSize, len);
|
|
|
|
if (NULL != whole
|
|
&& obuSize <= len
|
|
&& parseAv1SequenceHeader(whole + obu.headerSize, obu.payloadSize, seq) )
|
|
{
|
|
haveSeq = true;
|
|
_video.parameterSets.insert(_video.parameterSets.end(), whole, whole + obuSize);
|
|
}
|
|
}
|
|
else if (!keySeen
|
|
&& (av1::OBU_FRAME_HEADER == obu.type || av1::OBU_FRAME == obu.type) )
|
|
{
|
|
const uint8_t* head = scanner.window(payload, 1, len);
|
|
keySeen = true;
|
|
_video.frames.back().isKeyframe = NULL != head && av1FrameIsKey(head, 1, 0 != seq.reducedStillPictureHeader);
|
|
}
|
|
|
|
pos = next;
|
|
}
|
|
|
|
closeFrame(_video, scanner.size() );
|
|
|
|
if (!haveSeq)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
noteAv1SequenceHeader(_video, seq);
|
|
|
|
const int64_t frameUs = 0 != seq.frameUs ? seq.frameUs : kDefaultFrameUs;
|
|
|
|
for (uint32_t ii = 0; ii < uint32_t(_video.frames.size() ); ++ii)
|
|
{
|
|
_video.frames[ii].ptsUs = uint64_t(ii) * uint64_t(frameUs);
|
|
}
|
|
|
|
return !_video.frames.empty();
|
|
}
|
|
|
|
bool indexIvf(Impl& _video)
|
|
{
|
|
FileScanner scanner(&_video.reader, _video.fileSize);
|
|
|
|
uint32_t len;
|
|
const uint8_t* header = scanner.window(0, ivf::kHeaderSize, len);
|
|
|
|
if (NULL == header
|
|
|| len < ivf::kHeaderSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t headerSize = readLe<uint16_t>(header + 6);
|
|
const uint32_t timebaseDen = readLe<uint32_t>(header + 16);
|
|
const uint32_t timebaseNum = readLe<uint32_t>(header + 20);
|
|
|
|
Av1SequenceHeader seq = {};
|
|
bool haveSeq = false;
|
|
|
|
uint64_t pos = bx::max<uint32_t>(headerSize, ivf::kHeaderSize);
|
|
|
|
while (pos + ivf::kFrameHeaderSize <= scanner.size() )
|
|
{
|
|
const uint8_t* frameHeader = scanner.window(pos, ivf::kFrameHeaderSize, len);
|
|
|
|
if (NULL == frameHeader
|
|
|| len < ivf::kFrameHeaderSize)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint32_t size = readLe<uint32_t>(frameHeader);
|
|
const uint64_t pts = readLe<uint64_t>(frameHeader + 4);
|
|
const uint64_t offset = pos + ivf::kFrameHeaderSize;
|
|
|
|
if (0 == size
|
|
|| offset + size > scanner.size()
|
|
|| kScanBufferSize < size)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint8_t* data = scanner.window(offset, size, len);
|
|
|
|
if (NULL == data
|
|
|| len < size)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!haveSeq)
|
|
{
|
|
haveSeq = takeAv1SequenceHeader(_video, seq, data, size);
|
|
}
|
|
|
|
const uint64_t ptsUs = 0 != timebaseDen
|
|
? pts * uint64_t(timebaseNum) * 1000000ull / uint64_t(timebaseDen)
|
|
: uint64_t(_video.frames.size() ) * uint64_t(kDefaultFrameUs)
|
|
;
|
|
|
|
_video.frames.push_back(
|
|
{
|
|
.ptsUs = ptsUs,
|
|
.offset = offset,
|
|
.size = size,
|
|
.isKeyframe = av1TemporalUnitIsKey(data, size, 0 != seq.reducedStillPictureHeader),
|
|
});
|
|
|
|
_video.totalBytes += size;
|
|
pos = offset + size;
|
|
}
|
|
|
|
if (!haveSeq)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
noteAv1SequenceHeader(_video, seq);
|
|
|
|
return !_video.frames.empty();
|
|
}
|
|
|
|
bool parseAvcC(Impl& _video, const uint8_t* _data, uint32_t _size, uint32_t& _lengthSize)
|
|
{
|
|
if (NULL == _data
|
|
|| _size < kBoxHeaderSize + 7)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t pos = kBoxHeaderSize;
|
|
++pos;
|
|
|
|
const uint8_t profileIdc = _data[pos++];
|
|
++pos;
|
|
|
|
const uint8_t levelIdc = _data[pos++];
|
|
_lengthSize = uint32_t(_data[pos++] & 0x03) + 1;
|
|
const uint32_t numSps = uint32_t(_data[pos++] & 0x1f);
|
|
|
|
_video.profile = profileIdc;
|
|
_video.level = levelIdc;
|
|
_video.chroma = 0;
|
|
_video.bitDepth = 8;
|
|
|
|
H264Tables tables;
|
|
int64_t frameUs = 0;
|
|
stl::vector<uint8_t> rbsp;
|
|
|
|
for (uint32_t ii = 0; ii < numSps; ++ii)
|
|
{
|
|
if (pos + 2 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t nalLen = readBe<uint16_t>(_data + pos);
|
|
pos += 2;
|
|
|
|
if (pos + nalLen > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
appendNalAsAnnexB(_video.parameterSets, _data + pos, nalLen);
|
|
stripEmulationPrevention(rbsp, _data + pos, nalLen);
|
|
parseH264ParameterSet(tables, _video, frameUs, rbsp.data(), uint32_t(rbsp.size() ) );
|
|
|
|
pos += nalLen;
|
|
}
|
|
|
|
if (pos + 1 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t numPps = _data[pos++];
|
|
|
|
for (uint32_t ii = 0; ii < numPps; ++ii)
|
|
{
|
|
if (pos + 2 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t nalLen = readBe<uint16_t>(_data + pos);
|
|
pos += 2;
|
|
|
|
if (pos + nalLen > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
appendNalAsAnnexB(_video.parameterSets, _data + pos, nalLen);
|
|
pos += nalLen;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool parseHvcC(Impl& _video, const uint8_t* _data, uint32_t _size, uint32_t& _lengthSize)
|
|
{
|
|
if (NULL == _data
|
|
|| _size < kBoxHeaderSize + 23)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t pos = kBoxHeaderSize;
|
|
++pos;
|
|
|
|
const uint8_t profileIdc = _data[pos++] & 0x1f;
|
|
pos += 10;
|
|
|
|
const uint8_t levelIdc = _data[pos++];
|
|
pos += 3;
|
|
|
|
const uint8_t chromaFormat = _data[pos++] & 0x03;
|
|
const uint8_t bitDepthLuma = uint8_t( (_data[pos++] & 0x07) + 8);
|
|
pos += 3;
|
|
|
|
_lengthSize = uint32_t(_data[pos++] & 0x03) + 1;
|
|
|
|
if (pos + 1 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t numArrays = _data[pos++];
|
|
|
|
_video.profile = profileIdc;
|
|
_video.level = levelIdc;
|
|
_video.chroma = chromaFromFormatIdc(chromaFormat);
|
|
_video.bitDepth = bitDepthLuma;
|
|
|
|
stl::vector<uint8_t> vps;
|
|
stl::vector<uint8_t> sps;
|
|
stl::vector<uint8_t> pps;
|
|
|
|
for (uint32_t aa = 0; aa < numArrays; ++aa)
|
|
{
|
|
if (pos + 3 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t nalUnitType = _data[pos++] & 0x3f;
|
|
const uint32_t numNalus = readBe<uint16_t>(_data + pos);
|
|
pos += 2;
|
|
|
|
stl::vector<uint8_t>* dst = NULL;
|
|
|
|
switch (nalUnitType)
|
|
{
|
|
case hevc::VPS_NUT: dst = &vps; break;
|
|
case hevc::SPS_NUT: dst = &sps; break;
|
|
case hevc::PPS_NUT: dst = &pps; break;
|
|
default: break;
|
|
}
|
|
|
|
for (uint32_t nn = 0; nn < numNalus; ++nn)
|
|
{
|
|
if (pos + 2 > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t nalLen = readBe<uint16_t>(_data + pos);
|
|
pos += 2;
|
|
|
|
if (pos + nalLen > _size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (NULL != dst)
|
|
{
|
|
appendNalAsAnnexB(*dst, _data + pos, nalLen);
|
|
}
|
|
|
|
pos += nalLen;
|
|
}
|
|
}
|
|
|
|
_video.parameterSets.insert(_video.parameterSets.end(), vps.begin(), vps.end() );
|
|
_video.parameterSets.insert(_video.parameterSets.end(), sps.begin(), sps.end() );
|
|
_video.parameterSets.insert(_video.parameterSets.end(), pps.begin(), pps.end() );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool parseAv1C(Impl& _video, const uint8_t* _data, uint32_t _size)
|
|
{
|
|
if (NULL == _data
|
|
|| _size < kBoxHeaderSize + av1::kAv1CSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint8_t* av1c = _data + kBoxHeaderSize;
|
|
|
|
const uint8_t highBitdepth = (av1c[2] >> 6) & 1;
|
|
const uint8_t twelveBit = (av1c[2] >> 5) & 1;
|
|
const uint8_t subsamplingX = (av1c[2] >> 3) & 1;
|
|
const uint8_t subsamplingY = (av1c[2] >> 2) & 1;
|
|
|
|
_video.profile = av1c[1] >> 5;
|
|
_video.level = av1c[1] & 0x1f;
|
|
_video.bitDepth = twelveBit ? 12 : highBitdepth ? 10 : 8;
|
|
_video.chroma = subsamplingX && subsamplingY ? 0
|
|
: subsamplingX ? 2
|
|
: 4
|
|
;
|
|
|
|
Av1SequenceHeader seq;
|
|
takeAv1SequenceHeader(_video, seq, av1c + av1::kAv1CSize, _size - kBoxHeaderSize - av1::kAv1CSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
lsmash_codec_specific_t* getUnstructuredCodecSpecific(lsmash_summary_t* _sum, uint32_t _idx, lsmash_codec_specific_t*& _cs)
|
|
{
|
|
_cs = lsmash_get_codec_specific_data(_sum, _idx + 1);
|
|
|
|
if (NULL == _cs)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED == _cs->format
|
|
? _cs
|
|
: lsmash_convert_codec_specific_format(_cs, LSMASH_CODEC_SPECIFIC_FORMAT_UNSTRUCTURED)
|
|
;
|
|
}
|
|
|
|
void parseMp4CodecSpecific(Impl& _video, lsmash_summary_t* _sum, uint32_t& _lengthSize)
|
|
{
|
|
for (uint32_t ii = 0; ; ++ii)
|
|
{
|
|
lsmash_codec_specific_t* cs;
|
|
lsmash_codec_specific_t* raw = getUnstructuredCodecSpecific(_sum, ii, cs);
|
|
|
|
if (NULL == cs)
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool done = false;
|
|
|
|
if (NULL != raw)
|
|
{
|
|
const uint8_t* data = raw->data.unstructured;
|
|
const uint32_t size = raw->size;
|
|
|
|
if (bgfx::VideoCodec::H264 == _video.codec
|
|
&& LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264 == cs->type)
|
|
{
|
|
done = parseAvcC(_video, data, size, _lengthSize);
|
|
}
|
|
else if (bgfx::VideoCodec::H265 == _video.codec
|
|
&& LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_HEVC == cs->type)
|
|
{
|
|
done = parseHvcC(_video, data, size, _lengthSize);
|
|
}
|
|
else if (bgfx::VideoCodec::AV1 == _video.codec
|
|
&& LSMASH_CODEC_SPECIFIC_DATA_TYPE_UNKNOWN == cs->type
|
|
&& kBoxHeaderSize <= size
|
|
&& isFourCC(data + 4, "av1C") )
|
|
{
|
|
done = parseAv1C(_video, data, size);
|
|
}
|
|
}
|
|
|
|
if (raw != cs
|
|
&& NULL != raw)
|
|
{
|
|
lsmash_destroy_codec_specific_data(raw);
|
|
}
|
|
|
|
if (done)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void takeInBandParameterSets(Impl& _video)
|
|
{
|
|
lsmash_sample_t* s = lsmash_get_sample_from_media_timeline(_video.root, _video.trackId, 1);
|
|
|
|
if (NULL == s)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bgfx::VideoCodec::AV1 == _video.codec)
|
|
{
|
|
Av1SequenceHeader seq;
|
|
takeAv1SequenceHeader(_video, seq, s->data, s->length);
|
|
}
|
|
else
|
|
{
|
|
const bool isHevc = bgfx::VideoCodec::H265 == _video.codec;
|
|
H264Tables tables;
|
|
int64_t frameUs = 0;
|
|
stl::vector<uint8_t> rbsp;
|
|
|
|
visitLengthPrefixedNals(s->data, s->length, _video.lengthSize, [&](const uint8_t* _nal, uint32_t _nalLen)
|
|
{
|
|
if (0 == _nalLen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool keep;
|
|
|
|
if (isHevc)
|
|
{
|
|
keep = hevc::kNalHeaderSize <= _nalLen && hevc::isParameterSet(hevc::getNalUnitType(_nal) );
|
|
}
|
|
else
|
|
{
|
|
const uint8_t type = _nal[0] & kH264NalUnitTypeMask;
|
|
keep = h264::NAL_UNIT_TYPE_SPS == type || h264::NAL_UNIT_TYPE_PPS == type;
|
|
|
|
if (keep)
|
|
{
|
|
stripEmulationPrevention(rbsp, _nal, _nalLen);
|
|
parseH264ParameterSet(tables, _video, frameUs, rbsp.data(), uint32_t(rbsp.size() ) );
|
|
}
|
|
}
|
|
|
|
if (keep)
|
|
{
|
|
appendNalAsAnnexB(_video.parameterSets, _nal, _nalLen);
|
|
}
|
|
});
|
|
}
|
|
|
|
lsmash_delete_sample(s);
|
|
}
|
|
|
|
bool loadMp4(Impl& _video, const char* _path)
|
|
{
|
|
lsmash_root_t* root = lsmash_create_root();
|
|
|
|
if (NULL == root)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
lsmash_file_parameters_t fp = {};
|
|
|
|
if (lsmash_open_file(_path, 1, &fp) < 0)
|
|
{
|
|
lsmash_destroy_root(root);
|
|
return false;
|
|
}
|
|
|
|
_video.root = root;
|
|
_video.fp = fp;
|
|
_video.fpOpened = true;
|
|
|
|
lsmash_file_t* fh = lsmash_set_file(root, &fp);
|
|
|
|
if (NULL == fh
|
|
|| lsmash_read_file(fh, &fp) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
lsmash_movie_parameters_t mp;
|
|
lsmash_initialize_movie_parameters(&mp);
|
|
|
|
if (lsmash_get_movie_parameters(root, &mp) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t trackId = 0;
|
|
lsmash_media_parameters_t md;
|
|
lsmash_initialize_media_parameters(&md);
|
|
|
|
for (uint32_t ii = 0; ii < mp.number_of_tracks; ++ii)
|
|
{
|
|
const uint32_t tid = lsmash_get_track_ID(root, ii + 1);
|
|
|
|
if (0 == tid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
lsmash_initialize_media_parameters(&md);
|
|
|
|
if (lsmash_get_media_parameters(root, tid, &md) < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (md.handler_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK)
|
|
{
|
|
trackId = tid;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 == trackId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
lsmash_summary_t* sum = lsmash_get_summary(root, trackId, 1);
|
|
|
|
if (NULL == sum)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC1_VIDEO)
|
|
|| lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC2_VIDEO)
|
|
|| lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC3_VIDEO)
|
|
|| lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_AVC4_VIDEO) )
|
|
{
|
|
_video.codec = bgfx::VideoCodec::H264;
|
|
}
|
|
else if (lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_HVC1_VIDEO)
|
|
|| lsmash_check_codec_type_identical(sum->sample_type, ISOM_CODEC_TYPE_HEV1_VIDEO) )
|
|
{
|
|
_video.codec = bgfx::VideoCodec::H265;
|
|
}
|
|
else if (LSMASH_4CC('a', 'v', '0', '1') == sum->sample_type.fourcc)
|
|
{
|
|
_video.codec = bgfx::VideoCodec::AV1;
|
|
}
|
|
else
|
|
{
|
|
lsmash_cleanup_summary(sum);
|
|
return false;
|
|
}
|
|
|
|
const lsmash_video_summary_t* vsum = (const lsmash_video_summary_t*)sum;
|
|
_video.width = uint16_t(vsum->width);
|
|
_video.height = uint16_t(vsum->height);
|
|
|
|
uint32_t lengthSize = 4;
|
|
parseMp4CodecSpecific(_video, sum, lengthSize);
|
|
lsmash_cleanup_summary(sum);
|
|
|
|
_video.trackId = trackId;
|
|
_video.lengthSize = lengthSize;
|
|
|
|
if (lsmash_construct_timeline(root, trackId) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_video.parameterSets.empty() )
|
|
{
|
|
takeInBandParameterSets(_video);
|
|
}
|
|
|
|
const uint32_t timescale = bx::max<uint32_t>(md.timescale, 1);
|
|
const uint32_t numSamples = lsmash_get_sample_count_in_media_timeline(root, trackId);
|
|
|
|
_video.frames.reserve(numSamples);
|
|
|
|
for (uint32_t ii = 0; ii < numSamples; ++ii)
|
|
{
|
|
lsmash_sample_t s = {};
|
|
|
|
if (0 != lsmash_get_sample_info_from_media_timeline(root, trackId, ii + 1, &s) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
_video.frames.push_back(
|
|
{
|
|
.ptsUs = s.cts * 1000000ull / timescale,
|
|
.offset = 0,
|
|
.size = s.length,
|
|
.isKeyframe = 0 != (s.prop.ra_flags & ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC),
|
|
});
|
|
|
|
_video.totalBytes += s.length;
|
|
}
|
|
|
|
return !_video.frames.empty()
|
|
&& !_video.parameterSets.empty()
|
|
;
|
|
}
|
|
|
|
bool loadVideoFile(Impl& _video, const char* _path)
|
|
{
|
|
const bx::FilePath filePath(_path);
|
|
Probe probe;
|
|
|
|
if (!probeFile(filePath, probe) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_video.container = probe.container;
|
|
_video.codec = probe.codec;
|
|
|
|
if (Impl::Mp4 == probe.container)
|
|
{
|
|
return loadMp4(_video, _path);
|
|
}
|
|
|
|
bx::Error err;
|
|
|
|
if (!bx::open(&_video.reader, filePath, &err) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_video.readerOpened = true;
|
|
_video.fileSize = uint64_t(bx::seek(&_video.reader, 0, bx::Whence::End) );
|
|
bx::seek(&_video.reader, 0, bx::Whence::Begin);
|
|
|
|
switch (probe.container)
|
|
{
|
|
case Impl::AnnexB: return indexAnnexB(_video);
|
|
case Impl::Obu: return indexObu(_video);
|
|
case Impl::Ivf: return indexIvf(_video);
|
|
default: break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
VideoPlayer::VideoPlayer()
|
|
: m_impl(NULL)
|
|
, m_isOpen(false)
|
|
, m_hasError(false)
|
|
, m_paused(false)
|
|
, m_pendingSet(false)
|
|
, m_width(0)
|
|
, m_height(0)
|
|
, m_durationUs(0)
|
|
, m_playbackTimeUs(0)
|
|
, m_lastTickHpc(0)
|
|
, m_nextAuToSubmit(0)
|
|
, m_lastSetIdr(UINT32_MAX)
|
|
, m_playbackRate(1.0f)
|
|
, m_aMarkerUs(-1)
|
|
, m_bMarkerUs(-1)
|
|
, m_videoTexture(BGFX_INVALID_HANDLE)
|
|
, m_format(bgfx::TextureFormat::Count)
|
|
{
|
|
}
|
|
|
|
VideoPlayer::~VideoPlayer()
|
|
{
|
|
close();
|
|
}
|
|
|
|
bool VideoPlayer::isVideoFile(const bx::FilePath& _filePath)
|
|
{
|
|
Probe probe;
|
|
return probeFile(_filePath, probe);
|
|
}
|
|
|
|
bool VideoPlayer::open(const char* _filePath)
|
|
{
|
|
close();
|
|
|
|
m_impl = BX_NEW(entry::getAllocator(), Impl);
|
|
|
|
if (NULL == m_impl)
|
|
{
|
|
m_hasError = true;
|
|
return false;
|
|
}
|
|
|
|
if (!loadVideoFile(*m_impl, _filePath) )
|
|
{
|
|
bx::deleteObject(entry::getAllocator(), m_impl);
|
|
m_impl = NULL;
|
|
m_hasError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
m_width = m_impl->width;
|
|
m_height = m_impl->height;
|
|
|
|
const bgfx::Caps* caps = bgfx::getCaps();
|
|
|
|
m_format = 0 != (caps->formats[bgfx::TextureFormat::RGBA8] & BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST)
|
|
? bgfx::TextureFormat::RGBA8
|
|
: bgfx::TextureFormat::BGRA8
|
|
;
|
|
|
|
const bool codecAvailable = 0 != (caps->supported & BGFX_CAPS_VIDEO_DECODE);
|
|
const bool targetSupported = 0 != (caps->formats[m_format] & BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST);
|
|
const bool codecValid = bgfx::isVideoCodecValid(
|
|
m_impl->codec
|
|
, m_impl->chroma
|
|
, m_impl->bitDepth
|
|
, m_impl->width
|
|
, m_impl->height
|
|
, m_impl->maxDpbSlots
|
|
, m_impl->maxActiveReferences
|
|
);
|
|
|
|
if (!codecAvailable
|
|
|| !targetSupported
|
|
|| !codecValid)
|
|
{
|
|
bx::deleteObject(entry::getAllocator(), m_impl);
|
|
m_impl = NULL;
|
|
m_hasError = true;
|
|
return false;
|
|
}
|
|
|
|
const uint32_t psSize = uint32_t(m_impl->parameterSets.size() );
|
|
const uint32_t totalSize = uint32_t(sizeof(bgfx::VideoDecoderInit) ) + psSize;
|
|
|
|
const uint64_t durationUs = bx::max<uint64_t>(m_impl->frames.back().ptsUs, 1);
|
|
const uint64_t bytesPerSec = m_impl->totalBytes * 1000000ull / durationUs;
|
|
const uint32_t cachedAuBytes = uint32_t(bx::clamp<uint64_t>(bytesPerSec * kCacheWindowUs / 1000000ull, kMinCachedAuBytes, UINT32_MAX) );
|
|
|
|
const bgfx::Memory* mem = bgfx::alloc(totalSize);
|
|
|
|
bgfx::VideoDecoderInit* decInit = (bgfx::VideoDecoderInit*)mem->data;
|
|
*decInit =
|
|
{
|
|
.magic = BX_MAKEFOURCC('V', 'D', 'I', 0x0),
|
|
.codec = m_impl->codec,
|
|
.parameterSets = mem->data + sizeof(bgfx::VideoDecoderInit),
|
|
.parameterSetsSize = psSize,
|
|
.cachedAuBytes = cachedAuBytes,
|
|
.flags = 0,
|
|
};
|
|
|
|
bx::memCopy(
|
|
mem->data + sizeof(bgfx::VideoDecoderInit)
|
|
, m_impl->parameterSets.data()
|
|
, psSize
|
|
);
|
|
|
|
m_videoTexture = bgfx::createTexture2D(
|
|
m_width
|
|
, m_height
|
|
, false
|
|
, 1
|
|
, m_format
|
|
, BGFX_SAMPLER_U_CLAMP
|
|
| BGFX_SAMPLER_V_CLAMP
|
|
, mem
|
|
);
|
|
|
|
if (!bgfx::isValid(m_videoTexture) )
|
|
{
|
|
bx::deleteObject(entry::getAllocator(), m_impl);
|
|
m_impl = NULL;
|
|
m_hasError = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
m_durationUs = m_impl->frames.back().ptsUs;
|
|
m_playbackTimeUs = 0;
|
|
m_lastTickHpc = bx::getHPCounter();
|
|
m_nextAuToSubmit = 0;
|
|
m_lastSetIdr = UINT32_MAX;
|
|
m_playbackRate = 1.0f;
|
|
m_paused = false;
|
|
m_pendingSet = false;
|
|
m_aMarkerUs = -1;
|
|
m_bMarkerUs = -1;
|
|
m_isOpen = true;
|
|
m_hasError = false;
|
|
|
|
submitAccessUnitsUpTo(kLookaheadUs);
|
|
|
|
return true;
|
|
}
|
|
|
|
void VideoPlayer::close()
|
|
{
|
|
if (bgfx::isValid(m_videoTexture) )
|
|
{
|
|
bgfx::destroy(m_videoTexture);
|
|
m_videoTexture = BGFX_INVALID_HANDLE;
|
|
}
|
|
|
|
if (NULL != m_impl)
|
|
{
|
|
bx::deleteObject(entry::getAllocator(), m_impl);
|
|
m_impl = NULL;
|
|
}
|
|
|
|
m_isOpen = false;
|
|
m_hasError = false;
|
|
m_paused = false;
|
|
m_pendingSet = false;
|
|
m_width = 0;
|
|
m_height = 0;
|
|
m_durationUs = 0;
|
|
m_playbackTimeUs = 0;
|
|
m_nextAuToSubmit = 0;
|
|
m_lastSetIdr = UINT32_MAX;
|
|
m_playbackRate = 1.0f;
|
|
m_aMarkerUs = -1;
|
|
m_bMarkerUs = -1;
|
|
}
|
|
|
|
bgfx::VideoCodec::Enum VideoPlayer::codec() const
|
|
{
|
|
return NULL != m_impl ? m_impl->codec : bgfx::VideoCodec::Count;
|
|
}
|
|
|
|
uint32_t VideoPlayer::findIdrAtOrBefore(uint64_t _ptsUs) const
|
|
{
|
|
if (NULL == m_impl
|
|
|| m_impl->frames.empty() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t lastIdr = 0;
|
|
|
|
for (uint32_t ii = 0; ii < uint32_t(m_impl->frames.size() ); ++ii)
|
|
{
|
|
if (m_impl->frames[ii].ptsUs > _ptsUs)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (m_impl->frames[ii].isKeyframe)
|
|
{
|
|
lastIdr = ii;
|
|
}
|
|
}
|
|
|
|
return lastIdr;
|
|
}
|
|
|
|
uint32_t VideoPlayer::findIdrAtOrAfter(uint64_t _ptsUs) const
|
|
{
|
|
if (NULL == m_impl
|
|
|| m_impl->frames.empty() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t numFrames = uint32_t(m_impl->frames.size() );
|
|
|
|
for (uint32_t ii = 0; ii < numFrames; ++ii)
|
|
{
|
|
if (m_impl->frames[ii].ptsUs >= _ptsUs
|
|
&& m_impl->frames[ii].isKeyframe)
|
|
{
|
|
return ii;
|
|
}
|
|
}
|
|
|
|
return numFrames;
|
|
}
|
|
|
|
uint32_t VideoPlayer::findFrameAtOrBefore(uint64_t _ptsUs) const
|
|
{
|
|
if (NULL == m_impl
|
|
|| m_impl->frames.empty() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t numFrames = uint32_t(m_impl->frames.size() );
|
|
uint32_t result = 0;
|
|
|
|
for (uint32_t ii = 0; ii < numFrames; ++ii)
|
|
{
|
|
if (m_impl->frames[ii].ptsUs > _ptsUs)
|
|
{
|
|
break;
|
|
}
|
|
|
|
result = ii;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void VideoPlayer::submitDecodeFrame(uint32_t _index, bool _setPosition)
|
|
{
|
|
const Impl::Frame& f = m_impl->frames[_index];
|
|
|
|
stl::vector<uint8_t>& scratch = m_impl->scratch;
|
|
scratch.clear();
|
|
|
|
if (Impl::Mp4 == m_impl->container)
|
|
{
|
|
lsmash_sample_t* s = lsmash_get_sample_from_media_timeline(
|
|
m_impl->root
|
|
, m_impl->trackId
|
|
, _index + 1
|
|
);
|
|
|
|
if (NULL == s)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bgfx::VideoCodec::AV1 == m_impl->codec)
|
|
{
|
|
scratch.insert(scratch.end(), s->data, s->data + s->length);
|
|
}
|
|
else
|
|
{
|
|
avccToAnnexB(scratch, s->data, s->length, m_impl->lengthSize);
|
|
}
|
|
|
|
lsmash_delete_sample(s);
|
|
}
|
|
else
|
|
{
|
|
scratch.resize(f.size);
|
|
|
|
bx::Error err;
|
|
bx::seek(&m_impl->reader, int64_t(f.offset), bx::Whence::Begin);
|
|
const int32_t read = bx::read(&m_impl->reader, scratch.data(), int32_t(f.size), &err);
|
|
|
|
if (!err.isOk()
|
|
|| uint32_t(read) != f.size)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct Payload
|
|
{
|
|
bgfx::VideoDecoderFrame frame;
|
|
bgfx::VideoDecoderAu au;
|
|
};
|
|
|
|
const uint32_t bitstreamSize = uint32_t(scratch.size() );
|
|
const uint32_t totalSize = uint32_t(sizeof(Payload) ) + bitstreamSize;
|
|
|
|
const bgfx::Memory* mem = bgfx::alloc(totalSize);
|
|
Payload* dstPayload = (Payload*)mem->data;
|
|
uint8_t* dstBitstream = (uint8_t*)(dstPayload + 1);
|
|
|
|
bx::memCopy(dstBitstream, scratch.data(), bitstreamSize);
|
|
|
|
dstPayload->au =
|
|
{
|
|
.size = bitstreamSize,
|
|
.ptsUs = int64_t(f.ptsUs),
|
|
};
|
|
|
|
dstPayload->frame =
|
|
{
|
|
.magic = BX_MAKEFOURCC('V', 'D', 'F', 0x0),
|
|
.bitstream = dstBitstream,
|
|
.aus = &dstPayload->au,
|
|
.numAus = 1,
|
|
.presentationTimeUs = 0,
|
|
.flags = uint8_t(_setPosition
|
|
? BGFX_VIDEO_DECODE_FRAME_SET
|
|
: BGFX_VIDEO_DECODE_FRAME_NONE
|
|
),
|
|
};
|
|
|
|
bgfx::updateTexture2D(
|
|
m_videoTexture
|
|
, 0
|
|
, 0
|
|
, 0, 0
|
|
, m_width
|
|
, m_height
|
|
, mem
|
|
);
|
|
}
|
|
|
|
void VideoPlayer::submitAccessUnitsUpTo(int64_t _untilPtsUs)
|
|
{
|
|
if (NULL == m_impl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t submitted = 0;
|
|
|
|
while (m_nextAuToSubmit < uint32_t(m_impl->frames.size() )
|
|
&& int64_t(m_impl->frames[m_nextAuToSubmit].ptsUs) <= _untilPtsUs
|
|
&& submitted < kMaxSubmitPerCall)
|
|
{
|
|
submitDecodeFrame(m_nextAuToSubmit, m_pendingSet);
|
|
m_pendingSet = false;
|
|
++m_nextAuToSubmit;
|
|
++submitted;
|
|
}
|
|
}
|
|
|
|
void VideoPlayer::submitPresentationTick(int64_t _presentationTimeUs)
|
|
{
|
|
const bgfx::VideoDecoderFrame desc =
|
|
{
|
|
.magic = BX_MAKEFOURCC('V', 'D', 'F', 0x0),
|
|
.bitstream = NULL,
|
|
.aus = NULL,
|
|
.numAus = 0,
|
|
.presentationTimeUs = _presentationTimeUs,
|
|
.flags = BGFX_VIDEO_DECODE_FRAME_NONE,
|
|
};
|
|
|
|
bgfx::updateTexture2D(
|
|
m_videoTexture
|
|
, 0
|
|
, 0
|
|
, 0, 0
|
|
, m_width
|
|
, m_height
|
|
, bgfx::copy(&desc, sizeof(desc) )
|
|
);
|
|
}
|
|
|
|
void VideoPlayer::tick()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int64_t nowHpc = bx::getHPCounter();
|
|
const int64_t deltaHpc = nowHpc - m_lastTickHpc;
|
|
m_lastTickHpc = nowHpc;
|
|
|
|
if (!m_paused)
|
|
{
|
|
m_lastSetIdr = UINT32_MAX;
|
|
|
|
const uint64_t deltaUs = uint64_t(double(deltaHpc) * 1000000.0 * double(m_playbackRate) / double(bx::getHPFrequency() ) );
|
|
m_playbackTimeUs += deltaUs;
|
|
|
|
if (m_aMarkerUs >= 0
|
|
&& m_bMarkerUs > m_aMarkerUs
|
|
&& int64_t(m_playbackTimeUs) >= m_bMarkerUs)
|
|
{
|
|
seekTo(uint64_t(m_aMarkerUs) );
|
|
}
|
|
else if (m_playbackTimeUs > m_durationUs)
|
|
{
|
|
seekTo(m_aMarkerUs >= 0 ? uint64_t(m_aMarkerUs) : 0);
|
|
}
|
|
}
|
|
|
|
submitPresentationTick(int64_t(m_playbackTimeUs) );
|
|
submitAccessUnitsUpTo(int64_t(m_playbackTimeUs) + kLookaheadUs);
|
|
}
|
|
|
|
void VideoPlayer::seekTo(uint64_t _targetUs)
|
|
{
|
|
if (!m_isOpen
|
|
|| NULL == m_impl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const uint64_t target = bx::min(_targetUs, m_durationUs);
|
|
|
|
const uint32_t idr = findIdrAtOrBefore(target);
|
|
const uint32_t targetFrame = findFrameAtOrBefore(target);
|
|
const bool needReprime =
|
|
targetFrame < m_nextAuToSubmit
|
|
|| idr > m_nextAuToSubmit
|
|
;
|
|
|
|
if (needReprime
|
|
&& idr != m_lastSetIdr)
|
|
{
|
|
m_nextAuToSubmit = idr;
|
|
m_pendingSet = true;
|
|
m_lastSetIdr = idr;
|
|
}
|
|
|
|
m_playbackTimeUs = m_paused
|
|
? target
|
|
: m_impl->frames[idr].ptsUs
|
|
;
|
|
m_lastTickHpc = bx::getHPCounter();
|
|
}
|
|
|
|
void VideoPlayer::seekRelative(int64_t _deltaUs)
|
|
{
|
|
if (!m_isOpen
|
|
|| NULL == m_impl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_lastSetIdr = UINT32_MAX;
|
|
|
|
int64_t target = int64_t(m_playbackTimeUs) + _deltaUs;
|
|
|
|
int64_t lo = 0;
|
|
int64_t hi = int64_t(m_durationUs);
|
|
|
|
if (m_aMarkerUs >= 0
|
|
&& m_bMarkerUs > m_aMarkerUs)
|
|
{
|
|
lo = m_aMarkerUs;
|
|
hi = m_bMarkerUs;
|
|
}
|
|
|
|
if (_deltaUs > 0
|
|
&& target > hi)
|
|
{
|
|
seekTo(uint64_t(lo) );
|
|
return;
|
|
}
|
|
|
|
target = bx::clamp(target, lo, hi);
|
|
|
|
if (_deltaUs > 0)
|
|
{
|
|
const uint32_t idrBefore = findIdrAtOrBefore(uint64_t(target) );
|
|
const uint64_t idrBeforePts = m_impl->frames[idrBefore].ptsUs;
|
|
|
|
if (idrBeforePts <= m_playbackTimeUs)
|
|
{
|
|
const uint32_t numFrames = uint32_t(m_impl->frames.size() );
|
|
const uint32_t idrAfter = findIdrAtOrAfter(m_playbackTimeUs + 1);
|
|
|
|
if (idrAfter < numFrames)
|
|
{
|
|
m_nextAuToSubmit = idrAfter;
|
|
m_pendingSet = true;
|
|
m_lastSetIdr = idrAfter;
|
|
m_playbackTimeUs = m_impl->frames[idrAfter].ptsUs;
|
|
m_lastTickHpc = bx::getHPCounter();
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
seekTo(uint64_t(target) );
|
|
}
|
|
|
|
void VideoPlayer::restart()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_lastSetIdr = UINT32_MAX;
|
|
seekTo(m_aMarkerUs >= 0 ? uint64_t(m_aMarkerUs) : 0);
|
|
}
|
|
|
|
void VideoPlayer::togglePause()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_paused = !m_paused;
|
|
|
|
m_lastTickHpc = bx::getHPCounter();
|
|
}
|
|
|
|
void VideoPlayer::rateUp()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_playbackRate = bx::min(m_playbackRate * kRateStepFactor, kRateMax);
|
|
}
|
|
|
|
void VideoPlayer::rateDown()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_playbackRate = bx::max(m_playbackRate / kRateStepFactor, kRateMin);
|
|
}
|
|
|
|
void VideoPlayer::cycleAbMarker()
|
|
{
|
|
if (!m_isOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_aMarkerUs < 0)
|
|
{
|
|
m_aMarkerUs = int64_t(m_playbackTimeUs);
|
|
m_bMarkerUs = -1;
|
|
}
|
|
else if (m_bMarkerUs < 0)
|
|
{
|
|
int64_t b = int64_t(m_playbackTimeUs);
|
|
|
|
if (b < m_aMarkerUs)
|
|
{
|
|
m_bMarkerUs = m_aMarkerUs;
|
|
m_aMarkerUs = b;
|
|
}
|
|
else
|
|
{
|
|
m_bMarkerUs = b;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_aMarkerUs = -1;
|
|
m_bMarkerUs = -1;
|
|
}
|
|
}
|