mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
Original announce message: VDR developer version 1.7.21 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.21.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.20-1.7.21.diff MD5 checksums: 7300bfd997db1a848bd774fefe4aec80 vdr-1.7.21.tar.bz2 c4e745939f31543dd607b97d58fc86be vdr-1.7.20-1.7.21.diff WARNING: ======== This is a developer version. Even though I use it in my productive environment. I strongly recommend that you only use it under controlled conditions and for testing and debugging. This version contains functions to determine the "signal strength" and "signal quality" through cDevice. If you are using a DVB card that contains an stb0899 frontend chip (like the TT-budget S2-3200) you may want to apply the patches from ftp://ftp.tvdr.de/vdr/Developer/Driver-Patches to the LinuxDVB driver source in order to receive useful results from that frontend. From the HISTORY file: - Fixed detecting frames for channels that split frames into several payloads (reported by Derek Kelly). - Now initializing Setup.InitialChannel to an empty string to avoid problems in case there is no setup.conf. - The start time of an edited recording is now set to the time of the first editing mark (thanks to Udo Richter). This obsoletes the CUTTIME patch. - Direct access to the members start, priority, lifetime, and deleted of cRecording as well as to position and comment of cMark is now deprecated. Plugin authors should switch to the new access functions for these members. For now the macro __RECORDING_H_DEPRECATED_DIRECT_MEMBER_ACCESS is defined in recording.h, which exposes these members, so that existing plugins will still compile. Comment out this #define to check whether a particular plugin needs to be modified. This #define may be removed in a future version. - The new functions cRecording::NumFrames() and cRecording::LengthInSeconds() return the number of frames and length (in seconds) of a recording (suggested by Steffen Barszus). - The subtitle PIDs are now stored in the channels.conf file as an extension to the TPID field (thanks to Rolf Ahrenberg). - The new function cDevice::ProvidesEIT() is used to determine whether a device can provide EIT data and will thus be used in cEITScanner::Process() to receive EIT data from the channels it can receive (suggested by Rolf Ahrenberg). Note that by default it is assumed that a device can't provide EIT data, and only the builtin cDvbDevice returns true from this function. - The Audio and Subtitles options are now available through the Green and Yellow keys in the Setup/DVB menu (thanks to Rolf Ahrenberg). This is mainly for remote controls that don't have dedicated keys for these functions. - The SVDRP command HITK now accepts multiple keys (up to 31). - The Recordings menu now displays the length (in hours:minutes) of each recording (thanks to Rolf Ahrenberg). Note that the "new" indicator has been moved from the recording time to the length column. This new format is also used by the SVDRP command LSTR, so in case you have an application that parses the LSTR output, you will need to adjust it to the new format. - The dvbsddevice plugin now supports the new option --outputonly, which disables receiving on SD FF devices and uses the device only for output (thanks to Udo Richter). - Fixed detecting frames on radio channels (reported by Chris Mayo). - Revoked the changes to cFrameDetector that have been introduced in version 1.7.19. Detecting frames in case the Picture Start Code or Access Unit Delimiter extends over TS packet boundaries is now done by locally skipping TS packets in cFrameDetector.
763 lines
23 KiB
C
763 lines
23 KiB
C
/*
|
|
* dvbhdffdevice.c: The DVB HD Full Featured device interface
|
|
*
|
|
* See the README file for copyright information and how to reach the author.
|
|
*
|
|
* $Id: dvbhdffdevice.c 1.33 2011/08/27 09:32:18 kls Exp $
|
|
*/
|
|
|
|
#include "dvbhdffdevice.h"
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <libsi/si.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/dvb/audio.h>
|
|
#include <linux/dvb/dmx.h>
|
|
#include <linux/dvb/video.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <vdr/eitscan.h>
|
|
#include <vdr/transfer.h>
|
|
#include "hdffosd.h"
|
|
#include "setup.h"
|
|
|
|
// --- cDvbHdFfDevice ----------------------------------------------------------
|
|
|
|
int cDvbHdFfDevice::devHdffOffset = -1;
|
|
|
|
cDvbHdFfDevice::cDvbHdFfDevice(int Adapter, int Frontend)
|
|
:cDvbDevice(Adapter, Frontend)
|
|
{
|
|
spuDecoder = NULL;
|
|
audioChannel = 0;
|
|
playMode = pmNone;
|
|
mHdffCmdIf = NULL;
|
|
|
|
// Devices that are only present on cards with decoders:
|
|
|
|
fd_osd = DvbOpen(DEV_DVB_OSD, adapter, frontend, O_RDWR);
|
|
fd_video = DvbOpen(DEV_DVB_VIDEO, adapter, frontend, O_RDWR | O_NONBLOCK);
|
|
fd_audio = DvbOpen(DEV_DVB_AUDIO, adapter, frontend, O_RDWR | O_NONBLOCK);
|
|
|
|
//TODO missing /dev/video offset calculation
|
|
|
|
isHdffPrimary = false;
|
|
if (devHdffOffset < 0) {
|
|
devHdffOffset = adapter;
|
|
isHdffPrimary = true;
|
|
mHdffCmdIf = new HDFF::cHdffCmdIf(fd_osd);
|
|
mHdffCmdIf->CmdAvSetAudioDelay(gHdffSetup.AudioDelay);
|
|
mHdffCmdIf->CmdAvSetAudioDownmix((HDFF::eDownmixMode) gHdffSetup.AudioDownmix);
|
|
mHdffCmdIf->CmdMuxSetVideoOut((HDFF::eVideoOut) gHdffSetup.AnalogueVideo);
|
|
mHdffCmdIf->CmdHdmiSetVideoMode(gHdffSetup.GetVideoMode());
|
|
HDFF::tHdmiConfig hdmiConfig;
|
|
hdmiConfig.TransmitAudio = true;
|
|
hdmiConfig.ForceDviMode = false;
|
|
hdmiConfig.CecEnabled = gHdffSetup.CecEnabled;
|
|
hdmiConfig.VideoModeAdaption = (HDFF::eVideoModeAdaption) gHdffSetup.VideoModeAdaption;
|
|
mHdffCmdIf->CmdHdmiConfigure(&hdmiConfig);
|
|
if (gHdffSetup.CecEnabled)
|
|
mHdffCmdIf->CmdHdmiSendCecCommand(HDFF::cecCommandTvOn);
|
|
mHdffCmdIf->CmdRemoteSetProtocol((HDFF::eRemoteProtocol) gHdffSetup.RemoteProtocol);
|
|
mHdffCmdIf->CmdRemoteSetAddressFilter(gHdffSetup.RemoteAddress >= 0, gHdffSetup.RemoteAddress);
|
|
}
|
|
|
|
// Video format:
|
|
|
|
SetVideoFormat(Setup.VideoFormat);
|
|
}
|
|
|
|
cDvbHdFfDevice::~cDvbHdFfDevice()
|
|
{
|
|
delete spuDecoder;
|
|
if (isHdffPrimary)
|
|
delete mHdffCmdIf;
|
|
// We're not explicitly closing any device files here, since this sometimes
|
|
// caused segfaults. Besides, the program is about to terminate anyway...
|
|
}
|
|
|
|
void cDvbHdFfDevice::MakePrimaryDevice(bool On)
|
|
{
|
|
if (On)
|
|
new cHdffOsdProvider(mHdffCmdIf);
|
|
cDvbDevice::MakePrimaryDevice(On);
|
|
}
|
|
|
|
bool cDvbHdFfDevice::HasDecoder(void) const
|
|
{
|
|
return isHdffPrimary;
|
|
}
|
|
|
|
cSpuDecoder *cDvbHdFfDevice::GetSpuDecoder(void)
|
|
{
|
|
if (!spuDecoder && IsPrimaryDevice())
|
|
spuDecoder = new cDvbSpuDecoder();
|
|
return spuDecoder;
|
|
}
|
|
|
|
uchar *cDvbHdFfDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
|
|
{
|
|
//TODO
|
|
return NULL;
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
|
|
{
|
|
//TODO???
|
|
cDevice::SetVideoDisplayFormat(VideoDisplayFormat);
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetVideoFormat(bool VideoFormat16_9)
|
|
{
|
|
HDFF::tVideoFormat videoFormat;
|
|
videoFormat.AutomaticEnabled = true;
|
|
videoFormat.AfdEnabled = true;
|
|
videoFormat.TvFormat = (HDFF::eTvFormat) gHdffSetup.TvFormat;
|
|
videoFormat.VideoConversion = (HDFF::eVideoConversion) gHdffSetup.VideoConversion;
|
|
mHdffCmdIf->CmdAvSetVideoFormat(0, &videoFormat);
|
|
}
|
|
|
|
eVideoSystem cDvbHdFfDevice::GetVideoSystem(void)
|
|
{
|
|
eVideoSystem VideoSystem = vsPAL;
|
|
if (fd_video >= 0) {
|
|
video_size_t vs;
|
|
if (ioctl(fd_video, VIDEO_GET_SIZE, &vs) == 0) {
|
|
if (vs.h == 480 || vs.h == 240)
|
|
VideoSystem = vsNTSC;
|
|
}
|
|
else
|
|
LOG_ERROR;
|
|
}
|
|
return VideoSystem;
|
|
}
|
|
|
|
void cDvbHdFfDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect)
|
|
{
|
|
if (fd_video >= 0) {
|
|
video_size_t vs;
|
|
if (ioctl(fd_video, VIDEO_GET_SIZE, &vs) == 0) {
|
|
Width = vs.w;
|
|
Height = vs.h;
|
|
switch (vs.aspect_ratio) {
|
|
default:
|
|
case VIDEO_FORMAT_4_3: VideoAspect = 4.0 / 3.0; break;
|
|
case VIDEO_FORMAT_16_9: VideoAspect = 16.0 / 9.0; break;
|
|
case VIDEO_FORMAT_221_1: VideoAspect = 2.21; break;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
LOG_ERROR;
|
|
}
|
|
cDevice::GetVideoSize(Width, Height, VideoAspect);
|
|
}
|
|
|
|
void cDvbHdFfDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
|
|
{
|
|
gHdffSetup.GetOsdSize(Width, Height, PixelAspect);
|
|
}
|
|
|
|
/*TODO obsolete?
|
|
bool cDvbHdFfDevice::SetAudioBypass(bool On)
|
|
{
|
|
if (setTransferModeForDolbyDigital != 1)
|
|
return false;
|
|
return ioctl(fd_audio, AUDIO_SET_BYPASS_MODE, On) == 0;
|
|
}
|
|
TODO*/
|
|
|
|
bool cDvbHdFfDevice::SetPid(cPidHandle *Handle, int Type, bool On)
|
|
{
|
|
if (Handle->pid) {
|
|
dmx_pes_filter_params pesFilterParams;
|
|
memset(&pesFilterParams, 0, sizeof(pesFilterParams));
|
|
if (On) {
|
|
if (Handle->handle < 0) {
|
|
Handle->handle = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR | O_NONBLOCK, true);
|
|
if (Handle->handle < 0) {
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
}
|
|
if (Type == ptPcr)
|
|
mHdffCmdIf->CmdAvSetPcrPid(0, Handle->pid);
|
|
else if (Type == ptVideo) {
|
|
if (Handle->streamType == 0x1B)
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, Handle->pid, HDFF::videoStreamH264);
|
|
else
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, Handle->pid, HDFF::videoStreamMpeg2);
|
|
}
|
|
else if (Type == ptAudio)
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, Handle->pid, HDFF::audioStreamMpeg1);
|
|
else if (Type == ptDolby)
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, Handle->pid, HDFF::audioStreamAc3);
|
|
if (!(Type <= ptDolby && Handle->used <= 1)) {
|
|
pesFilterParams.pid = Handle->pid;
|
|
pesFilterParams.input = DMX_IN_FRONTEND;
|
|
pesFilterParams.output = DMX_OUT_TS_TAP;
|
|
pesFilterParams.pes_type= DMX_PES_OTHER;
|
|
pesFilterParams.flags = DMX_IMMEDIATE_START;
|
|
if (ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (!Handle->used) {
|
|
CHECK(ioctl(Handle->handle, DMX_STOP));
|
|
if (Type == ptPcr)
|
|
mHdffCmdIf->CmdAvSetPcrPid(0, 0);
|
|
else if (Type == ptVideo)
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, 0, HDFF::videoStreamMpeg2);
|
|
else if (Type == ptAudio)
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, 0, HDFF::audioStreamMpeg1);
|
|
else if (Type == ptDolby)
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, 0, HDFF::audioStreamAc3);
|
|
//TODO missing setting to 0x1FFF??? see cDvbDevice::SetPid()
|
|
close(Handle->handle);
|
|
Handle->handle = -1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void cDvbHdFfDevice::TurnOffLiveMode(bool LiveView)
|
|
{
|
|
// Turn off live PIDs:
|
|
|
|
DetachAll(pidHandles[ptAudio].pid);
|
|
DetachAll(pidHandles[ptVideo].pid);
|
|
DetachAll(pidHandles[ptPcr].pid);
|
|
DetachAll(pidHandles[ptTeletext].pid);
|
|
DelPid(pidHandles[ptAudio].pid);
|
|
DelPid(pidHandles[ptVideo].pid);
|
|
DelPid(pidHandles[ptPcr].pid, ptPcr);
|
|
DelPid(pidHandles[ptTeletext].pid);
|
|
DelPid(pidHandles[ptDolby].pid);
|
|
}
|
|
|
|
bool cDvbHdFfDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
|
|
{
|
|
int apid = Channel->Apid(0);
|
|
int vpid = Channel->Vpid();
|
|
int dpid = Channel->Dpid(0);
|
|
|
|
bool DoTune = !IsTunedToTransponder(Channel);
|
|
|
|
bool pidHandlesVideo = pidHandles[ptVideo].pid == vpid;
|
|
bool pidHandlesAudio = pidHandles[ptAudio].pid == apid;
|
|
|
|
bool TurnOffLivePIDs = DoTune
|
|
|| !IsPrimaryDevice()
|
|
|| LiveView // for a new live view the old PIDs need to be turned off
|
|
|| pidHandlesVideo // for recording the PIDs must be shifted from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER
|
|
;
|
|
|
|
bool StartTransferMode = IsPrimaryDevice() && !DoTune
|
|
&& (LiveView && HasPid(vpid ? vpid : apid) && (!pidHandlesVideo || (!pidHandlesAudio && (dpid ? pidHandles[ptAudio].pid != dpid : true)))// the PID is already set as DMX_PES_OTHER
|
|
|| !LiveView && (pidHandlesVideo || pidHandlesAudio) // a recording is going to shift the PIDs from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER
|
|
);
|
|
if (CamSlot() && !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), CamSlot()->SlotNumber()))
|
|
StartTransferMode |= LiveView && IsPrimaryDevice() && Channel->Ca() >= CA_ENCRYPTED_MIN;
|
|
|
|
bool TurnOnLivePIDs = !StartTransferMode && LiveView;
|
|
|
|
// Turn off live PIDs if necessary:
|
|
|
|
if (TurnOffLivePIDs)
|
|
TurnOffLiveMode(LiveView);
|
|
|
|
// Set the tuner:
|
|
|
|
if (!cDvbDevice::SetChannelDevice(Channel, LiveView))
|
|
return false;
|
|
|
|
// If this channel switch was requested by the EITScanner we don't wait for
|
|
// a lock and don't set any live PIDs (the EITScanner will wait for the lock
|
|
// by itself before setting any filters):
|
|
|
|
if (EITScanner.UsesDevice(this)) //XXX
|
|
return true;
|
|
|
|
// PID settings:
|
|
|
|
if (TurnOnLivePIDs) {
|
|
//SetAudioBypass(false);//TODO obsolete?
|
|
if (!(AddPid(Channel->Ppid(), ptPcr) && AddPid(vpid, ptVideo, Channel->Vtype()) && AddPid(apid, ptAudio))) {
|
|
esyslog("ERROR: failed to set PIDs for channel %d on device %d", Channel->Number(), CardIndex() + 1);
|
|
return false;
|
|
}
|
|
if (IsPrimaryDevice())
|
|
AddPid(Channel->Tpid(), ptTeletext);//TODO obsolete?
|
|
}
|
|
else if (StartTransferMode)
|
|
cControl::Launch(new cTransferControl(this, Channel));
|
|
|
|
return true;
|
|
}
|
|
|
|
int cDvbHdFfDevice::GetAudioChannelDevice(void)
|
|
{
|
|
return audioChannel;
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetAudioChannelDevice(int AudioChannel)
|
|
{
|
|
mHdffCmdIf->CmdAvSetAudioChannel(AudioChannel);
|
|
audioChannel = AudioChannel;
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetVolumeDevice(int Volume)
|
|
{
|
|
mHdffCmdIf->CmdMuxSetVolume(Volume * 100 / 255);
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetDigitalAudioDevice(bool On)
|
|
{
|
|
// not needed
|
|
}
|
|
|
|
void cDvbHdFfDevice::SetAudioTrackDevice(eTrackType Type)
|
|
{
|
|
//printf("SetAudioTrackDevice %d\n", Type);
|
|
const tTrackId *TrackId = GetTrack(Type);
|
|
if (TrackId && TrackId->id) {
|
|
if (IS_AUDIO_TRACK(Type)) {
|
|
if (pidHandles[ptAudio].pid && pidHandles[ptAudio].pid != TrackId->id) {
|
|
DetachAll(pidHandles[ptAudio].pid);
|
|
if (CamSlot())
|
|
CamSlot()->SetPid(pidHandles[ptAudio].pid, false);
|
|
pidHandles[ptAudio].pid = TrackId->id;
|
|
SetPid(&pidHandles[ptAudio], ptAudio, true);
|
|
if (CamSlot()) {
|
|
CamSlot()->SetPid(pidHandles[ptAudio].pid, true);
|
|
CamSlot()->StartDecrypting();
|
|
}
|
|
}
|
|
}
|
|
else if (IS_DOLBY_TRACK(Type)) {
|
|
pidHandles[ptDolby].pid = TrackId->id;
|
|
SetPid(&pidHandles[ptDolby], ptDolby, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cDvbHdFfDevice::CanReplay(void) const
|
|
{
|
|
return cDevice::CanReplay();
|
|
}
|
|
|
|
bool cDvbHdFfDevice::SetPlayMode(ePlayMode PlayMode)
|
|
{
|
|
if (PlayMode == pmNone) {
|
|
mHdffCmdIf->CmdAvEnableVideoAfterStop(0, false);
|
|
mHdffCmdIf->CmdAvSetPcrPid(0, 0);
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, 0, HDFF::videoStreamMpeg2);
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, 0, HDFF::audioStreamMpeg1);
|
|
|
|
ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX);
|
|
mHdffCmdIf->CmdAvSetDecoderInput(0, 0);
|
|
mHdffCmdIf->CmdAvEnableSync(0, true);
|
|
mHdffCmdIf->CmdAvSetPlayMode(0, true);
|
|
}
|
|
else {
|
|
if (playMode == pmNone)
|
|
TurnOffLiveMode(true);
|
|
|
|
mHdffCmdIf->CmdAvSetPlayMode(1, Transferring() || (cTransferControl::ReceiverDevice() == this));
|
|
mHdffCmdIf->CmdAvSetStc(0, 100000);
|
|
mHdffCmdIf->CmdAvEnableSync(0, true);
|
|
mHdffCmdIf->CmdAvEnableVideoAfterStop(0, true);
|
|
|
|
playVideoPid = -1;
|
|
playAudioPid = -1;
|
|
audioCounter = 0;
|
|
videoCounter = 0;
|
|
|
|
mHdffCmdIf->CmdAvSetDecoderInput(0, 2);
|
|
ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY);
|
|
}
|
|
playMode = PlayMode;
|
|
return true;
|
|
}
|
|
|
|
int64_t cDvbHdFfDevice::GetSTC(void)
|
|
{
|
|
if (fd_video >= 0) {
|
|
uint64_t pts;
|
|
if (ioctl(fd_video, VIDEO_GET_PTS, &pts) == -1) {
|
|
esyslog("ERROR: pts %d: %m", CardIndex() + 1);
|
|
return -1;
|
|
}
|
|
return pts;
|
|
}
|
|
if (fd_audio >= 0) {
|
|
uint64_t pts;
|
|
if (ioctl(fd_audio, AUDIO_GET_PTS, &pts) == -1) {
|
|
esyslog("ERROR: pts %d: %m", CardIndex() + 1);
|
|
return -1;
|
|
}
|
|
return pts;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cDvbHdFfDevice::TrickSpeed(int Speed)
|
|
{
|
|
mHdffCmdIf->CmdAvEnableSync(0, false);
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, 0, HDFF::audioStreamMpeg1);
|
|
playAudioPid = -1;
|
|
if (Speed > 0)
|
|
mHdffCmdIf->CmdAvSetVideoSpeed(0, 100 / Speed);
|
|
}
|
|
|
|
void cDvbHdFfDevice::Clear(void)
|
|
{
|
|
CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, 0, HDFF::videoStreamMpeg1);
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, 0, HDFF::audioStreamMpeg1);
|
|
playVideoPid = -1;
|
|
playAudioPid = -1;
|
|
cDevice::Clear();
|
|
}
|
|
|
|
void cDvbHdFfDevice::Play(void)
|
|
{
|
|
mHdffCmdIf->CmdAvEnableSync(0, true);
|
|
mHdffCmdIf->CmdAvSetVideoSpeed(0, 100);
|
|
mHdffCmdIf->CmdAvSetAudioSpeed(0, 100);
|
|
cDevice::Play();
|
|
}
|
|
|
|
void cDvbHdFfDevice::Freeze(void)
|
|
{
|
|
mHdffCmdIf->CmdAvSetVideoSpeed(0, 0);
|
|
mHdffCmdIf->CmdAvSetAudioSpeed(0, 0);
|
|
cDevice::Freeze();
|
|
}
|
|
|
|
void cDvbHdFfDevice::Mute(void)
|
|
{
|
|
//TODO???
|
|
cDevice::Mute();
|
|
}
|
|
|
|
static HDFF::eVideoStreamType MapVideoStreamTypes(int Vtype)
|
|
{
|
|
switch (Vtype) {
|
|
case 0x01: return HDFF::videoStreamMpeg1;
|
|
case 0x02: return HDFF::videoStreamMpeg2;
|
|
case 0x1B: return HDFF::videoStreamH264;
|
|
default: return HDFF::videoStreamMpeg2; // fallback to MPEG2
|
|
}
|
|
}
|
|
|
|
void cDvbHdFfDevice::StillPicture(const uchar *Data, int Length)
|
|
{
|
|
if (!Data || Length < TS_SIZE)
|
|
return;
|
|
if (Data[0] == 0x47) {
|
|
// TS data
|
|
cDevice::StillPicture(Data, Length);
|
|
}
|
|
else if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01 && (Data[3] & 0xF0) == 0xE0) {
|
|
// PES data
|
|
char *buf = MALLOC(char, Length);
|
|
if (!buf)
|
|
return;
|
|
int i = 0;
|
|
int blen = 0;
|
|
while (i < Length - 6) {
|
|
if (Data[i] == 0x00 && Data[i + 1] == 0x00 && Data[i + 2] == 0x01) {
|
|
int len = Data[i + 4] * 256 + Data[i + 5];
|
|
if ((Data[i + 3] & 0xF0) == 0xE0) { // video packet
|
|
// skip PES header
|
|
int offs = i + 6;
|
|
// skip header extension
|
|
if ((Data[i + 6] & 0xC0) == 0x80) {
|
|
// MPEG-2 PES header
|
|
if (Data[i + 8] >= Length)
|
|
break;
|
|
offs += 3;
|
|
offs += Data[i + 8];
|
|
len -= 3;
|
|
len -= Data[i + 8];
|
|
if (len < 0 || offs + len > Length)
|
|
break;
|
|
}
|
|
else {
|
|
// MPEG-1 PES header
|
|
while (offs < Length && len > 0 && Data[offs] == 0xFF) {
|
|
offs++;
|
|
len--;
|
|
}
|
|
if (offs <= Length - 2 && len >= 2 && (Data[offs] & 0xC0) == 0x40) {
|
|
offs += 2;
|
|
len -= 2;
|
|
}
|
|
if (offs <= Length - 5 && len >= 5 && (Data[offs] & 0xF0) == 0x20) {
|
|
offs += 5;
|
|
len -= 5;
|
|
}
|
|
else if (offs <= Length - 10 && len >= 10 && (Data[offs] & 0xF0) == 0x30) {
|
|
offs += 10;
|
|
len -= 10;
|
|
}
|
|
else if (offs < Length && len > 0) {
|
|
offs++;
|
|
len--;
|
|
}
|
|
}
|
|
if (blen + len > Length) // invalid PES length field
|
|
break;
|
|
memcpy(&buf[blen], &Data[offs], len);
|
|
i = offs + len;
|
|
blen += len;
|
|
}
|
|
else if (Data[i + 3] >= 0xBD && Data[i + 3] <= 0xDF) // other PES packets
|
|
i += len + 6;
|
|
else
|
|
i++;
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
mHdffCmdIf->CmdAvShowStillImage(0, (uint8_t *)buf, blen, MapVideoStreamTypes(PatPmtParser()->Vtype()));
|
|
free(buf);
|
|
}
|
|
else {
|
|
// non-PES data
|
|
mHdffCmdIf->CmdAvShowStillImage(0, Data, Length, MapVideoStreamTypes(PatPmtParser()->Vtype()));
|
|
}
|
|
}
|
|
|
|
bool cDvbHdFfDevice::Poll(cPoller &Poller, int TimeoutMs)
|
|
{
|
|
Poller.Add(fd_video, true);
|
|
return Poller.Poll(TimeoutMs);
|
|
}
|
|
|
|
bool cDvbHdFfDevice::Flush(int TimeoutMs)
|
|
{
|
|
//TODO actually this function should wait until all buffered data has been processed by the card, but how?
|
|
return true;
|
|
}
|
|
|
|
void cDvbHdFfDevice::BuildTsPacket(uint8_t * TsBuffer, bool PusiSet, uint16_t Pid, uint8_t Counter, const uint8_t * Data, uint32_t Length)
|
|
{
|
|
TsBuffer[0] = 0x47;
|
|
TsBuffer[1] = PusiSet ? 0x40 : 0x00;
|
|
TsBuffer[1] |= Pid >> 8;
|
|
TsBuffer[2] = Pid & 0xFF;
|
|
if (Length >= 184)
|
|
{
|
|
TsBuffer[3] = 0x10 | Counter;
|
|
memcpy(TsBuffer + 4, Data, 184);
|
|
}
|
|
else
|
|
{
|
|
uint8_t adaptationLength;
|
|
|
|
TsBuffer[3] = 0x30 | Counter;
|
|
adaptationLength = 183 - Length;
|
|
TsBuffer[4] = adaptationLength;
|
|
if (adaptationLength > 0)
|
|
{
|
|
TsBuffer[5] = 0x00;
|
|
memset(TsBuffer + 6, 0xFF, adaptationLength - 1);
|
|
}
|
|
memcpy(TsBuffer + 5 + adaptationLength, Data, Length);
|
|
}
|
|
}
|
|
|
|
uint32_t cDvbHdFfDevice::PesToTs(uint8_t * TsBuffer, uint16_t Pid, uint8_t & Counter, const uint8_t * Data, uint32_t Length)
|
|
{
|
|
uint32_t tsOffset;
|
|
uint32_t i;
|
|
|
|
tsOffset = 0;
|
|
i = 0;
|
|
while (Length > 0)
|
|
{
|
|
BuildTsPacket(TsBuffer + tsOffset, i == 0, Pid, Counter, Data + i * 184, Length);
|
|
if (Length >= 184)
|
|
Length -= 184;
|
|
else
|
|
Length = 0;
|
|
Counter = (Counter + 1) & 15;
|
|
tsOffset += 188;
|
|
i++;
|
|
}
|
|
return tsOffset;
|
|
}
|
|
|
|
int cDvbHdFfDevice::PlayVideo(const uchar *Data, int Length)
|
|
{
|
|
//TODO: support greater Length
|
|
uint8_t tsBuffer[188 * 16];
|
|
uint32_t tsLength;
|
|
int pid = 100;
|
|
|
|
tsLength = PesToTs(tsBuffer, pid, videoCounter, Data, Length);
|
|
|
|
if (pid != playVideoPid) {
|
|
playVideoPid = pid;
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, playVideoPid, HDFF::videoStreamMpeg2, true);
|
|
}
|
|
if (WriteAllOrNothing(fd_video, tsBuffer, tsLength, 1000, 10) <= 0)
|
|
Length = 0;
|
|
return Length;
|
|
}
|
|
|
|
int cDvbHdFfDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
|
|
{
|
|
uint8_t streamId;
|
|
uint8_t tsBuffer[188 * 16];
|
|
uint32_t tsLength;
|
|
HDFF::eAudioStreamType streamType = HDFF::audioStreamMpeg1;
|
|
HDFF::eAVContainerType containerType = HDFF::avContainerPes;
|
|
int pid;
|
|
|
|
streamId = Data[3];
|
|
if (streamId >= 0xC0 && streamId <= 0xDF)
|
|
{
|
|
streamType = HDFF::audioStreamMpeg1;
|
|
}
|
|
else if (streamId == 0xBD)
|
|
{
|
|
const uint8_t * payload = Data + 9 + Data[8];
|
|
if ((payload[0] & 0xF8) == 0xA0)
|
|
{
|
|
containerType = HDFF::avContainerPesDvd;
|
|
streamType = HDFF::audioStreamPcm;
|
|
}
|
|
else if ((payload[0] & 0xF8) == 0x88)
|
|
{
|
|
containerType = HDFF::avContainerPesDvd;
|
|
streamType = HDFF::audioStreamDts;
|
|
}
|
|
else if ((payload[0] & 0xF8) == 0x80)
|
|
{
|
|
containerType = HDFF::avContainerPesDvd;
|
|
streamType = HDFF::audioStreamAc3;
|
|
}
|
|
else
|
|
{
|
|
streamType = HDFF::audioStreamAc3;
|
|
}
|
|
}
|
|
pid = 200 + (int) streamType;
|
|
tsLength = PesToTs(tsBuffer, pid, audioCounter, Data, Length);
|
|
|
|
if (pid != playAudioPid) {
|
|
playAudioPid = pid;
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, playAudioPid, streamType, containerType);
|
|
}
|
|
if (WriteAllOrNothing(fd_video, tsBuffer, tsLength, 1000, 10) <= 0)
|
|
Length = 0;
|
|
return Length;
|
|
}
|
|
|
|
int cDvbHdFfDevice::PlayTsVideo(const uchar *Data, int Length)
|
|
{
|
|
int pid = TsPid(Data);
|
|
if (pid != playVideoPid) {
|
|
PatPmtParser();
|
|
if (pid == PatPmtParser()->Vpid()) {
|
|
playVideoPid = pid;
|
|
mHdffCmdIf->CmdAvSetVideoPid(0, playVideoPid, MapVideoStreamTypes(PatPmtParser()->Vtype()), true);
|
|
}
|
|
}
|
|
return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
|
|
}
|
|
|
|
static HDFF::eAudioStreamType MapAudioStreamTypes(int Atype)
|
|
{
|
|
switch (Atype) {
|
|
case 0x03: return HDFF::audioStreamMpeg1;
|
|
case 0x04: return HDFF::audioStreamMpeg2;
|
|
case SI::AC3DescriptorTag: return HDFF::audioStreamAc3;
|
|
case SI::EnhancedAC3DescriptorTag: return HDFF::audioStreamEAc3;
|
|
case 0x0F: return HDFF::audioStreamAac;
|
|
case 0x11: return HDFF::audioStreamHeAac;
|
|
default: return HDFF::audioStreamMaxValue; // there is no HDFF::audioStreamNone
|
|
}
|
|
}
|
|
|
|
int cDvbHdFfDevice::PlayTsAudio(const uchar *Data, int Length)
|
|
{
|
|
int pid = TsPid(Data);
|
|
if (pid != playAudioPid) {
|
|
playAudioPid = pid;
|
|
int AudioStreamType = -1;
|
|
for (int i = 0; PatPmtParser()->Apid(i); i++) {
|
|
if (playAudioPid == PatPmtParser()->Apid(i)) {
|
|
AudioStreamType = PatPmtParser()->Atype(i);
|
|
break;
|
|
}
|
|
}
|
|
if (AudioStreamType < 0) {
|
|
for (int i = 0; PatPmtParser()->Dpid(i); i++) {
|
|
if (playAudioPid == PatPmtParser()->Dpid(i)) {
|
|
AudioStreamType = PatPmtParser()->Dtype(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mHdffCmdIf->CmdAvSetAudioPid(0, playAudioPid, MapAudioStreamTypes(AudioStreamType));
|
|
}
|
|
return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
|
|
}
|
|
|
|
HDFF::cHdffCmdIf *cDvbHdFfDevice::GetHdffCmdHandler(void)
|
|
{
|
|
//TODO why not just keep a pointer?
|
|
if (devHdffOffset >= 0) {
|
|
cDvbHdFfDevice *device = (cDvbHdFfDevice *)GetDevice(devHdffOffset);
|
|
if (device)
|
|
return device->mHdffCmdIf;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// --- cDvbHdFfDeviceProbe ---------------------------------------------------
|
|
|
|
bool cDvbHdFfDeviceProbe::Probe(int Adapter, int Frontend)
|
|
{
|
|
static uint32_t SubsystemIds[] = {
|
|
0x13C23009, // Technotrend S2-6400 HDFF development samples
|
|
0x13C2300A, // Technotrend S2-6400 HDFF production version
|
|
0x00000000
|
|
};
|
|
cString FileName;
|
|
cReadLine ReadLine;
|
|
FILE *f = NULL;
|
|
uint32_t SubsystemId = 0;
|
|
FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_vendor", Adapter, Frontend);
|
|
if ((f = fopen(FileName, "r")) != NULL) {
|
|
if (char *s = ReadLine.Read(f))
|
|
SubsystemId = strtoul(s, NULL, 0) << 16;
|
|
fclose(f);
|
|
}
|
|
FileName = cString::sprintf("/sys/class/dvb/dvb%d.frontend%d/device/subsystem_device", Adapter, Frontend);
|
|
if ((f = fopen(FileName, "r")) != NULL) {
|
|
if (char *s = ReadLine.Read(f))
|
|
SubsystemId |= strtoul(s, NULL, 0);
|
|
fclose(f);
|
|
}
|
|
for (uint32_t *sid = SubsystemIds; *sid; sid++) {
|
|
if (*sid == SubsystemId) {
|
|
FileName = cString::sprintf("/dev/dvb/adapter%d/osd0", Adapter);
|
|
int fd = open(FileName, O_RDWR);
|
|
if (fd != -1) { //TODO treat the second path of the S2-6400 as a budget device
|
|
close(fd);
|
|
dsyslog("creating cDvbHdFfDevice");
|
|
new cDvbHdFfDevice(Adapter, Frontend);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|