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。

相关推荐
TechVoyager_82464 小时前
多形态输入与12 通道音频:IT6615 这颗 HDMI 发射器的接口宽度
音视频·音频芯片·芯片选型·视频发射
Raymond16885 小时前
AI配音工具长文本合成架构技术解析:全局音色锚定与声学状态跟踪
人工智能·架构·实时互动·视觉检测·音视频·语音识别
hflmwl811 小时前
Wise Data Recovery找回误删的文档照片音乐和视频
音视频·datarecovery·找回误删的文档照片音乐和视频·找回误删的文档照片音乐视频
技灵AI11 小时前
AI 视频转场提示词怎么写?让前后画面衔接更自然
人工智能·prompt·aigc·音视频
youandyouyou13 小时前
跨区培训的屏幕共享怎么做才稳?同屏同步与弱网优化实践
音视频·网络通信
开开心心就好13 小时前
课堂随机点名工具,绿色版免安装双击即用
java·服务器·前端·ffmpeg·音视频·perl·symfony
美狐美颜SDK开放平台14 小时前
直播APP集成美颜SDK,如何梳理第三方组件的数据权限?
开发语言·人工智能·深度学习·音视频·第三方美颜sdk
小鹿软件办公14 小时前
不依赖 FFmpeg 也能录屏?ShareX 22 新增录屏等十余款新工具
ffmpeg·sharex·免费录屏工具
l143723326715 小时前
批量视频硬字幕擦除多少钱?影响报价的关键因素全解析
音视频
重生六道15 小时前
B站视频时长统计:一段JS脚本搞定分P/合集总时长(附倍速计算)
开发语言·javascript·音视频