当前位置: 首页 > news >正文

用Qt开发的ffmpeg流媒体播放器,支持截图、录像,支持音视频播放,支持本地文件播放、网络流播放

前言

本工程qt用的版本是5.8-32位,ffmpeg用的版本是较新的5.1版本。它支持TCP或UDP方式拉取实时流,实时流我采用的是监控摄像头的RTSP流。音频播放采用的是QAudioOutput,视频经ffmpeg解码并由YUV转RGB后是在QOpenGLWidget下进行渲染显示。本工程的代码有注释,可以通过本博客查看代码或者在播放最后的链接处下载工程demo。

一、界面展示

在这里插入图片描述

二、功能代码

1.以下是主界面相关代码:mainwindow.h mainwindow.cpp

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include "commondef.h"
#include "mediathread.h"
#include "ctopenglwidget.h"namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);~MainWindow();void Init();private slots:void on_btn_play_clicked();void on_btn_open_clicked();void on_btn_play2_clicked();void on_btn_stop_clicked();void on_btn_record_clicked();void on_btn_snapshot_clicked();void on_btn_open_audio_clicked();void on_btn_close_audio_clicked();void on_btn_stop_record_clicked();private:Ui::MainWindow *ui;MediaThread* m_pMediaThread = nullptr;
};#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QFileDialog>
#include "ctaudioplayer.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);Init();
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::Init()
{ui->radioButton_TCP->setChecked(false);ui->radioButton_UDP->setChecked(true);
}void MainWindow::on_btn_play_clicked()
{QString sUrl = ui->lineEdit_Url->text();if(sUrl.isEmpty()){QMessageBox::critical(this, "myFFmpeg", "错误:实时流url不能为空.");return;}if(nullptr == m_pMediaThread){MY_DEBUG << "new MediaThread";m_pMediaThread = new MediaThread;}else{if(m_pMediaThread->isRunning()){QMessageBox::critical(this, "myFFmpeg", "错误:请先点击停止按钮关闭视频.");return;}}connect(m_pMediaThread, SIGNAL(sig_emitImage(const QImage&)),ui->openGLWidget, SLOT(slot_showImage(const QImage&)));bool bMediaInit = false;if(ui->radioButton_TCP->isChecked()){bMediaInit = m_pMediaThread->Init(sUrl, PROTOCOL_TCP);}else{bMediaInit = m_pMediaThread->Init(sUrl, PROTOCOL_UDP);}if(bMediaInit){m_pMediaThread->startThread();}
}void MainWindow::on_btn_open_clicked()
{QString sFileName = QFileDialog::getOpenFileName(this, QString::fromLocal8Bit("选择视频文件"));if(sFileName.isEmpty()){QMessageBox::critical(this, "myFFmpeg", "错误:文件不能为空.");return;}ui->lineEdit_File->setText(sFileName);}void MainWindow::on_btn_play2_clicked()
{QString sFileName = ui->lineEdit_File->text();if(sFileName.isEmpty()){QMessageBox::critical(this, "myFFmpeg", "错误:文件不能为空.");return;}if(nullptr == m_pMediaThread){m_pMediaThread = new MediaThread;}else{if(m_pMediaThread->isRunning()){QMessageBox::critical(this, "myFFmpeg", "错误:请先点击停止按钮关闭视频.");return;}}connect(m_pMediaThread, SIGNAL(sig_emitImage(const QImage&)),ui->openGLWidget, SLOT(slot_showImage(const QImage&)));if(m_pMediaThread->Init(sFileName)){m_pMediaThread->startThread();}
}void MainWindow::on_btn_stop_clicked()
{if(m_pMediaThread){qDebug() << "on_btn_stop_clicked 000";disconnect(m_pMediaThread, SIGNAL(sig_emitImage(const QImage&)),ui->openGLWidget, SLOT(slot_showImage(const QImage&)));m_pMediaThread->stopThread();qDebug() << "on_btn_stop_clicked 111";m_pMediaThread->quit();m_pMediaThread->wait();qDebug() << "on_btn_stop_clicked 222";m_pMediaThread->DeInit();qDebug() << "on_btn_stop_clicked 333";}}void MainWindow::on_btn_record_clicked()
{if(m_pMediaThread)m_pMediaThread->startRecord();
}void MainWindow::on_btn_snapshot_clicked()
{if(m_pMediaThread)m_pMediaThread->Snapshot();
}void MainWindow::on_btn_open_audio_clicked()
{ctAudioPlayer::getInstance().isPlay(true);
}void MainWindow::on_btn_close_audio_clicked()
{ctAudioPlayer::getInstance().isPlay(false);
}void MainWindow::on_btn_stop_record_clicked()
{if(m_pMediaThread)m_pMediaThread->stopRecord();
}

2.以下是流媒体线程相关代码:mediathread.h、mediathread.cpp

mediathread.h

#ifndef MEDIATHREAD_H
#define MEDIATHREAD_H#include <QThread>
#include <QImage>
#include "ctffmpeg.h"
#include "commondef.h"
#include "mp4recorder.h"#define MAX_AUDIO_OUT_SIZE 8*1152class MediaThread : public QThread
{Q_OBJECT
public:MediaThread();~MediaThread();bool Init(QString sUrl, int nProtocolType = PROTOCOL_UDP);void DeInit();void startThread();void stopThread();void setPause(bool bPause);void Snapshot();void startRecord();void stopRecord();public:int m_nMinAudioPlayerSize = 640;private:void run() override;signals:void sig_emitImage(const QImage&);private:bool m_bRun = false;bool m_bPause = false;bool m_bRecord = false;ctFFmpeg* m_pFFmpeg = nullptr;mp4Recorder m_pMp4Recorder;};#endif // MEDIATHREAD_H

