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

文章目录

  • [1 前言](#1 前言)
  • [2 项目内容详细说明](#2 项目内容详细说明)
    • [2.0 功能](#2.0 功能)
    • [2.1 工程文件夹说明](#2.1 工程文件夹说明)
  • [3 代码](#3 代码)
    • [3.1 CameraThread类](#3.1 CameraThread类)
    • [3.1 CameraThreadImpl类](#3.1 CameraThreadImpl类)
  • [4 资源下载](#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实现如下所示。

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具体实现如下所示。

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

相关推荐
froxy2 小时前
音频中的PDM、PCM概念解读
音视频·pcm
Zender Han13 小时前
Flutter 视频播放器——flick_video_player 介绍与使用
android·flutter·ios·音视频
max50060014 小时前
实时多模态电力交易决策系统:设计与实现
图像处理·人工智能·深度学习·算法·音视频
达讯数字21 小时前
浅谈“SVMSPro视频切片”技术应用场景
音视频·mp4·海康·大华·svmspro·视频切片
嘀咕博客1 天前
拍我AI:PixVerse国内版,爱诗科技推出的AI视频生成平台
人工智能·科技·音视频·ai工具
嘀咕博客1 天前
SafeEar:浙大和清华联合推出的AI音频伪造检测框架,错误率低至2.02%
人工智能·音视频·ai工具
嘀咕博客1 天前
PixVerse -免费在线AI视频生成工具
人工智能·音视频·ai工具
悟乙己1 天前
Github | MoneyPrinterTurbo:自动化视频内容生成系统
自动化·github·音视频
wan5555cn1 天前
多张图片生成视频模型技术深度解析
人工智能·笔记·深度学习·算法·音视频