Skip to content

Commit 966657b

Browse files
fwolterrellla
authored andcommitted
Handle multiple PES packets in StillPicture()
1 parent 90d7dc0 commit 966657b

9 files changed

Lines changed: 229 additions & 105 deletions

File tree

pes.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,28 @@ int cPes::GetPayloadSize()
176176
{
177177
return m_size - (GetPayload() - m_data);
178178
}
179+
180+
/**
181+
* Get the total length of the PES packet
182+
*
183+
* Returns the complete size of the PES packet including both header and payload.
184+
* The length is read from the PES packet header (bytes 4-5).
185+
*
186+
* For packets with a specified length field (common for audio):
187+
* - Returns the actual PES packet length from the header
188+
* - Calculated as 6 + length_field (per H.222.0 standard)
189+
* - The length_field specifies bytes after the 6-byte header prefix
190+
* (3 bytes start code + 1 byte stream ID + 2 bytes length field)
191+
*
192+
* For unbounded packets (length field = 0, common for video streams):
193+
* - Returns the input buffer size (m_size)
194+
*
195+
* @return Total size in bytes: actual packet length if specified, otherwise input buffer size
196+
*/
197+
int cPes::GetPacketLength()
198+
{
199+
if (!PesHasLength(m_data))
200+
return m_size; // Length field is 0, meaning unbounded/unspecified. Return raw data size.
201+
202+
return PesLength(m_data);
203+
}

pes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class cPes
5858
int64_t GetPts();
5959
const uint8_t *GetPayload();
6060
int GetPayloadSize();
61+
int GetPacketLength();
6162

6263
protected:
6364
void Init();

softhddevice.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ void cSoftHdDevice::OnEventReceived(const Event& event) {
732732
SetState(TRICK_SPEED);
733733
},
734734
[this](const StillPictureEvent& s) {
735-
m_pVideoStream->StillPicture(&s.pesPacket);
735+
HandleStillPicture(s.data, s.size);
736736
},
737737
}, event);
738738
break;
@@ -753,7 +753,7 @@ void cSoftHdDevice::OnEventReceived(const Event& event) {
753753
m_pRender->SetPlaybackPaused(false);
754754
},
755755
[this](const StillPictureEvent& s) {
756-
m_pVideoStream->StillPicture(&s.pesPacket);
756+
HandleStillPicture(s.data, s.size);
757757
},
758758
}, event);
759759
break;
@@ -990,11 +990,33 @@ void cSoftHdDevice::StillPicture(const uchar *data, int size)
990990
return;
991991
}
992992

993-
cPesVideo pesPacket((const uint8_t*)data, size);
994-
if (pesPacket.IsValid()) {
995-
OnEventReceived(StillPictureEvent{pesPacket});
996-
} else
997-
m_pVideoStream->ResetFragmentationBuffer();
993+
OnEventReceived(StillPictureEvent{data, size});
994+
}
995+
996+
/**
997+
* The still picture data received from VDR can contain multiple PES packets.
998+
* This sends each PES packet's raw data separately to PlayVideo(), and does a flush to display the frame immediately.
999+
*
1000+
* @param data pes data of one or more frames
1001+
* @param size length of data area
1002+
*/
1003+
void cSoftHdDevice::HandleStillPicture(const uchar *data, int size)
1004+
{
1005+
const uchar *currentPacketStart = data;
1006+
while (currentPacketStart < data + size) {
1007+
cPesVideo pesPacket((const uint8_t*)currentPacketStart, size - (currentPacketStart - data));
1008+
1009+
if (pesPacket.IsValid())
1010+
PlayVideo(currentPacketStart, pesPacket.GetPacketLength());
1011+
else {
1012+
LOGWARNING("device: %s: invalid PES packet", __FUNCTION__);
1013+
break;
1014+
}
1015+
1016+
currentPacketStart += pesPacket.GetPacketLength();
1017+
}
1018+
1019+
m_pVideoStream->Flush();
9981020
}
9991021

