vdr-plugin-softhddevice-drm-gles 1.7.3-98a2ef7
videostream.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
17#include <functional>
18#include <set>
19#include <string>
20#include <vector>
21
22extern "C" {
23#include <libavcodec/avcodec.h>
24#include <libavformat/avformat.h>
25#include <libavutil/timestamp.h>
26}
27
28#include <vdr/thread.h>
29
30#include "codec_video.h"
31#include "config.h"
32#include "h264parser.h"
33#include "hardwaredevice.h"
34#include "logger.h"
35#include "misc.h"
36#include "queue.h"
37#include "videofilter.h"
38#include "videostream.h"
39#include "videorender.h"
40
41/*****************************************************************************
42 * cVideoStream class
43 ****************************************************************************/
44
49 : cThread(isPipStream ? "shd PIP decode" : "shd main decode"),
50 m_pConfig(config),
51 m_pDecoder(nullptr),
52 m_pRender(render),
53 m_identifier(isPipStream ? "PIP" : "main"),
54 m_frameOutput(frameOutput),
55 m_pDrmBufferQueue(drmBufferQueue),
56 m_videoFilter(render, m_pDrmBufferQueue, isPipStream ? "shd PIP filter" : "shd main filter", frameOutput),
57 m_hardwareQuirks(hardwareQuirks),
58 m_userDisabledDeinterlacer(config->ConfigDisableDeint),
59 m_deinterlacerDeactivated(isPipStream ? true : false)
60{
62
63 LOGDEBUG("videostream %s: %s", __FUNCTION__, m_identifier);
65 LOGDEBUG2(L_CODEC, "videostream %s: %s: fallback to sw decoder after %d packets sent", __FUNCTION__, m_identifier, m_decoderFallbackToSwNumPkts);
66}
67
69{
70 LOGDEBUG("videostream %s: %s", __FUNCTION__, m_identifier);
71}
72
80{
81 return m_packets.Push(nullptr);
82}
83
95{
96 if (avpkt->pts != AV_NOPTS_VALUE)
97 m_inputPts = avpkt->pts;
98
99 LOGDEBUG2(L_PACKET, "videostream: %s: push PTS %s", __FUNCTION__, Timestamp2StringPts(avpkt->pts));
100
101 return m_packets.Push(avpkt);
102}
103
108{
109 LOGDEBUG("videostream %s: %s:", m_identifier, __FUNCTION__);
110
111 Stop();
112
113 if (m_pDecoder) {
114 m_pDecoder->Close();
115 delete(m_pDecoder);
116 m_pDecoder = nullptr;
117 }
118
120}
121
126{
127 LOGDEBUG("videostream %s: %s: packets %d", m_identifier, __FUNCTION__, m_packets.Size());
128
129 while (!m_packets.IsEmpty()) {
132 }
133
135}
136
141{
142 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
143
145
146 Start();
147}
148
153{
154 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
155
157 m_pDecoder->Close();
158 m_pPar = nullptr;
159 m_numIFrames = 0;
160 m_maxFrameNum = 1;
161 m_naluTypesAtStart.clear();
162 m_dpbFrames.clear();
165 m_framerate = 0.0;
166}
167
175{
176 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
178
181 LOGFATAL("videostream %s: %s: Could not reopen the decoder (flush)!", m_identifier, __FUNCTION__);
182 } else {
184 }
185}
186
200{
201 int minPkts = m_interlaced ? m_trickpkts : 1;
202
205 if (m_sentTrickPkts >= minPkts) {
207 m_sentTrickPkts = 0;
208 }
209 }
210}
211
226
240{
245
246 if (h264Packet.HasSPS()) {
247 m_maxFrameNum = 1 << (h264Packet.GetLog2MaxFrameNumMinus4() + 4);
248 m_log2MaxFrameNumMinus4 = h264Packet.GetLog2MaxFrameNumMinus4();
249 }
250
251 if (h264Packet.HasPPS()) {
252 m_ppsNumRefIdxL0DefaultActiveMinus1 = h264Packet.GetPpsNumRefIdxL0DefaultActiveMinus1();
253 m_ppsNumRefIdxL1DefaultActiveMinus1 = h264Packet.GetPpsNumRefIdxL1DefaultActiveMinus1();
254 }
255
256 int frameNumber = h264Packet.GetFrameNum();
257
258 if (h264Packet.IsReference()) {
259 h264Packet.AddFrameNumber(frameNumber);
260 m_dpbFrames.insert(frameNumber);
261 } else {
262 h264Packet.AddFrameNumber(-1);
263 }
264
265 if (h264Packet.IsISlice())
266 m_numIFrames++;
267
268 m_naluTypesAtStart.push_back(h264Packet.GetNalUnitString());
269
270 if (h264Packet.IsPSlice() || h264Packet.IsBSlice()) {
271 int numRefL0 = h264Packet.GetNumRefIdxL0Active();
272 int numRefL1 = h264Packet.GetNumRefIdxL1Active();
273
274 for (auto& mod : h264Packet.GetRefMods()) {
275 if (mod.idc != 0 && mod.idc != 1)
276 continue;
277
278 int activeRefs = (mod.list == 0) ? numRefL0 : numRefL1;
279 if (activeRefs <= 0)
280 continue;
281
282 // Compute the short-term reference frame number
283 int modRef = -1;
284 int diff = mod.abs_diff_pic_num_minus1 + 1;
285
286 if (mod.idc == 0) { // subtraction
288 } else if (mod.idc == 1) { // addition
290 }
291
292 // Check if this reference exists in the DPB
293 if (m_dpbFrames.find(modRef) == m_dpbFrames.end()) {
294 h264Packet.AddInvalidReference(modRef, frameNumber);
295 } else {
296 h264Packet.AddValidReference(modRef);
297 }
298 }
299
300 // only print invalid references for better readability
301 h264Packet.BuildInvalidReferenceString(frameNumber);
302 // h264Packet.BuildValidReferenceString();
303
304 if (h264Packet.HasInvalidBackwardReferences() && h264Packet.IsPSlice() && m_numIFrames - 1 < m_dropInvalidPackets) {
305 LOGDEBUG2(L_CODEC, "videostream %s: %s: invalid backward reference, drop P-Frame %d", m_identifier, __FUNCTION__, frameNumber);
306 return true;
307 }
308 }
309
310 return false;
311}
312
320{
322 return false;
323
324 // Check, if the packet should be dropped
326 return ParseH264Packet(avpkt);
327
328 // otherwise just log H.264 frames up to the given number of I-Frames
329 if (m_logPackets) {
330 LOGDEBUG("videostream %s: %s: parsed H.264 stream:", m_identifier, __FUNCTION__);
331 for (std::size_t i = 0; i < m_naluTypesAtStart.size(); i++)
332 LOGDEBUG("[H264] (%02d) %s", i, m_naluTypesAtStart[i].c_str());
333
334 m_logPackets = 0;
335 }
336
337 return false;
338}
339
348{
349 int ret = 0;
350
351 // Force a decoder drain if the new pts is more than AV_SYNC_BORDER_MS
352 // greater than the last one.
353 // VDR may deliver packets out of pts order around the 33-bit PTS wrap.
354 // In that case a numerically larger PTS may actually belong to an older
355 // frame. Ignore differences greater than half the PTS range so they are
356 // not mistaken for discontinuities.
359 std::abs(PtsToMs(avpkt->pts) - PtsToMs(m_lastDecodedPts)) < PtsToMs(1LL << 32)) {
360
361 LOGDEBUG2(L_CODEC, "videostream: %s: discontinuity detected in video PTS %s -> %s, force decoder flush", __FUNCTION__,
363
365 m_lastDecodedPts = avpkt->pts;
366
367 // send packet to decoder
368 } else if (!PacketDropNeeded(avpkt)) {
370 m_isResend = false;
371
372 if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
373 avpkt = m_packets.Pop();
374 if (avpkt && avpkt->pts != AV_NOPTS_VALUE)
375 m_lastDecodedPts = avpkt->pts;
377 // a repeated decoder drain was requested, drop the nullptr-packet from the queue
378 } else if (ret == AVERROR_EOF && !avpkt) {
379 m_packets.Pop();
380 } else {
381 // PacketDropNeeded() is skipped next time, because this packet needs to be resent to the decoder
382 m_isResend = true;
383 }
384
385 if (!ret && m_pRender->IsTrickSpeed())
387
388 // drop packet
389 } else {
390 avpkt = m_packets.Pop();
392 m_isResend = false;
393 }
394}
395
404{
405 int ret = 0;
406
407 // receive frame from decoder
408 ret = m_pDecoder->ReceiveFrame(frame);
409
410 if (ret == AVERROR_EOF) {
411 FlushDecoder();
412 m_sentTrickPkts = 0;
413 }
414
415 return ret;
416}
417
422{
424 return;
425
426 // open decoder on new stream
427 if (m_newStream)
428 OpenDecoder();
429
430 // send packet to decoder
431 if (!m_packets.IsEmpty()) {
434 }
435
436 // receive frame from decoder and send it to the render queue
437 AVFrame *frame = nullptr;
438 if (!ReceiveFrame(&frame))
439 RenderFrame(frame);
440
441 // software decoder fallback check
443 // log maximum number of packets needed for the hw decoder to deliver a frame
446
447 // fallback to software decoder if configured and hw decoder fails
449 LOGWARNING("videostream %s: %s: Could not decode frame after %d packets sent, fallback to software decoder!", m_identifier, __FUNCTION__, m_decoderFallbackToSwNumPkts);
451 LOGFATAL("videostream %s: %s: Could not reopen the decoder (sw fallback)!", m_identifier, __FUNCTION__);
452 }
453 }
454}
455
463void cVideoStream::GetVideoSize(int *width, int *height, double *aspect_ratio)
464{
466
467 if (m_pDecoder && videoCtx) {
468 *width = videoCtx->coded_width;
469 *height = videoCtx->coded_height;
470 *aspect_ratio = *width / (double)*height;
471 } else {
472 *width = 0;
473 *height = 0;
474 *aspect_ratio = 1.0;
475 }
476}
477
486{
487 m_newStream = true;
488 m_trickpkts = codecId == AV_CODEC_ID_MPEG2VIDEO ? 1 : 2;
490 m_codecId = codecId;
491 m_pPar = par;
492}
493
494/*****************************************************************************
495 * Thread
496 ****************************************************************************/
497
502{
503 LOGDEBUG("videostream: decoding thread started");
504 while(Running()) {
505 m_mutex.lock();
506
507 DecodeInput();
508
509 m_mutex.unlock();
510
511 usleep(1000);
512 }
513 LOGDEBUG("videostream: decoding thread stopped");
514}
515
520{
521 if (!Active())
522 return;
523
524 LOGDEBUG("videostream: stopping decoding thread");
525 Cancel(2);
526}
527
532 if (m_videoFilter.Active())
534
536 m_useDeinterlacer = false;
538}
539
549{
550 if (frame->decode_error_flags || frame->flags & AV_FRAME_FLAG_CORRUPT)
551 LOGWARNING("videostream: %s: %s: error_flag or FRAME_FLAG_CORRUPT", m_identifier, __FUNCTION__);
552
553 if (!m_framerate) {
554 if (av_q2d(m_pDecoder->GetContext()->framerate) > 0.0) {
555 // the decoder has a valid framerate, use it
556 m_framerate = av_q2d(m_pDecoder->GetContext()->framerate);
557 } else if (frame->pts != AV_NOPTS_VALUE) {
558 // The decoder has no valid framerate, calculate it from pts and timebase
559 // @todo We are only using the first two frames here.
560 // If there are issues in the stream this might not be correct.
563 m_framerate = 1.0 / (delta * av_q2d(m_timebase));
564 }
565 m_ptsForFramerateDetection = frame->pts;
566 }
567 }
568
569 // Filter thread will only be started, if the lambda function returns true
571 m_timebase = m_pDecoder->GetContext()->pkt_timebase;
572
573 // Enable the deinterlacer only if:
574 // --------------------------------
575 // - The user did not disable the deinterlacer
576 // - The deinterlacer is not temporarily deactivated (trickspeed and still picture)
577 // - A hardware quirk does not forbid using the deinterlacer
578 // - It is an interlaced stream, determined by:
579 // - The codec is different from HEVC (always progressive)
580 // - The framerate is lower or equal to 30fps
581 // - Or, if the frame's interlaced flag is set
582 // - the display cannot handle an interlaced mode
583 // - the display can handle an interlaced mode, but the user doesn't want to use it
584 //
585 // General interlaced stream detection:
586 // ------------------------------------
587 // We cannot solely rely on the frame's interlaced flag, because the deinterlacer shall also be enabled with mixed progressive/interlaced streams (e.g. TV station "ProSieben").
588
590 (m_pDecoder->GetContext()->codec_id != AV_CODEC_ID_HEVC &&
591 m_pDecoder->GetContext()->framerate.num > 0 &&
592 av_q2d(m_pDecoder->GetContext()->framerate) < 30.1) || isInterlacedFrame(frame); // account for rounding errors when comparing double
593
594 // don't use the deinterlacer, if display mode should follow video including interlacing
596
597 // test, if display can handle the requested mode
598 // if the interlaced stream should be deinterlaced, the new mode will have doubled framerate and is progressive
599 sDrmMode mode = { m_pDecoder->GetContext()->coded_width,
600 m_pDecoder->GetContext()->coded_height,
603
605 m_interlaced &&
609
611 LOGDEBUG("videostream: %s: %s: deinterlacer disabled by user configuration", m_identifier, __FUNCTION__);
612
613 // Use the filter thread if:
614 // - AV_PIX_FMT_YUV420P, interlaced -> software deinterlacer (bwdif filter)
615 // - AV_PIX_FMT_YUV420P, progressive -> scale filter to get NV12 frames
616 // - AV_PIX_FMT_DRM_PRIME, interlaced, hw deinterlacer available -> hw deinterlacer
617 if (frame->format == AV_PIX_FMT_YUV420P ||
618 (frame->format == AV_PIX_FMT_DRM_PRIME && m_useDeinterlacer))
620
622 }
623
624 // if interlaced material was forced to be deinterlaced, we have doubled framerate and progressive mode now
627
628 // reset the display mode if needed
629 if (m_framerate &&
634
636 m_pDecoder->GetContext()->coded_height,
639
641 }
642
643 if (m_videoFilter.Active())
645 else {
646 // AV_PIX_FMT_DRM_PRIME, interlaced, hw deinterlacer not available
647 // AV_PIX_FMT_DRM_PRIME, progressive
648 // -> put the frame directly into render buffer
650 m_frameOutput(frame);
651 }
652}
653
H.264 Parser.
Definition h264parser.h:45
T * Pop(void)
Pop an element from the back of the queue.
Definition queue.h:57
bool IsEmpty(void)
Check if the queue is empty.
Definition queue.h:99
bool IsFull(void)
Check if the queue is full.
Definition queue.h:110
bool Push(T *element)
Push an element to the front of the queue.
Definition queue.h:40
size_t Size(void)
Get the current size of the queue.
Definition queue.h:121
T * Peek(void)
Get a reference to the back element.
Definition queue.h:75
Plugin Configuration.
Definition config.h:65
int ConfigVideoDisplayMode
display mode (enum ConfigDisplayModes)
Definition config.h:76
int ConfigParseH264StreamStart
log frames at stream start up to the given number of I-Frames
Definition config.h:118
int ConfigDecoderFallbackToSwNumPkts
maximum number of packets sent before fallback to sw decoder
Definition config.h:117
void SetDecoderNeedsMaxPackets(int)
Definition config.cpp:149
const char * CurrentDecoderType
current decoder type: "hardware" or "software"
Definition config.h:142
const char * CurrentDecoderName
current decoder name
Definition config.h:141
bool ConfigDecoderFallbackToSw
fallback to software decoder if the hardware decoder fails
Definition config.h:116
int GetDecoderNeedsMaxPackets(void)
Definition config.cpp:155
sDrmMode CurrentVideoDrmMode
currently used video drm mode
Definition config.h:151
int ConfigDropInvalidH264PFrames
drop P-Frames with invalid references on stream start up to the given number of I-Frames
Definition config.h:119
Video Decoder.
Definition codec_video.h:37
int ReopenCodec(enum AVCodecID, AVCodecParameters *, AVRational, int)
Reopen the video decoder.
AVCodecContext * GetContext(void)
Definition codec_video.h:46
int SendPacket(const AVPacket *)
Send a video packet to be decoded.
bool IsHardwareDecoder(void)
Definition codec_video.h:47
int Open(enum AVCodecID, AVCodecParameters *, AVRational, bool)
Open the video decoder.
int GetFramesReceived(void)
Definition codec_video.h:50
void FlushBuffers(void)
Flush the video decoder buffers.
int ReceiveFrame(AVFrame **)
Receive a decoded a video frame.
int GetPacketsSent(void)
Definition codec_video.h:49
const char * Name(void)
Definition codec_video.h:48
void Close(void)
Close video decoder.
int GetNumFramesToFilter(void)
Definition videofilter.h:49
bool IsInputBufferEmpty(void)
Definition videofilter.h:48
void InitAndStart(const AVCodecContext *, AVFrame *, bool)
Init and start the video filter thread.
void Stop(void)
Stops the filter thread and does a cleanup.
bool IsInputBufferFull(void)
Definition videofilter.h:47
void PushFrame(AVFrame *)
Puts a frame in the buffer to be filtered.
Video Renderer.
void SetDisplayMode(int)
Wrapper to set the display mode.
bool IsForwardTrickspeed(void)
bool IsTrickSpeed(void)
bool PresentationPending(void)
bool CanHandleMode(sDrmMode *)
Wrapper to check, if drm can handle the display mode.
std::mutex m_mutex
mutex for decoding thread control
bool m_interlaced
flag for interlaced stream
void GetVideoSize(int *, int *, double *)
Get video size and aspect ratio.
bool Drain(void)
Drains the video stream by finalizing any pending data.
bool BuffersEmpty(void)
Return true, if the input AVPacket, videofilter and drm output buffer queues are empty.
bool ParseH264Packet(AVPacket *)
Parse an H.264 packet and test, if the packet is a P-Slice with invalid backward references.
void Open(AVCodecID, AVCodecParameters *=nullptr, AVRational={ .num=1,.den=PTSTICKS })
Open a video codec.
void OpenDecoder(void)
Open the decoder including an H.264 parsing if needed.
std::atomic< bool > m_newStream
flag for new stream
int m_dropInvalidPackets
drop P-Frames with invalid references until the given number of I-Frames arrived
std::function< void(AVFrame *)> m_frameOutput
function to output the frame
const char * m_identifier
identifier string for logging
Definition videostream.h:99
int ReceiveFrame(AVFrame **)
Receive a decoded frame from the decoder.
cSoftHdConfig * m_pConfig
plugin config
Definition videostream.h:96
int64_t m_inputPts
PTS of the first packet in the input buffer.
double m_framerate
current stream framerate
bool m_deinterlacerDeactivated
set, if the deinterlacer should be disabled temporarily (trickspeed, stillpicture,...
int m_sentTrickPkts
how many avpkt have been sent to the decoder in trickspeed mode?
int m_logPackets
parse and log all frames until the number of given I-Frames arrived
void StartDecoder()
Start the decoder.
void CheckForcingFrameDecode(void)
Check, if we need to force the decoder to decode the frame (force a decoder drain)
void ClearVdrCoreToDecoderQueue(void)
Clears all video stream data, which is buffered to be decoded.
bool m_userDisabledDeinterlacer
set, if the user configured the deinterlace to be disabled
int m_ppsNumRefIdxL1DefaultActiveMinus1
cache NumRefIdxL1DefaultActiveMinuns1 from a previous PPS parsing
enum AVCodecID m_codecId
current codec id
bool m_checkFilterThreadNeeded
set, if we have to check, if filter thread is needed at start of playback
int64_t m_ptsForFramerateDetection
helper PTS to calculate a framerate at stream start
bool m_useDeinterlacer
set, if the deinterlacer is used
int m_log2MaxFrameNumMinus4
cache Log2MaxFrameNumMinus4 from a previous SPS parsing
AVCodecParameters * m_pPar
current codec parameters
int m_decoderFallbackToSwNumPkts
fallback to sw decoder if hw decoder fails after the given number of packets sent
void DecodeInput(void)
Decodes a reassembled codec packet.
bool m_isResend
track, if we already tried to send the AVPacket to the decoder if so, skip the parsing
void Stop(void)
Stop the decoding thread.
static constexpr int AV_SYNC_BORDER_MS
absolute max a/v difference which should trigger a decoder flush
cQueue< cDrmBuffer > * m_pDrmBufferQueue
pointer to renderer's DRM buffer queue
virtual void Action(void)
Decoding thread loop, which periodically tries to decode input.
void CloseDecoder(void)
Close the decoder.
int m_hardwareQuirks
hardware specific quirks
cVideoDecoder * m_pDecoder
video decoder
Definition videostream.h:97
cVideoFilter m_videoFilter
pointer to deinterlace/scaling video filter thread
int m_maxFrameNum
= 1 << Log2MaxFrameNumMinus4 + 4
void Exit(void)
Exit video stream.
cVideoStream(cVideoRender *, int, cQueue< cDrmBuffer > *, cSoftHdConfig *, bool, std::function< void(AVFrame *)>)
Create a video stream.
virtual ~cVideoStream(void)
std::vector< std::string > m_naluTypesAtStart
array of strings to log the H.264 frames at stream start
void CancelFilterThread(void)
Stop filter thread.
int m_ppsNumRefIdxL0DefaultActiveMinus1
cache NumRefIdxL0DefaultActiveMinuns1 from a previous PPS parsing
cVideoRender * m_pRender
video renderer
Definition videostream.h:98
int m_trickpkts
how many avpkt does the decoder need in trickspeed mode?
void RenderFrame(AVFrame *)
Render a frame.
cQueue< AVPacket > m_packets
AVPackets queue.
int64_t m_lastDecodedPts
PTS of the latest packet, which was sent to the decoder in order to detect stream discontinuity.
void FlushDecoder(void)
Flush the decoder.
bool PushAvPacket(AVPacket *avpkt)
Pushes a pre-assembled AVPacket directly to the processing queue.
bool PacketDropNeeded(AVPacket *)
Test if the packet should be dropped and parse it if needed (see cVideoStream::ParseH264())
virtual void SetDeinterlacerDeactivated(bool deactivate)
Definition videostream.h:86
int64_t PtsToMs(int64_t pts)
int m_numIFrames
counter for the arriving I-Frames at H.264 stream start
void SendPacket(AVPacket *)
Send a packet to the decoder.
std::atomic< struct AVRational > m_timebase
current codec timebase
std::set< int > m_dpbFrames
private set of reference frames (internal short-time decoded picture buffer)
Video Decoder Header File.
Plugin Configuration Header File.
@ CONFIG_DISPLAY_MODE_FOLLOW_VIDEO_INTERLACED
Definition config.h:45
#define LOGDEBUG2
log to LOG_DEBUG and add a prefix
Definition logger.h:48
#define LOGDEBUG
log to LOG_DEBUG
Definition logger.h:46
#define AV_NOPTS_VALUE
Definition misc.h:76
#define LOGWARNING
log to LOG_WARN
Definition logger.h:42
#define LOGFATAL
log to LOG_ERR and abort
Definition logger.h:38
static const char * Timestamp2StringPts(int64_t ts)
Input PTS is in PTSTICKS.
Definition misc.h:149
static bool isInterlacedFrame(AVFrame *frame)
Check, if this is an interlaced frame.
Definition misc.h:88
@ QUIRK_CODEC_FLUSH_WORKAROUND
set, if we have to close and reopen the codec instead of avcodec_flush_buffers (rpi)
@ L_PACKET
decoder packet/frame tracking logs
Definition logger.h:72
@ L_CODEC
codec logs
Definition logger.h:65
H.264 Parser Header File.
Describes a hardware device.
Logger Header File.
Misc Functions.
Thread-safe Queue.
Holds possible display configurations.
Definition config.h:30
int width
display width
Definition config.h:31
bool interlaced
is this an interlaced mode?
Definition config.h:34
int height
display height
Definition config.h:32
double refreshRateHz
display refresh rate
Definition config.h:33
Deinterlace and Scaling Filters Header File.
Video Renderer (Display) Header File.
Video Input Stream Header File.