parsing AVC config

This commit is contained in:
Martchus 2016-02-17 20:19:05 +01:00
parent b48dcf5b3d
commit 488fce3ab8
20 changed files with 800 additions and 145 deletions

View File

@ -18,8 +18,9 @@ set(HEADER_FILES
abstracttrack.h
adts/adtsframe.h
adts/adtsstream.h
# avc/avcconfiguration.h
# avc/avcinfo.h
aspectratio.h
avc/avcconfiguration.h
avc/avcinfo.h
avi/bitmapinfoheader.h
backuphelper.h
basicfileinfo.h
@ -89,8 +90,9 @@ set(SRC_FILES
abstracttrack.cpp
adts/adtsframe.cpp
adts/adtsstream.cpp
# avc/avcconfiguration.cpp
# avc/avcinfo.cpp
aspectratio.cpp
avc/avcconfiguration.cpp
avc/avcinfo.cpp
avi/bitmapinfoheader.cpp
backuphelper.cpp
basicfileinfo.cpp
@ -134,6 +136,13 @@ set(SRC_FILES
mediafileinfo.cpp
mediaformat.cpp
)
set(TEST_HEADER_FILES
)
set(TEST_SRC_FILES
tests/cppunit.cpp
tests/matroska.cpp
)
# meta data
set(META_PROJECT_NAME tagparser)
@ -189,6 +198,18 @@ add_definitions(
-D_GLIBCXX_USE_CXX11_ABI=0
)
# add check target
if(NOT TARGET check)
set(CMAKE_CTEST_COMMAND ctest -V)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
enable_testing()
endif()
add_executable(${META_PROJECT_NAME}_tests EXCLUDE_FROM_ALL ${TEST_HEADER_FILES} ${TEST_SRC_FILES})
target_link_libraries(${META_PROJECT_NAME}_tests ${META_PROJECT_NAME} c++utilities cppunit)
set_target_properties(${META_PROJECT_NAME}_tests PROPERTIES CXX_STANDARD 11)
add_test(NAME ${META_PROJECT_NAME}_cppunit COMMAND ${META_PROJECT_NAME}_tests -p "${CMAKE_CURRENT_SOURCE_DIR}/testfiles")
add_dependencies(check ${META_PROJECT_NAME}_tests)
# executable and linking
add_library(${META_PROJECT_NAME} SHARED ${HEADER_FILES} ${SRC_FILES} ${RES_FILES} ${WINDOWS_ICON_PATH})
target_link_libraries(${META_PROJECT_NAME} c++utilities z)

View File

@ -15,6 +15,11 @@
using namespace std;
using namespace IoUtilities;
/*!
* \remarks Nothing of this code has been tested yet. These classes are
* not used by the rest of the library (currently).
*/
namespace Media {
/*!

View File

@ -55,6 +55,7 @@ AbstractTrack::AbstractTrack(istream &inputStream, ostream &outputStream, uint64
m_quality(0),
m_depth(0),
m_fps(0),
m_chromaFormat(nullptr),
m_interlaced(false),
m_timeScale(0),
m_enabled(true),

View File

@ -4,6 +4,7 @@
#include "./statusprovider.h"
#include "./size.h"
#include "./margin.h"
#include "./aspectratio.h"
#include "./mediaformat.h"
#include <c++utilities/conversion/types.h>
@ -21,6 +22,7 @@ enum class MediaType;
enum class GeneralMediaFormat;
class MpegAudioFrameStream;
class WaveAudioStream;
class Mp4Track;
/*!
* \brief Specifies the track type.
@ -40,6 +42,7 @@ class LIB_EXPORT AbstractTrack : public StatusProvider
{
friend class MpegAudioFrameStream;
friend class WaveAudioStream;
friend class Mp4Track;
public:
virtual ~AbstractTrack();
@ -85,6 +88,8 @@ public:
const std::string &compressorName() const;
uint16 depth() const;
uint32 fps() const;
const char *chromaFormat() const;
const AspectRatio &pixelAspectRatio() const;
bool isInterlaced() const;
uint32 timeScale() const;
bool isEnabled() const;
@ -141,6 +146,8 @@ protected:
std::string m_compressorName;
uint16 m_depth;
uint32 m_fps;
const char *m_chromaFormat;
AspectRatio m_pixelAspectRatio;
bool m_interlaced;
uint32 m_timeScale;
bool m_enabled;
@ -241,7 +248,7 @@ inline MediaFormat AbstractTrack::format() const
}
/*!
* \brief Returns the version of the track if known; otherwise returns 0.
* \brief Returns the version/level of the track if known; otherwise returns 0.
*/
inline double AbstractTrack::version() const
{
@ -489,6 +496,24 @@ inline uint32 AbstractTrack::fps() const
return m_fps;
}
/*!
* \brief Returns the chroma subsampling format if known; otherwise returns nullptr.
*
* This value only makes sense for video tracks.
*/
inline const char *AbstractTrack::chromaFormat() const
{
return m_chromaFormat;
}
/*!
* \brief Returns the pixel aspect ratio (PAR).
*/
inline const AspectRatio &AbstractTrack::pixelAspectRatio() const
{
return m_pixelAspectRatio;
}
/*!
* \brief Returns true if the video is denoted as interlaced; otherwise returns false.
*

32
aspectratio.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "./aspectratio.h"
using namespace std;
namespace Media {
/*!
* \struct Media::AspectRatio
* \brief The AspectRatio struct defines an aspect ratio.
*/
/*!
* \brief Constructs a PAR form the specified AVC aspectRatioType.
*/
AspectRatio::AspectRatio(byte aspectRatioType)
{
static const AspectRatio predefinedPars[] = {
AspectRatio(), AspectRatio(1, 1), AspectRatio(12, 11), AspectRatio(10, 11),
AspectRatio(16, 11), AspectRatio(40, 33), AspectRatio(24, 11), AspectRatio(20, 11),
AspectRatio(32, 11), AspectRatio(80, 33), AspectRatio(18, 11), AspectRatio(15, 11),
AspectRatio(64, 33), AspectRatio(160, 99), AspectRatio(4, 3), AspectRatio(3, 2),
AspectRatio(2, 1)
};
if(aspectRatioType < sizeof(predefinedPars)) {
*this = predefinedPars[aspectRatioType];
} else {
numerator = denominator = 0;
}
type = aspectRatioType;
}
}

57
aspectratio.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef MEDIA_ASPECTRATIO_H
#define MEDIA_ASPECTRATIO_H
#include <c++utilities/application/global.h>
#include <c++utilities/conversion/types.h>
namespace Media {
struct LIB_EXPORT AspectRatio {
AspectRatio();
AspectRatio(byte aspectRatioType);
AspectRatio(uint16 numerator, uint16 denominator);
bool isValid() const;
bool isExtended() const;
byte type;
uint16 numerator;
uint16 denominator;
};
/*!
* \brief Constructs an invalid aspect ratio.
*/
inline AspectRatio::AspectRatio() :
type(0),
numerator(0),
denominator(0)
{}
/*!
* \brief Constructs a aspect ratio with the specified \a numerator and \a denominator.
*/
inline AspectRatio::AspectRatio(uint16 numerator, uint16 denominator) :
type(0xFF),
numerator(numerator),
denominator(denominator)
{}
/*!
* \brief Returns an indication whether the aspect ratio is present and valid.
*/
inline bool AspectRatio::isValid() const
{
return !type || !numerator || !denominator;
}
/*!
* \brief Returns whether numerator and denominator must be read from extended SAR header.
*/
inline bool AspectRatio::isExtended() const
{
return type == 0xFF;
}
}
#endif // MEDIA_ASPECTRATIO_H

View File

@ -1,5 +1,75 @@
#include "./avcconfiguration.h"
#include "../exceptions.h"
#include "../mediaformat.h"
#include <c++utilities/io/binaryreader.h>
using namespace std;
using namespace IoUtilities;
namespace Media {
void AvcConfiguration::parse(BinaryReader &reader, uint64 maxSize)
{
if(maxSize < 7) {
throw TruncatedDataException();
}
maxSize -= 7;
reader.stream()->seekg(1, ios_base::cur); // always 1
profileIndication = reader.readByte();
profileCompat = reader.readByte();
levelIndication = reader.readByte();
naluSizeLength = (reader.readByte() & 0x03) + 1;
// read SPS info entries
byte entryCount = reader.readByte() & 0x0f;
spsInfos.reserve(entryCount);
for(; entryCount; --entryCount) {
if(maxSize < 2) {
throw TruncatedDataException();
}
spsInfos.emplace_back();
try {
spsInfos.back().parse(reader, maxSize);
} catch(const TruncatedDataException &) {
// TODO: log parsing error
if(spsInfos.back().size > maxSize - 2) {
throw;
}
spsInfos.pop_back();
} catch(const Failure &) {
spsInfos.pop_back();
// TODO: log parsing error
}
maxSize -= spsInfos.back().size;
}
// read PPS info entries
entryCount = reader.readByte();
ppsInfos.reserve(entryCount);
for(; entryCount; --entryCount) {
if(maxSize < 2) {
throw TruncatedDataException();
}
ppsInfos.emplace_back();
try {
ppsInfos.back().parse(reader, maxSize);
} catch(const TruncatedDataException &) {
// TODO: log parsing error
if(ppsInfos.back().size > maxSize - 2) {
throw;
}
ppsInfos.pop_back();
} catch(const Failure &) {
ppsInfos.pop_back();
// TODO: log parsing error
}
maxSize -= ppsInfos.back().size;
}
// ignore remaining data
}
}

View File

@ -7,21 +7,25 @@
namespace Media {
class MediaFormat;
struct LIB_EXPORT AvcConfiguration
{
AvcConfiguration();
byte profileIdc;
byte profileIndication;
byte profileCompat;
byte levelIdc;
byte levelIndication;
byte naluSizeLength;
std::vector<SpsInfo> spsInfos;
std::vector<PpsInfo> ppsInfos;
void parse(IoUtilities::BinaryReader &reader, uint64 maxSize);
};
inline AvcConfiguration::AvcConfiguration() :
profileIdc(0),
profileIndication(0),
profileCompat(0),
levelIdc(0),
levelIndication(0),
naluSizeLength(0)
{}

View File

@ -1,7 +1,16 @@
#include "./avcinfo.h"
#include "../exceptions.h"
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/bitreader.h>
#include <c++utilities/misc/memory.h>
#include <unordered_map>
using namespace std;
using namespace IoUtilities;
namespace Media {
/*!
@ -10,25 +19,255 @@ namespace Media {
*/
/*!
* \brief SpsInfo::parse
* \param stream
* \brief Parses the SPS info.
*/
void SpsInfo::parse(std::istream &stream)
void SpsInfo::parse(BinaryReader &reader, uint32 maxSize)
{
static auto highLevelProfileIds = std::unordered_map<unsigned int, bool> {
{ 44, true }, { 83, true }, { 86, true }, { 100, true }, { 110, true },
{ 118, true }, { 122, true }, { 128, true }, { 244, true }
};
int const addSpace = 100;
// read (and check) size
if(maxSize < 2) {
throw TruncatedDataException();
}
maxSize -= 2;
if((size = reader.readUInt16BE()) > maxSize) {
throw TruncatedDataException();
}
parNum = 1;
parDen = 1;
arFound = false;
// buffer data for reading with BitReader
auto buffer = make_unique<char[]>(size);
reader.read(buffer.get(), size);
BitReader bitReader(buffer.get(), size);
uint32 unitsInTick = 0;
uint32 timeScale = 0;
try {
// read general values
bitReader.skipBits(3);
if(bitReader.readBits<byte>(5) != 7) {
throw InvalidDataException();
}
profileIndication = bitReader.readBits<byte>(8);
profileConstraints = bitReader.readBits<byte>(8);
levelIndication = bitReader.readBits<byte>(8);
id = bitReader.readUnsignedExpGolombCodedBits<ugolomb>();
// read chroma profile specific values
switch(profileIndication) {
case 44: case 83: case 86: case 100: case 110:
case 118: case 122: case 128: case 244:
// high-level profile
if((chromaFormatIndication = bitReader.readUnsignedExpGolombCodedBits<ugolomb>()) == 3) {
bitReader.skipBits(1); // separate color plane flag
}
bitReader.readUnsignedExpGolombCodedBits<byte>(); // bit depth luma minus8
bitReader.readUnsignedExpGolombCodedBits<byte>(); // bit depth chroma minus8
bitReader.skipBits(1); // qpprime y zero transform bypass flag
if(bitReader.readBit()) { // sequence scaling matrix present flag
for(byte i = 0; i < 8; ++i) {
// TODO: store values
if(bitReader.readBit()) { // sequence scaling list present
if(i < 6) {
bitReader.skipBits(16); // scalingList4x4[i]
} else {
bitReader.skipBits(64); // scalingList8x8[i - 6]
}
}
}
}
break;
default:
chromaFormatIndication = 1; // assume YUV 4:2:0
}
// read misc values
log2MaxFrameNum = bitReader.readUnsignedExpGolombCodedBits<ugolomb>() + 4;
switch(pictureOrderCountType = bitReader.readUnsignedExpGolombCodedBits<ugolomb>()) {
case 0:
log2MaxPictureOrderCountLsb = bitReader.readUnsignedExpGolombCodedBits<ugolomb>() + 4;
break;
case 1:
deltaPicOrderAlwaysZeroFlag = bitReader.readBit();
offsetForNonRefPic = bitReader.readSignedExpGolombCodedBits<sgolomb>();
offsetForTopToBottomField = bitReader.readSignedExpGolombCodedBits<sgolomb>();
numRefFramesInPicOrderCntCycle = bitReader.readUnsignedExpGolombCodedBits<ugolomb>();
for(byte i = 0; i < numRefFramesInPicOrderCntCycle; ++i) {
bitReader.readUnsignedExpGolombCodedBits<ugolomb>(); // offset for ref frames
}
break;
case 2:
break;
default:
throw InvalidDataException();
}
bitReader.readUnsignedExpGolombCodedBits<ugolomb>(); // ref frames num
bitReader.skipBits(1); // gaps in frame num value allowed flag
// read picture size related values
Size mbSize;
mbSize.setWidth(bitReader.readUnsignedExpGolombCodedBits<uint32>() + 1);
mbSize.setHeight(bitReader.readUnsignedExpGolombCodedBits<uint32>() + 1);
if(!(frameMbsOnly = bitReader.readBit())) { // frame mbs only flag
bitReader.readBit(); // mb adaptive frame field flag
}
bitReader.skipBits(1); // distinct 8x8 inference flag
// read cropping values
if(bitReader.readBit()) { // frame cropping flag
cropping.setLeft(bitReader.readUnsignedExpGolombCodedBits<uint32>());
cropping.setRight(bitReader.readUnsignedExpGolombCodedBits<uint32>());
cropping.setTop(bitReader.readUnsignedExpGolombCodedBits<uint32>());
cropping.setBottom(bitReader.readUnsignedExpGolombCodedBits<uint32>());
}
// read VUI (video usability information)
if((vuiPresent = bitReader.readBit())) {
if((bitReader.readBit())) { // PAR present flag
pixelAspectRatio = AspectRatio(bitReader.readBits<byte>(8));
if(pixelAspectRatio.isExtended()) {
// read extended SAR
pixelAspectRatio.numerator = bitReader.readBits<uint16>(16);
pixelAspectRatio.denominator = bitReader.readBits<uint16>(16);
}
}
// read/skip misc values
if(bitReader.readBit()) { // overscan info present
bitReader.skipBits(1); // overscan appropriate
}
if(bitReader.readBit()) { // video signal type present
bitReader.skipBits(4); // video format and video full range
if(bitReader.readBit()) { // color description present
bitReader.skipBits(24); // color primaries, transfer characteristics, matrix coefficients
}
}
if(bitReader.readBit()) { // chroma loc info present
bitReader.readUnsignedExpGolombCodedBits<byte>(); // chroma sample loc type top field
bitReader.readUnsignedExpGolombCodedBits<byte>(); // chroma sample loc type bottom field
}
// read timing info
if((timingInfo.isPresent = bitReader.readBit())) {
timingInfo.unitsInTick = bitReader.readBits<uint32>(32);
timingInfo.timeScale = bitReader.readBits<uint32>(32);
timingInfo.fixedFrameRate = bitReader.readBit();
}
// skip hrd parameters
hrdParametersPresent = 0;
if(bitReader.readBit()) { // nal hrd parameters present
nalHrdParameters.parse(bitReader);
hrdParametersPresent = 1;
}
if(bitReader.readBit()) { // vcl hrd parameters present
vclHrdParameters.parse(bitReader);
hrdParametersPresent = 1;
}
if(hrdParametersPresent) {
bitReader.skipBits(1); // low delay hrd flag
}
pictureStructPresent = bitReader.readBit();
if(bitReader.readBit()) { // bitstream restriction flag
bitReader.skipBits(1); // motion vectors over pic boundries flag
bitReader.readUnsignedExpGolombCodedBits<byte>(); // max bytes per pic denom
bitReader.readUnsignedExpGolombCodedBits<byte>(); // max bytes per mb denom
bitReader.readUnsignedExpGolombCodedBits<byte>(); // log2 max mv length horizontal
bitReader.readUnsignedExpGolombCodedBits<byte>(); // log2 max mv length vertical
bitReader.readUnsignedExpGolombCodedBits<byte>(); // reorder frames num
bitReader.readUnsignedExpGolombCodedBits<byte>(); // max decoder frame buffering
}
}
// calculate actual picture size
if(!cropping.isNull()) {
// determine cropping scale
ugolomb croppingScaleX, croppingScaleY;
switch(chromaFormatIndication) {
case 1: // 4:2:0
croppingScaleX = 2;
croppingScaleY = frameMbsOnly ? 2 : 4;
break;
case 2: // 4:2:2
croppingScaleX = 2;
croppingScaleY = 2 - frameMbsOnly;
break;
default: // case 0: monochrome, case 3: 4:4:4
croppingScaleX = 1;
croppingScaleY = 2 - frameMbsOnly;
break;
}
pictureSize.setWidth(mbSize.width() * 16 - croppingScaleX * (cropping.left() + cropping.right()));
pictureSize.setHeight((2 - frameMbsOnly) * mbSize.height() * 16 - croppingScaleY * (cropping.top() + cropping.bottom()));
} else {
pictureSize.setWidth(mbSize.width() * 16);
pictureSize.setHeight((2 - frameMbsOnly) * mbSize.height() * 16);
}
} catch(const ios_base::failure &) {
throw TruncatedDataException();
}
}
/*!
* \struct Media::PpsInfo
* \brief The PpsInfo struct holds the picture parameter set.
*/
/*!
* \brief Parses the PPS info.
*/
void PpsInfo::parse(BinaryReader &reader, uint32 maxSize)
{
// read (and check) size
if(maxSize < 2) {
throw TruncatedDataException();
}
maxSize -= 2;
if((size = reader.readUInt16BE()) > maxSize) {
throw TruncatedDataException();
}
// buffer data for reading with BitReader
auto buffer = make_unique<char[]>(size);
reader.read(buffer.get(), size);
BitReader bitReader(buffer.get(), size);
try {
// read general values
bitReader.skipBits(1); // zero bit
if(bitReader.readBits<byte>(5) != 8) { // nal unit type
throw InvalidDataException();
}
id = bitReader.readUnsignedExpGolombCodedBits<ugolomb>();
spsId = bitReader.readUnsignedExpGolombCodedBits<ugolomb>();
bitReader.skipBits(1); // entropy coding mode flag
picOrderPresent = bitReader.readBit();
} catch(const ios_base::failure &) {
throw TruncatedDataException();
}
}
/*!
* \struct Media::HrdParameters
* \brief The HrdParameters struct holds "Hypothetical Reference Decoder" parameters.
* \remarks This is "a model for thinking about the decoding process".
*/
/*!
* \brief Parses HRD parameters.
*/
void HrdParameters::parse(IoUtilities::BitReader &reader)
{
cpbCount = reader.readUnsignedExpGolombCodedBits<ugolomb>() + 1;
bitRateScale = reader.readBits<byte>(4);
cpbSizeScale = reader.readBits<byte>(4);
for(ugolomb i = 0; i < cpbCount; ++i) {
// just skip those values
reader.readUnsignedExpGolombCodedBits<byte>(); // bit rate value minus 1
reader.readUnsignedExpGolombCodedBits<byte>(); // cpb size value minus 1
reader.skipBits(1); // cbr flag
}
initialCpbRemovalDelayLength = reader.readBits<byte>(5) + 1;
cpbRemovalDelayLength = reader.readBits<byte>(5) + 1;
cpbOutputDelayLength = reader.readBits<byte>(5) + 1;
timeOffsetLength = reader.readBits<byte>(5);
}

View File

@ -3,23 +3,39 @@
#include "../margin.h"
#include "../size.h"
#include "../aspectratio.h"
namespace IoUtilities {
class BinaryReader;
class BitReader;
}
namespace Media {
/*!
* \brief Type used to store unsigned integer values using golomb coding.
*/
typedef uint32 ugolomb;
/*!
* \brief Type used to store signed integer values using golomb coding.
*/
typedef int32 sgolomb;
struct LIB_EXPORT TimingInfo {
TimingInfo();
uint32 unitsInTick;
uint32 timeScale;
bool isPresent;
bool fixedFrameRate;
byte isPresent;
byte fixedFrameRate;
int64 defaultDuration() const;
};
inline TimingInfo::TimingInfo() :
unitsInTick(0),
timeScale(0),
isPresent(false),
fixedFrameRate(false)
isPresent(0),
fixedFrameRate(0)
{}
inline int64 TimingInfo::defaultDuration() const
@ -27,61 +43,93 @@ inline int64 TimingInfo::defaultDuration() const
return 1000000000ll * unitsInTick / timeScale;
}
struct LIB_EXPORT HrdParameters {
HrdParameters();
ugolomb cpbCount;
byte bitRateScale;
byte cpbSizeScale;
byte initialCpbRemovalDelayLength;
byte cpbRemovalDelayLength;
byte cpbOutputDelayLength;
byte timeOffsetLength;
void parse(IoUtilities::BitReader &reader);
};
inline HrdParameters::HrdParameters() :
cpbCount(0),
bitRateScale(0),
cpbSizeScale(0),
initialCpbRemovalDelayLength(0),
cpbRemovalDelayLength(0),
cpbOutputDelayLength(0),
timeOffsetLength(0)
{}
struct LIB_EXPORT SpsInfo {
SpsInfo();
uint32 id;
uint32 profileIndication;
uint32 profileCompat;
uint32 levelIdc;
uint32 chromatFormatIndication;
uint32 log2MaxFrameNum;
uint32 offsetForNonRefPic;
uint32 offsetForTopToBottomField;
uint32 numRefFramesInPicOrderCntCycle;
bool deltaPicOrderAlwaysZeroFlag;
bool frameMbsOnly;
bool vuiPresent;
bool arFound;
uint32 parNum;
uint32 parDen;
ugolomb id;
byte profileIndication;
byte profileConstraints;
byte levelIndication;
ugolomb chromaFormatIndication;
ugolomb pictureOrderCountType;
ugolomb log2MaxFrameNum;
ugolomb log2MaxPictureOrderCountLsb;
sgolomb offsetForNonRefPic;
sgolomb offsetForTopToBottomField;
ugolomb numRefFramesInPicOrderCntCycle;
byte deltaPicOrderAlwaysZeroFlag;
byte frameMbsOnly;
byte vuiPresent;
AspectRatio pixelAspectRatio;
TimingInfo timingInfo;
Margin cropping;
Size size;
uint32 checksum;
Size pictureSize;
byte hrdParametersPresent;
HrdParameters nalHrdParameters;
HrdParameters vclHrdParameters;
byte pictureStructPresent;
uint16 size;
void parse(std::istream &stream);
void parse(IoUtilities::BinaryReader &reader, uint32 maxSize);
};
inline SpsInfo::SpsInfo() :
id(0),
profileIndication(0),
profileCompat(0),
levelIdc(0),
chromatFormatIndication(0),
profileConstraints(0),
levelIndication(0),
chromaFormatIndication(0),
pictureOrderCountType(0),
log2MaxFrameNum(0),
log2MaxPictureOrderCountLsb(0),
offsetForNonRefPic(0),
offsetForTopToBottomField(0),
numRefFramesInPicOrderCntCycle(0),
deltaPicOrderAlwaysZeroFlag(false),
frameMbsOnly(false),
vuiPresent(false),
arFound(false),
parNum(0),
parDen(0),
checksum(0)
deltaPicOrderAlwaysZeroFlag(0),
frameMbsOnly(0),
vuiPresent(0),
hrdParametersPresent(0),
pictureStructPresent(0),
size(0)
{}
struct PpsInfo {
PpsInfo();
uint32 id;
uint32 spsId;
bool picOrderPresent;
ugolomb id;
ugolomb spsId;
byte picOrderPresent;
uint16 size;
void parse(IoUtilities::BinaryReader &reader, uint32 maxSize);
};
inline PpsInfo::PpsInfo() :
id(0),
spsId(0),
picOrderPresent(false)
picOrderPresent(false),
size(0)
{}
struct SliceInfo {
@ -99,7 +147,7 @@ struct SliceInfo {
uint32 deltaPicOrderCnt[2];
uint32 firstMbInSlice;
uint32 sps;
uint32 pps;
uint32 pps;
};
inline SliceInfo::SliceInfo() :

77
download_testfiles.sh Executable file
View File

@ -0,0 +1,77 @@
#!/bin/sh
testfilespath="$1"
# determine sequences for formatted output
red=$(tput setaf 1)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
blue=$(tput setaf 4)
bold=$(tput bold)
normal=$(tput sgr0)
inform() {
echo "${bold}==> ${blue}INFO:${normal} ${bold}${1}${normal}"
}
success() {
echo "${bold}==> ${green}SUCCESS:${normal} ${bold}${1}${normal}"
}
fail() {
echo "${bold}==> ${red}FAILURE:${normal} ${bold}${1}${normal}"
}
skipping() {
echo 'archive/files already exist -> skipping download'
}
download() {
title="$1" url="$2" filename="$3" destdir="$4"
inform "Downloading \"$title\" ..."
if [[ ! -d $destdir ]]; then
# actual download
pushd '/tmp'
if [[ ! -f $filename ]]; then
wget --output-document="$filename" "$url"
if [[ $? != 0 ]]; then
fail "unable to download: \"$url\""
return
fi
else
skipping
fi
popd
# extraction
mkdir "$destdir"
pushd "$destdir"
case "$filename" in
*.zip) unzip "/tmp/$filename";;
*) fail "unable to extract archive: format \"${filename#*.}\" not supported"
return;;
esac
if [[ $? != 0 ]]; then
fail "unable to extract \"/tmp/$filename\""
return
fi
popd
else
skipping
fi
}
# enter testfiles directory
if [[ -d $testfilespath ]]; then
cd "$testfilespath"
else
fail "specified testfiles directory does not exist"
exit -1
fi
download \
'Matroska Test Suite - Wave 1' \
'http://downloads.sourceforge.net/project/matroska/test_files/matroska_test_w1_1.zip?r=&ts=1454982254&use_mirror=netix' \
'matroska_test_w1_1.zip' \
'matroska_wave1'

View File

@ -123,7 +123,7 @@ inline std::string Margin::toString() const
{
std::stringstream res;
res << "top: " << m_top << "; left: " << m_left;
res << "bottom: " << m_bottom << "; right: " << m_right;
res << "; bottom: " << m_bottom << "; right: " << m_right;
return std::string(res.str());
}

View File

@ -7,6 +7,8 @@
#include "../wav/waveaudiostream.h"
#include "../avc/avcconfiguration.h"
#include "../mp4/mp4ids.h"
#include "../mp4/mp4track.h"
@ -411,6 +413,19 @@ void MatroskaTrack::internalParseHeader()
m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
}
break;
case GeneralMediaFormat::Avc:
if((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate))) {
auto avcConfig = make_unique<Media::AvcConfiguration>();
try {
m_istream->seekg(codecPrivateElement->dataOffset());
avcConfig->parse(m_reader, codecPrivateElement->dataSize());
Mp4Track::addInfo(*avcConfig, *this);
} catch(const TruncatedDataException &) {
addNotification(NotificationType::Critical, "AVC configuration is truncated.", context);
} catch(const Failure &) {
addNotification(NotificationType::Critical, "AVC configuration is invalid.", context);
}
}
default:
;
}

