///
/// @file softhddev.c	@brief A software HD device plugin for VDR.
///
/// Copyright (c) 2011 - 2015 by Johns.	 All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// This program is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
/// GNU Affero General Public License for more details.
///
/// $Id: 8881600a16f475cba7db8911ad88ce2234f72d14 $
//////////////////////////////////////////////////////////////////////////////

#define noUSE_SOFTLIMIT   ///< add soft buffer limits to Play..
#define noUSE_PIP         ///< include PIP support + new API
#define noDUMP_TRICKSPEED ///< dump raw trickspeed packets

#include <sys/stat.h>
#include <sys/types.h>
#ifdef __FreeBSD__
#include <signal.h>
#endif
#include <fcntl.h>

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut
#define _N(str) str         ///< gettext_noop shortcut

#include <libavcodec/avcodec.h>
#include <libavutil/mem.h>

#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <pthread.h>

#include "iatomic.h" // portable atomic_t
#include "misc.h"
#include "softhddev.h"

// clang-format off
#include "audio.h"
#include "video.h"
#include "codec.h"
// clang-format on

#if 0
static int DumpH264(const uint8_t *data, int size);
static void DumpMpeg(const uint8_t *data, int size);
#endif

//////////////////////////////////////////////////////////////////////////////
//  Variables
//////////////////////////////////////////////////////////////////////////////

extern int ConfigAudioBufferTime;        ///< config size ms of audio buffer
extern int ConfigVideoClearOnSwitch;     //<  clear decoder on channel switch
char ConfigStartX11Server;               ///< flag start the x11 server
static signed char ConfigStartSuspended; ///< flag to start in suspend mode
static char ConfigFullscreen;            ///< fullscreen modus
static const char *X11ServerArguments;   ///< default command arguments
static char ConfigStillDecoder;          ///< hw/sw decoder for still picture

static pthread_mutex_t SuspendLockMutex; ///< suspend lock mutex

static volatile char StreamFreezed; ///< stream freezed

//////////////////////////////////////////////////////////////////////////////
//  Audio
//////////////////////////////////////////////////////////////////////////////

static volatile char NewAudioStream; ///< new audio stream
static volatile char SkipAudio;      ///< skip audio stream
static AudioDecoder *MyAudioDecoder; ///< audio decoder
static enum AVCodecID AudioCodecID;  ///< current codec id
static int AudioChannelID;           ///< current audio channel id
static VideoStream *AudioSyncStream; ///< video stream for audio/video sync

/// Minimum free space in audio buffer 8 packets for 8 channels
#define AUDIO_MIN_BUFFER_FREE (3072 * 8 * 8)
#define AUDIO_BUFFER_SIZE (512 * 1024) ///< audio PES buffer default size
static AVPacket AudioAvPkt[1];         ///< audio a/v packet
int AudioDelay = 0;

//////////////////////////////////////////////////////////////////////////////
//  Audio codec parser
//////////////////////////////////////////////////////////////////////////////

///
/// Mpeg bitrate table.
///
/// BitRateTable[Version][Layer][Index]
///
static const uint16_t BitRateTable[2][4][16] = {
    // MPEG Version 1
    {{},
     {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0},
     {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0},
     {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}},
    // MPEG Version 2 & 2.5
    {{},
     {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0},
     {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
     {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}}};

///
/// Mpeg samperate table.
///
static const uint16_t SampleRateTable[4] = {44100, 48000, 32000, 0};

///
/// Fast check for Mpeg audio.
///
/// 4 bytes 0xFFExxxxx Mpeg audio
///
static inline int FastMpegCheck(const uint8_t *p) {
    if (p[0] != 0xFF) { // 11bit frame sync
        return 0;
    }
    if ((p[1] & 0xE0) != 0xE0) {
        return 0;
    }
    if ((p[1] & 0x18) == 0x08) { // version ID - 01 reserved
        return 0;
    }
    if (!(p[1] & 0x06)) { // layer description - 00 reserved
        return 0;
    }
    if ((p[2] & 0xF0) == 0xF0) { // bitrate index - 1111 reserved
        return 0;
    }
    if ((p[2] & 0x0C) == 0x0C) { // sampling rate index - 11 reserved
        return 0;
    }
    return 1;
}

///
/// Check for Mpeg audio.
///
/// 0xFFEx already checked.
///
/// @param data incomplete PES packet
/// @param size number of bytes
///
/// @retval <0	possible mpeg audio, but need more data
/// @retval 0	no valid mpeg audio
/// @retval >0	valid mpeg audio
///
/// From: http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
///
/// AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
///
/// o a 11x Frame sync
/// o b 2x  Mpeg audio version (2.5, reserved, 2, 1)
/// o c 2x  Layer (reserved, III, II, I)
/// o e 2x  BitRate index
/// o f 2x  SampleRate index (4100, 48000, 32000, 0)
/// o g 1x  Paddding bit
/// o ..    Doesn't care
///
/// frame length:
/// Layer I:
/// FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
/// Layer II & III:
/// FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
///
static int MpegCheck(const uint8_t *data, int size) {
    int mpeg2;
    int mpeg25;
    int layer;
    int bit_rate_index;
    int sample_rate_index;
    int padding;
    int bit_rate;
    int sample_rate;
    int frame_size;

    mpeg2 = !(data[1] & 0x08) && (data[1] & 0x10);
    mpeg25 = !(data[1] & 0x08) && !(data[1] & 0x10);
    layer = 4 - ((data[1] >> 1) & 0x03);
    bit_rate_index = (data[2] >> 4) & 0x0F;
    sample_rate_index = (data[2] >> 2) & 0x03;
    padding = (data[2] >> 1) & 0x01;

    sample_rate = SampleRateTable[sample_rate_index];
    if (!sample_rate) { // no valid sample rate try next
        // moved into fast check
        abort();
        return 0;
    }
    sample_rate >>= mpeg2;  // mpeg 2 half rate
    sample_rate >>= mpeg25; // mpeg 2.5 quarter rate

    bit_rate = BitRateTable[mpeg2 | mpeg25][layer][bit_rate_index];
    if (!bit_rate) { // no valid bit-rate try next
        // FIXME: move into fast check?
        return 0;
    }
    bit_rate *= 1000;
    switch (layer) {
        case 1:
            frame_size = (12 * bit_rate) / sample_rate;
            frame_size = (frame_size + padding) * 4;
            break;
        case 2:
        case 3:
        default:
            frame_size = (144 * bit_rate) / sample_rate;
            frame_size = frame_size + padding;
            break;
    }
    if (0) {
        Debug(3, "pesdemux: mpeg%s layer%d bitrate=%d samplerate=%d %d bytes\n",
              mpeg25  ? "2.5"
              : mpeg2 ? "2"
                      : "1",
              layer, bit_rate, sample_rate, frame_size);
    }

    if (frame_size + 4 > size) {
        return -frame_size - 4;
    }
    // check if after this frame a new mpeg frame starts
    if (FastMpegCheck(data + frame_size)) {
        return frame_size;
    }

    return 0;
}

///
/// Fast check for AAC LATM audio.
///
/// 3 bytes 0x56Exxx AAC LATM audio
///
static inline int FastLatmCheck(const uint8_t *p) {
    if (p[0] != 0x56) { // 11bit sync
        return 0;
    }
    if ((p[1] & 0xE0) != 0xE0) {
        return 0;
    }
    return 1;
}

///
/// Check for AAC LATM audio.
///
/// 0x56Exxx already checked.
///
/// @param data incomplete PES packet
/// @param size number of bytes
///
/// @retval <0	possible AAC LATM audio, but need more data
/// @retval 0	no valid AAC LATM audio
/// @retval >0	valid AAC LATM audio
///
static int LatmCheck(const uint8_t *data, int size) {
    int frame_size;

    // 13 bit frame size without header
    frame_size = ((data[1] & 0x1F) << 8) + data[2];
    frame_size += 3;

    if (frame_size + 2 > size) {
        return -frame_size - 2;
    }
    // check if after this frame a new AAC LATM frame starts
    if (FastLatmCheck(data + frame_size)) {
        return frame_size;
    }

    return 0;
}

///
/// Possible AC-3 frame sizes.
///
/// from ATSC A/52 table 5.18 frame size code table.
///
const uint16_t Ac3FrameSizeTable[38][3] = {
    {64, 69, 96},       {64, 70, 96},       {80, 87, 120},      {80, 88, 120},      {96, 104, 144},
    {96, 105, 144},     {112, 121, 168},    {112, 122, 168},    {128, 139, 192},    {128, 140, 192},
    {160, 174, 240},    {160, 175, 240},    {192, 208, 288},    {192, 209, 288},    {224, 243, 336},
    {224, 244, 336},    {256, 278, 384},    {256, 279, 384},    {320, 348, 480},    {320, 349, 480},
    {384, 417, 576},    {384, 418, 576},    {448, 487, 672},    {448, 488, 672},    {512, 557, 768},
    {512, 558, 768},    {640, 696, 960},    {640, 697, 960},    {768, 835, 1152},   {768, 836, 1152},
    {896, 975, 1344},   {896, 976, 1344},   {1024, 1114, 1536}, {1024, 1115, 1536}, {1152, 1253, 1728},
    {1152, 1254, 1728}, {1280, 1393, 1920}, {1280, 1394, 1920},
};

///
/// Fast check for (E-)AC-3 audio.
///
/// 5 bytes 0x0B77xxxxxx AC-3 audio
///
static inline int FastAc3Check(const uint8_t *p) {
    if (p[0] != 0x0B) { // 16bit sync
        return 0;
    }
    if (p[1] != 0x77) {
        return 0;
    }
    return 1;
}

///
/// Check for (E-)AC-3 audio.
///
/// 0x0B77xxxxxx already checked.
///
/// @param data incomplete PES packet
/// @param size number of bytes
///
/// @retval <0	possible AC-3 audio, but need more data
/// @retval 0	no valid AC-3 audio
/// @retval >0	valid AC-3 audio
///
/// o AC-3 Header
/// AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB CCDDDDDD EEEEEFFF
///
/// o a 16x Frame sync, always 0x0B77
/// o b 16x CRC 16
/// o c 2x  Samplerate
/// o d 6x  Framesize code
/// o e 5x  Bitstream ID
/// o f 3x  Bitstream mode
///
/// o E-AC-3 Header
/// AAAAAAAA AAAAAAAA BBCCCDDD DDDDDDDD EEFFGGGH IIIII...
///
/// o a 16x Frame sync, always 0x0B77
/// o b 2x  Frame type
/// o c 3x  Sub stream ID
/// o d 10x Framesize - 1 in words
/// o e 2x  Framesize code
/// o f 2x  Framesize code 2
///
static int Ac3Check(const uint8_t *data, int size) {
    int frame_size;

    if (size < 5) { // need 5 bytes to see if AC-3/E-AC-3
        return -5;
    }

    if (data[5] > (10 << 3)) {          // E-AC-3
        if ((data[4] & 0xF0) == 0xF0) { // invalid fscod fscod2
            return 0;
        }
        frame_size = ((data[2] & 0x07) << 8) + data[3] + 1;
        frame_size *= 2;
    } else { // AC-3
        int fscod;
        int frmsizcod;

        // crc1 crc1 fscod|frmsizcod
        fscod = data[4] >> 6;
        if (fscod == 0x03) { // invalid sample rate
            return 0;
        }
        frmsizcod = data[4] & 0x3F;
        if (frmsizcod > 37) { // invalid frame size
            return 0;
        }
        // invalid is checked above
        frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2;
    }

    if (frame_size + 5 > size) {
        return -frame_size - 5;
    }
    // FIXME: relaxed checks if codec is already detected
    // check if after this frame a new AC-3 frame starts
    if (FastAc3Check(data + frame_size)) {
        return frame_size;
    }

    return 0;
}

///
/// Fast check for ADTS Audio Data Transport Stream.
///
/// 7/9 bytes 0xFFFxxxxxxxxxxx(xxxx)  ADTS audio
///
static inline int FastAdtsCheck(const uint8_t *p) {
    if (p[0] != 0xFF) { // 12bit sync
        return 0;
    }
    if ((p[1] & 0xF6) != 0xF0) { // sync + layer must be 0
        return 0;
    }
    if ((p[2] & 0x3C) == 0x3C) { // sampling frequency index != 15
        return 0;
    }
    return 1;
}

///
/// Check for ADTS Audio Data Transport Stream.
///
/// 0xFFF already checked.
///
/// @param data incomplete PES packet
/// @param size number of bytes
///
/// @retval <0	possible ADTS audio, but need more data
/// @retval 0	no valid ADTS audio
/// @retval >0	valid AC-3 audio
///
/// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP
/// (QQQQQQQQ QQQQQQQ)
///
/// o A*12  syncword 0xFFF
/// o B*1   MPEG Version: 0 for MPEG-4, 1 for MPEG-2
/// o C*2   layer: always 0
/// o ..
/// o F*4   sampling frequency index (15 is invalid)
/// o ..
/// o M*13  frame length
///
static int AdtsCheck(const uint8_t *data, int size) {
    int frame_size;

    if (size < 6) {
        return -6;
    }

    frame_size = (data[3] & 0x03) << 11;
    frame_size |= (data[4] & 0xFF) << 3;
    frame_size |= (data[5] & 0xE0) >> 5;

    if (frame_size + 3 > size) {
        return -frame_size - 3;
    }
    // check if after this frame a new ADTS frame starts
    if (FastAdtsCheck(data + frame_size)) {
        return frame_size;
    }

    return 0;
}

//////////////////////////////////////////////////////////////////////////////
//  PES Demux
//////////////////////////////////////////////////////////////////////////////

///
/// PES type.
///
enum {
    PES_PROG_STREAM_MAP = 0xBC,
    PES_PRIVATE_STREAM1 = 0xBD,
    PES_PADDING_STREAM = 0xBE, ///< filler, padding stream
    PES_PRIVATE_STREAM2 = 0xBF,
    PES_AUDIO_STREAM_S = 0xC0,
    PES_AUDIO_STREAM_E = 0xDF,
    PES_VIDEO_STREAM_S = 0xE0,
    PES_VIDEO_STREAM_E = 0xEF,
    PES_ECM_STREAM = 0xF0,
    PES_EMM_STREAM = 0xF1,
    PES_DSM_CC_STREAM = 0xF2,
    PES_ISO13522_STREAM = 0xF3,
    PES_TYPE_E_STREAM = 0xF8, ///< ITU-T rec. h.222.1 type E stream
    PES_PROG_STREAM_DIR = 0xFF,
};

#ifndef NO_TS_AUDIO

///
/// PES parser state.
///
enum {
    PES_INIT, ///< unknown codec

    PES_SKIP,    ///< skip packet
    PES_SYNC,    ///< search packet sync byte
    PES_HEADER,  ///< copy header
    PES_START,   ///< pes packet start found
    PES_PAYLOAD, ///< copy payload

    PES_LPCM_HEADER,  ///< copy lcpm header
    PES_LPCM_PAYLOAD, ///< copy lcpm payload
};

#define PES_START_CODE_SIZE 6                       ///< size of pes start code with length
#define PES_HEADER_SIZE 9                           ///< size of pes header
#define PES_MAX_HEADER_SIZE (PES_HEADER_SIZE + 256) ///< maximal header size
#define PES_MAX_PAYLOAD (512 * 1024)                ///< max pay load size

///
/// PES demuxer.
///
typedef struct _pes_demux_ {
    // int Pid;		///< packet id
    // int PcrPid;	///< program clock reference pid
    // int StreamType;	    ///< stream type

    int State;                           ///< parsing state
    uint8_t Header[PES_MAX_HEADER_SIZE]; ///< buffer for pes header
    int HeaderIndex;                     ///< header index
    int HeaderSize;                      ///< size of pes header
    uint8_t *Buffer;                     ///< payload buffer
    int Index;                           ///< buffer index
    int Skip;                            ///< buffer skip
    int Size;                            ///< size of payload buffer

    uint8_t StartCode; ///< pes packet start code

    int64_t PTS; ///< presentation time stamp
    int64_t DTS; ///< decode time stamp
} PesDemux;

///
/// Reset packetized elementary stream demuxer.
///
static void PesReset(PesDemux *pesdx) {
    pesdx->State = PES_INIT;
    pesdx->Index = 0;
    pesdx->Skip = 0;
    pesdx->StartCode = -1;
    pesdx->PTS = AV_NOPTS_VALUE;
    pesdx->DTS = AV_NOPTS_VALUE;
}

///
/// Initialize a packetized elementary stream demuxer.
///
/// @param pesdx    packetized elementary stream demuxer
///
static void PesInit(PesDemux *pesdx) {
    memset(pesdx, 0, sizeof(*pesdx));
    pesdx->Size = PES_MAX_PAYLOAD;
    pesdx->Buffer = av_malloc(PES_MAX_PAYLOAD + AV_INPUT_BUFFER_PADDING_SIZE);
    if (!pesdx->Buffer) {
        Fatal(_("pesdemux: out of memory\n"));
    }
    PesReset(pesdx);
}

///
/// Parse packetized elementary stream.
///
/// @param pesdx    packetized elementary stream demuxer
/// @param data payload data of transport stream
/// @param size number of payload data bytes
/// @param is_start flag, start of pes packet
///
static void PesParse(PesDemux *pesdx, const uint8_t *data, int size, int is_start) {
    const uint8_t *p;
    const uint8_t *q;

    if (is_start) { // start of pes packet
        if (pesdx->Index && pesdx->Skip) {
            // copy remaining bytes down
            pesdx->Index -= pesdx->Skip;
            memmove(pesdx->Buffer, pesdx->Buffer + pesdx->Skip, pesdx->Index);
            pesdx->Skip = 0;
        }
        pesdx->State = PES_SYNC;
        pesdx->HeaderIndex = 0;
        pesdx->PTS = AV_NOPTS_VALUE; // reset if not yet used
        pesdx->DTS = AV_NOPTS_VALUE;
    }
    // cleanup, if too much cruft
    if (pesdx->Skip > PES_MAX_PAYLOAD / 2) {
        // copy remaining bytes down
        pesdx->Index -= pesdx->Skip;
        memmove(pesdx->Buffer, pesdx->Buffer + pesdx->Skip, pesdx->Index);
        pesdx->Skip = 0;
    }

    p = data;
    do {
        int n;

        switch (pesdx->State) {
            case PES_SKIP: // skip this packet
                return;

            case PES_START: // at start of pes packet payload
#if 0
		// Played with PlayAudio
		// FIXME: need 0x80 -- 0xA0 state
		if (AudioCodecID == AV_CODEC_ID_NONE) {
		    if ((*p & 0xF0) == 0x80) {	// AC-3 & DTS
			Debug(3, "pesdemux: dvd ac-3\n");
		    } else if ((*p & 0xFF) == 0xA0) {	// LPCM
			Debug(3, "pesdemux: dvd lpcm\n");
			pesdx->State = PES_LPCM_HEADER;
			pesdx->HeaderIndex = 0;
			pesdx->HeaderSize = 7;
			// FIXME: need harder LPCM check
			//break;
		    }
		}
#endif

            case PES_INIT: // find start of audio packet
                // FIXME: increase if needed the buffer

                // fill buffer
                n = pesdx->Size - pesdx->Index;
                if (n > size) {
                    n = size;
                }
                memcpy(pesdx->Buffer + pesdx->Index, p, n);
                pesdx->Index += n;
                p += n;
                size -= n;

                q = pesdx->Buffer + pesdx->Skip;
                n = pesdx->Index - pesdx->Skip;
                while (n >= 5) {
                    int r = 0;
                    unsigned codec_id = AV_CODEC_ID_NONE;

                    // 4 bytes 0xFFExxxxx Mpeg audio
                    // 5 bytes 0x0B77xxxxxx AC-3 audio
                    // 6 bytes 0x0B77xxxxxxxx E-AC-3 audio
                    // 3 bytes 0x56Exxx AAC LATM audio
                    // 7/9 bytes 0xFFFxxxxxxxxxxx ADTS audio
                    // PCM audio can't be found
                    // FIXME: simple+faster detection, if codec already known
                    if (FastMpegCheck(q)) {
                        r = MpegCheck(q, n);
                        codec_id = AV_CODEC_ID_MP2;
                    }
                    if (!r && FastAc3Check(q)) {
                        r = Ac3Check(q, n);
                        codec_id = AV_CODEC_ID_AC3;
                        if (r > 0 && q[5] > (10 << 3)) {
                            codec_id = AV_CODEC_ID_EAC3;
                        }
                    }
                    if (!r && FastLatmCheck(q)) {
                        r = LatmCheck(q, n);
                        codec_id = AV_CODEC_ID_AAC_LATM;
                    }
                    if (!r && FastAdtsCheck(q)) {
                        r = AdtsCheck(q, n);
                        codec_id = AV_CODEC_ID_AAC;
                    }
                    if (r < 0) { // need more bytes
                        break;
                    }
                    if (r > 0) {
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,33,100)
				        AVPacket avpkt[1];
				        av_init_packet(avpkt);
#else
                        AVPacket * avpkt;
				        avpkt = av_packet_alloc();
#endif

                        // new codec id, close and open new
                        if (AudioCodecID != codec_id) {
                            Debug(3, "pesdemux: new codec %#06x -> %#06x\n", AudioCodecID, codec_id);
                            CodecAudioClose(MyAudioDecoder);
                            CodecAudioOpen(MyAudioDecoder, codec_id);
                            AudioCodecID = codec_id;
                        }
                        avpkt->data = (void *)q;
                        avpkt->size = r;
                        avpkt->pts = pesdx->PTS;
                        avpkt->dts = pesdx->DTS;
                        // FIXME: not aligned for ffmpeg
                        CodecAudioDecode(MyAudioDecoder, avpkt);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58,33,100)
				        av_packet_free(&avpkt);
#endif
                        pesdx->PTS = AV_NOPTS_VALUE;
                        pesdx->DTS = AV_NOPTS_VALUE;
                        pesdx->Skip += r;
                        // FIXME: switch to decoder state
                        // pesdx->State = PES_MPEG_DECODE;
                        break;
                    }
                    if (AudioCodecID != AV_CODEC_ID_NONE) {
                        // shouldn't happen after we have a vaild codec
                        // detected
                        Debug(4, "pesdemux: skip @%d %02x\n", pesdx->Skip, q[0]);
                    }
                    // try next byte
                    ++pesdx->Skip;
                    ++q;
                    --n;
                }
                break;

            case PES_SYNC: // wait for pes sync
                n = PES_START_CODE_SIZE - pesdx->HeaderIndex;
                if (n > size) {
                    n = size;
                }
                memcpy(pesdx->Header + pesdx->HeaderIndex, p, n);
                pesdx->HeaderIndex += n;
                p += n;
                size -= n;

                // have complete packet start code
                if (pesdx->HeaderIndex >= PES_START_CODE_SIZE) {
                    unsigned code;

                    // bad mpeg pes packet start code prefix 0x00001xx
                    if (pesdx->Header[0] || pesdx->Header[1] || pesdx->Header[2] != 0x01) {
                        Debug(3, "pesdemux: bad pes packet\n");
                        pesdx->State = PES_SKIP;
                        return;
                    }
                    code = pesdx->Header[3];
                    if (code != pesdx->StartCode) {
                        Debug(3, "pesdemux: pes start code id %#02x\n", code);
                        // FIXME: need to save start code id?
                        pesdx->StartCode = code;
                        // we could have already detect a valid stream type
                        // don't switch to codec 'none'
                    }

                    pesdx->State = PES_HEADER;
                    pesdx->HeaderSize = PES_HEADER_SIZE;
                }
                break;

            case PES_HEADER: // parse PES header
                n = pesdx->HeaderSize - pesdx->HeaderIndex;
                if (n > size) {
                    n = size;
                }
                memcpy(pesdx->Header + pesdx->HeaderIndex, p, n);
                pesdx->HeaderIndex += n;
                p += n;
                size -= n;

                // have header upto size bits
                if (pesdx->HeaderIndex == PES_HEADER_SIZE) {
                    if ((pesdx->Header[6] & 0xC0) != 0x80) {
                        Error(_("pesdemux: mpeg1 pes packet unsupported\n"));
                        pesdx->State = PES_SKIP;
                        return;
                    }
                    // have pes extension
                    if (!pesdx->Header[8]) {
                        goto empty_header;
                    }
                    pesdx->HeaderSize += pesdx->Header[8];
                    // have complete header
                } else if (pesdx->HeaderIndex == pesdx->HeaderSize) {
                    int64_t pts;
                    int64_t dts;

                    if ((pesdx->Header[7] & 0xC0) == 0x80) {
                        pts = (int64_t)(data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 |
                              data[12] << 7 | (data[13] & 0xFE) >> 1;
                        pesdx->PTS = pts;
                        pesdx->DTS = AV_NOPTS_VALUE;
                    } else if ((pesdx->Header[7] & 0xC0) == 0xC0) {
                        pts = (int64_t)(data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 |
                              data[12] << 7 | (data[13] & 0xFE) >> 1;
                        pesdx->PTS = pts;
                        dts = (int64_t)(data[14] & 0x0E) << 29 | data[15] << 22 | (data[16] & 0xFE) << 14 |
                              data[17] << 7 | (data[18] & 0xFE) >> 1;
                        pesdx->DTS = dts;
                        Debug(4, "pesdemux: pts %#012" PRIx64 " %#012" PRIx64 "\n", pts, dts);
                    }
                empty_header:
                    pesdx->State = PES_INIT;
                    if (pesdx->StartCode == PES_PRIVATE_STREAM1) {
                        // only private stream 1, has sub streams
                        pesdx->State = PES_START;
                    }
                }
                break;

#if 0
		// Played with PlayAudio
	    case PES_LPCM_HEADER:      // lpcm header
		n = pesdx->HeaderSize - pesdx->HeaderIndex;
		if (n > size) {
		    n = size;
		}
		memcpy(pesdx->Header + pesdx->HeaderIndex, p, n);
		pesdx->HeaderIndex += n;
		p += n;
		size -= n;

		if (pesdx->HeaderIndex == pesdx->HeaderSize) {
		    static int samplerates[] = { 48000, 96000, 44100, 32000 };
		    int samplerate;
		    int channels;
		    int bits_per_sample;
		    const uint8_t *q;

		    if (AudioCodecID != AV_CODEC_ID_PCM_DVD) {

			q = pesdx->Header;
			Debug(3, "pesdemux: LPCM %d sr:%d bits:%d chan:%d\n", q[0], q[5] >> 4,
			    (((q[5] >> 6) & 0x3) + 4) * 4, (q[5] & 0x7) + 1);
			CodecAudioClose(MyAudioDecoder);

			bits_per_sample = (((q[5] >> 6) & 0x3) + 4) * 4;
			if (bits_per_sample != 16) {
			    Error(_("softhddev: LPCM %d bits per sample aren't supported\n"), bits_per_sample);
			    // FIXME: handle unsupported formats.
			}
			samplerate = samplerates[q[5] >> 4];
			channels = (q[5] & 0x7) + 1;
			AudioSetup(&samplerate, &channels, 0);
			if (samplerate != samplerates[q[5] >> 4]) {
			    Error(_("softhddev: LPCM %d sample-rate is unsupported\n"), samplerates[q[5] >> 4]);
			    // FIXME: support resample
			}
			if (channels != (q[5] & 0x7) + 1) {
			    Error(_("softhddev: LPCM %d channels are unsupported\n"), (q[5] & 0x7) + 1);
			    // FIXME: support resample
			}
			//CodecAudioOpen(MyAudioDecoder, AV_CODEC_ID_PCM_DVD);
			AudioCodecID = AV_CODEC_ID_PCM_DVD;
		    }
		    pesdx->State = PES_LPCM_PAYLOAD;
		    pesdx->Index = 0;
		    pesdx->Skip = 0;
		}
		break;

	    case PES_LPCM_PAYLOAD:     // lpcm payload
		// fill buffer
		n = pesdx->Size - pesdx->Index;
		if (n > size) {
		    n = size;
		}
		memcpy(pesdx->Buffer + pesdx->Index, p, n);
		pesdx->Index += n;
		p += n;
		size -= n;

		if (pesdx->PTS != (int64_t) AV_NOPTS_VALUE) {
		    // FIXME: needs bigger buffer
		    AudioSetClock(pesdx->PTS);
		    pesdx->PTS = AV_NOPTS_VALUE;
		}
		swab(pesdx->Buffer, pesdx->Buffer, pesdx->Index);
		AudioEnqueue(pesdx->Buffer, pesdx->Index);
		pesdx->Index = 0;
		break;
#endif
        }
    } while (size > 0);
}

//////////////////////////////////////////////////////////////////////////////
//  Transport stream demux
//////////////////////////////////////////////////////////////////////////////

/// Transport stream packet size
#define TS_PACKET_SIZE 188
/// Transport stream packet sync byte
#define TS_PACKET_SYNC 0x47

///
/// transport stream demuxer typedef.
///
typedef struct _ts_demux_ TsDemux;

///
/// transport stream demuxer structure.
///
struct _ts_demux_ {
    int Packets; ///< packets between PCR
};

static PesDemux PesDemuxAudio[1]; ///< audio demuxer

///
/// Transport stream demuxer.
///
/// @param tsdx transport stream demuxer
/// @param data buffer of transport stream packets
/// @param size size of buffer
///
/// @returns number of bytes consumed from buffer.
///
static int TsDemuxer(TsDemux *tsdx, const uint8_t *data, int size) {
    const uint8_t *p;

    p = data;
    while (size >= TS_PACKET_SIZE) {
#ifdef DEBUG
        int pid;
#endif
        int payload;

        if (p[0] != TS_PACKET_SYNC) {
            Error(_("tsdemux: transport stream out of sync\n"));
            // FIXME: kill all buffers
            return size;
        }
        ++tsdx->Packets;
        if (p[1] & 0x80) { // error indicator
            Debug(3, "tsdemux: transport error\n");
            // FIXME: kill all buffers
            goto next_packet;
        }
#ifdef DEBUG
        pid = (p[1] & 0x1F) << 8 | p[2];
        Debug(4, "tsdemux: PID: %#04x%s%s\n", pid, p[1] & 0x40 ? " start" : "", p[3] & 0x10 ? " payload" : "");
#endif
        // skip adaptation field
        switch (p[3] & 0x30) { // adaption field
            case 0x00:         // reserved
            case 0x20:         // adaptation field only
            default:
                goto next_packet;
            case 0x10: // only payload
                payload = 4;
                break;
            case 0x30: // skip adapation field
                payload = 5 + p[4];
                // illegal length, ignore packet
                if (payload >= TS_PACKET_SIZE) {
                    Debug(3, "tsdemux: illegal adaption field length\n");
                    goto next_packet;
                }
                break;
        }

        PesParse(PesDemuxAudio, p + payload, TS_PACKET_SIZE - payload, p[1] & 0x40);
#if 0
	int tmp;

	// check continuity
	tmp = p[3] & 0x0F;		// continuity counter
	if (((tsdx->CC + 1) & 0x0F) != tmp) {
	    Debug(3, "tsdemux: OUT OF SYNC: %d %d\n", tmp, tsdx->CC);
	    // TS discontinuity (received 8, expected 0) for PID
	}
	tsdx->CC = tmp;
#endif

    next_packet:
        p += TS_PACKET_SIZE;
        size -= TS_PACKET_SIZE;
    }

    return p - data;
}

#endif

/**
**  Play audio packet.
**
**  @param data data of exactly one complete PES packet
**  @param size size of PES packet
**  @param id	PES packet type
*/
int PlayAudio(const uint8_t *data, int size, uint8_t id) {
    int n;
    const uint8_t *p;

    // channel switch: SetAudioChannelDevice: SetDigitalAudioDevice:

    if (SkipAudio || !MyAudioDecoder) { // skip audio
        return size;
    }
    if (StreamFreezed) { // stream freezed
        return 0;
    }
    if (AudioDelay) {
        Debug(3, "AudioDelay %dms\n", AudioDelay);
        usleep(AudioDelay / 90);
        AudioDelay = 0;
        return 0;
    }
    if (NewAudioStream) {
        // this clears the audio ringbuffer indirect, open and setup does it
        CodecAudioClose(MyAudioDecoder);
        AudioFlushBuffers();
        AudioSetBufferTime(ConfigAudioBufferTime);
        AudioCodecID = AV_CODEC_ID_NONE;
        AudioChannelID = -1;
        NewAudioStream = 0;
    }
    // hard limit buffer full: don't overrun audio buffers on replay
    if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
        return 0;
    }
#ifdef USE_SOFTLIMIT
    // soft limit buffer full
    if (AudioSyncStream && VideoGetBuffers(AudioSyncStream) > 3 && AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE * 2) {
        return 0;
    }
#endif
    // PES header 0x00 0x00 0x01 ID
    // ID 0xBD 0xC0-0xCF

    // must be a PES start code
    if (size < 9 || !data || data[0] || data[1] || data[2] != 0x01) {
        Error(_("[softhddev] invalid PES audio packet\n"));
        return size;
    }
    n = data[8]; // header size

    if (size < 9 + n + 4) { // wrong size
        if (size == 9 + n) {
            Warning(_("[softhddev] empty audio packet\n"));
        } else {
            Error(_("[softhddev] invalid audio packet %d bytes\n"), size);
        }
        return size;
    }

    if (data[7] & 0x80 && n >= 5) {
        AudioAvPkt->pts = (int64_t)(data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 | data[12] << 7 |
                          (data[13] & 0xFE) >> 1;
        // Debug(3, "audio: pts %#012" PRIx64 "\n", AudioAvPkt->pts);
    }
    if (0) { // dts is unused
        if (data[7] & 0x40) {
            AudioAvPkt->dts = (int64_t)(data[14] & 0x0E) << 29 | data[15] << 22 | (data[16] & 0xFE) << 14 |
                              data[17] << 7 | (data[18] & 0xFE) >> 1;
            Debug(3, "audio: dts %#012" PRIx64 "\n", AudioAvPkt->dts);
        }
    }

    p = data + 9 + n;
    n = size - 9 - n; // skip pes header
    if (n + AudioAvPkt->stream_index > AudioAvPkt->size) {
        Fatal(_("[softhddev] audio buffer too small\n"));
        AudioAvPkt->stream_index = 0;
    }

    if (AudioChannelID != id) { // id changed audio track changed
        AudioChannelID = id;
        AudioCodecID = AV_CODEC_ID_NONE;
        Debug(3, "audio/demux: new channel id\n");
    }
    // Private stream + LPCM ID
    if ((id & 0xF0) == 0xA0) {
        if (n < 7) {
            Error(_("[softhddev] invalid LPCM audio packet %d bytes\n"), size);
            return size;
        }
        if (AudioCodecID != AV_CODEC_ID_PCM_DVD) {
            static int samplerates[] = {48000, 96000, 44100, 32000};
            int samplerate;
            int channels;
            int bits_per_sample;

            Debug(3, "[softhddev]%s: LPCM %d sr:%d bits:%d chan:%d\n", __FUNCTION__, id, p[5] >> 4,
                  (((p[5] >> 6) & 0x3) + 4) * 4, (p[5] & 0x7) + 1);
            CodecAudioClose(MyAudioDecoder);

            bits_per_sample = (((p[5] >> 6) & 0x3) + 4) * 4;
            if (bits_per_sample != 16) {
                Error(_("[softhddev] LPCM %d bits per sample aren't supported\n"), bits_per_sample);
                // FIXME: handle unsupported formats.
            }
            samplerate = samplerates[p[5] >> 4];
            channels = (p[5] & 0x7) + 1;

            // FIXME: ConfigAudioBufferTime + x
            AudioSetBufferTime(400);
            AudioSetup(&samplerate, &channels, 0);
            if (samplerate != samplerates[p[5] >> 4]) {
                Error(_("[softhddev] LPCM %d sample-rate is unsupported\n"), samplerates[p[5] >> 4]);
                // FIXME: support resample
            }
            if (channels != (p[5] & 0x7) + 1) {
                Error(_("[softhddev] LPCM %d channels are unsupported\n"), (p[5] & 0x7) + 1);
                // FIXME: support resample
            }
            // CodecAudioOpen(MyAudioDecoder, AV_CODEC_ID_PCM_DVD);
            AudioCodecID = AV_CODEC_ID_PCM_DVD;
        }

        if (AudioAvPkt->pts != (int64_t)AV_NOPTS_VALUE) {
            AudioSetClock(AudioAvPkt->pts);
            AudioAvPkt->pts = AV_NOPTS_VALUE;
        }
        swab(p + 7, AudioAvPkt->data, n - 7);
        AudioEnqueue(AudioAvPkt->data, n - 7);

        return size;
    }
    // DVD track header
    if ((id & 0xF0) == 0x80 && (p[0] & 0xF0) == 0x80) {
        p += 4;
        n -= 4; // skip track header
        if (AudioCodecID == AV_CODEC_ID_NONE) {
            // FIXME: ConfigAudioBufferTime + x
            AudioSetBufferTime(400);
        }
    }
    // append new packet, to partial old data
    memcpy(AudioAvPkt->data + AudioAvPkt->stream_index, p, n);
    AudioAvPkt->stream_index += n;

    n = AudioAvPkt->stream_index;
    p = AudioAvPkt->data;
    while (n >= 5) {
        int r;
        unsigned codec_id;

        // 4 bytes 0xFFExxxxx Mpeg audio
        // 3 bytes 0x56Exxx AAC LATM audio
        // 5 bytes 0x0B77xxxxxx AC-3 audio
        // 6 bytes 0x0B77xxxxxxxx E-AC-3 audio
        // 7/9 bytes 0xFFFxxxxxxxxxxx ADTS audio
        // PCM audio can't be found
        r = 0;
        codec_id = AV_CODEC_ID_NONE; // keep compiler happy
        if (id != 0xbd && FastMpegCheck(p)) {
            r = MpegCheck(p, n);
            codec_id = AV_CODEC_ID_MP2;
        }
        if (id != 0xbd && !r && FastLatmCheck(p)) {
            r = LatmCheck(p, n);
            codec_id = AV_CODEC_ID_AAC_LATM;
        }
        if ((id == 0xbd || (id & 0xF0) == 0x80) && !r && FastAc3Check(p)) {
            r = Ac3Check(p, n);
            codec_id = AV_CODEC_ID_AC3;
            if (r > 0 && p[5] > (10 << 3)) {
                codec_id = AV_CODEC_ID_EAC3;
            }
            /* faster ac3 detection at end of pes packet (no improvemnts)
               if (AudioCodecID == codec_id && -r - 2 == n) {
               r = n;
               }
             */
        }
        if (id != 0xbd && !r && FastAdtsCheck(p)) {
            r = AdtsCheck(p, n);
            codec_id = AV_CODEC_ID_AAC;
        }
        if (r < 0) { // need more bytes
            break;
        }
        if (r > 0) {
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,33,100)
	        AVPacket avpkt[1];
	        av_init_packet(avpkt);
#else
	        AVPacket * avpkt;
	        avpkt = av_packet_alloc();
#endif
            // new codec id, close and open new
            if (AudioCodecID != codec_id) {
                CodecAudioClose(MyAudioDecoder);
                CodecAudioOpen(MyAudioDecoder, codec_id);
                AudioCodecID = codec_id;
            }
            avpkt->data = (void *)p;
            avpkt->size = r;
            avpkt->pts = AudioAvPkt->pts;
            avpkt->dts = AudioAvPkt->dts;
            // FIXME: not aligned for ffmpeg
            CodecAudioDecode(MyAudioDecoder, avpkt);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58,33,100)
	        av_packet_free(&avpkt);
#endif
            AudioAvPkt->pts = AV_NOPTS_VALUE;
            AudioAvPkt->dts = AV_NOPTS_VALUE;
            p += r;
            n -= r;
            continue;
        }
        ++p;
        --n;
    }

    // copy remaining bytes to start of packet
    if (n) {
        memmove(AudioAvPkt->data, p, n);
    }
    AudioAvPkt->stream_index = n;

    return size;
}

#ifndef NO_TS_AUDIO

/**
**  Play transport stream audio packet.
**
**  VDR can have buffered data belonging to previous channel!
**
**  @param data data of exactly one complete TS packet
**  @param size size of TS packet (always TS_PACKET_SIZE)
**
**  @returns number of bytes consumed;
*/
int PlayTsAudio(const uint8_t *data, int size) {
    static TsDemux tsdx[1];

    if (SkipAudio || !MyAudioDecoder) { // skip audio
        return size;
    }
    if (StreamFreezed) { // stream freezed
        return 0;
    }

    if (NewAudioStream) {
        // this clears the audio ringbuffer indirect, open and setup does it
        CodecAudioClose(MyAudioDecoder);
        AudioFlushBuffers();
        // max time between audio packets 200ms + 24ms hw buffer
        AudioSetBufferTime(ConfigAudioBufferTime);
        AudioCodecID = AV_CODEC_ID_NONE;
        AudioChannelID = -1;
        NewAudioStream = 0;
        PesReset(PesDemuxAudio);
    }
    // hard limit buffer full: don't overrun audio buffers on replay
    if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
        return 0;
    }
#ifdef USE_SOFTLIMIT
    // soft limit buffer full
    if (AudioSyncStream && VideoGetBuffers(AudioSyncStream) > 3 && AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE * 2) {
        return 0;
    }
#endif
    if (AudioDelay) {
        Debug(3, "AudioDelay %dms\n", AudioDelay);
        usleep(AudioDelay * 1000);
        AudioDelay = 0;
        // TsDemuxer(tsdx, data, size);	  // insert dummy audio
    }
    return TsDemuxer(tsdx, data, size);
}

#endif

/**
**  Set volume of audio device.
**
**  @param volume   VDR volume (0 .. 255)
*/
void SetVolumeDevice(int volume) { AudioSetVolume((volume * 1000) / 255); }

/**
*** Resets channel ID (restarts audio).
**/
void ResetChannelId(void) {
    AudioChannelID = -1;
    Debug(3, "audio/demux: reset channel id\n");
}

//////////////////////////////////////////////////////////////////////////////
//  Video
//////////////////////////////////////////////////////////////////////////////

#define VIDEO_BUFFER_SIZE (1024 * 1024) ///< video PES buffer default size  512 * 1024
#define VIDEO_PACKET_MAX 256            ///< max number of video packets  192

/**
**  Video output stream device structure.   Parser, decoder, display.
*/
struct __video_stream__ {
    VideoHwDecoder *HwDecoder;        ///< video hardware decoder
    VideoDecoder *Decoder;            ///< video decoder
    pthread_mutex_t DecoderLockMutex; ///< video decoder lock mutex

    enum AVCodecID CodecID;     ///< current codec id
    enum AVCodecID LastCodecID; ///< last codec id

    volatile char NewStream;     ///< flag new video stream
    volatile char ClosingStream; ///< flag closing video stream
    volatile char SkipStream;    ///< skip video stream
    volatile char Freezed;       ///< stream freezed

    volatile char TrickSpeed;   ///< current trick speed
    volatile char Close;        ///< command close video stream
    volatile char ClearBuffers; ///< command clear video buffers
    volatile char ClearClose;   ///< clear video buffers for close

    int InvalidPesCounter; ///< counter of invalid PES packets

    enum AVCodecID CodecIDRb[VIDEO_PACKET_MAX]; ///< codec ids in ring buffer
    AVPacket PacketRb[VIDEO_PACKET_MAX];        ///< PES packet ring buffer
    int StartCodeState;                         ///< last three bytes start code state

    int PacketWrite;        ///< ring buffer write pointer
    int PacketRead;         ///< ring buffer read pointer
    atomic_t PacketsFilled; ///< how many of the ring buffer is used
};

static VideoStream MyVideoStream[1]; ///< normal video stream

#ifdef USE_PIP
static VideoStream PipVideoStream[1];         ///< pip video stream
static int PiPActive = 0, mwx, mwy, mww, mwh; ///< main window frame for PiP
#endif

#ifdef DEBUG
uint32_t VideoSwitch;          ///< debug video switch ticks
static int VideoMaxPacketSize; ///< biggest used packet buffer
#endif
// #define STILL_DEBUG 2
#ifdef STILL_DEBUG
static char InStillPicture; ///< flag still picture
#endif

const char *X11DisplayName;      ///< x11 display name
static volatile char Usr1Signal; ///< true got usr1 signal

//////////////////////////////////////////////////////////////////////////////

/**
**  Initialize video packet ringbuffer.
**
**  @param stream   video stream
*/
static void VideoPacketInit(VideoStream *stream) {
    int i;

    for (i = 0; i < VIDEO_PACKET_MAX; ++i) {
        AVPacket *avpkt;

        avpkt = &stream->PacketRb[i];
        // build a clean ffmpeg av packet
        if (av_new_packet(avpkt, VIDEO_BUFFER_SIZE)) {
            Fatal(_("[softhddev] out of memory\n"));
        }
    }

    atomic_set(&stream->PacketsFilled, 0);
    stream->PacketRead = stream->PacketWrite = 0;
}

/**
**  Cleanup video packet ringbuffer.
**
**  @param stream   video stream
*/
static void VideoPacketExit(VideoStream *stream) {
    int i;

    atomic_set(&stream->PacketsFilled, 0);

    for (i = 0; i < VIDEO_PACKET_MAX; ++i) {
        av_packet_unref(&stream->PacketRb[i]);
    }
}

/**
**  Place video data in packet ringbuffer.
**
**  @param stream   video stream
**  @param pts	presentation timestamp of pes packet
**  @param data data of pes packet
**  @param size size of pes packet
*/
static void VideoEnqueue(VideoStream *stream, int64_t pts, int64_t dts, const void *data, int size) {
    AVPacket *avpkt;

    // Debug(3, "video: enqueue %d\n", size);

    avpkt = &stream->PacketRb[stream->PacketWrite];
    if (!avpkt->stream_index) { // add pts only for first added
        avpkt->pts = pts;
        avpkt->dts = dts;
    }

    if (avpkt->stream_index + size >= avpkt->size) {

        // Warning(_("video: packet buffer too small for %d\n"),
        // avpkt->stream_index + size);

        // new + grow reserves FF_INPUT_BUFFER_PADDING_SIZE
        av_grow_packet(avpkt, ((size + VIDEO_BUFFER_SIZE / 2) / (VIDEO_BUFFER_SIZE / 2)) * (VIDEO_BUFFER_SIZE / 2));
        // FIXME: out of memory!
#ifdef DEBUG
        if (avpkt->size <= avpkt->stream_index + size) {
            fprintf(stderr, "%d %d %d\n", avpkt->size, avpkt->stream_index, size);
            fflush(stderr);
            abort();
        }
#endif
    }

    memcpy(avpkt->data + avpkt->stream_index, data, size);
    avpkt->stream_index += size;
#ifdef DEBUG
    if (avpkt->stream_index > VideoMaxPacketSize) {
        VideoMaxPacketSize = avpkt->stream_index;
        Debug(4, "video: max used PES packet size: %d\n", VideoMaxPacketSize);
    }
#endif
}

/**
**  Reset current packet.
**
**  @param stream   video stream
*/
static void VideoResetPacket(VideoStream *stream) {
    AVPacket *avpkt;

    stream->StartCodeState = 0; // reset start code state

    stream->CodecIDRb[stream->PacketWrite] = AV_CODEC_ID_NONE;
    avpkt = &stream->PacketRb[stream->PacketWrite];
    avpkt->stream_index = 0;
    avpkt->pts = AV_NOPTS_VALUE;
    avpkt->dts = AV_NOPTS_VALUE;
}

/**
**  Finish current packet advance to next.
**
**  @param stream   video stream
**  @param codec_id codec id of packet (MPEG/H264)
*/
static void VideoNextPacket(VideoStream *stream, int codec_id) {
    AVPacket *avpkt;

    avpkt = &stream->PacketRb[stream->PacketWrite];
    if (!avpkt->stream_index) { // ignore empty packets
        if (codec_id != AV_CODEC_ID_NONE) {
            return;
        }
        Debug(3, "video: possible stream change loss\n");
    }

    if (atomic_read(&stream->PacketsFilled) >= VIDEO_PACKET_MAX - 1) {
        // no free slot available drop last packet
        Error(_("video: no empty slot in packet ringbuffer\n"));
        avpkt->stream_index = 0;
        if (codec_id == AV_CODEC_ID_NONE) {
            Debug(3, "video: possible stream change loss\n");
        }
        return;
    }
    // clear area for decoder, always enough space allocated
    memset(avpkt->data + avpkt->stream_index, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    stream->CodecIDRb[stream->PacketWrite] = codec_id;
    // DumpH264(avpkt->data, avpkt->stream_index);

    // advance packet write
    stream->PacketWrite = (stream->PacketWrite + 1) % VIDEO_PACKET_MAX;
    atomic_inc(&stream->PacketsFilled);
    VideoDisplayWakeup();

    // intialize next package to use
    VideoResetPacket(stream);
}

#if defined(USE_PIP) || defined(VAAPI)

/**
**  Place mpeg video data in packet ringbuffer.
**
**  Some tv-stations sends mulitple pictures in a single PES packet.
**  Split the packet into single picture packets.
**  Nick/CC, Viva, MediaShop, Deutsches Music Fernsehen
**
**  FIXME: this code can be written much faster
**
**  @param stream   video stream
**  @param pts	presentation timestamp of pes packet
**  @param data data of pes packet
**  @param size size of pes packet
*/
static void VideoMpegEnqueue(VideoStream *stream, int64_t pts, int64_t dts, const uint8_t *data, int size) {
    static const char startcode[3] = {0x00, 0x00, 0x01};
    const uint8_t *p;
    int n;
    int first;

    // first scan
    first = !stream->PacketRb[stream->PacketWrite].stream_index;
    p = data;
    n = size;

#ifdef DEBUG
    if (n < 4) {
        // is a problem with the pes start code detection
        Error(_("[softhddev] too short PES video packet\n"));
        fprintf(stderr, "[softhddev] too short PES video packet\n");
    }
#endif

    switch (stream->StartCodeState) { // prefix starting in last packet
        case 3:                       // 0x00 0x00 0x01 seen
#ifdef DEBUG
            fprintf(stderr, "last: %d\n", stream->StartCodeState);
#endif
            if (!p[0] || p[0] == 0xb3) {
#ifdef DEBUG
                printf("last: %d start	aspect %02x\n", stream->StartCodeState, p[4]);
#endif
                stream->PacketRb[stream->PacketWrite].stream_index -= 3;
                VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
                VideoEnqueue(stream, pts, dts, startcode, 3);
                first = p[0] == 0xb3;
                p++;
                n--;
                pts = AV_NOPTS_VALUE;
            }

            break;
        case 2: // 0x00 0x00 seen
#ifdef DEBUG
            fprintf(stderr, "last: %d\n", stream->StartCodeState);
#endif
            if (p[0] == 0x01 && (!p[1] || p[1] == 0xb3)) {
#ifdef DEBUG
                printf("last: %d start	aspect %02x\n", stream->StartCodeState, p[5]);
#endif
                stream->PacketRb[stream->PacketWrite].stream_index -= 2;
                VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
                VideoEnqueue(stream, pts, dts, startcode, 2);
                first = p[1] == 0xb3;
                p += 2;
                n -= 2;
                pts = AV_NOPTS_VALUE;
            }
            break;
        case 1: // 0x00 seen
#ifdef DEBUG
            fprintf(stderr, "last: %d\n", stream->StartCodeState);
#endif
            if (!p[0] && p[1] == 0x01 && (!p[2] || p[2] == 0xb3)) {
#ifdef DEBUG
                printf("last: %d start	aspect %02x\n", stream->StartCodeState, p[6]);
#endif
                stream->PacketRb[stream->PacketWrite].stream_index -= 1;
                VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
                VideoEnqueue(stream, pts, dts, startcode, 1);
                first = p[2] == 0xb3;
                p += 3;
                n -= 3;
                pts = AV_NOPTS_VALUE;
            }
        case 0:
            break;
    }

    // b3 b4 b8 00 b5 ... 00 b5 ...

    while (n > 3) {
        if (0 && !p[0] && !p[1] && p[2] == 0x01) {
            fprintf(stderr, " %02x", p[3]);
        }
        // scan for picture header 0x00000100
        // FIXME: not perfect, must split at 0xb3 also
        if (!p[0] && !p[1] && p[2] == 0x01 && !p[3]) {
            if (first) {
                first = 0;
                n -= 4;
                p += 4;
                continue;
            }
            // packet has already an picture header
            /*
               fprintf(stderr, "\nfix:%9d,%02x%02x%02x %02x ", n,
               p[0], p[1], p[2], p[3]);
             */
            // first packet goes only upto picture header
            VideoEnqueue(stream, pts, dts, data, p - data);
            VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
#ifdef DEBUG
            fprintf(stderr, "fix\r");
#endif
            data = p;
            size = n;

            // time-stamp only valid for first packet
            pts = AV_NOPTS_VALUE;
            n -= 4;
            p += 4;
            continue;
        }
        if (!p[0] && !p[1] && p[2] == 0x01 && p[3] == 0xb3) {
            // printf("aspectratio %02x\n",p[7]>>4);
        }
        --n;
        ++p;
    }

    stream->StartCodeState = 0;
    switch (n) { // handle packet border start code
        case 3:
            if (!p[0] && !p[1] && p[2] == 0x01) {
                stream->StartCodeState = 3;
            }
            break;
        case 2:
            if (!p[0] && !p[1]) {
                stream->StartCodeState = 2;
            }
            break;
        case 1:
            if (!p[0]) {
                stream->StartCodeState = 1;
            }
            break;
        case 0:
            break;
    }
    VideoEnqueue(stream, pts, dts, data, size);
}

#endif

/**
**  Fix packet for FFMpeg.
**
**  Some tv-stations sends mulitple pictures in a single PES packet.
**  Current ffmpeg 0.10 and libav-0.8 has problems with this.
**  Split the packet into single picture packets.
**
**  FIXME: there are stations which have multiple pictures and
**  the last picture incomplete in the PES packet.
**
**  FIXME: move function call into PlayVideo, than the hardware
**  decoder didn't need to support multiple frames decoding.
**
**  @param avpkt    ffmpeg a/v packet
*/

#if !defined USE_PIP &&  !defined VAAPI
static void FixPacketForFFMpeg(VideoDecoder *vdecoder, AVPacket *avpkt) {
    uint8_t *p;
    int n;
    AVPacket tmp[1];
    int first;

    p = avpkt->data;
    n = avpkt->size;
    *tmp = *avpkt;

    first = 1;
#if STILL_DEBUG > 1
    if (InStillPicture) {
        fprintf(stderr, "fix(%d): ", n);
    }
#endif

    while (n > 3) {
#if STILL_DEBUG > 1
        if (InStillPicture && !p[0] && !p[1] && p[2] == 0x01) {
            fprintf(stderr, " %02x", p[3]);
        }
#endif
        // scan for picture header 0x00000100
        if (!p[0] && !p[1] && p[2] == 0x01 && !p[3]) {
            if (first) {
                first = 0;
                n -= 4;
                p += 4;
                continue;
            }
            // packet has already an picture header
            tmp->size = p - tmp->data;
#if STILL_DEBUG > 1
            if (InStillPicture) {
                fprintf(stderr, "\nfix:%9d,%02x %02x %02x %02x\n", tmp->size, tmp->data[0], tmp->data[1], tmp->data[2],
                        tmp->data[3]);
            }
#endif
            CodecVideoDecode(vdecoder, tmp);
            // time-stamp only valid for first packet
            tmp->pts = AV_NOPTS_VALUE;
            tmp->dts = AV_NOPTS_VALUE;
            tmp->data = p;
            tmp->size = n;
        }
        --n;
        ++p;
    }

#if STILL_DEBUG > 1
    if (InStillPicture) {
        fprintf(stderr, "\nfix:%9d.%02x %02x %02x %02x\n", tmp->size, tmp->data[0], tmp->data[1], tmp->data[2],
                tmp->data[3]);
    }
#endif
    CodecVideoDecode(vdecoder, tmp);
}
#endif

/**
**  Open video stream.
**
**  @param stream   video stream
*/
static void VideoStreamOpen(VideoStream *stream) {
    stream->SkipStream = 1;
    stream->CodecID = AV_CODEC_ID_NONE;
    stream->LastCodecID = AV_CODEC_ID_NONE;
    if ((stream->HwDecoder = VideoNewHwDecoder(stream))) {
        stream->Decoder = CodecVideoNewDecoder(stream->HwDecoder);
        VideoPacketInit(stream);
        stream->SkipStream = 0;
    }
}

/**
**  Close video stream.
**
**  @param stream   video stream
**  @param delhw    flag delete hardware decoder
**
**  @note must be called from the video thread, otherwise xcb has a
**  deadlock.
*/
static void VideoStreamClose(VideoStream *stream, int delhw) {
    stream->SkipStream = 1;
    if (stream->Decoder) {
        VideoDecoder *decoder;

        Debug(3, "VideoStreamClose");
        decoder = stream->Decoder;
        // FIXME: remove this lock for main stream close
        pthread_mutex_lock(&stream->DecoderLockMutex);
        stream->Decoder = NULL; // lock read thread
        pthread_mutex_unlock(&stream->DecoderLockMutex);
        CodecVideoClose(decoder);
        CodecVideoDelDecoder(decoder);
    }
    if (stream->HwDecoder) {
        if (delhw) {
            VideoDelHwDecoder(stream->HwDecoder);
        }
        stream->HwDecoder = NULL;
        // FIXME: CodecVideoClose calls/uses hw decoder
    }
    VideoPacketExit(stream);

    stream->NewStream = 1;
    stream->InvalidPesCounter = 0;
}

/**
**  Poll PES packet ringbuffer.
**
**  Called if video frame buffers are full.
**
**  @param stream   video stream
**
**  @retval 1	something todo
**  @retval -1	empty stream
*/
int VideoPollInput(VideoStream *stream) {
    if (!stream->Decoder || !stream->HwDecoder) { // closing
#ifdef DEBUG
        fprintf(stderr, "no decoder\n");
#endif
        return -1;
    }

    if (stream->Close) { // close stream request
        VideoStreamClose(stream, 1);
        stream->Close = 0;
        return 1;
    }
    if (stream->ClearBuffers) { // clear buffer request
        atomic_set(&stream->PacketsFilled, 0);
        stream->PacketRead = stream->PacketWrite;
        // FIXME: ->Decoder already checked
        Debug(3, "Clear buffer request in Poll\n");
        if (stream->Decoder && stream->HwDecoder) {
            CodecVideoFlushBuffers(stream->Decoder);
            //	      VideoResetStart(stream->HwDecoder);
        }
        stream->ClearBuffers = 0;
        return 1;
    }
    if (!atomic_read(&stream->PacketsFilled)) {
        return -1;
    }
    return 1;
}

/**
**  Decode from PES packet ringbuffer.
**
**  @param stream   video stream
**
**  @retval 0	packet decoded
**  @retval 1	stream paused
**  @retval -1	empty stream
*/
int VideoDecodeInput(VideoStream *stream, int trick) {
    int filled;
    AVPacket *avpkt;
    int saved_size;

    if (!stream->Decoder) { // closing
#ifdef DEBUG
        fprintf(stderr, "no decoder\n");
#endif
        return -1;
    }

    if (stream->Close) { // close stream request
        VideoStreamClose(stream, 1);
        stream->Close = 0;
        return 1;
    }
    if (stream->ClearBuffers && trick)
        stream->ClearBuffers = 0;

    if (stream->ClearBuffers) { // clear buffer request
        atomic_set(&stream->PacketsFilled, 0);
        stream->PacketRead = stream->PacketWrite;
        // FIXME: ->Decoder already checked
        if (stream->Decoder) {
            CodecVideoFlushBuffers(stream->Decoder);
            Debug(3, "Clear buffer request in Decode\n");
            VideoResetStart(stream->HwDecoder);
        }
        stream->ClearBuffers = 0;
        return 1;
    }
    if (stream->Freezed) { // stream freezed
        // clear is called during freezed
        return 1;
    }

    filled = atomic_read(&stream->PacketsFilled);
    // printf("Packets in Decode %d\n",filled);
    if (!filled) {
        return -1;
    }
#if 0
    // clearing for normal channel switch has no advantage
    if (stream->ClearClose || stream->ClosingStream) {
	int f;

	// FIXME: during replay all packets are always checked

	// flush buffers, if close is in the queue
	for (f = 0; f < filled; ++f) {
	    if (stream->CodecIDRb[(stream->PacketRead + f) % VIDEO_PACKET_MAX] == AV_CODEC_ID_NONE) {
		if (f) {
		    Debug(3, "video: cleared upto close\n");
		    atomic_sub(f, &stream->PacketsFilled);
		    stream->PacketRead = (stream->PacketRead + f) % VIDEO_PACKET_MAX;
		    stream->ClearClose = 0;
		}
		break;
	    }
	}
	stream->ClosingStream = 0;
    }
#endif

    //
    //	handle queued commands
    //
    avpkt = &stream->PacketRb[stream->PacketRead];
    switch (stream->CodecIDRb[stream->PacketRead]) {
        case AV_CODEC_ID_NONE:
            stream->ClosingStream = 0;
            if (stream->LastCodecID != AV_CODEC_ID_NONE) {
                Debug(3, "in VideoDecode make close\n");
                stream->LastCodecID = AV_CODEC_ID_NONE;
                CodecVideoClose(stream->Decoder);
                // FIXME: CodecVideoClose calls/uses hw decoder
                goto skip;
            }
            // FIXME: look if more close are in the queue
            // size can be zero
            goto skip;
        case AV_CODEC_ID_MPEG2VIDEO:
            if (stream->LastCodecID != AV_CODEC_ID_MPEG2VIDEO) {
                stream->LastCodecID = AV_CODEC_ID_MPEG2VIDEO;
                CodecVideoOpen(stream->Decoder, AV_CODEC_ID_MPEG2VIDEO);
            }
            break;
        case AV_CODEC_ID_H264:
            if (stream->LastCodecID != AV_CODEC_ID_H264) {
                Debug(3, "CodecVideoOpen h264\n");
                stream->LastCodecID = AV_CODEC_ID_H264;
                CodecVideoOpen(stream->Decoder, AV_CODEC_ID_H264);
            }
            break;
        case AV_CODEC_ID_HEVC:
            if (stream->LastCodecID != AV_CODEC_ID_HEVC) {
                stream->LastCodecID = AV_CODEC_ID_HEVC;
                CodecVideoOpen(stream->Decoder, AV_CODEC_ID_HEVC);
            }
            break;
        default:
            break;
    }

    // avcodec_decode_video2 needs size
    saved_size = avpkt->size;
    avpkt->size = avpkt->stream_index;
    avpkt->stream_index = 0;

#if defined(USE_PIP) || defined(VAAPI)
    // fprintf(stderr, "[");
    // DumpMpeg(avpkt->data, avpkt->size);
#ifdef STILL_DEBUG
    if (InStillPicture) {
        DumpMpeg(avpkt->data, avpkt->size);
    }
#endif
    // lock decoder against close
    pthread_mutex_lock(&stream->DecoderLockMutex);
    if (stream->Decoder) {
        CodecVideoDecode(stream->Decoder, avpkt);
    }
    pthread_mutex_unlock(&stream->DecoderLockMutex);
    // fprintf(stderr, "]\n");
#else
    // old version
    if (stream->LastCodecID == AV_CODEC_ID_MPEG2VIDEO) {
        FixPacketForFFMpeg(stream->Decoder, avpkt);
    } else {
        CodecVideoDecode(stream->Decoder, avpkt);
    }
#endif

    avpkt->size = saved_size;

skip:
    // advance packet read
    stream->PacketRead = (stream->PacketRead + 1) % VIDEO_PACKET_MAX;
    atomic_dec(&stream->PacketsFilled);

    return 0;
}

/**
**  Get number of video buffers.
**
**  @param stream   video stream
*/
int VideoGetBuffers(const VideoStream *stream) { return atomic_read(&stream->PacketsFilled); }

/**
**  Try video start.
**
**  NOT TRUE: Could be called, when already started.
*/
static void StartVideo(void) {
    VideoInit(X11DisplayName);

    if (ConfigFullscreen) {
        // FIXME: not good looking, mapped and then resized.
        VideoSetFullscreen(1);
    }
    VideoOsdInit();
    if (!MyVideoStream->Decoder) {
        VideoStreamOpen(MyVideoStream);
        AudioSyncStream = MyVideoStream;
    }
}

/**
**  Stop video.
*/
static void StopVideo(void) {
    VideoOsdExit();
    VideoExit();
    AudioSyncStream = NULL;
#if 1
    // FIXME: done by exit: VideoDelHwDecoder(MyVideoStream->HwDecoder);
    VideoStreamClose(MyVideoStream, 0);
#else
    MyVideoStream->SkipStream = 1;
    if (MyVideoStream->Decoder) {
        VideoDecoder *decoder;

        decoder = MyVideoStream->Decoder;
        pthread_mutex_lock(&MyVideoStream->DecoderLockMutex);
        MyVideoStream->Decoder = NULL; // lock read thread
        pthread_mutex_unlock(&MyVideoStream->DecoderLockMutex);
        // FIXME: this can crash, hw decoder released by video exit
        Debug(3, "in Stop Video");
        CodecVideoClose(decoder);
        CodecVideoDelDecoder(decoder);
    }
    if (MyVideoStream->HwDecoder) {
        // done by exit: VideoDelHwDecoder(MyVideoStream->HwDecoder);
        MyVideoStream->HwDecoder = NULL;
    }
    VideoPacketExit(MyVideoStream);

    MyVideoStream->NewStream = 1;
    MyVideoStream->InvalidPesCounter = 0;
#endif
}

#if 0

/**
**  Dump mpeg video packet.
**
**  Function to dump a mpeg packet, not needed.
*/
static void DumpMpeg(const uint8_t *data, int size) {
    fprintf(stderr, "%8d: ", size);

    // b3 b4 b8 00 b5 ... 00 b5 ...

    while (size > 3) {
        if (!data[0] && !data[1] && data[2] == 0x01) {
            fprintf(stderr, " %02x", data[3]);
            size -= 4;
            data += 4;
            continue;
        }
        --size;
        ++data;
    }
    fprintf(stderr, "\n");
}

/**
**  Dump h264 video packet.
**
**  Function to Dump a h264 packet, not needed.
*/
static int DumpH264(const uint8_t *data, int size) {
    printf("H264:");
    do {
        if (size < 4) {
            printf("\n");
            return -1;
        }
        if (!data[0] && !data[1] && data[2] == 0x01) {
            printf("%02x ", data[3]);
        }
        ++data;
        --size;
    } while (size);
    printf("\n");

    return 0;
}

/**
**  Validate mpeg video packet.
**
**  Function to validate a mpeg packet, not needed.
*/
static int ValidateMpeg(const uint8_t *data, int size) {
    int pes_l;

    do {
        if (size < 9) {
            return -1;
        }
        if (data[0] || data[1] || data[2] != 0x01) {
            printf("%02x: %02x %02x %02x %02x %02x\n", data[-1], data[0], data[1], data[2], data[3], data[4]);
            return -1;
        }

        pes_l = (data[4] << 8) | data[5];
        if (!pes_l) { // contains unknown length
            return 1;
        }

        if (6 + pes_l > size) {
            return -1;
        }

        data += 6 + pes_l;
        size -= 6 + pes_l;
    } while (size);

    return 0;
}
#endif

/**
**  Play video packet.
**
**  @param stream   video stream
**  @param data data of exactly one complete PES packet
**  @param size size of PES packet
**
**  @return number of bytes used, 0 if internal buffer are full.
**
*/
int PlayVideo3(VideoStream *stream, const uint8_t *data, int size) {
    const uint8_t *check;
    int64_t pts, dts;
    int n;
    int z;
    int l;

    if (!stream->Decoder) { // no x11 video started
        return size;
    }
    if (stream->SkipStream) { // skip video stream
        return size;
    }
    if (stream->Freezed) { // stream freezed
        return 0;
    }
    if (stream->NewStream) { // channel switched
        Debug(3, "video: new stream %dms\n", GetMsTicks() - VideoSwitch);
        if (atomic_read(&stream->PacketsFilled) >= VIDEO_PACKET_MAX - 1) {
            Debug(3, "video: new video stream lost\n");
            return 0;
        }
        VideoNextPacket(stream, AV_CODEC_ID_NONE);
        stream->CodecID = AV_CODEC_ID_NONE;
        stream->ClosingStream = 1;
        stream->NewStream = 0;
    }
    // must be a PES start code
    // FIXME: Valgrind-3.8.1 has a problem with this code
    if (size < 9 || !data || data[0] || data[1] || data[2] != 0x01) {
        if (!stream->InvalidPesCounter++) {
            Error(_("[softhddev] invalid PES video packet\n"));
        }
        return size;
    }
    if (stream->InvalidPesCounter) {
        if (stream->InvalidPesCounter > 1) {
            Error(_("[softhddev] %d invalid PES video packet(s)\n"), stream->InvalidPesCounter);
        }
        stream->InvalidPesCounter = 0;
    }
    // 0xBE, filler, padding stream
    if (data[3] == PES_PADDING_STREAM) { // from DVD plugin
        return size;
    }

    n = data[8];         // header size
    if (size <= 9 + n) { // wrong size
        if (size == 9 + n) {
            Warning(_("[softhddev] empty video packet\n"));
        } else {
            Error(_("[softhddev] invalid video packet %d/%d bytes\n"), 9 + n, size);
        }
        return size;
    }
    // hard limit buffer full: needed for replay
    if (atomic_read(&stream->PacketsFilled) >= VIDEO_PACKET_MAX - 10) {
        //Debug(3, "video: video buffer full\n");
        return 0;
    }
#ifdef USE_SOFTLIMIT
    // soft limit buffer full
    if (AudioSyncStream == stream && atomic_read(&stream->PacketsFilled) > 3 &&
        AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE * 2) {
        return 0;
    }
#endif
    // get pts/dts
    pts = AV_NOPTS_VALUE;
    dts = AV_NOPTS_VALUE;
    if ((data[7] & 0xc0) == 0x80) {
        pts = (int64_t)(data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 | data[12] << 7 |
              (data[13] & 0xFE) >> 1;
    }
    if ((data[7] & 0xC0) == 0xc0) {
        pts = (int64_t)(data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] & 0xFE) << 14 | data[12] << 7 |
              (data[13] & 0xFE) >> 1;
        dts = (int64_t)(data[14] & 0x0E) << 29 | data[15] << 22 | (data[16] & 0xFE) << 14 | data[17] << 7 |
              (data[18] & 0xFE) >> 1;
    }

    check = data + 9 + n;
    l = size - 9 - n;
    z = 0;
    while (!*check) { // count leading zeros
        if (l < 3) {
            // Warning(_("[softhddev] empty video packet %d bytes\n"), size);
            z = 0;
            break;
        }
        --l;
        ++check;
        ++z;
    }

    // H264 NAL AUD Access Unit Delimiter (0x00) 0x00 0x00 0x01 0x09
    // and next start code
    if ((data[6] & 0xC0) == 0x80 && z >= 2 && check[0] == 0x01 && check[1] == 0x09 && !check[3] && !check[4]) {
        // old PES HDTV recording z == 2 -> stronger check!
        if (stream->CodecID == AV_CODEC_ID_H264) {
#ifdef DUMP_TRICKSPEED
            if (stream->TrickSpeed) {
                char buf[1024];
                int fd;
                static int FrameCounter;

                snprintf(buf, sizeof(buf), "frame_%06d_%08d.raw", getpid(), FrameCounter++);
                if ((fd = open(buf, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0666)) >= 0) {
                    if (write(fd, data + 9 + n, size - 9 - n)) {
                        // this construct is to remove the annoying warning
                    }
                    close(fd);
                }
            }
#endif
#ifdef H264_EOS_TRICKSPEED
            // this should improve ffwd+frew, but produce crash in ffmpeg
            // with some streams
            if (stream->TrickSpeed && pts != (int64_t)AV_NOPTS_VALUE) {
                // H264 NAL End of Sequence
                static uint8_t seq_end_h264[] = {0x00, 0x00, 0x00, 0x01, 0x0A};

                // 1-5=SLICE 6=SEI 7=SPS 8=PPS
                // NAL SPS sequence parameter set
                if ((check[7] & 0x1F) == 0x07) {
                    VideoNextPacket(stream, AV_CODEC_ID_H264);
                    VideoEnqueue(stream, AV_NOPTS_VALUE, AV_NOPTS_VALUE, seq_end_h264, sizeof(seq_end_h264));
                }
            }
#endif
            VideoNextPacket(stream, AV_CODEC_ID_H264);
        } else {
            Debug(3, "video: h264 detected\n");
            stream->CodecID = AV_CODEC_ID_H264;
        }
        // SKIP PES header (ffmpeg supports short start code)
        VideoEnqueue(stream, pts, dts, check - 2, l + 2);
        return size;
    }
    // HEVC Codec
    if ((data[6] & 0xC0) == 0x80 && z >= 2 && check[0] == 0x01 && check[1] == 0x46) {
        // old PES HDTV recording z == 2 -> stronger check!
        if (stream->CodecID == AV_CODEC_ID_HEVC) {
            VideoNextPacket(stream, AV_CODEC_ID_HEVC);
        } else {
            Debug(3, "video: hvec detected\n");
            stream->CodecID = AV_CODEC_ID_HEVC;
        }
        // SKIP PES header (ffmpeg supports short start code)
        VideoEnqueue(stream, pts, dts, check - 2, l + 2);
        return size;
    }

    // PES start code 0x00 0x00 0x01 0x00|0xb3
    if (z > 1 && check[0] == 0x01 && (!check[1] || check[1] == 0xb3)) {
        if (stream->CodecID == AV_CODEC_ID_MPEG2VIDEO) {
            VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
        } else {
            Debug(3, "video: mpeg2 detected ID %02x\n", check[3]);
            stream->CodecID = AV_CODEC_ID_MPEG2VIDEO;
        }

        // SKIP PES header, begin of start code
#if defined(USE_PIP) || defined(VAAPI)
        VideoMpegEnqueue(stream, pts, dts, check - 2, l + 2);
#else
        VideoEnqueue(stream, pts, dts, check - 2, l + 2);
#endif
        return size;
    }
    // this happens when vdr sends incomplete packets
    if (stream->CodecID == AV_CODEC_ID_NONE) {
        Debug(3, "video: not detected\n");
        return size;
    }

#if defined(USE_PIP) || defined(VAAPI)
    if (stream->CodecID == AV_CODEC_ID_MPEG2VIDEO) {
        // SKIP PES header
        VideoMpegEnqueue(stream, pts, dts, data + 9 + n, size - 9 - n);
#ifndef USE_MPEG_COMPLETE
        if (size < 65526) {
            // mpeg codec supports incomplete packets
            // waiting for a full complete packages, increases needed delays
            // PES recordings sends incomplete packets
            // incomplete packets  breaks the decoder for some stations
            // for the new USE_PIP code, this is only a very little improvement
            VideoNextPacket(stream, stream->CodecID);
        }
#endif
    } else {
        // SKIP PES header
        VideoEnqueue(stream, pts, dts, data + 9 + n, size - 9 - n);
    }
#else

    // SKIP PES header
    VideoEnqueue(stream, pts, dts, data + 9 + n, size - 9 - n);

    // incomplete packets produce artefacts after channel switch
    // packet < 65526 is the last split packet, detect it here for
    // better latency
    if (size < 65526 && stream->CodecID == AV_CODEC_ID_MPEG2VIDEO) {
        // mpeg codec supports incomplete packets
        // waiting for a full complete packages, increases needed delays
        VideoNextPacket(stream, AV_CODEC_ID_MPEG2VIDEO);
    }
#endif

    return size;
}

/**
**  Play video packet.
**
**  @param data data of exactly one complete PES packet
**  @param size size of PES packet
**
**  @return number of bytes used, 0 if internal buffer are full.
**
**  @note vdr sends incomplete packets, va-api h264 decoder only
**  supports complete packets.
**  We buffer here until we receive an complete PES Packet, which
**  is no problem, the audio is always far behind us.
**  cTsToPes::GetPes splits the packets.
**
**  @todo FIXME: combine the 5 ifs at start of the function
*/
int PlayVideo(const uint8_t *data, int size) { return PlayVideo3(MyVideoStream, data, size); }

/// call VDR support function
extern uint8_t *CreateJpeg(uint8_t *, int *, int, int, int);

#if defined(USE_JPEG) && JPEG_LIB_VERSION >= 80

/**
**  Create a jpeg image in memory.
**
**  @param image	raw RGB image
**  @param raw_size	size of raw image
**  @param size[out]	size of jpeg image
**  @param quality	jpeg quality
**  @param width	number of horizontal pixels in image
**  @param height	number of vertical pixels in image
**
**  @returns allocated jpeg image.
*/
uint8_t *CreateJpeg(uint8_t *image, int raw_size, int *size, int quality, int width, int height) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPROW row_ptr[1];
    int row_stride;
    uint8_t *outbuf;
    long unsigned int outsize;

    outbuf = NULL;
    outsize = 0;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_mem_dest(&cinfo, &outbuf, &outsize);

    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = raw_size / height / width;
    cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    row_stride = width * 3;
    while (cinfo.next_scanline < cinfo.image_height) {
        row_ptr[0] = &image[cinfo.next_scanline * row_stride];
        jpeg_write_scanlines(&cinfo, row_ptr, 1);
    }

    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    *size = outsize;

    return outbuf;
}

#endif

/**
**  Grabs the currently visible screen image.
**
**  @param size size of the returned data
**  @param jpeg flag true, create JPEG data
**  @param quality  JPEG quality
**  @param width    number of horizontal pixels in the frame
**  @param height   number of vertical pixels in the frame
*/
uint8_t *GrabImage(int *size, int jpeg, int quality, int width, int height) {
    if (jpeg) {
        uint8_t *image;
        int raw_size;

        raw_size = 0;
        image = VideoGrab(&raw_size, &width, &height, 0);
        if (image) { // can fail, suspended, ...
            uint8_t *jpg_image;

            jpg_image = CreateJpeg(image, size, quality, width, height);

            free(image);
            return jpg_image;
        }
        return NULL;
    }
    return VideoGrab(size, &width, &height, 1);
}

//////////////////////////////////////////////////////////////////////////////

/**
**  Set play mode, called on channel switch.
**
**  @param play_mode	play mode (none, video+audio, audio-only, ...)
*/
int SetPlayMode(int play_mode) {
    Debug(3, "Set Playmode %d\n", play_mode);
    switch (play_mode) {
        case 0: // audio/video from decoder
            // tell video parser we get new stream
            if (MyVideoStream->Decoder && !MyVideoStream->SkipStream) {
                // clear buffers on close configured always or replay only
                if (ConfigVideoClearOnSwitch || MyVideoStream->ClearClose) {
                    Clear(); // flush all buffers
                    MyVideoStream->ClearClose = 0;
                }
                if (MyVideoStream->CodecID != AV_CODEC_ID_NONE) {
                    MyVideoStream->NewStream = 1;
                    MyVideoStream->InvalidPesCounter = 0;
                    // tell hw decoder we are closing stream
                    VideoSetClosing(MyVideoStream->HwDecoder);
                    VideoResetStart(MyVideoStream->HwDecoder);
#ifdef DEBUG
                    VideoSwitch = GetMsTicks();
                    Debug(3, "video: new stream start\n");
#endif
                }
            }
            if (MyAudioDecoder) { // tell audio parser we have new stream
                if (AudioCodecID != AV_CODEC_ID_NONE) {
                    NewAudioStream = 1;
                }
            }
            break;
        case 1: // audio/video from player
            VideoDisplayWakeup();
            Play();
            break;
        case 2: // audio only from player, video from decoder
        case 3: // audio only from player, no video (black screen)
            Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
            VideoDisplayWakeup();
            Play();
            break;
        case 4: // video only from player, audio from decoder
            VideoDisplayWakeup();
            Play();
            break;
    }
    return 1;
}

/**
**  Gets the current System Time Counter, which can be used to
**  synchronize audio, video and subtitles.
*/
int64_t GetSTC(void) {
    if (MyVideoStream->HwDecoder) {
        return VideoGetClock(MyVideoStream->HwDecoder);
    }
    // could happen during dettached
    Warning(_("softhddev: %s called without hw decoder\n"), __FUNCTION__);
    return AV_NOPTS_VALUE;
}

/**
**  Get video stream size and aspect.
**
**  @param width[OUT]	width of video stream
**  @param height[OUT]	height of video stream
**  @param aspect[OUT]	aspect ratio (4/3, 16/9, ...) of video stream
*/
void GetVideoSize(int *width, int *height, double *aspect) {
#ifdef DEBUG
    static int done_width;
    static int done_height;
#endif
    int aspect_num;
    int aspect_den;

    if (MyVideoStream->HwDecoder) {
        VideoGetVideoSize(MyVideoStream->HwDecoder, width, height, &aspect_num, &aspect_den);
        *aspect = (double)aspect_num / (double)aspect_den;
    } else {
        *width = 0;
        *height = 0;
        *aspect = 1.0; // like default cDevice::GetVideoSize
    }

#ifdef DEBUG
    if (done_width != *width || done_height != *height) {
        Debug(3, "[softhddev]%s: %dx%d %g\n", __FUNCTION__, *width, *height, *aspect);
        done_width = *width;
        done_height = *height;
    }
#endif
}

/**
**  Set trick play speed.
**
**  Every single frame shall then be displayed the given number of
**  times.
**
**  @param speed    trick speed
*/
void TrickSpeed(int speed) {
    MyVideoStream->TrickSpeed = speed;
    if (MyVideoStream->HwDecoder) {
        VideoSetTrickSpeed(MyVideoStream->HwDecoder, speed);
    } else {
        // can happen, during startup
        Debug(3, "softhddev: %s called without hw decoder\n", __FUNCTION__);
    }
    StreamFreezed = 0;
    MyVideoStream->Freezed = 0;
}

/**
**  Clears all video and audio data from the device.
*/
void Clear(void) {
    int i;

    VideoResetPacket(MyVideoStream); // terminate work
    MyVideoStream->ClearBuffers = 1;
    if (!SkipAudio) {
        AudioFlushBuffers();
        // NewAudioStream = 1;
    }
    // FIXME: audio avcodec_flush_buffers, video is done by VideoClearBuffers

    // wait for empty buffers
    // FIXME: without softstart sync VideoDecode isn't called.
    for (i = 0; MyVideoStream->ClearBuffers && i < 20; ++i) {
        usleep(1 * 100);
    }
    Debug(3, "[softhddev]%s: %dms buffers %d\n", __FUNCTION__, i, VideoGetBuffers(MyVideoStream));
}

/**
**  Sets the device into play mode.
*/
void Play(void) {
    TrickSpeed(0); // normal play
    SkipAudio = 0;
    AudioPlay();
}

/**
**  Sets the device into "freeze frame" mode.
*/
void Freeze(void) {
    StreamFreezed = 1;
    MyVideoStream->Freezed = 1;
    AudioPause();
}

/**
**  Turns off audio while replaying.
*/
void Mute(void) {
    SkipAudio = 1;
    AudioFlushBuffers();
    // AudioSetVolume(0);
}

/**
**  Display the given I-frame as a still picture.
**
**  @param data pes frame data
**  @param size number of bytes in frame
*/
void StillPicture(const uint8_t *data, int size) {
    static uint8_t seq_end_mpeg[] = {0x00, 0x00, 0x01, 0xB7};
    // H264 NAL End of Sequence
    static uint8_t seq_end_h264[] = {0x00, 0x00, 0x00, 0x01, 0x0A};
    // H265 NAL End of Sequence
    static uint8_t seq_end_h265[] = {0x00, 0x00, 0x00, 0x01, 0x48, 0x01}; // 0x48 = end of seq	  0x4a = end of stream
    int i;

    // might be called in Suspended Mode
    if (!MyVideoStream->Decoder || MyVideoStream->SkipStream) {
        return;
    }
    // must be a PES start code
    if (size < 9 || !data || data[0] || data[1] || data[2] != 0x01) {
        Error(_("[softhddev] invalid still video packet\n"));
        return;
    }
#ifdef STILL_DEBUG
    InStillPicture = 1;
#endif
    VideoSetTrickSpeed(MyVideoStream->HwDecoder, 1);
    VideoResetPacket(MyVideoStream);

    VideoNextPacket(MyVideoStream, AV_CODEC_ID_NONE); // close last stream

    if (MyVideoStream->CodecID == AV_CODEC_ID_NONE) {
        // FIXME: should detect codec, see PlayVideo
        Error(_("[softhddev] no codec known for still picture\n"));
    }
    // FIXME: can check video backend, if a frame was produced.
    // output for max reference frames
#ifdef STILL_DEBUG
    fprintf(stderr, "still-picture\n");
#endif

    for (i = 0; i < (MyVideoStream->CodecID == AV_CODEC_ID_HEVC ? 12 : 12); ++i) {
        const uint8_t *split;
        int n;

        // FIXME: vdr pes recordings sends mixed audio/video
        if ((data[3] & 0xF0) == 0xE0) { // PES packet

            split = data;
            n = size;
            // split the I-frame into single pes packets
            do {
                int len;

#ifdef DEBUG
                if (split[0] || split[1] || split[2] != 0x01) {
                    Error(_("[softhddev] invalid still video packet\n"));
                    break;
                }
#endif

                len = (split[4] << 8) + split[5];
                if (!len || len + 6 > n) {
                    if ((split[3] & 0xF0) == 0xE0) {
                        // video only
                        while (!PlayVideo3(MyVideoStream, split, n)) { // feed remaining bytes
                        }
                    }
                    break;
                }
                if ((split[3] & 0xF0) == 0xE0) {
                    // video only
                    while (!PlayVideo3(MyVideoStream, split, len + 6)) { // feed it
                    }
                }
                split += 6 + len;
                n -= 6 + len;
            } while (n > 6);

            VideoNextPacket(MyVideoStream,
                            MyVideoStream->CodecID); // terminate last packet
        } else {                                     // ES packet
            if (MyVideoStream->CodecID != AV_CODEC_ID_MPEG2VIDEO) {
                VideoNextPacket(MyVideoStream, AV_CODEC_ID_NONE); // close last stream
                MyVideoStream->CodecID = AV_CODEC_ID_MPEG2VIDEO;
            }
            VideoEnqueue(MyVideoStream, AV_NOPTS_VALUE, AV_NOPTS_VALUE, data, size);
        }
        if (MyVideoStream->CodecID == AV_CODEC_ID_H264) {
            VideoEnqueue(MyVideoStream, AV_NOPTS_VALUE, AV_NOPTS_VALUE, seq_end_h264, sizeof(seq_end_h264));
        } else if (MyVideoStream->CodecID == AV_CODEC_ID_HEVC) {
            VideoEnqueue(MyVideoStream, AV_NOPTS_VALUE, AV_NOPTS_VALUE, seq_end_h265, sizeof(seq_end_h265));
        } else {
            VideoEnqueue(MyVideoStream, AV_NOPTS_VALUE, AV_NOPTS_VALUE, seq_end_mpeg, sizeof(seq_end_mpeg));
        }
        VideoNextPacket(MyVideoStream,
                        MyVideoStream->CodecID); // terminate last packet
    }

    // wait for empty buffers
    for (i = 0; VideoGetBuffers(MyVideoStream) && i < 50; ++i) {
        usleep(10 * 1000);
    }
    Debug(3, "[softhddev]%s: buffers %d %dms\n", __FUNCTION__, VideoGetBuffers(MyVideoStream), i * 10);
#ifdef STILL_DEBUG
    InStillPicture = 0;
#endif

    VideoNextPacket(MyVideoStream, AV_CODEC_ID_NONE); // close last stream

    VideoSetTrickSpeed(MyVideoStream->HwDecoder, 0);
}

/**
**  Poll if device is ready.  Called by replay.
**
**  This function is useless, the return value is ignored and
**  all buffers are overrun by vdr.
**
**  The dvd plugin is using this correct.
**
**  @param timeout  timeout to become ready in ms
**
**  @retval true    if ready
**  @retval false   if busy
*/
int Poll(int timeout) {
    // poll is only called during replay, flush buffers after replay
    MyVideoStream->ClearClose = 1;
    for (;;) {
        int full;
        int t;
        int used;
        int filled;

        used = AudioUsedBytes();
        // FIXME: no video!
        filled = atomic_read(&MyVideoStream->PacketsFilled);
        // soft limit + hard limit
        full = (used > AUDIO_MIN_BUFFER_FREE && filled > 3) || AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE ||
               filled >= VIDEO_PACKET_MAX - 10;

        if (!full || !timeout) {
            return !full;
        }

        t = 15;
        if (timeout < t) {
            t = timeout;
        }
        usleep(t * 1000); // let display thread work
        timeout -= t;
    }
}

/**
**  Flush the device output buffers.
**
**  @param timeout  timeout to flush in ms
*/
int Flush(int timeout) {
    if (atomic_read(&MyVideoStream->PacketsFilled)) {
        if (timeout) { // let display thread work
            usleep(timeout * 1000);
        }
        return !atomic_read(&MyVideoStream->PacketsFilled);
    }
    return 1;
}

//////////////////////////////////////////////////////////////////////////////
//  OSD
//////////////////////////////////////////////////////////////////////////////

/**
**  Get OSD size and aspect.
**
**  @param width[OUT]	width of OSD
**  @param height[OUT]	height of OSD
**  @param aspect[OUT]	aspect ratio (4/3, 16/9, ...) of OSD
*/
void GetOsdSize(int *width, int *height, double *aspect) {
#ifdef DEBUG
    static int done_width;
    static int done_height;
#endif

    VideoGetOsdSize(width, height);
    *aspect = 16.0 / 9.0 / (double)*width * (double)*height;

#ifdef DEBUG
    if (done_width != *width || done_height != *height) {
        Debug(3, "[softhddev]%s: %dx%d %g\n", __FUNCTION__, *width, *height, *aspect);
        done_width = *width;
        done_height = *height;
    }
#endif
}

/**
**  Close OSD.
*/
void OsdClose(void) { VideoOsdClear(); }

/**
**  Draw an OSD pixmap.
**
**  @param xi	x-coordinate in argb image
**  @param yi	y-coordinate in argb image
**  @paran height   height in pixel in argb image
**  @paran width    width in pixel in argb image
**  @param pitch    pitch of argb image
**  @param argb 32bit ARGB image data
**  @param x	x-coordinate on screen of argb image
**  @param y	y-coordinate on screen of argb image
*/
void OsdDrawARGB(int xi, int yi, int height, int width, int pitch, const uint8_t *argb, int x, int y) {
    // wakeup display for showing remote learning dialog
    VideoDisplayWakeup();
    VideoOsdDrawARGB(xi, yi, height, width, pitch, argb, x, y);
}

//////////////////////////////////////////////////////////////////////////////

/**
**  Return command line help string.
*/
const char *CommandLineHelp(void) {
    return "  -a device\taudio device (fe. alsa: hw:0,0 oss: /dev/dsp)\n"
           "  -p device\taudio device for pass-through (hw:0,1 or /dev/dsp1)\n"
           "  -c channel\taudio mixer channel name (fe. PCM)\n"
           "	-d display\tdisplay of x11 server (fe. :0.0)\n"
           "  -f\t\tstart with fullscreen window (only with window manager)\n"
           "  -g geometry\tx11 window geometry wxh+x+y\n"
           "  -r Refresh\tRefreshrate for DRM (default is 50 Hz)\n"
           "  -C Connector\tConnector for DRM (default is current Connector)\n"
           "  -S shader\tShader to use.\n\t\tOnly with placebo. Can be repeated "
           "for more shaders\n"
           "  -v device\tvideo driver device (cuvid)\n"
           "	-s\t\tstart in suspended mode\n"
           "  -x\t\tstart x11 server, with -xx try to connect, if this fails\n"
           "  -X args\tX11 server arguments (f.e. -nocursor)\n"
           "	-w workaround\tenable/disable workarounds\n"
           "\tno-hw-decoder\t\tdisable hw decoder, use software decoder only\n"
           "\tno-mpeg-hw-decoder\tdisable hw decoder for mpeg only\n"
           "\tstill-hw-decoder\tenable hardware decoder for still-pictures\n"
           "\tstill-h264-hw-decoder\tenable h264 hw decoder for still-pictures\n"
           "\talsa-driver-broken\tdisable broken alsa driver message\n"
           "\talsa-no-close-open\tdisable close open to fix alsa no sound bug\n"
           "\talsa-no-test\tdisable Alsa Capability test on start for NUC11/12\n"
           "\talsa-close-open-delay\tenable close open delay to fix no sound bug\n"
           "\tignore-repeat-pict\tdisable repeat pict message\n"
           "\tuse-possible-defect-frames prefer faster channel switch\n"
           "	 -D\t\tstart in detached mode\n";
}

/**
**  Process the command line arguments.
**
**  @param argc number of arguments
**  @param argv arguments vector
*/
int ProcessArgs(int argc, char *const argv[]) {
    //
    //	Parse arguments.
    //
#ifdef __FreeBSD__
    if (!strcmp(*argv, "softhddevice")) {
        ++argv;
        --argc;
    }
#endif

    for (;;) {
        switch (getopt(argc, argv, "-a:c:C:r:d:fg:p:S:sv:w:xDX:")) {
            case 'a': // audio device for pcm
                AudioSetDevice(optarg);
                continue;
            case 'c': // channel of audio mixer
                AudioSetChannel(optarg);
                continue;
            case 'C': // Connector for DRM
                VideoSetConnector(optarg);
                continue;
            case 'r': // Connector for DRM
                VideoSetRefresh(optarg);
                continue;
            case 'S': // Shader
                if (VideoSetShader(optarg) < 0) {
                    fprintf(stderr, _("Too much shaders definded\n"));
                    return 0;
                }
                continue;
            case 'p': // pass-through audio device
                AudioSetPassthroughDevice(optarg);
                continue;
            case 'd': // x11 display name
                X11DisplayName = optarg;
                continue;
            case 'f': // fullscreen mode
                ConfigFullscreen = 1;
                continue;
            case 'g': // geometry
                if (VideoSetGeometry(optarg) < 0) {
                    fprintf(stderr, _("Bad formated geometry please use: "
                                      "[=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]\n"));
                    return 0;
                }
                continue;
            case 'v': // video driver
                VideoSetDevice(optarg);
                continue;
            case 'x': // x11 server
                ConfigStartX11Server++;
                continue;
            case 'X': // x11 server arguments
                X11ServerArguments = optarg;
                continue;
            case 's': // start in suspend mode
                ConfigStartSuspended = 1;
                continue;
            case 'D': // start in detached mode
                ConfigStartSuspended = -1;
                continue;
            case 'w': // workarounds
                if (!strcasecmp("no-hw-decoder", optarg)) {
                    VideoHardwareDecoder = 0;
                } else if (!strcasecmp("no-mpeg-hw-decoder", optarg)) {
                    VideoHardwareDecoder = 1;
                    if (ConfigStillDecoder) {
                        ConfigStillDecoder = 1;
                    }
                } else if (!strcasecmp("still-hw-decoder", optarg)) {
                    ConfigStillDecoder = -1;
                } else if (!strcasecmp("still-h264-hw-decoder", optarg)) {
                    ConfigStillDecoder = 1;
                } else if (!strcasecmp("alsa-driver-broken", optarg)) {
                    AudioAlsaDriverBroken = 1;
                } else if (!strcasecmp("alsa-no-close-open", optarg)) {
                    AudioAlsaNoCloseOpen = 1;
                } else if (!strcasecmp("alsa-no-test", optarg)) {
                    AudioAlsaNotest = 1;
                } else if (!strcasecmp("alsa-close-open-delay", optarg)) {
                    AudioAlsaCloseOpenDelay = 1;
                } else if (!strcasecmp("ignore-repeat-pict", optarg)) {
                    VideoIgnoreRepeatPict = 1;
                } else if (!strcasecmp("use-possible-defect-frames", optarg)) {
                    CodecUsePossibleDefectFrames = 1;
                } else {
                    fprintf(stderr, _("Workaround '%s' unsupported\n"), optarg);
                    return 0;
                }
                continue;
            case EOF:
                break;
            case '-':
                fprintf(stderr, _("We need no long options\n"));
                return 0;
            case ':':
                fprintf(stderr, _("Missing argument for option '%c'\n"), optopt);
                return 0;
            default:
                fprintf(stderr, _("Unknown option '%c'\n"), optopt);
                return 0;
        }
        break;
    }
    while (optind < argc) {
        fprintf(stderr, _("Unhandled argument '%s'\n"), argv[optind++]);
    }

    return 1;
}

//////////////////////////////////////////////////////////////////////////////
//  Init/Exit
//////////////////////////////////////////////////////////////////////////////

#include <sys/types.h>
#include <sys/wait.h>

#define XSERVER_MAX_ARGS 512 ///< how many arguments support

#ifndef __FreeBSD__
static const char *X11Server = "/usr/bin/X"; ///< default x11 server
#else
static const char *X11Server = LOCALBASE "/bin/X"; ///< default x11 server
#endif
static pid_t X11ServerPid; ///< x11 server pid

/**
**  USR1 signal handler.
**
**  @param sig	signal number
*/
static void Usr1Handler(int __attribute__((unused)) sig) {
    ++Usr1Signal;

    Debug(3, "x-setup: got signal usr1\n");
}

/**
**  Start the X server
*/
static void StartXServer(void) {
    struct sigaction usr1;
    pid_t pid;
    const char *sval;
    const char *args[XSERVER_MAX_ARGS];
    int argn;
    char *buf;
    int maxfd;
    int fd;

    // X server
    if (X11Server) {
        args[0] = X11Server;
    } else {
        Error(_("x-setup: No X server configured!\n"));
        return;
    }

    argn = 1;
    if (X11DisplayName) { // append display name
        args[argn++] = X11DisplayName;
        // export display for childs
        setenv("DISPLAY", X11DisplayName, 1);
    }
    // split X server arguments string into words
    if ((sval = X11ServerArguments)) {
        char *s;

#ifndef __FreeBSD__
        s = buf = strdupa(sval);
#else
        s = buf = alloca(strlen(sval) + 1);
        strcpy(buf, sval);
#endif
        while ((sval = strsep(&s, " \t"))) {
            args[argn++] = sval;

            if (argn == XSERVER_MAX_ARGS - 1) { // argument overflow
                Error(_("x-setup: too many arguments for xserver\n"));
                // argn = 1;
                break;
            }
        }
    }
    // FIXME: auth
    // FIXME: append VTxx
    args[argn] = NULL;

    // arm the signal
    memset(&usr1, 0, sizeof(struct sigaction));
    usr1.sa_handler = Usr1Handler;
    sigaction(SIGUSR1, &usr1, NULL);

    Debug(3, "x-setup: Starting X server '%s' '%s'\n", args[0], X11ServerArguments);
    // fork
    if ((pid = fork())) { // parent

        X11ServerPid = pid;
        Debug(3, "x-setup: Started x-server pid=%d\n", X11ServerPid);

        return;
    }
    // child
    signal(SIGUSR1, SIG_IGN); // ignore to force answer
    // setpgid(0,getpid());
    setpgid(pid, 0);

    // close all open file-handles
    maxfd = sysconf(_SC_OPEN_MAX);
    for (fd = 3; fd < maxfd; fd++) { // keep stdin, stdout, stderr
        close(fd);                   // vdr should open with O_CLOEXEC
    }

    // start the X server
    execvp(args[0], (char *const *)args);

    Error(_("x-setup: Failed to start X server '%s'\n"), args[0]);
    exit(-1);
}

/**
**  Exit + cleanup.
*/
void SoftHdDeviceExit(void) {
    // lets hope that vdr does a good thread cleanup

    AudioExit();
    if (MyAudioDecoder) {
        CodecAudioClose(MyAudioDecoder);
        CodecAudioDelDecoder(MyAudioDecoder);
        MyAudioDecoder = NULL;
    }
    NewAudioStream = 0;
    av_packet_unref(AudioAvPkt);

    StopVideo();

    CodecExit();

    if (ConfigStartX11Server) {
        Debug(3, "x-setup: Stop x11 server\n");

        if (X11ServerPid) {
            int waittime;
            int timeout;
            pid_t wpid;
            int status;

            kill(X11ServerPid, SIGTERM);
            waittime = 0;
            timeout = 500; // 0.5s
            // wait for x11 finishing, with timeout
            do {
                wpid = waitpid(X11ServerPid, &status, WNOHANG);
                if (wpid) {
                    break;
                }
                if (waittime++ < timeout) {
                    usleep(1 * 1000);
                    continue;
                }
                kill(X11ServerPid, SIGKILL);
            } while (waittime < timeout);
            if (wpid && WIFEXITED(status)) {
                Debug(3, "x-setup: x11 server exited (%d)\n", WEXITSTATUS(status));
            }
            if (wpid && WIFSIGNALED(status)) {
                Debug(3, "x-setup: x11 server killed (%d)\n", WTERMSIG(status));
            }
        }
    }

    pthread_mutex_destroy(&SuspendLockMutex);
#ifdef USE_PIP
    pthread_mutex_destroy(&PipVideoStream->DecoderLockMutex);
#endif
    pthread_mutex_destroy(&MyVideoStream->DecoderLockMutex);
}

/**
**  Prepare plugin.
**
**  @retval 0	normal start
**  @retval 1	suspended start
**  @retval -1	detached start
*/
int Start(void) {
    if (ConfigStartX11Server) {
        StartXServer();
    }
    CodecInit();

    pthread_mutex_init(&MyVideoStream->DecoderLockMutex, NULL);
#ifdef USE_PIP
    pthread_mutex_init(&PipVideoStream->DecoderLockMutex, NULL);
#endif
    pthread_mutex_init(&SuspendLockMutex, NULL);

    if (!ConfigStartSuspended) {
        // FIXME: AudioInit for HDMI after X11 startup
        // StartAudio();
        AudioInit();
        av_new_packet(AudioAvPkt, AUDIO_BUFFER_SIZE);
        MyAudioDecoder = CodecAudioNewDecoder();
        AudioCodecID = AV_CODEC_ID_NONE;
        AudioChannelID = -1;

        if (!ConfigStartX11Server) {
            StartVideo();
        }
    } else {
        MyVideoStream->SkipStream = 1;
        SkipAudio = 1;
    }

#ifndef NO_TS_AUDIO
    PesInit(PesDemuxAudio);
#endif
    Info(_("[softhddev] ready%s\n"),
         ConfigStartSuspended ? ConfigStartSuspended == -1 ? " detached" : " suspended" : "");

    return ConfigStartSuspended;
}

/**
**  Stop plugin.
**
**  @note stop everything, but don't cleanup, module is still called.
*/
void Stop(void) {
#ifdef DEBUG
    Debug(3, "video: max used PES packet size: %d\n", VideoMaxPacketSize);
#endif
}

/**
**  Perform any cleanup or other regular tasks.
*/
void Housekeeping(void) {
    //
    //	when starting an own X11 server fails, try to connect to a already
    //	running X11 server.  This can take some time.
    //
    if (X11ServerPid) { // check if X11 server still running
        pid_t wpid;
        int status;

        wpid = waitpid(X11ServerPid, &status, WNOHANG);
        if (wpid) {
            if (WIFEXITED(status)) {
                Debug(3, "x-setup: x11 server exited (%d)\n", WEXITSTATUS(status));
            }
            if (WIFSIGNALED(status)) {
                Debug(3, "x-setup: x11 server killed (%d)\n", WTERMSIG(status));
            }
            X11ServerPid = 0;
            // video not running
            if (ConfigStartX11Server > 1 && !MyVideoStream->HwDecoder) {
                StartVideo();
            }
        }
    }
}

/**
**  Main thread hook, periodic called from main thread.
*/
void MainThreadHook(void) {
    if (Usr1Signal) { // x11 server ready
        // FIYME: x11 server keeps sending sigusr1 signals
        signal(SIGUSR1, SIG_IGN); // ignore further signals
        Usr1Signal = 0;
        StartVideo();
        VideoDisplayWakeup();
    }
}

//////////////////////////////////////////////////////////////////////////////
//  Suspend/Resume
//////////////////////////////////////////////////////////////////////////////

/// call VDR support function
extern void DelPip(void);

/**
**  Suspend plugin.
**
**  @param video    suspend closes video
**  @param audio    suspend closes audio
**  @param dox11    suspend closes x11 server
*/
void Suspend(int video, int audio, int dox11) {
    pthread_mutex_lock(&SuspendLockMutex);
    if (MyVideoStream->SkipStream && SkipAudio) { // already suspended
        pthread_mutex_unlock(&SuspendLockMutex);
        return;
    }

    Debug(3, "[softhddev]%s:\n", __FUNCTION__);

#ifdef USE_PIP
    DelPip(); // must stop PIP
#endif

    // FIXME: should not be correct, if not both are suspended!
    // Move down into if (video) ...
    MyVideoStream->SkipStream = 1;
    SkipAudio = 1;

    if (audio) {
        AudioExit();
        if (MyAudioDecoder) {
            CodecAudioClose(MyAudioDecoder);
            CodecAudioDelDecoder(MyAudioDecoder);
            MyAudioDecoder = NULL;
        }
        NewAudioStream = 0;
        av_packet_unref(AudioAvPkt);
    }
    if (video) {
        StopVideo();
    }

    if (dox11) {
        // FIXME: stop x11, if started
    }

    pthread_mutex_unlock(&SuspendLockMutex);
}

/**
**  Resume plugin.
*/
void Resume(void) {
    if (!MyVideoStream->SkipStream && !SkipAudio) { // we are not suspended
        return;
    }

    Debug(3, "[softhddev]%s:\n", __FUNCTION__);

    pthread_mutex_lock(&SuspendLockMutex);
    // FIXME: start x11

    if (!MyVideoStream->HwDecoder) { // video not running
        StartVideo();
    }
    if (!MyAudioDecoder) { // audio not running
        // StartAudio();
        AudioInit();
        av_new_packet(AudioAvPkt, AUDIO_BUFFER_SIZE);
        MyAudioDecoder = CodecAudioNewDecoder();
        AudioCodecID = AV_CODEC_ID_NONE;
        AudioChannelID = -1;
    }

    if (MyVideoStream->Decoder) {
        MyVideoStream->SkipStream = 0;
    }
    SkipAudio = 0;

    pthread_mutex_unlock(&SuspendLockMutex);
}

/*
**  Get decoder statistics.
**
**  @param[out] missed	missed frames
**  @param[out] duped	duped frames
**  @param[out] dropped dropped frames
**  @param[out] count	number of decoded frames
*/
void GetStats(int *missed, int *duped, int *dropped, int *counter, float *frametime, int *width, int *height,
              int *color, int *eotf) {
    *missed = 0;
    *duped = 0;
    *dropped = 0;
    *counter = 0;
    *frametime = 0.0f;
    *width = 0;
    *height = 0;
    *color = 0;
    *eotf = 0;
    if (MyVideoStream->HwDecoder) {
        VideoGetStats(MyVideoStream->HwDecoder, missed, duped, dropped, counter, frametime, width, height, color,
                      eotf);
    }
}

/**
**  Scale the currently shown video.
**
**  @param x	video window x coordinate OSD relative
**  @param y	video window y coordinate OSD relative
**  @param width    video window width OSD relative
**  @param height   video window height OSD relative
*/
void ScaleVideo(int x, int y, int width, int height) {
#ifdef USE_PIP
    if (PiPActive && !(x & y & width & height)) {
        Info("[softhddev]%s: fullscreen with PiP active.\n", __FUNCTION__);
        x = mwx;
        y = mwy;
        width = mww;
        height = mwh;
    }
#endif
    if (MyVideoStream->HwDecoder) {
        VideoSetOutputPosition(MyVideoStream->HwDecoder, x, y, width, height);
    }
}

//////////////////////////////////////////////////////////////////////////////
//  PIP
//////////////////////////////////////////////////////////////////////////////

#ifdef USE_PIP

/**
**  Set PIP position.
**
**  @param x	    video window x coordinate OSD relative
**  @param y	    video window y coordinate OSD relative
**  @param width	video window width OSD relative
**  @param height	video window height OSD relative
**  @param pip_x	pip window x coordinate OSD relative
**  @param pip_y	pip window y coordinate OSD relative
**  @param pip_width	pip window width OSD relative
**  @param pip_height	pip window height OSD relative
*/
void PipSetPosition(int x, int y, int width, int height, int pip_x, int pip_y, int pip_width, int pip_height) {
    if (!MyVideoStream->HwDecoder) { // video not running
        return;
    }
    ScaleVideo(x, y, width, height);

    if (!PipVideoStream->HwDecoder) { // pip not running
        return;
    }
    VideoSetOutputPosition(PipVideoStream->HwDecoder, pip_x, pip_y, pip_width, pip_height);
}

/**
**  Start PIP stream.
**
**  @param x	    video window x coordinate OSD relative
**  @param y	    video window y coordinate OSD relative
**  @param width	video window width OSD relative
**  @param height	video window height OSD relative
**  @param pip_x	pip window x coordinate OSD relative
**  @param pip_y	pip window y coordinate OSD relative
**  @param pip_width	pip window width OSD relative
**  @param pip_height	pip window height OSD relative
*/
void PipStart(int x, int y, int width, int height, int pip_x, int pip_y, int pip_width, int pip_height) {
    if (!MyVideoStream->HwDecoder) { // video not running
        return;
    }

    if (!PipVideoStream->Decoder) {
        VideoStreamOpen(PipVideoStream);
    }
    PipSetPosition(x, y, width, height, pip_x, pip_y, pip_width, pip_height);
    mwx = x;
    mwy = y;
    mww = width;
    mwh = height;
    PiPActive = 1;
}

/**
**  Stop PIP.
*/
void PipStop(void) {
    int i;

    if (!MyVideoStream->HwDecoder) { // video not running
        return;
    }

    PiPActive = 0;
    mwx = 0;
    mwy = 0;
    mww = 0;
    mwh = 0;
    ScaleVideo(0, 0, 0, 0);

    PipVideoStream->Close = 1;
    for (i = 0; PipVideoStream->Close && i < 50; ++i) {
        usleep(1 * 1000);
    }
    Info("[softhddev]%s: pip close %dms\n", __FUNCTION__, i);
}

/**
**  PIP play video packet.
**
**  @param data data of exactly one complete PES packet
**  @param size size of PES packet
**
**  @return number of bytes used, 0 if internal buffer are full.
*/
int PipPlayVideo(const uint8_t *data, int size) { return PlayVideo3(PipVideoStream, data, size); }

#endif