用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或者数据库方式去…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...