10001022
/**

softhddevice.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ struct TrickSpeedEvent {
5353
bool forward;
5454
};
5555
struct StillPictureEvent {
56-
cPesVideo& pesPacket;
56+
const uchar *data;
57+
int size;
5758
};
5859

5960
using Event = std::variant<PlayEvent, PauseEvent, StopEvent, TrickSpeedEvent, StillPictureEvent>;
@@ -219,6 +220,7 @@ class cSoftHdDevice:public cDevice
219220
int PesHeadLength(const uint8_t *);
220221
void OnEventReceived(const Event&);
221222
void HandlePause(void);
223+
void HandleStillPicture(const uchar *data, int size);
222224
};
223225

224226
#endif

tests/test_pes.cpp

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extern "C" {
2828
// - Stream ID: 1 byte
2929
// - PES packet length: 2 bytes
3030
// - Optional PES header fields
31-
std::vector<uint8_t> createBasicPesVideoHeader(uint8_t streamId, bool withPts = false) {
31+
std::vector<uint8_t> createBasicPesVideoHeader(uint8_t streamId, bool withPts = false, uint16_t pesLength = 0) {
3232
std::vector<uint8_t> data;
3333

3434
// Start code prefix
@@ -40,8 +40,8 @@ std::vector<uint8_t> createBasicPesVideoHeader(uint8_t streamId, bool withPts =
4040
data.push_back(streamId);
4141

4242
// PES packet length (0 = unspecified)
43-
data.push_back(0x00);
44-
data.push_back(0x00);
43+
data.push_back((pesLength >> 8) & 0xFF);
44+
data.push_back(pesLength & 0xFF);
4545

4646
// PES extension
4747
data.push_back(0x80); // '10'xxxxxx (no PES scrambling control, PES priority, data alignment indicator, copyright, original or copy)
@@ -334,6 +334,116 @@ TEST_CASE("cPesVideo - Payload extraction", "[pes]") {
334334
}
335335
}
336336

337+
TEST_CASE("cPesVideo - Packet length", "[pes]") {
338+
SECTION("Get packet length for unbounded MPEG2 (length field = 0)") {
339+
auto data = createMpeg2PesPacket();
340+
cPesVideo pes(data.data(), data.size());
341+
342+
REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
343+
}
344+
345+
SECTION("Get packet length for unbounded H.264 (length field = 0)") {
346+
auto data = createH264PesPacket(false);
347+
cPesVideo pes(data.data(), data.size());
348+
349+
REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
350+
}
351+
352+
SECTION("Get packet length with specified length field") {
353+
// Create a PES packet with a specific length
354+
// PES length field specifies bytes after the length field itself
355+
uint16_t pesPayloadLength = 20; // Header data (3 bytes) + actual payload
356+
auto data = createBasicPesVideoHeader(0xE0, false, pesPayloadLength);
357+
358+
// Add some payload to match the specified length
359+
for (int i = data.size() - 6; i < pesPayloadLength; i++) {
360+
data.push_back(0x00);
361+
}
362+
363+
cPesVideo pes(data.data(), data.size());
364+
365+
REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
366+
}
367+
368+
SECTION("Get packet length for packet with PTS and specified length") {
369+
// PTS takes 5 bytes, so header data length = 5
370+
// Total PES header = 9 (fixed header) + 5 (PTS) = 14 bytes
371+
// If we want total packet of 50 bytes, length field = 50 - 6 = 44
372+
uint16_t pesPayloadLength = 44;
373+
auto data = createBasicPesVideoHeader(0xE0, true, pesPayloadLength);
374+
375+
// Add payload to make total packet 50 bytes
376+
int currentSize = data.size();
377+
int targetTotalSize = 6 + pesPayloadLength;
378+
for (int i = currentSize; i < targetTotalSize; i++) {
379+
data.push_back(0x00);
380+
}
381+
382+
cPesVideo pes(data.data(), data.size());
383+
384+
REQUIRE(pes.GetPacketLength() == 50);
385+
}
386+
387+
SECTION("Get packet length when input buffer is larger than PES packet") {
388+
// Create a PES packet with specified length
389+
uint16_t pesPayloadLength = 20;
390+
auto data = createBasicPesVideoHeader(0xE0, false, pesPayloadLength);
391+
392+
// Add payload matching the PES length
393+
for (int i = data.size() - 6; i < pesPayloadLength; i++) {
394+
data.push_back(0xAA);
395+
}
396+
397+
// Add extra data beyond the PES packet (simulating buffer with multiple packets)
398+
for (int i = 0; i < 50; i++) {
399+
data.push_back(0xFF);
400+
}
401+
402+
cPesVideo pes(data.data(), data.size());
403+
404+
REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
405+
REQUIRE(pes.GetPacketLength() < static_cast<int>(data.size()));
406+
}
407+
408+
SECTION("Unbounded packet with buffer larger than actual data") {
409+
// Create an unbounded packet (length field = 0)
410+
auto data = createBasicPesVideoHeader(0xE0, false, 0);
411+
412+
// Add some actual payload
413+
for (int i = 0; i < 30; i++) {
414+
data.push_back(0xAA);
415+
}
416+
417+
// Store the actual data size
418+
int actualSize = data.size();
419+
420+
// Add extra buffer space (simulating oversized buffer)
421+
for (int i = 0; i < 50; i++) {
422+
data.push_back(0xFF);
423+
}
424+
425+
cPesVideo pes(data.data(), data.size());
426+
427+
REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
428+
REQUIRE(pes.GetPacketLength() > actualSize);
429+
}
430+
431+
SECTION("Get packet length for audio packet with specified length") {
432+
// Audio packets typically have bounded length
433+
uint16_t pesPayloadLength = 30;
434+
auto data = createBasicPesVideoHeader(0xC0, false, pesPayloadLength);
435+
436+
// Add audio payload
437+
for (int i = data.size() - 6; i < pesPayloadLength; i++) {
438+
data.push_back(0xFF);
439+
}
440+
441+
cPesAudio pes(data.data(), data.size());
442+
443+
REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
444+
}
445+
}
446+
337447
TEST_CASE("cPesVideo - Audio stream handling", "[pes]") {
338448
SECTION("Audio stream without codec parsing") {
339449
auto data = createAudioPesPacket();

videorender.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ bool cVideoRender::IsKeyFrame(AVFrame *frame)
577577
void cVideoRender::MarkAsTrickspeedFrame(AVFrame *frame)
578578
{
579579
SetFrameFlags(frame, FRAME_FLAG_TRICKSPEED);
580+
MarkAsProgressiveFrame(frame);
580581
}
581582

582583
/**
@@ -588,24 +589,21 @@ void cVideoRender::MarkAsStillpictureFrame(AVFrame *frame)
588589
{
589590
SetFrameFlags(frame, FRAME_FLAG_STILLPICTURE);
590591
frame->pts = AV_NOPTS_VALUE;
592+
MarkAsProgressiveFrame(frame);
591593
}
592594

593595
/**
594596
* Force this frame to be a progressive frame
595597
*
596598
* @param frame AVFrame
597-
*
598-
* @returns true, if the frame was an interlaced frame before
599599
*/
600-
bool cVideoRender::MarkAsProgressiveFrame(AVFrame *frame)
600+
void cVideoRender::MarkAsProgressiveFrame(AVFrame *frame)
601601
{
602-
bool wasInterlaced = IsInterlacedFrame(frame);
603602
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(58,7,100)
604603
frame->interlaced_frame = 0;
605604
#else
606605
frame->flags &= ~AV_FRAME_FLAG_INTERLACED;
607606
#endif
608-
return wasInterlaced;
609607
}
610608

