//ffmpegDecode.h #ifndef __FFMPEG_DECODE_H__ #define __FFMPEG_DECODE_H__ #include "global.h" extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" //圖像轉換結構需要引入的頭文件 #include "libswscale/swscale.h" }; class ffmpegDecode { public: ffmpegDecode(char * file = NULL); ~ffmpegDecode(); cv::Mat getDecodedFrame(); cv::Mat getLastFrame(); int readOneFrame(); int getFrameIndex(){return m_framIndex;}; private: AVFrame *pAvFrame; AVFormatContext *pFormatCtx; AVCodecContext *pCodecCtx; AVCodec *pCodec; int i; int videoindex; int m_framIndex; char *filepath; int ret, got_picture; SwsContext *img_convert_ctx; int y_size; AVPacket *packet; cv::Mat *pCvMat; bool m_initResult; bool init(); bool openDecode(); void prepare(); void get(AVCodecContext *pCodecCtx, SwsContext *img_convert_ctx,AVFrame *pFrame); public: bool getInitResult(); }; #endif
//ffmpegDecode.cpp #include "ffmpegDecode.h" #include <QDebug> int time_out = 0; int firsttimeplay = 1; int interrupt_cb(void *ctx) { // do something time_out++; if (time_out > 40) { time_out=0; if (firsttimeplay) { firsttimeplay=0; return -1;//這個就是超時的返回 } } return 0; } ffmpegDecode :: ~ffmpegDecode() { pCvMat->release(); pCvMat->release(); //釋放本次讀取的幀內存 av_free_packet(packet); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); } ffmpegDecode :: ffmpegDecode(char * file) { firsttimeplay = 1; pAvFrame = NULL/**pFrameRGB = NULL*/; pFormatCtx = NULL; pCodecCtx = NULL; pCodec = NULL; pCvMat = new cv::Mat(); i=0; videoindex=0; m_framIndex =0; ret = 0; got_picture = 0; img_convert_ctx = NULL; y_size = 0; packet = NULL; if (NULL == file) { filepath = "rtsp://admin:admin123@192.168.10.239:554"; } else { filepath = file; } m_initResult = false; if(init()) { if(openDecode()) { prepare(); m_initResult =true; } } } bool ffmpegDecode::getInitResult() { return m_initResult; } bool ffmpegDecode :: init() { printf("init start...\n"); //ffmpeg注冊復用器,編碼器等的函數av_register_all()。 //該函數在所有基於ffmpeg的應用程序中幾乎都是第一個被調用的。只有調用了該函數,才能使用復用器,編碼器等。 //這里注冊了所有的文件格式和編解碼器的庫,所以它們將被自動的使用在被打開的合適格式的文件上。注意你只需要調用 av_register_all()一次,因此我們在主函數main()中來調用它。如果你喜歡,也可以只注冊特定的格式和編解碼器,但是通常你沒有必要這樣做。 av_register_all(); avformat_network_init(); // pFormatCtx = avformat_alloc_context(); // pFormatCtx->interrupt_callback.callback = interrupt_cb;//--------注冊回調函數 // pFormatCtx->interrupt_callback.opaque = pFormatCtx; //打開視頻文件,通過參數filepath來獲得文件名。這個函數讀取文件的頭部並且把信息保存到我們給的AVFormatContext結構體中。 //最后2個參數用來指定特殊的文件格式,緩沖大小和格式參數,但如果把它們設置為空NULL或者0,libavformat將自動檢測這些參數。 AVDictionary* options = NULL; av_dict_set(&options, "rtsp_transport", "tcp", 0); av_dict_set(&options, "stimeout", "2000000", 0); //設置超時斷開連接時間,單位微秒 if(avformat_open_input(&pFormatCtx,filepath,NULL,&options)!=0) { printf("無法打開文件\n"); return false; } //查找文件的流信息,avformat_open_input函數只是檢測了文件的頭部,接着要檢查在文件中的流的信息 if(avformat_find_stream_info(pFormatCtx,&options)<0) { printf("Couldn't find stream information.\n"); return false; } printf("init finished...\n"); return true; } bool ffmpegDecode :: openDecode() { printf("openDecode start...\n"); //在庫里面查找支持該格式的解碼器 videoindex = -1; for(i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) { videoindex=i; break; } } if(videoindex==-1) { printf("Didn't find a video stream.\n"); return false; } pCodecCtx=pFormatCtx->streams[videoindex]->codec; //在庫里面查找支持該格式的解碼器 pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { printf("Codec not found.\n"); return false; } //打開解碼器 if(avcodec_open2(pCodecCtx, pCodec,NULL) < 0) { printf("Could not open codec.\n"); return false; } printf("openDecode finished\n"); return true; } void ffmpegDecode :: prepare() { //printf("prepare int\n"); //分配一個幀指針,指向解碼后的原始幀 pAvFrame=av_frame_alloc(); y_size = pCodecCtx->width * pCodecCtx->height; //分配幀內存 packet=(AVPacket *)av_malloc(sizeof(AVPacket)); av_new_packet(packet, y_size); //輸出一下信息----------------------------- printf("文件信息-----------------------------------------\n"); av_dump_format(pFormatCtx,0,filepath,0); //av_dump_format只是個調試函數,輸出文件的音、視頻流的基本信息了,幀率、分辨率、音頻采樣等等 //printf("prepare out\n"); } int ffmpegDecode :: readOneFrame() { int result = 0; pCvMat->release(); result = av_read_frame(pFormatCtx, packet); return result; } cv::Mat ffmpegDecode :: getDecodedFrame() { readOneFrame(); if(packet->stream_index==videoindex) { //解碼一個幀 ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet); if(ret < 0) { printf("解碼錯誤\n"); return cv::Mat(); } if(got_picture) { m_framIndex++; //根據編碼信息設置渲染格式 if(img_convert_ctx == NULL){ img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); } //----------------------opencv if (pCvMat->empty()) { pCvMat->create(cv::Size(pCodecCtx->width, pCodecCtx->height),CV_8UC3); } if(img_convert_ctx != NULL) { get(pCodecCtx, img_convert_ctx, pAvFrame); } } } av_free_packet(packet); return *pCvMat; } cv::Mat ffmpegDecode :: getLastFrame() { ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet); if(got_picture) { //根據編碼信息設置渲染格式 img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); if(img_convert_ctx != NULL) { get(pCodecCtx, img_convert_ctx,pAvFrame); } } return *pCvMat; } void ffmpegDecode :: get(AVCodecContext * pCodecCtx, SwsContext * img_convert_ctx, AVFrame * pFrame) { if (pCvMat->empty()) { pCvMat->create(cv::Size(pCodecCtx->width, pCodecCtx->height),CV_8UC3); } AVFrame *pFrameRGB = NULL; uint8_t *out_bufferRGB = NULL; pFrameRGB = av_frame_alloc(); //給pFrameRGB幀加上分配的內存; int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); out_bufferRGB = new uint8_t[size]; avpicture_fill((AVPicture *)pFrameRGB, out_bufferRGB, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); //YUV to RGB sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); memcpy(pCvMat->data,out_bufferRGB,size); delete[] out_bufferRGB; av_free(pFrameRGB); }
//crtspdecodethread.h #ifndef CRTSPDECODETHREAD_H #define CRTSPDECODETHREAD_H #include <QThread> #include <QMutex> #include "ffmpegDecode.h" class CRTSPDecodeThread : public QThread { Q_OBJECT public: CRTSPDecodeThread(QObject* parent); ~CRTSPDecodeThread(); void SetCameraParam(QString, int, int cameraID); void run(); signals: void SendVideoFrame(cv::Mat); void SendDetectFrame(cv::Mat); private: ffmpegDecode* m_pVdoDecode; bool m_isExist; unsigned long m_FrameCount; int m_detectInterval; VideoCapture m_VideoCap; QString m_cameraURL; QMutex m_Mutex; int m_cameraID; bool m_decodeInitResult; }; #endif // CRTSPDECODETHREAD_H
//crtspdecodethread.cpp #include "crtspdecodethread.h" #include "ffmpegDecode.h" #include <QDebug> #include <QDateTime> #include <queue> #include <QMutexLocker> extern bool g_ImportLib; std::queue<ST_IMGINFO> g_OrgImgQueue; CRTSPDecodeThread::CRTSPDecodeThread(QObject* parent):QThread(parent) { m_isExist = false; } CRTSPDecodeThread::~CRTSPDecodeThread() { requestInterruption(); quit(); wait(); m_isExist = true; } void CRTSPDecodeThread::SetCameraParam(QString strURL, int iInterval, int cameraID) { m_cameraID = cameraID; m_detectInterval = iInterval; m_cameraURL =strURL; if(m_cameraURL == "USB") { bool bRet = m_VideoCap.open(0); if(!bRet) { qDebug()<<"打開USB攝像頭失敗..."; } } else { m_pVdoDecode = new ffmpegDecode((char*)strURL.toStdString().c_str()); m_decodeInitResult = m_pVdoDecode->getInitResult(); } } void CRTSPDecodeThread::run() { m_FrameCount = 0; cv::Mat img; unsigned long iRTSPOfflineTick = GetTickCount(); while(!isInterruptionRequested()) { if(m_isExist) { break; } if(m_cameraURL == "USB") { m_VideoCap>>img; } else { if(m_decodeInitResult) { img =m_pVdoDecode->getDecodedFrame(); } } if(!img.empty()) { m_FrameCount++; //cvtColor(img, img, COLOR_BGR2RGB); iRTSPOfflineTick = GetTickCount(); emit SendVideoFrame(img); if(m_FrameCount % m_detectInterval == 0) { ST_IMGINFO imgInfo; img.copyTo(imgInfo.img); imgInfo.camera_id =m_cameraID;// m_pManager->GetCameraID(); QDateTime dtTime; imgInfo.time = dtTime.currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); QMutexLocker lock(&m_Mutex); g_OrgImgQueue.push(imgInfo); } img.release(); } else { qDebug()<<"獲取原始視頻幀失敗..."; if( (GetTickCount() -iRTSPOfflineTick ) > 1000*15) { qDebug()<<"重新打開視頻流..."; iRTSPOfflineTick = GetTickCount(); if(m_cameraURL == "USB") { bool bRet = m_VideoCap.open(0); if(!bRet) { qDebug()<<"打開USB攝像頭失敗..."; } } else { delete m_pVdoDecode; m_pVdoDecode = NULL; m_pVdoDecode = new ffmpegDecode((char*)m_cameraURL.toStdString().c_str()); m_decodeInitResult = m_pVdoDecode->getInitResult(); } } } } }