mediathread.cpp

#include "mediathread.h"
#include "ctaudioplayer.h"
#include <QDate>
#include <QTime>MediaThread::MediaThread()
{
}MediaThread::~MediaThread()
{if(m_pFFmpeg){m_pFFmpeg->DeInit();delete m_pFFmpeg;m_pFFmpeg = nullptr;}
}bool MediaThread::Init(QString sUrl, int nProtocolType)
{if(nullptr == m_pFFmpeg){MY_DEBUG << "new ctFFmpeg";m_pFFmpeg = new ctFFmpeg;connect(m_pFFmpeg, SIGNAL(sig_getImage(const QImage&)),this, SIGNAL(sig_emitImage(const QImage&)));}if(m_pFFmpeg->Init(sUrl, nProtocolType) != 0){MY_DEBUG << "FFmpeg Init error.";return false;}return true;
}void MediaThread::DeInit()
{MY_DEBUG << "DeInit 000";m_pFFmpeg->DeInit();MY_DEBUG << "DeInit 111";if(m_pFFmpeg){delete m_pFFmpeg;m_pFFmpeg = nullptr;}MY_DEBUG << "DeInit end";
}void MediaThread::startThread()
{m_bRun = true;start();
}void MediaThread::stopThread()
{m_bRun = false;
}void MediaThread::setPause(bool bPause)
{m_bPause = bPause;
}void MediaThread::Snapshot()
{m_pFFmpeg->Snapshot();
}void MediaThread::startRecord()
{QString sPath = "./record/";QDate date = QDate::currentDate();QTime time = QTime::currentTime();QString sRecordPath = QString("%1%2-%3-%4-%5%6%7.mp4").arg(sPath).arg(date.year()). \arg(date.month()).arg(date.day()).arg(time.hour()).arg(time.minute()). \arg(time.second());MY_DEBUG << "sRecordPath:" << sRecordPath;if(nullptr != m_pFFmpeg->m_pAVFmtCxt && m_bRun){m_bRecord = m_pMp4Recorder.Init(m_pFFmpeg->m_pAVFmtCxt, sRecordPath);}
}void MediaThread::stopRecord()
{if(m_bRecord){MY_DEBUG << "stopRecord...";m_pMp4Recorder.DeInit();m_bRecord = false;}
}void MediaThread::run()
{char audioOut[MAX_AUDIO_OUT_SIZE] = {0};while(m_bRun){if(m_bPause){msleep(100);continue;}//获取播放器缓存大小if(m_pFFmpeg->m_bSupportAudioPlay){int nFreeSize = ctAudioPlayer::getInstance().getFreeSize();if(nFreeSize < m_nMinAudioPlayerSize){msleep(1);continue;}}AVPacket pkt = m_pFFmpeg->getPacket();if (pkt.size <= 0){msleep(10);continue;}//解码播放if (pkt.stream_index == m_pFFmpeg->m_nAudioIndex &&m_pFFmpeg->m_bSupportAudioPlay){if(m_pFFmpeg->Decode(&pkt)){int nLen = m_pFFmpeg->getAudioFrame(audioOut);//获取一帧音频的pcmif(nLen > 0)ctAudioPlayer::getInstance().Write(audioOut, nLen);}}else{//目前只支持录制视频if(m_bRecord){//MY_DEBUG << "record...";AVPacket* pPkt = av_packet_clone(&pkt);m_pMp4Recorder.saveOneFrame(*pPkt);av_packet_free(&pPkt);}if(m_pFFmpeg->Decode(&pkt)){m_pFFmpeg->getVideoFrame();}}av_packet_unref(&pkt);}MY_DEBUG << "run end";
}

3.以下是ffmpeg处理的相关代码:ctffmpeg.h、ctffmpeg.cpp

ctffmpeg.h

#ifndef CTFFMPEG_H
#define CTFFMPEG_H#include <QObject>
#include <QMutex>extern "C"
{#include "libavcodec/avcodec.h"#include "libavcodec/dxva2.h"#include "libavutil/avstring.h"#include "libavutil/mathematics.h"#include "libavutil/pixdesc.h"#include "libavutil/imgutils.h"#include "libavutil/dict.h"#include "libavutil/parseutils.h"#include "libavutil/samplefmt.h"#include "libavutil/avassert.h"#include "libavutil/time.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "libavutil/opt.h"#include "libavcodec/avfft.h"#include "libswresample/swresample.h"#include "libavfilter/buffersink.h"#include "libavfilter/buffersrc.h"#include "libavutil/avutil.h"
}
#include "commondef.h"class ctFFmpeg : public QObject
{Q_OBJECT
public:ctFFmpeg();~ctFFmpeg();int Init(QString sUrl, int nProtocolType = PROTOCOL_UDP);void DeInit();AVPacket getPacket(); //读取一帧bool Decode(const AVPacket *pkt); //解码int getVideoFrame();int getAudioFrame(char* pOut);void Snapshot();private:int InitVideo();int InitAudio();signals:void sig_getImage(const QImage &image);public:int m_nVideoIndex = -1;int m_nAudioIndex = -1;bool m_bSupportAudioPlay = false;AVFormatContext *m_pAVFmtCxt = nullptr;              //流媒体的上下文private:AVCodecContext* m_pVideoCodecCxt = nullptr;          //视频解码器上下文AVCodecContext* m_pAudioCodecCxt = nullptr;          //音频解码器上下文AVFrame *m_pYuvFrame = nullptr;                      //解码后的视频帧数据AVFrame *m_pPcmFrame = nullptr;                      //解码后的音频数据SwrContext *m_pAudioSwrContext = nullptr;            //音频重采样上下文SwsContext *m_pVideoSwsContext = nullptr;            //处理像素问题,格式转换 yuv->rbgAVPacket m_packet;                                   //每一帧数据 原始数据AVFrame* m_pFrameRGB = nullptr;                      //转换后的RGB数据enum AVCodecID m_CodecId;uint8_t* m_pOutBuffer = nullptr;int m_nAudioSampleRate = 8000;                       //音频采样率int m_nAudioPlaySampleRate = 44100;                  //音频播放采样率int m_nAudioPlayChannelNum = 1;                      //音频播放通道数int64_t m_nLastReadPacktTime = 0;bool m_bSnapshot = false;QString m_sSnapPath = "./snapshot/test.jpg";QMutex m_mutex;
};#endif // CTFFMPEG_H

ctffmpeg.cpp

#include "ctffmpeg.h"
#include <QImage>
#include "ctaudioplayer.h"
#include <QPixmap>
#include <QDate>
#include <QTime>ctFFmpeg::ctFFmpeg()
{
}ctFFmpeg::~ctFFmpeg()
{
}int ctFFmpeg::Init(QString sUrl, int nProtocolType)
{DeInit();avformat_network_init();//初始化网络流//参数设置AVDictionary* pOptDict = NULL;if(nProtocolType == PROTOCOL_UDP)av_dict_set(&pOptDict, "rtsp_transport", "udp", 0);elseav_dict_set(&pOptDict, "rtsp_transport", "tcp", 0);av_dict_set(&pOptDict, "stimeout", "5000000", 0);av_dict_set(&pOptDict, "buffer_size", "8192000", 0);if(nullptr == m_pAVFmtCxt)m_pAVFmtCxt = avformat_alloc_context();if(nullptr == m_pYuvFrame)m_pYuvFrame = av_frame_alloc();if(nullptr == m_pPcmFrame)m_pPcmFrame = av_frame_alloc();//加入中断处理m_nLastReadPacktTime = av_gettime();m_pAVFmtCxt->interrupt_callback.opaque = this;m_pAVFmtCxt->interrupt_callback.callback = [](void* ctx){ctFFmpeg* pThis = (ctFFmpeg*)ctx;int nTimeout = 3;if (av_gettime() - pThis->m_nLastReadPacktTime > nTimeout * 1000 * 1000){return -1;}return 0;};//打开码流int nRet = avformat_open_input(&m_pAVFmtCxt, sUrl.toStdString().c_str(), nullptr, nullptr);if(nRet < 0){MY_DEBUG << "avformat_open_input failed nRet:" << nRet;return nRet;}//设置探测时间,获取码流信息m_pAVFmtCxt->probesize = 400 * 1024;m_pAVFmtCxt->max_analyze_duration = 2 * AV_TIME_BASE;nRet = avformat_find_stream_info(m_pAVFmtCxt, nullptr);if(nRet < 0){MY_DEBUG << "avformat_find_stream_info failed nRet:" << nRet;return nRet;}//打印码流信息av_dump_format(m_pAVFmtCxt, 0, sUrl.toStdString().c_str(), 0);//查找码流for (int nIndex = 0; nIndex < m_pAVFmtCxt->nb_streams; nIndex++){if (m_pAVFmtCxt->streams[nIndex]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){m_nVideoIndex = nIndex;}if (m_pAVFmtCxt->streams[nIndex]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){m_nAudioIndex = nIndex;}}//初始化视频if(InitVideo() < 0){MY_DEBUG << "InitVideo() error";return -1;}//初始化音频if(m_nAudioIndex != -1){if(InitAudio() < 0){MY_DEBUG << "InitAudio() error";m_bSupportAudioPlay = false;}elsem_bSupportAudioPlay = true;}return 0;
}void ctFFmpeg::DeInit()
{MY_DEBUG << "DeInit 000";m_mutex.lock();MY_DEBUG << "DeInit 111";if (nullptr != m_pVideoSwsContext){sws_freeContext(m_pVideoSwsContext);}MY_DEBUG << "DeInit 222";if (nullptr != m_pAudioSwrContext){swr_free(&m_pAudioSwrContext);m_pAudioSwrContext = nullptr;}MY_DEBUG << "DeInit 333";if(nullptr != m_pYuvFrame)av_free(m_pYuvFrame);MY_DEBUG << "DeInit 444";if(nullptr != m_pPcmFrame)av_free(m_pPcmFrame);MY_DEBUG << "DeInit 555";if(nullptr != m_pOutBuffer){av_free(m_pOutBuffer);}MY_DEBUG << "DeInit 666";if(nullptr != m_pVideoCodecCxt){avcodec_close(m_pVideoCodecCxt);//avcodec_free_context(&m_pVideoCodecCxt);m_pVideoCodecCxt = nullptr;}MY_DEBUG << "DeInit 777";if (nullptr != m_pAudioCodecCxt){avcodec_close(m_pAudioCodecCxt);//avcodec_free_context(&m_pAudioCodecCxt);m_pAudioCodecCxt = nullptr;}MY_DEBUG << "DeInit 888";if(nullptr != m_pAVFmtCxt){avformat_close_input(&m_pAVFmtCxt);MY_DEBUG << "DeInit 999";avformat_free_context(m_pAVFmtCxt);m_pAVFmtCxt = nullptr;}MY_DEBUG << "DeInit end";m_mutex.unlock();
}AVPacket ctFFmpeg::getPacket()
{AVPacket pkt;memset(&pkt, 0, sizeof(AVPacket));if(!m_pAVFmtCxt){return pkt;}m_nLastReadPacktTime = av_gettime();int nErr = av_read_frame(m_pAVFmtCxt, &pkt);if(nErr < 0){//错误信息char errorbuff[1024];av_strerror(nErr, errorbuff, sizeof(errorbuff));}return pkt;
}bool ctFFmpeg::Decode(const AVPacket *pkt)
{m_mutex.lock();if(!m_pAVFmtCxt){m_mutex.unlock();return false;}AVCodecContext* pCodecCxt = nullptr;AVFrame *pFrame;if (pkt->stream_index == m_nAudioIndex){pFrame = m_pPcmFrame;pCodecCxt = m_pAudioCodecCxt;}else{pFrame = m_pYuvFrame;pCodecCxt = m_pVideoCodecCxt;}//发送编码数据包int nRet = avcodec_send_packet(pCodecCxt, pkt);if (nRet != 0){m_mutex.unlock();MY_DEBUG << "avcodec_send_packet error---" << nRet;return false;}//获取解码的输出数据nRet = avcodec_receive_frame(pCodecCxt, pFrame);if (nRet != 0){m_mutex.unlock();qDebug()<<"avcodec_receive_frame error---" << nRet;return false;}m_mutex.unlock();return true;
}int ctFFmpeg::getVideoFrame()
{m_mutex.lock();auto nRet = sws_scale(m_pVideoSwsContext, (const uint8_t* const*)m_pYuvFrame->data,m_pYuvFrame->linesize, 0, m_pVideoCodecCxt->height,m_pFrameRGB->data, m_pFrameRGB->linesize);if(nRet < 0){//MY_DEBUG << "sws_scale error.";m_mutex.unlock();return -1;}//发送获取一帧图像信号QImage image(m_pFrameRGB->data[0], m_pVideoCodecCxt->width,m_pVideoCodecCxt->height, QImage::Format_ARGB32);//截图if(m_bSnapshot){QPixmap pixPicture = QPixmap::fromImage(image);QString sPath = "./snapshot/";QDate date = QDate::currentDate();QTime time = QTime::currentTime();m_sSnapPath = QString("%1%2-%3-%4-%5%6%7.jpg").arg(sPath).arg(date.year()). \arg(date.month()).arg(date.day()).arg(time.hour()).arg(time.minute()). \arg(time.second());MY_DEBUG << "Snapshot... m_sSnapPath:" << m_sSnapPath;pixPicture.save(m_sSnapPath, "jpg");m_bSnapshot = false;}emit sig_getImage(image);m_mutex.unlock();return 0;
}int ctFFmpeg::getAudioFrame(char *pOut)
{m_mutex.lock();uint8_t  *pData[1];pData[0] = (uint8_t *)pOut;//获取目标样本数auto nDstNbSamples = av_rescale_rnd(m_pPcmFrame->nb_samples,m_nAudioPlaySampleRate,m_nAudioSampleRate,AV_ROUND_ZERO);//重采样int nLen = swr_convert(m_pAudioSwrContext, pData, nDstNbSamples,(const uint8_t **)m_pPcmFrame->data,m_pPcmFrame->nb_samples);if(nLen <= 0){MY_DEBUG << "swr_convert error";m_mutex.unlock();return -1;}//获取样本保存的缓存大小int nOutsize = av_samples_get_buffer_size(nullptr, m_pAudioCodecCxt->channels,m_pPcmFrame->nb_samples,AV_SAMPLE_FMT_S16,0);m_mutex.unlock();return nOutsize;
}void ctFFmpeg::Snapshot()
{m_bSnapshot = true;
}int ctFFmpeg::InitVideo()
{if(m_nVideoIndex == -1){MY_DEBUG << "m_nVideoIndex == -1 error";return -1;}//查找视频解码器const AVCodec *pAVCodec = avcodec_find_decoder(m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar->codec_id);if(!pAVCodec){MY_DEBUG << "video decoder not found";return -1;}//视频解码器参数配置m_CodecId = pAVCodec->id;if(!m_pVideoCodecCxt)m_pVideoCodecCxt = avcodec_alloc_context3(nullptr);if(nullptr == m_pVideoCodecCxt){MY_DEBUG << "avcodec_alloc_context3 error m_pVideoCodecCxt=nullptr";return -1;}avcodec_parameters_to_context(m_pVideoCodecCxt, m_pAVFmtCxt->streams[m_nVideoIndex]->codecpar);if(m_pVideoCodecCxt){if (m_pVideoCodecCxt->width == 0 || m_pVideoCodecCxt->height == 0){MY_DEBUG << "m_pVideoCodecCxt->width=0 or m_pVideoCodecCxt->height=0 error";return -1;}}//打开视频解码器int nRet = avcodec_open2(m_pVideoCodecCxt, pAVCodec, nullptr);if(nRet < 0){MY_DEBUG << "avcodec_open2 video error";return -1;}//申请并分配内存,初始化转换上下文,用于YUV转RGBm_pOutBuffer = (uint8_t*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_BGRA,m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1));if(nullptr == m_pOutBuffer){MY_DEBUG << "nullptr == m_pOutBuffer error";return -1;}if(nullptr == m_pFrameRGB)m_pFrameRGB = av_frame_alloc();if(nullptr == m_pFrameRGB){MY_DEBUG << "nullptr == m_pFrameRGB error";return -1;}av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, m_pOutBuffer,AV_PIX_FMT_BGRA, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1);m_pVideoSwsContext = sws_getContext(m_pVideoCodecCxt->width, m_pVideoCodecCxt->height,m_pVideoCodecCxt->pix_fmt, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height,AV_PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL);if(nullptr == m_pVideoSwsContext){MY_DEBUG << "nullptr == m_pVideoSwsContex error";return -1;}return 0;
}int ctFFmpeg::InitAudio()
{if(m_nAudioIndex == -1){MY_DEBUG << "m_nAudioIndex == -1";return -1;}//查找音频解码器const AVCodec *pAVCodec = avcodec_find_decoder(m_pAVFmtCxt->streams[m_nAudioIndex]->codecpar->codec_id);if(!pAVCodec){MY_DEBUG << "audio decoder not found";return -1;}//音频解码器参数配置if (!m_pAudioCodecCxt)m_pAudioCodecCxt = avcodec_alloc_context3(nullptr);if(nullptr == m_pAudioCodecCxt){MY_DEBUG << "avcodec_alloc_context3 error m_pAudioCodecCxt=nullptr";return -1;}avcodec_parameters_to_context(m_pAudioCodecCxt, m_pAVFmtCxt->streams[m_nAudioIndex]->codecpar);//打开音频解码器int nRet = avcodec_open2(m_pAudioCodecCxt, pAVCodec, nullptr);if(nRet < 0){avcodec_close(m_pAudioCodecCxt);MY_DEBUG << "avcodec_open2 error m_pAudioCodecCxt";return -1;}//音频重采样初始化if (nullptr == m_pAudioSwrContext){if(m_pAudioCodecCxt->channel_layout <= 0 || m_pAudioCodecCxt->channel_layout > 3)m_pAudioCodecCxt->channel_layout = 1;qDebug() << "m_audioCodecContext->channel_layout:" << m_pAudioCodecCxt->channel_layout;qDebug() << "m_audioCodecContext->channels:" << m_pAudioCodecCxt->channels;m_pAudioSwrContext = swr_alloc_set_opts(0,m_pAudioCodecCxt->channel_layout,AV_SAMPLE_FMT_S16,m_pAudioCodecCxt->sample_rate,av_get_default_channel_layout(m_pAudioCodecCxt->channels),m_pAudioCodecCxt->sample_fmt,m_pAudioCodecCxt->sample_rate,0,0);auto nRet = swr_init(m_pAudioSwrContext);if(nRet < 0){MY_DEBUG << "swr_init error";return -1;}}//音频播放设备初始化int nSampleSize = 16;switch (m_pAudioCodecCxt->sample_fmt)//样本大小{case AV_SAMPLE_FMT_S16:nSampleSize = 16;break;case  AV_SAMPLE_FMT_S32:nSampleSize = 32;default:break;}m_nAudioSampleRate = m_pAudioCodecCxt->sample_rate;ctAudioPlayer::getInstance().m_nSampleRate = m_nAudioSampleRate;//采样率ctAudioPlayer::getInstance().m_nChannelCount = m_pAudioCodecCxt->channels;//通道数ctAudioPlayer::getInstance().m_nSampleSize = nSampleSize;//样本大小if(!ctAudioPlayer::getInstance().Init())return -1;return 0;
}

4.以下是opengl处理的相关代码:ctopenglwidget.h、ctopenglwidget.cpp

ctopenglwidget.h

#ifndef CTOPENGLWIDGET_H
#define CTOPENGLWIDGET_H#include <QOpenGLWidget>class ctOpenglWidget : public QOpenGLWidget
{Q_OBJECT
public:ctOpenglWidget(QWidget *parent = nullptr);~ctOpenglWidget();protected:void paintEvent(QPaintEvent *e);private slots:void slot_showImage(const QImage& image);private:QImage m_image;};#endif // CTOPENGLWIDGET_H

ctopenglwidget.cpp

#include "ctopenglwidget.h"
#include <QPainter>
#include "commondef.h"ctOpenglWidget::ctOpenglWidget(QWidget *parent) : QOpenGLWidget(parent)
{
}ctOpenglWidget::~ctOpenglWidget()
{
}void ctOpenglWidget::paintEvent(QPaintEvent *e)
{Q_UNUSED(e)QPainter painter;painter.begin(this);//清理屏幕painter.drawImage(QPoint(0, 0), m_image);//绘制FFMpeg解码后的视频painter.end();
}void ctOpenglWidget::slot_showImage(const QImage &image)
{if(image.width() > image.height())m_image = image.scaledToWidth(width(),Qt::SmoothTransformation);elsem_image = image.scaledToHeight(height(),Qt::SmoothTransformation);update();
}

4.以下是QAudioOutput音频播放器处理的相关代码:ctaudioplayer.h、ctaudioplayer.cpp

ctaudioplayer.h

#ifndef CTAUDIOPLAYER_H
#define CTAUDIOPLAYER_H#include <QObject>
#include <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QMutex>
#include "commondef.h"class ctAudioPlayer
{
public:ctAudioPlayer();~ctAudioPlayer();static ctAudioPlayer& getInstance();bool Init();void DeInit();void isPlay(bool bPlay);void Write(const char *pData, int nDatasize);int getFreeSize();public:int m_nSampleRate = 8000;//采样率int m_nSampleSize = 16;//采样大小int m_nChannelCount = 1;//通道数
private:QAudioDeviceInfo m_audio_device;QAudioOutput* m_pAudioOut = nullptr;QIODevice* m_pIODevice = nullptr;QMutex m_mutex;
};#endif // CTAUDIOPLAYER_H

ctaudioplayer.cpp

#include "ctaudioplayer.h"ctAudioPlayer::ctAudioPlayer()
{}ctAudioPlayer::~ctAudioPlayer()
{}ctAudioPlayer &ctAudioPlayer::getInstance()
{static ctAudioPlayer s_obj;return s_obj;
}bool ctAudioPlayer::Init()
{DeInit();m_mutex.lock();MY_DEBUG << "m_nSampleRate:" << m_nSampleRate;MY_DEBUG << "m_nSampleSize:" << m_nSampleSize;MY_DEBUG << "m_nChannelCount:" << m_nChannelCount;m_audio_device = QAudioDeviceInfo::defaultOutputDevice();MY_DEBUG << "m_audio_device.deviceName():" << m_audio_device.deviceName();if(m_audio_device.deviceName().isEmpty()){return false;}QAudioFormat format;format.setSampleRate(m_nSampleRate);format.setSampleSize(m_nSampleSize);format.setChannelCount(m_nChannelCount);format.setCodec("audio/pcm");format.setByteOrder(QAudioFormat::LittleEndian);format.setSampleType(QAudioFormat::UnSignedInt);if(!m_audio_device.isFormatSupported(format)){MY_DEBUG << "QAudioDeviceInfo format No Supported.";//format = m_audio_device.nearestFormat(format);m_mutex.unlock();return false;}if(m_pAudioOut){m_pAudioOut->stop();delete m_pAudioOut;m_pAudioOut = nullptr;m_pIODevice = nullptr;}m_pAudioOut = new QAudioOutput(format);m_pIODevice = m_pAudioOut->start();m_mutex.unlock();return true;
}void ctAudioPlayer::DeInit()
{m_mutex.lock();if(m_pAudioOut){m_pAudioOut->stop();delete m_pAudioOut;m_pAudioOut = nullptr;m_pIODevice = nullptr;}m_mutex.unlock();
}void ctAudioPlayer::isPlay(bool bPlay)
{m_mutex.lock();if(!m_pAudioOut){m_mutex.unlock();return;}if(bPlay)m_pAudioOut->resume();//恢复播放elsem_pAudioOut->suspend();//暂停播放m_mutex.unlock();
}void ctAudioPlayer::Write(const char *pData, int nDatasize)
{m_mutex.lock();if(m_pIODevice)m_pIODevice->write(pData, nDatasize);//将获取的音频写入到缓冲区中m_mutex.unlock();
}int ctAudioPlayer::getFreeSize()
{m_mutex.lock();if(!m_pAudioOut){m_mutex.unlock();return 0;}int nFreeSize = m_pAudioOut->bytesFree();//剩余空间m_mutex.unlock();return nFreeSize;
}

三、测试成果

1.实时流

在这里插入图片描述

2.文件流

在这里插入图片描述

2.录像截图

在这里插入图片描述

3.音频测试

在这里插入图片描述

四、demo下载

下载链接:https://download.csdn.net/download/linyibin_123/87435635

五、参考

https://blog.csdn.net/qq871580236/article/details/120364013

相关文章:

用Qt开发的ffmpeg流媒体播放器,支持截图、录像,支持音视频播放,支持本地文件播放、网络流播放

前言 本工程qt用的版本是5.8-32位&#xff0c;ffmpeg用的版本是较新的5.1版本。它支持TCP或UDP方式拉取实时流&#xff0c;实时流我采用的是监控摄像头的RTSP流。音频播放采用的是QAudioOutput&#xff0c;视频经ffmpeg解码并由YUV转RGB后是在QOpenGLWidget下进行渲染显示。本…...

第七节 平台设备驱动

在之前的字符设备程序中驱动程序&#xff0c;我们只要调用open() 函数打开了相应的设备文件&#xff0c;就可以使用read()/write() 函数&#xff0c;通过file_operations 这个文件操作接口来进行硬件的控制。这种驱动开发方式简单直观&#xff0c;但是从软件设计的角度看&#…...

代理模式详解

本文首更于《从零开始手把手教你实现一个简单的RPC框架》 。 1. 代理模式2. 静态代理3. 动态代理 3.1. JDK 动态代理机制 3.1.1. 介绍3.1.2. JDK 动态代理类使用步骤3.1.3. 代码示例 3.2. CGLIB 动态代理机制 3.2.1. 介绍3.2.2. CGLIB 动态代理类使用步骤3.2.3. 代码示例 3.3. …...

根据报告20%的白领在一年内做过副业,你有做副业吗?

现在大部分人收入单一&#xff0c;收入都是来源于本职工作&#xff0c;当没有了工作就没有了收入的来源&#xff0c;而生活压力又很大&#xff0c;各种开支&#xff0c;各种消费。所以很多人想要增加收入来源&#xff0c;增加被动收入&#xff0c;同时通过副业提升自己的价值和…...

第二十三周周报

学习内容&#xff1a; 修改ViTGAN代码 学习时间&#xff1a; 2.3-2.10 学习产出&#xff1a; 现在的效果 可以看到在700k左右fid开始上升&#xff0c;相比vitgan&#xff0c;改的vitgan鉴别器loss有所下降&#xff0c;但是fid没有降下来&#xff0c;最好为23.134&#xf…...

2023年Q1业绩增长背后,迪士尼亟待扭转流媒体亏损困局

重新执掌迪士尼后&#xff0c;鲍勃伊格尔交出了一份表现尚可的“答卷”。 图源:迪士尼 美东时间2023年2月8日&#xff0c;迪士尼披露了2023财年Q1财报&#xff0c;营收为235.1亿美元&#xff0c;同比增长8%&#xff1b;持续经营净利润13亿美元&#xff0c;同比增长11%。受此利…...

LKWA靶场通关和源码分析

文章目录一、Blind RCE&#xff1f;二、XSSI三、PHP Object Injection四、PHP Object Injection(cookie)五、PHP Object Injection(Referer)六、PHAR七、SSRF八、Variables总结一、Blind RCE&#xff1f; 源码&#xff1a; <?php include("sidebar.php"); /***…...

logcpp demo

step1&#xff1a;nug下载log4cppstep2&#xff1a;实现demo#include <iostream>#include <log4cpp/Category.hh>#include <log4cpp/Appender.hh>#include <log4cpp/FileAppender.hh>#include <log4cpp/Priority.hh>#include <log4cpp/Patter…...

平价款的血糖血压监测工具,用它养成健康生活习惯,dido F50S Pro上手

之前看有数据显示国内的三高人群越来越年轻&#xff0c;很多人不到三十就有了高血压、高血糖的问题&#xff0c;埋下了不小的健康隐患&#xff0c;加上前阵子的疫情管控放松&#xff0c;人们了解到了新冠病毒对心脏负担的认知&#xff0c;预防慢病被大众提上了日程&#xff0c;…...

算法训练营 day42 动态规划 理论基础 斐波那契数 爬楼梯 使用最小花费爬楼梯

算法训练营 day42 动态规划 理论基础 斐波那契数 爬楼梯 使用最小花费爬楼梯 理论基础 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状…...

MySQL8 创建用户,设置修改密码,授权

MySQL8 创建用户,设置修改密码,授权 MySQL5.7可以 (创建用户,设置密码,授权) 一步到位 &#x1f447; GRANT ALL PRIVILEGES ON *.* TO 用户名% IDENTIFIED BY 密码 WITH GRANT OPTION&#x1f446;这样的语句在MySQL8.0中行不通, 必须 创设和授权 分步执行&#x1f447; CR…...

MySQL —— 内置函数

目录 内置函数 一、日期函数 二、字符串函数 三、数学函数 四、其他函数 内置函数 一、日期函数 函数名称描述current_date()获取当前日期current_time()获取当前时间current_timestamp()获取当前时间戳now()获取当前日期时间date(datetime)获取datetime参数的日期部分d…...

Mybatis框架(全部基础知识)

&#x1f44c; 棒棒有言&#xff1a;也许我一直照着别人的方向飞&#xff0c;可是这次&#xff0c;我想要用我的方式飞翔一次&#xff01;人生&#xff0c;既要淡&#xff0c;又要有味。凡事不必太在意&#xff0c;一切随缘&#xff0c;缘深多聚聚&#xff0c;缘浅随它去。凡事…...

pixhawk2.4.8使用调试记录—APM固件

目录一、硬件准备二、APM固件、MP地面站下载三、地面站配置1 刷固件2 机架选择3 加速度计校准4 指南针校准5 遥控器校准6 飞行模式7 紧急断电&无头模式8 基础参数设置9 电流计校准10 电调校准11 起飞前检查&#xff08;每一项都非常重要&#xff09;12 飞行经验四、遇到的问…...

终于进了字节,记录一下我作为一名测试员磕磕碰碰的三个月找工作经历...

我是裸辞后重新找工作的&#xff0c;从去年到今年&#xff0c;前前后后花了大概三个月&#xff0c;大大小小参加了几百场面试。不是我说&#xff0c;作为一名测试员是真的挺难的&#xff0c;不过很庆幸自己最后拿到了字节的offer&#xff0c;今天在这里做一下记录吧&#xff0c…...

基于PYTHON django四川旅游景点推荐系统

摘 要基于四川旅游景点推荐系统的设计与实现是一个专为四川旅游景点为用户打造的旅游网站。该课题基于网站比较流行的Python 语言系统架构,B/S三层结构模式&#xff0c;通过Maven项目管理工具进行Jar包版本的控制。本系统用户可以发布个人游记&#xff0c;查看景点使用户达到良…...

MySql服务多版本之间的切换

从网上总结的经验&#xff0c;然后根据自己所遇到的问题合并记录一下&#xff0c;方便日后再次需要用到 MySql服务多版本同时运行 步骤 1、如果你电脑上已经有一个mysql版本&#xff0c;例如mysql-5.7.39-winx64&#xff0c;它占据了3306端口。此时如果你想下仔另一版本&…...

嵌入式开发:通过嵌入式虚

嵌入式虚拟化为实现多核处理能力的优势提供了一种可扩展的机制。嵌入式应用中的虚拟化与其企业和桌面应用有许多共同之处。独特的嵌入式使用案例和专业的底层技术为嵌入式开发人员提供了优化性能和响应设计的新机会。在台式机、数据中心以及现在的嵌入式设计中采用多核技术可以…...

广州穗雅医院杨济安:了解症状表现 有效防治口腔黏膜下纤维化

“医生&#xff0c;我出现口干大半年时间&#xff0c;最近两月张嘴费劲&#xff0c;吃点辣的&#xff0c;嘴就刺疼刺疼的&#xff0c;这是怎么回事&#xff1f;”半年前&#xff0c;家住南沙的文先生走进广州穗雅医院口腔黏膜科如是说到。在科室杨济安主任的详细问诊与检查后&a…...

[数据分析] 数据指标体系搭建

在数据分析的学习过程中&#xff0c;我们通常会要求掌握以下两点: 1.理解数据&#xff0c;懂得从数据中发现业务指标(学会如何去看懂数据) 2.使用相关指标去分析数据&#xff0c;同时使用多个指标去分析一个问题(了解常见的指标) 当我们拿到数据(通常以Excel或者数据库方式去…...

Dubbo 源码分析 – 集群容错之 Cluster

3.2.2 FailbackClusterInvoker FailbackClusterInvoker 会在调用失败后&#xff0c;返回一个空结果给服务提供者。并通过定时任务对失败的调用进行重传&#xff0c;适合执行消息通知等操作。下面来看一下它的实现逻辑。 public class FailbackClusterInvoker<T> extend…...

Spring学习20230208-09

IOC底层原理 IOC概念 &#xff1a;面向对象编程中的一种设计原则&#xff0c;用来降低耦合度 通过控制反转&#xff0c;对象在被创建的时候&#xff0c;由一个调控系统内所有对象的外界实体将其所依赖的对象引用传递给他。可以说&#xff0c;依赖被注入到对象中。控制反转&…...

tomcat10部署报错WebStatFilter cannot be cast to jakarta.servlet.Filter

异常信息09-Feb-2023 23:08:49.946 严重 [main] org.apache.catalina.core.StandardContext.filterStart 启动过滤器异常[DruidWebStatFilter]java.lang.ClassCastException: com.alibaba.druid.support.http.WebStatFilter cannot be cast to jakarta.servlet.Filterat org.ap…...

Linux修改文件时间或创建新文件:touch

每个文件在Linux下面都记录了许多的时间参数&#xff0c;其实是三个主要的变动时间 修改时间&#xff08;modification time&#xff0c;mtime&#xff09;&#xff1a;当该文件的【内容数据】变更时&#xff0c;就会更新这个时间&#xff0c;内容数据是指文件的内容&#xff…...

原生微信小程序按需引入vant

vant Vant Weapp - 轻量、可靠的小程序 UI 组件库 1.npm安装 找到项目根目录 安装 # 通过 npm 安装 npm i vant/weapp -S --production# 通过 yarn 安装 yarn add vant/weapp --production# 安装 0.x 版本 npm i vant-weapp -S --production 2 .修改 app.json 将 app.jso…...

高性能IO模型:为什么单线程Redis能那么快?

我们通常说Redis是单线程&#xff0c;主要是指Redis的网络IO和键值对读写是由一个线程来完成的。这也是Redis对外提供键值存储服务的主要流程。 但redis的其他功能&#xff0c;比如持久化、异步删除、集群数据同步等&#xff0c;其实是由额外的线程执行的。 Redis为什么用单线…...

【数据集】中国各类水文专业常用数据集合集

1 水文气象数据 1.1 中国站点尺度天然径流量估算数据集&#xff08;1961&#xff5e;2018年&#xff09; 论文&#xff1a; J2022-High-quality reconstruction of China’s natural streamflow-缪驰远&#xff08;北京师范大学地理科学学部&#xff09; 研究内容&#xff1a…...

落枕、肩颈酸痛,用磁疗就可缓解!

睡觉之前还是好好的&#xff0c;一觉醒来脖子莫名疼痛&#xff0c;转都转不了&#xff0c;有时候连肩膀和上肢都难受&#xff0c;很可能是“落枕”了。 落枕引起的肩颈疼痛与多种因素有关&#xff0c;如颈肩部肌肉的过度使用、不良的睡眠姿势或颈肩部受寒湿空气的侵袭&#xff…...

一文教会你如何选择远程桌面(五大主流远程软件全面讲解)

写在前面 作为程序员的我们&#xff0c;随时随地写代码改代码是我们的日常。刚回到家&#xff0c;就被老板、产品经理cue是常有的事。基于这种情况&#xff0c;一般都会随身携带电脑&#xff0c;随时备战&#xff0c;不过每天背着电脑上下班非常不方便。因此资深程序员的解决方…...

【yolov5】yolov5训练自己的数据集全流程----包含本人设计的快速数据处理脚本

关于yolo应用时能用到的脚本集合&#xff0c;推荐收藏&#xff1a; https://chenlinwei.blog.csdn.net/article/details/127299428 1. 工程化快速yolo训练流程指定版&#xff08;无讲解&#xff09; 1.1 抽样数据集xml转txt输出量化分析 python make_dataset.pymake_dataset…...