611609
/**
@@ -1121,11 +1119,8 @@ void cVideoRender::EnqueueFB(AVFrame *inframe)
11211119
*
11221120
* @param videoCtx ffmpeg video codec context
11231121
* @param frame frame to render
1124-
*
1125-
* @retval 0 success or error, return (frame is either freed or moved to the render ringbuffer)
1126-
* @retval -1 ringbuffer full, try again
11271122
*/
1128-
int cVideoRender::RenderFrame(AVCodecContext * videoCtx, AVFrame * frame)
1123+
void cVideoRender::RenderFrame(AVCodecContext * videoCtx, AVFrame * frame)
11291124
{
11301125
if (!m_startCounter) {
11311126
m_timebaseMutex.Lock();
@@ -1180,7 +1175,7 @@ int cVideoRender::RenderFrame(AVCodecContext * videoCtx, AVFrame * frame)
11801175
LOGDEBUG("videorender: %s: wakeup filter thread", __FUNCTION__);
11811176
if (m_pFilterThread->Init(videoCtx, frame, m_deintDisabled)) {
11821177
av_frame_free(&frame);
1183-
return 0;
1178+
return;
11841179
} else {
11851180
m_pFilterThread->Start();
11861181
}
@@ -1200,7 +1195,7 @@ int cVideoRender::RenderFrame(AVCodecContext * videoCtx, AVFrame * frame)
12001195
FramesRbUnlock();
12011196
} else {
12021197
FramesRbUnlock();
1203-
return -1;
1198+
return;
12041199
}
12051200
} else {
12061201
// AV_PIX_FMT_DRM_NV12 ?
@@ -1211,12 +1206,10 @@ int cVideoRender::RenderFrame(AVCodecContext * videoCtx, AVFrame * frame)
12111206
EnqueueFB(frame);
12121207
} else {
12131208
FramesRbUnlock();
1214-
return -1;
1209+
return;
12151210
}
12161211
}
12171212
}
1218-
1219-
return 0;
12201213
}
12211214

12221215

videorender.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class cVideoRender
136136
int DrmHandleEvent(void);
137137

138138
// Frame and buffer
139-
int RenderFrame(AVCodecContext *, AVFrame *);
139+
void RenderFrame(AVCodecContext *, AVFrame *);
140140
void DisplayFrame(AVFrame *);
141141
void EnqueueFB(AVFrame *);
142142
int GetFramesFilled(void) { return atomic_read(&m_framesFilled); };
@@ -150,7 +150,6 @@ class cVideoRender
150150
bool IsKeyFrame(AVFrame *);
151151
void MarkAsTrickspeedFrame(AVFrame *);
152152
void MarkAsStillpictureFrame(AVFrame *);
153-
bool MarkAsProgressiveFrame(AVFrame *);
154153
void ScheduleDisplayBlackFrame(void) { m_displayBlackFrame = true; };
155154
void DestroyFrameBuffers(void);
156155
void ClearDecoderToDisplayQueue(void);
@@ -236,6 +235,7 @@ class cVideoRender
236235
#endif
237236
int GetFrameFlags(AVFrame *);
238237
void SetFrameFlags(AVFrame *, int);
238+
void MarkAsProgressiveFrame(AVFrame *);
239239
void SetVideoClock(int64_t);
240240
bool ShouldWaitForAudio(void);
241241
void WaitForAudioReady(int64_t, int64_t);

0 commit comments

Comments
 (0)