341 lines
13 KiB
C++
341 lines
13 KiB
C++
|
|
#ifndef VIDEO_PLAYER_H
|
|
#define VIDEO_PLAYER_H
|
|
|
|
#include "sys_inc.h"
|
|
#include "hqueue.h"
|
|
#include "video_decoder.h"
|
|
#include "audio_decoder.h"
|
|
#include "audio_play.h"
|
|
#include "avi_write.h"
|
|
#include "media_util.h"
|
|
#include <list>
|
|
#include <string>
|
|
#include <opencv2/imgproc.hpp>
|
|
#include <opencv2/highgui.hpp>
|
|
#include <opencv2/opencv.hpp>
|
|
#include <turbojpeg.h>
|
|
|
|
typedef struct
|
|
{
|
|
uint32 SyncTimestamp;
|
|
struct timeval SyncTime;
|
|
} HTCLOCK;
|
|
|
|
typedef std::list<AVFrame*> FRAMELIST;
|
|
|
|
// Thread-safe queue for AVFrame
|
|
class FrameQueue {
|
|
private:
|
|
std::queue<AVFrame*> frameQueue;
|
|
std::mutex queueMutex;
|
|
const size_t maxFrames = 20; // Increased buffer size for better performance
|
|
uint64_t m_frameSeq = 0; // Sequence counter — increments on each new frame push
|
|
|
|
public:
|
|
// Push a new frame to the queue (removes oldest if full)
|
|
void pushFrame(AVFrame* newFrame) {
|
|
if (!newFrame) return;
|
|
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
|
|
// Clone the frame to ensure proper ownership transfer
|
|
AVFrame* frameCopy = av_frame_clone(newFrame);
|
|
if (!frameCopy) {
|
|
std::cerr << "Failed to clone AVFrame!" << std::endl;
|
|
return;
|
|
}
|
|
|
|
frameQueue.push(frameCopy);
|
|
m_frameSeq++; // New frame arrived
|
|
|
|
// If queue exceeds max size, remove the oldest frame
|
|
if (frameQueue.size() > maxFrames) {
|
|
AVFrame* oldFrame = frameQueue.front();
|
|
frameQueue.pop();
|
|
av_frame_free(&oldFrame);
|
|
}
|
|
}
|
|
|
|
// Get current sequence number (check if new frames arrived)
|
|
uint64_t getSequence() {
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
return m_frameSeq;
|
|
}
|
|
|
|
// Retrieve latest frame (returns a clone for thread safety)
|
|
AVFrame* getLatestFrame() {
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
|
|
if (frameQueue.empty()) {
|
|
return nullptr; // No frames available
|
|
}
|
|
|
|
// Clone the latest frame before returning it
|
|
return av_frame_clone(frameQueue.back());
|
|
}
|
|
|
|
// Retrieve and remove the oldest frame from the queue
|
|
AVFrame* popFrame() {
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
|
|
if (frameQueue.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
AVFrame* frontFrame = frameQueue.front();
|
|
frameQueue.pop();
|
|
return frontFrame; // Caller must free the returned frame
|
|
}
|
|
|
|
// Check if the queue is empty
|
|
bool isEmpty() {
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
return frameQueue.empty();
|
|
}
|
|
|
|
// Clear the queue (e.g., when shutting down)
|
|
void clearQueue() {
|
|
std::lock_guard<std::mutex> lock(queueMutex);
|
|
while (!frameQueue.empty()) {
|
|
AVFrame* frame = frameQueue.front();
|
|
frameQueue.pop();
|
|
av_frame_free(&frame);
|
|
}
|
|
m_frameSeq = 0;
|
|
}
|
|
};
|
|
|
|
class CVideoPlayer
|
|
{
|
|
|
|
public:
|
|
CVideoPlayer();
|
|
virtual ~CVideoPlayer();
|
|
virtual BOOL open(std::string fileName);
|
|
virtual BOOL open(std::string username, std::string password, std::string url);
|
|
virtual BOOL play() = 0;
|
|
virtual void stop() = 0;
|
|
virtual BOOL pause() = 0;
|
|
virtual void close();
|
|
virtual BOOL seek(int pos) { return FALSE; }
|
|
virtual BOOL isPlaying() { return m_bPlaying; }
|
|
virtual BOOL isPaused() { return m_bPaused; }
|
|
virtual void setVolume(int volume);
|
|
virtual void snapshot(int videofmt);
|
|
virtual BOOL record(std::string baseName);
|
|
virtual void stopRecord();
|
|
virtual BOOL isRecording() { return m_bRecording; }
|
|
virtual BOOL onRecord() { return FALSE; }
|
|
virtual int getVideoClock() { return 1000; }
|
|
virtual int getAudioClock() { return 1000; }
|
|
//virtual void setWindowSize(int size);
|
|
virtual int64 getElapse() { return 0; }
|
|
virtual int64 getDuration() { return 0; }
|
|
virtual int getVideoCodec() { return m_nVideoCodec; }
|
|
virtual int getAudioCodec() { return m_nAudioCodec; }
|
|
|
|
virtual void setAuthInfo(std::string acct, std::string pass) { m_acct = acct; m_pass = pass; }
|
|
//virtual void setRenderMode(int mode) { m_nRenderMode = mode; }
|
|
virtual void setHWDecoding(int mode, int preferredGpu = -1) { m_nHWDecoding = mode; m_nPreferredGpu = preferredGpu; }
|
|
virtual bool isHWDecodingActive() const {
|
|
return m_pVideoDecoder && m_pVideoDecoder->isHardwareDecoderEnabled();
|
|
}
|
|
virtual int getHWDecodingGpuIndex() const {
|
|
return m_pVideoDecoder ? m_pVideoDecoder->getHWGpuIndex() : -1;
|
|
}
|
|
// Image quality mode: 0=fast (OpenCV BT.601, ~2ms), 1=quality (sws BT.709+range, ~12ms)
|
|
virtual void setImageQuality(int mode) { m_nImageQuality = mode; }
|
|
virtual void setRtpMulticast(BOOL flag) {}
|
|
virtual void setRtpOverUdp(BOOL flag) {}
|
|
|
|
#ifdef OVER_HTTP
|
|
virtual void setRtspOverHttp(int flag, int port) {}
|
|
#endif
|
|
|
|
#ifdef OVER_WEBSOCKET
|
|
virtual void setRtspOverWs(int flag, int port) {}
|
|
#endif
|
|
|
|
#ifdef BACKCHANNEL
|
|
virtual int getBCFlag() { return 0; }
|
|
virtual void setBCFlag(int flag) {}
|
|
virtual int getBCDataFlag() { return 0; }
|
|
virtual void setBCDataFlag(int flag) {}
|
|
virtual void setAudioDevice(int index) {}
|
|
#endif
|
|
|
|
#ifdef REPLAY
|
|
virtual int getReplayFlag() { return 0; }
|
|
virtual void setReplayFlag(int flag) {}
|
|
virtual void setScale(double scale) {}
|
|
virtual void setRateControlFlag(int flag) {}
|
|
virtual void setImmediateFlag(int flag) {}
|
|
virtual void setFramesFlag(int flag, int interval) {}
|
|
virtual void setReplayRange(time_t start, time_t end) {}
|
|
#endif
|
|
|
|
BOOL openVideo(int codec, uint8* extradata = NULL, int extradata_size = 0);
|
|
BOOL openVideo(enum AVCodecID codec, uint8* extradata = NULL, int extradata_size = 0);
|
|
void closeVideo();
|
|
BOOL openAudio(int codec, int samplerate, int channels, int bitpersample = 0);
|
|
BOOL openAudio(enum AVCodecID codec, int samplerate, int channels, int bitpersample = 0);
|
|
void closeAudio();
|
|
void enableAudio(bool status);
|
|
void playVideo(uint8* data, int len, uint32 ts, uint16 seq);
|
|
void playAudio(uint8* data, int len, uint32 ts, uint16 seq);
|
|
|
|
void recordVideo(uint8* data, int len, uint32 ts, uint16 seq);
|
|
void recordAudio(uint8* data, int len, uint32 ts, uint16 seq);
|
|
|
|
int getVideoWidth();
|
|
int getVideoHeight();
|
|
int getOriginalWidth() { return m_nv12OrigWidth; } // Full NV12 resolution (before display downscale)
|
|
int getOriginalHeight() { return m_nv12OrigHeight; } // Full NV12 resolution (before display downscale)
|
|
double getFrameRate();
|
|
int getSampleRate() { return m_nSampleRate; }
|
|
int getChannel() { return m_nChannel; }
|
|
|
|
void onVideoFrame(AVFrame* frame);
|
|
void onAudioFrame(AVFrame* frame);
|
|
void StartVideoDecoder();
|
|
void StopVideoDecoder();
|
|
|
|
|
|
cv::Mat getImage(int& width, int& height, int64_t& pts);// Get image
|
|
std::string getJpegImage(int& width, int& height, int64_t& pts);// Get image
|
|
AVFrame* getNV12Frame(); // Transfers ownership of NV12/YUV frame for GPU fast-path (caller must av_frame_free)
|
|
AVFrame* getCudaHWFrame(); // Returns clone of CUDA HW frame (device ptrs); caller must av_frame_free
|
|
bool isCudaHWAccel() const;
|
|
void setBbox(cv::Rect bbox);
|
|
void setCrop(bool crop);
|
|
protected:
|
|
void updateClock(HTCLOCK* clock, uint32 ts, int frequency);
|
|
BOOL initFrame(AVFrame*& frame, int width, int height, AVPixelFormat pixfmt);
|
|
BOOL doSnapshot(AVFrame* srcframe);
|
|
AVFrame* convertFrame(AVFrame* srcframe, AVFrame* dstframe, BOOL updown);
|
|
void recordVideoEx(uint8* data, int len, uint32 ts, uint16 seq);
|
|
BOOL recordSwitchCheck();
|
|
void recordFileSwitch();
|
|
AVFrame* cropFrame(const AVFrame* srcFrame, cv::Rect bBox, bool cropFlag);
|
|
|
|
cv::Mat avframeAnyToCvmat(const AVFrame* frame);
|
|
cv::Mat avframeNV12ToCvMat(const AVFrame* frame);
|
|
cv::Mat avframeYUVJ420PToCvmat(const AVFrame* frame);
|
|
cv::Mat avframeToCVMat(const AVFrame* frame);
|
|
|
|
// YUVJ420P to JPEG
|
|
std::string avframeYUVJ420PToJpegString(const AVFrame* pFrame);
|
|
std::string avframeYUVJ420PToJpegStringUsingTurboJPEG(const AVFrame* pFrame);
|
|
std::string avframeYUVJ420PToJpegStringUsingFFMpeg(const AVFrame* pFrame);
|
|
AVFrame* convertNV12ToYUVJ420P(const AVFrame* nv12Frame);
|
|
std::string encodeYUVJ420PToJPEG(AVFrame* frame, int quality = 90);
|
|
|
|
//Direct conversion from NV12 to JPEG
|
|
std::string avframeToJpegString(const AVFrame* pFrame);
|
|
std::string encodeNV12ToJPEG_TurboJPEG(const AVFrame* frame, int quality = 90);
|
|
std::string encodeNV12ToJPEG_FFmpeg(const AVFrame* pFrame, int quality = 90);
|
|
|
|
|
|
// Check if frame are identical
|
|
bool areFramesIdentical(AVFrame* frame1, AVFrame* frame2);
|
|
protected:
|
|
// Use a constant for the maximum queue size
|
|
//const int MAX_VIDEO_FRAMES = 10;
|
|
//const int MAX_AUDIO_FRAMES = 2;
|
|
BOOL m_bVideoInited;
|
|
BOOL m_bAudioInited;
|
|
tjhandle _tjInstance;
|
|
|
|
std::recursive_mutex _mutex;
|
|
|
|
std::unique_ptr<CVideoDecoder> m_pVideoDecoder = std::make_unique<CVideoDecoder>();
|
|
std::unique_ptr<CAudioDecoder> m_pAudioDecoder = std::make_unique<CAudioDecoder>();
|
|
std::unique_ptr<CAudioPlay> m_pAudioPlay = nullptr;
|
|
|
|
cv::Mat m_currentImage;
|
|
AVFrame* m_currentNV12Frame = nullptr; // Preserved NV12/YUV frame for GPU fast-path
|
|
AVFrame* m_currentCudaHWFrame = nullptr; // CUDA HW frame with device ptrs for zero-copy
|
|
int m_Width;
|
|
int m_Height;
|
|
int m_nv12OrigWidth = 0; // Original NV12 frame width (before display resize)
|
|
int m_nv12OrigHeight = 0; // Original NV12 frame height (before display resize)
|
|
int64_t m_pts;
|
|
uint64_t m_lastFrameSeq = 0; // Last processed frame sequence number
|
|
bool m_bWaitingForKeyframe = true; // Skip frames until first keyframe after start/restart
|
|
int m_cleanFrameCount = 0; // Count of clean frames after keyframe
|
|
static const int SETTLE_FRAME_COUNT = 5; // Number of clean frames before delivering new frames
|
|
|
|
BOOL m_bPlaying;
|
|
BOOL m_bPaused;
|
|
|
|
std::string m_acct;
|
|
std::string m_pass;
|
|
std::string m_sFileName;
|
|
std::string m_sBaseName;
|
|
std::string m_jpegImage;
|
|
std::string m_lastJpegImage;
|
|
|
|
int m_nHWDecoding;
|
|
int m_nPreferredGpu = -1; // -1=auto (least-loaded), >=0 = prefer this GPU for NVDEC
|
|
int m_nImageQuality = 0; // 0=fast (default), 1=quality BT.709
|
|
//AVPixelFormat m_nDstVideoFmt;
|
|
BOOL m_bUpdown;
|
|
BOOL m_bSnapshot;
|
|
int m_nSnapVideoFmt;
|
|
|
|
H26XParamSets m_h26XParamSets;
|
|
|
|
int m_nVideoCodec;
|
|
int m_nAudioCodec;
|
|
int m_nSampleRate;
|
|
int m_nChannel;
|
|
int m_nBitPerSample;
|
|
|
|
AVFrame* m_pSnapFrame;
|
|
FrameQueue g_frameQueue;
|
|
FrameQueue a_frameQueue;
|
|
|
|
BOOL m_bRecording;
|
|
BOOL m_bNalFlag;
|
|
AVICTX* m_pAviCtx;
|
|
void* m_pRecordMutex;
|
|
|
|
HTCLOCK m_audioClock;
|
|
void* m_pAudioListMutex;
|
|
//FRAMELIST m_audioFrameList;
|
|
BOOL m_audioPlayFlag;
|
|
//pthread_t m_audioPlayThread;
|
|
|
|
HTCLOCK m_videoClock;
|
|
void* m_pVideoListMutex;
|
|
//FRAMELIST m_videoFrameList;
|
|
BOOL m_videoPlayFlag;
|
|
//pthread_t m_videoPlayThread;
|
|
|
|
uint64 m_nLastAudioPts;
|
|
time_t m_lastAudioTS;
|
|
cv::Rect m_Bbox;
|
|
bool m_bCrop;
|
|
SwsContext* swsCtx = nullptr; // Shared SwsContext
|
|
int lastWidth = 0, lastHeight = 0;
|
|
AVPixelFormat lastPixFmt = AV_PIX_FMT_NONE;
|
|
AVPixelFormat lastOutPixFmt = AV_PIX_FMT_NONE;
|
|
|
|
// Dedicated NV12→BGR context with correct color space (BT.709 for HD/4K)
|
|
SwsContext* m_nv12SwsCtx = nullptr;
|
|
int m_nv12LastWidth = 0, m_nv12LastHeight = 0;
|
|
int m_nv12LastColorspace = -1;
|
|
int m_nv12LastRange = -1;
|
|
|
|
void initSwsContext(int width, int height, AVPixelFormat pixFmt, AVPixelFormat outputPixFmt = AV_PIX_FMT_YUVJ420P);
|
|
void initNV12SwsContext(const AVFrame* frame);
|
|
void cropPlane(const AVFrame* srcFrame, AVFrame* croppedFrame, int planeIndex, int offsetX, int offsetY, int subsampleX, int subsampleY);
|
|
bool cropFrameData(const AVFrame* srcFrame, AVFrame* croppedFrame, const cv::Rect& bBox);
|
|
};
|
|
|
|
#endif // end of VIDEO_PLAYER_H
|
|
|
|
|
|
|