View File

@ -42,6 +42,7 @@ const char *MediaFormat::name() const
case GeneralMediaFormat::Amr: return "Adaptive Multi-Rate audio codec";
case GeneralMediaFormat::Avc:
switch(sub) {
case AvcCavlc444IntraProfile: return "Advanced Video Coding CAVLC 4:4:4 Intra Profile";
case AvcBaselineProfile: return "Advanced Video Coding Basline Profile";
case AvcMainProfile: return "Advanced Video Coding Main Profile";
case AvcScalableBaselineProfile: return "Advanced Video Coding Scalable Basline Profile";
@ -49,8 +50,12 @@ const char *MediaFormat::name() const
case AvcExtendedProfile: return "Advanced Video Coding Extended Profile";
case AvcHighProfile: return "Advanced Video Coding High Profile";
case AvcHigh10Profile: return "Advanced Video Coding High 10 Profile";
case AvcHighMultiviewProfile: return "Advanced Video Coding Multiview Profile";
case AvcHigh422Profile: return "Advanced Video Coding High 4:2:2 Profile";
case AvcStereoHighProfile: return "Advanced Video Coding Stereo High Profile";
case AvcHighMultiviewDepthProfile: return "Advanced Video Coding Multiview Depth High Profile";
case AvcHigh444Profile: return "Advanced Video Coding High 4:4:4 Profile";
case AvcHigh444PredictiveProfile: return "Advanced Video Coding High 4:4:4 Predictive Profile";
default: return "Advanced Video Coding";
}
case GeneralMediaFormat::Bitmap: return "Windows Bitmap";

