Merge branch 'refs/heads/devel'
This commit is contained in:
commit
c473f6ad7e
|
@ -15,6 +15,7 @@
|
|||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
*.pro.user
|
||||
*.txt.user
|
||||
*.pro.user.*
|
||||
*.qbs.user
|
||||
*.qbs.user.*
|
||||
|
|
|
@ -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
|
||||
|
@ -32,6 +33,7 @@ set(HEADER_FILES
|
|||
ogg/oggiterator.h
|
||||
ogg/oggpage.h
|
||||
ogg/oggstream.h
|
||||
opus/opusidentificationheader.h
|
||||
positioninset.h
|
||||
signature.h
|
||||
size.h
|
||||
|
@ -88,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
|
||||
|
@ -101,6 +104,7 @@ set(SRC_FILES
|
|||
ogg/oggiterator.cpp
|
||||
ogg/oggpage.cpp
|
||||
ogg/oggstream.cpp
|
||||
opus/opusidentificationheader.cpp
|
||||
signature.cpp
|
||||
statusprovider.cpp
|
||||
tag.cpp
|
||||
|
@ -132,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)
|
||||
|
@ -139,9 +150,9 @@ set(META_APP_NAME "Tag Parser")
|
|||
set(META_APP_AUTHOR "Martchus")
|
||||
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
|
||||
set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.")
|
||||
set(META_VERSION_MAJOR 4)
|
||||
set(META_VERSION_MAJOR 5)
|
||||
set(META_VERSION_MINOR 0)
|
||||
set(META_VERSION_PATCH 2)
|
||||
set(META_VERSION_PATCH 0)
|
||||
|
||||
# stringification of meta data
|
||||
set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"")
|
||||
|
@ -187,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)
|
||||
|
@ -213,21 +236,31 @@ foreach(HEADER_FILE ${HEADER_FILES})
|
|||
COMPONENT header
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(install-binary
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
add_custom_target(install-header
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=header -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
add_custom_target(install-mingw-w64
|
||||
DEPENDS install-binary install-header
|
||||
)
|
||||
add_custom_target(install-binary-strip
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_DO_STRIP=1 -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
add_custom_target(install-mingw-w64-strip
|
||||
DEPENDS install-binary-strip install-header
|
||||
)
|
||||
if(NOT TARGET install-binary)
|
||||
add_custom_target(install-binary
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
endif()
|
||||
if(NOT TARGET install-header)
|
||||
add_custom_target(install-header
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=header -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
endif()
|
||||
if(NOT TARGET install-mingw-w64)
|
||||
add_custom_target(install-mingw-w64
|
||||
DEPENDS install-binary install-header
|
||||
)
|
||||
endif()
|
||||
if(NOT TARGET install-binary-strip)
|
||||
add_custom_target(install-binary-strip
|
||||
DEPENDS ${META_PROJECT_NAME}
|
||||
COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_DO_STRIP=1 -DCMAKE_INSTALL_COMPONENT=binary -P "${CMAKE_BINARY_DIR}/cmake_install.cmake"
|
||||
)
|
||||
endif()
|
||||
if(NOT TARGET install-mingw-w64-strip)
|
||||
add_custom_target(install-mingw-w64-strip
|
||||
DEPENDS install-binary-strip install-header
|
||||
)
|
||||
endif()
|
||||
|
|
|
@ -5,7 +5,7 @@ C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.
|
|||
The tag library can read and write the following tag formats:
|
||||
- iTunes-style MP4 tags (MP4-DASH is supported)
|
||||
- ID3v1 and ID3v2 tags
|
||||
- Vorbis comments (cover art via "METADATA_BLOCK_PICTURE" is supported)
|
||||
- Vorbis and Opus comments (cover art via "METADATA_BLOCK_PICTURE" is supported) in Ogg streams
|
||||
- Matroska/WebM tags and attachments
|
||||
|
||||
## File layout options
|
||||
|
@ -21,6 +21,8 @@ or appending the tag. Usage of padding can be configured:
|
|||
|
||||
However, it is also possible to force rewriting the entire file.
|
||||
|
||||
Taking advantage of padding is currently not supported when dealing with Ogg streams.
|
||||
|
||||
## Additional features
|
||||
The library can also display technical information such as the ID, format, language, bitrate,
|
||||
duration, size, timestamps, sampling frequency, FPS and other information of the tracks.
|
||||
|
|
|
@ -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 {
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{}
|
||||
|
||||
|
|
265
avc/avcinfo.cpp
265
avc/avcinfo.cpp
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
122
avc/avcinfo.h
122
avc/avcinfo.h
|
@ -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() :
|
||||
|
|
|
@ -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'
|
2
margin.h
2
margin.h
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
;
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ startParsingSignature:
|
|||
goto startParsingSignature;
|
||||
}
|
||||
if(m_paddingSize) {
|
||||
addNotification(NotificationType::Warning, ConversionUtilities::numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
|
||||
addNotification(NotificationType::Warning, numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
|
||||
}
|
||||
|
||||
// parse signature
|
||||
|
@ -198,7 +198,7 @@ startParsingSignature:
|
|||
stream().read(buff, 5);
|
||||
|
||||
// set the container offset to skip ID3v2 header
|
||||
m_containerOffset += ConversionUtilities::toNormalInt(ConversionUtilities::BE::toUInt32(buff + 1)) + 10;
|
||||
m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
|
||||
if((*buff) & 0x10) {
|
||||
// footer present
|
||||
m_containerOffset += 10;
|
||||
|
@ -208,17 +208,19 @@ startParsingSignature:
|
|||
goto startParsingSignature;
|
||||
|
||||
case ContainerFormat::Mp4: {
|
||||
// EBML/Matroska is handled using Mp4Container instance
|
||||
m_container = make_unique<Mp4Container>(*this, m_containerOffset);
|
||||
NotificationList notifications;
|
||||
try {
|
||||
static_cast<Mp4Container *>(m_container.get())->validateElementStructure(notifications, &m_paddingSize);
|
||||
} catch (Failure &) {
|
||||
} catch(const Failure &) {
|
||||
m_containerParsingStatus = ParsingStatus::CriticalFailure;
|
||||
}
|
||||
addNotifications(notifications);
|
||||
break;
|
||||
|
||||
} case ContainerFormat::Ebml: {
|
||||
// EBML/Matroska is handled using MatroskaContainer instance
|
||||
auto container = make_unique<MatroskaContainer>(*this, m_containerOffset);
|
||||
NotificationList notifications;
|
||||
try {
|
||||
|
@ -234,16 +236,28 @@ startParsingSignature:
|
|||
container->validateElementStructure(notifications, &m_paddingSize);
|
||||
container->validateIndex();
|
||||
}
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
m_containerParsingStatus = ParsingStatus::CriticalFailure;
|
||||
}
|
||||
m_container = move(container);
|
||||
addNotifications(notifications);
|
||||
break;
|
||||
} case ContainerFormat::Ogg:
|
||||
// Ogg is handled using OggContainer instance
|
||||
m_container = make_unique<OggContainer>(*this, m_containerOffset);
|
||||
static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(m_forceFullParse);
|
||||
break;
|
||||
case ContainerFormat::Unknown:
|
||||
// container format is still unknown -> check for magic numbers at odd offsets
|
||||
// -> check for tar (magic number at offset 0x101)
|
||||
if(size() > 0x107) {
|
||||
stream().seekg(0x101);
|
||||
stream().read(buff, 6);
|
||||
if(buff[0] == 0x75 && buff[1] == 0x73 && buff[2] == 0x74 && buff[3] == 0x61 && buff[4] == 0x72 && buff[5] == 0x00) {
|
||||
m_containerFormat = ContainerFormat::Tar;
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
@ -306,10 +320,10 @@ void MediaFileInfo::parseTracks()
|
|||
m_singleTrack->parseHeader();
|
||||
}
|
||||
m_tracksParsingStatus = ParsingStatus::Ok;
|
||||
} catch (NotImplementedException &) {
|
||||
} catch(const NotImplementedException &) {
|
||||
addNotification(NotificationType::Information, "Parsing tracks is not implemented for the container format of the file.", context);
|
||||
m_tracksParsingStatus = ParsingStatus::NotSupported;
|
||||
} catch (Failure &) {
|
||||
} catch(const Failure &) {
|
||||
addNotification(NotificationType::Critical, "Unable to parse tracks.", context);
|
||||
m_tracksParsingStatus = ParsingStatus::CriticalFailure;
|
||||
}
|
||||
|
@ -349,9 +363,9 @@ void MediaFileInfo::parseTags()
|
|||
try {
|
||||
m_id3v1Tag->parse(stream(), true);
|
||||
m_actualExistingId3v1Tag = true;
|
||||
} catch(NoDataFoundException &) {
|
||||
} catch(const NoDataFoundException &) {
|
||||
m_id3v1Tag.reset(); // no ID3v1 tag found
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
m_tagsParsingStatus = ParsingStatus::CriticalFailure;
|
||||
addNotification(NotificationType::Critical, "Unable to parse ID3v1 tag.", context);
|
||||
}
|
||||
|
@ -364,9 +378,9 @@ void MediaFileInfo::parseTags()
|
|||
try {
|
||||
id3v2Tag->parse(stream(), size() - offset);
|
||||
m_paddingSize += id3v2Tag->paddingSize();
|
||||
} catch(NoDataFoundException &) {
|
||||
} catch(const NoDataFoundException &) {
|
||||
continue;
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
m_tagsParsingStatus = ParsingStatus::CriticalFailure;
|
||||
addNotification(NotificationType::Critical, "Unable to parse ID3v2 tag.", context);
|
||||
}
|
||||
|
@ -375,13 +389,13 @@ void MediaFileInfo::parseTags()
|
|||
if(m_container) {
|
||||
try {
|
||||
m_container->parseTags();
|
||||
} catch (NotImplementedException &) {
|
||||
} catch(const NotImplementedException &) {
|
||||
if(m_tagsParsingStatus == ParsingStatus::NotParsedYet) {
|
||||
// do not override parsing status from ID3 tags here
|
||||
m_tagsParsingStatus = ParsingStatus::NotSupported;
|
||||
}
|
||||
addNotification(NotificationType::Information, "Parsing tags is not implemented for the container format of the file.", context);
|
||||
} catch (Failure &) {
|
||||
} catch(const Failure &) {
|
||||
m_tagsParsingStatus = ParsingStatus::CriticalFailure;
|
||||
addNotification(NotificationType::Critical, "Unable to parse tag.", context);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -183,6 +188,7 @@ const char *MediaFormat::name() const
|
|||
case Mpeg4FineGranularityScalableProfile5: return "MPEG-4 Fine Granularity Scalable Profile L5";
|
||||
default: return "MPEG-4 Visual";
|
||||
}
|
||||
case GeneralMediaFormat::Mpeg4TimedText: return "MPEG-4 Timed Text";
|
||||
case GeneralMediaFormat::Mpc: return "Musepack SV8";
|
||||
case GeneralMediaFormat::Pcm:
|
||||
switch(sub) {
|
||||
|
|
|
@ -12,11 +12,11 @@ namespace Media {
|
|||
*/
|
||||
enum class MediaType
|
||||
{
|
||||
Unknown,
|
||||
Audio,
|
||||
Video,
|
||||
Text,
|
||||
Hint
|
||||
Unknown, /**< the type is unknown */
|
||||
Audio, /**< audio/sound */
|
||||
Video, /**< video */
|
||||
Text, /**< text/subtitle */
|
||||
Hint /**< hint */
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -24,7 +24,7 @@ enum class MediaType
|
|||
*/
|
||||
enum class GeneralMediaFormat
|
||||
{
|
||||
Unknown,
|
||||
Unknown, /**< the format is unknown */
|
||||
Aac, /**< Advanced Video Coding */
|
||||
Ac3, /**< Dolby Digital */
|
||||
Ac4, /**< AC-4 */
|
||||
|
@ -50,16 +50,17 @@ enum class GeneralMediaFormat
|
|||
InteractionStream, /**< Interaction Stream */
|
||||
Jpeg, /**< JPEG */
|
||||
OggKate, /**< Karaoke And Text Encapsulation */
|
||||
Opus = 64, /**< Opus */
|
||||
MicrosoftAudioCodecManager = 26, /**< Microsoft Audio Codec Manager (ACM) */
|
||||
Opus, /**< Opus */
|
||||
MicrosoftAudioCodecManager, /**< Microsoft Audio Codec Manager (ACM) */
|
||||
MicrosoftMpeg4, /**< Microsoft MPEG-4 */
|
||||
MicrosoftVideoCodecManager, /**< Microsoft Video Codec Manager (VCM) */
|
||||
DolbyMlp, /** < Dolby MLP */
|
||||
Mpeg1Audio, /**< MPEG-1 Audio */
|
||||
Mpeg1Video, /**< MPEG-1 Vudio */
|
||||
Mpeg2Audio, /**< MPEG-2 Audio */
|
||||
Mpeg2Video, /**< MPEG-2 Vudio */
|
||||
Mpeg4Video, /**< MPEG-4 */
|
||||
Mpeg2Video, /**< MPEG-2 Video */
|
||||
Mpeg4Video, /**< MPEG-4 Video */
|
||||
Mpeg4TimedText, /**< MPEG-4 Timed Text / Streaming text format / Part 17 */
|
||||
Mpc, /**< Musepack */
|
||||
Pcm, /**< Pulse Code Modulation */
|
||||
Png, /**< PNG */
|
||||
|
@ -195,6 +196,7 @@ enum Mpeg4VideoProfile : unsigned char {
|
|||
};
|
||||
|
||||
enum AvcProfile : unsigned char {
|
||||
AvcCavlc444IntraProfile = 0x2C,
|
||||
AvcBaselineProfile = 0x42,
|
||||
AvcMainProfile = 0x4D,
|
||||
AvcScalableBaselineProfile = 0x53,
|
||||
|
@ -202,8 +204,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 {
|
||||
|
|
|
@ -51,6 +51,8 @@ MediaFormat fourccToMediaFormat(uint32 fourccId)
|
|||
return GeneralMediaFormat::Mpeg2Video;
|
||||
case Mpeg4Video:
|
||||
return GeneralMediaFormat::Mpeg4Video;
|
||||
case Mpeg4TimedText:
|
||||
return GeneralMediaFormat::Mpeg4TimedText;
|
||||
case Hevc1: case Hevc2:
|
||||
return MediaFormat(GeneralMediaFormat::Hevc);
|
||||
case Avc1: case Avc2: case Avc3: case Avc4: case H264Decoder1: case H264Decoder2:
|
||||
|
|
|
@ -318,6 +318,7 @@ enum KnownValue : uint32 {
|
|||
Mpeg4Decoder2 = 0x53454447,
|
||||
Mpeg4Decoder3 = 0x57563146,
|
||||
Mpeg4Sample = 0x6d703473, /**< MPEG-4 stream (other then video/audio) */
|
||||
Mpeg4TimedText = 0x74783367, /**< MPEG-4 Timed Text / Streaming text format / Part 17 */
|
||||
Mpeg4Video = 0x6d703476, /**< MPEG-4 video */
|
||||
MsMpeg4V1Decoder1 = 0x44495631,
|
||||
MsMpeg4V1Decoder2 = 0x64697631,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <c++utilities/io/binarywriter.h>
|
||||
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
namespace Media
|
||||
{
|
||||
|
|
146
mp4/mp4track.cpp
146
mp4/mp4track.cpp
|
@ -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.
|
||||
|
@ -1285,23 +1265,28 @@ void Mp4Track::internalParseHeader()
|
|||
buff[2] = ((rawLanguage & 0x001F) >> 0x0) + 0x60;
|
||||
m_language = string(buff, 3);
|
||||
// read hdlr atom
|
||||
// track type
|
||||
m_istream->seekg(m_hdlrAtom->startOffset() + 16); // seek to beg, skip size, name, version, flags and reserved bytes
|
||||
string trackTypeStr = reader.readString(4);
|
||||
if(trackTypeStr == "soun") {
|
||||
m_mediaType = MediaType::Audio;
|
||||
} else if(trackTypeStr == "vide") {
|
||||
// -> seek to begin skipping size, name, version, flags and reserved bytes
|
||||
m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
|
||||
// -> track type
|
||||
switch(reader.readUInt32BE()) {
|
||||
case 0x76696465:
|
||||
m_mediaType = MediaType::Video;
|
||||
} else if(trackTypeStr == "hint") {
|
||||
break;
|
||||
case 0x736F756E:
|
||||
m_mediaType = MediaType::Audio;
|
||||
break;
|
||||
case 0x68696E74:
|
||||
m_mediaType = MediaType::Hint;
|
||||
} else if(trackTypeStr == "meta") {
|
||||
break;
|
||||
case 0x6D657461: case 0x74657874:
|
||||
m_mediaType = MediaType::Text;
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
m_mediaType = MediaType::Unknown;
|
||||
}
|
||||
|
||||
// name
|
||||
m_istream->seekg(12, ios_base::cur); // skip reserved bytes
|
||||
//name = reader.readString(hdlrAtom->size - 16 - 4 - 12);
|
||||
m_name = reader.readTerminatedString(m_hdlrAtom->totalSize() - 12 - 4 - 12, 0);
|
||||
// read stco atom (only chunk count)
|
||||
m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
|
||||
|
@ -1380,7 +1365,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 +1379,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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -58,8 +58,7 @@ void MpegAudioFrameStream::internalParseHeader()
|
|||
m_bitrate = frame.isXingFramefieldPresent()
|
||||
? ((static_cast<double>(m_size) * 8.0) / (static_cast<double>(frame.xingFrameCount() * frame.sampleCount()) / static_cast<double>(frame.samplingFrequency())) / 1024.0)
|
||||
: frame.bitrate();
|
||||
m_bytesPerSecond = m_bitrate * 125;
|
||||
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bitrate * 128.0));
|
||||
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bytesPerSecond = m_bitrate * 125));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,9 +25,6 @@ OggContainer::OggContainer(MediaFileInfo &fileInfo, uint64 startOffset) :
|
|||
m_validateChecksums(false)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Destroys the container.
|
||||
*/
|
||||
OggContainer::~OggContainer()
|
||||
{}
|
||||
|
||||
|
@ -42,7 +39,6 @@ void OggContainer::internalParseHeader()
|
|||
static const string context("parsing OGG bitstream header");
|
||||
// iterate through pages using OggIterator helper class
|
||||
try {
|
||||
uint32 pageSequenceNumber = 0;
|
||||
// ensure iterator is setup properly
|
||||
for(m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage()) {
|
||||
const OggPage &page = m_iterator.currentPage();
|
||||
|
@ -51,25 +47,29 @@ void OggContainer::internalParseHeader()
|
|||
addNotification(NotificationType::Warning, "The denoted checksum of the OGG page at " + ConversionUtilities::numberToString(m_iterator.currentSegmentOffset()) + " does not match the computed checksum.", context);
|
||||
}
|
||||
}
|
||||
if(!m_streamsBySerialNo.count(page.streamSerialNumber())) {
|
||||
OggStream *stream;
|
||||
try {
|
||||
stream = m_tracks[m_streamsBySerialNo.at(page.streamSerialNumber())].get();
|
||||
} catch(const out_of_range &) {
|
||||
// new stream serial number recognized -> add new stream
|
||||
m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
|
||||
m_tracks.emplace_back(new OggStream(*this, m_iterator.currentPageIndex()));
|
||||
m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex()));
|
||||
stream = m_tracks.back().get();
|
||||
}
|
||||
if(pageSequenceNumber != page.sequenceNumber()) {
|
||||
if(pageSequenceNumber != 0) {
|
||||
if(stream->m_currentSequenceNumber != page.sequenceNumber()) {
|
||||
if(stream->m_currentSequenceNumber) {
|
||||
addNotification(NotificationType::Warning, "Page is missing (page sequence number omitted).", context);
|
||||
pageSequenceNumber = page.sequenceNumber();
|
||||
}
|
||||
stream->m_currentSequenceNumber = page.sequenceNumber() + 1;
|
||||
} else {
|
||||
++pageSequenceNumber;
|
||||
++stream->m_currentSequenceNumber;
|
||||
}
|
||||
}
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
// thrown when page exceeds max size
|
||||
addNotification(NotificationType::Critical, "The OGG file is truncated.", context);
|
||||
throw;
|
||||
} catch(InvalidDataException &) {
|
||||
} catch(const InvalidDataException &) {
|
||||
// thrown when first 4 byte do not match capture pattern
|
||||
addNotification(NotificationType::Critical, "Capture pattern \"OggS\" at " + ConversionUtilities::numberToString(m_iterator.currentSegmentOffset()) + " expected.", context);
|
||||
throw;
|
||||
|
@ -79,19 +79,28 @@ void OggContainer::internalParseHeader()
|
|||
void OggContainer::internalParseTags()
|
||||
{
|
||||
parseTracks(); // tracks needs to be parsed because tags are stored at stream level
|
||||
for(auto &i : m_commentTable) {
|
||||
//fileInfo().stream().seekg(get<1>(i));
|
||||
m_iterator.setPageIndex(i.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(i.firstSegmentIndex);
|
||||
m_tags[i.tagIndex]->parse(m_iterator);
|
||||
i.lastPageIndex = m_iterator.currentPageIndex();
|
||||
i.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
for(VorbisCommentInfo &vorbisCommentInfo : m_commentTable) {
|
||||
m_iterator.setPageIndex(vorbisCommentInfo.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(vorbisCommentInfo.firstSegmentIndex);
|
||||
switch(vorbisCommentInfo.streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
m_iterator.seekForward(8); // skip header (has already been detected by OggStream)
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator, true);
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Critical, "Stream format not supported.", "parsing tags from OGG streams");
|
||||
}
|
||||
vorbisCommentInfo.lastPageIndex = m_iterator.currentPageIndex();
|
||||
vorbisCommentInfo.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void OggContainer::ariseComment(vector<OggPage>::size_type pageIndex, vector<uint32>::size_type segmentIndex)
|
||||
void OggContainer::ariseComment(vector<OggPage>::size_type pageIndex, vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat)
|
||||
{
|
||||
m_commentTable.emplace_back(pageIndex, segmentIndex, m_tags.size());
|
||||
m_commentTable.emplace_back(pageIndex, segmentIndex, m_tags.size(), mediaFormat);
|
||||
m_tags.emplace_back(make_unique<VorbisComment>());
|
||||
}
|
||||
|
||||
|
@ -99,14 +108,14 @@ void OggContainer::internalParseTracks()
|
|||
{
|
||||
if(!areTracksParsed()) {
|
||||
parseHeader();
|
||||
static const string context("parsing OGG bit streams");
|
||||
static const string context("parsing OGG stream");
|
||||
for(auto &stream : m_tracks) {
|
||||
try { // try to parse header
|
||||
stream->parseHeader();
|
||||
if(stream->duration() > m_duration) {
|
||||
m_duration = stream->duration();
|
||||
}
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
addNotification(NotificationType::Critical, "Unable to parse stream at " + ConversionUtilities::numberToString(stream->startOffset()) + ".", context);
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +163,18 @@ void OggContainer::internalMakeFile()
|
|||
if(m_iterator.currentPageIndex() == commentTableIterator->firstPageIndex) {
|
||||
// make Vorbis Comment segment
|
||||
auto offset = buffer.tellp();
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer);
|
||||
switch(commentTableIterator->streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
ConversionUtilities::BE::getBytes(0x4F70757354616773u, copy.buffer());
|
||||
buffer.write(copy.buffer(), 8);
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer, true);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
newSegmentSizes.push_back(buffer.tellp() - offset);
|
||||
}
|
||||
if(m_iterator.currentPageIndex() > commentTableIterator->lastPageIndex
|
||||
|
|
|
@ -18,21 +18,23 @@ class MediaFileInfo;
|
|||
|
||||
struct LIB_EXPORT VorbisCommentInfo
|
||||
{
|
||||
VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex);
|
||||
VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::vector<OggPage>::size_type firstPageIndex;
|
||||
std::vector<OggPage>::size_type firstSegmentIndex;
|
||||
std::vector<OggPage>::size_type lastPageIndex;
|
||||
std::vector<OggPage>::size_type lastSegmentIndex;
|
||||
std::vector<std::unique_ptr<VorbisComment> >::size_type tagIndex;
|
||||
GeneralMediaFormat streamFormat;
|
||||
};
|
||||
|
||||
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex) :
|
||||
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat mediaFormat) :
|
||||
firstPageIndex(firstPageIndex),
|
||||
firstSegmentIndex(firstSegmentIndex),
|
||||
lastPageIndex(0),
|
||||
lastSegmentIndex(0),
|
||||
tagIndex(tagIndex)
|
||||
tagIndex(tagIndex),
|
||||
streamFormat(mediaFormat)
|
||||
{}
|
||||
|
||||
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>
|
||||
|
@ -54,7 +56,7 @@ protected:
|
|||
void internalMakeFile();
|
||||
|
||||
private:
|
||||
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex);
|
||||
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ public:
|
|||
|
||||
std::istream &stream();
|
||||
void setStream(std::istream &stream);
|
||||
uint64 startOffset() const;
|
||||
uint64 streamSize() const;
|
||||
void reset();
|
||||
void nextPage();
|
||||
void nextSegment();
|
||||
|
@ -34,6 +36,7 @@ public:
|
|||
bool areAllPagesFetched() const;
|
||||
void read(char *buffer, size_t count);
|
||||
void seekForward(size_t count);
|
||||
bool bytesRemaining(size_t atLeast) const;
|
||||
|
||||
operator bool() const;
|
||||
OggIterator &operator ++();
|
||||
|
@ -92,6 +95,22 @@ inline void OggIterator::setStream(std::istream &stream)
|
|||
m_stream = &stream;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the start offset (which has been specified when constructing the iterator).
|
||||
*/
|
||||
inline uint64 OggIterator::startOffset() const
|
||||
{
|
||||
return m_startOffset;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the stream size (which has been specified when constructing the iterator).
|
||||
*/
|
||||
inline uint64 OggIterator::streamSize() const
|
||||
{
|
||||
return m_streamSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns a vector of containing the OGG pages that have been fetched yet.
|
||||
*/
|
||||
|
@ -226,6 +245,14 @@ inline bool OggIterator::areAllPagesFetched() const
|
|||
return (m_pages.empty() ? m_startOffset : m_pages.back().startOffset() + m_pages.back().totalSize()) >= m_streamSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether there are \a atLeast bytes remaining.
|
||||
*/
|
||||
inline bool OggIterator::bytesRemaining(size_t atLeast) const
|
||||
{
|
||||
return *this && currentCharacterOffset() + atLeast <= streamSize();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Increments the current position by one segment if the iterator is valid; otherwise nothing happens.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "../vorbis/vorbispackagetypes.h"
|
||||
#include "../vorbis/vorbisidentificationheader.h"
|
||||
|
||||
#include "../opus/opusidentificationheader.h"
|
||||
|
||||
#include "../mediafileinfo.h"
|
||||
#include "../exceptions.h"
|
||||
#include "../mediaformat.h"
|
||||
|
@ -28,7 +30,8 @@ namespace Media {
|
|||
OggStream::OggStream(OggContainer &container, vector<OggPage>::size_type startPage) :
|
||||
AbstractTrack(container.stream(), container.m_iterator.pages()[startPage].startOffset()),
|
||||
m_startPage(startPage),
|
||||
m_container(container)
|
||||
m_container(container),
|
||||
m_currentSequenceNumber(0)
|
||||
{}
|
||||
|
||||
/*!
|
||||
|
@ -52,12 +55,12 @@ void OggStream::internalParseHeader()
|
|||
bool hasIdentificationHeader = false;
|
||||
bool hasCommentHeader = false;
|
||||
for(; iterator; ++iterator) {
|
||||
uint32 currentSize = iterator.currentSegmentSize();
|
||||
const uint32 currentSize = iterator.currentSegmentSize();
|
||||
m_size += currentSize;
|
||||
if(currentSize >= 8) {
|
||||
// determine stream format
|
||||
inputStream().seekg(iterator.currentSegmentOffset());
|
||||
uint64 sig = reader().readUInt64BE();
|
||||
const uint64 sig = reader().readUInt64BE();
|
||||
if((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
|
||||
// Vorbis header detected
|
||||
// set Vorbis as format
|
||||
|
@ -75,8 +78,10 @@ void OggStream::internalParseHeader()
|
|||
switch(sig >> 56) {
|
||||
case VorbisPackageTypes::Identification:
|
||||
if(!hasIdentificationHeader) {
|
||||
// parse identification header
|
||||
VorbisIdentificationHeader ind;
|
||||
ind.parseHeader(iterator);
|
||||
m_version = ind.version();
|
||||
m_channelCount = ind.channels();
|
||||
m_samplingFrequency = ind.sampleRate();
|
||||
if(ind.nominalBitrate()) {
|
||||
|
@ -87,6 +92,7 @@ void OggStream::internalParseHeader()
|
|||
if(m_bitrate) {
|
||||
m_bitrate = static_cast<double>(m_bitrate) / 1000.0;
|
||||
}
|
||||
// determine sample count and duration if all pages have been fetched
|
||||
if(iterator.areAllPagesFetched()) {
|
||||
auto pred = [this] (const OggPage &page) -> bool {
|
||||
return page.streamSerialNumber() == this->id();
|
||||
|
@ -105,9 +111,9 @@ void OggStream::internalParseHeader()
|
|||
}
|
||||
break;
|
||||
case VorbisPackageTypes::Comments:
|
||||
// Vorbis comment found -> notify container about comment
|
||||
if(!hasCommentHeader) {
|
||||
//m_container.m_commentOffsets.push_back(iterator.currentOffset());
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex());
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Vorbis);
|
||||
hasCommentHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Vorbis comment header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
|
@ -118,7 +124,84 @@ void OggStream::internalParseHeader()
|
|||
default:
|
||||
;
|
||||
}
|
||||
} // currently only Vorbis supported
|
||||
} else if(sig == 0x4F70757348656164u) {
|
||||
// Opus header detected
|
||||
// set Opus as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Opus;
|
||||
m_mediaType = MediaType::Audio;
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
if(!hasIdentificationHeader) {
|
||||
// parse identification header
|
||||
OpusIdentificationHeader ind;
|
||||
ind.parseHeader(iterator);
|
||||
m_version = ind.version();
|
||||
m_channelCount = ind.channels();
|
||||
m_samplingFrequency = ind.sampleRate();
|
||||
// determine sample count and duration if all pages have been fetched
|
||||
if(iterator.areAllPagesFetched()) {
|
||||
auto pred = [this] (const OggPage &page) -> bool {
|
||||
return page.streamSerialNumber() == this->id();
|
||||
};
|
||||
const auto &pages = iterator.pages();
|
||||
auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
|
||||
auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
|
||||
if(firstPage != pages.cend() && lastPage != pages.crend()) {
|
||||
m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
|
||||
// must apply "pre-skip" here do calculate effective sample count and duration?
|
||||
if(m_sampleCount > ind.preSkip()) {
|
||||
m_sampleCount -= ind.preSkip();
|
||||
} else {
|
||||
m_sampleCount = 0;
|
||||
}
|
||||
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
|
||||
}
|
||||
}
|
||||
hasIdentificationHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus identification header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
} else if(sig == 0x4F70757354616773u) {
|
||||
// Opus comment detected
|
||||
// set Opus as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Opus;
|
||||
m_mediaType = MediaType::Audio;
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
// notify container about comment
|
||||
if(!hasCommentHeader) {
|
||||
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Opus);
|
||||
hasCommentHeader = true;
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus tags/comment header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
} else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
|
||||
// Theora header detected
|
||||
// set Theora as format
|
||||
switch(m_format.general) {
|
||||
case GeneralMediaFormat::Unknown:
|
||||
m_format = GeneralMediaFormat::Theora;
|
||||
m_mediaType = MediaType::Video;
|
||||
break;
|
||||
case GeneralMediaFormat::Theora:
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
// TODO: read more information about Theora stream
|
||||
} // currently only Vorbis, Opus and Theora can be detected
|
||||
}
|
||||
}
|
||||
if(m_duration.isNull() && m_size && m_bitrate) {
|
||||
|
|
|
@ -26,6 +26,7 @@ protected:
|
|||
private:
|
||||
std::vector<OggPage>::size_type m_startPage;
|
||||
OggContainer &m_container;
|
||||
uint32 m_currentSequenceNumber;
|
||||
};
|
||||
|
||||
inline TrackType OggStream::type() const
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#include "./opusidentificationheader.h"
|
||||
|
||||
#include "../ogg/oggiterator.h"
|
||||
|
||||
#include "../exceptions.h"
|
||||
|
||||
#include <c++utilities/conversion/binaryconversion.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace ConversionUtilities;
|
||||
|
||||
namespace Media {
|
||||
|
||||
/*!
|
||||
* \class Media::OpusIdentificationHeader
|
||||
* \brief The OpusIdentificationHeader class is an Opus identification header parser.
|
||||
* \sa https://wiki.xiph.org/OggOpus
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \brief Parses the Opus identification header which is read using the specified \a iterator.
|
||||
* \remarks The header is assumed to start at the current position of \a iterator.
|
||||
*/
|
||||
void OpusIdentificationHeader::parseHeader(OggIterator &iterator)
|
||||
{
|
||||
char buff[19 - 8];
|
||||
iterator.read(buff, 8);
|
||||
if(BE::toUInt64(buff) != 0x4F70757348656164u) {
|
||||
throw InvalidDataException(); // not Opus identification header
|
||||
}
|
||||
iterator.read(buff, sizeof(buff));
|
||||
m_version = static_cast<byte>(*(buff));
|
||||
m_channels = static_cast<byte>(*(buff + 1));
|
||||
m_preSkip = LE::toUInt16(buff + 2);
|
||||
m_sampleRate = LE::toUInt32(buff + 4);
|
||||
m_outputGain = LE::toUInt16(buff + 8);
|
||||
m_channelMap = static_cast<byte>(*(buff + 10));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
#ifndef MEDIA_OPUSIDENTIFICATIONHEADER_H
|
||||
#define MEDIA_OPUSIDENTIFICATIONHEADER_H
|
||||
|
||||
#include <c++utilities/application/global.h>
|
||||
#include <c++utilities/conversion/types.h>
|
||||
|
||||
#include <istream>
|
||||
|
||||
namespace Media {
|
||||
|
||||
class OggIterator;
|
||||
|
||||
class LIB_EXPORT OpusIdentificationHeader
|
||||
{
|
||||
public:
|
||||
OpusIdentificationHeader();
|
||||
|
||||
void parseHeader(OggIterator &iterator);
|
||||
|
||||
byte version() const;
|
||||
byte channels() const;
|
||||
uint16 preSkip() const;
|
||||
uint32 sampleRate() const;
|
||||
uint16 outputGain() const;
|
||||
byte channelMap() const;
|
||||
|
||||
private:
|
||||
byte m_version;
|
||||
byte m_channels;
|
||||
uint16 m_preSkip;
|
||||
uint32 m_sampleRate;
|
||||
uint16 m_outputGain;
|
||||
byte m_channelMap;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new vorbis identification header.
|
||||
*/
|
||||
inline OpusIdentificationHeader::OpusIdentificationHeader() :
|
||||
m_version(0),
|
||||
m_channels(0),
|
||||
m_sampleRate(0),
|
||||
m_outputGain(0),
|
||||
m_channelMap(0)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Returns the version (which should be 1 currently).
|
||||
*/
|
||||
inline byte OpusIdentificationHeader::version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the number of channels for the Opus stream.
|
||||
*/
|
||||
inline byte OpusIdentificationHeader::channels() const
|
||||
{
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns "pre-skip" value for the Opus stream.
|
||||
*
|
||||
* This is the number of samples (at 48 kHz) to discard from the decoder
|
||||
* output when starting playback, and also the number to subtract from a
|
||||
* page's granule position to calculate its PCM sample position.
|
||||
*/
|
||||
inline uint16 OpusIdentificationHeader::preSkip() const
|
||||
{
|
||||
return m_preSkip;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the INPUT sample rate.
|
||||
* \remarks This is not the sample rate to use for playback of the encoded data.
|
||||
* \sa https://wiki.xiph.org/OggOpus
|
||||
*/
|
||||
inline uint32 OpusIdentificationHeader::sampleRate() const
|
||||
{
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the output gain.
|
||||
*
|
||||
* This is a gain to be applied by the decoder. Virtually all players and media frameworks
|
||||
* should apply it by default.
|
||||
*/
|
||||
inline uint16 OpusIdentificationHeader::outputGain() const
|
||||
{
|
||||
return m_outputGain;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the channel mapping family.
|
||||
*
|
||||
* The channel mapping family indicates the order and semantic meaning of the various channels
|
||||
* encoded in each Opus packet.
|
||||
* \sa https://wiki.xiph.org/OggOpus
|
||||
*/
|
||||
inline byte OpusIdentificationHeader::channelMap() const
|
||||
{
|
||||
return m_channelMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MEDIA_OPUSIDENTIFICATIONHEADER_H
|
|
@ -11,6 +11,7 @@ namespace Media {
|
|||
*/
|
||||
enum Sig64 : uint64
|
||||
{
|
||||
Ar = 0x213C617263683E0A,
|
||||
Asf1 = 0x3026B2758E66CF11ul,
|
||||
Asf2 = 0xA6D900AA0062CE6Cul,
|
||||
Png = 0x89504E470D0A1A0Aul,
|
||||
|
@ -109,6 +110,8 @@ ContainerFormat parseSignature(const char *buffer, int bufferSize)
|
|||
}
|
||||
// return corresponding container format
|
||||
switch(sig) { // check 64-bit signatures
|
||||
case Ar:
|
||||
return ContainerFormat::Ar;
|
||||
case Asf1:
|
||||
return ContainerFormat::Asf;
|
||||
case Asf2:
|
||||
|
@ -223,6 +226,7 @@ ContainerFormat parseSignature(const char *buffer, int bufferSize)
|
|||
const char *containerFormatAbbreviation(ContainerFormat containerFormat, MediaType mediaType, unsigned int version)
|
||||
{
|
||||
switch(containerFormat) {
|
||||
case ContainerFormat::Ar: return "a";
|
||||
case ContainerFormat::Asf: return "asf";
|
||||
case ContainerFormat::Elf: return "elf";
|
||||
case ContainerFormat::Gif87a:
|
||||
|
@ -273,6 +277,7 @@ const char *containerFormatAbbreviation(ContainerFormat containerFormat, MediaTy
|
|||
case ContainerFormat::Riff: return "riff";
|
||||
case ContainerFormat::RiffWave: return "wav";
|
||||
case ContainerFormat::RiffAvi: return "avi";
|
||||
case ContainerFormat::Tar: return "tar";
|
||||
case ContainerFormat::TiffBigEndian:
|
||||
case ContainerFormat::TiffLittleEndian: return "tiff";
|
||||
case ContainerFormat::WindowsBitmap: return "bmp";
|
||||
|
@ -296,6 +301,8 @@ const char *containerFormatName(ContainerFormat containerFormat)
|
|||
switch(containerFormat) {
|
||||
case ContainerFormat::Adts:
|
||||
return "Audio Data Transport Stream";
|
||||
case ContainerFormat::Ar:
|
||||
return "Archive (GNU ar)";
|
||||
case ContainerFormat::Asf:
|
||||
return "Advanced Systems Format";
|
||||
case ContainerFormat::Elf:
|
||||
|
@ -337,6 +344,8 @@ const char *containerFormatName(ContainerFormat containerFormat)
|
|||
return "RIFF/WAVE";
|
||||
case ContainerFormat::RiffAvi:
|
||||
return "RIFF/Audio Video Interleave";
|
||||
case ContainerFormat::Tar:
|
||||
return "TAR archive";
|
||||
case ContainerFormat::TiffBigEndian:
|
||||
case ContainerFormat::TiffLittleEndian:
|
||||
return "Tagged Image File Format";
|
||||
|
@ -356,10 +365,10 @@ const char *containerFormatName(ContainerFormat containerFormat)
|
|||
return "gzip compressed file";
|
||||
case ContainerFormat::Lzip:
|
||||
return "lzip compressed file";
|
||||
case ContainerFormat::Zip:
|
||||
return "ZIP archive";
|
||||
case ContainerFormat::SevenZ:
|
||||
return "7z archive";
|
||||
case ContainerFormat::Zip:
|
||||
return "ZIP archive";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ enum class ContainerFormat
|
|||
{
|
||||
Unknown, /**< unknown container format */
|
||||
Adts, /** < Audio Data Transport Stream */
|
||||
Ar, /** < "GNU ar" archive */
|
||||
Asf, /**< Advanced Systems Format */
|
||||
Bzip2, /** bzip2 compressed file */
|
||||
Elf, /**< Executable and Linkable Format */
|
||||
|
@ -39,6 +40,7 @@ enum class ContainerFormat
|
|||
Riff, /**< Resource Interchange File Format */
|
||||
RiffWave, /**< WAVE (subset of RIFF) */
|
||||
RiffAvi, /**< Audio Video Interleave (subset of RIFF) */
|
||||
Tar, /** < Tar archive */
|
||||
TiffBigEndian, /**< Tagged Image File Format (big endian) */
|
||||
TiffLittleEndian, /**< Tagged Image File Format (little endian) */
|
||||
Utf16Text, /**< UTF-16 text */
|
||||
|
|
|
@ -4,7 +4,7 @@ appname = "Tag Parser"
|
|||
appauthor = Martchus
|
||||
appurl = "https://github.com/$${appauthor}/$${projectname}"
|
||||
QMAKE_TARGET_DESCRIPTION = "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags."
|
||||
VERSION = 4.0.2
|
||||
VERSION = 5.0.0
|
||||
|
||||
# include ../../common.pri when building as part of a subdirs project; otherwise include general.pri
|
||||
!include(../../common.pri) {
|
||||
|
@ -30,6 +30,9 @@ forcefullparsedefault {
|
|||
HEADERS += \
|
||||
abstractcontainer.h \
|
||||
abstracttrack.h \
|
||||
aspectratio.h \
|
||||
avc/avcinfo.h \
|
||||
avc/avcconfiguration.h \
|
||||
backuphelper.h \
|
||||
basicfileinfo.h \
|
||||
exceptions.h \
|
||||
|
@ -78,6 +81,7 @@ HEADERS += \
|
|||
ogg/oggcontainer.h \
|
||||
vorbis/vorbispackagetypes.h \
|
||||
vorbis/vorbisidentificationheader.h \
|
||||
opus/opusidentificationheader.h \
|
||||
ogg/oggiterator.h \
|
||||
vorbis/vorbiscommentids.h \
|
||||
abstractchapter.h \
|
||||
|
@ -97,6 +101,9 @@ HEADERS += \
|
|||
SOURCES += \
|
||||
abstractcontainer.cpp \
|
||||
abstracttrack.cpp \
|
||||
aspectratio.cpp \
|
||||
avc/avcinfo.cpp \
|
||||
avc/avcconfiguration.cpp
|
||||
backuphelper.cpp \
|
||||
basicfileinfo.cpp \
|
||||
exceptions.cpp \
|
||||
|
@ -136,6 +143,7 @@ SOURCES += \
|
|||
ogg/oggstream.cpp \
|
||||
ogg/oggcontainer.cpp \
|
||||
vorbis/vorbisidentificationheader.cpp \
|
||||
opus/opusidentificationheader.cpp \
|
||||
ogg/oggiterator.cpp \
|
||||
vorbis/vorbiscommentids.cpp \
|
||||
abstractchapter.cpp \
|
||||
|
@ -152,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 += \
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
#include <c++utilities/tests/cppunit.h>
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
|
@ -64,6 +64,7 @@ string VorbisComment::fieldId(KnownField field) const
|
|||
case KnownField::Description: return description();
|
||||
case KnownField::RecordLabel: return label();
|
||||
case KnownField::Performers: return performer();
|
||||
case KnownField::Language: return language();
|
||||
case KnownField::Lyricist: return lyricist();
|
||||
default: return string();
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ KnownField VorbisComment::knownField(const string &id) const
|
|||
* \throws Throws Media::Failure or a derived exception when a parsing
|
||||
* error occurs.
|
||||
*/
|
||||
void VorbisComment::parse(OggIterator &iterator)
|
||||
void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
|
||||
{
|
||||
// prepare parsing
|
||||
invalidateStatus();
|
||||
|
@ -115,15 +116,23 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
try {
|
||||
// read signature: 0x3 + "vorbis"
|
||||
char sig[8];
|
||||
iterator.read(sig, 7);
|
||||
if((ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u) {
|
||||
if(!skipSignature) {
|
||||
iterator.read(sig, 7);
|
||||
skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
|
||||
}
|
||||
if(skipSignature) {
|
||||
// read vendor (length prefixed string)
|
||||
{
|
||||
iterator.read(sig, 4);
|
||||
auto vendorSize = LE::toUInt32(sig);
|
||||
auto buff = make_unique<char []>(vendorSize);
|
||||
iterator.read(buff.get(), vendorSize);
|
||||
m_vendor = string(buff.get(), vendorSize);
|
||||
if(iterator.currentCharacterOffset() + vendorSize <= iterator.streamSize()) {
|
||||
auto buff = make_unique<char []>(vendorSize);
|
||||
iterator.read(buff.get(), vendorSize);
|
||||
m_vendor = string(buff.get(), vendorSize);
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Vendor information is truncated.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
// read field count
|
||||
iterator.read(sig, 4);
|
||||
|
@ -135,10 +144,10 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
try {
|
||||
field.parse(iterator);
|
||||
fields().insert(pair<fieldType::identifierType, fieldType>(fieldId, field));
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
addNotifications(field);
|
||||
throw;
|
||||
} catch(Failure &) {
|
||||
} catch(const Failure &) {
|
||||
// nothing to do here since notifications will be added anyways
|
||||
}
|
||||
addNotifications(field);
|
||||
|
@ -150,7 +159,7 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
addNotification(NotificationType::Critical, "Signature is invalid.", context);
|
||||
throw InvalidDataException();
|
||||
}
|
||||
} catch(TruncatedDataException &) {
|
||||
} catch(const TruncatedDataException &) {
|
||||
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
|
||||
addNotification(NotificationType::Critical, "Vorbis comment is truncated.", context);
|
||||
throw;
|
||||
|
@ -164,7 +173,7 @@ void VorbisComment::parse(OggIterator &iterator)
|
|||
* \throws Throws Media::Failure or a derived exception when a making
|
||||
* error occurs.
|
||||
*/
|
||||
void VorbisComment::make(std::ostream &stream)
|
||||
void VorbisComment::make(std::ostream &stream, bool noSignature)
|
||||
{
|
||||
// prepare making
|
||||
invalidateStatus();
|
||||
|
@ -176,9 +185,11 @@ void VorbisComment::make(std::ostream &stream)
|
|||
addNotification(NotificationType::Warning, "Can not convert the assigned vendor to string.", context);
|
||||
}
|
||||
BinaryWriter writer(&stream);
|
||||
// write signature
|
||||
static const char sig[7] = {0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73};
|
||||
stream.write(sig, sizeof(sig));
|
||||
if(!noSignature) {
|
||||
// write signature
|
||||
static const char sig[7] = {0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73};
|
||||
stream.write(sig, sizeof(sig));
|
||||
}
|
||||
// write vendor
|
||||
writer.writeUInt32LE(vendor.size());
|
||||
writer.writeString(vendor);
|
||||
|
|
|
@ -25,8 +25,8 @@ public:
|
|||
std::string fieldId(KnownField field) const;
|
||||
KnownField knownField(const std::string &id) const;
|
||||
|
||||
void parse(OggIterator &iterator);
|
||||
void make(std::ostream &stream);
|
||||
void parse(OggIterator &iterator, bool skipSignature = false);
|
||||
void make(std::ostream &stream, bool noSignature = false);
|
||||
|
||||
const TagValue &vendor() const;
|
||||
void setVendor(const TagValue &vendor);
|
||||
|
|
|
@ -55,46 +55,51 @@ void VorbisCommentField::parse(OggIterator &iterator)
|
|||
char buff[4];
|
||||
iterator.read(buff, 4);
|
||||
if(auto size = LE::toUInt32(buff)) { // read size
|
||||
// read data
|
||||
auto data = make_unique<char []>(size);
|
||||
iterator.read(data.get(), size);
|
||||
uint32 idSize = 0;
|
||||
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
|
||||
// extract id
|
||||
setId(string(data.get(), idSize));
|
||||
if(!idSize) {
|
||||
// empty field ID
|
||||
addNotification(NotificationType::Critical, "The field ID is empty.", context);
|
||||
throw InvalidDataException();
|
||||
} else if(id() == VorbisCommentIds::cover()) {
|
||||
// extract cover value
|
||||
try {
|
||||
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
|
||||
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
|
||||
ss.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
ss.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
|
||||
BinaryReader reader(&ss);
|
||||
setTypeInfo(reader.readUInt32BE());
|
||||
auto size = reader.readUInt32BE();
|
||||
value().setMimeType(reader.readString(size));
|
||||
size = reader.readUInt32BE();
|
||||
value().setDescription(reader.readString(size));
|
||||
// skip width, height, color depth, number of colors used
|
||||
ss.seekg(4 * 4, ios_base::cur);
|
||||
size = reader.readUInt32BE();
|
||||
auto data = make_unique<char[]>(size);
|
||||
ss.read(data.get(), size);
|
||||
value().assignData(move(data), size, TagDataType::Picture);
|
||||
} catch (const ios_base::failure &) {
|
||||
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
|
||||
throw Failure();
|
||||
} catch (const ConversionException &) {
|
||||
addNotification(NotificationType::Critical, "Base64 data from METADATA_BLOCK_PICTURE is invalid.", context);
|
||||
if(iterator.currentCharacterOffset() + size <= iterator.streamSize()) {
|
||||
// read data
|
||||
auto data = make_unique<char []>(size);
|
||||
iterator.read(data.get(), size);
|
||||
uint32 idSize = 0;
|
||||
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
|
||||
// extract id
|
||||
setId(string(data.get(), idSize));
|
||||
if(!idSize) {
|
||||
// empty field ID
|
||||
addNotification(NotificationType::Critical, "The field ID is empty.", context);
|
||||
throw InvalidDataException();
|
||||
} else if(id() == VorbisCommentIds::cover()) {
|
||||
// extract cover value
|
||||
try {
|
||||
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
|
||||
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
|
||||
ss.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
ss.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
|
||||
BinaryReader reader(&ss);
|
||||
setTypeInfo(reader.readUInt32BE());
|
||||
auto size = reader.readUInt32BE();
|
||||
value().setMimeType(reader.readString(size));
|
||||
size = reader.readUInt32BE();
|
||||
value().setDescription(reader.readString(size));
|
||||
// skip width, height, color depth, number of colors used
|
||||
ss.seekg(4 * 4, ios_base::cur);
|
||||
size = reader.readUInt32BE();
|
||||
auto data = make_unique<char[]>(size);
|
||||
ss.read(data.get(), size);
|
||||
value().assignData(move(data), size, TagDataType::Picture);
|
||||
} catch (const ios_base::failure &) {
|
||||
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
|
||||
throw Failure();
|
||||
} catch (const ConversionException &) {
|
||||
addNotification(NotificationType::Critical, "Base64 data from METADATA_BLOCK_PICTURE is invalid.", context);
|
||||
throw InvalidDataException();
|
||||
}
|
||||
} else if(id().size() + 1 < size) {
|
||||
// extract other values (as string)
|
||||
setValue(string(data.get() + idSize + 1, size - idSize - 1));
|
||||
}
|
||||
} else if(id().size() + 1 < size) {
|
||||
// extract other values (as string)
|
||||
setValue(string(data.get() + idSize + 1, size - idSize - 1));
|
||||
} else {
|
||||
addNotification(NotificationType::Critical, "Field is truncated.", context);
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ inline LIB_EXPORT const char *label() {
|
|||
inline LIB_EXPORT const char *labelNo() {
|
||||
return "LABELNO";
|
||||
}
|
||||
inline LIB_EXPORT const char *language() {
|
||||
return "LANGUAGE";
|
||||
}
|
||||
inline LIB_EXPORT const char *performer() {
|
||||
return "PERFORMER";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue