/// /// @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 #include #ifdef __FreeBSD__ #include #endif #include #include #include #include #include #include #include #include #define _(str) gettext(str) ///< gettext shortcut #define _N(str) str ///< gettext_noop shortcut #include #include #ifndef __USE_GNU #define __USE_GNU #endif #include #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 #ifdef DEBUG 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] & 0x03) << 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) { AVPacket avpkt[1]; // 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; } av_init_packet(avpkt); avpkt->data = (void *)q; avpkt->size = r; avpkt->pts = pesdx->PTS; avpkt->dts = pesdx->DTS; // FIXME: not aligned for ffmpeg CodecAudioDecode(MyAudioDecoder, avpkt); 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) { AVPacket avpkt[1]; // new codec id, close and open new if (AudioCodecID != codec_id) { CodecAudioClose(MyAudioDecoder); CodecAudioOpen(MyAudioDecoder, codec_id); AudioCodecID = codec_id; } av_init_packet(avpkt); avpkt->data = (void *)p; avpkt->size = r; avpkt->pts = AudioAvPkt->pts; avpkt->dts = AudioAvPkt->dts; // FIXME: not aligned for ffmpeg CodecAudioDecode(MyAudioDecoder, avpkt); 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 */ #ifndef USE_PIP 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 } #ifdef DEBUG /** ** 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-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: " "[=][{xX}][{+-}{+-}]\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-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 #include #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