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.27 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.27.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.26-1.7.27.diff MD5 checksums: bfeaa79a9e55144bca2b69139c45f1bb vdr-1.7.27.tar.bz2 b23344be51d3e2c2d96cc2dd4e8e564e vdr-1.7.26-1.7.27.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. From the HISTORY file: - Updated the Finnish OSD texts (thanks to Rolf Ahrenberg). - Changed the Green button in the "Edit timer" menu from "Once" to "Single" (suggested by Rolf Ahrenberg). - Fixed some typos in HISTORY and CONTRIBUTORS (thanks to Ville Skyttä). - The channel name column in the "What's on now/next" menu now adjusts its width to display the full short name of each channel (suggested by Dominic Evans). - Dropped the meanwhile obsolete script 'i18n-to-gettext'. - Removed the obsolete function cPlugin::RegisterI18n(). - Removed the obsolete typedef tI18nPhrase. - Adapted menu column widths of 'skincurses' to the wider HD OSD sizes. - Deactivated definition of __RECORDING_H_DEPRECATED_DIRECT_MEMBER_ACCESS (recording.h) and LEGACY_CRECEIVER (receiver.h) to trigger an error for any plugin that still uses the respective code. You can reactivate these to quickly make your plugin compile again, but beware that these code parts will be removed in one of the next versions. - Made the "overloaded-virtual" warning an error to detect hidden overloaded virtual functions (thanks to Anssi Hannula for pointing out -Werror=...). Plugin authors may want to change -Woverloaded-virtual to -Werror=overloaded-virtual in their Makefiles. - Updated the Estonian OSD texts (thanks to Arthur Konovalov). - Improved fast forwarding to the end of a timeshift recording. - The new function cDevice::DeviceName() returns a string identifying the name of the given device. - When toggling a timer between "Single" and "Repeating", the previous setting is now retained in case the user toggles back to the original value. - When estimating the remaining disk space (in hours), the average data rate of all existing recordings is now taken into account. If this value can't be determined, the previous value of 25.75 MB/min is taken. - No longer using GetFont() (which is not thread safe) in the 'osddemo' plugin. - No longer using GetFont() (which is not thread safe) in cSubtitleRegion::UpdateTextData(). - Fixed a memory leak in cSubtitleRegion::UpdateTextData(). - Moved setting LC_NUMERIC further up to make sure any floating point numbers use a decimal point (suggested by Tobias Grimm). - Added missing channel locking to cEIT. - Fixed reduced bpp support for DVB subtitles (thanks to Rolf Ahrenberg). - Updated the Italian OSD texts (thanks to Diego Pierotto). - Reverted some improvements to Make.config.template (thanks to Christian Ruppert). - Fixed handling IDLEPRIORITY in cDvbDevice::ProvidesChannel() (thanks to Frank Schmirler).
793 lines
27 KiB
C
793 lines
27 KiB
C
/*
|
|
* dvbsdffdevice.h: The DVB SD Full Featured device interface
|
|
*
|
|
* See the README file for copyright information and how to reach the author.
|
|
*
|
|
* $Id: dvbsdffdevice.c 2.33 2012/03/11 13:32:42 kls Exp $
|
|
*/
|
|
|
|
#include "dvbsdffdevice.h"
|
|
#include <errno.h>
|
|
#include <limits.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 "dvbsdffosd.h"
|
|
|
|
// --- cDvbSdFfDevice --------------------------------------------------------
|
|
|
|
int cDvbSdFfDevice::devVideoOffset = -1;
|
|
|
|
cDvbSdFfDevice::cDvbSdFfDevice(int Adapter, int Frontend, bool OutputOnly)
|
|
:cDvbDevice(Adapter, Frontend)
|
|
{
|
|
spuDecoder = NULL;
|
|
digitalAudio = false;
|
|
playMode = pmNone;
|
|
outputOnly = OutputOnly;
|
|
|
|
// 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);
|
|
fd_stc = DvbOpen(DEV_DVB_DEMUX, adapter, frontend, O_RDWR);
|
|
|
|
// The offset of the /dev/video devices:
|
|
|
|
if (devVideoOffset < 0) { // the first one checks this
|
|
FILE *f = NULL;
|
|
char buffer[PATH_MAX];
|
|
for (int ofs = 0; ofs < 100; ofs++) {
|
|
snprintf(buffer, sizeof(buffer), "/proc/video/dev/video%d", ofs);
|
|
if ((f = fopen(buffer, "r")) != NULL) {
|
|
if (fgets(buffer, sizeof(buffer), f)) {
|
|
if (strstr(buffer, "DVB Board")) { // found the _first_ DVB card
|
|
devVideoOffset = ofs;
|
|
dsyslog("video device offset is %d", devVideoOffset);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
fclose(f);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
if (devVideoOffset < 0)
|
|
devVideoOffset = 0;
|
|
if (f)
|
|
fclose(f);
|
|
}
|
|
devVideoIndex = devVideoOffset >= 0 ? devVideoOffset++ : -1;
|
|
}
|
|
|
|
cDvbSdFfDevice::~cDvbSdFfDevice()
|
|
{
|
|
delete spuDecoder;
|
|
// We're not explicitly closing any device files here, since this sometimes
|
|
// caused segfaults. Besides, the program is about to terminate anyway...
|
|
}
|
|
|
|
void cDvbSdFfDevice::MakePrimaryDevice(bool On)
|
|
{
|
|
if (On)
|
|
new cDvbOsdProvider(fd_osd);
|
|
cDvbDevice::MakePrimaryDevice(On);
|
|
}
|
|
|
|
bool cDvbSdFfDevice::HasDecoder(void) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool cDvbSdFfDevice::AvoidRecording(void) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
cSpuDecoder *cDvbSdFfDevice::GetSpuDecoder(void)
|
|
{
|
|
if (!spuDecoder && IsPrimaryDevice())
|
|
spuDecoder = new cDvbSpuDecoder();
|
|
return spuDecoder;
|
|
}
|
|
|
|
uchar *cDvbSdFfDevice::GrabImage(int &Size, bool Jpeg, int Quality, int SizeX, int SizeY)
|
|
{
|
|
if (devVideoIndex < 0)
|
|
return NULL;
|
|
char buffer[PATH_MAX];
|
|
snprintf(buffer, sizeof(buffer), "%s%d", DEV_VIDEO, devVideoIndex);
|
|
int videoDev = open(buffer, O_RDWR);
|
|
if (videoDev >= 0) {
|
|
uchar *result = NULL;
|
|
// set up the size and RGB
|
|
v4l2_format fmt;
|
|
memset(&fmt, 0, sizeof(fmt));
|
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
fmt.fmt.pix.width = SizeX;
|
|
fmt.fmt.pix.height = SizeY;
|
|
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
|
|
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
|
if (ioctl(videoDev, VIDIOC_S_FMT, &fmt) == 0) {
|
|
v4l2_requestbuffers reqBuf;
|
|
memset(&reqBuf, 0, sizeof(reqBuf));
|
|
reqBuf.count = 2;
|
|
reqBuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
reqBuf.memory = V4L2_MEMORY_MMAP;
|
|
if (ioctl(videoDev, VIDIOC_REQBUFS, &reqBuf) >= 0) {
|
|
v4l2_buffer mbuf;
|
|
memset(&mbuf, 0, sizeof(mbuf));
|
|
mbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
mbuf.memory = V4L2_MEMORY_MMAP;
|
|
if (ioctl(videoDev, VIDIOC_QUERYBUF, &mbuf) == 0) {
|
|
int msize = mbuf.length;
|
|
unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0);
|
|
if (mem && mem != (unsigned char *)-1) {
|
|
v4l2_buffer buf;
|
|
memset(&buf, 0, sizeof(buf));
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
buf.index = 0;
|
|
if (ioctl(videoDev, VIDIOC_QBUF, &buf) == 0) {
|
|
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
if (ioctl (videoDev, VIDIOC_STREAMON, &type) == 0) {
|
|
memset(&buf, 0, sizeof(buf));
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = V4L2_MEMORY_MMAP;
|
|
buf.index = 0;
|
|
if (ioctl(videoDev, VIDIOC_DQBUF, &buf) == 0) {
|
|
if (ioctl(videoDev, VIDIOC_STREAMOFF, &type) == 0) {
|
|
// make RGB out of BGR:
|
|
int memsize = fmt.fmt.pix.width * fmt.fmt.pix.height;
|
|
unsigned char *mem1 = mem;
|
|
for (int i = 0; i < memsize; i++) {
|
|
unsigned char tmp = mem1[2];
|
|
mem1[2] = mem1[0];
|
|
mem1[0] = tmp;
|
|
mem1 += 3;
|
|
}
|
|
|
|
if (Quality < 0)
|
|
Quality = 100;
|
|
|
|
dsyslog("grabbing to %s %d %d %d", Jpeg ? "JPEG" : "PNM", Quality, fmt.fmt.pix.width, fmt.fmt.pix.height);
|
|
if (Jpeg) {
|
|
// convert to JPEG:
|
|
result = RgbToJpeg(mem, fmt.fmt.pix.width, fmt.fmt.pix.height, Size, Quality);
|
|
if (!result)
|
|
esyslog("ERROR: failed to convert image to JPEG");
|
|
}
|
|
else {
|
|
// convert to PNM:
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "P6\n%d\n%d\n255\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
|
|
int l = strlen(buf);
|
|
int bytes = memsize * 3;
|
|
Size = l + bytes;
|
|
result = MALLOC(uchar, Size);
|
|
if (result) {
|
|
memcpy(result, buf, l);
|
|
memcpy(result + l, mem, bytes);
|
|
}
|
|
else
|
|
esyslog("ERROR: failed to convert image to PNM");
|
|
}
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_STREAMOFF failed");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_DQBUF failed");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_STREAMON failed");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_QBUF failed");
|
|
munmap(mem, msize);
|
|
}
|
|
else
|
|
esyslog("ERROR: failed to memmap video device");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_QUERYBUF failed");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_REQBUFS failed");
|
|
}
|
|
else
|
|
esyslog("ERROR: video device VIDIOC_S_FMT failed");
|
|
close(videoDev);
|
|
return result;
|
|
}
|
|
else
|
|
LOG_ERROR_STR(buffer);
|
|
return NULL;
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
|
|
{
|
|
cDevice::SetVideoDisplayFormat(VideoDisplayFormat);
|
|
if (Setup.VideoFormat) {
|
|
CHECK(ioctl(fd_video, VIDEO_SET_DISPLAY_FORMAT, VIDEO_LETTER_BOX));
|
|
}
|
|
else {
|
|
switch (VideoDisplayFormat) {
|
|
case vdfPanAndScan:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_DISPLAY_FORMAT, VIDEO_PAN_SCAN));
|
|
break;
|
|
case vdfLetterBox:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_DISPLAY_FORMAT, VIDEO_LETTER_BOX));
|
|
break;
|
|
case vdfCenterCutOut:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_DISPLAY_FORMAT, VIDEO_CENTER_CUT_OUT));
|
|
break;
|
|
default: esyslog("ERROR: unknown video display format %d", VideoDisplayFormat);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetVideoFormat(bool VideoFormat16_9)
|
|
{
|
|
CHECK(ioctl(fd_video, VIDEO_SET_FORMAT, VideoFormat16_9 ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3));
|
|
SetVideoDisplayFormat(eVideoDisplayFormat(Setup.VideoDisplayFormat));
|
|
}
|
|
|
|
eVideoSystem cDvbSdFfDevice::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 cDvbSdFfDevice::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 cDvbSdFfDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect)
|
|
{
|
|
if (fd_video >= 0) {
|
|
video_size_t vs;
|
|
if (ioctl(fd_video, VIDEO_GET_SIZE, &vs) == 0) {
|
|
Width = 720;
|
|
if (vs.h != 480 && vs.h != 240)
|
|
Height = 576; // PAL
|
|
else
|
|
Height = 480; // NTSC
|
|
switch (Setup.VideoFormat ? vs.aspect_ratio : VIDEO_FORMAT_4_3) {
|
|
default:
|
|
case VIDEO_FORMAT_4_3: PixelAspect = 4.0 / 3.0; break;
|
|
case VIDEO_FORMAT_221_1: // FF DVB cards only distinguish between 4:3 and 16:9
|
|
case VIDEO_FORMAT_16_9: PixelAspect = 16.0 / 9.0; break;
|
|
}
|
|
PixelAspect /= double(Width) / Height;
|
|
return;
|
|
}
|
|
else
|
|
LOG_ERROR;
|
|
}
|
|
cDevice::GetOsdSize(Width, Height, PixelAspect);
|
|
}
|
|
|
|
bool cDvbSdFfDevice::SetAudioBypass(bool On)
|
|
{
|
|
if (setTransferModeForDolbyDigital != 1)
|
|
return false;
|
|
return ioctl(fd_audio, AUDIO_SET_BYPASS_MODE, On) == 0;
|
|
}
|
|
|
|
// ptAudio ptVideo ptPcr ptTeletext ptDolby ptOther
|
|
static dmx_pes_type_t PesTypes[] = { DMX_PES_AUDIO, DMX_PES_VIDEO, DMX_PES_PCR, DMX_PES_TELETEXT, DMX_PES_OTHER, DMX_PES_OTHER };
|
|
|
|
bool cDvbSdFfDevice::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;
|
|
}
|
|
}
|
|
pesFilterParams.pid = Handle->pid;
|
|
pesFilterParams.input = DMX_IN_FRONTEND;
|
|
pesFilterParams.output = (Type <= ptTeletext && Handle->used <= 1) ? DMX_OUT_DECODER : DMX_OUT_TS_TAP;
|
|
pesFilterParams.pes_type= PesTypes[Type < ptOther ? Type : ptOther];
|
|
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 <= ptTeletext) {
|
|
pesFilterParams.pid = 0x1FFF;
|
|
pesFilterParams.input = DMX_IN_FRONTEND;
|
|
pesFilterParams.output = DMX_OUT_DECODER;
|
|
pesFilterParams.pes_type= PesTypes[Type];
|
|
pesFilterParams.flags = DMX_IMMEDIATE_START;
|
|
CHECK(ioctl(Handle->handle, DMX_SET_PES_FILTER, &pesFilterParams));
|
|
if (PesTypes[Type] == DMX_PES_VIDEO) // let's only do this once
|
|
SetPlayMode(pmNone); // necessary to switch a PID from DMX_PES_VIDEO/AUDIO to DMX_PES_OTHER
|
|
}
|
|
close(Handle->handle);
|
|
Handle->handle = -1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cDvbSdFfDevice::ProvidesSource(int Source) const
|
|
{
|
|
if (outputOnly)
|
|
return false;
|
|
else
|
|
return cDvbDevice::ProvidesSource(Source);
|
|
}
|
|
|
|
void cDvbSdFfDevice::TurnOffLiveMode(bool LiveView)
|
|
{
|
|
if (LiveView) {
|
|
// Avoid noise while switching:
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true));
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
|
|
CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
|
|
}
|
|
|
|
// 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 cDvbSdFfDevice::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;
|
|
|
|
// PID settings:
|
|
|
|
if (TurnOnLivePIDs) {
|
|
SetAudioBypass(false);
|
|
if (!(AddPid(Channel->Ppid(), ptPcr) && AddPid(vpid, ptVideo) && 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);
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true)); // actually one would expect 'false' here, but according to Marco Schluessler <marco@lordzodiac.de> this works
|
|
// to avoid missing audio after replaying a DVD; with 'false' there is an audio disturbance when switching
|
|
// between two channels on the same transponder on DVB-S
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
|
|
}
|
|
else if (StartTransferMode)
|
|
cControl::Launch(new cTransferControl(this, Channel));
|
|
|
|
return true;
|
|
}
|
|
|
|
int cDvbSdFfDevice::GetAudioChannelDevice(void)
|
|
{
|
|
audio_status_t as;
|
|
CHECK(ioctl(fd_audio, AUDIO_GET_STATUS, &as));
|
|
return as.channel_select;
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetAudioChannelDevice(int AudioChannel)
|
|
{
|
|
CHECK(ioctl(fd_audio, AUDIO_CHANNEL_SELECT, AudioChannel));
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetVolumeDevice(int Volume)
|
|
{
|
|
if (digitalAudio)
|
|
Volume = 0;
|
|
audio_mixer_t am;
|
|
// conversion for linear volume response:
|
|
am.volume_left = am.volume_right = 2 * Volume - Volume * Volume / 255;
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_MIXER, &am));
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetDigitalAudioDevice(bool On)
|
|
{
|
|
if (digitalAudio != On) {
|
|
if (digitalAudio)
|
|
cCondWait::SleepMs(1000); // Wait until any leftover digital data has been flushed
|
|
digitalAudio = On;
|
|
SetVolumeDevice(On || IsMute() ? 0 : CurrentVolume());
|
|
}
|
|
}
|
|
|
|
void cDvbSdFfDevice::SetAudioTrackDevice(eTrackType Type)
|
|
{
|
|
const tTrackId *TrackId = GetTrack(Type);
|
|
if (TrackId && TrackId->id) {
|
|
SetAudioBypass(false);
|
|
if (IS_AUDIO_TRACK(Type) || (IS_DOLBY_TRACK(Type) && SetAudioBypass(true))) {
|
|
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)) {
|
|
if (setTransferModeForDolbyDigital == 0)
|
|
return;
|
|
// Currently this works only in Transfer Mode
|
|
ForceTransferMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cDvbSdFfDevice::CanReplay(void) const
|
|
{
|
|
return cDevice::CanReplay();
|
|
}
|
|
|
|
bool cDvbSdFfDevice::SetPlayMode(ePlayMode PlayMode)
|
|
{
|
|
if (PlayMode != pmExtern_THIS_SHOULD_BE_AVOIDED && fd_video < 0 && fd_audio < 0) {
|
|
// reopen the devices
|
|
fd_video = DvbOpen(DEV_DVB_VIDEO, adapter, frontend, O_RDWR | O_NONBLOCK);
|
|
fd_audio = DvbOpen(DEV_DVB_AUDIO, adapter, frontend, O_RDWR | O_NONBLOCK);
|
|
SetVideoFormat(Setup.VideoFormat);
|
|
}
|
|
|
|
switch (PlayMode) {
|
|
case pmNone:
|
|
// special handling to return from PCM replay:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
|
|
CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY));
|
|
CHECK(ioctl(fd_video, VIDEO_PLAY));
|
|
|
|
CHECK(ioctl(fd_video, VIDEO_STOP, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_STOP, true));
|
|
CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
|
|
CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
|
|
CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX));
|
|
CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false));
|
|
break;
|
|
case pmAudioVideo:
|
|
case pmAudioOnlyBlack:
|
|
if (playMode == pmNone)
|
|
TurnOffLiveMode(true);
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, PlayMode == pmAudioVideo));
|
|
CHECK(ioctl(fd_audio, AUDIO_PLAY));
|
|
CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY));
|
|
CHECK(ioctl(fd_video, VIDEO_PLAY));
|
|
break;
|
|
case pmAudioOnly:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_STOP, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
|
|
CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
|
|
CHECK(ioctl(fd_audio, AUDIO_PLAY));
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, false));
|
|
break;
|
|
case pmVideoOnly:
|
|
CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true));
|
|
CHECK(ioctl(fd_video, VIDEO_STOP, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
|
|
CHECK(ioctl(fd_audio, AUDIO_PLAY));
|
|
CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
|
|
CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY));
|
|
CHECK(ioctl(fd_video, VIDEO_PLAY));
|
|
break;
|
|
case pmExtern_THIS_SHOULD_BE_AVOIDED:
|
|
close(fd_video);
|
|
close(fd_audio);
|
|
fd_video = fd_audio = -1;
|
|
break;
|
|
default: esyslog("ERROR: unknown playmode %d", PlayMode);
|
|
}
|
|
playMode = PlayMode;
|
|
return true;
|
|
}
|
|
|
|
int64_t cDvbSdFfDevice::GetSTC(void)
|
|
{
|
|
if (fd_stc >= 0) {
|
|
struct dmx_stc stc;
|
|
stc.num = 0;
|
|
if (ioctl(fd_stc, DMX_GET_STC, &stc) == -1) {
|
|
esyslog("ERROR: stc %d: %m", CardIndex() + 1);
|
|
return -1;
|
|
}
|
|
return stc.stc / stc.base;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cDvbSdFfDevice::TrickSpeed(int Speed)
|
|
{
|
|
if (fd_video >= 0)
|
|
CHECK(ioctl(fd_video, VIDEO_SLOWMOTION, Speed));
|
|
}
|
|
|
|
void cDvbSdFfDevice::Clear(void)
|
|
{
|
|
if (fd_video >= 0)
|
|
CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER));
|
|
if (fd_audio >= 0)
|
|
CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER));
|
|
cDevice::Clear();
|
|
}
|
|
|
|
void cDvbSdFfDevice::Play(void)
|
|
{
|
|
if (playMode == pmAudioOnly || playMode == pmAudioOnlyBlack) {
|
|
if (fd_audio >= 0)
|
|
CHECK(ioctl(fd_audio, AUDIO_CONTINUE));
|
|
}
|
|
else {
|
|
if (fd_audio >= 0) {
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true));
|
|
CHECK(ioctl(fd_audio, AUDIO_CONTINUE));
|
|
}
|
|
if (fd_video >= 0)
|
|
CHECK(ioctl(fd_video, VIDEO_CONTINUE));
|
|
}
|
|
cDevice::Play();
|
|
}
|
|
|
|
void cDvbSdFfDevice::Freeze(void)
|
|
{
|
|
if (playMode == pmAudioOnly || playMode == pmAudioOnlyBlack) {
|
|
if (fd_audio >= 0)
|
|
CHECK(ioctl(fd_audio, AUDIO_PAUSE));
|
|
}
|
|
else {
|
|
if (fd_audio >= 0) {
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
|
|
CHECK(ioctl(fd_audio, AUDIO_PAUSE));
|
|
}
|
|
if (fd_video >= 0)
|
|
CHECK(ioctl(fd_video, VIDEO_FREEZE));
|
|
}
|
|
cDevice::Freeze();
|
|
}
|
|
|
|
void cDvbSdFfDevice::Mute(void)
|
|
{
|
|
if (fd_audio >= 0) {
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false));
|
|
CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true));
|
|
}
|
|
cDevice::Mute();
|
|
}
|
|
|
|
void cDvbSdFfDevice::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++;
|
|
}
|
|
video_still_picture sp = { buf, blen };
|
|
CHECK(ioctl(fd_video, VIDEO_STILLPICTURE, &sp));
|
|
free(buf);
|
|
}
|
|
else {
|
|
// non-PES data
|
|
video_still_picture sp = { (char *)Data, Length };
|
|
CHECK(ioctl(fd_video, VIDEO_STILLPICTURE, &sp));
|
|
}
|
|
}
|
|
|
|
bool cDvbSdFfDevice::Poll(cPoller &Poller, int TimeoutMs)
|
|
{
|
|
Poller.Add((playMode == pmAudioOnly || playMode == pmAudioOnlyBlack) ? fd_audio : fd_video, true);
|
|
return Poller.Poll(TimeoutMs);
|
|
}
|
|
|
|
bool cDvbSdFfDevice::Flush(int TimeoutMs)
|
|
{
|
|
//TODO actually this function should wait until all buffered data has been processed by the card, but how?
|
|
return true;
|
|
}
|
|
|
|
int cDvbSdFfDevice::PlayVideo(const uchar *Data, int Length)
|
|
{
|
|
return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
|
|
}
|
|
|
|
int cDvbSdFfDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
|
|
{
|
|
return WriteAllOrNothing(fd_audio, Data, Length, 1000, 10);
|
|
}
|
|
|
|
int cDvbSdFfDevice::PlayTsVideo(const uchar *Data, int Length)
|
|
{
|
|
return WriteAllOrNothing(fd_video, Data, Length, 1000, 10);
|
|
}
|
|
|
|
int cDvbSdFfDevice::PlayTsAudio(const uchar *Data, int Length)
|
|
{
|
|
return WriteAllOrNothing(fd_audio, Data, Length, 1000, 10);
|
|
}
|
|
|
|
// --- cDvbSdFfDeviceProbe ---------------------------------------------------
|
|
|
|
cDvbSdFfDeviceProbe::cDvbSdFfDeviceProbe(void)
|
|
{
|
|
outputOnly = false;
|
|
}
|
|
|
|
bool cDvbSdFfDeviceProbe::Probe(int Adapter, int Frontend)
|
|
{
|
|
static uint32_t SubsystemIds[] = {
|
|
0x110A0000, // Fujitsu Siemens DVB-C
|
|
0x13C20000, // Technotrend/Hauppauge WinTV DVB-S rev1.X or Fujitsu Siemens DVB-C
|
|
0x13C20001, // Technotrend/Hauppauge WinTV DVB-T rev1.X
|
|
0x13C20002, // Technotrend/Hauppauge WinTV DVB-C rev2.X
|
|
0x13C20003, // Technotrend/Hauppauge WinTV Nexus-S rev2.X
|
|
0x13C20004, // Galaxis DVB-S rev1.3
|
|
0x13C20006, // Fujitsu Siemens DVB-S rev1.6
|
|
0x13C20008, // Technotrend/Hauppauge DVB-T
|
|
0x13C2000A, // Technotrend/Hauppauge WinTV Nexus-CA rev1.X
|
|
0x13C2000E, // Technotrend/Hauppauge WinTV Nexus-S rev2.3
|
|
0x13C21002, // Technotrend/Hauppauge WinTV DVB-S rev1.3 SE
|
|
0x00000000
|
|
};
|
|
uint32_t SubsystemId = GetSubsystemId(Adapter, Frontend);
|
|
for (uint32_t *sid = SubsystemIds; *sid; sid++) {
|
|
if (*sid == SubsystemId) {
|
|
dsyslog("creating cDvbSdFfDevice");
|
|
new cDvbSdFfDevice(Adapter, Frontend, outputOnly);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|