Skip to content

Commit 0ce8215

Browse files
committed
Refactor PES parser
1 parent f6c521c commit 0ce8215

5 files changed

Lines changed: 310 additions & 78 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ override CFLAGS += $(_CFLAGS) $(DEFINES) $(INCLUDES) \
128128
### The object files (add further files here):
129129

130130
OBJS = $(PLUGIN).o audio.o buf2rgb.o codec_audio.o codec_video.o config.o drmbuffer.o drmdevice.o drmplane.o grab.o h264parser.o \
131-
logger.o mediaplayer.o queue.o ringbuffer.o softhddevice.o softhdmenu.o softhdosd.o threads.o videorender.o videostream.o
131+
logger.o mediaplayer.o pes.o queue.o ringbuffer.o softhddevice.o softhdmenu.o softhdosd.o threads.o videorender.o videostream.o
132132

133133
ifeq ($(GLES),1)
134134
OBJS += openglosd.o

pes.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @file pes.cpp
3+
* PES packet parser implementation
4+
*
5+
* @license{AGPLv3
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.}
16+
*/
17+
18+
#include "pes.h"
19+
20+
#include "vdr/remux.h"
21+
22+
extern "C"
23+
{
24+
#include <libavutil/avutil.h>
25+
}
26+
27+
/**
28+
* Construct a PES packet parser
29+
*
30+
* @param data Pointer to the raw PES packet data
31+
* @param size Size of the PES packet in bytes
32+
*/
33+
cPes::cPes(const uint8_t *data, int size)
34+
: m_data(data), m_size(size)
35+
{
36+
}
37+
38+
/**
39+
* Parse the PES packet to detect the video codec
40+
*
41+
* Analyzes the PES payload to identify the codec type (MPEG2, H.264, or HEVC).
42+
* This method looks for codec-specific start codes and stream type identifiers
43+
* in the payload. The detected codec can be retrieved using GetCodec().
44+
*
45+
* Supported codecs:
46+
* - MPEG2 (AV_CODEC_ID_MPEG2VIDEO)
47+
* - H.264 (AV_CODEC_ID_H264)
48+
* - HEVC (AV_CODEC_ID_HEVC)
49+
*
50+
* @note This method must be called before GetCodec()
51+
*/
52+
void cPes::Parse() {
53+
int pesPayloadStart = PesPayloadOffset(m_data);
54+
55+
if (pesPayloadStart + START_CODE_PREFIX_LEN + 1 >= (unsigned int)m_size)
56+
return;
57+
58+
uint32_t firstThreePesPayloadBytes = ReadBytes(pesPayloadStart, START_CODE_PREFIX_LEN);
59+
const uint8_t *codecPayload = &m_data[pesPayloadStart + START_CODE_PREFIX_LEN];
60+
61+
// Looking for the MPEG2 start code and stream type
62+
if (firstThreePesPayloadBytes == START_CODE_PREFIX && codecPayload[0] == MPEG2_STREAM_TYPE)
63+
m_codec = AV_CODEC_ID_MPEG2VIDEO;
64+
// Looking for the H.264/HEVC start code. It can have an optional leading zero byte.
65+
else if (ReadBytes(pesPayloadStart + 1, START_CODE_PREFIX_LEN) == START_CODE_PREFIX) {
66+
codecPayload++;
67+
m_payloadHasLeadingZero = true;
68+
} else if (firstThreePesPayloadBytes != START_CODE_PREFIX)
69+
return; // no start code found
70+
71+
if (m_size > &codecPayload[7] - m_data) {
72+
if ( codecPayload[0] == H264_STREAM_TYPE && (codecPayload[1] == 0x10 || codecPayload[1] == 0xF0 || codecPayload[7] == 0x64))
73+
m_codec = AV_CODEC_ID_H264;
74+
else if (codecPayload[0] == HEVC_STREAM_TYPE && (codecPayload[1] == 0x10 || codecPayload[1] == 0x50 || codecPayload[7] == 0x40))
75+
m_codec = AV_CODEC_ID_HEVC;
76+
}
77+
}
78+
79+
uint32_t cPes::ReadBytes(int offset, int count)
80+
{
81+
uint32_t value = 0;
82+
83+
for (int i = 0; i < count; i++) {
84+
value <<= 8;
85+
value |= m_data[offset + i];
86+
}
87+
88+
return value;
89+
}
90+
91+
/**
92+
* Check if the PES header is valid
93+
*
94+
* Validates that the PES packet has a valid header by checking:
95+
* - The packet is long enough to contain a header
96+
* - The start code prefix (0x000001) is present
97+
*
98+
* @return true if the header is valid, false otherwise
99+
*/
100+
bool cPes::IsHeaderValid()
101+
{
102+
return PesLongEnough(m_size) && ReadBytes(0, 3) == START_CODE_PREFIX;
103+
}
104+
105+
/**
106+
* Check if this is a video stream
107+
*
108+
* Determines if the PES packet contains a video stream based on the stream ID.
109+
* Video streams have stream IDs in the range 0xE0-0xEF according to
110+
* H.222.0 03/2017 Table 2-22.
111+
*
112+
* @return true if this is a video stream, false otherwise
113+
*/
114+
bool cPes::IsVideoStream()
115+
{
116+
// The low nibble is the stream number
117+
return (GetStreamId() & 0xF0) == 0xE0;
118+
}
119+
120+
/**
121+
* Check if this is an audio stream
122+
*
123+
* Determines if the PES packet contains an audio stream based on the stream ID.
124+
* Audio streams have stream IDs in the range 0xC0-0xCF according to
125+
* H.222.0 03/2017 Table 2-22.
126+
*
127+
* @return true if this is an audio stream, false otherwise
128+
*/
129+
bool cPes::IsAudioStream()
130+
{
131+
// The low nibble is the stream number
132+
return (GetStreamId() & 0xF0) == 0xC0;
133+
}
134+
135+
/**
136+
* Get the Presentation Time Stamp (PTS) from the PES header
137+
*
138+
* Extracts the PTS value from the PES packet header if present.
139+
* The PTS indicates when the decoded content should be presented.
140+
*
141+
* @return The PTS value in 90 kHz units, or AV_NOPTS_VALUE if no PTS is present
142+
*/
143+
int64_t cPes::GetPts()
144+
{
145+
if (!PesHasPts(m_data))
146+
return AV_NOPTS_VALUE;
147+
148+
return PesGetPts(m_data);
149+
}
150+
151+
/**
152+
* Get a pointer to the PES payload data
153+
*
154+
* Returns a pointer to the start of the payload data, skipping the PES header.
155+
* For H.264/HEVC streams with a leading zero byte, the leading zero is also skipped.
156+
*
157+
* @return Pointer to the payload data
158+
*/
159+
const uint8_t *cPes::GetPayload()
160+
{
161+
int headerLen = PesPayloadOffset(m_data);
162+
163+
if (m_payloadHasLeadingZero)
164+
headerLen++; // Skip the leading zero byte for H.264/HEVC
165+
166+
return &m_data[headerLen];
167+
}
168+
169+
/**
170+
* Get the size of the PES payload
171+
*
172+
* Calculates the size of the payload by subtracting the header size
173+
* (and optional leading zero for H.264/HEVC) from the total packet size.
174+
*
175+
* @return Size of the payload in bytes
176+
*/
177+
int cPes::GetPayloadSize()
178+
{
179+
return m_size - (GetPayload() - m_data);
180+
}

