added detection of ADTS files

This commit is contained in:
Martchus 2015-07-15 00:10:24 +02:00
parent 15a03a0029
commit 1f4800c9be
14 changed files with 249 additions and 97 deletions

View File

@ -32,7 +32,8 @@ enum class TrackType
MpegAudioFrameStream, /**< The track is a Media::MpegAudioFrameStream. */
Mp4Track, /**< The track is a Media::Mp4Track. */
WaveAudioStream, /**< The track is a Media::WaveAudioStream. */
OggStream /**< The track is a Media::OggStream. */
OggStream, /**< The track is a Media::OggStream. */
AdtsStream /**< The track is a Media::AdtsStream. */
};
class LIB_EXPORT AbstractTrack : public StatusProvider

40
adts/adtsstream.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "adtsstream.h"
#include "../mp4/mp4ids.h"
#include "../exceptions.h"
#include <string>
using namespace std;
namespace Media {
/*!
* \class Media::AdtsStream
* \brief Implementation of Media::AbstractTrack for ADTS streams.
*/
void AdtsStream::internalParseHeader()
{
//static const string context("parsing ADTS frame header");
if(!m_istream) {
throw NoDataFoundException();
}
// get size
m_istream->seekg(-128, ios_base::end);
if(m_reader.readUInt24BE() == 0x544147) {
m_size = static_cast<uint64>(m_istream->tellg()) - 3u - m_startOffset;
} else {
m_size = static_cast<uint64>(m_istream->tellg()) + 125u - m_startOffset;
}
m_istream->seekg(m_startOffset, ios_base::beg);
// parse frame header
m_firstFrame.parseHeader(m_reader);
m_format = Mpeg4AudioObjectIds::idToMediaFormat(m_firstFrame.mpeg4AudioObjectId());
m_channelCount = Mpeg4ChannelConfigs::channelCount(m_firstFrame.mpeg4ChannelConfig());
byte sampleRateIndex = m_firstFrame.mpeg4SampleRateIndex();
m_sampleRate = sampleRateIndex < sizeof(mpeg4SampleRateTable) ? mpeg4SampleRateTable[sampleRateIndex] : 0;
}
} // namespace Media

43
adts/adtsstream.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef MEDIA_ADTSSTREAM_H
#define MEDIA_ADTSSTREAM_H
#include "adtsframe.h"
#include "../abstracttrack.h"
namespace Media {
class LIB_EXPORT AdtsStream : public AbstractTrack
{
public:
AdtsStream(std::iostream &stream, uint64 startOffset);
~AdtsStream();
TrackType type() const;
protected:
void internalParseHeader();
private:
AdtsFrame m_firstFrame;
};
/*!
* \brief Constructs a new track for the \a stream at the specified \a startOffset.
*/
inline AdtsStream::AdtsStream(std::iostream &stream, uint64 startOffset) :
AbstractTrack(stream, startOffset)
{
m_mediaType = MediaType::Audio;
}
inline AdtsStream::~AdtsStream()
{}
inline TrackType AdtsStream::type() const
{
return TrackType::AdtsStream;
}
} // namespace Media
#endif // MEDIA_ADTSSTREAM_H

View File

