Index: mythtv/configure =================================================================== --- mythtv/configure (revision 18528) +++ mythtv/configure (working copy) @@ -157,6 +157,7 @@ echo " --disable-hdhomerun disable support for HDHomeRun boxes" echo " --disable-v4l disable Video4Linux support" echo " --disable-ivtv disable ivtv support (PVR-x50) req. v4l support" + echo " --disable-hdpvr disable HD-PVR support" echo " --disable-dvb disable DVB support" echo " --dvb-path=HDRLOC location of directory containing" echo " 'linux/dvb/frontend.h', not the" @@ -891,6 +892,7 @@ hdhomerun iptv ivtv + hdpvr joystick_menu libfftw3 lirc @@ -1048,6 +1050,7 @@ firewire_deps="backend" iptv_deps="backend" ivtv_deps="backend v4l" +hdpvr_deps="backend v4l" hdhomerun_deps="backend" opengl_deps="GL_gl_h" opengl_deps_any="windows x11" @@ -1179,6 +1182,7 @@ hdhomerun="yes" iptv="yes" ivtv="yes" +hdpvr="yes" joystick_menu="default" lamemp3="yes" lirc="yes" @@ -1628,6 +1632,7 @@ SHFLAGS='-dynamiclib -Wl,-single_module -Wl,-install_name,$(SHLIBDIR)/$(SLIBNAME),-current_version,$(SPPVERSION),-compatibility_version,$(SPPVERSION) -Wl,-read_only_relocs,suppress' VHOOKSHFLAGS='-dynamiclib -Wl,-single_module -flat_namespace -undefined suppress -Wl,-install_name,$(SHLIBDIR)/vhook/$@' strip="strip -x" + disable hdpvr FFLDFLAGS="-Wl,-dynamic,-search_paths_first" SLIBSUF=".dylib" SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME).$(LIBVERSION)$(SLIBSUF)' @@ -1662,6 +1667,7 @@ disable ffserver SLIBPREF="lib" SLIBSUF=".dll" + disable hdpvr EXESUF=".exe" SLIBNAME_WITH_VERSION='$(SLIBPREF)$(NAME)-$(LIBVERSION)$(SLIBSUF)' SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(NAME)-$(LIBMAJOR)$(SLIBSUF)' @@ -2999,6 +3005,7 @@ if enabled backend; then echo "Video4Linux sup. ${v4l-no}" echo "ivtv support ${ivtv-no}" + echo "HR-PVR support ${hdpvr-no}" echo "FireWire support ${firewire-no}" echo "DVB support ${dvb-no} [$dvb_path]" echo "DBox2 support ${dbox2-no}" Index: mythtv/libs/libmythtv/cardutil.h =================================================================== --- mythtv/libs/libmythtv/cardutil.h (revision 18528) +++ mythtv/libs/libmythtv/cardutil.h (working copy) @@ -53,6 +53,7 @@ FIREWIRE, HDHOMERUN, FREEBOX, + HDPVR = 12, }; static enum CARD_TYPES toCardType(const QString &name) @@ -81,6 +82,8 @@ return HDHOMERUN; if ("FREEBOX" == name) return FREEBOX; + if ("HDPVR" == name) + return HDPVR; return ERROR_UNKNOWN; } @@ -95,7 +98,8 @@ static bool IsUnscanable(const QString &rawtype) { return - (rawtype == "FIREWIRE") || (rawtype == "DBOX2"); + (rawtype == "FIREWIRE") || (rawtype == "DBOX2") || + (rawtype == "HDPVR"); } static bool IsEITCapable(const QString &rawtype) @@ -119,7 +123,9 @@ static bool IsTuningAnalog(const QString &rawtype) { - return (rawtype == "V4L"); + return + (rawtype == "V4L") || (rawtype == "MPEG") || + (rawtype == "HDPVR"); } /// Convenience function for GetCardIDs(const QString&, QString, QString) @@ -137,6 +143,8 @@ static bool IsCardTypePresent(const QString &rawtype, QString hostname = QString::null); + static QStringList GetCardTypes(void); // card types on ALL hosts + static QStringVec GetVideoDevices(const QString &rawtype, QString hostname = QString::null); @@ -144,6 +152,8 @@ { return get_on_cardid("cardtype", cardid).upper(); } static QString GetVideoDevice(uint cardid) { return get_on_cardid("videodevice", cardid); } + static QString GetAudioDevice(uint cardid) + { return get_on_cardid("audiodevice", cardid); } static QString GetVBIDevice(uint cardid) { return get_on_cardid("vbidevice", cardid); } static uint GetHDHRTuner(uint cardid) @@ -188,8 +198,10 @@ static QString ProbeSubTypeName(uint cardid); - static QStringList probeInputs(QString device, - QString cardtype = QString::null); + static QStringList ProbeVideoInputs(QString device, + QString cardtype = QString::null); + static QStringList ProbeAudioInputs(QString device, + QString cardtype = QString::null); static void GetCardInputs(uint cardid, const QString &device, const QString &cardtype, @@ -234,11 +246,13 @@ uint32_t &version); static bool GetV4LInfo(int videofd, QString &card, QString &driver) { uint32_t dummy; return GetV4LInfo(videofd, card, driver, dummy); } - static InputNames probeV4LInputs(int videofd, bool &ok); + static InputNames ProbeV4LVideoInputs(int videofd, bool &ok); + static InputNames ProbeV4LAudioInputs(int videofd, bool &ok); private: - static QStringList probeV4LInputs(QString device); - static QStringList probeDVBInputs(QString device); + static QStringList ProbeV4LVideoInputs(QString device); + static QStringList ProbeV4LAudioInputs(QString device); + static QStringList ProbeDVBInputs(QString device); }; #endif //_CARDUTIL_H_ Index: mythtv/libs/libmythtv/videosource.h =================================================================== --- mythtv/libs/libmythtv/videosource.h (revision 18528) +++ mythtv/libs/libmythtv/videosource.h (working copy) @@ -299,6 +299,22 @@ } }; +class TunerCardAudioInput : public ComboBoxSetting, public CaptureCardDBStorage +{ + Q_OBJECT + public: + TunerCardAudioInput(const CaptureCard &parent, + QString dev = QString::null, + QString type = QString::null); + + public slots: + void fillSelections(const QString &device); + + private: + QString last_device; + QString last_cardtype; +}; + class DVBAudioDevice : public LineEditSetting, public CaptureCardDBStorage { Q_OBJECT @@ -385,6 +401,23 @@ TunerCardInput *input; }; +class HDPVRConfigurationGroup: public VerticalConfigurationGroup +{ + Q_OBJECT + + public: + HDPVRConfigurationGroup(CaptureCard &parent); + + public slots: + void probeCard(const QString &device); + + private: + CaptureCard &parent; + TransLabelSetting *cardinfo; + TunerCardInput *videoinput; + TunerCardAudioInput *audioinput; +}; + class DVBCardNum; class DVBInput; class DVBCardName; Index: mythtv/libs/libmythtv/libmythtv.pro =================================================================== --- mythtv/libs/libmythtv/libmythtv.pro (revision 18528) +++ mythtv/libs/libmythtv/libmythtv.pro (working copy) @@ -386,8 +386,8 @@ # Support for Video4Linux devices using_v4l { - HEADERS += channel.h analogsignalmonitor.h - SOURCES += channel.cpp analogsignalmonitor.h + HEADERS += v4lchannel.h analogsignalmonitor.h + SOURCES += v4lchannel.cpp analogsignalmonitor.h DEFINES += USING_V4L } @@ -472,6 +472,11 @@ using_ivtv:SOURCES += mpegrecorder.cpp using_ivtv:DEFINES += USING_IVTV + # Support for HD-PVR on Linux + using_hdpvr:HEADERS *= mpegrecorder.h + using_hdpvr:SOURCES *= mpegrecorder.cpp + using_hdpvr:DEFINES += USING_HDPVR + # Support for Linux DVB drivers using_dvb { # Basic DVB types Index: mythtv/libs/libmythtv/v4lchannel.h =================================================================== --- mythtv/libs/libmythtv/v4lchannel.h (revision 0) +++ mythtv/libs/libmythtv/v4lchannel.h (revision 0) @@ -0,0 +1,108 @@ +// -*- Mode: c++ -*- + +#ifndef V4LCHANNEL_H +#define V4LCHANNEL_H + +#include "dtvchannel.h" +#include "videodev_myth.h" // needed for v4l2_std_id type + +using namespace std; + +#define FAKE_VIDEO 0 + +class TVRec; + +typedef QMap VidModV4L1; +typedef QMap VidModV4L2; + +/** \class V4LChannel + * \brief Implements tuning for TV cards using the V4L driver API, + * both versions 1 and 2. + * + * This class supports a wide range of tuning hardware including + * frame grabbers (whose output requires encoding), hardware encoders, + * digital cameras, and non-encoding hardware which simply records + * pre-encoded broadcast streams. + * + */ +class V4LChannel : public DTVChannel +{ + public: + V4LChannel(TVRec *parent, const QString &videodevice); + virtual ~V4LChannel(void); + + bool Init(QString &inputname, QString &startchannel, bool setchan); + + bool Open(void); + void Close(void); + + // Sets + void SetFd(int fd); + void SetFormat(const QString &format); + int SetDefaultFreqTable(const QString &name); + bool SetChannelByString(const QString &chan); + + // Gets + bool IsOpen(void) const { return GetFd() >= 0; } + int GetFd(void) const { return videofd; } + QString GetDevice(void) const { return device; } + QString GetSIStandard(void) const { return "atsc"; } + + // Commands + bool SwitchToInput(int newcapchannel, bool setstarting); + bool Retune(void); + + // Picture attributes. + bool InitPictureAttributes(void); + int GetPictureAttribute(PictureAttribute) const; + int ChangePictureAttribute(PictureAdjustType, PictureAttribute, bool up); + + // PID caching + void SaveCachedPids(const pid_cache_t&) const; + void GetCachedPids(pid_cache_t&) const; + + // Digital scanning stuff + bool TuneMultiplex(uint mplexid, QString inputname); + bool Tune(const DTVMultiplex &tuning, QString inputname); + + // Analog scanning stuff + bool Tune(uint frequency, QString inputname, + QString modulation, QString si_std); + + private: + // Helper Sets + void SetFreqTable(const int index); + int SetFreqTable(const QString &name); + bool SetInputAndFormat(int newcapchannel, QString newFmt); + + // Helper Gets + int GetCurrentChannelNum(const QString &channame); + QString GetFormatForChannel(QString channum, + QString inputname); + + // Helper Commands + bool InitPictureAttribute(const QString db_col_name); + bool TuneTo(const QString &chan, int finetune); + bool InitializeInputs(void); + + private: + // Data + QString device; + int videofd; + QString device_name; + QString driver_name; + QMap pict_attr_default; + + struct CHANLIST *curList; + int totalChannels; + + QString currentFormat; + bool is_dtv; ///< Set if 'currentFormat' is a DTV format + bool usingv4l2; ///< Set to true if tuner accepts v4l2 commands + VidModV4L1 videomode_v4l1; ///< Current video mode if 'usingv4l2' is false + VidModV4L2 videomode_v4l2; ///< Current video mode if 'usingv4l2' is true + + int defaultFreqTable; +}; + +#endif Index: mythtv/libs/libmythtv/channel.h =================================================================== --- mythtv/libs/libmythtv/channel.h (revision 18528) +++ mythtv/libs/libmythtv/channel.h (working copy) @@ -1,108 +0,0 @@ -// -*- Mode: c++ -*- - -#ifndef CHANNEL_H -#define CHANNEL_H - -#include "dtvchannel.h" -#include "videodev_myth.h" // needed for v4l2_std_id type - -using namespace std; - -#define FAKE_VIDEO 0 - -class TVRec; - -typedef QMap VidModV4L1; -typedef QMap VidModV4L2; - -/** \class Channel - * \brief Implements tuning for TV cards using the V4L driver API, - * both versions 1 and 2. - * - * This class supports a wide range of tuning hardware including - * frame grabbers (whose output requires encoding), hardware encoders, - * digital cameras, and non-encoding hardware which simply records - * pre-encoded broadcast streams. - * - */ -class Channel : public DTVChannel -{ - public: - Channel(TVRec *parent, const QString &videodevice); - virtual ~Channel(void); - - bool Init(QString &inputname, QString &startchannel, bool setchan); - - bool Open(void); - void Close(void); - - // Sets - void SetFd(int fd); - void SetFormat(const QString &format); - int SetDefaultFreqTable(const QString &name); - bool SetChannelByString(const QString &chan); - - // Gets - bool IsOpen(void) const { return GetFd() >= 0; } - int GetFd(void) const { return videofd; } - QString GetDevice(void) const { return device; } - QString GetSIStandard(void) const { return "atsc"; } - - // Commands - bool SwitchToInput(int newcapchannel, bool setstarting); - bool Retune(void); - - // Picture attributes. - bool InitPictureAttributes(void); - int GetPictureAttribute(PictureAttribute) const; - int ChangePictureAttribute(PictureAdjustType, PictureAttribute, bool up); - - // PID caching - void SaveCachedPids(const pid_cache_t&) const; - void GetCachedPids(pid_cache_t&) const; - - // Digital scanning stuff - bool TuneMultiplex(uint mplexid, QString inputname); - bool Tune(const DTVMultiplex &tuning, QString inputname); - - // Analog scanning stuff - bool Tune(uint frequency, QString inputname, - QString modulation, QString si_std); - - private: - // Helper Sets - void SetFreqTable(const int index); - int SetFreqTable(const QString &name); - bool SetInputAndFormat(int newcapchannel, QString newFmt); - - // Helper Gets - int GetCurrentChannelNum(const QString &channame); - QString GetFormatForChannel(QString channum, - QString inputname); - - // Helper Commands - bool InitPictureAttribute(const QString db_col_name); - bool TuneTo(const QString &chan, int finetune); - bool InitializeInputs(void); - - private: - // Data - QString device; - int videofd; - QString device_name; - QString driver_name; - QMap pict_attr_default; - - struct CHANLIST *curList; - int totalChannels; - - QString currentFormat; - bool is_dtv; ///< Set if 'currentFormat' is a DTV format - bool usingv4l2; ///< Set to true if tuner accepts v4l2 commands - VidModV4L1 videomode_v4l1; ///< Current video mode if 'usingv4l2' is false - VidModV4L2 videomode_v4l2; ///< Current video mode if 'usingv4l2' is true - - int defaultFreqTable; -}; - -#endif Index: mythtv/libs/libmythtv/dbcheck.cpp =================================================================== --- mythtv/libs/libmythtv/dbcheck.cpp (revision 18528) +++ mythtv/libs/libmythtv/dbcheck.cpp (working copy) @@ -3640,8 +3640,6 @@ return true; } - - bool InitializeDatabase(void) { MSqlQuery query(MSqlQuery::InitCon()); Index: mythtv/libs/libmythtv/scanwizardscanner.cpp =================================================================== --- mythtv/libs/libmythtv/scanwizardscanner.cpp (revision 18528) +++ mythtv/libs/libmythtv/scanwizardscanner.cpp (working copy) @@ -46,7 +46,7 @@ #include "dvbconfparser.h" #ifdef USING_V4L -#include "channel.h" +#include "v4lchannel.h" #include "analogsignalmonitor.h" #endif @@ -511,8 +511,11 @@ #endif #ifdef USING_V4L - if (("V4L" == card_type) || ("MPEG" == card_type)) - channel = new Channel(NULL, device); + if (("V4L" == card_type) || ("MPEG" == card_type) || + ("HDPVR" == card_type)) + { + channel = new V4LChannel(NULL, device); + } #endif #ifdef USING_HDHOMERUN Index: mythtv/libs/libmythtv/dtvrecorder.h =================================================================== --- mythtv/libs/libmythtv/dtvrecorder.h (revision 18528) +++ mythtv/libs/libmythtv/dtvrecorder.h (working copy) @@ -55,16 +55,20 @@ void BufferedWrite(const TSPacket &tspacket); - // MPEG "audio only" support + // MPEG TS "audio only" support bool FindAudioKeyframes(const TSPacket *tspacket); - // MPEG2 support + // MPEG2 TS support bool FindMPEG2Keyframes(const TSPacket* tspacket); - // MPEG4 AVC / H.264 support + // MPEG4 AVC / H.264 TS support bool FindH264Keyframes(const TSPacket* tspacket); void HandleH264Keyframe(void); + // MPEG2 PS support (Hauppauge PVR-x50/PVR-500) + void HandlePSKeyframe(void); + bool FindPSKeyFrames(unsigned char *buffer, int len); + // For handling other (non audio/video) packets bool FindOtherKeyframes(const TSPacket *tspacket); @@ -93,6 +97,9 @@ bool _has_written_other_keyframe; + /// Used for PVR-150/250/500 which have a fixed keyframe distance of 12 or 15 + int _keyframedist; + // state tracking variables /// True iff recording is actually being performed bool _recording; Index: mythtv/libs/libmythtv/analogsignalmonitor.cpp =================================================================== --- mythtv/libs/libmythtv/analogsignalmonitor.cpp (revision 18528) +++ mythtv/libs/libmythtv/analogsignalmonitor.cpp (working copy) @@ -8,12 +8,12 @@ #include "videodev_myth.h" #include "mythcontext.h" #include "analogsignalmonitor.h" -#include "channel.h" +#include "v4lchannel.h" #define LOC QString("AnalogSM: ").arg(channel->GetDevice()) #define LOC_ERR QString("AnalogSM, Error: ").arg(channel->GetDevice()) -AnalogSignalMonitor::AnalogSignalMonitor(int db_cardnum, Channel *_channel, +AnalogSignalMonitor::AnalogSignalMonitor(int db_cardnum, V4LChannel *_channel, uint64_t _flags, const char *_name) : SignalMonitor(db_cardnum, _channel, _flags, _name), usingv4l2(false) Index: mythtv/libs/libmythtv/siscan.cpp =================================================================== --- mythtv/libs/libmythtv/siscan.cpp (revision 18528) +++ mythtv/libs/libmythtv/siscan.cpp (working copy) @@ -29,7 +29,7 @@ #include "dvbchannel.h" #include "hdhrchannel.h" -#include "channel.h" +#include "v4lchannel.h" #include "compat.h" QString SIScan::loc(const SIScan *siscan) @@ -555,10 +555,10 @@ #endif } -Channel *SIScan::GetChannel(void) +V4LChannel *SIScan::GetV4LChannel(void) { #ifdef USING_V4L - return dynamic_cast(channel); + return dynamic_cast(channel); #else return NULL; #endif @@ -1619,7 +1619,7 @@ #endif // USING_DVB #ifdef USING_V4L - if (GetChannel()) + if (GetV4LChannel()) { // convert to visual carrier tuning.frequency = tuning.frequency - 1750000; Index: mythtv/libs/libmythtv/dtvrecorder.cpp =================================================================== --- mythtv/libs/libmythtv/dtvrecorder.cpp (revision 18528) +++ mythtv/libs/libmythtv/dtvrecorder.cpp (working copy) @@ -46,6 +46,7 @@ _request_recording(false), _wait_for_keyframe_option(true), _has_written_other_keyframe(false), + _keyframedist(15), // state _recording(false), _error(false), @@ -57,6 +58,7 @@ _frames_seen_count(0), _frames_written_count(0) { SetPositionMapType(MARK_GOP_BYFRAME); + _payload_buffer.reserve(TSPacket::SIZE * (50 + 1)); } DTVRecorder::~DTVRecorder() @@ -431,7 +433,10 @@ bool DTVRecorder::FindH264Keyframes(const TSPacket *tspacket) { if (!ringBuffer) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "FindH264Keyframes: No ringbuffer"); return false; + } bool haveBufferedData = !_payload_buffer.empty(); if (!tspacket->HasPayload()) // no payload to scan @@ -528,7 +533,7 @@ hasFrame = true; } - if (_h264_kf_seq.IsOnFrame()) + else if (_h264_kf_seq.IsOnFrame()) hasFrame = true; } } // for (; i < TSPacket::SIZE; i++) @@ -572,4 +577,108 @@ CheckForRingBufferSwitch(); } +/** \fn DTVRecorder::HandlePSKeyframe(void) + * \brief This save the current frame to the position maps + * and handles ringbuffer switching. + */ +void DTVRecorder::HandlePSKeyframe(void) +{ + if (!ringBuffer) + return; + + unsigned long long frameNum = _last_gop_seen; + + _first_keyframe = (_first_keyframe < 0) ? frameNum : _first_keyframe; + + // Add key frame to position map + positionMapLock.lock(); + if (!positionMap.contains(frameNum)) + { + long long startpos = ringBuffer->GetWritePosition(); + // FIXME: handle keyframes with start code spanning over two ts packets + startpos += _payload_buffer.size(); + positionMapDelta[frameNum] = startpos; + positionMap[frameNum] = startpos; + } + positionMapLock.unlock(); + + // Perform ringbuffer switch if needed. + CheckForRingBufferSwitch(); +} + + +bool DTVRecorder::FindPSKeyFrames(unsigned char *buffer, int len) +{ + unsigned char *bufptr = buffer, *bufstart = buffer; + uint v = 0; + int leftlen = len; + bool hasKeyFrame = false; + + while (bufptr < buffer + len) + { + v = *bufptr++; + if (_start_code == 0x000001) + { + _start_code = ((_start_code << 8) | v) & 0xFFFFFF; + const int stream_id = _start_code & 0x000000ff; + + if (stream_id == PESStreamID::PackHeader) + { + _last_keyframe_seen = ringBuffer->GetWritePosition() + + _payload_buffer.size(); + + int curpos = bufptr - bufstart - 4; + if (curpos < 0) + { + // header was split + if (_payload_buffer.size() + curpos > 0) + ringBuffer->Write(&_payload_buffer[0], + _payload_buffer.size() + curpos); + + _payload_buffer.resize(4); + memcpy(&_payload_buffer[0], &_start_code, 4); + + leftlen = leftlen - curpos + 4; + bufstart = bufptr; + } + else + { + // header was entirely in this packet + int idx = _payload_buffer.size(); + _payload_buffer.resize(idx + curpos); + memcpy(&_payload_buffer[idx], bufstart, curpos); + + bufstart += curpos; + leftlen -= curpos; + + if (_payload_buffer.size() > 0) + ringBuffer->Write(&_payload_buffer[0], + _payload_buffer.size()); + _payload_buffer.clear(); + } + _frames_seen_count++; + } + else if (stream_id == PESStreamID::SequenceStartCode) + { + _last_seq_seen = _last_keyframe_seen; + } + else if (stream_id == PESStreamID::GOPStartCode && + _last_seq_seen == _last_keyframe_seen) + { + _frames_written_count = _last_gop_seen * _keyframedist; + _last_gop_seen++; + HandlePSKeyframe(); + } + } + else + _start_code = ((_start_code << 8) | v) & 0xFFFFFF; + } + + int idx = _payload_buffer.size(); + _payload_buffer.resize(idx + leftlen); + memcpy(&_payload_buffer[idx], bufstart, leftlen); + + return hasKeyFrame; +} + /* vim: set expandtab tabstop=4 shiftwidth=4: */ Index: mythtv/libs/libmythtv/mpegrecorder.h =================================================================== --- mythtv/libs/libmythtv/mpegrecorder.h (revision 18528) +++ mythtv/libs/libmythtv/mpegrecorder.h (working copy) @@ -3,12 +3,19 @@ #ifndef MPEGRECORDER_H_ #define MPEGRECORDER_H_ -#include "recorderbase.h" +#include "dtvrecorder.h" +#include "tspacket.h" +#include "mpegstreamdata.h" +#include "DeviceReadBuffer.h" struct AVFormatContext; struct AVPacket; -class MpegRecorder : public RecorderBase +class MpegRecorder : public DTVRecorder, + public MPEGSingleProgramStreamListener, + public TSPacketListener, + public TSPacketListenerAV, + public ReaderPausedCB { public: MpegRecorder(TVRec*); @@ -33,36 +40,55 @@ bool PauseAndWait(int timeout = 100); bool IsRecording(void) { return recording; } - bool IsErrored(void) { return errored; } - long long GetFramesWritten(void) { return framesWritten; } - bool Open(void); int GetVideoFd(void) { return chanfd; } - long long GetKeyframePosition(long long desired); + // TS + virtual void SetStreamData(MPEGStreamData*); + virtual MPEGStreamData *GetStreamData(void) { return _stream_data; } - void SetNextRecording(const ProgramInfo*, RingBuffer*); + // TSPacketListener + bool ProcessTSPacket(const TSPacket &tspacket); + // TSPacketListenerAV + bool ProcessVideoTSPacket(const TSPacket &tspacket); + bool ProcessAudioTSPacket(const TSPacket &tspacket); + bool ProcessAVTSPacket(const TSPacket &tspacket); + + // Implements MPEGSingleProgramStreamListener + void HandleSingleProgramPAT(ProgramAssociationTable *pat); + void HandleSingleProgramPMT(ProgramMapTable *pmt); + + // ReaderPausedCB + virtual void ReaderPaused(int fd) { paused = true; pauseWait.wakeAll(); } + private: - bool SetupRecording(void); - void FinishRecording(void); - void HandleKeyframe(void); + void SetIntOption(RecordingProfile *profile, const QString &name); + void SetStrOption(RecordingProfile *profile, const QString &name); - void ProcessData(unsigned char *buffer, int len); - bool OpenMpegFileAsInput(void); bool OpenV4L2DeviceAsInput(void); bool SetIVTVDeviceOptions(int chanfd); bool SetV4L2DeviceOptions(int chanfd); + bool SetFormat(int chanfd); + bool SetLanguageMode(int chanfd); + bool SetRecordingVolume(int chanfd); bool SetVBIOptions(int chanfd); uint GetFilteredStreamType(void) const; uint GetFilteredAudioSampleRate(void) const; uint GetFilteredAudioLayer(void) const; uint GetFilteredAudioBitRate(uint audio_layer) const; + bool StartEncoding(int fd); + bool StopEncoding(int fd); + void ResetForNewFile(void); + void HandleResolutionChanges(void); + + inline bool CheckCC(uint pid, uint cc); + bool deviceIsMpegFile; int bufferSize; @@ -78,42 +104,62 @@ // State bool recording; bool encoding; - bool errored; + bool needs_resolution; + mutable QMutex start_stop_encoding_lock; + QMutex recording_wait_lock; + QWaitCondition recording_wait; // Pausing state bool cleartimeonpause; - // Number of frames written - long long framesWritten; - // Encoding info int width, height; int bitrate, maxbitrate, streamtype, aspectratio; int audtype, audsamplerate, audbitratel1, audbitratel2, audbitratel3; int audvolume; unsigned int language; ///< 0 is Main Lang; 1 is SAP Lang; 2 is Dual + unsigned int low_mpeg4avgbitrate; + unsigned int low_mpeg4peakbitrate; + unsigned int medium_mpeg4avgbitrate; + unsigned int medium_mpeg4peakbitrate; + unsigned int high_mpeg4avgbitrate; + unsigned int high_mpeg4peakbitrate; // Input file descriptors int chanfd; int readfd; - // Keyframe tracking inforamtion - int keyframedist; - bool gopset; - unsigned int leftovers; - long long lastpackheaderpos; - long long lastseqstart; - long long numgops; - - // buffer used for ... - unsigned char *buildbuffer; - unsigned int buildbuffersize; - static const int audRateL1[]; static const int audRateL2[]; static const int audRateL3[]; static const char *streamType[]; static const char *aspectRatio[]; static const unsigned int kBuildBufferMaxSize; + + // Buffer device reads + DeviceReadBuffer *_device_read_buffer; + + // TS + MPEGStreamData *_stream_data; + unsigned char _stream_id[0x1fff]; + unsigned char _pid_status[0x1fff]; + unsigned char _continuity_counter[0x1fff]; + static const unsigned char kPayloadStartSeen = 0x2; + + // Statistics + mutable uint _continuity_error_count; + mutable uint _stream_overflow_count; + mutable uint _bad_packet_count; }; + +inline bool MpegRecorder::CheckCC(uint pid, uint new_cnt) +{ + bool ok = ((((_continuity_counter[pid] + 1) & 0xf) == new_cnt) || + (_continuity_counter[pid] == 0xFF)); + + _continuity_counter[pid] = new_cnt & 0xf; + + return ok; +} + #endif Index: mythtv/libs/libmythtv/channelbase.cpp =================================================================== --- mythtv/libs/libmythtv/channelbase.cpp (revision 18528) +++ mythtv/libs/libmythtv/channelbase.cpp (working copy) @@ -17,7 +17,7 @@ // MythTV headers #include "videodev_myth.h" -#include "channel.h" +#include "channelbase.h" #include "frequencies.h" #include "tv_rec.h" #include "mythcontext.h" Index: mythtv/libs/libmythtv/mpeg/h264utils.h =================================================================== --- mythtv/libs/libmythtv/mpeg/h264utils.h (revision 18528) +++ mythtv/libs/libmythtv/mpeg/h264utils.h (working copy) @@ -35,6 +35,11 @@ #include +extern "C" { +#include // golomb.h should include this... +#include "golomb.h" +} + namespace H264 { @@ -174,6 +179,8 @@ private: void KeyframePredicate(const uint8_t new_first_NAL_byte); /* throw() */ + void decode_Header(GetBitContext *gb); + void decode_SPS(GetBitContext *gb); bool errored; bool state_changed; @@ -182,14 +189,27 @@ int64_t sync_stream_offset; uint8_t first_NAL_byte; + int log2_max_frame_num; + int frame_num; + int prev_frame_num; bool saw_AU_delimiter; bool saw_first_VCL_NAL_unit; bool saw_sps; + bool new_VLC_NAL; + bool separate_colour_plane_flag; + bool frame_mbs_only_flag; + bool prev_field_pic_flag; + bool prev_bottom_field_flag; + uint prev_pic_parameter_set_id; + + bool did_evaluate_once; bool keyframe; int64_t keyframe_sync_stream_offset; + + GetBitContext gb; }; } // namespace H264 Index: mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp =================================================================== --- mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp (revision 18528) +++ mythtv/libs/libmythtv/mpeg/mpegdescriptors.cpp (working copy) @@ -3,9 +3,15 @@ #include +#include + #include "atscdescriptors.h" #include "dvbdescriptors.h" +QMutex RegistrationDescriptor::description_map_lock; +bool RegistrationDescriptor::description_map_initialized = false; +QMap RegistrationDescriptor::description_map; + desc_list_t MPEGDescriptor::Parse( const unsigned char* data, uint len) { @@ -448,19 +454,86 @@ return str; } +void RegistrationDescriptor::InitializeDescriptionMap(void) +{ + QMutexLocker locker(&description_map_lock); + if (description_map_initialized) + return; + + description_map["AC-3"] = "ATSC audio stream A/52"; + description_map["AVSV"] = "China A/V Working Group"; + description_map["BDC0"] = "Broadcast Data Corporation Software Data Service"; + description_map["BSSD"] = "SMPTE 302M-1998 Audio data as specified in (AES3)"; + description_map["CAPO"] = "SMPTE 315M-1999 Camera Positioning Information"; + description_map["CUEI"] = "SCTE 35 2003, Cable Digital Program Insertion Cueing Message"; + description_map["DDED"] = "LGEUS Digital Delivery with encryption and decryption"; + description_map["DISH"] = "EchoStar MPEG streams"; + description_map["DRA1"] = "Chinese EIS SJ/T11368-2006 DRA digital audio"; + description_map["DTS1"] = "DTS Frame size of 512 bytes"; + description_map["DTS2"] = "DTS Frame size of 1024 bytes"; + description_map["DTS3"] = "DTS Frame size of 2048"; + description_map["DVDF"] = "DVD compatible MPEG2-TS"; + description_map["ETV1"] = "CableLabs ETV info is present"; + description_map["GA94"] = "ATSC program ID A/53"; + description_map["GWKS"] = "GuideWorks EPG info"; + description_map["HDMV"] = "Blu-Ray A/V for read-only media (H.264 TS)"; + description_map["HDMX"] = "Matsushita-TS"; + description_map["KLVA"] = "SMPTE RP 217-2001 MXF KLV packets present"; + description_map["LU-A"] = "SMPTE RDD-11 HDSDI HD serial/video data"; + description_map["MTRM"] = "D-VHS compatible MPEG2-TS"; + description_map["NMR1"] = "Nielsen content identifier"; + description_map["PAUX"] = "Philips ES containing low-speed data"; + description_map["PMSF"] = "MPEG-TS stream modified by STB"; + description_map["PRMC"] = "Philips ES containing remote control data"; + description_map["SCTE"] = "SCTE 54 2003 Digital Video Service Multiplex and TS for Cable"; + description_map["SEN1"] = "ATSC Private Information identifies source of stream"; + description_map["SESF"] = "Blu-Ray A/V for ReWritable media (H.264 TS)"; + description_map["SPLC"] = "SMPTE Proposed 312M Splice Point compatible TS"; + description_map["SVMD"] = "SMPTE Proposed Video Metatdata Dictionary for MPEG2-TS"; + description_map["SZMI"] = "ATSC Private Info from Building B"; + description_map["TRIV"] = "ATSC Private Info from Triveni Digital"; + description_map["TSBV"] = "Toshiba self-encoded H.264 TS"; + description_map["TSHV"] = "Sony self-encoded MPEG-TS and private data"; + description_map["TSMV"] = "Sony self-encoded MPEG-TS and private data"; + description_map["TVG1"] = "TV Guide EPG Data"; + description_map["TVG2"] = "TV Guide EPG Data"; + description_map["TVG3"] = "TV Guide EPG Data"; + description_map["ULE1"] = "IETF RFC4326 compatible MPEG2-TS"; + description_map["VC-1"] = "SMPTE Draft RP 227 VC-1 Bitstream Transport Encodings"; + + for (uint i = 0; i <= 99; i++) + { + description_map[QString("US%1").arg(i, 2, 0)] = + "NIMA, Unspecified military application"; + } + + description_map_initialized = true; +} + +QString RegistrationDescriptor::GetDescription(const QString &fmt) +{ + InitializeDescriptionMap(); + + QString ret = QString::null; + { + QMutexLocker locker(&description_map_lock); + QMap::const_iterator it = description_map.find(fmt); + if (it != description_map.end()) + ret = QDeepCopy(*it); + } + + return ret; +} + QString RegistrationDescriptor::toString() const { QString fmt = FormatIdentifierString(); QString msg = QString("Registration Descriptor: '%1' ").arg(fmt); - QString msg2 = "Unknown"; - if (fmt == "CUEI") - msg2 = "SCTE 35 2003, Cable Digital Program Insertion Cueing Message"; - else if (fmt == "AC-3") - msg2 = "ATSC audio stream A/52"; - else if (fmt == "GA94") - msg2 = "ATSC program ID A/53"; - else - msg2 = "Unknown, see http://www.smpte-ra.org/mpegreg.html"; + + QString msg2 = GetDescription(fmt); + if (msg2.isEmpty()) + msg2 = "Unknown, see http://www.smpte-ra.org/mpegreg/mpegreg.html"; + return msg + msg2; } Index: mythtv/libs/libmythtv/mpeg/h264utils.cpp =================================================================== --- mythtv/libs/libmythtv/mpeg/h264utils.cpp (revision 18528) +++ mythtv/libs/libmythtv/mpeg/h264utils.cpp (working copy) @@ -33,15 +33,14 @@ // C headers #include -// C++ headers -#include - // MythTV headers +#include "mythverbose.h" #include "h264utils.h" extern "C" { // from libavcodec extern const uint8_t *ff_find_start_code(const uint8_t * p, const uint8_t *end, uint32_t * state); +#include "avcodec.h" } namespace H264 @@ -62,9 +61,16 @@ first_NAL_byte = H264::NALUnitType::UNKNOWN; + log2_max_frame_num = -1; + frame_num = 0; + prev_frame_num = -1; + saw_AU_delimiter = false; saw_first_VCL_NAL_unit = false; saw_sps = false; + separate_colour_plane_flag = false; + frame_mbs_only_flag = true; + new_VLC_NAL = false; did_evaluate_once = false; keyframe = false; @@ -127,7 +133,7 @@ // stage 3: did we see the AU's first VCL NAL unit yet? if (!saw_first_VCL_NAL_unit && NALUnitType::IsVCLType(new_NAL_type)) { - saw_first_VCL_NAL_unit = true; + saw_first_VCL_NAL_unit = new_VLC_NAL; saw_AU_delimiter = false; state_changed = true; if (saw_sps) @@ -154,9 +160,35 @@ if ((sync_accumulator & 0xffffff00) == 0x00000100) { uint8_t k = *(local_bytes-1); + uint8_t NAL_type = k & 0x1f; sync_stream_offset = stream_offset; keyframe = false; + if (NAL_type == NALUnitType::SPS || + NAL_type == NALUnitType::SLICE || + NAL_type == NALUnitType::SLICE_DPA) + { + /* + bitstream buffer, must be FF_INPUT_BUFFER_PADDING_SIZE + bytes larger then the actual read bits + */ + if (local_bytes + 20 + FF_INPUT_BUFFER_PADDING_SIZE < + local_bytes_end) + { + init_get_bits(&gb, local_bytes, + 8 * (local_bytes_end - local_bytes)); + + if (NAL_type == NALUnitType::SPS) + decode_SPS(&gb); + else + decode_Header(&gb); + } + } + else if (NAL_type == NALUnitType::SLICE_IDR) + { + frame_num = 0; + } + KeyframePredicate(k); first_NAL_byte = k; @@ -175,4 +207,141 @@ return saw_first_VCL_NAL_unit; } +void KeyframeSequencer::decode_Header(GetBitContext *gb) +{ + uint first_mb_in_slice; + uint slice_type; + uint pic_parameter_set_id; + bool field_pic_flag; + bool bottom_field_flag; + + if (log2_max_frame_num < 1) + { + VERBOSE(VB_IMPORTANT, "KeyframeSequencer::decode_Header: " + "SPS has not been parsed!"); + return; + } + + new_VLC_NAL = false; + + prev_frame_num = frame_num; + + first_mb_in_slice = get_ue_golomb(gb); + slice_type = get_ue_golomb(gb); + + pic_parameter_set_id = get_ue_golomb(gb); + if (pic_parameter_set_id != prev_pic_parameter_set_id) + { + new_VLC_NAL = true; + prev_pic_parameter_set_id = pic_parameter_set_id; + } + + if (separate_colour_plane_flag) + get_bits(gb, 2); // colour_plane_id + + frame_num = get_bits(gb, log2_max_frame_num); + + if (frame_mbs_only_flag) + { + new_VLC_NAL = true; + } + else + { + /* From section 7.3.3 Slice header syntax + if (!frame_mbs_only_flag) + { + field_pic_flag = get_bits1(gb); + if (field_pic_flag) + bottom_field_flag = get_bits1(gb); + } + */ + + field_pic_flag = get_bits1(gb); + if (field_pic_flag != prev_field_pic_flag) + { + new_VLC_NAL = true; + prev_field_pic_flag = field_pic_flag; + } + + if (field_pic_flag) + { + bottom_field_flag = get_bits1(gb); + if (bottom_field_flag != prev_bottom_field_flag) + { + new_VLC_NAL = !bottom_field_flag; + prev_bottom_field_flag = bottom_field_flag; + } + } + } +} + +/* + * libavcodec used for example + */ +void KeyframeSequencer::decode_SPS(GetBitContext * gb) +{ + int profile_idc, chroma_format_idc; + + profile_idc = get_bits(gb, 8); // profile_idc + get_bits1(gb); // constraint_set0_flag + get_bits1(gb); // constraint_set1_flag + get_bits1(gb); // constraint_set2_flag + get_bits1(gb); // constraint_set3_flag + get_bits(gb, 4); // reserved + get_bits(gb, 8); // level_idc + get_ue_golomb(gb); // sps_id + + if (profile_idc >= 100) + { // high profile + if ((chroma_format_idc = get_ue_golomb(gb)) == 3) // chroma_format_idc + separate_colour_plane_flag = (get_bits1(gb) == 1); + + get_ue_golomb(gb); // bit_depth_luma_minus8 + get_ue_golomb(gb); // bit_depth_chroma_minus8 + get_bits1(gb); // qpprime_y_zero_transform_bypass_flag + + if (get_bits1(gb)) // seq_scaling_matrix_present_flag + { + for (int idx = 0; idx < ((chroma_format_idc != 3) ? 8 : 12); ++idx) + { + get_bits1(gb); // scaling_list + } + } + } + + log2_max_frame_num = get_ue_golomb(gb) + 4; + + uint pic_order_cnt_type; + uint log2_max_pic_order_cnt_lsb; + bool delta_pic_order_always_zero_flag; + int offset_for_non_ref_pic; + int offset_for_top_to_bottom_field; + uint tmp; + uint num_ref_frames; + bool gaps_in_frame_num_allowed_flag; + uint pic_width_in_mbs; + uint pic_height_in_map_units; + + pic_order_cnt_type = get_ue_golomb(gb); + if (pic_order_cnt_type == 0) + { + log2_max_pic_order_cnt_lsb = get_ue_golomb(gb) + 4; + } + else if (pic_order_cnt_type == 1) + { + delta_pic_order_always_zero_flag = get_bits1(gb); + offset_for_non_ref_pic = get_se_golomb(gb); + offset_for_top_to_bottom_field = get_se_golomb(gb); + tmp = get_ue_golomb(gb); + for (uint idx = 0; idx < tmp; ++idx) + get_se_golomb(gb); // offset_for_ref_frame[i] + } + + num_ref_frames = get_ue_golomb(gb); + gaps_in_frame_num_allowed_flag = get_bits1(gb); + pic_width_in_mbs = get_ue_golomb(gb) + 1; + pic_height_in_map_units = get_ue_golomb(gb) + 1; + frame_mbs_only_flag = get_bits1(gb); +} + } // namespace H264 Index: mythtv/libs/libmythtv/mpeg/mpegdescriptors.h =================================================================== --- mythtv/libs/libmythtv/mpeg/mpegdescriptors.h (revision 18528) +++ mythtv/libs/libmythtv/mpeg/mpegdescriptors.h (working copy) @@ -3,7 +3,18 @@ #ifndef _MPEG_DESCRIPTORS_H_ #define _MPEG_DESCRIPTORS_H_ +// ANSI C headers #include + +// C++ headers +#include +using namespace std; + +// Qt headers +#include +#include + +// MythTV #include "iso639.h" typedef vector desc_list_t; @@ -188,6 +199,15 @@ QChar(_data[4]) + QChar(_data[5]); } QString toString() const; + + private: + static void InitializeDescriptionMap(void); + static QString GetDescription(const QString &fmt); + + private: + static QMutex description_map_lock; + static bool description_map_initialized; + static QMap description_map; }; class ConditionalAccessDescriptor : public MPEGDescriptor Index: mythtv/libs/libmythtv/signalmonitor.cpp =================================================================== --- mythtv/libs/libmythtv/signalmonitor.cpp (revision 18528) +++ mythtv/libs/libmythtv/signalmonitor.cpp (working copy) @@ -23,7 +23,7 @@ #ifdef USING_V4L # include "analogsignalmonitor.h" -# include "channel.h" +# include "v4lchannel.h" #endif #ifdef USING_HDHOMERUN @@ -93,9 +93,10 @@ #ifdef USING_V4L if ((cardtype.upper() == "V4L") || - (cardtype.upper() == "MPEG")) + (cardtype.upper() == "MPEG") || + (cardtype.upper() == "HDPVR")) { - Channel *chan = dynamic_cast(channel); + V4LChannel *chan = dynamic_cast(channel); if (chan) signalMonitor = new AnalogSignalMonitor(db_cardnum, chan); } Index: mythtv/libs/libmythtv/v4lchannel.cpp =================================================================== --- mythtv/libs/libmythtv/v4lchannel.cpp (revision 0) +++ mythtv/libs/libmythtv/v4lchannel.cpp (revision 0) @@ -0,0 +1,1320 @@ +// Std C headers +#include +#include +#include + +// POSIX headers +#include +#include +#include +#include +#include +#include + +// C++ headers +#include +#include +using namespace std; + +// Qt headers +#include + +// MythTV headers +#include "videodev_myth.h" +#include "v4lchannel.h" +#include "frequencies.h" +#include "tv_rec.h" +#include "mythcontext.h" +#include "mythdbcon.h" +#include "channelutil.h" +#include "cardutil.h" + +#define DEBUG_ATTRIB 1 + +#define LOC QString("Channel(%1): ").arg(device) +#define LOC_WARN QString("Channel(%1) Warning: ").arg(device) +#define LOC_ERR QString("Channel(%1) Error: ").arg(device) + +static int format_to_mode(const QString& fmt, int v4l_version); +static QString mode_to_format(int mode, int v4l_version); + +/** \class Channel + * \brief Class implementing ChannelBase interface to tuning hardware + * when using Video4Linux based drivers. + */ + +V4LChannel::V4LChannel(TVRec *parent, const QString &videodevice) + : DTVChannel(parent), + device(videodevice), videofd(-1), + device_name(QString::null), driver_name(QString::null), + curList(NULL), totalChannels(0), + currentFormat(""), is_dtv(false), + usingv4l2(false), defaultFreqTable(1) +{ +} + +V4LChannel::~V4LChannel(void) +{ + Close(); +} + +bool V4LChannel::Init(QString &inputname, QString &startchannel, bool setchan) +{ + if (setchan) + { + SetFormat(gContext->GetSetting("TVFormat")); + SetDefaultFreqTable(gContext->GetSetting("FreqTable")); + } + return ChannelBase::Init(inputname, startchannel, setchan); +} + +bool V4LChannel::Open(void) +{ +#if FAKE_VIDEO + return true; +#endif + if (videofd >= 0) + return true; + + videofd = open(device.ascii(), O_RDWR); + if (videofd < 0) + { + VERBOSE(VB_IMPORTANT, + QString("Channel(%1)::Open(): Can't open video device, " + "error \"%2\"").arg(device).arg(strerror(errno))); + return false; + } + + usingv4l2 = CardUtil::hasV4L2(videofd); + CardUtil::GetV4LInfo(videofd, device_name, driver_name); + VERBOSE(VB_CHANNEL, LOC + QString("Device name '%1' driver '%2'.") + .arg(device_name).arg(driver_name)); + + if (!InitializeInputs()) + { + Close(); + return false; + } + + SetFormat("Default"); + + return true; +} + +void V4LChannel::Close(void) +{ + if (videofd >= 0) + close(videofd); + videofd = -1; +} + +void V4LChannel::SetFd(int fd) +{ + if (fd != videofd) + Close(); + videofd = (fd >= 0) ? fd : -1; +} + +static int format_to_mode(const QString &fmt, int v4l_version) +{ + if (2 == v4l_version) + { + if (fmt == "PAL-BG") + return V4L2_STD_PAL_BG; + else if (fmt == "PAL-D") + return V4L2_STD_PAL_D; + else if (fmt == "PAL-DK") + return V4L2_STD_PAL_DK; + else if (fmt == "PAL-I") + return V4L2_STD_PAL_I; + else if (fmt == "PAL-60") + return V4L2_STD_PAL_60; + else if (fmt == "SECAM") + return V4L2_STD_SECAM; + else if (fmt == "SECAM-D") + return V4L2_STD_SECAM_D; + else if (fmt == "PAL-NC") + return V4L2_STD_PAL_Nc; + else if (fmt == "PAL-M") + return V4L2_STD_PAL_M; + else if (fmt == "PAL-N") + return V4L2_STD_PAL_N; + else if (fmt == "NTSC-JP") + return V4L2_STD_NTSC_M_JP; + // generics... + else if (fmt.left(4) == "NTSC") + return V4L2_STD_NTSC; + else if (fmt.left(4) == "ATSC") + return V4L2_STD_NTSC; // We've dropped V4L ATSC support... + else if (fmt.left(3) == "PAL") + return V4L2_STD_PAL; + return V4L2_STD_NTSC; + } + else if (1 == v4l_version) + { + if (fmt == "NTSC-JP") + return 6; + else if (fmt.left(5) == "SECAM") + return VIDEO_MODE_SECAM; + else if (fmt == "PAL-NC") + return 3; + else if (fmt == "PAL-M") + return 4; + else if (fmt == "PAL-N") + return 5; + // generics... + else if (fmt.left(3) == "PAL") + return VIDEO_MODE_PAL; + else if (fmt.left(4) == "NTSC") + return VIDEO_MODE_NTSC; + else if (fmt.left(4) == "ATSC") + return VIDEO_MODE_NTSC; // We've dropped V4L ATSC support... + return VIDEO_MODE_NTSC; + } + + VERBOSE(VB_IMPORTANT, + "format_to_mode() does not recognize V4L" << v4l_version); + + return V4L2_STD_NTSC; // assume V4L version 2 +} + +static QString mode_to_format(int mode, int v4l_version) +{ + if (2 == v4l_version) + { + if (mode == V4L2_STD_NTSC) + return "NTSC"; + else if (mode == V4L2_STD_NTSC_M_JP) + return "NTSC-JP"; + else if (mode == V4L2_STD_PAL) + return "PAL"; + else if (mode == V4L2_STD_PAL_60) + return "PAL-60"; + else if (mode == V4L2_STD_PAL_BG) + return "PAL-BG"; + else if (mode == V4L2_STD_PAL_D) + return "PAL-D"; + else if (mode == V4L2_STD_PAL_DK) + return "PAL-DK"; + else if (mode == V4L2_STD_PAL_I) + return "PAL-I"; + else if (mode == V4L2_STD_PAL_M) + return "PAL-M"; + else if (mode == V4L2_STD_PAL_N) + return "PAL-N"; + else if (mode == V4L2_STD_PAL_Nc) + return "PAL-NC"; + else if (mode == V4L2_STD_SECAM) + return "SECAM"; + else if (mode == V4L2_STD_SECAM_D) + return "SECAM-D"; + // generic.. + else if ((V4L2_STD_NTSC_M == mode) || + (V4L2_STD_NTSC_443 == mode) || + (V4L2_STD_NTSC_M_KR == mode)) + return "NTSC"; + else if ((V4L2_STD_PAL_B == mode) || + (V4L2_STD_PAL_B1 == mode) || + (V4L2_STD_PAL_G == mode) || + (V4L2_STD_PAL_H == mode) || + (V4L2_STD_PAL_D1 == mode) || + (V4L2_STD_PAL_K == mode)) + return "PAL"; + else if ((V4L2_STD_SECAM_B == mode) || + (V4L2_STD_SECAM_DK == mode) || + (V4L2_STD_SECAM_G == mode) || + (V4L2_STD_SECAM_H == mode) || + (V4L2_STD_SECAM_K == mode) || + (V4L2_STD_SECAM_K1 == mode) || + (V4L2_STD_SECAM_L == mode) || + (V4L2_STD_SECAM_LC == mode)) + return "SECAM"; + else if ((V4L2_STD_ATSC == mode) || + (V4L2_STD_ATSC_8_VSB == mode) || + (V4L2_STD_ATSC_16_VSB == mode)) + { + // We've dropped V4L ATSC support, but this still needs to be + // returned here so we will change the mode if the device is + // in ATSC mode already. + return "ATSC"; + } + } + else if (1 == v4l_version) + { + if (mode == VIDEO_MODE_NTSC) + return "NTSC"; + else if (mode == VIDEO_MODE_ATSC) + return "ATSC"; + else if (mode == VIDEO_MODE_PAL) + return "PAL"; + else if (mode == VIDEO_MODE_SECAM) + return "SECAM"; + else if (mode == 3) + return "PAL-NC"; + else if (mode == 4) + return "PAL-M"; + else if (mode == 5) + return "PAL-N"; + else if (mode == 6) + return "NTSC-JP"; + } + else + { + VERBOSE(VB_IMPORTANT, + "mode_to_format() does not recognize V4L" << v4l_version); + } + + return "Unknown"; +} + +/** \fn V4LChannel::InitializeInputs(void) + * This enumerates the inputs, converts the format + * string to something the hardware understands, and + * if the parent pointer is valid retrieves the + * channels from the database. + */ +bool V4LChannel::InitializeInputs(void) +{ + // Get Inputs from DB + if (!ChannelBase::InitializeInputs()) + return false; + + // Get global TVFormat setting + QString fmt = gContext->GetSetting("TVFormat"); + VERBOSE(VB_CHANNEL, QString("Global TVFormat Setting '%1'").arg(fmt)); + int videomode_v4l1 = format_to_mode(fmt.upper(), 1); + int videomode_v4l2 = format_to_mode(fmt.upper(), 2); + + bool ok = false; + InputNames v4l_inputs = CardUtil::ProbeV4LVideoInputs(videofd, ok); + + // Insert info from hardware + uint valid_cnt = 0; + InputMap::const_iterator it; + for (it = inputs.begin(); it != inputs.end(); ++it) + { + InputNames::const_iterator v4l_it = v4l_inputs.begin(); + for (; v4l_it != v4l_inputs.end(); ++v4l_it) + { + if (*v4l_it == (*it)->name) + { + (*it)->inputNumV4L = v4l_it.key(); + (*it)->videoModeV4L1 = videomode_v4l1; + (*it)->videoModeV4L2 = videomode_v4l2; + valid_cnt++; + } + } + } + + // print em + for (it = inputs.begin(); it != inputs.end(); ++it) + { + VERBOSE(VB_CHANNEL, LOC + QString("Input #%1: '%2' schan(%3) " + "tun(%4) v4l1(%5) v4l2(%6)") + .arg(it.key()).arg((*it)->name).arg((*it)->startChanNum) + .arg((*it)->tuneToChannel) + .arg(mode_to_format((*it)->videoModeV4L1,1)) + .arg(mode_to_format((*it)->videoModeV4L2,2))); + } + + return valid_cnt; +} + +/** \fn V4LChannel::SetFormat(const QString &format) + * \brief Initializes tuner and modulator variables + * + * \param format One of twelve formats: + * "NTSC", "NTSC-JP", "ATSC", + * "SECAM", + * "PAL", "PAL-BG", "PAL-DK", "PAL-I", + * "PAL-60", "PAL-NC", "PAL-M", or "PAL-N" + */ +void V4LChannel::SetFormat(const QString &format) +{ + if (!Open()) + return; + + int inputNum = currentInputID; + if (currentInputID < 0) + inputNum = GetNextInputNum(); + + QString fmt = format; + if ((fmt == "Default") || format.isEmpty()) + { + InputMap::const_iterator it = inputs.find(inputNum); + if (it != inputs.end()) + fmt = mode_to_format((*it)->videoModeV4L2, 2); + } + + VERBOSE(VB_CHANNEL, LOC + QString("SetFormat(%1) fmt(%2) input(%3)") + .arg(format).arg(fmt).arg(inputNum)); + + if ((fmt == currentFormat) || SetInputAndFormat(inputNum, fmt)) + { + currentFormat = fmt; + is_dtv = (fmt == "ATSC"); + } +} + +int V4LChannel::SetDefaultFreqTable(const QString &name) +{ + defaultFreqTable = SetFreqTable(name); + return defaultFreqTable; +} + +void V4LChannel::SetFreqTable(const int index) +{ + curList = chanlists[index].list; + totalChannels = chanlists[index].count; +} + +int V4LChannel::SetFreqTable(const QString &name) +{ + int i = 0; + char *listname = (char *)chanlists[i].name; + + curList = NULL; + while (listname != NULL) + { + if (name == listname) + { + SetFreqTable(i); + return i; + } + i++; + listname = (char *)chanlists[i].name; + } + + VERBOSE(VB_CHANNEL, QString("Channel(%1)::SetFreqTable(): Invalid " + "frequency table name %2, using %3."). + arg(device).arg(name).arg((char *)chanlists[1].name)); + SetFreqTable(1); + return 1; +} + +int V4LChannel::GetCurrentChannelNum(const QString &channame) +{ + for (int i = 0; i < totalChannels; i++) + { + if (channame == curList[i].name) + return i; + } + + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("GetCurrentChannelNum(%1): " + "Failed to find Channel").arg(channame)); + + return -1; +} + +void V4LChannel::SaveCachedPids(const pid_cache_t &pid_cache) const +{ + int chanid = GetChanID(); + if (chanid > 0) + DTVChannel::SaveCachedPids(chanid, pid_cache); +} + +void V4LChannel::GetCachedPids(pid_cache_t &pid_cache) const +{ + int chanid = GetChanID(); + if (chanid > 0) + DTVChannel::GetCachedPids(chanid, pid_cache); +} + +bool V4LChannel::SetChannelByString(const QString &channum) +{ + QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); + QString loc_err = loc + ", Error: "; + VERBOSE(VB_CHANNEL, loc); + + if (!Open()) + { + VERBOSE(VB_IMPORTANT, loc_err + "Channel object " + "will not open, can not change channels."); + + return false; + } + + QString inputName; + if (!CheckChannel(channum, inputName)) + { + VERBOSE(VB_IMPORTANT, loc_err + + "CheckChannel failed.\n\t\t\tPlease verify the channel " + "in the 'mythtv-setup' Channel Editor."); + + return false; + } + + // If CheckChannel filled in the inputName then we need to + // change inputs and return, since the act of changing + // inputs will change the channel as well. + if (!inputName.isEmpty()) + return ChannelBase::SwitchToInput(inputName, channum); + + ClearDTVInfo(); + + InputMap::const_iterator it = inputs.find(currentInputID); + if (it == inputs.end()) + return false; + + uint mplexid_restriction; + if (!IsInputAvailable(currentInputID, mplexid_restriction)) + return false; + + // Fetch tuning data from the database. + QString tvformat, modulation, freqtable, freqid, dtv_si_std; + int finetune; + uint64_t frequency; + int mpeg_prog_num; + uint atsc_major, atsc_minor, mplexid, tsid, netid; + + if (!ChannelUtil::GetChannelData( + (*it)->sourceid, channum, + tvformat, modulation, freqtable, freqid, + finetune, frequency, + dtv_si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, + mplexid, commfree)) + { + return false; + } + + if (mplexid_restriction && (mplexid != mplexid_restriction)) + return false; + + // If the frequency is zeroed out, don't use it directly. + bool ok = (frequency > 0); + + if (!ok) + { + frequency = (freqid.toInt(&ok) + finetune) * 1000; + mplexid = 0; + } + bool isFrequency = ok && (frequency > 10000000); + + // If we are tuning to a freqid, rather than an actual frequency, + // we need to set the frequency table to use. + if (!isFrequency) + { + if (freqtable == "default" || freqtable.isEmpty()) + SetFreqTable(defaultFreqTable); + else + SetFreqTable(freqtable); + } + + // Set NTSC, PAL, ATSC, etc. + SetFormat(tvformat); + + // If a tuneToChannel is set make sure we're still on it + if (!(*it)->tuneToChannel.isEmpty() && (*it)->tuneToChannel != "Undefined") + TuneTo((*it)->tuneToChannel, 0); + + // Tune to proper frequency + if ((*it)->externalChanger.isEmpty()) + { + if ((*it)->name.contains("composite", false) || + (*it)->name.contains("s-video", false)) + { + VERBOSE(VB_GENERAL, LOC_WARN + "You have not set " + "an external channel changing" + "\n\t\t\tscript for a composite or s-video " + "input. Channel changing will do nothing."); + } + else if (isFrequency) + { + if (!Tune(frequency, "", (is_dtv) ? "8vsb" : "analog", dtv_si_std)) + { + return false; + } + } + else + { + if (!TuneTo(freqid, finetune)) + return false; + } + } + else if (!ChangeExternalChannel(freqid)) + return false; + + // Set the current channum to the new channel's channum + curchannelname = QDeepCopy(channum); + + // Setup filters & recording picture attributes for framegrabing recorders + // now that the new curchannelname has been established. + if (pParent) + pParent->SetVideoFiltersForChannel(GetCurrentSourceID(), channum); + InitPictureAttributes(); + + // Set the major and minor channel for any additional multiplex tuning + SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); + + // Set this as the future start channel for this source + inputs[currentInputID]->startChanNum = QDeepCopy(curchannelname); + + return true; +} + +bool V4LChannel::TuneTo(const QString &channum, int finetune) +{ + int i = GetCurrentChannelNum(channum); + VERBOSE(VB_CHANNEL, QString("Channel(%1)::TuneTo(%2): " + "curList[%3].freq(%4)") + .arg(device).arg(channum).arg(i) + .arg((i != -1) ? curList[i].freq : -1)); + + if (i == -1) + { + VERBOSE(VB_IMPORTANT, QString("Channel(%1)::TuneTo(%2): Error, " + "failed to find channel.") + .arg(device).arg(channum)); + return false; + } + + int frequency = (curList[i].freq + finetune) * 1000; + + return Tune(frequency, "", "analog", "analog"); +} + +bool V4LChannel::Tune(const DTVMultiplex &tuning, QString inputname) +{ + return Tune(tuning.frequency - 1750000, // to visual carrier + inputname, tuning.modulation.toString(), tuning.sistandard); +} + +/** \fn V4LChannel::Tune(uint,QString,QString,QString) + * \brief Tunes to a specific frequency (Hz) on a particular input, using + * the specified modulation. + * + * Note: This function always uses modulator zero. + * + * \param frequency Frequency in Hz, this is divided by 62.5 kHz or 62.5 Hz + * depending on the modulator and sent to the hardware. + * \param inputname Name of the input (Television, Antenna 1, etc.) + * \param modulation "radio", "analog", or "digital" + */ +bool V4LChannel::Tune(uint frequency, QString inputname, + QString modulation, QString si_std) +{ + VERBOSE(VB_CHANNEL, LOC + QString("Tune(%1, %2, %3, %4)") + .arg(frequency).arg(inputname).arg(modulation).arg(si_std)); + + int ioctlval = 0; + + if (modulation == "8vsb") + SetFormat("ATSC"); + modulation = (is_dtv) ? "digital" : modulation; + + int inputnum = GetInputByName(inputname); + + bool ok = true; + if ((inputnum >= 0) && (GetCurrentInputNum() != inputnum)) + ok = SwitchToInput(inputnum, false); + else if (GetCurrentInputNum() < 0) + ok = SwitchToInput(0, false); + + if (!ok) + return false; + + // If the frequency is a center frequency and not + // a visual carrier frequency, convert it. + int offset = frequency % 1000000; + offset = (offset > 500000) ? 1000000 - offset : offset; + bool is_visual_carrier = (offset > 150000) && (offset < 350000); + if (!is_visual_carrier && currentFormat == "ATSC") + { + VERBOSE(VB_CHANNEL, QString("Channel(%1): ").arg(device) + + QString("Converting frequency from center frequency " + "(%1 Hz) to visual carrier frequency (%2 Hz).") + .arg(frequency).arg(frequency - 1750000)); + frequency -= 1750000; // convert to visual carrier + } + + // Video4Linux version 2 tuning + if (usingv4l2) + { + bool isTunerCapLow = false; + struct v4l2_modulator mod; + bzero(&mod, sizeof(mod)); + mod.index = 0; + ioctlval = ioctl(videofd, VIDIOC_G_MODULATOR, &mod); + if (ioctlval >= 0) + { + isTunerCapLow = (mod.capability & V4L2_TUNER_CAP_LOW); + VERBOSE(VB_CHANNEL, " name: "<=0) + return true; + VERBOSE(VB_CHANNEL, "digital modulation failed"); + } + + vf.type = V4L2_TUNER_ANALOG_TV; + + ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, + QString("Channel(%1)::Tune(): Error %2 " + "while setting frequency (v2): %3") + .arg(device).arg(ioctlval).arg(strerror(errno))); + return false; + } + ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf); + + if (ioctlval >= 0) + { + VERBOSE(VB_CHANNEL, QString( + "Channel(%1)::Tune(): Frequency is now %2") + .arg(device).arg(vf.frequency * 62500)); + } + + return true; + } + + // Video4Linux version 1 tuning + uint freq = frequency / 62500; + ioctlval = ioctl(videofd, VIDIOCSFREQ, &freq); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, + QString("Channel(%1)::Tune(): Error %2 " + "while setting frequency (v1): %3") + .arg(device).arg(ioctlval).arg(strerror(errno))); + return false; + } + + SetSIStandard(si_std); + + return true; +} + +/** \fn V4LChannel::Retune(void) + * \brief Retunes to last tuned frequency. + * + * NOTE: This only works for V4L2 and only for analog tuning. + */ +bool V4LChannel::Retune(void) +{ + if (usingv4l2) + { + struct v4l2_frequency vf; + bzero(&vf, sizeof(vf)); + + vf.tuner = 0; // use first tuner + vf.type = V4L2_TUNER_ANALOG_TV; + + // Get the last tuned frequency + int ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Retune failed (1)" + ENO); + return false; + } + + // Set the last tuned frequency again... + ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Retune failed (2)" + ENO); + return false; + } + + return true; + } + + return false; +} + +// documented in dtvchannel.h +bool V4LChannel::TuneMultiplex(uint mplexid, QString inputname) +{ + VERBOSE(VB_CHANNEL, LOC + QString("TuneMultiplex(%1)").arg(mplexid)); + + QString modulation; + QString si_std; + uint64_t frequency; + uint transportid; + uint dvb_networkid; + + if (!ChannelUtil::GetTuningParams( + mplexid, modulation, frequency, + transportid, dvb_networkid, si_std)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "TuneMultiplex(): " + + QString("Could not find tuning parameters for multiplex %1.") + .arg(mplexid)); + + return false; + } + + if (!Tune(frequency, inputname, modulation, si_std)) + return false; + + return true; +} + +QString V4LChannel::GetFormatForChannel(QString channum, QString inputname) +{ + MSqlQuery query(MSqlQuery::InitCon()); + query.prepare( + "SELECT tvformat " + "FROM channel, cardinput " + "WHERE channum = :CHANNUM AND " + " inputname = :INPUTNAME AND " + " cardinput.cardid = :CARDID AND " + " cardinput.sourceid = channel.sourceid"); + query.bindValue(":CHANNUM", channum); + query.bindValue(":INPUTNAME", inputname); + query.bindValue(":CARDID", GetCardID()); + + QString fmt = QString::null; + if (!query.exec() || !query.isActive()) + MythContext::DBError("SwitchToInput:find format", query); + else if (query.next()) + fmt = query.value(0).toString(); + return fmt; +} + +bool V4LChannel::SetInputAndFormat(int inputNum, QString newFmt) +{ + InputMap::const_iterator it = inputs.find(inputNum); + if (it == inputs.end() || (*it)->inputNumV4L < 0) + return false; + + int inputNumV4L = (*it)->inputNumV4L; + bool usingv4l1 = !usingv4l2; + bool ok = true; + + QString msg = + QString("SetInputAndFormat(%1, %2) ").arg(inputNum).arg(newFmt); + + if (usingv4l2) + { + VERBOSE(VB_CHANNEL, LOC + msg + "(v4l v2)"); + + int ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); + + // ConvertX (wis-go7007) requires streaming to be disabled + // before an input switch, do this if initial switch failed. + bool streamingDisabled = false; + int streamType = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ((ioctlval < 0) && (errno == EBUSY)) + { + ioctlval = ioctl(videofd, VIDIOC_STREAMOFF, &streamType); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile disabling streaming (v4l v2)" + ENO); + + ok = false; + ioctlval = 0; + } + else + { + streamingDisabled = true; + + // Resend the input switch ioctl. + ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); + } + } + + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile setting input (v4l v2)" + ENO); + + ok = false; + } + + v4l2_std_id vid_mode = format_to_mode(newFmt, 2); + ioctlval = ioctl(videofd, VIDIOC_S_STD, &vid_mode); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile setting format (v4l v2)" + ENO); + + ok = false; + } + + // ConvertX (wis-go7007) requires streaming to be disabled + // before an input switch, here we try to re-enable streaming. + if (streamingDisabled) + { + ioctlval = ioctl(videofd, VIDIOC_STREAMON, &streamType); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile reenabling streaming (v4l v2)" + ENO); + + ok = false; + } + } + } + + if (usingv4l1) + { + VERBOSE(VB_CHANNEL, LOC + msg + "(v4l v1)"); + + // read in old settings + struct video_channel set; + bzero(&set, sizeof(set)); + ioctl(videofd, VIDIOCGCHAN, &set); + + // set new settings + set.channel = inputNumV4L; + set.norm = format_to_mode(newFmt, 1); + int ioctlval = ioctl(videofd, VIDIOCSCHAN, &set); + + ok = (ioctlval >= 0); + if (!ok) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile setting format (v4l v1)" + ENO); + } + else if (usingv4l2) + { + VERBOSE(VB_IMPORTANT, LOC + msg + + "\n\t\t\tSetting video mode with v4l version 1 worked"); + } + } + return ok; +} + +bool V4LChannel::SwitchToInput(int inputnum, bool setstarting) +{ + InputMap::const_iterator it = inputs.find(inputnum); + if (it == inputs.end()) + return false; + + QString tuneFreqId = (*it)->tuneToChannel; + QString channum = (*it)->startChanNum; + QString inputname = (*it)->name; + + VERBOSE(VB_CHANNEL, QString("Channel(%1)::SwitchToInput(in %2, '%3')") + .arg(device).arg(inputnum) + .arg(setstarting ? channum : QString(""))); + + uint mplexid_restriction; + if (!IsInputAvailable(inputnum, mplexid_restriction)) + return false; + + QString newFmt = mode_to_format((*it)->videoModeV4L2, 2); + + // If we are setting a channel, get its video mode... + bool chanValid = (channum != "Undefined") && !channum.isEmpty(); + if (setstarting && chanValid) + { + QString tmp = GetFormatForChannel(channum, inputname); + if (tmp != "Default" && !tmp.isEmpty()) + newFmt = tmp; + } + + bool ok = SetInputAndFormat(inputnum, newFmt); + + // Try to set ATSC mode if NTSC fails + if (!ok && newFmt == "NTSC") + ok = SetInputAndFormat(inputnum, "ATSC"); + + if (!ok) + { + VERBOSE(VB_IMPORTANT, LOC + "SetInputAndFormat() failed"); + return false; + } + + currentFormat = newFmt; + is_dtv = newFmt == "ATSC"; + currentInputID = inputnum; + curchannelname = ""; // this will be set by SetChannelByString + + if (!tuneFreqId.isEmpty() && tuneFreqId != "Undefined") + ok = TuneTo(tuneFreqId, 0); + + if (!ok) + return false; + + if (setstarting && chanValid) + ok = SetChannelByString(channum); + else if (setstarting && !chanValid) + { + VERBOSE(VB_IMPORTANT, LOC + + QString("SwitchToInput(in %2, set ch): ").arg(inputnum) + + QString("\n\t\t\tDefault channel '%1' is not valid.") + .arg(channum)); + ok = false; + } + + return ok; +} + +static unsigned short *get_v4l1_field( + int v4l2_attrib, struct video_picture &vid_pic) +{ + switch (v4l2_attrib) + { + case V4L2_CID_CONTRAST: + return &vid_pic.contrast; + case V4L2_CID_BRIGHTNESS: + return &vid_pic.brightness; + case V4L2_CID_SATURATION: + return &vid_pic.colour; + case V4L2_CID_HUE: + return &vid_pic.hue; + default: + VERBOSE(VB_IMPORTANT, "get_v4l1_field: " + "invalid attribute argument "< cdb %2 rdb %3 d %4 -> %5") + .arg(db_col_name).arg(cfield).arg(sfield) + .arg(dfield).arg(val)); +#endif + + return val; +} + +static int get_v4l2_attribute_value(int videofd, int v4l2_attrib) +{ + struct v4l2_control ctrl; + struct v4l2_queryctrl qctrl; + bzero(&ctrl, sizeof(ctrl)); + bzero(&qctrl, sizeof(qctrl)); + + ctrl.id = qctrl.id = v4l2_attrib; + if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0) + { + VERBOSE(VB_IMPORTANT, "get_v4l2_attribute_value: " + "failed to query controls (1)" + ENO); + return -1; + } + + if (ioctl(videofd, VIDIOC_G_CTRL, &ctrl) < 0) + { + VERBOSE(VB_IMPORTANT, "get_v4l2_attribute_value: " + "failed to get controls (2)" + ENO); + return -1; + } + + float mult = 65535.0 / (qctrl.maximum - qctrl.minimum); + return min(max((int)(mult * (ctrl.value - qctrl.minimum)), 0), 65525); +} + +static int get_v4l1_attribute_value(int videofd, int v4l2_attrib) +{ + struct video_picture vid_pic; + bzero(&vid_pic, sizeof(vid_pic)); + + if (ioctl(videofd, VIDIOCGPICT, &vid_pic) < 0) + { + VERBOSE(VB_IMPORTANT, "get_v4l1_attribute_value: " + "failed to get picture control (1)" + ENO); + return -1; + } + + unsigned short *setfield = get_v4l1_field(v4l2_attrib, vid_pic); + if (setfield) + return *setfield; + + return -1; +} + +static int get_attribute_value(bool usingv4l2, int videofd, int v4l2_attrib) +{ + if (usingv4l2) + return get_v4l2_attribute_value(videofd, v4l2_attrib); + return get_v4l1_attribute_value(videofd, v4l2_attrib); +} + +static int set_v4l2_attribute_value(int videofd, int v4l2_attrib, int newvalue) +{ + struct v4l2_control ctrl; + struct v4l2_queryctrl qctrl; + bzero(&ctrl, sizeof(ctrl)); + bzero(&qctrl, sizeof(qctrl)); + + ctrl.id = qctrl.id = v4l2_attrib; + if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0) + { + VERBOSE(VB_IMPORTANT, "set_v4l2_attribute_value: " + "failed to query control" + ENO); + return -1; + } + + float mult = (qctrl.maximum - qctrl.minimum) / 65535.0; + ctrl.value = (int)(mult * newvalue + qctrl.minimum); + ctrl.value = min(ctrl.value, qctrl.maximum); + ctrl.value = max(ctrl.value, qctrl.minimum); + + if (ioctl(videofd, VIDIOC_S_CTRL, &ctrl) < 0) + { + VERBOSE(VB_IMPORTANT, "set_v4l2_attribute_value: " + "failed to set control" + ENO); + return -1; + } + + return 0; +} + +static int set_v4l1_attribute_value(int videofd, int v4l2_attrib, int newvalue) +{ + unsigned short *setfield; + struct video_picture vid_pic; + bzero(&vid_pic, sizeof(vid_pic)); + + if (ioctl(videofd, VIDIOCGPICT, &vid_pic) < 0) + { + VERBOSE(VB_IMPORTANT, "set_v4l1_attribute_value: " + "failed to get picture control." + ENO); + return -1; + } + setfield = get_v4l1_field(v4l2_attrib, vid_pic); + if (newvalue != -1 && setfield) + { + *setfield = newvalue; + if (ioctl(videofd, VIDIOCSPICT, &vid_pic) < 0) + { + VERBOSE(VB_IMPORTANT, "set_v4l1_attribute_value: " + "failed to set picture control." + ENO); + return -1; + } + } + else + { + // ??? + return -1; + } + + return 0; +} + +static int set_attribute_value(bool usingv4l2, int videofd, + int v4l2_attrib, int newvalue) +{ + if (usingv4l2) + return set_v4l2_attribute_value(videofd, v4l2_attrib, newvalue); + return set_v4l1_attribute_value(videofd, v4l2_attrib, newvalue); +} + +int V4LChannel::ChangePictureAttribute( + PictureAdjustType type, PictureAttribute attr, bool up) +{ + if (!pParent || is_dtv) + return -1; + + QString db_col_name = toDBString(attr); + if (db_col_name.isEmpty()) + return -1; + + int v4l2_attrib = get_v4l2_attribute(db_col_name); + if (v4l2_attrib == -1) + return -1; + + // get the old attribute value from the hardware, this is + // just a sanity check on whether this attribute exists + if (get_attribute_value(usingv4l2, videofd, v4l2_attrib) < 0) + return -1; + + int old_value = GetPictureAttribute(attr); + int new_value = old_value + ((up) ? 655 : -655); + + // make sure we are within bounds (wrap around for hue) + if (V4L2_CID_HUE == v4l2_attrib) + new_value &= 0xffff; + new_value = min(max(new_value, 0), 65535); + +#if DEBUG_ATTRIB + VERBOSE(VB_CHANNEL, QString( + "ChangePictureAttribute(%1,%2,%3) cur %4 -> new %5") + .arg(type).arg(db_col_name).arg(up) + .arg(old_value).arg(new_value)); +#endif + + // actually set the new attribute value on the hardware + if (set_attribute_value(usingv4l2, videofd, v4l2_attrib, new_value) < 0) + return -1; + + // tell the DB about the new attribute value + if (kAdjustingPicture_Channel == type) + { + int adj_value = ChannelUtil::GetChannelValueInt( + db_col_name, GetCurrentSourceID(), curchannelname); + + int tmp = new_value - old_value + adj_value; + tmp = (tmp < 0) ? tmp + 0x10000 : tmp; + tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp; + ChannelUtil::SetChannelValue(db_col_name, QString::number(tmp), + GetCurrentSourceID(), curchannelname); + } + else if (kAdjustingPicture_Recording == type) + { + int adj_value = CardUtil::GetValueInt( + db_col_name, GetCardID()); + + int tmp = new_value - old_value + adj_value; + tmp = (tmp < 0) ? tmp + 0x10000 : tmp; + tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp; + CardUtil::SetValue(db_col_name, GetCardID(), + GetCurrentSourceID(), tmp); + } + + return new_value; +} Index: mythtv/libs/libmythtv/remoteencoder.h =================================================================== --- mythtv/libs/libmythtv/remoteencoder.h (revision 18528) +++ mythtv/libs/libmythtv/remoteencoder.h (working copy) @@ -23,7 +23,7 @@ int GetRecorderNumber(void); ProgramInfo *GetRecording(void); - bool IsRecording(void); + bool IsRecording(bool *ok = NULL); float GetFrameRate(void); long long GetFramesWritten(void); /// \brief Return value last returned by GetFramesWritten(). Index: mythtv/libs/libmythtv/cardutil.cpp =================================================================== --- mythtv/libs/libmythtv/cardutil.cpp (revision 18528) +++ mythtv/libs/libmythtv/cardutil.cpp (working copy) @@ -114,6 +114,28 @@ return count > 0; } +QStringList CardUtil::GetCardTypes(void) +{ + QStringList cardtypes; + + MSqlQuery query(MSqlQuery::InitCon()); + query.prepare("SELECT DISTINCT cardtype " + "FROM capturecard " + "ORDER BY cardtype"); + + if (!query.exec()) + { + MythContext::DBError("CardUtil::GetCardTypes()", query); + } + else + { + while (query.next()) + cardtypes.push_back(query.value(0).toString()); + } + + return cardtypes; +} + /** \fn CardUtil::GetVideoDevices(const QString&, QString) * \brief Returns the videodevices of the matching cards, duplicates removed * \param rawtype Card type as used in DB or empty string for all cardids @@ -1365,7 +1387,7 @@ return !card.isEmpty(); } -InputNames CardUtil::probeV4LInputs(int videofd, bool &ok) +InputNames CardUtil::ProbeV4LVideoInputs(int videofd, bool &ok) { (void) videofd; @@ -1430,6 +1452,39 @@ return list; } +InputNames CardUtil::ProbeV4LAudioInputs(int videofd, bool &ok) +{ + (void) videofd; + + InputNames list; + ok = false; + +#ifdef USING_V4L + bool usingv4l2 = hasV4L2(videofd); + + // V4L v2 query + struct v4l2_audio ain; + bzero(&ain, sizeof(ain)); + while (usingv4l2 && (ioctl(videofd, VIDIOC_ENUMAUDIO, &ain) >= 0)) + { + QString input((char *)ain.name); + list[ain.index] = input; + ain.index++; + } + if (ain.index) + { + ok = true; + return list; + } + + ok = true; +#else // if !USING_V4L + list[-1] += QObject::tr( + "ERROR, Compile with V4L support to query audio inputs"); +#endif // !USING_V4L + return list; +} + InputNames CardUtil::GetConfiguredDVBInputs(uint cardid) { InputNames list; @@ -1450,7 +1505,7 @@ return list; } -QStringList CardUtil::probeInputs(QString device, QString cardtype) +QStringList CardUtil::ProbeVideoInputs(QString device, QString cardtype) { QStringList ret; @@ -1462,15 +1517,26 @@ ret += "MPEG2TS"; } else if ("DVB" == cardtype) - ret += probeDVBInputs(device); + ret += ProbeDVBInputs(device); else - ret += probeV4LInputs(device); + ret += ProbeV4LVideoInputs(device); return ret; } -QStringList CardUtil::probeV4LInputs(QString device) +QStringList CardUtil::ProbeAudioInputs(QString device, QString cardtype) { + VERBOSE(VB_IMPORTANT, QString("ProbeAudioInputs(%1,%2)").arg(device).arg(cardtype)); + QStringList ret; + + if ("HDPVR" == cardtype) + ret += ProbeV4LAudioInputs(device); + + return ret; +} + +QStringList CardUtil::ProbeV4LVideoInputs(QString device) +{ bool ok; QStringList ret; int videofd = open(device.ascii(), O_RDWR); @@ -1480,7 +1546,7 @@ "to probe its inputs.").arg(device); return ret; } - InputNames list = CardUtil::probeV4LInputs(videofd, ok); + InputNames list = CardUtil::ProbeV4LVideoInputs(videofd, ok); close(videofd); if (!ok) @@ -1499,10 +1565,43 @@ return ret; } -QStringList CardUtil::probeDVBInputs(QString device) +QStringList CardUtil::ProbeV4LAudioInputs(QString device) { + VERBOSE(VB_IMPORTANT, QString("ProbeV4LAudioInputs(%1)").arg(device)); + + bool ok; QStringList ret; + int videofd = open(device.ascii(), O_RDWR); + if (videofd < 0) + { + VERBOSE(VB_IMPORTANT, QString("ProbeAudioInputs() -> couldn't open device")); + ret += QObject::tr("Could not open '%1' " + "to probe its inputs.").arg(device); + return ret; + } + InputNames list = CardUtil::ProbeV4LAudioInputs(videofd, ok); + close(videofd); + if (!ok) + { + ret += list[-1]; + return ret; + } + + InputNames::iterator it; + for (it = list.begin(); it != list.end(); ++it) + { + if (it.key() >= 0) + ret += *it; + } + + return ret; +} + +QStringList CardUtil::ProbeDVBInputs(QString device) +{ + QStringList ret; + #ifdef USING_DVB uint cardid = CardUtil::GetFirstCardID(device); if (!cardid) @@ -1587,7 +1686,7 @@ inputs += "MPEG2TS"; } else if ("DVB" != cardtype) - inputs += probeV4LInputs(device); + inputs += ProbeV4LVideoInputs(device); QString dev_label = GetDeviceLabel(cardid, cardtype, device); Index: mythtv/libs/libmythtv/siscan.h =================================================================== --- mythtv/libs/libmythtv/siscan.h (revision 18528) +++ mythtv/libs/libmythtv/siscan.h (working copy) @@ -20,7 +20,7 @@ class ChannelBase; class DTVChannel; -class Channel; +class V4LChannel; class DVBChannel; class HDHRChannel; @@ -116,7 +116,7 @@ private: // some useful gets DTVChannel *GetDTVChannel(void); - Channel *GetChannel(void); + V4LChannel *GetV4LChannel(void); DVBChannel *GetDVBChannel(void); /// \brief Called by SpawnScanner to run scanning thread Index: mythtv/libs/libmythtv/videosource.cpp =================================================================== --- mythtv/libs/libmythtv/videosource.cpp (revision 18528) +++ mythtv/libs/libmythtv/videosource.cpp (working copy) @@ -1446,6 +1446,47 @@ input->fillSelections(device); } +HDPVRConfigurationGroup::HDPVRConfigurationGroup(CaptureCard &a_parent) : + VerticalConfigurationGroup(false, true, false, false), + parent(a_parent), cardinfo(new TransLabelSetting()), + videoinput(new TunerCardInput(parent)), + audioinput(new TunerCardAudioInput(parent, QString::null, "HDPVR")) +{ + VideoDevice *device = + new VideoDevice(parent, 0, 15, QString::null, "hdpvr"); + + cardinfo->setLabel(tr("Probed info")); + + addChild(device); + addChild(cardinfo); + addChild(videoinput); + addChild(audioinput); + + connect(device, SIGNAL(valueChanged(const QString&)), + this, SLOT( probeCard( const QString&))); + + probeCard(device->getValue()); +} + +void HDPVRConfigurationGroup::probeCard(const QString &device) +{ + QString cn = tr("Failed to open"), ci = cn, dn = QString::null; + + int videofd = open(device.ascii(), O_RDWR); + if (videofd >= 0) + { + if (!CardUtil::GetV4LInfo(videofd, cn, dn)) + ci = cn = tr("Failed to probe"); + else if (!dn.isEmpty()) + ci = cn + " [" + dn + "]"; + close(videofd); + } + + cardinfo->setValue(ci); + videoinput->fillSelections(device); + audioinput->fillSelections(device); +} + CaptureCardGroup::CaptureCardGroup(CaptureCard &parent) : TriggeredConfigurationGroup(true, true, false, false) { @@ -1462,6 +1503,9 @@ # ifdef USING_IVTV addTarget("MPEG", new MPEGConfigurationGroup(parent)); # endif // USING_IVTV +# ifdef USING_HDPVR + addTarget("HDPVR", new HDPVRConfigurationGroup(parent)); +# endif // USING_HDPVR #endif // USING_V4L #ifdef USING_DVB @@ -1639,6 +1683,10 @@ setting->addSelection( QObject::tr("MPEG-2 encoder card (PVR-x50, PVR-500)"), "MPEG"); # endif // USING_IVTV +# ifdef USING_HDPVR + setting->addSelection( + QObject::tr("H.264 encoder card (HD-PVR)"), "HDPVR"); +# endif // USING_HDPVR #endif // USING_V4L #ifdef USING_DVB @@ -2850,12 +2898,44 @@ last_device = device; QStringList inputs = - CardUtil::probeInputs(device, last_cardtype); + CardUtil::ProbeVideoInputs(device, last_cardtype); for (QStringList::iterator i = inputs.begin(); i != inputs.end(); ++i) addSelection(*i); } +TunerCardAudioInput::TunerCardAudioInput(const CaptureCard &parent, + QString dev, QString type) : + ComboBoxSetting(this), CaptureCardDBStorage(this, parent, "audiodevice"), + last_device(dev), last_cardtype(type) +{ + setLabel(QObject::tr("Audio input")); + int cardid = parent.getCardID(); + if (cardid <= 0) + return; + + last_cardtype = CardUtil::GetRawCardType(cardid); + last_device = CardUtil::GetAudioDevice(cardid); +} + +void TunerCardAudioInput::fillSelections(const QString &device) +{ + clearSelections(); + + if (device.isEmpty()) + return; + + last_device = device; + QStringList inputs = + CardUtil::ProbeAudioInputs(device, last_cardtype); + + for (uint i = 0; i < inputs.size(); i++) + { + addSelection(inputs[i], QString::number(i), + last_device == QString::number(i)); + } +} + DVBConfigurationGroup::DVBConfigurationGroup(CaptureCard& a_parent) : VerticalConfigurationGroup(false, true, false, false), parent(a_parent), Index: mythtv/libs/libmythtv/remoteencoder.cpp =================================================================== --- mythtv/libs/libmythtv/remoteencoder.cpp (revision 18528) +++ mythtv/libs/libmythtv/remoteencoder.cpp (working copy) @@ -89,14 +89,28 @@ return sock; } -bool RemoteEncoder::IsRecording(void) +bool RemoteEncoder::IsRecording(bool *ok) { QStringList strlist = QString("QUERY_RECORDER %1").arg(recordernum); strlist << "IS_RECORDING"; SendReceiveStringList(strlist); + if (strlist.isEmpty()) + { + VERBOSE(VB_IMPORTANT, + "RemoteEncoder::IsRecording(), Error: No Reply."); + if (ok) + *ok = false; + + return false; + } + bool retval = strlist[0].toInt(); + + if (ok) + *ok = true; + return retval; } Index: mythtv/libs/libmythtv/profilegroup.cpp =================================================================== --- mythtv/libs/libmythtv/profilegroup.cpp (revision 18528) +++ mythtv/libs/libmythtv/profilegroup.cpp (working copy) @@ -4,6 +4,7 @@ #include "libmyth/mythcontext.h" #include "libmyth/mythdbcon.h" #include +#include "cardutil.h" #include #include #include @@ -66,52 +67,47 @@ load(); } -void ProfileGroup::fillSelections(SelectSetting* setting) { - QStringList cardtypes; - QString transcodeID; +void ProfileGroup::fillSelections(SelectSetting* setting) +{ + QStringList cardtypes = CardUtil::GetCardTypes(); + QString tid = QString::null; MSqlQuery result(MSqlQuery::InitCon()); + result.prepare( + "SELECT name, id, hostname, is_default, cardtype " + "FROM profilegroups"); - result.prepare("SELECT DISTINCT cardtype FROM capturecard;"); - - if (result.exec() && result.isActive() && result.size() > 0) + if (!result.exec()) { - while (result.next()) - { - cardtypes.append(result.value(0).toString()); - } + MythContext::DBError("ProfileGroup::fillSelections", result); + return; } - result.prepare("SELECT name,id,hostname,is_default,cardtype " - "FROM profilegroups;"); + while (result.next()) + { + QString name = result.value(0).toString(); + QString id = result.value(1).toString(); + QString hostname = result.value(2).toString(); + bool is_default = (bool) result.value(3).toInt(); + QString cardtype = result.value(4).toString(); - if (result.exec() && result.isActive() && result.size() > 0) - while (result.next()) + // Only show default profiles that match installed cards + bool have_cardtype = cardtypes.contains(cardtype); + if (is_default && (cardtype == "TRANSCODE") && !have_cardtype) { - // Only show default profiles that match installed cards - if (result.value(3).toInt()) - { - bool match = false; - for(QStringList::Iterator it = cardtypes.begin(); - it != cardtypes.end(); it++) - if (result.value(4).toString() == *it) - match = true; + tid = id; + } + else if (have_cardtype) + { + if (!hostname.isEmpty()) + name += QString(" (%1)").arg(result.value(2).toString()); - if (! match) - { - if (result.value(4).toString() == "TRANSCODE") - transcodeID = result.value(1).toString(); - continue; - } - } - QString value = QString::fromUtf8(result.value(0).toString()); - if (result.value(2).toString() != NULL && - result.value(2).toString() != "") - value += QString(" (%1)").arg(result.value(2).toString()); - setting->addSelection(value, result.value(1).toString()); + setting->addSelection(name, id); } - if (! transcodeID.isNull()) - setting->addSelection(QObject::tr("Transcoders"), transcodeID); + } + + if (!tid.isEmpty()) + setting->addSelection(QObject::tr("Transcoders"), tid); } QString ProfileGroup::getName(int group) Index: mythtv/libs/libmythtv/scanwizardscanner.h =================================================================== --- mythtv/libs/libmythtv/scanwizardscanner.h (revision 18528) +++ mythtv/libs/libmythtv/scanwizardscanner.h (working copy) @@ -50,7 +50,7 @@ class ScanProgressPopup; class ChannelBase; -class Channel; +class V4LChannel; class DVBChannel; class SignalMonitorValue; Index: mythtv/libs/libmythtv/analogsignalmonitor.h =================================================================== --- mythtv/libs/libmythtv/analogsignalmonitor.h (revision 18528) +++ mythtv/libs/libmythtv/analogsignalmonitor.h (working copy) @@ -7,13 +7,13 @@ // MythTV headers #include "signalmonitor.h" -class Channel; +class V4LChannel; class AnalogSignalMonitor : public SignalMonitor { public: AnalogSignalMonitor( - int db_cardnum, Channel *_channel, + int db_cardnum, V4LChannel *_channel, uint64_t _flags = kDTVSigMon_WaitForSig, const char *_name = "AnalogSignalMonitor"); Index: mythtv/libs/libmythtv/mpegrecorder.cpp =================================================================== --- mythtv/libs/libmythtv/mpegrecorder.cpp (revision 18528) +++ mythtv/libs/libmythtv/mpegrecorder.cpp (working copy) @@ -3,6 +3,11 @@ // C headers #include +// C++ headers +#include +#include +using namespace std; + // POSIX headers #include #include @@ -15,9 +20,6 @@ #include #include -#include -using namespace std; - // avlib headers extern "C" { #include "../libavcodec/avcodec.h" @@ -73,13 +75,10 @@ "Square", "4:3", "16:9", "2.21:1", 0 }; -const unsigned int MpegRecorder::kBuildBufferMaxSize = 1024 * 1024; - MpegRecorder::MpegRecorder(TVRec *rec) : - RecorderBase(rec), + DTVRecorder(rec), // Debugging variables deviceIsMpegFile(false), - bufferSize(4096), // Driver info card(QString::null), driver(QString::null), version(0), usingv4l2(false), @@ -87,11 +86,10 @@ requires_special_pause(false), // State recording(false), encoding(false), - errored(false), + needs_resolution(false), start_stop_encoding_lock(true), + recording_wait_lock(), recording_wait(), // Pausing state cleartimeonpause(false), - // Number of frames written - framesWritten(0), // Encoding info width(720), height(480), bitrate(4500), maxbitrate(6000), @@ -100,15 +98,14 @@ audbitratel1(14), audbitratel2(14), audbitratel3(10), audvolume(80), language(0), + low_mpeg4avgbitrate(4500), low_mpeg4peakbitrate(6000), + medium_mpeg4avgbitrate(9000), medium_mpeg4peakbitrate(13500), + high_mpeg4avgbitrate(13500), high_mpeg4peakbitrate(20200), // Input file descriptors chanfd(-1), readfd(-1), - // Keyframe tracking inforamtion - keyframedist(15), gopset(false), - leftovers(0), lastpackheaderpos(0), - lastseqstart(0), numgops(0), - // buffer used for ... - buildbuffer(new unsigned char[kBuildBufferMaxSize + 1]), - buildbuffersize(0) + _device_read_buffer(NULL), + // TS packet handling + _stream_data(NULL) { SetPositionMapType(MARK_GOP_START); } @@ -116,11 +113,12 @@ MpegRecorder::~MpegRecorder() { TeardownAll(); - delete [] buildbuffer; } void MpegRecorder::TeardownAll(void) { + StopRecording(); + if (chanfd >= 0) { close(chanfd); @@ -191,6 +189,28 @@ } else if (opt == "mpeg2audvolume") audvolume = value; + else if (opt.right(16) == "_mpeg4avgbitrate") + { + if (opt.left(3) == "low") + low_mpeg4avgbitrate = value; + else if (opt.left(6) == "medium") + medium_mpeg4avgbitrate = value; + else if (opt.left(4) == "high") + high_mpeg4avgbitrate = value; + else + RecorderBase::SetOption(opt, value); + } + else if (opt.right(17) == "_mpeg4peakbitrate") + { + if (opt.left(3) == "low") + low_mpeg4peakbitrate = value; + else if (opt.left(6) == "medium") + medium_mpeg4peakbitrate = value; + else if (opt.left(4) == "high") + high_mpeg4peakbitrate = value; + else + RecorderBase::SetOption(opt, value); + } else RecorderBase::SetOption(opt, value); } @@ -287,6 +307,8 @@ SetOption("videodevice", videodev); } + SetOption("audiodevice", audiodev); + SetOption("tvformat", gContext->GetSetting("TVFormat")); SetOption("vbiformat", gContext->GetSetting("VbiFormat")); @@ -305,8 +327,31 @@ SetIntOption(profile, "width"); SetIntOption(profile, "height"); + + SetIntOption(profile, "low_mpeg4avgbitrate"); + SetIntOption(profile, "low_mpeg4peakbitrate"); + SetIntOption(profile, "medium_mpeg4avgbitrate"); + SetIntOption(profile, "medium_mpeg4peakbitrate"); + SetIntOption(profile, "high_mpeg4avgbitrate"); + SetIntOption(profile, "high_mpeg4peakbitrate"); } +// same as the base class, it just doesn't complain if an option is missing +void MpegRecorder::SetIntOption(RecordingProfile *profile, const QString &name) +{ + const Setting *setting = profile->byName(name); + if (setting) + SetOption(name, setting->getValue().toInt()); +} + +// same as the base class, it just doesn't complain if an option is missing +void MpegRecorder::SetStrOption(RecordingProfile *profile, const QString &name) +{ + const Setting *setting = profile->byName(name); + if (setting) + SetOption(name, setting->getValue()); +} + bool MpegRecorder::OpenMpegFileAsInput(void) { chanfd = readfd = open(videodevice.ascii(), O_RDONLY); @@ -324,6 +369,9 @@ bool MpegRecorder::OpenV4L2DeviceAsInput(void) { chanfd = open(videodevice.ascii(), O_RDWR); + // open implicitly starts encoding, so we need the lock.. + QMutexLocker locker(&start_stop_encoding_lock); + if (chanfd < 0) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Can't open video device. " + ENO); @@ -334,15 +382,26 @@ { if (driver == "ivtv") { + bufferSize = 4096; usingv4l2 = (version >= IVTV_KERNEL_VERSION(0, 8, 0)); has_v4l2_vbi = (version >= IVTV_KERNEL_VERSION(0, 3, 8)); has_buggy_vbi = true; requires_special_pause = (version >= IVTV_KERNEL_VERSION(0, 10, 0)); } + else if (driver == "hdpvr") + { + bufferSize = 1500 * TSPacket::SIZE; + usingv4l2 = true; + requires_special_pause = true; + + bzero(_stream_id, sizeof(_stream_id)); + bzero(_pid_status, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); + } else { - VERBOSE(VB_IMPORTANT, "\n\nNot ivtv driver??\n\n"); + VERBOSE(VB_IMPORTANT, "\n\nNot ivtv or hdpvr driver??\n\n"); usingv4l2 = has_v4l2_vbi = true; has_buggy_vbi = requires_special_pause = false; } @@ -352,6 +411,72 @@ "has_buggy_vbi(%3)") .arg(usingv4l2).arg(has_v4l2_vbi).arg(has_buggy_vbi)); + + if ((driver != "hdpvr") && !SetFormat(chanfd)) + return false; + + if (driver != "hdpvr") + { + SetLanguageMode(chanfd); // we don't care if this fails... + SetRecordingVolume(chanfd); // we don't care if this fails... + } + + bool ok = true; + if (usingv4l2) + ok = SetV4L2DeviceOptions(chanfd); + else + { + ok = SetIVTVDeviceOptions(chanfd); + if (!ok) + usingv4l2 = ok = SetV4L2DeviceOptions(chanfd); + } + + if (!ok) + return false; + + SetVBIOptions(chanfd); + + readfd = open(videodevice.ascii(), O_RDWR | O_NONBLOCK); + + if (readfd < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Can't open video device." + ENO); + return false; + } + + if (_device_read_buffer) + { + if (_device_read_buffer->IsRunning()) + _device_read_buffer->Stop(); + + delete _device_read_buffer; + _device_read_buffer = NULL; + } + + _device_read_buffer = new DeviceReadBuffer(this); + + if (!_device_read_buffer) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); + _error = true; + return false; + } + + if (!_device_read_buffer->Setup(videodevice.ascii(), readfd)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); + _error = true; + return false; + } + + VERBOSE(VB_RECORD, LOC + "DRB ready"); + + return true; +} + + +bool MpegRecorder::SetFormat(int chanfd) +{ struct v4l2_format vfmt; bzero(&vfmt, sizeof(vfmt)); @@ -372,14 +497,18 @@ return false; } - // Set audio language mode - bool do_audmode_set = true; + return true; +} + +/// Set audio language mode +bool MpegRecorder::SetLanguageMode(int chanfd) +{ struct v4l2_tuner vt; bzero(&vt, sizeof(struct v4l2_tuner)); if (ioctl(chanfd, VIDIOC_G_TUNER, &vt) < 0) { VERBOSE(VB_IMPORTANT, LOC_WARN + "Unable to get audio mode" + ENO); - do_audmode_set = false; + return false; } switch (language) @@ -401,18 +530,26 @@ } int audio_layer = GetFilteredAudioLayer(); - if (do_audmode_set && (2 == language) && (1 == audio_layer)) + bool success = true; + if ((2 == language) && (1 == audio_layer)) { VERBOSE(VB_GENERAL, "Dual audio mode incompatible with Layer I audio." "\n\t\t\tFalling back to Main Language"); vt.audmode = V4L2_TUNER_MODE_LANG1; + success = false; } - if (do_audmode_set && ioctl(chanfd, VIDIOC_S_TUNER, &vt) < 0) + if (ioctl(chanfd, VIDIOC_S_TUNER, &vt) < 0) { VERBOSE(VB_IMPORTANT, LOC_WARN + "Unable to set audio mode" + ENO); + success = false; } + return success; +} + +bool MpegRecorder::SetRecordingVolume(int chanfd) +{ // Get volume min/max values struct v4l2_queryctrl qctrl; qctrl.id = V4L2_CID_AUDIO_VOLUME; @@ -440,28 +577,7 @@ VERBOSE(VB_IMPORTANT, LOC_WARN + "Unable to set recording volume" + ENO + "\n\t\t\t" + "If you are using an AverMedia M179 card this is normal."); - } - - bool ok = true; - if (usingv4l2) - ok = SetV4L2DeviceOptions(chanfd); - else - { - ok = SetIVTVDeviceOptions(chanfd); - if (!ok) - usingv4l2 = ok = SetV4L2DeviceOptions(chanfd); - } - - if (!ok) return false; - - SetVBIOptions(chanfd); - - readfd = open(videodevice.ascii(), O_RDWR | O_NONBLOCK); - if (readfd < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Can't open video device." + ENO); - return false; } return true; @@ -578,7 +694,7 @@ return false; } - keyframedist = (ivtvcodec.framerate) ? 12 : keyframedist; + _keyframedist = (ivtvcodec.framerate) ? 12 : _keyframedist; return true; } @@ -602,83 +718,162 @@ } } -bool MpegRecorder::SetV4L2DeviceOptions(int chanfd) +static void add_ext_ctrl(vector &ctrl_list, + uint32_t id, int32_t value) { - static const uint kNumControls = 7; - struct v4l2_ext_controls ctrls; - struct v4l2_ext_control ext_ctrl[kNumControls]; - QString control_description[kNumControls] = + struct v4l2_ext_control tmp_ctrl; + bzero(&tmp_ctrl, sizeof(struct v4l2_ext_control)); + tmp_ctrl.id = id; + tmp_ctrl.value = value; + ctrl_list.push_back(tmp_ctrl); +} + +static void set_ctrls(int fd, vector &ext_ctrls) +{ + static QMutex control_description_lock; + static QMap control_description; + + control_description_lock.lock(); + if (control_description.isEmpty()) { - "Audio Sampling Frequency", - "Video Aspect ratio", - "Audio Encoding", - "Audio L2 Bitrate", - "Video Peak Bitrate", - "Video Average Bitrate", - "MPEG Stream type", - }; + control_description[V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ] = + "Audio Sampling Frequency"; + control_description[V4L2_CID_MPEG_VIDEO_ASPECT] = + "Video Aspect ratio"; + control_description[V4L2_CID_MPEG_AUDIO_ENCODING] = + "Audio Encoding"; + control_description[V4L2_CID_MPEG_AUDIO_L2_BITRATE] = + "Audio L2 Bitrate"; + control_description[V4L2_CID_MPEG_VIDEO_BITRATE_PEAK] = + "Video Peak Bitrate"; + control_description[V4L2_CID_MPEG_VIDEO_BITRATE] = + "Video Average Bitrate"; + control_description[V4L2_CID_MPEG_STREAM_TYPE] = + "MPEG Stream type"; + control_description[V4L2_CID_MPEG_VIDEO_BITRATE_MODE] = + "MPEG Bitrate mode"; + } + control_description_lock.unlock(); + for (uint i = 0; i < ext_ctrls.size(); i++) + { + struct v4l2_ext_controls ctrls; + bzero(&ctrls, sizeof(struct v4l2_ext_controls)); + + int value = ext_ctrls[i].value; + + ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; + ctrls.count = 1; + ctrls.controls = &ext_ctrls[i]; + + if (ioctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls) < 0) + { + QMutexLocker locker(&control_description_lock); + VERBOSE(VB_IMPORTANT, QString("mpegrecorder.cpp:set_ctrls(): ") + + QString("Could not set %1 to %2") + .arg(control_description[ext_ctrls[i].id]).arg(value) + + ENO); + } + } +} + +bool MpegRecorder::SetV4L2DeviceOptions(int chanfd) +{ + vector ext_ctrls; + // Set controls - bzero(&ctrls, sizeof(struct v4l2_ext_controls)); - bzero(&ext_ctrl, sizeof(struct v4l2_ext_control) * kNumControls); + if (driver != "hdpvr") + { + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, + GetFilteredAudioSampleRate()); + + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_ASPECT, + aspectratio - 1); - uint audio_layer = GetFilteredAudioLayer(); - uint audbitrate = GetFilteredAudioBitRate(audio_layer); + uint audio_layer = GetFilteredAudioLayer(); + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_AUDIO_ENCODING, + audio_layer - 1); + + uint audbitrate = GetFilteredAudioBitRate(audio_layer); + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_AUDIO_L2_BITRATE, + audbitrate - 1); - ext_ctrl[0].id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ; - ext_ctrl[0].value = GetFilteredAudioSampleRate(); + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_STREAM_TYPE, + streamtype_ivtv_to_v4l2(GetFilteredStreamType())); - ext_ctrl[1].id = V4L2_CID_MPEG_VIDEO_ASPECT; - ext_ctrl[1].value = aspectratio - 1; + } + else + { + maxbitrate = high_mpeg4peakbitrate; + bitrate = high_mpeg4avgbitrate; + } + maxbitrate = std::max(maxbitrate, bitrate); - ext_ctrl[2].id = V4L2_CID_MPEG_AUDIO_ENCODING; - ext_ctrl[2].value = audio_layer - 1; + if (driver == "hdpvr") + { + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + (maxbitrate == bitrate) ? + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR : + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + } - ext_ctrl[3].id = V4L2_CID_MPEG_AUDIO_L2_BITRATE; - ext_ctrl[3].value = audbitrate - 1; + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE, + bitrate * 1000); - ext_ctrl[4].id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK; - ext_ctrl[4].value = maxbitrate * 1000; + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + maxbitrate * 1000); - ext_ctrl[5].id = V4L2_CID_MPEG_VIDEO_BITRATE; - ext_ctrl[5].value = (bitrate = min(bitrate, maxbitrate)) * 1000; + set_ctrls(chanfd, ext_ctrls); - ext_ctrl[6].id = V4L2_CID_MPEG_STREAM_TYPE; - ext_ctrl[6].value = streamtype_ivtv_to_v4l2(GetFilteredStreamType()); + bool ok; + int audioinput = audiodevice.toUInt(&ok); + if (ok) + { + struct v4l2_audio ain; + bzero(&ain, sizeof(ain)); + ain.index = audioinput; + if (ioctl(chanfd, VIDIOC_ENUMAUDIO, &ain) < 0) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "Unable to get audio input."); + } + else + { + ain.index = audioinput; + if (ioctl(chanfd, VIDIOC_S_AUDIO, &ain) < 0) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "Unable to set audio input."); + } + } + } - for (uint i = 0; i < kNumControls; i++) + if (driver != "hdpvr") { - int value = ext_ctrl[i].value; + // Get GOP size in frames + struct v4l2_ext_control ext_ctrl; + struct v4l2_ext_controls ctrls; + bzero(&ext_ctrl, sizeof(struct v4l2_ext_control)); + bzero(&ctrls, sizeof(struct v4l2_ext_controls)); + + ext_ctrl.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE; + ext_ctrl.value = 0; + ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; ctrls.count = 1; - ctrls.controls = ext_ctrl + i; + ctrls.controls = &ext_ctrl; - if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &ctrls) < 0) + if (ioctl(chanfd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) { - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("Could not set %1 to %2") - .arg(control_description[i]).arg(value) + ENO); + VERBOSE(VB_IMPORTANT, LOC_WARN + "Unable to get " + "V4L2_CID_MPEG_VIDEO_GOP_SIZE, defaulting to 12" + ENO); + ext_ctrl.value = 12; } - } - // Get controls - ext_ctrl[0].id = V4L2_CID_MPEG_VIDEO_GOP_SIZE; - ext_ctrl[0].value = 0; - - ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; - ctrls.count = 1; - ctrls.controls = ext_ctrl; - - if (ioctl(chanfd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) - { - VERBOSE(VB_IMPORTANT, LOC_WARN + "Unable to get " - "V4L2_CID_MPEG_VIDEO_GOP_SIZE, defaulting to 12" + ENO); - ext_ctrl[0].value = 12; + _keyframedist = ext_ctrl.value; } - keyframedist = ext_ctrl[0].value; - return true; } @@ -687,6 +882,9 @@ if (!vbimode) return true; + if (driver == "hdpvr") + return true; + if (has_buggy_vbi) { cout<<" *********************** WARNING ***********************"<SetRecordingType(_recording_type); + SetStreamData(sd); + + _stream_data->AddAVListener(this); + _stream_data->AddWritingListener(this); + + // Make sure the first things in the file are a PAT & PMT + _wait_for_keyframe_option = false; + HandleSingleProgramPAT(_stream_data->PATSingleProgram()); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); + _wait_for_keyframe_option = true; } + else + { + SetPositionMapType(MARK_GOP_START); + } encoding = true; recording = true; unsigned char *buffer = new unsigned char[bufferSize + 1]; - int ret; + int len; + uint remainder = 0; MythTimer elapsedTimer; float elapsed; @@ -833,224 +1059,325 @@ if (deviceIsMpegFile) elapsedTimer.start(); + else if (_device_read_buffer) + _device_read_buffer->Start(); - while (encoding) + needs_resolution = (driver == "hdpvr"); + + while (encoding && !_error) { if (PauseAndWait(100)) continue; + HandleResolutionChanges(); - if ((deviceIsMpegFile) && (framesWritten)) + if (deviceIsMpegFile) { - elapsed = (elapsedTimer.elapsed() / 1000.0) + 1; - while ((framesWritten / elapsed) > 30) + if (GetFramesWritten()) { - usleep(50000); elapsed = (elapsedTimer.elapsed() / 1000.0) + 1; + while ((GetFramesWritten() / elapsed) > 30) + { + usleep(50000); + elapsed = (elapsedTimer.elapsed() / 1000.0) + 1; + } } } + else + { + if (readfd < 0) + { + if (!Open()) + { + _error = true; + return; + } - if (readfd < 0) - readfd = open(videodevice.ascii(), O_RDWR); + if (readfd < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to open device '%1'") + .arg(videodevice)); + continue; + } + } + } - tv.tv_sec = 5; - tv.tv_usec = 0; - FD_ZERO(&rdset); - FD_SET(readfd, &rdset); - -#if defined(__FreeBSD__) - // HACK. FreeBSD PVR150/500 driver doesn't currently support select() -#else - switch (select(readfd + 1, &rdset, NULL, NULL, &tv)) + if (_device_read_buffer) { - case -1: - if (errno == EINTR) - continue; + len = _device_read_buffer->Read( + &(buffer[remainder]), bufferSize - remainder); - VERBOSE(VB_IMPORTANT, LOC_ERR + "Select error" + ENO); - continue; + // Check for DRB errors + if (_device_read_buffer->IsErrored()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Device error detected"); - case 0: - VERBOSE(VB_IMPORTANT, LOC_ERR + "select timeout - " - "ivtv driver has stopped responding"); + _device_read_buffer->Stop(); - if (close(readfd) != 0) + QMutexLocker locker(&start_stop_encoding_lock); + + StopEncoding(readfd); + + // Make sure the next things in the file are a PAT & PMT + if (_stream_data->PATSingleProgram() && + _stream_data->PMTSingleProgram()) { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Close error" + ENO); + bool tmp = _wait_for_keyframe_option; + _wait_for_keyframe_option = false; + HandleSingleProgramPAT(_stream_data->PATSingleProgram()); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); + _wait_for_keyframe_option = tmp; } - readfd = -1; // Force PVR card to be reopened on next iteration - continue; + if (StartEncoding(readfd)) + { + _device_read_buffer->Start(); + } + else + { + if (0 != close(readfd)) + VERBOSE(VB_IMPORTANT, LOC_ERR + "Close error" + ENO); - default: break; + // Force card to be reopened on next iteration.. + readfd = -1; + } + } + else if (_device_read_buffer->IsEOF()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Device EOF detected"); + _error = true; + } } -#endif + else + { + if (has_select) + { + tv.tv_sec = 5; + tv.tv_usec = 0; + FD_ZERO(&rdset); + FD_SET(readfd, &rdset); - ret = read(readfd, buffer, bufferSize); + switch (select(readfd + 1, &rdset, NULL, NULL, &tv)) + { + case -1: + if (errno == EINTR) + continue; - if ((ret == 0) && - (deviceIsMpegFile)) - { - close(readfd); - readfd = open(videodevice.ascii(), O_RDONLY); + VERBOSE(VB_IMPORTANT, LOC_ERR + "Select error" + ENO); + continue; - if (readfd >= 0) - ret = read(readfd, buffer, bufferSize); - if (ret <= 0) + case 0: + VERBOSE(VB_IMPORTANT, LOC_ERR + "select timeout - " + "driver has stopped responding"); + + if (close(readfd) != 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Close error" + ENO); + } + + // Force card to be reopened on next iteration.. + readfd = -1; + continue; + + default: + break; + } + } + + len = read(readfd, &(buffer[remainder]), bufferSize - remainder); + + if (len < 0 && !has_select) { - encoding = false; + usleep(25 * 1000); continue; } + + if ((len == 0) && (deviceIsMpegFile)) + { + close(readfd); + readfd = open(videodevice.ascii(), O_RDONLY); + + if (readfd >= 0) + { + len = read(readfd, + &(buffer[remainder]), bufferSize - remainder); + } + + if (len <= 0) + { + encoding = false; + continue; + } + } + else if (len < 0 && errno != EAGAIN) + { + VERBOSE(VB_IMPORTANT, + LOC_ERR + QString("error reading from: %1") + .arg(videodevice) + ENO); + continue; + } } - else if (ret < 0 && errno != EAGAIN) + + if (len > 0) { - VERBOSE(VB_IMPORTANT, LOC_ERR + QString("error reading from: %1") - .arg(videodevice) + ENO); + len += remainder; - continue; + if (driver == "hdpvr") { + remainder = _stream_data->ProcessData(buffer, len); + int start_remain = len - remainder; + if (remainder && (start_remain >= remainder)) + memcpy(buffer, buffer+start_remain, remainder); + else if (remainder) + memmove(buffer, buffer+start_remain, remainder); + } + else + { + FindPSKeyFrames(buffer, len); + } } - else if (ret > 0) - { - ProcessData(buffer, ret); - } } + if (_device_read_buffer) + { + if (_device_read_buffer->IsRunning()) + _device_read_buffer->Stop(); + + delete _device_read_buffer; + _device_read_buffer = NULL; + } + StopEncoding(readfd); + FinishRecording(); delete[] buffer; + SetStreamData(NULL); recording = false; + QMutexLocker locker(&recording_wait_lock); + recording_wait.wakeAll(); } -bool MpegRecorder::SetupRecording(void) +bool MpegRecorder::ProcessTSPacket(const TSPacket &tspacket_real) { - leftovers = 0xFFFFFFFF; - numgops = 0; - lastseqstart = 0; - return true; -} + const uint pid = tspacket_real.PID(); -void MpegRecorder::FinishRecording(void) -{ - ringBuffer->WriterFlush(); - - if (curRecording) + TSPacket *tspacket_fake = NULL; + if ((driver == "hdpvr") && (pid == 0x1001)) // PCRPID for HD-PVR { - curRecording->SetFilesize(ringBuffer->GetRealFileSize()); - SavePositionMap(true); + tspacket_fake = tspacket_real.CreateClone(); + uint cc = (_continuity_counter[pid] == 0xFF) ? + 0 : (_continuity_counter[pid] + 1) & 0xf; + tspacket_fake->SetContinuityCounter(cc); } - positionMapLock.lock(); - positionMap.clear(); - positionMapDelta.clear(); - positionMapLock.unlock(); -} -#define PACK_HEADER 0x000001BA -#define GOP_START 0x000001B8 -#define SEQ_START 0x000001B3 -#define SLICE_MIN 0x00000101 -#define SLICE_MAX 0x000001af + const TSPacket *tspacket = (tspacket_fake) ? + tspacket_fake : &tspacket_real; -void MpegRecorder::ProcessData(unsigned char *buffer, int len) -{ - unsigned char *bufptr = buffer, *bufstart = buffer; - unsigned int state = leftovers, v = 0; - int leftlen = len; + // Check continuity counter + if ((pid != 0x1fff) && !CheckCC(pid, tspacket->ContinuityCounter())) + { + VERBOSE(VB_RECORD, LOC + + QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); + _continuity_error_count++; + } - while (bufptr < buffer + len) + // Only write the packet + // if audio/video key-frames have been found + if (!(_wait_for_keyframe_option && _first_keyframe < 0)) { - v = *bufptr++; - if (state == 0x000001) - { - state = ((state << 8) | v) & 0xFFFFFF; - - if (state == PACK_HEADER) - { - long long startpos = ringBuffer->GetWritePosition(); - startpos += buildbuffersize + bufptr - bufstart - 4; - lastpackheaderpos = startpos; + _buffer_packets = true; - int curpos = bufptr - bufstart - 4; - if (curpos < 0) - { - // header was split - buildbuffersize += curpos; - if (buildbuffersize > 0) - ringBuffer->Write(buildbuffer, buildbuffersize); + BufferedWrite(*tspacket); + } - buildbuffersize = 4; - memcpy(buildbuffer, &state, 4); + if (tspacket_fake) + delete tspacket_fake; - leftlen = leftlen - curpos + 4; - bufstart = bufptr; - } - else - { - // header was entirely in this packet - memcpy(buildbuffer + buildbuffersize, bufstart, curpos); - buildbuffersize += curpos; - bufstart += curpos; - leftlen -= curpos; + return true; +} - if (buildbuffersize > 0) - ringBuffer->Write(buildbuffer, buildbuffersize); +bool MpegRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) +{ + _buffer_packets = !FindH264Keyframes(&tspacket); + if (!_seen_sps) + return true; - buildbuffersize = 0; - } - } + return ProcessAVTSPacket(tspacket); +} - if (state == SEQ_START) - { - lastseqstart = lastpackheaderpos; - } +bool MpegRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) +{ + _buffer_packets = !FindAudioKeyframes(&tspacket); + return ProcessAVTSPacket(tspacket); +} - if (state == GOP_START && lastseqstart == lastpackheaderpos) - { - framesWritten = numgops * keyframedist; - numgops++; - HandleKeyframe(); - } - } - else - state = ((state << 8) | v) & 0xFFFFFF; +/// Common code for processing either audio or video packets +bool MpegRecorder::ProcessAVTSPacket(const TSPacket &tspacket) +{ + const uint pid = tspacket.PID(); + + // Check continuity counter + if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) + { + VERBOSE(VB_RECORD, LOC + + QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); + _continuity_error_count++; } - leftovers = state; + // Sync recording start to first keyframe + if (_wait_for_keyframe_option && _first_keyframe < 0) + return true; - if (buildbuffersize + leftlen > kBuildBufferMaxSize) + // Sync streams to the first Payload Unit Start Indicator + // _after_ first keyframe iff _wait_for_keyframe_option is true + if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) { - ringBuffer->Write(buildbuffer, buildbuffersize); - buildbuffersize = 0; + if (!tspacket.PayloadStart()) + return true; // not payload start - drop packet + + VERBOSE(VB_RECORD, + QString("PID 0x%1 Found Payload Start").arg(pid,0,16)); + + _pid_status[pid] |= kPayloadStartSeen; } - // copy remaining.. - memcpy(buildbuffer + buildbuffersize, bufstart, leftlen); - buildbuffersize += leftlen; + BufferedWrite(tspacket); + + return true; } void MpegRecorder::StopRecording(void) { - encoding = false; + QMutexLocker locker(&recording_wait_lock); + if (encoding) { + encoding = false; + recording_wait.wait(&recording_wait_lock); + } } void MpegRecorder::ResetForNewFile(void) { - errored = false; - framesWritten = 0; - numgops = 0; - lastseqstart = lastpackheaderpos = 0; + DTVRecorder::ResetForNewFile(); - positionMap.clear(); - positionMapDelta.clear(); + bzero(_stream_id, sizeof(_stream_id)); + bzero(_pid_status, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); } void MpegRecorder::Reset(void) { + VERBOSE(VB_RECORD, LOC + "Reset(void)"); ResetForNewFile(); - leftovers = 0xFFFFFFFF; - buildbuffersize = 0; + _start_code = 0xffffffff; if (curRecording) - curRecording->ClearPositionMap(MARK_GOP_START); + { + curRecording->ClearPositionMap( + (driver == "hdpvr") ? MARK_GOP_BYFRAME : MARK_GOP_START); + } + if (_stream_data) + _stream_data->Reset(_stream_data->DesiredProgram()); } void MpegRecorder::Pause(bool clear) @@ -1064,89 +1391,303 @@ { if (request_pause) { + QMutex waitlock; + waitlock.lock(); + if (!paused) { - if (requires_special_pause) + if (_device_read_buffer) { - // Some ivtv drivers require streaming to be disabled before - // an input switch and other channel format setting. - struct v4l2_encoder_cmd command; - memset(&command, 0, sizeof(struct v4l2_encoder_cmd)); - command.cmd = V4L2_ENC_CMD_STOP; - ioctl(readfd, VIDIOC_ENCODER_CMD, &command); + QMutex drb_lock; + drb_lock.lock(); + + _device_read_buffer->SetRequestPause(true); + + pauseWait.wait(&drb_lock, timeout); } + else + { + paused = true; + pauseWait.wakeAll(); + } - paused = true; - pauseWait.wakeAll(); + // Some drivers require streaming to be disabled before + // an input switch and other channel format setting. + if (requires_special_pause) + StopEncoding(readfd); + if (tvrec) tvrec->RecorderPaused(); } + unpauseWait.wait(timeout); } if (!request_pause) { if (paused) { + // Some drivers require streaming to be disabled before + // an input switch and other channel format setting. if (requires_special_pause) - { - // Some ivtv drivers require streaming to be disabled before - // an input switch and other channel format setting. - struct v4l2_encoder_cmd command; - command.cmd = V4L2_ENC_CMD_START; - ioctl(readfd, VIDIOC_ENCODER_CMD, &command); - } + StartEncoding(readfd); + + if (_device_read_buffer) + _device_read_buffer->SetRequestPause(false); + + if (_stream_data) + _stream_data->Reset(_stream_data->DesiredProgram()); } paused = false; } return paused; } -long long MpegRecorder::GetKeyframePosition(long long desired) +bool MpegRecorder::StartEncoding(int fd) { - QMutexLocker locker(&positionMapLock); - long long ret = -1; + QMutexLocker locker(&start_stop_encoding_lock); - if (positionMap.find(desired) != positionMap.end()) - ret = positionMap[desired]; + struct v4l2_encoder_cmd command; + memset(&command, 0, sizeof(struct v4l2_encoder_cmd)); + command.cmd = V4L2_ENC_CMD_START; - return ret; + VERBOSE(VB_RECORD, LOC + "StartEncoding"); + needs_resolution = (driver == "hdpvr"); + + for (int idx = 0; idx < 10; ++idx) + { + if (ioctl(fd, VIDIOC_ENCODER_CMD, &command) == 0) + { + VERBOSE(VB_RECORD, LOC + "Encoding started"); + return true; + } + + if (errno != EAGAIN) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "StartEncoding" + ENO); + return false; + } + + usleep(250 * 1000); + } + + VERBOSE(VB_IMPORTANT, LOC_ERR + "StartEncoding - giving up" + ENO); + return false; } -// documented in recorderbase.h -void MpegRecorder::SetNextRecording(const ProgramInfo *progInf, RingBuffer *rb) +bool MpegRecorder::StopEncoding(int fd) { - // First we do some of the time consuming stuff we can do now - SavePositionMap(true); - ringBuffer->WriterFlush(); - if (curRecording) - curRecording->SetFilesize(ringBuffer->GetRealFileSize()); + QMutexLocker locker(&start_stop_encoding_lock); - // Then we set the next info + struct v4l2_encoder_cmd command; + memset(&command, 0, sizeof(struct v4l2_encoder_cmd)); + command.cmd = V4L2_ENC_CMD_STOP; + + VERBOSE(VB_RECORD, LOC + "StopEncoding"); + + for (int idx = 0; idx < 10; ++idx) { - QMutexLocker locker(&nextRingBufferLock); - nextRecording = NULL; - if (progInf) - nextRecording = new ProgramInfo(*progInf); - nextRingBuffer = rb; + + if (ioctl(fd, VIDIOC_ENCODER_CMD, &command) == 0) + { + VERBOSE(VB_RECORD, LOC + "Encoding stopped"); + return true; + } + + if (errno != EAGAIN) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "StopEncoding" + ENO); + return false; + } + + usleep(250 * 1000); } + + VERBOSE(VB_IMPORTANT, LOC_ERR + "StopEncoding - giving up" + ENO); + return false; } -/** \fn MpegRecorder::HandleKeyframe(void) - * \brief This save the current frame to the position maps - * and handles ringbuffer switching. - */ -void MpegRecorder::HandleKeyframe(void) +void MpegRecorder::SetStreamData(MPEGStreamData *data) { - // Add key frame to position map - positionMapLock.lock(); - if (!positionMap.contains(numgops)) + VERBOSE(VB_RECORD, LOC + "SetStreamData("<AddMPEGSPListener(this); + data->SetDesiredProgram(1); + } + + VERBOSE(VB_RECORD, LOC + "SetStreamData("<GetWritePosition(), _payload_buffer.size() }; + + uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf; + pat->tsheader()->SetContinuityCounter(next_cc); + DTVRecorder::BufferedWrite(*(reinterpret_cast(pat->tsheader()))); + +// uint posB[2] = { ringBuffer->GetWritePosition(), _payload_buffer.size() }; + +#if 0 + if (posB[0] + posB[1] * TSPacket::SIZE > + posA[0] + posA[1] * TSPacket::SIZE) + { + VERBOSE(VB_RECORD, LOC + "Wrote PAT @" + << posA[0] << " + " << (posA[1] * TSPacket::SIZE)); + } + else + { + VERBOSE(VB_RECORD, LOC + "Saw PAT but did not write to disk yet"); + } +#endif +} + +void MpegRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) +{ + if (!pmt) +{ + return; + } + + // collect stream types for H.264 (MPEG-4 AVC) keyframe detection + for (uint i = 0; i < pmt->StreamCount(); i++) + _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); + + if (!ringBuffer) + return; + + unsigned char buf[8 * 1024]; + uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf; + pmt->tsheader()->SetContinuityCounter(next_cc); + uint size = pmt->WriteAsTSPackets(buf, next_cc); + + uint posA[2] = { ringBuffer->GetWritePosition(), _payload_buffer.size() }; + + for (uint i = 0; i < size ; i += TSPacket::SIZE) + DTVRecorder::BufferedWrite(*(reinterpret_cast(&buf[i]))); + + uint posB[2] = { ringBuffer->GetWritePosition(), _payload_buffer.size() }; + +#if 0 + if (posB[0] + posB[1] * TSPacket::SIZE > + posA[0] + posA[1] * TSPacket::SIZE) + { + VERBOSE(VB_RECORD, LOC + "Wrote PMT @" + << posA[0] << " + " << (posA[1] * TSPacket::SIZE)); + } + else + { + VERBOSE(VB_RECORD, LOC + "Saw PMT but did not write to disk yet"); + } +#endif +} + +void MpegRecorder::HandleResolutionChanges(void) +{ + if (!needs_resolution) + return; + + VERBOSE(VB_RECORD, LOC + "Checking Resolution"); + struct v4l2_format vfmt; + memset(&vfmt, 0, sizeof(vfmt)); + vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + uint pix = 0; + if (0 == ioctl(chanfd, VIDIOC_G_FMT, &vfmt)) + { + VERBOSE(VB_RECORD, LOC + QString("Got Resolution %1x%2") + .arg(vfmt.fmt.pix.width).arg(vfmt.fmt.pix.height)); + pix = vfmt.fmt.pix.width * vfmt.fmt.pix.height; + needs_resolution = false; + } + + if (!pix) + return; // nothing to do, we don't have a resolution yet + + int old_max = maxbitrate, old_avg = bitrate; + if (pix <= 768*568) + { + maxbitrate = low_mpeg4peakbitrate; + bitrate = low_mpeg4avgbitrate; + } + else if (pix >= 1920*1080) + { + maxbitrate = high_mpeg4peakbitrate; + bitrate = high_mpeg4avgbitrate; + } + else + { + maxbitrate = medium_mpeg4peakbitrate; + bitrate = medium_mpeg4avgbitrate; + } + maxbitrate = std::max(maxbitrate, bitrate); + + if ((old_max != maxbitrate) || (old_avg != bitrate)) + { + if (old_max == old_avg) + { + VERBOSE(VB_RECORD, LOC + + QString("Old bitrate %1 CBR").arg(old_avg)); + } + else + { + VERBOSE(VB_RECORD, LOC + + QString("Old bitrate %1/%2 VBR") + .arg(old_avg).arg(old_max)); + } + + if (maxbitrate == bitrate) + { + VERBOSE(VB_RECORD, LOC + QString("New bitrate %1 kbps CBR") + .arg(bitrate)); + } + else + { + VERBOSE(VB_RECORD, LOC + QString("New bitrate %1/%2 kbps VBR") + .arg(bitrate).arg(maxbitrate)); + } + + vector ext_ctrls; + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + (maxbitrate == bitrate) ? + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR : + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE, + bitrate * 1000); + + add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + maxbitrate * 1000); + + set_ctrls(readfd, ext_ctrls); + } + + // Restart streaming. Shouldn't be needed? seems to be with current driver. + QMutexLocker locker(&start_stop_encoding_lock); + StopEncoding(readfd); + StartEncoding(readfd); + + needs_resolution = false; +} Index: mythtv/libs/libmythtv/tv_rec.cpp =================================================================== --- mythtv/libs/libmythtv/tv_rec.cpp (revision 18528) +++ mythtv/libs/libmythtv/tv_rec.cpp (working copy) @@ -61,7 +61,7 @@ #include "firewirerecorder.h" #ifdef USING_V4L -#include "channel.h" +#include "v4lchannel.h" #endif #define DEBUG_CHANNEL_PREFIX 0 /**< set to 1 to channel prefixing */ @@ -202,14 +202,14 @@ else // "V4L" or "MPEG", ie, analog TV { #ifdef USING_V4L - channel = new Channel(this, genOpt.videodev); + channel = new V4LChannel(this, genOpt.videodev); if (!channel->Open()) return false; InitChannel(genOpt.defaultinput, startchannel); CloseChannel(); init_run = true; #endif - if (genOpt.cardtype != "MPEG") + if ((genOpt.cardtype != "MPEG") && (genOpt.cardtype != "HDPVR")) rbFileExt = "nuv"; } @@ -958,7 +958,7 @@ * \brief Allocates and initializes the RecorderBase instance. * * Based on the card type, one of the possible recorders are started. - * If the card type is "MPEG" a MpegRecorder is started, + * If the card type is "MPEG" or "HDPVR" a MpegRecorder is started, * if the card type is "HDHOMERUN" a HDHRRecorder is started, * if the card type is "FIREWIRE" a FirewireRecorder is started, * if the card type is "DVB" a DVBRecorder is started, @@ -976,6 +976,12 @@ recorder = new MpegRecorder(this); #endif // USING_IVTV } + else if (genOpt.cardtype == "HDPVR") + { +#ifdef USING_HDPVR + recorder = new MpegRecorder(this); +#endif // USING_HDPVR + } else if (genOpt.cardtype == "FIREWIRE") { #ifdef USING_FIREWIRE @@ -1230,10 +1236,10 @@ #endif // USING_FIREWIRE } -Channel *TVRec::GetV4LChannel(void) +V4LChannel *TVRec::GetV4LChannel(void) { #ifdef USING_V4L - return dynamic_cast(channel); + return dynamic_cast(channel); #else return NULL; #endif // USING_V4L @@ -2536,6 +2542,8 @@ long long bitrate; if (genOpt.cardtype == "MPEG") bitrate = 10080000LL; // use DVD max bit rate + if (genOpt.cardtype == "HDPVR") + bitrate = 20200000LL; // Peek bit rate for HD-PVR else if (genOpt.cardtype == "DBOX2") bitrate = 10080000LL; // use DVD max bit rate else if (!CardUtil::IsEncoder(genOpt.cardtype)) Index: mythtv/libs/libmythtv/DeviceReadBuffer.cpp =================================================================== --- mythtv/libs/libmythtv/DeviceReadBuffer.cpp (revision 18528) +++ mythtv/libs/libmythtv/DeviceReadBuffer.cpp (working copy) @@ -138,9 +138,8 @@ void DeviceReadBuffer::SetPaused(bool val) { - lock.lock(); + QMutexLocker locker(&lock); paused = val; - lock.unlock(); if (val) pauseWait.wakeAll(); else @@ -155,6 +154,8 @@ bool DeviceReadBuffer::WaitForUnpause(int timeout) { + QMutexLocker locker(&lock); + if (IsPaused()) unpauseWait.wait(timeout); return IsPaused(); Index: mythtv/libs/libmythtv/recordingprofile.cpp =================================================================== --- mythtv/libs/libmythtv/recordingprofile.cpp (revision 18528) +++ mythtv/libs/libmythtv/recordingprofile.cpp (working copy) @@ -652,32 +652,43 @@ }; }; -class MPEG2bitrate : public SliderSetting, public CodecParamStorage +class AverageBitrate : public SliderSetting, public CodecParamStorage { public: - MPEG2bitrate(const RecordingProfile &parent) : - SliderSetting(this, 1000, 16000, 100), - CodecParamStorage(this, parent, "mpeg2bitrate") + AverageBitrate(const RecordingProfile &parent, + QString setting = "mpeg2bitrate", + uint min_br = 1000, uint max_br = 16000, + uint default_br = 4500, uint increment = 100, + QString label = QString::null) : + SliderSetting(this, min_br, max_br, increment), + CodecParamStorage(this, parent, setting) { - - setLabel(QObject::tr("Bitrate")); - setValue(4500); - setHelpText(QObject::tr("Bitrate in kilobits/second. 2200Kbps is " - "approximately 1 Gigabyte per hour.")); + if (label.isEmpty()) + label = QObject::tr("Avg. Bitrate"); + setLabel(label); + setValue(default_br); + setHelpText(QObject::tr( + "Average bit rate in kilobits/second. " + "2200Kbps is approximately 1 Gigabyte per hour.")); }; }; -class MPEG2maxBitrate : public SliderSetting, public CodecParamStorage +class PeakBitrate : public SliderSetting, public CodecParamStorage { public: - MPEG2maxBitrate(const RecordingProfile &parent) : - SliderSetting(this, 1000, 16000, 100), - CodecParamStorage(this, parent, "mpeg2maxbitrate") + PeakBitrate(const RecordingProfile &parent, + QString setting = "mpeg2maxbitrate", + uint min_br = 1000, uint max_br = 16000, + uint default_br = 6000, uint increment = 100, + QString label = QString::null) : + SliderSetting(this, min_br, max_br, increment), + CodecParamStorage(this, parent, setting) { - - setLabel(QObject::tr("Max. Bitrate")); - setValue(6000); - setHelpText(QObject::tr("Maximum Bitrate in kilobits/second. " + if (label.isEmpty()) + label = QObject::tr("Max. Bitrate"); + setLabel(label); + setValue(default_br); + setHelpText(QObject::tr("Maximum bit rate in kilobits/second. " "2200Kbps is approximately 1 Gigabyte per hour.")); }; }; @@ -819,7 +830,7 @@ params = new VerticalConfigurationGroup(false); params->setLabel(QObject::tr("MPEG-2 Parameters")); - params->addChild(new MPEG2bitrate(parent)); + params->addChild(new AverageBitrate(parent)); params->addChild(new ScaleBitrate(parent)); //params->addChild(new MPEG4MaxQuality(parent)); //params->addChild(new MPEG4MinQuality(parent)); @@ -843,17 +854,47 @@ params->setLabel(QObject::tr("MPEG-2 Hardware Encoder")); params->addChild(new MPEG2streamType(parent)); params->addChild(new MPEG2aspectRatio(parent)); - params->addChild(new MPEG2bitrate(parent)); - params->addChild(new MPEG2maxBitrate(parent)); + params->addChild(new AverageBitrate(parent)); + params->addChild(new PeakBitrate(parent)); addTarget("MPEG-2 Hardware Encoder", params); + + params = new VerticalConfigurationGroup(false); + params->setLabel(QObject::tr("MPEG-4 AVC Hardware Encoder")); + ConfigurationGroup *h0 = new HorizontalConfigurationGroup( + true, false, true, true); + h0->setLabel(QObject::tr("Low Resolution")); + h0->addChild(new AverageBitrate(parent, "low_mpeg4avgbitrate", + 1000, 13500, 4500, 500)); + h0->addChild(new PeakBitrate(parent, "low_mpeg4peakbitrate", + 1100, 20200, 6000, 500)); + params->addChild(h0); + ConfigurationGroup *h1 = new HorizontalConfigurationGroup( + true, false, true, true); + h1->setLabel(QObject::tr("Medium Resolution")); + h1->addChild(new AverageBitrate(parent, "medium_mpeg4avgbitrate", + 1000, 13500, 9000, 500)); + h1->addChild(new PeakBitrate(parent, "medium_mpeg4peakbitrate", + 1100, 20200, 11000, 500)); + params->addChild(h1); + ConfigurationGroup *h2 = new HorizontalConfigurationGroup( + true, false, true, true); + h2->setLabel(QObject::tr("High Resolution")); + h2->addChild(new AverageBitrate(parent, "high_mpeg4avgbitrate", + 1000, 13500, 13500, 500)); + h2->addChild(new PeakBitrate(parent, "high_mpeg4peakbitrate", + 1100, 20200, 20200, 500)); + params->addChild(h2); + addTarget("MPEG-4 AVC Hardware Encoder", params); } void selectCodecs(QString groupType) { if (!groupType.isNull()) { - if (groupType == "MPEG") + if (groupType == "HDPVR") + codecName->addSelection("MPEG-4 AVC Hardware Encoder"); + else if (groupType == "MPEG") codecName->addSelection("MPEG-2 Hardware Encoder"); else if (groupType == "MJPEG") codecName->addSelection("Hardware MJPEG"); @@ -1210,13 +1251,17 @@ if (isEncoder) { QString tvFormat = gContext->GetSetting("TVFormat"); - addChild(new ImageSize(*this, tvFormat, profileName)); + if (type.upper() != "HDPVR") + addChild(new ImageSize(*this, tvFormat, profileName)); videoSettings = new VideoCompressionSettings(*this, profileName); addChild(videoSettings); - audioSettings = new AudioCompressionSettings(*this, profileName); - addChild(audioSettings); + if (type.upper() != "HDPVR") + { + audioSettings = new AudioCompressionSettings(*this, profileName); + addChild(audioSettings); + } if (profileName && profileName.left(11) == "Transcoders") { Index: mythtv/libs/libmythtv/tv_rec.h =================================================================== --- mythtv/libs/libmythtv/tv_rec.h (revision 18528) +++ mythtv/libs/libmythtv/tv_rec.h (working copy) @@ -37,7 +37,7 @@ class DTVChannel; class DVBChannel; class FirewireChannel; -class Channel; +class V4LChannel; class HDHRChannel; class MPEGStreamData; @@ -281,7 +281,7 @@ HDHRChannel *GetHDHRChannel(void); DVBChannel *GetDVBChannel(void); FirewireChannel *GetFirewireChannel(void); - Channel *GetV4LChannel(void); + V4LChannel *GetV4LChannel(void); bool SetupSignalMonitor(bool enable_table_monitoring, bool notify); bool SetupDTVSignalMonitor(void); Index: mythtv/libs/libmythtv/scanwizardhelpers.cpp =================================================================== --- mythtv/libs/libmythtv/scanwizardhelpers.cpp (revision 18528) +++ mythtv/libs/libmythtv/scanwizardhelpers.cpp (working copy) @@ -58,6 +58,9 @@ # ifdef USING_IVTV cardTypes += ",'MPEG'"; # endif // USING_IVTV +# ifdef USING_HDPVR + cardTypes += ",'HDPVR'"; +# endif // USING_HDPVR #endif // USING_V4L #ifdef USING_IPTV Index: mythtv/libs/libmythtv/channel.cpp =================================================================== --- mythtv/libs/libmythtv/channel.cpp (revision 18528) +++ mythtv/libs/libmythtv/channel.cpp (working copy) @@ -1,1320 +0,0 @@ -// Std C headers -#include -#include -#include - -// POSIX headers -#include -#include -#include -#include -#include -#include - -// C++ headers -#include -#include -using namespace std; - -// Qt headers -#include - -// MythTV headers -#include "videodev_myth.h" -#include "channel.h" -#include "frequencies.h" -#include "tv_rec.h" -#include "mythcontext.h" -#include "mythdbcon.h" -#include "channelutil.h" -#include "cardutil.h" - -#define DEBUG_ATTRIB 1 - -#define LOC QString("Channel(%1): ").arg(device) -#define LOC_WARN QString("Channel(%1) Warning: ").arg(device) -#define LOC_ERR QString("Channel(%1) Error: ").arg(device) - -static int format_to_mode(const QString& fmt, int v4l_version); -static QString mode_to_format(int mode, int v4l_version); - -/** \class Channel - * \brief Class implementing ChannelBase interface to tuning hardware - * when using Video4Linux based drivers. - */ - -Channel::Channel(TVRec *parent, const QString &videodevice) - : DTVChannel(parent), - device(videodevice), videofd(-1), - device_name(QString::null), driver_name(QString::null), - curList(NULL), totalChannels(0), - currentFormat(""), is_dtv(false), - usingv4l2(false), defaultFreqTable(1) -{ -} - -Channel::~Channel(void) -{ - Close(); -} - -bool Channel::Init(QString &inputname, QString &startchannel, bool setchan) -{ - if (setchan) - { - SetFormat(gContext->GetSetting("TVFormat")); - SetDefaultFreqTable(gContext->GetSetting("FreqTable")); - } - return ChannelBase::Init(inputname, startchannel, setchan); -} - -bool Channel::Open(void) -{ -#if FAKE_VIDEO - return true; -#endif - if (videofd >= 0) - return true; - - videofd = open(device.ascii(), O_RDWR); - if (videofd < 0) - { - VERBOSE(VB_IMPORTANT, - QString("Channel(%1)::Open(): Can't open video device, " - "error \"%2\"").arg(device).arg(strerror(errno))); - return false; - } - - usingv4l2 = CardUtil::hasV4L2(videofd); - CardUtil::GetV4LInfo(videofd, device_name, driver_name); - VERBOSE(VB_CHANNEL, LOC + QString("Device name '%1' driver '%2'.") - .arg(device_name).arg(driver_name)); - - if (!InitializeInputs()) - { - Close(); - return false; - } - - SetFormat("Default"); - - return true; -} - -void Channel::Close(void) -{ - if (videofd >= 0) - close(videofd); - videofd = -1; -} - -void Channel::SetFd(int fd) -{ - if (fd != videofd) - Close(); - videofd = (fd >= 0) ? fd : -1; -} - -static int format_to_mode(const QString &fmt, int v4l_version) -{ - if (2 == v4l_version) - { - if (fmt == "PAL-BG") - return V4L2_STD_PAL_BG; - else if (fmt == "PAL-D") - return V4L2_STD_PAL_D; - else if (fmt == "PAL-DK") - return V4L2_STD_PAL_DK; - else if (fmt == "PAL-I") - return V4L2_STD_PAL_I; - else if (fmt == "PAL-60") - return V4L2_STD_PAL_60; - else if (fmt == "SECAM") - return V4L2_STD_SECAM; - else if (fmt == "SECAM-D") - return V4L2_STD_SECAM_D; - else if (fmt == "PAL-NC") - return V4L2_STD_PAL_Nc; - else if (fmt == "PAL-M") - return V4L2_STD_PAL_M; - else if (fmt == "PAL-N") - return V4L2_STD_PAL_N; - else if (fmt == "NTSC-JP") - return V4L2_STD_NTSC_M_JP; - // generics... - else if (fmt.left(4) == "NTSC") - return V4L2_STD_NTSC; - else if (fmt.left(4) == "ATSC") - return V4L2_STD_NTSC; // We've dropped V4L ATSC support... - else if (fmt.left(3) == "PAL") - return V4L2_STD_PAL; - return V4L2_STD_NTSC; - } - else if (1 == v4l_version) - { - if (fmt == "NTSC-JP") - return 6; - else if (fmt.left(5) == "SECAM") - return VIDEO_MODE_SECAM; - else if (fmt == "PAL-NC") - return 3; - else if (fmt == "PAL-M") - return 4; - else if (fmt == "PAL-N") - return 5; - // generics... - else if (fmt.left(3) == "PAL") - return VIDEO_MODE_PAL; - else if (fmt.left(4) == "NTSC") - return VIDEO_MODE_NTSC; - else if (fmt.left(4) == "ATSC") - return VIDEO_MODE_NTSC; // We've dropped V4L ATSC support... - return VIDEO_MODE_NTSC; - } - - VERBOSE(VB_IMPORTANT, - "format_to_mode() does not recognize V4L" << v4l_version); - - return V4L2_STD_NTSC; // assume V4L version 2 -} - -static QString mode_to_format(int mode, int v4l_version) -{ - if (2 == v4l_version) - { - if (mode == V4L2_STD_NTSC) - return "NTSC"; - else if (mode == V4L2_STD_NTSC_M_JP) - return "NTSC-JP"; - else if (mode == V4L2_STD_PAL) - return "PAL"; - else if (mode == V4L2_STD_PAL_60) - return "PAL-60"; - else if (mode == V4L2_STD_PAL_BG) - return "PAL-BG"; - else if (mode == V4L2_STD_PAL_D) - return "PAL-D"; - else if (mode == V4L2_STD_PAL_DK) - return "PAL-DK"; - else if (mode == V4L2_STD_PAL_I) - return "PAL-I"; - else if (mode == V4L2_STD_PAL_M) - return "PAL-M"; - else if (mode == V4L2_STD_PAL_N) - return "PAL-N"; - else if (mode == V4L2_STD_PAL_Nc) - return "PAL-NC"; - else if (mode == V4L2_STD_SECAM) - return "SECAM"; - else if (mode == V4L2_STD_SECAM_D) - return "SECAM-D"; - // generic.. - else if ((V4L2_STD_NTSC_M == mode) || - (V4L2_STD_NTSC_443 == mode) || - (V4L2_STD_NTSC_M_KR == mode)) - return "NTSC"; - else if ((V4L2_STD_PAL_B == mode) || - (V4L2_STD_PAL_B1 == mode) || - (V4L2_STD_PAL_G == mode) || - (V4L2_STD_PAL_H == mode) || - (V4L2_STD_PAL_D1 == mode) || - (V4L2_STD_PAL_K == mode)) - return "PAL"; - else if ((V4L2_STD_SECAM_B == mode) || - (V4L2_STD_SECAM_DK == mode) || - (V4L2_STD_SECAM_G == mode) || - (V4L2_STD_SECAM_H == mode) || - (V4L2_STD_SECAM_K == mode) || - (V4L2_STD_SECAM_K1 == mode) || - (V4L2_STD_SECAM_L == mode) || - (V4L2_STD_SECAM_LC == mode)) - return "SECAM"; - else if ((V4L2_STD_ATSC == mode) || - (V4L2_STD_ATSC_8_VSB == mode) || - (V4L2_STD_ATSC_16_VSB == mode)) - { - // We've dropped V4L ATSC support, but this still needs to be - // returned here so we will change the mode if the device is - // in ATSC mode already. - return "ATSC"; - } - } - else if (1 == v4l_version) - { - if (mode == VIDEO_MODE_NTSC) - return "NTSC"; - else if (mode == VIDEO_MODE_ATSC) - return "ATSC"; - else if (mode == VIDEO_MODE_PAL) - return "PAL"; - else if (mode == VIDEO_MODE_SECAM) - return "SECAM"; - else if (mode == 3) - return "PAL-NC"; - else if (mode == 4) - return "PAL-M"; - else if (mode == 5) - return "PAL-N"; - else if (mode == 6) - return "NTSC-JP"; - } - else - { - VERBOSE(VB_IMPORTANT, - "mode_to_format() does not recognize V4L" << v4l_version); - } - - return "Unknown"; -} - -/** \fn Channel::InitializeInputs(void) - * This enumerates the inputs, converts the format - * string to something the hardware understands, and - * if the parent pointer is valid retrieves the - * channels from the database. - */ -bool Channel::InitializeInputs(void) -{ - // Get Inputs from DB - if (!ChannelBase::InitializeInputs()) - return false; - - // Get global TVFormat setting - QString fmt = gContext->GetSetting("TVFormat"); - VERBOSE(VB_CHANNEL, QString("Global TVFormat Setting '%1'").arg(fmt)); - int videomode_v4l1 = format_to_mode(fmt.upper(), 1); - int videomode_v4l2 = format_to_mode(fmt.upper(), 2); - - bool ok = false; - InputNames v4l_inputs = CardUtil::probeV4LInputs(videofd, ok); - - // Insert info from hardware - uint valid_cnt = 0; - InputMap::const_iterator it; - for (it = inputs.begin(); it != inputs.end(); ++it) - { - InputNames::const_iterator v4l_it = v4l_inputs.begin(); - for (; v4l_it != v4l_inputs.end(); ++v4l_it) - { - if (*v4l_it == (*it)->name) - { - (*it)->inputNumV4L = v4l_it.key(); - (*it)->videoModeV4L1 = videomode_v4l1; - (*it)->videoModeV4L2 = videomode_v4l2; - valid_cnt++; - } - } - } - - // print em - for (it = inputs.begin(); it != inputs.end(); ++it) - { - VERBOSE(VB_CHANNEL, LOC + QString("Input #%1: '%2' schan(%3) " - "tun(%4) v4l1(%5) v4l2(%6)") - .arg(it.key()).arg((*it)->name).arg((*it)->startChanNum) - .arg((*it)->tuneToChannel) - .arg(mode_to_format((*it)->videoModeV4L1,1)) - .arg(mode_to_format((*it)->videoModeV4L2,2))); - } - - return valid_cnt; -} - -/** \fn Channel::SetFormat(const QString &format) - * \brief Initializes tuner and modulator variables - * - * \param format One of twelve formats: - * "NTSC", "NTSC-JP", "ATSC", - * "SECAM", - * "PAL", "PAL-BG", "PAL-DK", "PAL-I", - * "PAL-60", "PAL-NC", "PAL-M", or "PAL-N" - */ -void Channel::SetFormat(const QString &format) -{ - if (!Open()) - return; - - int inputNum = currentInputID; - if (currentInputID < 0) - inputNum = GetNextInputNum(); - - QString fmt = format; - if ((fmt == "Default") || format.isEmpty()) - { - InputMap::const_iterator it = inputs.find(inputNum); - if (it != inputs.end()) - fmt = mode_to_format((*it)->videoModeV4L2, 2); - } - - VERBOSE(VB_CHANNEL, LOC + QString("SetFormat(%1) fmt(%2) input(%3)") - .arg(format).arg(fmt).arg(inputNum)); - - if ((fmt == currentFormat) || SetInputAndFormat(inputNum, fmt)) - { - currentFormat = fmt; - is_dtv = (fmt == "ATSC"); - } -} - -int Channel::SetDefaultFreqTable(const QString &name) -{ - defaultFreqTable = SetFreqTable(name); - return defaultFreqTable; -} - -void Channel::SetFreqTable(const int index) -{ - curList = chanlists[index].list; - totalChannels = chanlists[index].count; -} - -int Channel::SetFreqTable(const QString &name) -{ - int i = 0; - char *listname = (char *)chanlists[i].name; - - curList = NULL; - while (listname != NULL) - { - if (name == listname) - { - SetFreqTable(i); - return i; - } - i++; - listname = (char *)chanlists[i].name; - } - - VERBOSE(VB_CHANNEL, QString("Channel(%1)::SetFreqTable(): Invalid " - "frequency table name %2, using %3."). - arg(device).arg(name).arg((char *)chanlists[1].name)); - SetFreqTable(1); - return 1; -} - -int Channel::GetCurrentChannelNum(const QString &channame) -{ - for (int i = 0; i < totalChannels; i++) - { - if (channame == curList[i].name) - return i; - } - - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("GetCurrentChannelNum(%1): " - "Failed to find Channel").arg(channame)); - - return -1; -} - -void Channel::SaveCachedPids(const pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::SaveCachedPids(chanid, pid_cache); -} - -void Channel::GetCachedPids(pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::GetCachedPids(chanid, pid_cache); -} - -bool Channel::SetChannelByString(const QString &channum) -{ - QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); - QString loc_err = loc + ", Error: "; - VERBOSE(VB_CHANNEL, loc); - - if (!Open()) - { - VERBOSE(VB_IMPORTANT, loc_err + "Channel object " - "will not open, can not change channels."); - - return false; - } - - QString inputName; - if (!CheckChannel(channum, inputName)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "CheckChannel failed.\n\t\t\tPlease verify the channel " - "in the 'mythtv-setup' Channel Editor."); - - return false; - } - - // If CheckChannel filled in the inputName then we need to - // change inputs and return, since the act of changing - // inputs will change the channel as well. - if (!inputName.isEmpty()) - return ChannelBase::SwitchToInput(inputName, channum); - - ClearDTVInfo(); - - InputMap::const_iterator it = inputs.find(currentInputID); - if (it == inputs.end()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(currentInputID, mplexid_restriction)) - return false; - - // Fetch tuning data from the database. - QString tvformat, modulation, freqtable, freqid, dtv_si_std; - int finetune; - uint64_t frequency; - int mpeg_prog_num; - uint atsc_major, atsc_minor, mplexid, tsid, netid; - - if (!ChannelUtil::GetChannelData( - (*it)->sourceid, channum, - tvformat, modulation, freqtable, freqid, - finetune, frequency, - dtv_si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, - mplexid, commfree)) - { - return false; - } - - if (mplexid_restriction && (mplexid != mplexid_restriction)) - return false; - - // If the frequency is zeroed out, don't use it directly. - bool ok = (frequency > 0); - - if (!ok) - { - frequency = (freqid.toInt(&ok) + finetune) * 1000; - mplexid = 0; - } - bool isFrequency = ok && (frequency > 10000000); - - // If we are tuning to a freqid, rather than an actual frequency, - // we need to set the frequency table to use. - if (!isFrequency) - { - if (freqtable == "default" || freqtable.isEmpty()) - SetFreqTable(defaultFreqTable); - else - SetFreqTable(freqtable); - } - - // Set NTSC, PAL, ATSC, etc. - SetFormat(tvformat); - - // If a tuneToChannel is set make sure we're still on it - if (!(*it)->tuneToChannel.isEmpty() && (*it)->tuneToChannel != "Undefined") - TuneTo((*it)->tuneToChannel, 0); - - // Tune to proper frequency - if ((*it)->externalChanger.isEmpty()) - { - if ((*it)->name.contains("composite", false) || - (*it)->name.contains("s-video", false)) - { - VERBOSE(VB_GENERAL, LOC_WARN + "You have not set " - "an external channel changing" - "\n\t\t\tscript for a composite or s-video " - "input. Channel changing will do nothing."); - } - else if (isFrequency) - { - if (!Tune(frequency, "", (is_dtv) ? "8vsb" : "analog", dtv_si_std)) - { - return false; - } - } - else - { - if (!TuneTo(freqid, finetune)) - return false; - } - } - else if (!ChangeExternalChannel(freqid)) - return false; - - // Set the current channum to the new channel's channum - curchannelname = QDeepCopy(channum); - - // Setup filters & recording picture attributes for framegrabing recorders - // now that the new curchannelname has been established. - if (pParent) - pParent->SetVideoFiltersForChannel(GetCurrentSourceID(), channum); - InitPictureAttributes(); - - // Set the major and minor channel for any additional multiplex tuning - SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); - - // Set this as the future start channel for this source - inputs[currentInputID]->startChanNum = QDeepCopy(curchannelname); - - return true; -} - -bool Channel::TuneTo(const QString &channum, int finetune) -{ - int i = GetCurrentChannelNum(channum); - VERBOSE(VB_CHANNEL, QString("Channel(%1)::TuneTo(%2): " - "curList[%3].freq(%4)") - .arg(device).arg(channum).arg(i) - .arg((i != -1) ? curList[i].freq : -1)); - - if (i == -1) - { - VERBOSE(VB_IMPORTANT, QString("Channel(%1)::TuneTo(%2): Error, " - "failed to find channel.") - .arg(device).arg(channum)); - return false; - } - - int frequency = (curList[i].freq + finetune) * 1000; - - return Tune(frequency, "", "analog", "analog"); -} - -bool Channel::Tune(const DTVMultiplex &tuning, QString inputname) -{ - return Tune(tuning.frequency - 1750000, // to visual carrier - inputname, tuning.modulation.toString(), tuning.sistandard); -} - -/** \fn Channel::Tune(uint,QString,QString,QString) - * \brief Tunes to a specific frequency (Hz) on a particular input, using - * the specified modulation. - * - * Note: This function always uses modulator zero. - * - * \param frequency Frequency in Hz, this is divided by 62.5 kHz or 62.5 Hz - * depending on the modulator and sent to the hardware. - * \param inputname Name of the input (Television, Antenna 1, etc.) - * \param modulation "radio", "analog", or "digital" - */ -bool Channel::Tune(uint frequency, QString inputname, - QString modulation, QString si_std) -{ - VERBOSE(VB_CHANNEL, LOC + QString("Tune(%1, %2, %3, %4)") - .arg(frequency).arg(inputname).arg(modulation).arg(si_std)); - - int ioctlval = 0; - - if (modulation == "8vsb") - SetFormat("ATSC"); - modulation = (is_dtv) ? "digital" : modulation; - - int inputnum = GetInputByName(inputname); - - bool ok = true; - if ((inputnum >= 0) && (GetCurrentInputNum() != inputnum)) - ok = SwitchToInput(inputnum, false); - else if (GetCurrentInputNum() < 0) - ok = SwitchToInput(0, false); - - if (!ok) - return false; - - // If the frequency is a center frequency and not - // a visual carrier frequency, convert it. - int offset = frequency % 1000000; - offset = (offset > 500000) ? 1000000 - offset : offset; - bool is_visual_carrier = (offset > 150000) && (offset < 350000); - if (!is_visual_carrier && currentFormat == "ATSC") - { - VERBOSE(VB_CHANNEL, QString("Channel(%1): ").arg(device) + - QString("Converting frequency from center frequency " - "(%1 Hz) to visual carrier frequency (%2 Hz).") - .arg(frequency).arg(frequency - 1750000)); - frequency -= 1750000; // convert to visual carrier - } - - // Video4Linux version 2 tuning - if (usingv4l2) - { - bool isTunerCapLow = false; - struct v4l2_modulator mod; - bzero(&mod, sizeof(mod)); - mod.index = 0; - ioctlval = ioctl(videofd, VIDIOC_G_MODULATOR, &mod); - if (ioctlval >= 0) - { - isTunerCapLow = (mod.capability & V4L2_TUNER_CAP_LOW); - VERBOSE(VB_CHANNEL, " name: "<=0) - return true; - VERBOSE(VB_CHANNEL, "digital modulation failed"); - } - - vf.type = V4L2_TUNER_ANALOG_TV; - - ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, - QString("Channel(%1)::Tune(): Error %2 " - "while setting frequency (v2): %3") - .arg(device).arg(ioctlval).arg(strerror(errno))); - return false; - } - ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf); - - if (ioctlval >= 0) - { - VERBOSE(VB_CHANNEL, QString( - "Channel(%1)::Tune(): Frequency is now %2") - .arg(device).arg(vf.frequency * 62500)); - } - - return true; - } - - // Video4Linux version 1 tuning - uint freq = frequency / 62500; - ioctlval = ioctl(videofd, VIDIOCSFREQ, &freq); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, - QString("Channel(%1)::Tune(): Error %2 " - "while setting frequency (v1): %3") - .arg(device).arg(ioctlval).arg(strerror(errno))); - return false; - } - - SetSIStandard(si_std); - - return true; -} - -/** \fn Channel::Retune(void) - * \brief Retunes to last tuned frequency. - * - * NOTE: This only works for V4L2 and only for analog tuning. - */ -bool Channel::Retune(void) -{ - if (usingv4l2) - { - struct v4l2_frequency vf; - bzero(&vf, sizeof(vf)); - - vf.tuner = 0; // use first tuner - vf.type = V4L2_TUNER_ANALOG_TV; - - // Get the last tuned frequency - int ioctlval = ioctl(videofd, VIDIOC_G_FREQUENCY, &vf); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Retune failed (1)" + ENO); - return false; - } - - // Set the last tuned frequency again... - ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Retune failed (2)" + ENO); - return false; - } - - return true; - } - - return false; -} - -// documented in dtvchannel.h -bool Channel::TuneMultiplex(uint mplexid, QString inputname) -{ - VERBOSE(VB_CHANNEL, LOC + QString("TuneMultiplex(%1)").arg(mplexid)); - - QString modulation; - QString si_std; - uint64_t frequency; - uint transportid; - uint dvb_networkid; - - if (!ChannelUtil::GetTuningParams( - mplexid, modulation, frequency, - transportid, dvb_networkid, si_std)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "TuneMultiplex(): " + - QString("Could not find tuning parameters for multiplex %1.") - .arg(mplexid)); - - return false; - } - - if (!Tune(frequency, inputname, modulation, si_std)) - return false; - - return true; -} - -QString Channel::GetFormatForChannel(QString channum, QString inputname) -{ - MSqlQuery query(MSqlQuery::InitCon()); - query.prepare( - "SELECT tvformat " - "FROM channel, cardinput " - "WHERE channum = :CHANNUM AND " - " inputname = :INPUTNAME AND " - " cardinput.cardid = :CARDID AND " - " cardinput.sourceid = channel.sourceid"); - query.bindValue(":CHANNUM", channum); - query.bindValue(":INPUTNAME", inputname); - query.bindValue(":CARDID", GetCardID()); - - QString fmt = QString::null; - if (!query.exec() || !query.isActive()) - MythContext::DBError("SwitchToInput:find format", query); - else if (query.next()) - fmt = query.value(0).toString(); - return fmt; -} - -bool Channel::SetInputAndFormat(int inputNum, QString newFmt) -{ - InputMap::const_iterator it = inputs.find(inputNum); - if (it == inputs.end() || (*it)->inputNumV4L < 0) - return false; - - int inputNumV4L = (*it)->inputNumV4L; - bool usingv4l1 = !usingv4l2; - bool ok = true; - - QString msg = - QString("SetInputAndFormat(%1, %2) ").arg(inputNum).arg(newFmt); - - if (usingv4l2) - { - VERBOSE(VB_CHANNEL, LOC + msg + "(v4l v2)"); - - int ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); - - // ConvertX (wis-go7007) requires streaming to be disabled - // before an input switch, do this if initial switch failed. - bool streamingDisabled = false; - int streamType = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ((ioctlval < 0) && (errno == EBUSY)) - { - ioctlval = ioctl(videofd, VIDIOC_STREAMOFF, &streamType); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile disabling streaming (v4l v2)" + ENO); - - ok = false; - ioctlval = 0; - } - else - { - streamingDisabled = true; - - // Resend the input switch ioctl. - ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); - } - } - - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile setting input (v4l v2)" + ENO); - - ok = false; - } - - v4l2_std_id vid_mode = format_to_mode(newFmt, 2); - ioctlval = ioctl(videofd, VIDIOC_S_STD, &vid_mode); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile setting format (v4l v2)" + ENO); - - ok = false; - } - - // ConvertX (wis-go7007) requires streaming to be disabled - // before an input switch, here we try to re-enable streaming. - if (streamingDisabled) - { - ioctlval = ioctl(videofd, VIDIOC_STREAMON, &streamType); - if (ioctlval < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile reenabling streaming (v4l v2)" + ENO); - - ok = false; - } - } - } - - if (usingv4l1) - { - VERBOSE(VB_CHANNEL, LOC + msg + "(v4l v1)"); - - // read in old settings - struct video_channel set; - bzero(&set, sizeof(set)); - ioctl(videofd, VIDIOCGCHAN, &set); - - // set new settings - set.channel = inputNumV4L; - set.norm = format_to_mode(newFmt, 1); - int ioctlval = ioctl(videofd, VIDIOCSCHAN, &set); - - ok = (ioctlval >= 0); - if (!ok) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile setting format (v4l v1)" + ENO); - } - else if (usingv4l2) - { - VERBOSE(VB_IMPORTANT, LOC + msg + - "\n\t\t\tSetting video mode with v4l version 1 worked"); - } - } - return ok; -} - -bool Channel::SwitchToInput(int inputnum, bool setstarting) -{ - InputMap::const_iterator it = inputs.find(inputnum); - if (it == inputs.end()) - return false; - - QString tuneFreqId = (*it)->tuneToChannel; - QString channum = (*it)->startChanNum; - QString inputname = (*it)->name; - - VERBOSE(VB_CHANNEL, QString("Channel(%1)::SwitchToInput(in %2, '%3')") - .arg(device).arg(inputnum) - .arg(setstarting ? channum : QString(""))); - - uint mplexid_restriction; - if (!IsInputAvailable(inputnum, mplexid_restriction)) - return false; - - QString newFmt = mode_to_format((*it)->videoModeV4L2, 2); - - // If we are setting a channel, get its video mode... - bool chanValid = (channum != "Undefined") && !channum.isEmpty(); - if (setstarting && chanValid) - { - QString tmp = GetFormatForChannel(channum, inputname); - if (tmp != "Default" && !tmp.isEmpty()) - newFmt = tmp; - } - - bool ok = SetInputAndFormat(inputnum, newFmt); - - // Try to set ATSC mode if NTSC fails - if (!ok && newFmt == "NTSC") - ok = SetInputAndFormat(inputnum, "ATSC"); - - if (!ok) - { - VERBOSE(VB_IMPORTANT, LOC + "SetInputAndFormat() failed"); - return false; - } - - currentFormat = newFmt; - is_dtv = newFmt == "ATSC"; - currentInputID = inputnum; - curchannelname = ""; // this will be set by SetChannelByString - - if (!tuneFreqId.isEmpty() && tuneFreqId != "Undefined") - ok = TuneTo(tuneFreqId, 0); - - if (!ok) - return false; - - if (setstarting && chanValid) - ok = SetChannelByString(channum); - else if (setstarting && !chanValid) - { - VERBOSE(VB_IMPORTANT, LOC + - QString("SwitchToInput(in %2, set ch): ").arg(inputnum) + - QString("\n\t\t\tDefault channel '%1' is not valid.") - .arg(channum)); - ok = false; - } - - return ok; -} - -static unsigned short *get_v4l1_field( - int v4l2_attrib, struct video_picture &vid_pic) -{ - switch (v4l2_attrib) - { - case V4L2_CID_CONTRAST: - return &vid_pic.contrast; - case V4L2_CID_BRIGHTNESS: - return &vid_pic.brightness; - case V4L2_CID_SATURATION: - return &vid_pic.colour; - case V4L2_CID_HUE: - return &vid_pic.hue; - default: - VERBOSE(VB_IMPORTANT, "get_v4l1_field: " - "invalid attribute argument "< cdb %2 rdb %3 d %4 -> %5") - .arg(db_col_name).arg(cfield).arg(sfield) - .arg(dfield).arg(val)); -#endif - - return val; -} - -static int get_v4l2_attribute_value(int videofd, int v4l2_attrib) -{ - struct v4l2_control ctrl; - struct v4l2_queryctrl qctrl; - bzero(&ctrl, sizeof(ctrl)); - bzero(&qctrl, sizeof(qctrl)); - - ctrl.id = qctrl.id = v4l2_attrib; - if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0) - { - VERBOSE(VB_IMPORTANT, "get_v4l2_attribute_value: " - "failed to query controls (1)" + ENO); - return -1; - } - - if (ioctl(videofd, VIDIOC_G_CTRL, &ctrl) < 0) - { - VERBOSE(VB_IMPORTANT, "get_v4l2_attribute_value: " - "failed to get controls (2)" + ENO); - return -1; - } - - float mult = 65535.0 / (qctrl.maximum - qctrl.minimum); - return min(max((int)(mult * (ctrl.value - qctrl.minimum)), 0), 65525); -} - -static int get_v4l1_attribute_value(int videofd, int v4l2_attrib) -{ - struct video_picture vid_pic; - bzero(&vid_pic, sizeof(vid_pic)); - - if (ioctl(videofd, VIDIOCGPICT, &vid_pic) < 0) - { - VERBOSE(VB_IMPORTANT, "get_v4l1_attribute_value: " - "failed to get picture control (1)" + ENO); - return -1; - } - - unsigned short *setfield = get_v4l1_field(v4l2_attrib, vid_pic); - if (setfield) - return *setfield; - - return -1; -} - -static int get_attribute_value(bool usingv4l2, int videofd, int v4l2_attrib) -{ - if (usingv4l2) - return get_v4l2_attribute_value(videofd, v4l2_attrib); - return get_v4l1_attribute_value(videofd, v4l2_attrib); -} - -static int set_v4l2_attribute_value(int videofd, int v4l2_attrib, int newvalue) -{ - struct v4l2_control ctrl; - struct v4l2_queryctrl qctrl; - bzero(&ctrl, sizeof(ctrl)); - bzero(&qctrl, sizeof(qctrl)); - - ctrl.id = qctrl.id = v4l2_attrib; - if (ioctl(videofd, VIDIOC_QUERYCTRL, &qctrl) < 0) - { - VERBOSE(VB_IMPORTANT, "set_v4l2_attribute_value: " - "failed to query control" + ENO); - return -1; - } - - float mult = (qctrl.maximum - qctrl.minimum) / 65535.0; - ctrl.value = (int)(mult * newvalue + qctrl.minimum); - ctrl.value = min(ctrl.value, qctrl.maximum); - ctrl.value = max(ctrl.value, qctrl.minimum); - - if (ioctl(videofd, VIDIOC_S_CTRL, &ctrl) < 0) - { - VERBOSE(VB_IMPORTANT, "set_v4l2_attribute_value: " - "failed to set control" + ENO); - return -1; - } - - return 0; -} - -static int set_v4l1_attribute_value(int videofd, int v4l2_attrib, int newvalue) -{ - unsigned short *setfield; - struct video_picture vid_pic; - bzero(&vid_pic, sizeof(vid_pic)); - - if (ioctl(videofd, VIDIOCGPICT, &vid_pic) < 0) - { - VERBOSE(VB_IMPORTANT, "set_v4l1_attribute_value: " - "failed to get picture control." + ENO); - return -1; - } - setfield = get_v4l1_field(v4l2_attrib, vid_pic); - if (newvalue != -1 && setfield) - { - *setfield = newvalue; - if (ioctl(videofd, VIDIOCSPICT, &vid_pic) < 0) - { - VERBOSE(VB_IMPORTANT, "set_v4l1_attribute_value: " - "failed to set picture control." + ENO); - return -1; - } - } - else - { - // ??? - return -1; - } - - return 0; -} - -static int set_attribute_value(bool usingv4l2, int videofd, - int v4l2_attrib, int newvalue) -{ - if (usingv4l2) - return set_v4l2_attribute_value(videofd, v4l2_attrib, newvalue); - return set_v4l1_attribute_value(videofd, v4l2_attrib, newvalue); -} - -int Channel::ChangePictureAttribute( - PictureAdjustType type, PictureAttribute attr, bool up) -{ - if (!pParent || is_dtv) - return -1; - - QString db_col_name = toDBString(attr); - if (db_col_name.isEmpty()) - return -1; - - int v4l2_attrib = get_v4l2_attribute(db_col_name); - if (v4l2_attrib == -1) - return -1; - - // get the old attribute value from the hardware, this is - // just a sanity check on whether this attribute exists - if (get_attribute_value(usingv4l2, videofd, v4l2_attrib) < 0) - return -1; - - int old_value = GetPictureAttribute(attr); - int new_value = old_value + ((up) ? 655 : -655); - - // make sure we are within bounds (wrap around for hue) - if (V4L2_CID_HUE == v4l2_attrib) - new_value &= 0xffff; - new_value = min(max(new_value, 0), 65535); - -#if DEBUG_ATTRIB - VERBOSE(VB_CHANNEL, QString( - "ChangePictureAttribute(%1,%2,%3) cur %4 -> new %5") - .arg(type).arg(db_col_name).arg(up) - .arg(old_value).arg(new_value)); -#endif - - // actually set the new attribute value on the hardware - if (set_attribute_value(usingv4l2, videofd, v4l2_attrib, new_value) < 0) - return -1; - - // tell the DB about the new attribute value - if (kAdjustingPicture_Channel == type) - { - int adj_value = ChannelUtil::GetChannelValueInt( - db_col_name, GetCurrentSourceID(), curchannelname); - - int tmp = new_value - old_value + adj_value; - tmp = (tmp < 0) ? tmp + 0x10000 : tmp; - tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp; - ChannelUtil::SetChannelValue(db_col_name, QString::number(tmp), - GetCurrentSourceID(), curchannelname); - } - else if (kAdjustingPicture_Recording == type) - { - int adj_value = CardUtil::GetValueInt( - db_col_name, GetCardID()); - - int tmp = new_value - old_value + adj_value; - tmp = (tmp < 0) ? tmp + 0x10000 : tmp; - tmp = (tmp > 0xffff) ? tmp - 0x10000 : tmp; - CardUtil::SetValue(db_col_name, GetCardID(), - GetCurrentSourceID(), tmp); - } - - return new_value; -}