pes.h

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* @file pes.h
3+
* PES packet parser header
4+
*
5+
* @copyright (c) 2025 by Andreas Baierl. All Rights Reserved.
6+
*
7+
* @license{AGPLv3
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.}
18+
*/
19+
20+
#ifndef __SOFTHDDEVICE_PES_H
21+
#define __SOFTHDDEVICE_PES_H
22+
23+
#include <stdint.h>
24+
#include <stddef.h>
25+
26+
extern "C"
27+
{
28+
#include <libavcodec/avcodec.h>
29+
}
30+
31+
/**
32+
* @brief Convert AVCodecID to a human-readable string
33+
*
34+
* @param c The codec ID to convert
35+
* @return A string representation of the codec ID
36+
*/
37+
constexpr const char* to_string(AVCodecID c) {
38+
switch (c) {
39+
case AV_CODEC_ID_NONE: return "None";
40+
case AV_CODEC_ID_MPEG2VIDEO: return "MPEG2";
41+
case AV_CODEC_ID_H264: return "H.264";
42+
case AV_CODEC_ID_HEVC: return "HEVC";
43+
default: return "Unknown codec";
44+
}
45+
}
46+
47+
/**
48+
* PES packet parser class
49+
*
50+
* This class parses PES (Packetized Elementary Stream) packets
51+
* to extract header information, video codec, PTS, and payload data.
52+
*/
53+
class cPes
54+
{
55+
public:
56+
cPes(const uint8_t *data, int size);
57+
void Parse();
58+
bool IsHeaderValid();
59+
bool IsVideoStream();
60+
bool IsAudioStream();
61+
AVCodecID GetCodec() { return m_codec; }
62+
int64_t GetPts();
63+
const uint8_t *GetPayload();
64+
int GetPayloadSize();
65+
66+
private:
67+
uint32_t ReadBytes(int offset, int count);
68+
uint8_t GetStreamId() { return m_data[3]; }
69+
AVCodecID ParseCodec();
70+
71+
// According to H.222.0 03/2017 Table 2-21 packet_start_code_prefix
72+
// And also according to H.264/HEVC payload
73+
static constexpr uint32_t START_CODE_PREFIX = 0x00'0001;
74+
static constexpr uint32_t START_CODE_PREFIX_LEN = 3;
75+
76+
static constexpr uint8_t MPEG2_STREAM_TYPE = 0xB3;
77+
static constexpr uint8_t H264_STREAM_TYPE = 0x09;
78+
static constexpr uint8_t HEVC_STREAM_TYPE = 0x46;
79+
80+
const uint8_t *m_data; ///< pointer to the raw PES packet data
81+
int m_size; ///< size of the PES packet
82+
bool m_payloadHasLeadingZero = false; ///< flag indicating if codec payload has leading zero byte for H.264/HEVC
83+
AVCodecID m_codec = AV_CODEC_ID_NONE; ///< detected codec ID
84+
};
85+
86+
#endif

0 commit comments

Comments
 (0)