View File

@ -195,6 +195,7 @@ enum Mpeg4VideoProfile : unsigned char {
};
enum AvcProfile : unsigned char {
AvcCavlc444IntraProfile = 0x2C,
AvcBaselineProfile = 0x42,
AvcMainProfile = 0x4D,
AvcScalableBaselineProfile = 0x53,
@ -202,8 +203,12 @@ enum AvcProfile : unsigned char {
AvcExtendedProfile = 0x58,
AvcHighProfile = 0x64,
AvcHigh10Profile = 0x6E,
AvcHighMultiviewProfile = 0x76,
AvcHigh422Profile = 0x7A,
AvcHigh444Profile = 0x90
AvcStereoHighProfile = 0x80,
AvcHighMultiviewDepthProfile = 0x8A,
AvcHigh444Profile = 0x90,
AvcHigh444PredictiveProfile = 0xF4
};
enum DtsSpecifier : unsigned char {

View File

@ -4,6 +4,8 @@
#include "./mp4ids.h"
#include "./mpeg4descriptor.h"
#include "../avc/avcconfiguration.h"
#include "../mpegaudio/mpegaudioframe.h"
#include "../mpegaudio/mpegaudioframestream.h"
@ -48,7 +50,8 @@ Mpeg4AudioSpecificConfig::Mpeg4AudioSpecificConfig() :
epConfig(0)
{}
Mpeg4VideoSpecificConfig::Mpeg4VideoSpecificConfig()
Mpeg4VideoSpecificConfig::Mpeg4VideoSpecificConfig() :
profile(0)
{}
/*!
@ -423,63 +426,6 @@ vector<uint64> Mp4Track::readChunkSizes()
return chunkSizes;
}
#ifdef UNDER_CONSTRUCTION
/*!
* \brief Reads the AVC configuration for the track.
* \remarks
* - Returns an empty configuration for non-AVC tracks.
* - Notifications might be added.
*/
AvcConfiguration Mp4Track::parseAvcConfiguration(StatusProvider &statusProvider, BinaryReader &reader, uint64 startOffset, uint64 size)
{
AvcConfiguration config;
try {
if(size >= 5) {
// skip first byte (is always 1)
reader.stream()->seekg(startOffset + 1);
// read profile, IDC level, NALU size length
config.profileIdc = reader.readByte();
config.profileCompat = reader.readByte();
config.levelIdc = reader.readByte();
config.naluSizeLength = reader.readByte() & 0x03;
// read SPS infos
if((size -= 5) >= 3) {
byte entryCount = reader.readByte() & 0x0f;
uint16 entrySize;
while(entryCount && size) {
if((entrySize = reader.readUInt16BE()) <= size) {
// TODO: read entry
size -= entrySize;
} else {
throw TruncatedDataException();
}
--entryCount;
}
// read PPS infos
if((size -= 5) >= 3) {
entryCount = reader.readByte();
while(entryCount && size) {
if((entrySize = reader.readUInt16BE()) <= size) {
// TODO: read entry
size -= entrySize;
} else {
throw TruncatedDataException();
}
--entryCount;
}
// TODO: read trailer
return config;
}
}
}
throw TruncatedDataException();
} catch (TruncatedDataException &) {
statusProvider.addNotification(NotificationType::Critical, "AVC configuration is truncated.", "parsing AVC configuration");
}
return config;
}
#endif
/*!
* \brief Reads the MPEG-4 elementary stream descriptor for the track.
* \remarks
@ -761,8 +707,7 @@ std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(Sta
default:
;
}
// skip stuff we're not interested in to get the start of the
// next video object
// skip remainging values to get the start of the next video object
while(size >= 3) {
if(reader.readUInt24BE() != 1) {
reader.stream()->seekg(-2, ios_base::cur);
@ -915,6 +860,41 @@ void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset)
}
}
/*!
* \brief Adds the information from the specified \a avcConfig to the specified \a track.
*/
void Mp4Track::addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
{
if(!avcConfig.spsInfos.empty()) {
const SpsInfo &spsInfo = avcConfig.spsInfos.back();
track.m_format.sub = spsInfo.profileIndication;
track.m_version = static_cast<double>(spsInfo.levelIndication) / 10;
track.m_cropping = spsInfo.cropping;
track.m_pixelSize = spsInfo.pictureSize;
switch(spsInfo.chromaFormatIndication) {
case 0:
track.m_chromaFormat = "monochrome";
break;
case 1:
track.m_chromaFormat = "YUV 4:2:0";
break;
case 2:
track.m_chromaFormat = "YUV 4:2:2";
break;
case 3:
track.m_chromaFormat = "YUV 4:4:4";
break;
default:
;
}
track.m_pixelAspectRatio = spsInfo.pixelAspectRatio;
} else {
track.m_format.sub = avcConfig.profileIndication;
track.m_version = static_cast<double>(avcConfig.levelIndication) / 10;
}
}
/*!
* \brief Makes the track entry ("trak"-atom) for the track. The data is written to the assigned output stream
* at the current position.
@ -1380,7 +1360,7 @@ void Mp4Track::internalParseHeader()
}
break;
case FourccIds::Mpeg4Sample:
//m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes and data reference index
// skip reserved bytes and data reference index
codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
if(!esDescParentAtom) {
esDescParentAtom = codecConfigContainerAtom;
@ -1394,10 +1374,23 @@ void Mp4Track::internalParseHeader()
;
}
}
// parse AVC configuration
//codecConfigContainerAtom->childById(Mp4AtomIds::AvcConfiguration);
// parse MPEG-4 elementary stream descriptor
if(esDescParentAtom) {
// parse AVC configuration
if(Mp4Atom *avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration)) {
m_istream->seekg(avcConfigAtom->dataOffset());
m_avcConfig = make_unique<Media::AvcConfiguration>();
try {
m_avcConfig->parse(reader, avcConfigAtom->dataSize());
addInfo(*m_avcConfig, *this);
} catch(const TruncatedDataException &) {
addNotification(NotificationType::Critical, "AVC configuration is truncated.", context);
} catch(const Failure &) {
addNotification(NotificationType::Critical, "AVC configuration is invalid.", context);
}
}
// parse MPEG-4 elementary stream descriptor
Mp4Atom *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor);
if(!esDescAtom) {
esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2);

View File

@ -1,10 +1,6 @@
#ifndef MP4TRACK_H
#define MP4TRACK_H
#ifdef UNDER_CONSTRUCTION
#include "../avc/avcconfiguration.h"
#endif
#include "../abstracttrack.h"
#include <vector>
@ -15,6 +11,7 @@ namespace Media
class Mp4Atom;
class Mpeg4Descriptor;
class AvcConfiguration;
class LIB_EXPORT Mpeg4AudioSpecificConfig
{
@ -133,11 +130,9 @@ public:
uint32 chunkCount() const;
uint32 sampleToChunkEntryCount() const;
const Mpeg4ElementaryStreamInfo *mpeg4ElementaryStreamInfo() const;
const AvcConfiguration *avcConfiguration() const;
// methods to parse configuration details from the track header
#ifdef UNDER_CONSTRUCTION
static AvcConfiguration parseAvcConfiguration(StatusProvider &statusProvider, IoUtilities::BinaryReader &reader, uint64 startOffset, uint64 size);
#endif
static std::unique_ptr<Mpeg4ElementaryStreamInfo> parseMpeg4ElementaryStreamInfo(StatusProvider &statusProvider, IoUtilities::BinaryReader &reader, Mp4Atom *esDescAtom);
static std::unique_ptr<Mpeg4AudioSpecificConfig> parseAudioSpecificConfig(StatusProvider &statusProvider, std::istream &stream, uint64 startOffset, uint64 size);
static std::unique_ptr<Mpeg4VideoSpecificConfig> parseVideoSpecificConfig(StatusProvider &statusProvider, IoUtilities::BinaryReader &reader, uint64 startOffset, uint64 size);
@ -160,6 +155,8 @@ public:
void updateChunkOffsets(const std::vector<uint64> &chunkOffsets);
void updateChunkOffset(uint32 chunkIndex, uint64 offset);
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track);
protected:
void internalParseHeader();
@ -187,6 +184,7 @@ private:
uint32 m_chunkCount;
uint32 m_sampleToChunkEntryCount;
std::unique_ptr<Mpeg4ElementaryStreamInfo> m_esInfo;
std::unique_ptr<AvcConfiguration> m_avcConfig;
};
/*!
@ -238,20 +236,26 @@ inline uint32 Mp4Track::sampleToChunkEntryCount() const
/*!
* \brief Returns information about the MPEG-4 elementary stream.
* \remarks
* - The Mp4Track::readMpeg4ElementaryStreamInfo() method must be called before
* to parse the information. This is done when parsing the track.
* - The information is only available, if the track has an MPEG-4 elementary stream
* descriptor atom.
* - The track must be parsed before this information becomes available.
* - The information is only available, if the track has an MPEG-4 elementary stream descriptor atom.
* - The track keeps ownership over the returned object.
* \sa
* - readMpeg4ElementaryStreamInfo()
* - hasMpeg4ElementaryStreamDesc()
*/
inline const Mpeg4ElementaryStreamInfo *Mp4Track::mpeg4ElementaryStreamInfo() const
{
return m_esInfo.get();
}
/*!
* \brief Returns the AVC configuration.
* \remarks
* - The track must be parsed before this information becomes available.
* - The track keeps ownership over the returned object.
*/
inline const AvcConfiguration *Mp4Track::avcConfiguration() const
{
return m_avcConfig.get();
}
}
#endif // MP4TRACK_H

View File

@ -30,6 +30,9 @@ forcefullparsedefault {
HEADERS += \
abstractcontainer.h \
abstracttrack.h \
aspectratio.h \
avc/avcinfo.h \
avc/avcconfiguration.h \
backuphelper.h \
basicfileinfo.h \
exceptions.h \
@ -98,6 +101,9 @@ HEADERS += \
SOURCES += \
abstractcontainer.cpp \
abstracttrack.cpp \
aspectratio.cpp \
avc/avcinfo.cpp \
avc/avcconfiguration.cpp
backuphelper.cpp \
basicfileinfo.cpp \
exceptions.cpp \
@ -154,15 +160,11 @@ SOURCES += \
underconstruction {
HEADERS += \
aac/aacframe.h \
aac/aaccodebook.h \
avc/avcinfo.h \
avc/avcconfiguration.h
aac/aaccodebook.h
SOURCES += \
aac/aacframe.cpp \
aac/aaccodebook.cpp \
avc/avcinfo.cpp \
avc/avcconfiguration.cpp
aac/aaccodebook.cpp
}
OTHER_FILES += \

1
tests/cppunit.cpp Normal file
View File

@ -0,0 +1 @@
#include <c++utilities/tests/cppunit.h>

51
tests/matroska.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <c++utilities/tests/testutils.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
using namespace CPPUNIT_NS;
class MatroskaTests : public TestFixture
{
CPPUNIT_TEST_SUITE(MatroskaTests);
CPPUNIT_TEST(testParsing);
CPPUNIT_TEST(testMaking);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testParsing();
void testMaking();
};
CPPUNIT_TEST_SUITE_REGISTRATION(MatroskaTests);
void MatroskaTests::setUp()
{}
void MatroskaTests::tearDown()
{}
/*!
* \brief Tests the Matroska parser via MediaFileInfo.
*/
void MatroskaTests::testParsing()
{
cerr << TestUtilities::workingCopyPath("matroska/test1.mkv") << endl;
}
/*!
* \brief Tests the Matroska maker via MediaFileInfo.
*/
void MatroskaTests::testMaking()
{
}