Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ void SESSION::CSession::UpdateStream(CStream& stream)
stream.m_info.SetCodecName(CODEC::NAME_DTS);
else if (CODEC::Contains(codecs, CODEC::FOURCC_AC_3, codecStr))
stream.m_info.SetCodecName(CODEC::NAME_AC3);
else if (CODEC::Contains(codecs, CODEC::FOURCC_AC_4, codecStr))
stream.m_info.SetCodecName(CODEC::NAME_AC4);
else if (CODEC::Contains(codecs, CODEC::FOURCC_EC_3, codecStr))
{
stream.m_info.SetCodecName(CODEC::NAME_EAC3);
Expand Down
57 changes: 53 additions & 4 deletions src/demuxers/ADTSReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,12 @@ ADTSFrame::ADTSFrameInfo ADTSFrame::GetFrameInfo(AP4_ByteStream* stream)
case AdtsType::AC3:
ParseAc3Header(stream, frameInfo);
break;
case AdtsType::AC4:
ParseAc4Header(stream, frameInfo);
break;
case AdtsType::EAC3:
ParseEc3Header(stream, frameInfo);
break;
case AdtsType::AC4:
break;
default:
break;
}
Expand All @@ -157,10 +158,10 @@ bool ADTSFrame::parse(AP4_ByteStream* stream)
return ParseAac(stream);
case AdtsType::AC3:
return ParseAc3(stream);
case AdtsType::AC4:
return ParseAc4(stream);
case AdtsType::EAC3:
return ParseEc3(stream);
case AdtsType::AC4:
return false;
default:
return false;
}
Expand Down Expand Up @@ -255,6 +256,50 @@ bool ADTSFrame::ParseAc3Header(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo)
return true;
}

bool ADTSFrame::ParseAc4(AP4_ByteStream* stream)
{
if (!ParseAc4Header(stream, m_frameInfo))
return false;

m_summedFrameCount += m_frameInfo.m_frameCount;

// rewind stream to beginning of syncframe
AP4_Position currentPos;
stream->Tell(currentPos);
stream->Seek(currentPos - (AP4_AC4_HEADER_SIZE));

m_dataBuffer.SetDataSize(m_frameInfo.m_frameSize);
if (!AP4_SUCCEEDED(stream->Read(m_dataBuffer.UseData(), m_dataBuffer.GetDataSize())))
return false;

AdjustStreamForPadding(stream);
return true;
}

bool ADTSFrame::ParseAc4Header(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo)
{
AP4_DataBuffer buffer;
buffer.SetDataSize(AP4_AC3_HEADER_SIZE);

if (!AP4_SUCCEEDED(stream->Read(buffer.UseData(), AP4_AC3_HEADER_SIZE)))
Comment on lines +282 to +284
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing AC4 headers should use AP4_AC4_HEADER_SIZE instead of AP4_AC3_HEADER_SIZE to allocate the correct buffer size.

Suggested change
buffer.SetDataSize(AP4_AC3_HEADER_SIZE);
if (!AP4_SUCCEEDED(stream->Read(buffer.UseData(), AP4_AC3_HEADER_SIZE)))
buffer.SetDataSize(AP4_AC4_HEADER_SIZE);
if (!AP4_SUCCEEDED(stream->Read(buffer.UseData(), AP4_AC4_HEADER_SIZE)))

Copilot uses AI. Check for mistakes.

return false;

CAdaptiveAc3Parser parser;
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AC4 header parser is using CAdaptiveAc3Parser; this should be CAdaptiveAc4Parser to correctly parse AC4 frames.

Suggested change
CAdaptiveAc3Parser parser;
CAdaptiveAc4Parser parser;

Copilot uses AI. Check for mistakes.

AP4_Size sz = buffer.GetDataSize();
parser.Feed(buffer.GetData(), &sz);

AP4_Ac3Frame frame;
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re instantiating an AP4_Ac3Frame for AC4 parsing; this should be an AP4_Ac4Frame to match the AC4 parser.

Copilot uses AI. Check for mistakes.

AP4_Result result = parser.FindFrameHeader(frame);
if (!AP4_SUCCEEDED(result))
return false;

frameInfo.m_frameSize = frame.m_Info.m_FrameSize;
frameInfo.m_frameCount = 256u * frame.m_Info.m_ChannelCount;
frameInfo.m_sampleRate = frame.m_Info.m_SampleRate;
frameInfo.m_channels = frame.m_Info.m_ChannelCount;
return true;
}

