/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2017 Edouard Griffiths, F4EXB // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU General Public License as published by // // the Free Software Foundation as version 3 of the License, or // // // // This program is distributed in the hope that it will be useful, // // but WITHOUT ANY WARRANTY; without even the implied warranty of // // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // // GNU General Public License V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #ifndef PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ #define PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ #include #include #include #include #include #include #include "dsp/basebandsamplesource.h" #include "dsp/nco.h" #include "dsp/interpolator.h" #include "dsp/movingaverage.h" #include "dsp/fftfilt.h" #include "util/message.h" class ATVMod : public BasebandSampleSource { Q_OBJECT public: typedef enum { ATVStdPAL625, ATVStdPAL525, ATVStd525L20F, ATVStd405 } ATVStd; typedef enum { ATVModInputUniform, ATVModInputHBars, ATVModInputVBars, ATVModInputChessboard, ATVModInputHGradient, ATVModInputVGradient, ATVModInputImage, ATVModInputVideo, ATVModInputCamera } ATVModInput; typedef enum { ATVModulationAM, ATVModulationFM, ATVModulationUSB, ATVModulationLSB, ATVModulationVestigialUSB, ATVModulationVestigialLSB } ATVModulation; class MsgConfigureImageFileName : public Message { MESSAGE_CLASS_DECLARATION public: const QString& getFileName() const { return m_fileName; } static MsgConfigureImageFileName* create(const QString& fileName) { return new MsgConfigureImageFileName(fileName); } private: QString m_fileName; MsgConfigureImageFileName(const QString& fileName) : Message(), m_fileName(fileName) { } }; class MsgConfigureVideoFileName : public Message { MESSAGE_CLASS_DECLARATION public: const QString& getFileName() const { return m_fileName; } static MsgConfigureVideoFileName* create(const QString& fileName) { return new MsgConfigureVideoFileName(fileName); } private: QString m_fileName; MsgConfigureVideoFileName(const QString& fileName) : Message(), m_fileName(fileName) { } }; class MsgConfigureVideoFileSourceSeek : public Message { MESSAGE_CLASS_DECLARATION public: int getPercentage() const { return m_seekPercentage; } static MsgConfigureVideoFileSourceSeek* create(int seekPercentage) { return new MsgConfigureVideoFileSourceSeek(seekPercentage); } protected: int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 MsgConfigureVideoFileSourceSeek(int seekPercentage) : Message(), m_seekPercentage(seekPercentage) { } }; class MsgConfigureVideoFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION public: static MsgConfigureVideoFileSourceStreamTiming* create() { return new MsgConfigureVideoFileSourceStreamTiming(); } private: MsgConfigureVideoFileSourceStreamTiming() : Message() { } }; class MsgReportVideoFileSourceStreamTiming : public Message { MESSAGE_CLASS_DECLARATION public: int getFrameCount() const { return m_frameCount; } static MsgReportVideoFileSourceStreamTiming* create(int frameCount) { return new MsgReportVideoFileSourceStreamTiming(frameCount); } protected: int m_frameCount; MsgReportVideoFileSourceStreamTiming(int frameCount) : Message(), m_frameCount(frameCount) { } }; class MsgReportVideoFileSourceStreamData : public Message { MESSAGE_CLASS_DECLARATION public: int getFrameRate() const { return m_frameRate; } quint32 getVideoLength() const { return m_videoLength; } static MsgReportVideoFileSourceStreamData* create(int frameRate, quint32 recordLength) { return new MsgReportVideoFileSourceStreamData(frameRate, recordLength); } protected: int m_frameRate; int m_videoLength; //!< Video length in frames MsgReportVideoFileSourceStreamData(int frameRate, int videoLength) : Message(), m_frameRate(frameRate), m_videoLength(videoLength) { } }; class MsgConfigureCameraIndex : public Message { MESSAGE_CLASS_DECLARATION public: int getIndex() const { return m_index; } static MsgConfigureCameraIndex* create(int index) { return new MsgConfigureCameraIndex(index); } private: int m_index; MsgConfigureCameraIndex(int index) : Message(), m_index(index) { } }; class MsgReportCameraData : public Message { MESSAGE_CLASS_DECLARATION public: int getdeviceNumber() const { return m_deviceNumber; } float getFPS() const { return m_fps; } int getWidth() const { return m_width; } int getHeight() const { return m_height; } int getStatus() const { return m_status; } static MsgReportCameraData* create( int deviceNumber, float fps, int width, int height, int status) { return new MsgReportCameraData( deviceNumber, fps, width, height, status); } protected: int m_deviceNumber; float m_fps; int m_width; int m_height; int m_status; MsgReportCameraData( int deviceNumber, float fps, int width, int height, int status) : Message(), m_deviceNumber(deviceNumber), m_fps(fps), m_width(width), m_height(height), m_status(status) { } }; class MsgConfigureOverlayText : public Message { MESSAGE_CLASS_DECLARATION public: const QString& getOverlayText() const { return m_overlayText; } static MsgConfigureOverlayText* create(const QString& overlayText) { return new MsgConfigureOverlayText(overlayText); } private: QString m_overlayText; MsgConfigureOverlayText(const QString& overlayText) : Message(), m_overlayText(overlayText) { } }; class MsgConfigureShowOverlayText : public Message { MESSAGE_CLASS_DECLARATION public: bool getShowOverlayText() const { return m_showOverlayText; } static MsgConfigureShowOverlayText* create(bool showOverlayText) { return new MsgConfigureShowOverlayText(showOverlayText); } private: bool m_showOverlayText; MsgConfigureShowOverlayText(bool showOverlayText) : Message(), m_showOverlayText(showOverlayText) { } }; class MsgReportEffectiveSampleRate : public Message { MESSAGE_CLASS_DECLARATION public: int getSampleRate() const { return m_sampleRate; } static MsgReportEffectiveSampleRate* create(int sampleRate) { return new MsgReportEffectiveSampleRate(sampleRate); } protected: int m_sampleRate; MsgReportEffectiveSampleRate(int sampleRate) : Message(), m_sampleRate(sampleRate) { } }; ATVMod(); ~ATVMod(); void configure(MessageQueue* messageQueue, Real rfBandwidth, Real rfOppBandwidth, ATVStd atvStd, int nbLines, int fps, ATVModInput atvModInput, Real uniformLevel, ATVModulation atvModulation, bool videoPlayLoop, bool videoPlay, bool cameraPLay, bool channelMute, bool invertedVideo, float rfScaling, float fmExcursion); virtual void pull(Sample& sample); virtual void pullAudio(int nbSamples); // this is used for video signal actually virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& cmd); int getEffectiveSampleRate() const { return m_tvSampleRate; }; Real getMagSq() const { return m_movingAverage.average(); } void getCameraNumbers(std::vector& numbers); static void getBaseValues(int linesPerSecond, int& sampleRateUnits, int& nbPointsPerRateUnit); static float getRFBandwidthDivisor(ATVModulation modulation); signals: /** * Level changed * \param rmsLevel RMS level in range 0.0 - 1.0 * \param peakLevel Peak level in range 0.0 - 1.0 * \param numSamples Number of audio samples analyzed */ void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); private: class MsgConfigureATVMod : public Message { MESSAGE_CLASS_DECLARATION public: Real getRFBandwidth() const { return m_rfBandwidth; } Real getRFOppBandwidth() const { return m_rfOppBandwidth; } ATVStd getATVStd() const { return m_atvStd; } ATVModInput getATVModInput() const { return m_atvModInput; } int getNbLines() const { return m_nbLines; } int getFPS() const { return m_fps; } Real getUniformLevel() const { return m_uniformLevel; } ATVModulation getModulation() const { return m_atvModulation; } bool getVideoPlayLoop() const { return m_videoPlayLoop; } bool getVideoPlay() const { return m_videoPlay; } bool getCameraPlay() const { return m_cameraPlay; } bool getChannelMute() const { return m_channelMute; } bool getInvertedVideo() const { return m_invertedVideo; } float getRFScaling() const { return m_rfScaling; } float getFMExcursion() const { return m_fmExcursion; } static MsgConfigureATVMod* create( Real rfBandwidth, Real rfOppBandwidth, ATVStd atvStd, int nbLines, int fps, ATVModInput atvModInput, Real uniformLevel, ATVModulation atvModulation, bool videoPlayLoop, bool videoPlay, bool cameraPlay, bool channelMute, bool invertedVideo, float rfScaling, float fmExcursion) { return new MsgConfigureATVMod( rfBandwidth, rfOppBandwidth, atvStd, nbLines, fps, atvModInput, uniformLevel, atvModulation, videoPlayLoop, videoPlay, cameraPlay, channelMute, invertedVideo, rfScaling, fmExcursion); } private: Real m_rfBandwidth; Real m_rfOppBandwidth; ATVStd m_atvStd; int m_nbLines; int m_fps; ATVModInput m_atvModInput; Real m_uniformLevel; ATVModulation m_atvModulation; bool m_videoPlayLoop; bool m_videoPlay; bool m_cameraPlay; bool m_channelMute; bool m_invertedVideo; float m_rfScaling; float m_fmExcursion; MsgConfigureATVMod( Real rfBandwidth, Real rfOppBandwidth, ATVStd atvStd, int nbLines, int fps, ATVModInput atvModInput, Real uniformLevel, ATVModulation atvModulation, bool videoPlayLoop, bool videoPlay, bool cameraPlay, bool channelMute, bool invertedVideo, float rfScaling, float fmExcursion) : Message(), m_rfBandwidth(rfBandwidth), m_rfOppBandwidth(rfOppBandwidth), m_atvStd(atvStd), m_nbLines(nbLines), m_fps(fps), m_atvModInput(atvModInput), m_uniformLevel(uniformLevel), m_atvModulation(atvModulation), m_videoPlayLoop(videoPlayLoop), m_videoPlay(videoPlay), m_cameraPlay(cameraPlay), m_channelMute(channelMute), m_invertedVideo(invertedVideo), m_rfScaling(rfScaling), m_fmExcursion(fmExcursion) { } }; struct ATVCamera { cv::VideoCapture m_camera; //!< camera object cv::Mat m_videoframeOriginal; //!< camera non resized image cv::Mat m_videoFrame; //!< displayable camera frame int m_cameraNumber; //!< camera device number float m_videoFPS; //!< camera FPS rate int m_videoWidth; //!< camera frame width int m_videoHeight; //!< camera frame height float m_videoFx; //!< camera horizontal scaling factor float m_videoFy; //!< camera vertictal scaling factor float m_videoFPSq; //!< camera FPS sacaling factor float m_videoFPSCount; //!< camera FPS fractional counter int m_videoPrevFPSCount; //!< camera FPS previous integer counter ATVCamera() : m_cameraNumber(-1), m_videoFPS(25.0f), m_videoWidth(1), m_videoHeight(1), m_videoFx(1.0f), m_videoFy(1.0f), m_videoFPSq(1.0f), m_videoFPSCount(0.0f), m_videoPrevFPSCount(0) {} }; struct Config { int m_outputSampleRate; //!< sample rate from channelizer qint64 m_inputFrequencyOffset; //!< offset from baseband center frequency Real m_rfBandwidth; //!< Bandwidth of modulated signal or direct sideband for SSB / vestigial SSB Real m_rfOppBandwidth; //!< Bandwidth of opposite sideband for vestigial SSB ATVStd m_atvStd; //!< Standard int m_nbLines; //!< Number of lines per full frame int m_fps; //!< Number of frames per second ATVModInput m_atvModInput; //!< Input source type Real m_uniformLevel; //!< Percentage between black and white for uniform screen display ATVModulation m_atvModulation; //!< RF modulation type bool m_videoPlayLoop; //!< Play video in a loop bool m_videoPlay; //!< True to play video and false to pause bool m_cameraPlay; //!< True to play camera video and false to pause bool m_channelMute; //!< Mute channel baseband output bool m_invertedVideo; //!< True if video signal is inverted before modulation float m_rfScalingFactor; //!< Scaling factor from +/-1 to +/-2^15 float m_fmExcursion; //!< FM excursion factor relative to full bandwidth Config() : m_outputSampleRate(-1), m_inputFrequencyOffset(0), m_rfBandwidth(0), m_rfOppBandwidth(0), m_atvStd(ATVStdPAL625), m_nbLines(625), m_fps(25), m_atvModInput(ATVModInputHBars), m_uniformLevel(0.5f), m_atvModulation(ATVModulationAM), m_videoPlayLoop(false), m_videoPlay(false), m_cameraPlay(false), m_channelMute(false), m_invertedVideo(false), m_rfScalingFactor(29204.0f), // -1dB m_fmExcursion(0.5f) // half bandwidth { } }; Config m_config; Config m_running; NCO m_carrierNco; Complex m_modSample; float m_modPhasor; //!< For FM modulation Interpolator m_interpolator; Real m_interpolatorDistance; Real m_interpolatorDistanceRemain; int m_tvSampleRate; //!< sample rate for generating signal uint32_t m_pointsPerSync; //!< number of line points for the horizontal sync uint32_t m_pointsPerBP; //!< number of line points for the back porch uint32_t m_pointsPerImgLine; //!< number of line points for the image line uint32_t m_pointsPerFP; //!< number of line points for the front porch uint32_t m_pointsPerFSync; //!< number of line points for the field first sync uint32_t m_pointsPerHBar; //!< number of line points for a bar of the bar chart uint32_t m_linesPerVBar; //!< number of lines for a bar of the bar chart uint32_t m_pointsPerTU; //!< number of line points per time unit uint32_t m_nbLines; //!< number of lines per complete frame uint32_t m_nbLines2; //!< same number as above (non interlaced) or half the number above (interlaced) uint32_t m_nbImageLines; //!< number of image lines excluding synchronization lines uint32_t m_nbImageLines2; //!< same number as above (non interlaced) or half the number above (interlaced) uint32_t m_nbHorizPoints; //!< number of line points per horizontal line uint32_t m_nbSyncLinesHead; //!< number of header sync lines uint32_t m_nbBlankLines; //!< number of lines in a frame (full or half) that are blanked (black) at the top of the image float m_hBarIncrement; //!< video level increment at each horizontal bar increment float m_vBarIncrement; //!< video level increment at each vertical bar increment bool m_interlaced; //!< true if image is interlaced (2 half frames per frame) bool m_evenImage; //!< in interlaced mode true if this is an even image QMutex m_settingsMutex; int m_horizontalCount; //!< current point index on line int m_lineCount; //!< current line index in frame float m_fps; //!< resulting frames per second MovingAverage m_movingAverage; quint32 m_levelCalcCount; Real m_peakLevel; Real m_levelSum; cv::Mat m_imageFromFile; //!< original image not resized not overlaid by text cv::Mat m_imageOriginal; //!< original not resized image cv::Mat m_image; //!< resized image for transmission at given rate bool m_imageOK; cv::VideoCapture m_video; //!< current video capture cv::Mat m_videoframeOriginal; //!< current frame from video cv::Mat m_videoFrame; //!< current displayable video frame float m_videoFPS; //!< current video FPS rate int m_videoWidth; //!< current video frame width int m_videoHeight; //!< current video frame height float m_videoFx; //!< current video horizontal scaling factor float m_videoFy; //!< current video vertictal scaling factor float m_videoFPSq; //!< current video FPS sacaling factor float m_videoFPSCount; //!< current video FPS fractional counter int m_videoPrevFPSCount; //!< current video FPS previous integer counter int m_videoLength; //!< current video length in frames bool m_videoEOF; //!< current video has reached end of file bool m_videoOK; std::vector m_cameras; //!< vector of available cameras int m_cameraIndex; //!< curent camera index in list of available cameras std::string m_overlayText; bool m_showOverlayText; // Used for standard SSB fftfilt* m_SSBFilter; Complex* m_SSBFilterBuffer; int m_SSBFilterBufferIndex; // Used for vestigial SSB with asymmetrical filtering (needs double sideband scheme) fftfilt* m_DSBFilter; Complex* m_DSBFilterBuffer; int m_DSBFilterBufferIndex; static const int m_ssbFftLen; static const float m_blackLevel; static const float m_spanLevel; static const int m_levelNbSamples; static const int m_nbBars; //!< number of bars in bar or chessboard patterns static const int m_cameraFPSTestNbFrames; //!< number of frames for camera FPS test void apply(bool force = false); void pullFinalize(Complex& ci, Sample& sample); void pullVideo(Real& sample); void calculateLevel(Real& sample); void modulateSample(); Complex& modulateSSB(Real& sample); Complex& modulateVestigialSSB(Real& sample); void applyStandard(); void openImage(const QString& fileName); void openVideo(const QString& fileName); void resizeImage(); void calculateVideoSizes(); void resizeVideo(); void seekVideoFileStream(int seekPercentage); void scanCameras(); void releaseCameras(); void calculateCamerasSizes(); void resizeCameras(); void resizeCamera(); void mixImageAndText(cv::Mat& image); inline void pullImageLine(Real& sample) { if (m_horizontalCount < m_pointsPerSync) // sync pulse { sample = 0.0f; // ultra-black } else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP) // back porch { sample = m_blackLevel; // black } else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP + m_pointsPerImgLine) { int pointIndex = m_horizontalCount - (m_pointsPerSync + m_pointsPerBP); int iLine = m_lineCount % m_nbLines2; int oddity = m_lineCount < m_nbLines2 ? 0 : 1; int iLineImage = iLine - m_nbSyncLinesHead - m_nbBlankLines; switch(m_running.m_atvModInput) { case ATVModInputHBars: sample = (pointIndex / m_pointsPerHBar) * m_hBarIncrement + m_blackLevel; break; case ATVModInputVBars: sample = (iLine / m_linesPerVBar) * m_vBarIncrement + m_blackLevel; break; case ATVModInputChessboard: sample = (((iLine / m_linesPerVBar)*5 + (pointIndex / m_pointsPerHBar)) % 2) * m_spanLevel * m_running.m_uniformLevel + m_blackLevel; break; case ATVModInputHGradient: sample = (pointIndex / (float) m_pointsPerImgLine) * m_spanLevel + m_blackLevel; break; case ATVModInputVGradient: sample = ((iLine -5) / (float) m_nbImageLines2) * m_spanLevel + m_blackLevel; break; case ATVModInputImage: if (!m_imageOK || (iLineImage < -oddity) || m_image.empty()) { sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; } else { unsigned char pixv; if (m_interlaced) { pixv = m_image.at(2*iLineImage + oddity, pointIndex); // row (y), col (x) } else { pixv = m_image.at(iLineImage, pointIndex); // row (y), col (x) } sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel; } break; case ATVModInputVideo: if (!m_videoOK || (iLineImage < -oddity) || m_videoFrame.empty()) { sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; } else { unsigned char pixv; if (m_interlaced) { pixv = m_videoFrame.at(2*iLineImage + oddity, pointIndex); // row (y), col (x) } else { pixv = m_videoFrame.at(iLineImage, pointIndex); // row (y), col (x) } sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel; } break; case ATVModInputCamera: if ((iLineImage < -oddity) || (m_cameraIndex < 0)) { sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; } else { ATVCamera& camera = m_cameras[m_cameraIndex]; if (camera.m_videoFrame.empty()) { sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; } else { unsigned char pixv; if (m_interlaced) { pixv = camera.m_videoFrame.at(2*iLineImage + oddity, pointIndex); // row (y), col (x) } else { pixv = camera.m_videoFrame.at(iLineImage, pointIndex); // row (y), col (x) } sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel; } } break; case ATVModInputUniform: default: sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; } } else // front porch { sample = m_blackLevel; // black } } inline void pullVSyncLine(Real& sample) { int fieldLine = m_lineCount % m_nbLines2; if (m_lineCount < m_nbLines2) // even { if (fieldLine < 2) // 0,1: Whole line "long" pulses { int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); if (halfIndex < (m_nbHorizPoints/2) - m_pointsPerSync) // ultra-black { sample = 0.0f; } else // black { sample = m_blackLevel; } } else if (fieldLine == 2) // long pulse then equalizing pulse { if (m_horizontalCount < (m_nbHorizPoints/2) - m_pointsPerSync) { sample = 0.0f; // ultra-black } else if (m_horizontalCount < (m_nbHorizPoints/2)) { sample = m_blackLevel; // black } else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync) { sample = 0.0f; // ultra-black } else { sample = m_blackLevel; // black } } else if ((fieldLine < 5) || (fieldLine > m_nbLines2 - 3)) // Whole line equalizing pulses { int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); if (halfIndex < m_pointsPerFSync) // ultra-black { sample = 0.0f; } else // black { sample = m_blackLevel; } } else // black images { if (m_horizontalCount < m_pointsPerSync) { sample = 0.0f; } else { sample = m_blackLevel; } } } else // odd { if (fieldLine < 1) // equalizing pulse then long pulse { if (m_horizontalCount < m_pointsPerFSync) { sample = 0.0f; // ultra-black } else if (m_horizontalCount < (m_nbHorizPoints/2)) { sample = m_blackLevel; // black } else if (m_horizontalCount < m_nbHorizPoints - m_pointsPerSync) { sample = 0.0f; // ultra-black } else { sample = m_blackLevel; // black } } else if (fieldLine < 3) // Whole line "long" pulses { int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); if (halfIndex < (m_nbHorizPoints/2) - m_pointsPerSync) // ultra-black { sample = 0.0f; } else // black { sample = m_blackLevel; } } else if ((fieldLine < 5) || (fieldLine > m_nbLines2 - 5)) // Whole line equalizing pulses { int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); if (halfIndex < m_pointsPerFSync) // ultra-black { sample = 0.0f; } else // black { sample = m_blackLevel; } } else // black images { if (m_horizontalCount < m_pointsPerSync) { sample = 0.0f; } else { sample = m_blackLevel; } } } } }; #endif /* PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ */