用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位,ffmpeg用的版本是较新的5.1版本。它支持TCP或UDP方式拉取实时流,实时流我采用的是监控摄像头的RTSP流。音频播放采用的是QAudioOutput,视频经ffmpeg解码并由YUV转RGB后是在QOpenGLWidget下进行渲染显示。本…...

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

代理模式详解
本文首更于《从零开始手把手教你实现一个简单的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%的白领在一年内做过副业,你有做副业吗?
现在大部分人收入单一,收入都是来源于本职工作,当没有了工作就没有了收入的来源,而生活压力又很大,各种开支,各种消费。所以很多人想要增加收入来源,增加被动收入,同时通过副业提升自己的价值和…...

第二十三周周报
学习内容: 修改ViTGAN代码 学习时间: 2.3-2.10 学习产出: 现在的效果 可以看到在700k左右fid开始上升,相比vitgan,改的vitgan鉴别器loss有所下降,但是fid没有降下来,最好为23.134…...

2023年Q1业绩增长背后,迪士尼亟待扭转流媒体亏损困局
重新执掌迪士尼后,鲍勃伊格尔交出了一份表现尚可的“答卷”。 图源:迪士尼 美东时间2023年2月8日,迪士尼披露了2023财年Q1财报,营收为235.1亿美元,同比增长8%;持续经营净利润13亿美元,同比增长11%。受此利…...

LKWA靶场通关和源码分析
文章目录一、Blind RCE?二、XSSI三、PHP Object Injection四、PHP Object Injection(cookie)五、PHP Object Injection(Referer)六、PHAR七、SSRF八、Variables总结一、Blind RCE? 源码: <?php include("sidebar.php"); /***…...
logcpp demo
step1:nug下载log4cppstep2:实现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上手
之前看有数据显示国内的三高人群越来越年轻,很多人不到三十就有了高血压、高血糖的问题,埋下了不小的健康隐患,加上前阵子的疫情管控放松,人们了解到了新冠病毒对心脏负担的认知,预防慢病被大众提上了日程,…...
算法训练营 day42 动态规划 理论基础 斐波那契数 爬楼梯 使用最小花费爬楼梯
算法训练营 day42 动态规划 理论基础 斐波那契数 爬楼梯 使用最小花费爬楼梯 理论基础 动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。 所以动态规划中每一个状…...

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

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

Mybatis框架(全部基础知识)
👌 棒棒有言:也许我一直照着别人的方向飞,可是这次,我想要用我的方式飞翔一次!人生,既要淡,又要有味。凡事不必太在意,一切随缘,缘深多聚聚,缘浅随它去。凡事…...

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

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

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

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

嵌入式开发:通过嵌入式虚
嵌入式虚拟化为实现多核处理能力的优势提供了一种可扩展的机制。嵌入式应用中的虚拟化与其企业和桌面应用有许多共同之处。独特的嵌入式使用案例和专业的底层技术为嵌入式开发人员提供了优化性能和响应设计的新机会。在台式机、数据中心以及现在的嵌入式设计中采用多核技术可以…...
广州穗雅医院杨济安:了解症状表现 有效防治口腔黏膜下纤维化
“医生,我出现口干大半年时间,最近两月张嘴费劲,吃点辣的,嘴就刺疼刺疼的,这是怎么回事?”半年前,家住南沙的文先生走进广州穗雅医院口腔黏膜科如是说到。在科室杨济安主任的详细问诊与检查后&a…...

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

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...