RK3399平台ffmpeg-VPU硬编码录制USB摄像头视频、H264或MJPEG编码 - 指南


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 默认保存路径
#endif
double 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_WIN
const AVInputFormat *m_inputFormat = av_find_input_format("dshow");
#else
const AVInputFormat *m_inputFormat = av_find_input_format("v4l2");
#endif
int 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

posted @ 2025-09-12 13:58  yjbjingcha  阅读(18)  评论(0)    收藏  举报