@ -5,8 +5,8 @@
#include "id3/id3v1tag.h"
#include "id3/id3v2tag.h"
#include "wav/waveaudiostream.h"
#include "mpegaudio/mpegaudioframe.h"
#include "mpegaudio/mpegaudioframestream.h"
#include "adts/adtsstream.h"
#include "mp4/mp4container.h"
#include "mp4/mp4atom.h"
#include "mp4/mp4tag.h"
@ -131,7 +131,7 @@ startParsingSignature:
stream().seekg(m_containerOffset, ios_base::beg);
stream().read(buff, sizeof(buff));
// skip zero bytes/padding
if(buff[0] == 0 && buff[1] == 0 && buff[2] == 0 && buff[3] == 0) {
if(!(*reinterpret_cast<uint32 *>(buff))) {
// give up after 0x100 bytes
if(m_paddingSize >= 0x100u) {
m_containerParsed = true;
@ -143,11 +143,10 @@ startParsingSignature:
goto startParsingSignature; // read signature again
}
if(m_paddingSize) {
addNotification(NotificationType::Warning, ConversionUtilities::numberToString(m_paddingSize) + " empty bytes skipped.", context);
addNotification(NotificationType::Warning, ConversionUtilities::numberToString(m_paddingSize) + " zero-bytes skipped at the beginning of the file.", context);
}
// parse signature
m_containerFormat = parseSignature(buff, sizeof(buff));
switch(m_containerFormat) {
switch(m_containerFormat = parseSignature(buff, sizeof(buff))) {
case ContainerFormat::Id2v2Tag:
m_actualId3v2TagOffsets.push_back(m_containerOffset);
if(m_actualId3v2TagOffsets.size() == 2) {
@ -233,16 +232,18 @@ void MediaFileInfo::parseTracks()
} else {
switch(m_containerFormat) {
case ContainerFormat::RiffWave:
m_waveAudioStream = make_unique<WaveAudioStream>(stream(), m_containerOffset);
m_waveAudioStream->parseHeader();
m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
break;
case ContainerFormat::MpegAudioFrames:
m_mpegAudioFrameStream = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
m_mpegAudioFrameStream->parseHeader();
m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
break;
case ContainerFormat::Adts:
m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
break;
default:
throw NotImplementedException();
}
m_singleTrack->parseHeader();
}
} catch (NotImplementedException &) {
addNotification(NotificationType::Information, "Parsing tracks is not implemented for the container format of the file.", context);
@ -339,28 +340,32 @@ void MediaFileInfo::parseTags()
void MediaFileInfo::parseChapters()
{
static const string context("parsing chapters");
if(m_container) {
try {
try {
if(m_container) {
m_container->parseChapters();
} catch (NotImplementedException &) {
addNotification(NotificationType::Information, "Parsing chapters is not implemented for the container format of the file.", context);
} catch (Failure &) {
addNotification(NotificationType::Critical, "Unable to parse chapters.", context);
} else {
throw NotImplementedException();
}
} catch (NotImplementedException &) {
addNotification(NotificationType::Information, "Parsing chapters is not implemented for the container format of the file.", context);
} catch (Failure &) {
addNotification(NotificationType::Critical, "Unable to parse chapters.", context);
}
}
void MediaFileInfo::parseAttachments()
{
static const string context("parsing attachments");
if(m_container) {
try {
try {
if(m_container) {
m_container->parseAttachments();
} catch (NotImplementedException &) {
addNotification(NotificationType::Information, "Parsing attachments is not implemented for the container format of the file.", context);
} catch (Failure &) {
addNotification(NotificationType::Critical, "Unable to parse attachments.", context);
} else {
throw NotImplementedException();
}
} catch (NotImplementedException &) {
addNotification(NotificationType::Information, "Parsing attachments is not implemented for the container format of the file.", context);
} catch (Failure &) {
addNotification(NotificationType::Critical, "Unable to parse attachments.", context);
}
}
@ -540,8 +545,8 @@ const char *MediaFileInfo::containerFormatAbbreviation() const
mediaType = hasTracksOfType(MediaType::Video) ? MediaType::Video : MediaType::Audio;
break;
case ContainerFormat::MpegAudioFrames:
if(m_mpegAudioFrameStream) {
version = m_mpegAudioFrameStream->format().sub;
if(m_singleTrack) {
version = m_singleTrack->format().sub;
}
break;
default:
@ -627,29 +632,6 @@ const char *MediaFileInfo::mimeType() const
}
}
/*!
* \brief Returns the number of tracks that could be parsed.
*
* parseTracks() needs to be called before. Otherwise this
* method always returns zero.
*
* \sa parseTracks()
*/
size_t MediaFileInfo::trackCount() const
{
size_t c = 0;
if(m_waveAudioStream) {
++c;
}
if(m_mpegAudioFrameStream) {
++c;
}
if(m_container) {
c += m_container->trackCount();
}
return c;
}
/*!
* \brief Returns the tracks for the current file.
*
@ -665,11 +647,8 @@ size_t MediaFileInfo::trackCount() const
vector<AbstractTrack *> MediaFileInfo::tracks() const
{
vector<AbstractTrack *> res;
if(m_waveAudioStream) {
res.push_back(m_waveAudioStream.get());
}
if(m_mpegAudioFrameStream) {
res.push_back(m_mpegAudioFrameStream.get());
if(m_singleTrack) {
res.push_back(m_singleTrack.get());
}
if(m_container) {
for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
@ -692,7 +671,7 @@ bool MediaFileInfo::hasTracksOfType(MediaType type) const
if(!areTracksParsed()) {
return false;
}
if(type == MediaType::Audio && (m_waveAudioStream || m_mpegAudioFrameStream)) {
if(type == MediaType::Audio && m_singleTrack) {
return true;
} else if(m_container) {
for(size_t i = 0, count = m_container->trackCount(); i < count; ++i) {
@ -1142,8 +1121,7 @@ void MediaFileInfo::clearParsingResults()
m_actualId3v2TagOffsets.clear();
m_actualExistingId3v1Tag = false;
m_container.reset();
m_waveAudioStream.reset();
m_mpegAudioFrameStream.reset();
m_singleTrack.reset();
}
/*!

View File

@ -142,8 +142,7 @@ private:
std::unique_ptr<AbstractContainer> m_container;
// fields related to the tracks
bool m_tracksParsed;
std::unique_ptr<WaveAudioStream> m_waveAudioStream;
std::unique_ptr<MpegAudioFrameStream> m_mpegAudioFrameStream;
std::unique_ptr<AbstractTrack> m_singleTrack;
// fields related to the tag
bool m_tagParsed;
std::unique_ptr<Id3v1Tag> m_id3v1Tag;
@ -203,6 +202,19 @@ inline bool MediaFileInfo::areTracksParsed() const
return m_tracksParsed;
}
/*!
* \brief Returns the number of tracks that could be parsed.
*
* parseTracks() needs to be called before. Otherwise this
* method always returns zero.
*
* \sa parseTracks()
*/
inline size_t MediaFileInfo::trackCount() const
{
return m_singleTrack ? 1 : 0 + m_container->trackCount();
}
/*!
* \brief Returns whether the chapters have been parsed yet.
*/

View File

@ -223,7 +223,7 @@ const char *streamTypeName(byte streamTypeId)
}
/*!
* \brief Encapsulates all supported MPEG-4 audio object format IDs.
* \brief Encapsulates all supported MPEG-4 audio object type IDs.
* \sa http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio
*/
namespace Mpeg4AudioObjectIds {
@ -279,6 +279,61 @@ LIB_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent,
}
uint32 mpeg4SampleRateTable[] = {
96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350
};
namespace Mpeg4ChannelConfigs {
const char *channelConfigString(byte config)
{
switch(config) {
case AotSpecificConfig:
return "defined in AOT Specific Config";
case FrontCenter:
return "1 channel: front-center";
case FrontLeftFrontRight:
return "2 channels: front-left, front-right";
case FrontCenterFrontLeftFrontRight:
return "3 channels: front-center, front-left, front-right";
case FrontCenterFrontLeftFrontRightBackCenter:
return "4 channels: front-center, front-left, front-right, back-center";
case FrontCenterFrontLeftFrontRightBackLeftBackRight:
return "5 channels: front-center, front-left, front-right, back-left, back-right";
case FrontCenterFrontLeftFrontRightBackLeftBackRightLFEChannel:
return "6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel";
case FrontCenterFrontLeftFrontRightSideLeftSideRightBackLeftBackRightLFEChannel:
return "8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel";
default:
return "unknown";
}
}
byte channelCount(byte config)
{
switch(config) {
case FrontCenter:
return 1;
case FrontLeftFrontRight:
return 2;
case FrontCenterFrontLeftFrontRight:
return 3;
case FrontCenterFrontLeftFrontRightBackCenter:
return 4;
case FrontCenterFrontLeftFrontRightBackLeftBackRight:
return 5;
case FrontCenterFrontLeftFrontRightBackLeftBackRightLFEChannel:
return 6;
case FrontCenterFrontLeftFrontRightSideLeftSideRightBackLeftBackRightLFEChannel:
return 8;
default:
return 0;
}
}
}
/*!
* \brief Encapsulates MPEG-4 video (14496-2) codes.
*/

View File

@ -585,6 +585,26 @@ LIB_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent
}
extern uint32 mpeg4SampleRateTable[13];
namespace Mpeg4ChannelConfigs {
enum Mpeg4ChannelConfig : byte
{
AotSpecificConfig = 0,
FrontCenter,
FrontLeftFrontRight,
FrontCenterFrontLeftFrontRight,
FrontCenterFrontLeftFrontRightBackCenter,
FrontCenterFrontLeftFrontRightBackLeftBackRight,
FrontCenterFrontLeftFrontRightBackLeftBackRightLFEChannel,
FrontCenterFrontLeftFrontRightSideLeftSideRightBackLeftBackRightLFEChannel
};
LIB_EXPORT const char *channelConfigString(byte config);
LIB_EXPORT byte channelCount(byte config);
}
namespace Mpeg4VideoCodes {
enum KnownValue : byte {
VideoObjectStart = 0x00,

View File

@ -26,10 +26,6 @@ namespace Media {
DateTime startDate = DateTime::fromDate(1904, 1, 1);
uint32 sampleRateTable[] = {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
};
MediaFormat fmtTable[] = {
GeneralMediaFormat::Unknown,
MediaFormat(GeneralMediaFormat::Aac, SubFormats::AacMpeg4MainProfile),
@ -1369,15 +1365,15 @@ void Mp4Track::internalParseHeader()
m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType, m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
if(m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
m_sampleRate = m_esInfo->audioSpecificConfig->sampleFrequency;
} else if(m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(sampleRateTable)) {
m_sampleRate = sampleRateTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
} else if(m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SampleRateTable)) {
m_sampleRate = mpeg4SampleRateTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
} else {
addNotification(NotificationType::Warning, "Audio specific config has invalid sample frequency index.", context);
}
if(m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
m_extensionSampleRate = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
} else if(m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(sampleRateTable)) {
m_extensionSampleRate = sampleRateTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
} else if(m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SampleRateTable)) {
m_extensionSampleRate = mpeg4SampleRateTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
} else {
addNotification(NotificationType::Warning, "Audio specific config has invalid extension sample frequency index.", context);
}
@ -1397,7 +1393,7 @@ void Mp4Track::internalParseHeader()
MpegAudioFrame frame;
m_istream->seekg(m_stcoAtom->dataOffset() + 8);
m_istream->seekg(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE());
frame.parseHeader(*m_istream);
frame.parseHeader(reader);
MpegAudioFrameStream::addInfo(frame, *this);
break;
} default:

View File

@ -28,15 +28,14 @@ const int MpegAudioFrame::m_bitrateTable[0x2][0x3][0xF] =
const uint32 MpegAudioFrame::m_sync = 0xFFE00000u;
/*!
* \brief Parses the header read from the specified \a stream at the current position.
* \brief Parses the header read using the specified \a reader.
* \throws Throws InvalidDataException if the data read from the stream is
* no valid frame header.
*/
void MpegAudioFrame::parseHeader(istream &stream)
void MpegAudioFrame::parseHeader(BinaryReader &reader)
{
BinaryReader reader(&stream);
m_header = reader.readUInt32BE();
stream.seekg(m_xingHeaderOffset - 4, ios_base::cur);
reader.stream()->seekg(m_xingHeaderOffset - 4, ios_base::cur);
m_xingHeader = reader.readUInt64BE();
m_xingHeaderFlags = static_cast<XingHeaderFlags>(m_xingHeader & 0xffffffffuL);
if(isXingHeaderAvailable()) {
@ -47,7 +46,7 @@ void MpegAudioFrame::parseHeader(istream &stream)
m_xingBytesfield = reader.readUInt32BE();
}
if(isXingTocFieldPresent()) {
stream.seekg(64, ios_base::cur);
reader.stream()->seekg(64, ios_base::cur);
}
if(isXingQualityIndicatorFieldPresent()) {
m_xingQualityIndicator = reader.readUInt32BE();

View File

@ -6,6 +6,10 @@
#include <iostream>
namespace IoUtilities {
class BinaryReader;
}
namespace Media
{
@ -35,7 +39,7 @@ class LIB_EXPORT MpegAudioFrame
public:
MpegAudioFrame();
void parseHeader(std::istream &stream);
void parseHeader(IoUtilities::BinaryReader &reader);
bool isValid() const;
double mpegVersion() const;

View File

@ -14,30 +14,9 @@ namespace Media {
/*!
* \class Media::MpegAudioFrameStream
* \brief Implementation of Media::AbstractTrack for a stream of
* MPEG audio frames (run-of-the-mill MP3 file).
* \brief Implementation of Media::AbstractTrack MPEG audio streams.
*/
/*!
* \brief Constructs a new track for the \a stream at the specified \a startOffset.
*/
MpegAudioFrameStream::MpegAudioFrameStream(iostream &stream, uint64 startOffset) :
AbstractTrack(stream, startOffset)
{
m_mediaType = MediaType::Audio;
}
/*!
* \brief Destroys the track.
*/
MpegAudioFrameStream::~MpegAudioFrameStream()
{}
TrackType MpegAudioFrameStream::type() const
{
return TrackType::MpegAudioFrameStream;
}
/*!
* \brief Adds the information from the specified \a frame to the specified \a track.
*/
@ -66,7 +45,7 @@ void MpegAudioFrameStream::internalParseHeader()
// parse frame header
m_frames.emplace_back();
MpegAudioFrame &frame = m_frames.back();
frame.parseHeader(*m_istream);
frame.parseHeader(m_reader);
addInfo(frame, *this);
if(frame.isXingBytesfieldPresent()) {
uint32 xingSize = frame.xingBytesfield();

View File

@ -26,6 +26,23 @@ private:
std::list<MpegAudioFrame> m_frames;
};
/*!
* \brief Constructs a new track for the \a stream at the specified \a startOffset.
*/
inline MpegAudioFrameStream::MpegAudioFrameStream(std::iostream &stream, uint64 startOffset) :
AbstractTrack(stream, startOffset)
{
m_mediaType = MediaType::Audio;
}
inline MpegAudioFrameStream::~MpegAudioFrameStream()
{}
inline TrackType MpegAudioFrameStream::type() const
{
return TrackType::MpegAudioFrameStream;
}
}
#endif // MPEGAUDIOFRAMESTREAM_H

View File

@ -66,7 +66,7 @@ enum Sig24 : uint32
Bzip2 = 0x425A68u,
Gzip = 0x1F8B08u,
Id3v2 = 0x494433u,
Utf8Text = 0xEFBBBFu
Utf8Text = 0xEFBBBFu,
};
/*!
@ -74,6 +74,8 @@ enum Sig24 : uint32
*/
enum Sig16 : uint16
{
Adts = 0xFFF0u,
AdtsMask = 0xFFF6u,
Jpeg = 0xffd8u,
Lha = 0x1FA0u,
Lzw = 0x1F9Du,
@ -202,6 +204,9 @@ ContainerFormat parseSignature(const char *buffer, int bufferSize)
default:
;
}
if(((sig >> 48) & AdtsMask) == Adts) {
return ContainerFormat::Adts;
}
return ContainerFormat::Unknown;
}
@ -282,6 +287,8 @@ const char *containerFormatAbbreviation(ContainerFormat containerFormat, MediaTy
const char *containerFormatName(ContainerFormat containerFormat)
{
switch(containerFormat) {
case ContainerFormat::Adts:
return "Audio Data Transport Stream";
case ContainerFormat::Asf:
return "Advanced Systems Format";
case ContainerFormat::Elf:

View File

@ -14,6 +14,7 @@ namespace Media {
enum class ContainerFormat
{
Unknown, /**< unknown container format */
Adts, /** < Audio Data Transport Stream */
Asf, /**< Advanced Systems Format */
Bzip2, /** bzip2 compressed file */
Elf, /**< Executable and Linkable Format */