vdr-plugin-softhddevice-drm-gles 1.4.0
videostream.cpp
Go to the documentation of this file.
1
25#include <string>
26
27#include <assert.h>
28#include <unistd.h>
29
30#include <libintl.h>
31
32#include <pthread.h>
33#include <sys/types.h>
34#include <sys/wait.h>
35
36extern "C" {
37#include <libavcodec/avcodec.h>
38#include <libavformat/avformat.h>
39#include <libavutil/timestamp.h>
40}
41
43#include "softhddevice.h"
44#include "logger.h"
45#include "h264parser.h"
46
47#include "buf2rgb.h"
48
49#include "iatomic.h"
50#include "videostream.h"
51#include "audio.h"
52#include "videorender.h"
53#include "codec_audio.h"
54#include "codec_video.h"
55
56#include "queue.h"
57#include "misc.h"
58
68static size_t ReadLineFromFile(char *buf, size_t size, const char * file)
69{
70 FILE *fd = NULL;
71 size_t character;
72
73 fd = fopen(file, "r");
74 if (fd == NULL) {
75 LOGERROR("videostream: %s: Can't open %s", __FUNCTION__, file);
76 return 0;
77 }
78
79 character = getline(&buf, &size, fd);
80
81 fclose(fd);
82
83 return character;
84}
85
91static int ReadHWPlatform(void)
92{
93 char *txt_buf;
94 char *read_ptr;
95 size_t bufsize = 128;
96 size_t read_size;
97
98 txt_buf = (char *) calloc(bufsize, sizeof(char));
99 int hardwareQuirks = 0;
100
101 read_size = ReadLineFromFile(txt_buf, bufsize, "/sys/firmware/devicetree/base/compatible");
102 if (!read_size) {
103 free((void *)txt_buf);
104 return 0;
105 }
106
107 read_ptr = txt_buf;
108 // be aware: device tree string can contain \x0 bytes, so every C-string function
109 // thinks, we already reached the string's terminating null bytes
110 // so copy the string into a temporary string without the "\0"
111 char *_txt_buf = (char *) calloc(bufsize, sizeof(char));
112 char *_read_ptr = _txt_buf;
113 for (size_t i = 0; i < bufsize; i++) {
114 if (memcmp(read_ptr, "\0", sizeof(char))) {
115 memcpy(_read_ptr, read_ptr, sizeof(char));
116 _read_ptr++;
117 }
118 read_ptr++;
119 }
120
121 read_ptr = txt_buf;
122 LOGDEBUG2(L_DRM, "videostream: %s: found \"%s\", set hardware quirks", __FUNCTION__, _txt_buf);
123
124 while(read_size) {
125 if (strstr(read_ptr, "bcm2836")) {
126 LOGDEBUG2(L_DRM, "videostream: %s: bcm2836 (Raspberry Pi 2 Model B) found", __FUNCTION__);
127 hardwareQuirks |= QUIRK_CODEC_FLUSH_WORKAROUND;
128 break;
129 }
130 if (strstr(read_ptr, "bcm2837")) {
131 LOGDEBUG2(L_DRM, "videostream: %s: bcm2837 (Raspberry Pi 2 Model B v1.2/ 3 Model B, Raspberry Pi 3 Compute Module 3) found", __FUNCTION__);
132 hardwareQuirks |= QUIRK_CODEC_FLUSH_WORKAROUND;
133 break;
134 }
135 if (strstr(read_ptr, "bcm2711")) {
136 LOGDEBUG2(L_DRM, "videostream: %s: bcm2711 (Raspberry Pi 4 Model B, Compute Module 4, Pi 400) found", __FUNCTION__);
137 hardwareQuirks |= QUIRK_CODEC_FLUSH_WORKAROUND;
138 break;
139 }
140 if (strstr(read_ptr, "bcm2712")) {
141 LOGDEBUG2(L_DRM, "videostream: %s: bcm2712 (Raspberry Pi 5, Compute Module 5, Pi 500) found", __FUNCTION__);
142 hardwareQuirks |= QUIRK_CODEC_FLUSH_WORKAROUND;
143 break;
144 }
145 if (strstr(read_ptr, "amlogic")) {
146 LOGDEBUG2(L_DRM, "videostream: %s: amlogic found, disable HW deinterlacer", __FUNCTION__);
147 hardwareQuirks |= QUIRK_CODEC_NEEDS_EXT_INIT
150 break;
151 }
152
153 read_size -= (strlen(read_ptr) + 1);
154 read_ptr = (char *)&read_ptr[(strlen(read_ptr) + 1)];
155 }
156 free((void *)_txt_buf);
157 free((void *)txt_buf);
158
159 return hardwareQuirks;
160}
161
162/*****************************************************************************
163 * cVideoStream class
164 ****************************************************************************/
165
169cVideoStream::cVideoStream(cVideoRender *render, cQueue<cDrmBuffer> *drmBufferQueue, cSoftHdConfig *config, const char *identifier, std::function<void(AVFrame *)> frameOutput)
170{
171 LOGDEBUG("videostream %s: %s", __FUNCTION__, identifier);
172
173 m_pRender = render;
174 m_pDrmBufferQueue = drmBufferQueue;
175 m_pDecoder = nullptr;
176 m_frameOutput = frameOutput;
177 m_identifier = identifier;
178
179 m_codecId = AV_CODEC_ID_NONE;
180 m_newStream = false;
181 m_pPar = nullptr;
182
184
185 m_filterThreadName = "shd " + std::string(identifier) + " filter";
186 m_pFilterThread = new cFilterThread(render, m_pDrmBufferQueue, m_filterThreadName.c_str(), frameOutput);
188}
189
194{
195 LOGDEBUG("videostream %s:", __FUNCTION__);
196
197 if (m_pFilterThread)
198 delete m_pFilterThread;
199}
200
208{
209 m_packets.Push(nullptr);
210}
211
222bool cVideoStream::PushAvPacket(AVPacket *avpkt)
223{
224 if (avpkt->pts != AV_NOPTS_VALUE)
225 m_inputPts = avpkt->pts;
226
227 return m_packets.Push(avpkt);
228}
229
231{
232 return m_inputPts * 1000 * av_q2d(m_timebase);
233}
234
239{
240 LOGDEBUG("videostream %s: %s:", m_identifier, __FUNCTION__);
241
243
244 if (m_pDecoder) {
245 m_pDecoder->Close();
246 delete(m_pDecoder);
247 m_pDecoder = nullptr;
248 }
249
251}
252
257{
258 LOGDEBUG("videostream %s: %s: packets %d", m_identifier, __FUNCTION__, m_packets.Size());
259
260 while (!m_packets.IsEmpty()) {
261 AVPacket *avpkt = m_packets.Pop();
262 av_packet_free(&avpkt);
263 }
264
266}
267
272{
273 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
274
276
277 m_decodingThreadName = "shd " + std::string(m_identifier) + " decode";
279}
280
285{
286 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
287
288 m_codecId = AV_CODEC_ID_NONE;
289 m_pDecoder->Close();
290 m_pPar = nullptr;
291}
292
300{
301 LOGDEBUG2(L_CODEC, "videostream %s: %s", m_identifier, __FUNCTION__);
302
305 LOGFATAL("videostream %s: %s: Could not reopen the decoder (flush)!", m_identifier, __FUNCTION__);
306 } else {
308 }
309}
310
315{
316 AVFrame *frame = nullptr;
317 int ret = 0;
318
320 return;
321
322 if (m_newStream) {
323 int width = 0;
324 int height = 0;
325
326 // amlogic h264 decoder needs width an height for correct decoder open
327 if ((m_codecId == AV_CODEC_ID_H264) && (m_hardwareQuirks & QUIRK_CODEC_NEEDS_EXT_INIT)) {
328 cH264Parser h264Parser(m_packets.Peek());
329 h264Parser.GetDimensions(&width, &height);
330
331 LOGDEBUG2(L_CODEC, "videostream %s: %s: Parsed width %d height %d", m_identifier, __FUNCTION__, width, height);
332 }
333
334 if (m_pDecoder->Open(m_codecId, m_pPar, m_timebase, 0, width, height))
335 LOGFATAL("videostream %s: %s: Could not open the decoder!", m_identifier, __FUNCTION__);
336 m_newStream = false;
337 }
338
339 // wait for m_trickpkts packets
340 //
341 // m_trickpkts is the number of packets we need to have in the buffer
342 // while in interlaced trickspeed mode, needed to get a frame.
343 // This guarantees, that we don't drain the decoder too early, but exactly after
344 // m_trickpkts sent packets
345 int minPkts = (m_pRender->GetTrickSpeed() && m_interlaced) ? m_trickpkts : 1;
346
347 // send packet to decoder
348 AVPacket *avpkt = m_packets.Peek();
349
350 ret = m_pDecoder->SendPacket(avpkt);
351
352 if (ret != AVERROR(EAGAIN)) {
353 avpkt = m_packets.Pop();
354 av_packet_free(&avpkt);
355 }
356
357 // in backward trickspeed force the decoder to decode the frame, if minPkts are sent
358 bool flushDecoder = false;
359 if (ret == 0 && m_pRender->GetTrickSpeed() && !m_pRender->GetTrickForward()) {
361 if (m_sentTrickPkts >= minPkts) {
362 m_pDecoder->SendPacket(NULL);
363 m_sentTrickPkts = 0;
364 flushDecoder = true;
365 }
366 }
367
368 // receive frame from decoder
369 if (!m_newStream) { // this is for mediaplayer?
370 if (m_pDecoder->ReceiveFrame(&frame) == 0)
371 RenderFrame(frame);
372 }
373
374 if (ret == AVERROR_EOF || flushDecoder) {
375 FlushDecoder();
376 m_sentTrickPkts = 0;
377 }
378}
379
387void cVideoStream::GetVideoSize(int *width, int *height, double *aspect_ratio)
388{
389 AVCodecContext *videoCtx = m_pDecoder->GetContext();
390
391 if (m_pDecoder && videoCtx) {
392 *width = videoCtx->coded_width;
393 *height = videoCtx->coded_height;
394 *aspect_ratio = *width / (double)*height;
395 } else {
396 *width = 0;
397 *height = 0;
398 *aspect_ratio = 1.0;
399 }
400}
401
409void cVideoStream::Open(AVCodecID codecId, AVCodecParameters *par, AVRational timebase) {
410 m_newStream = true;
411 m_trickpkts = codecId == AV_CODEC_ID_MPEG2VIDEO ? 1 : 2;
412 m_timebase = timebase;
413 m_codecId = codecId;
414 m_pPar = par;
415}
416
417/*****************************************************************************
418 * Thread
419 ****************************************************************************/
420
425{
426 LOGDEBUG("videostream %s: %s", m_identifier, __FUNCTION__);
427
428 if (m_pDecodingThread->Active())
430
432 delete m_pDecodingThread;
433}
434
439 if (m_pFilterThread->Active())
441
443}
444
453void cVideoStream::RenderFrame(AVFrame * frame)
454{
455 if (frame->decode_error_flags || frame->flags & AV_FRAME_FLAG_CORRUPT)
456 LOGWARNING("videostream: %s: %s: error_flag or FRAME_FLAG_CORRUPT", m_identifier, __FUNCTION__);
457
458 // Filter thread will only be started, if the lambda function returns true
460 m_timebase = m_pDecoder->GetContext()->pkt_timebase;
461
462 // Enable the deinterlacer only if:
463 // - The user did not disable the deinterlacer
464 // - The deinterlacer is not temporarily deactivated (trickspeed and still picture)
465 // - A hardware quirk does not forbid using the deinterlacer
466 // - It is an interlaced stream, determined by:
467 // - The codec is different from HEVC (always progressive)
468 // - The framerate is lower or equal to 30fps
469 // - Or, if the frame's interlaced flag is set
470 // 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").
471
473 (m_pDecoder->GetContext()->codec_id != AV_CODEC_ID_HEVC &&
474 m_pDecoder->GetContext()->framerate.num > 0 &&
475 av_q2d(m_pDecoder->GetContext()->framerate) < 30.1) || isInterlacedFrame(frame); // account for rounding errors when comparing double
476
477 bool useDeinterlacer =
482
484 LOGDEBUG("videostream: %s: %s: deinterlacer disabled by user configuration", m_identifier, __FUNCTION__);
485
486 // Use the filter thread if:
487 // - AV_PIX_FMT_YUV420P, interlaced -> software deinterlacer (bwdif filter)
488 // - AV_PIX_FMT_YUV420P, progressive -> scale filter to get NV12 frames
489 // - AV_PIX_FMT_DRM_PRIME, interlaced, hw deinterlacer available -> hw deinterlacer
490 if (frame->format == AV_PIX_FMT_YUV420P || (frame->format == AV_PIX_FMT_DRM_PRIME && useDeinterlacer))
491 m_pFilterThread->InitAndStart(m_pDecoder->GetContext(), frame, useDeinterlacer);
492
494 }
495
496 if (m_pFilterThread->Active())
498 else {
499 // AV_PIX_FMT_DRM_PRIME, interlaced, hw deinterlacer not available
500 // AV_PIX_FMT_DRM_PRIME, progressive
501 // -> put the frame directly into render buffer
503 m_frameOutput(frame);
504 }
505}
Audio and alsa module header file.
Some helper functions header file.
Decoding thread class.
Definition: threads.h:39
void Stop(void)
Definition: threads.cpp:73
Filter thread class.
Definition: threads.h:100
void InitAndStart(const AVCodecContext *, AVFrame *, bool)
Init and start the video filter thread.
Definition: threads.cpp:185
int GetNumFramesToFilter(void)
Definition: threads.h:108
void Stop(void)
Definition: threads.cpp:349
void PushFrame(AVFrame *)
Put a frame in the buffer to be filtered.
Definition: threads.cpp:344
bool IsInputBufferFull(void)
Definition: threads.h:107
cH264Parser - H264 Parser class
Definition: h264parser.h:31
void GetDimensions(int *, int *)
Get width and height from stream.
Definition: h264parser.cpp:61
T * Pop(void)
Pop an element from the back of the queue.
Definition: queue.h:64
bool IsEmpty(void)
Check if the queue is empty.
Definition: queue.h:106
bool IsFull(void)
Check if the queue is full.
Definition: queue.h:117
bool Push(T *element)
Push an element to the front of the queue.
Definition: queue.h:47
size_t Size(void)
Get the current size of the queue.
Definition: queue.h:128
T * Peek(void)
Get a reference to the back element.
Definition: queue.h:82
bool ConfigDisableDeint
disable deinterlacer
Definition: config.h:61
cVideoDecoder - VideoDecoder class
Definition: codec_video.h:34
int ReopenCodec(enum AVCodecID, AVCodecParameters *, AVRational, int)
Reopen the video decoder.
AVCodecContext * GetContext(void)
Definition: codec_video.h:44
int SendPacket(const AVPacket *)
Send a video packet to be decoded.
void FlushBuffers(void)
Flush the video decoder.
int ReceiveFrame(AVFrame **)
Receive a decoded a video frame.
void Close(void)
Close video decoder.
int Open(enum AVCodecID, AVCodecParameters *, AVRational, int, int, int)
Open the video decoder.
cVideoRender - Video render class
Definition: videorender.h:122
int GetTrickForward(void)
Get the current trickspeed direction.
int GetTrickSpeed(void)
Get the current trickspeed.
cDecodingThread * m_pDecodingThread
pointer to decoding thread
Definition: videostream.h:118
bool m_interlaced
flag for interlaced stream
Definition: videostream.h:116
void GetVideoSize(int *, int *, double *)
Get video size and aspect ratio.
cFilterThread * m_pFilterThread
pointer to deinterlace filter thread
Definition: videostream.h:96
std::function< void(AVFrame *)> m_frameOutput
function to output the frame
Definition: videostream.h:100
const char * m_identifier
identifier string for logging
Definition: videostream.h:97
int64_t m_inputPts
PTS of the first packet in the input buffer.
Definition: videostream.h:119
bool m_deinterlacerDeactivated
set, if the deinterlacer shall be deactivated temporarily (used for trick speed and still picture)
Definition: videostream.h:106
int m_sentTrickPkts
how many avpkt have been sent to the decoder in trickspeed mode?
Definition: videostream.h:114
std::string m_decodingThreadName
decoding thread name string (persists for object lifetime)
Definition: videostream.h:99
void Flush(void)
Flushes the video stream by finalizing any pending data.
void StartDecoder()
Start the decoder.
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
Definition: videostream.h:105
volatile bool m_newStream
flag for new stream
Definition: videostream.h:115
enum AVCodecID m_codecId
current codec id
Definition: videostream.h:110
bool m_checkFilterThreadNeeded
set, if we have to check, if filter thread is needed at start of playback
Definition: videostream.h:103
AVCodecParameters * m_pPar
current codec parameters
Definition: videostream.h:111
void DecodeInput(void)
Decodes a reassembled codec packet.
cQueue< cDrmBuffer > * m_pDrmBufferQueue
pointer to renderer's DRM buffer queue
Definition: videostream.h:101
cVideoStream(cVideoRender *, cQueue< cDrmBuffer > *, cSoftHdConfig *, const char *, std::function< void(AVFrame *)>)
cVideoStream constructor
void CloseDecoder(void)
Close the decoder.
int m_hardwareQuirks
hardware specific quirks
Definition: videostream.h:104
cVideoDecoder * m_pDecoder
video decoder
Definition: videostream.h:94
void Exit(void)
Exit video stream.
virtual ~cVideoStream(void)
cVideoStream destructor
void CancelFilterThread(void)
Stop filter thread.
cVideoRender * m_pRender
video renderer
Definition: videostream.h:95
int m_trickpkts
how many avpkt does the decoder need in trickspeed mode?
Definition: videostream.h:113
void RenderFrame(AVFrame *)
Render a frame.
cQueue< AVPacket > m_packets
AVPackets queue.
Definition: videostream.h:108
void ExitDecodingThread(void)
Stop decoding thread.
void FlushDecoder(void)
Flush the decoder.
void Open(AVCodecID, AVCodecParameters *=nullptr, AVRational={ .num=1,.den=90000 })
Open a video codec.
bool PushAvPacket(AVPacket *avpkt)
Pushes a pre-assembled AVPacket directly to the processing queue.
int64_t GetInputPtsMs(void)
std::string m_filterThreadName
filter thread name string (persists for object lifetime)
Definition: videostream.h:98
std::atomic< struct AVRational > m_timebase
current codec timebase
Definition: videostream.h:112
Audio decoder header file.
Video decoder header file.
H264 parser header file.
Atomic wrapper macros function header file.
Logger class header file.
#define LOGDEBUG2
Definition: logger.h:50
#define LOGDEBUG
Definition: logger.h:49
#define LOGERROR
Definition: logger.h:46
#define L_DRM
Definition: logger.h:62
#define L_CODEC
Definition: logger.h:63
#define LOGWARNING
Definition: logger.h:47
#define LOGFATAL
Logger macros.
Definition: logger.h:45
Misc function header file.
#define AV_NOPTS_VALUE
Definition: misc.h:35
static bool isInterlacedFrame(AVFrame *frame)
Check, if this is an interlaced frame.
Definition: misc.h:47
Thread-safe queue header file.
Main plugin class header file.
Device class header file.
Rendering class header file.
#define QUIRK_CODEC_FLUSH_WORKAROUND
set, if we have to close and reopen the codec instead of avcodec_flush_buffers (rpi)
Definition: videorender.h:69
#define QUIRK_CODEC_NEEDS_EXT_INIT
set, if codec needs some infos for init (coded_width and coded_height)
Definition: videorender.h:70
#define QUIRK_NO_HW_DEINT
set, if no hw deinterlacer available
Definition: videorender.h:68
#define QUIRK_CODEC_SKIP_FIRST_FRAMES
set, if codec should skip first I-Frames
Definition: videorender.h:71
static size_t ReadLineFromFile(char *buf, size_t size, const char *file)
Helper function to read a line from a given file.
Definition: videostream.cpp:68
static int ReadHWPlatform(void)
Helper function to find out which platform we are on.
Definition: videostream.cpp:91
Videostream class header file.