bool ADTSFrame::ParseEc3(AP4_ByteStream* stream)
{
if (!ParseEc3Header(stream, m_frameInfo))
Expand Down Expand Up @@ -379,6 +424,10 @@ bool ADTSReader::GetInformation(kodi::addon::InputstreamInfo& info)
{
codecName = CODEC::NAME_AC3;
}
else if (frameInfo.m_codecType == AdtsType::AC4)
{
codecName = CODEC::NAME_AC4;
}
else if (frameInfo.m_codecType == AdtsType::EAC3)
{
codecName = CODEC::NAME_EAC3;
Expand Down
2 changes: 2 additions & 0 deletions src/demuxers/ADTSReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class ATTR_DLL_LOCAL ADTSFrame
bool ParseAacHeader(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo);
bool ParseAc3(AP4_ByteStream* stream);
bool ParseAc3Header(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo);
bool ParseAc4(AP4_ByteStream* stream);
bool ParseAc4Header(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo);
bool ParseEc3(AP4_ByteStream* stream);
bool ParseEc3Header(AP4_ByteStream* stream, ADTSFrameInfo& frameInfo);
void reset();
Expand Down
169 changes: 169 additions & 0 deletions src/parser/CodecParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,175 @@ AP4_Result CAdaptiveAc3Parser::FindFrameHeader(AP4_Ac3Frame& frame)
return AP4_SUCCESS;
}

AP4_Result CAdaptiveAc4Parser::FindFrameHeader(AP4_Ac4Frame& frame)
{
unsigned int available;
unsigned char raw_header[AP4_AC4_HEADER_SIZE];
AP4_Result result;

/* align to the start of the next byte */
m_Bits.ByteAlign();

/* find a frame header */
result = FindHeader(raw_header);
if (AP4_FAILED(result))
return result;

// duplicated work, just to get the frame size
AP4_BitReader tmp_bits(raw_header, AP4_AC4_HEADER_SIZE);
unsigned int sync_frame_size = GetSyncFrameSize(tmp_bits);
if (sync_frame_size > (AP4_BITSTREAM_BUFFER_SIZE - 1))
{
return AP4_ERROR_NOT_ENOUGH_DATA;
}

/*
* Error handling to skip the 'fake' sync word.
* - the maximum sync frame size is about (AP4_BITSTREAM_BUFFER_SIZE - 1) bytes.
*/
if (m_Bits.GetBytesAvailable() < sync_frame_size)
{
if (m_Bits.GetBytesAvailable() == (AP4_BITSTREAM_BUFFER_SIZE - 1))
{
// skip the sync word, assume it's 'fake' sync word
m_Bits.SkipBytes(2);
}
return AP4_ERROR_NOT_ENOUGH_DATA;
}

unsigned char* rawframe = new unsigned char[sync_frame_size];
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider using a std::vector<unsigned char> or smart pointer instead of manual new[]/delete[] to manage frame buffers.

Copilot uses AI. Check for mistakes.


// copy the whole frame becasue toc size is unknown
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix typo: replace “becasue” with “because”.

Suggested change
// copy the whole frame becasue toc size is unknown
// copy the whole frame because toc size is unknown

Copilot uses AI. Check for mistakes.

m_Bits.PeekBytes(rawframe, sync_frame_size);
/* parse the header */
AP4_Ac4Header ac4_header(rawframe, sync_frame_size);

delete[] rawframe;

// Place before goto statement to resolve Xcode compiler issue
unsigned int bit_rate_mode = 0;

/* check the header */
result = ac4_header.Check();
if (AP4_FAILED(result))
{
m_Bits.SkipBytes(sync_frame_size);
goto fail;
}

/* check if we have enough data to peek at the next header */
available = m_Bits.GetBytesAvailable();
// TODO: find the proper AP4_AC4_MAX_TOC_SIZE or just parse what this step need ?
if (available >= ac4_header.m_FrameSize + ac4_header.m_HeaderSize + ac4_header.m_CrcSize +
AP4_AC4_HEADER_SIZE + AP4_AC4_MAX_TOC_SIZE)
{
// enough to peek at the header of the next frame

m_Bits.SkipBytes(ac4_header.m_FrameSize + ac4_header.m_HeaderSize + ac4_header.m_CrcSize);
m_Bits.PeekBytes(raw_header, AP4_AC4_HEADER_SIZE);

// duplicated work, just to get the frame size
AP4_BitReader peak_tmp_bits(raw_header, AP4_AC4_HEADER_SIZE);
unsigned int next_sync_frame_size = GetSyncFrameSize(peak_tmp_bits);

unsigned char* next_rawframe = new unsigned char[next_sync_frame_size];

// copy the whole frame becasue toc size is unknown
if (m_Bits.GetBytesAvailable() < (next_sync_frame_size))
{
next_sync_frame_size = m_Bits.GetBytesAvailable();
}
m_Bits.PeekBytes(next_rawframe, next_sync_frame_size);

m_Bits.SkipBytes(
-((int)(ac4_header.m_FrameSize + ac4_header.m_HeaderSize + ac4_header.m_CrcSize)));

/* check the header */
AP4_Ac4Header peek_ac4_header(next_rawframe, next_sync_frame_size);

delete[] next_rawframe;

result = peek_ac4_header.Check();
if (AP4_FAILED(result))
{
// TODO: need to reserve current sync frame ?
m_Bits.SkipBytes(sync_frame_size + next_sync_frame_size);
goto fail;
}

/* check that the fixed part of this header is the same as the */
/* fixed part of the previous header */
if (!AP4_Ac4Header::MatchFixed(ac4_header, peek_ac4_header))
{
// TODO: need to reserve current sync frame ?
m_Bits.SkipBytes(sync_frame_size + next_sync_frame_size);
goto fail;
}
}
else if (available < (ac4_header.m_FrameSize + ac4_header.m_HeaderSize + ac4_header.m_CrcSize) ||
(m_Bits.m_Flags & AP4_BITSTREAM_FLAG_EOS) == 0)
{
// not enough for a frame, or not at the end (in which case we'll want to peek at the next header)
return AP4_ERROR_NOT_ENOUGH_DATA;
}

m_Bits.SkipBytes(ac4_header.m_HeaderSize);

/* fill in the frame info */
frame.m_Info.m_HeaderSize = ac4_header.m_HeaderSize;
frame.m_Info.m_FrameSize = ac4_header.m_FrameSize;
frame.m_Info.m_CRCSize = ac4_header.m_CrcSize;
frame.m_Info.m_ChannelCount = ac4_header.m_ChannelCount;
frame.m_Info.m_SampleDuration =
(ac4_header.m_FsIndex == 0) ? 2048 : AP4_Ac4SampleDeltaTable[ac4_header.m_FrameRateIndex];
frame.m_Info.m_MediaTimeScale =
(ac4_header.m_FsIndex == 0) ? 44100 : AP4_Ac4MediaTimeScaleTable[ac4_header.m_FrameRateIndex];
frame.m_Info.m_Iframe = ac4_header.m_BIframeGlobal;

/* fill the AC4 DSI info */
frame.m_Info.m_Ac4Dsi.ac4_dsi_version = 1;
frame.m_Info.m_Ac4Dsi.d.v1.bitstream_version = ac4_header.m_BitstreamVersion;
frame.m_Info.m_Ac4Dsi.d.v1.fs_index = ac4_header.m_FsIndex;
frame.m_Info.m_Ac4Dsi.d.v1.fs =
AP4_Ac4SamplingFrequencyTable[frame.m_Info.m_Ac4Dsi.d.v1.fs_index];
frame.m_Info.m_Ac4Dsi.d.v1.frame_rate_index = ac4_header.m_FrameRateIndex;
frame.m_Info.m_Ac4Dsi.d.v1.b_program_id = ac4_header.m_BProgramId;
frame.m_Info.m_Ac4Dsi.d.v1.short_program_id = ac4_header.m_ShortProgramId;
frame.m_Info.m_Ac4Dsi.d.v1.b_uuid = ac4_header.m_BProgramUuidPresent;
AP4_CopyMemory(frame.m_Info.m_Ac4Dsi.d.v1.program_uuid, ac4_header.m_ProgramUuid, 16);

// Calcuate the bit rate mode according to ETSI TS 103 190-2 V1.2.1 Annex B
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix typo: replace “Calcuate” with “Calculate”.

Suggested change
// Calcuate the bit rate mode according to ETSI TS 103 190-2 V1.2.1 Annex B
// Calculate the bit rate mode according to ETSI TS 103 190-2 V1.2.1 Annex B

Copilot uses AI. Check for mistakes.

if (ac4_header.m_WaitFrames == 0)
{
bit_rate_mode = 1;
}
else if (ac4_header.m_WaitFrames >= 1 && ac4_header.m_WaitFrames <= 6)
{
bit_rate_mode = 2;
}
else if (ac4_header.m_WaitFrames > 6)
{
bit_rate_mode = 3;
}

frame.m_Info.m_Ac4Dsi.d.v1.ac4_bitrate_dsi.bit_rate_mode = bit_rate_mode;
frame.m_Info.m_Ac4Dsi.d.v1.ac4_bitrate_dsi.bit_rate = 0; // unknown, fixed value now
frame.m_Info.m_Ac4Dsi.d.v1.ac4_bitrate_dsi.bit_rate_precision =
0xffffffff; // unknown, fixed value now
frame.m_Info.m_Ac4Dsi.d.v1.n_presentations = ac4_header.m_NPresentations;
frame.m_Info.m_Ac4Dsi.d.v1.presentations = ac4_header.m_PresentationV1;

/* set the frame source */
frame.m_Source = &m_Bits;

return AP4_SUCCESS;

fail:
/* skip the header and return (only skip the first byte in */
/* case this was a false header that hides one just after) */
return AP4_ERROR_CORRUPTED_BITSTREAM;
}

AP4_Result CAdaptiveEac3Parser::FindFrameHeader(AP4_Eac3Frame& frame)
{
bool dependent_stream_exist = false;
Expand Down
8 changes: 8 additions & 0 deletions src/parser/CodecParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

#include <bento4/Ap4Ac3Parser.h>
#include <bento4/Ap4Ac4Parser.h>
#include <bento4/Ap4AdtsParser.h>
#include <bento4/Ap4Eac3Parser.h>
#include <kodi/addon-instance/Inputstream.h>
Expand Down Expand Up @@ -44,6 +45,13 @@ class ATTR_DLL_LOCAL CAdaptiveAc3Parser : public AP4_Ac3Parser
AP4_Result FindFrameHeader(AP4_Ac3Frame& frame);
};

class ATTR_DLL_LOCAL CAdaptiveAc4Parser : public AP4_Ac4Parser
{
public:
CAdaptiveAc4Parser() {}
AP4_Result FindFrameHeader(AP4_Ac4Frame& frame);
Copy link
Preview

Copilot AI May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Mark this method as override to clarify that it overrides the base class implementation.

Suggested change
AP4_Result FindFrameHeader(AP4_Ac4Frame& frame);
AP4_Result FindFrameHeader(AP4_Ac4Frame& frame) override;

Copilot uses AI. Check for mistakes.

};

class ATTR_DLL_LOCAL CAdaptiveEac3Parser : public AP4_Eac3Parser
{
public:
Expand Down
12 changes: 7 additions & 5 deletions src/utils/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ constexpr std::array VIDEO_NAME_LIST = {NAME_MPEG1, NAME_MPEG2, NAME_MPEG4, NAME
constexpr const char* NAME_AAC = "aac";
constexpr const char* NAME_DTS = "dca";
constexpr const char* NAME_AC3 = "ac3";
constexpr const char* NAME_AC4 = "ac4";
constexpr const char* NAME_EAC3 = "eac3"; // Enhanced AC-3
constexpr const char* NAME_OPUS = "opus";
constexpr const char* NAME_VORBIS = "vorbis";

constexpr std::array AUDIO_NAME_LIST = {NAME_AAC, NAME_DTS, NAME_AC3,
constexpr std::array AUDIO_NAME_LIST = {NAME_AAC, NAME_DTS, NAME_AC3, NAME_AC4,
NAME_EAC3, NAME_OPUS, NAME_VORBIS};

// Subtitles definitions
Expand Down Expand Up @@ -132,6 +133,7 @@ constexpr const char* FOURCC_MP4A = "mp4a";
constexpr const char* FOURCC_AAC_ = "aac"; // Generic prefix for all aac* fourcc, e.g. aac, aacl...
constexpr const char* FOURCC_AACL = "aacl";
constexpr const char* FOURCC_AC_3 = "ac-3";
constexpr const char* FOURCC_AC_4 = "ac-4";
constexpr const char* FOURCC_EC_3 = "ec-3"; // Enhanced AC-3
constexpr const char* FOURCC_OPUS = "opus";
constexpr const char* FOURCC_VORB = "vorb"; // Vorbis
Expand All @@ -143,10 +145,10 @@ constexpr const char* FOURCC_VORB3 = "vor3"; // Vorbis 3
constexpr const char* FOURCC_VORB3P = "vo3+"; // Vorbis 3+
constexpr const char* FOURCC_DTS_ = "dts"; // Generic prefix for all dts* fourcc, e.g. dtsx

constexpr std::array AUDIO_FOURCC_LIST = {FOURCC_MP4A, FOURCC_AAC_, FOURCC_AACL,
FOURCC_AC_3, FOURCC_EC_3, FOURCC_OPUS, FOURCC_VORB, FOURCC_VORB1,
FOURCC_VORB1P, FOURCC_VORB2, FOURCC_VORB2P, FOURCC_VORB3,
FOURCC_VORB3P, FOURCC_DTS_};
constexpr std::array AUDIO_FOURCC_LIST = {FOURCC_MP4A, FOURCC_AAC_, FOURCC_AACL, FOURCC_AC_3,
FOURCC_AC_4, FOURCC_EC_3, FOURCC_OPUS, FOURCC_VORB,
FOURCC_VORB1, FOURCC_VORB1P, FOURCC_VORB2, FOURCC_VORB2P,
FOURCC_VORB3, FOURCC_VORB3P, FOURCC_DTS_};

// Fourcc subtitles definitions

Expand Down