文章目錄
- 1 前言
- 2 項目內容詳細說明
- 2.0 功能
- 2.1 工程文件夾說明
- 3 代碼
- 3.1 CameraThread類
- 3.1 CameraThreadImpl類
- 4 資源下載
1 前言
??在某項目中需要在RK3399平臺實現USB攝像頭畫面的實時預覽、視頻錄制、拍照存儲等功能。
??先來看需要實現的最終效果。
??
ffmpeg USB攝像頭 H264編碼錄視頻拍照
??本項目使用了搭載了RK3399芯片的嵌入式平臺,操作系統Debain10,調用ffmpeg-rkmpp庫進行H264硬編碼。
??本項目需要外接一個支持 2592x1944,1080p,720p,480p的USB攝像頭。
2 項目內容詳細說明
2.0 功能
??(1)實時預覽;
??(2)攝像頭分辨率設置,可設置為480p,720p,1080p;
??(3)編碼格式設置,可按照H264格式編碼,或直接存MJPEG;
??(4)存儲文件格式,可設置為.avi或者.mp4格式;
??(5)錄制視頻時,顯示當前錄制時間以及“REC”閃爍字樣;
??(6)拍照功能,拍攝照片分辨率為2592x1944(500萬像素);
??(7)通過F3、F4鍵移動光標,通過 ↑ 按鍵實現“開始、停止”錄視頻,通過 → 按鍵實現“確定”,通過 ← 按鍵實現“拍照”。
2.1 工程文件夾說明
??工程文件夾結構如下:
??在cameraModule文件夾內的是視頻相關內容,可單獨編譯成庫,在cameravideowidget.cpp中實現代碼的調用。
3 代碼
3.1 CameraThread類
??CameraThread類為視頻錄制、拍照實現的關鍵類,其接口如下表所示。
成員類型 | 名稱 | 描述 |
構造函數 | CameraThread() | 初始化 CameraThread 對象 |
析構函數 | ~CameraThread() | 釋放 CameraThread 對象的資源。 |
函數 | int CheckUSBCamera() | 檢查 USB 相機是否正常工作。返回值為 0 表示正常,其他值表示錯誤代碼。 |
void setParamter(int, int, int) | 設置視頻參數(分辨率、編碼格式、封裝格式)。必須在 OpenCamera() 之前調用。 | |
int OpenCamera() | 開啟相機。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
int CloseCamera() | 關閉相機。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
int TakePhoto(QString name) | 拍照接口,調用該函數使用相機拍攝一張照片并保存至指定路徑。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
int DeletPhoto() | 刪除照片接口,調用該函數刪除當前拍攝的照片。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
int StartTakeVideo(QString name) | 開始視頻錄制并保存到指定路徑。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
int StopTakeVideo() | 停止當前視頻錄制操作,并完成所有未完成任務。返回值為 0 表示成功,其他值表示錯誤代碼。 | |
信號 | void sendPhoto(QImage img) | 當拍攝照片后發出此信號,傳遞一個 QImage 類型的圖像用于UI繪制。 |
信號 | void sendRealTimePhoto(QImage img) | 發送實時圖像幀信號,用于傳遞實時預覽圖像。 |
公有變量 | bool isOpen | 標志相機是否已打開。 |
公有變量 | bool isRecording | 標志是否正在錄制視頻。 |
公有變量 | std::unique_ptr impl | 使用 PIMPL 設計模式隱藏實現細節的實現類指針。 |
??camerathread.cpp
實現如下所示。
#include "camerathread.h"
#include "camerathreadimpl.h"CameraThread::CameraThread() :impl(std::make_unique<CameraThreadImpl>())
{connect(impl.get(), &CameraThreadImpl::sendPhoto, this, &CameraThread::sendPhoto);connect(impl.get(), &CameraThreadImpl::sendRealTimePhoto, this, &CameraThread::sendRealTimePhoto);qDebug()<<"libcamerathread.so_version_20250731_";
}CameraThread::~CameraThread()
{}int CameraThread::CheckUSBCamera()
{int ret = 0;qDebug()<<"CheckUSBCamera!";if(isOpen==true){ret = 0;}else{ret = impl->CheckUSBCamera();}return ret;
}void CameraThread::setParamter(int resolution, int encodeformat, int containerformat)
{qDebug()<<"resolution: "<<resolution<<" encodeformat: "<<encodeformat<<"containerformat "<<containerformat;impl->setParamter(resolution, encodeformat, containerformat);
}int CameraThread::OpenCamera()
{int ret = 0;qDebug()<<"Camera Open!";ret=impl->OpenCamera();if(!ret){isOpen = true;}qDebug()<<"OpenCamera isOpen: "<<isOpen;return ret;
}int CameraThread::CloseCamera()
{int ret = 0;qDebug()<<"Camera Close!";qDebug()<<"CloseCamera isOpen: "<<isOpen;if(isOpen == true){ret = impl->CloseCamera();}if(!ret){isOpen = false;}ret = StopTakeVideo();return ret;
}int CameraThread::TakePhoto(QString name)
{int ret = 0;ret=impl->TakePhoto(name);return ret;
}int CameraThread::DeletPhoto()
{int ret = 0;qDebug()<<"Delete Photo";ret=impl->DeletPhoto();return ret;
}int CameraThread::StartTakeVideo(QString name)
{int ret = 0;qDebug()<<"StartTakeVideo: "<<name;ret=impl->StartTakeVideo(name);isRecording = true;return ret;
}int CameraThread::StopTakeVideo()
{int ret = 0;if(isRecording){ret=impl->StopTakeVideo();}isRecording = false;return ret;
}
3.1 CameraThreadImpl類
??CameraThread類中使用CameraThreadImpl類作為內部實現類,可使得CameraThread接口不暴露具體實現方法。
camerathreadimpl.cpp
具體實現如下所示。
#include "camerathreadimpl.h"CameraThreadImpl::CameraThreadImpl()
{initParam();/***************************分配緩沖隊列**************************************/video_packet_queue = new AVPacketQueue();video_packet_queue_output = new AVPacketQueue();/***************************分配緩沖隊列**************************************/video_frame_queue = new AVFrameQueue();fmtConvert_queue_output = new AVFrameQueue();}CameraThreadImpl::~CameraThreadImpl()
{delete demux_thread;delete decode_thread;delete save_thread;delete video_packet_queue_output;delete video_packet_queue;}void CameraThreadImpl::initParam()
{// 讀取INI配置文件QString configFilePath = QCoreApplication::applicationDirPath() + "/config.ini";QSettings settings(configFilePath, QSettings::IniFormat);/************************確認配置文件路徑以及是否存在配置文件***************/
// qDebug()<<"configFilePath: "<<configFilePath;
// qDebug()<<"File Exists: " << QFile::exists(configFilePath); // 確保文件存在
// qDebug()<<"QSettings file path: " << settings.fileName();/*********************************************************************/// 設置默認值QString defaultCameraUrl;QString defaultSavePath;
#ifdef Q_OS_WIN
// defaultCameraUrl = "video=Rmoncam FHD 1080P"; // Windows 平臺默認相機名稱defaultCameraUrl = "video=USB Video Device"; // Windows 平臺默認相機名稱defaultSavePath = "C:\\Users\\chao8\\Desktop\\"; // Windows 默認保存路徑
#else
// defaultCameraUrl = "/dev/video10"; // Forlinx 平臺默認相機名稱
// defaultSavePath = "/home/forlinx/Desktop/"; // Forlinx 默認保存路徑defaultCameraUrl = "/dev/video0"; // Debain11平臺默認相機名稱defaultSavePath = "/mnt/ums/data/"; // Debain11 默認保存路徑
#endifdouble defaultRemainingSpace = 0.8;int defaultRecordIntervalMinutes = 1000;QString defaultResolution = "1920x1080";QString defaultBitrate = "40M";// 從INI文件讀取配置值,如果沒有配置則使用默認值savePath_ = settings.value("Parameters/savePath", defaultSavePath).toString().toStdString();url_ = settings.value("Parameters/url", defaultCameraUrl).toString().toStdString();remainingSpace = settings.value("Parameters/remainingStorageSpace", defaultRemainingSpace).toDouble();recordIntervalMinutes = settings.value("Parameters/recordIntervalMinutes", defaultRecordIntervalMinutes).toInt();Resolution = settings.value("Parameters/resolution", defaultResolution).toString();Bitrate = settings.value("Parameters/bitrate", defaultBitrate).toString();// 設置定時器時間,定時處理視頻信息,每隔一段時間自動記錄視頻timer = new QTimer(this);timer->setInterval(recordIntervalMinutes * 60 * 1000);connect(timer, &QTimer::timeout, this, &CameraThreadImpl::handleVideoData);/*****************************打印各參數配置數值*****************************/
// qDebug()<<"savePath_: "<<QString::fromStdString(savePath_);
// qDebug()<<"url_: "<<QString::fromStdString(url_);
// qDebug()<<"remainingSpace: "<<remainingSpace;
// qDebug()<<"Resolution: "<<Resolution;
// qDebug()<<"Bitrate: "<<Bitrate;
// qDebug()<<"recordIntervalMinutes: "<<recordIntervalMinutes;/**************************************************************************/}int CameraThreadImpl::CheckUSBCamera()
{avdevice_register_all();std::string url_str = url_;AVFormatContext *ifmt_ctx = avformat_alloc_context();char err2str[256] = {0};AVDictionary *options = nullptr;av_dict_set(&options, "fflags", "nobuffer", 0);av_dict_set(&options, "probesize", "4096", 0);av_dict_set(&options, "framerate", "30", 0);av_dict_set(&options, "video_size", "1920x1080", 0);av_dict_set(&options, "input_format", "mjpeg", 0);#ifdef Q_OS_WINconst AVInputFormat *m_inputFormat = av_find_input_format("dshow");
#elseconst AVInputFormat *m_inputFormat = av_find_input_format("v4l2");
#endifint ret = avformat_open_input(&ifmt_ctx, url_str.c_str(), m_inputFormat, &options);// qDebug()<<"ret: "<<ret;if (ret < 0) {av_strerror(ret, err2str, sizeof(err2str));qDebug("avformat_open_input failed, ret:%d, err2str:%s", ret, err2str);return ret;}ret = avformat_find_stream_info(ifmt_ctx, nullptr);if (ret < 0) {av_strerror(ret, err2str, sizeof(err2str));qDebug("avformat_find_stream_info failed, ret:%d, err2str:%s", ret, err2str);avformat_close_input(&ifmt_ctx);return ret;}avformat_close_input(&ifmt_ctx);qDebug() << "USB camera check OK!";return ret;
}void CameraThreadImpl::ReleaseQueue()
{video_packet_queue_output->release();video_packet_queue->release();video_frame_queue->release();fmtConvert_queue_output->release();
}void CameraThreadImpl::setParamter(int resolution, int encodeformat, int containerformat)
{if(resolution==0){Resolution = "1920x1080";}else if(resolution==1){Resolution = "1280x720";}else if(resolution==2){Resolution = "640x480";}if(encodeformat==0){EncodeFormat = "MJPEG";}else if(encodeformat==1){EncodeFormat = "H264";}if(containerformat==0){VideoContainerFormat = "avi";}else if(containerformat==1){VideoContainerFormat = "mp4";}
}void CameraThreadImpl::handleVideoData()
{qDebug()<<"handleVideoData! ";demux_thread->start_pts = 0;save_thread->Stop();ReleaseQueue();save_thread->Init();save_thread->Start();}int CameraThreadImpl::OpenCamera()
{int ret = 0;/*************************************************************************************/if(EncodeFormat == "MJPEG"){demux_thread = new DemuxThread(video_packet_queue, &url_);demux_thread->setResolution(Resolution);ret = demux_thread->Start();if(ret<0){isCameraValid = false;}else{isCameraValid = true;}/**************************************************************************************/decode_thread = new DecodeThread(video_packet_queue, video_packet_queue_output, &savePath_);decode_thread->setRemainingSpace(remainingSpace);ret = decode_thread->Init(demux_thread->VideoCodecParameters());ret = decode_thread->Start();}if(EncodeFormat == "H264"){demux_thread = new DemuxThread(video_packet_queue, &url_);demux_thread->setResolution(Resolution);ret = demux_thread->Start();if(ret<0){isCameraValid = false;}else{isCameraValid = true;}/**************************************************************************************/decode_thread = new DecodeThread(video_packet_queue, video_frame_queue, &savePath_);decode_thread->setRemainingSpace(remainingSpace);ret = decode_thread->Init(demux_thread->VideoCodecParameters());ret = decode_thread->Start();}/**************************************************************************************/connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::sendPhoto);connect(decode_thread, &DecodeThread::realTimeframeReady, this, &CameraThreadImpl::sendRealTimePhoto);connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::CloseCamera);connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::setisTakePhoto);return ret;
}int CameraThreadImpl::StartTakeVideo(QString name)
{int ret = 0;std::string savefilename = name.toStdString();ReleaseQueue();CloseCamera();OpenCamera();/**************************************************************************************/if(isCameraValid){if(EncodeFormat == "MJPEG"){save_thread = new SaveThread(video_packet_queue_output, demux_thread->inputStream, &savefilename);save_thread->setRemainingSpace(remainingSpace);save_thread->setVideoContainerFormat(VideoContainerFormat);ret = save_thread->Init();ReleaseQueue();demux_thread->start_pts = 0;decode_thread->isSaveFile = true;ret = save_thread->Start();}if(EncodeFormat == "H264"){/************************************************************************/fmtConvert_thread = new FormatConvertThread(video_frame_queue, fmtConvert_queue_output);/*********************************************************************/encode_thread = new EncodeThread(fmtConvert_queue_output, video_packet_queue_output, demux_thread->inputStream);encode_thread->setBitrate(Bitrate);encode_thread->isFIlter = isFilterOn;encode_thread->EncodeFormat = "H264";ret = encode_thread->Init();ret = fmtConvert_thread->Start();ret = encode_thread->Start();save_thread = new SaveThread(video_packet_queue_output, demux_thread->inputStream, &savefilename, encode_thread->encoderContext);save_thread->setRemainingSpace(remainingSpace);save_thread->setVideoContainerFormat(VideoContainerFormat);ret = save_thread->Init();ReleaseQueue();demux_thread->start_pts = 0;decode_thread->isSaveFile = true;ret = save_thread->Start();}timer->start();}else{qDebug()<<"Camera is not Valid! Cannot video!";}return ret;
}int CameraThreadImpl::StopTakeVideo()
{if(isCameraValid){if(save_thread!=nullptr){save_thread->Stop();save_thread = nullptr;decode_thread->isSaveFile = false;if (fmtConvert_thread) fmtConvert_thread->Stop();if (encode_thread) encode_thread->Stop();ReleaseQueue();timer->stop();qDebug()<<"StopTakeVideo!";}else{qDebug()<<"No save_thread!";}}else{qDebug()<<"Camera is not Valid! Cannot video!";}return 0;
}int CameraThreadImpl::CloseCamera()
{StopTakeVideo();if(isCameraValid){decode_thread->Stop();demux_thread->Stop();isCameraValid = false;}ReleaseQueue(); //清空緩沖隊列里的東西,每次關閉相機的時候,必須要有!return 0;
}void CameraThreadImpl::setisTakePhoto()
{isTakePhoto = false;
}int CameraThreadImpl::TakePhoto(QString name)
{qDebug()<<"isTakePhoto: "<<isTakePhoto;if(!isTakePhoto){qDebug()<<"In TakePhoto!!!!!! ";isTakePhoto = true;CloseCamera();int ret = 0;demux_thread = new DemuxThread(video_packet_queue, &url_);demux_thread->setResolution("2592x1944");ret = demux_thread->Start();if(ret<0){isCameraValid = false;}else{isCameraValid = true;}/**************************************************************************************/decode_thread = new DecodeThread(video_packet_queue, video_packet_queue_output, &savePath_);decode_thread->setRemainingSpace(remainingSpace);ret = decode_thread->Init(demux_thread->VideoCodecParameters());ret = decode_thread->Start();connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::sendPhoto);connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::CloseCamera);connect(decode_thread, &DecodeThread::frameReady, this, &CameraThreadImpl::setisTakePhoto);if (decode_thread){decode_thread->takePhoto(name);}else{return -1;}return ret;}else{return 0;}}int CameraThreadImpl::DeletPhoto()
{decode_thread->DeletPhoto();return 0;
}
4 資源下載
本案例中涉及到的所有代碼請到此處下載 https://download.csdn.net/download/wang_chao118/91923444。