基于Qt ffmpeg opengl开发跨平台安卓实时投屏软件QtScrcpy

[开源]基于Qt ffmpeg opengl开发跨平台安卓实时投屏软件

码云地址 https://gitee.com/Barryda/QtScrcpy
github地址 https://github.com/barry-ran/QtScrcpy

如果需要更加专业的投屏和批量操作软件,可以尝试作者开发的极限投屏:

  • 极限投屏功能&特点:
    • 设备投屏&控制:批量投屏、单个控制、批量控制
    • 分组管理
    • wifi投屏/OTG投屏
    • adb shell快捷指令
    • 文件传输、apk安装
    • 投屏数量多:在OTG投屏模式,设置分辨率和流畅度为低的情况下,单台电脑可以同时管理500+台手机
    • 低延迟:usb投屏1080p延迟在30ms以内,在相同分辨率流畅度情况下,比市面上所有投屏软件延迟都低
    • cpu占用率低:纯C++开发,高性能GPU视频渲染
    • 高分辨率:可调节,最大支持安卓终端的原生分辨率
    • 完美中文输入:支持闲鱼app,支持三星手机
    • 免费版最多投屏20台,功能无限制(除了自动重新投屏)
  • 极限投屏使用教程:https://lrbnfell4p.feishu.cn/docx/QRMhd9nImorAGgxVLlmczxSdnYf
  • 极限投屏qq交流群:822464342
  • 极限投屏界面预览:
    在这里插入图片描述

课程介绍

课程地址:https://edu.csdn.net/course/detail/10750
本课程是一个音视频相关的Qt项目的实战教程,涉及Qt开发实际项目的完整流程。基于qt ffmpeg opengl实现了安卓手机实时投屏到电脑端,电脑端键鼠控制安卓手机的功能。可以自定义按键映射从而实现键鼠玩吃鸡手游的效果。基于Qt的跨平台特性,本软件支持windows,linux,mac三大主流平台。本课程适合以下人员:

  • 计算机专业的学生:学习了解公司中实战项目的开发流程,掌握自己的一个实战项目,对于毕业答辩或者找工作在简历中增加实战经验都是非常有帮助的。
  • Qt新手:你可以从中学到如何使用Qt开发一个实际的项目,学习巩固Qt基础知识,使自己的Qt技能更上一层楼。
  • 工作多年的程序员:常年从事Qt或者客户端开发,准备跳槽了发现很多公司要求有音视频相关的技能,那么本课程中的音视频介绍,H264视频解码,opengl渲染yuv等技术点可以带你进入音视频领域。

这里有绿色包供你下载体验,windows版本绿色包链接:https://pan.baidu.com/s/1C4OVUj4FAHnQSMyzNhBgvA 密码:a5nr 购买课程后,课程全部源码在第二章第4节课件中下载,可到QtScrcpy学员群 901736468 中和同学们学习交流,讲师会在学员群给同学答疑解惑。

软件介绍

  • 截图

安卓手机实时投屏到电脑,不是模拟器!不是模拟器!不是模拟器!重要的事情说三遍
Windows平台展示
mac平台展示
linux平台展示

  • 点此查看视频展示
  • 功能
    • 实时同步安卓屏幕到电脑
    • 电脑鼠标键盘实时控制安卓手机
    • 支持游戏映射(键鼠吃鸡)
  • 优点
    • 低延迟:300ms以内,720p吃鸡感觉不到延迟,具体根据视频分辨率有关
    • cpu占用率低:视频渲染使用GPU
    • 高分辨率:可调节,最大支持安卓终端的原生分辨率
    • 跨平台:同时支持win,mac,linux
    • 支持usb+无线连接
  • 新增功能
    • mp4录制
    • 点亮屏幕,调节音量等辅助功能
    • 视频分辨率调节
      技术栈

  • Qt相关技术实战

    • Qt多线程,多进程,自定义事件
    • 网络编程
    • qss自定义界面
    • 键盘鼠标事件处理
  • 音视频

    • 音视频基础知识
    • ffmpeg解码h264为yuv
    • opengl基础
    • opengl渲染yuv

你的收获

  • 学习巩固Qt开发相关内容,提高使用Qt开发熟练度
  • 掌握基于Qt开发跨平台实战项目的基本流程
  • 掌握音视频相关知识
  • 锻炼独立解决问题的能力,学会搜索
  • 履历中增加音视频项目的经验
  • 提高c++编码实战能力
  • 本项目完整代码,使用QtCreator直接编译运行
  • 加群与志同道合的朋友一起学习QtScrcpy,群聊号码:901736468
    互相交流,共同进步

技术要求

  • C++必须会
  • 网络编程必须了解:知道tcp协议,会用socket
  • Qt基础最好会,或者其他界面编程经验
  • 105
    点赞
  • 305
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 73
    评论
以下是一个使用QtFFmpegOpenGLWidget播放RTSP流的示例代码: 首先,确保已经安装了QtFFmpeg库,并在Qt项目中添加了相应的依赖项。 在Qt项目中创建一个自定义的OpenGLWidget类,用于显示视频帧: ```cpp // myopenglwidget.h #ifndef MYOPENGLWIDGET_H #define MYOPENGLWIDGET_H #include <QOpenGLWidget> #include <QOpenGLFunctions> #include <QOpenGLBuffer> #include <QOpenGLShaderProgram> #include <QOpenGLTexture> class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: explicit MyOpenGLWidget(QWidget *parent = nullptr); ~MyOpenGLWidget(); protected: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; private: QOpenGLBuffer m_vertexBuffer; QOpenGLShaderProgram m_shaderProgram; QOpenGLTexture m_texture; float m_vertices[12] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f }; }; #endif // MYOPENGLWIDGET_H ``` ```cpp // myopenglwidget.cpp #include "myopenglwidget.h" MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } MyOpenGLWidget::~MyOpenGLWidget() { } void MyOpenGLWidget::initializeGL() { initializeOpenGLFunctions(); m_vertexBuffer.create(); m_vertexBuffer.bind(); m_vertexBuffer.allocate(m_vertices, sizeof(m_vertices)); m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, "attribute vec3 aPosition;" "void main() {" " gl_Position = vec4(aPosition, 1.0);" "}"); m_shaderProgram.link(); m_shaderProgram.bind(); m_texture.create(); m_texture.setMinificationFilter(QOpenGLTexture::Nearest); m_texture.setMagnificationFilter(QOpenGLTexture::Linear); } void MyOpenGLWidget::resizeGL(int w, int h) { glViewport(0, 0, w, h); } void MyOpenGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT); m_vertexBuffer.bind(); m_shaderProgram.bind(); int vertexLocation = m_shaderProgram.attributeLocation("aPosition"); m_shaderProgram.enableAttributeArray(vertexLocation); m_shaderProgram.setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3); glDrawArrays(GL_QUADS, 0, 4); } ``` 接下来,创建一个Qt窗口类,并在其中使用FFmpeg来解码和播放RTSP流,并将帧渲染到OpenGLWidget中: ```cpp // mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QThread> #include <QTimer> #include <QImage> #include <QMutex> #include "myopenglwidget.h" extern "C" { #include <libavformat/avformat.h> #include <libswscale/swscale.h> } class VideoDecoder : public QThread { Q_OBJECT public: explicit VideoDecoder(QObject *parent = nullptr); ~VideoDecoder(); void setUrl(const QString &url); void stop(); signals: void frameDecoded(const QImage &image); protected: void run() override; private: QString m_url; bool m_stopRequested; QMutex m_mutex; void decodePacket(AVPacket *packet, AVCodecContext *codecContext, SwsContext *swsContext); }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void onFrameDecoded(const QImage &image); void onTimerTimeout(); private: MyOpenGLWidget *m_openglWidget; VideoDecoder *m_videoDecoder; QTimer *m_timer; }; #endif // MAINWINDOW_H ``` ```cpp // mainwindow.cpp #include "mainwindow.h" VideoDecoder::VideoDecoder(QObject *parent) : QThread(parent), m_stopRequested(false) { } VideoDecoder::~VideoDecoder() { stop(); } void VideoDecoder::setUrl(const QString &url) { m_url = url; } void VideoDecoder::stop() { QMutexLocker locker(&m_mutex); m_stopRequested = true; } void VideoDecoder::run() { av_register_all(); AVFormatContext *formatContext = nullptr; AVCodecContext *codecContext = nullptr; SwsContext *swsContext = nullptr; if (avformat_open_input(&formatContext, m_url.toUtf8().constData(), nullptr, nullptr) != 0) { qDebug() << "Failed to open input file"; return; } if (avformat_find_stream_info(formatContext, nullptr) < 0) { qDebug() << "Failed to find stream info"; avformat_close_input(&formatContext); return; } int videoStreamIndex = -1; for (unsigned int i = 0; i < formatContext->nb_streams; ++i) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { qDebug() << "Failed to find video stream"; avformat_close_input(&formatContext); return; } AVCodec *codec = avcodec_find_decoder(formatContext->streams[videoStreamIndex]->codecpar->codec_id); if (!codec) { qDebug() << "Failed to find decoder"; avformat_close_input(&formatContext); return; } codecContext = avcodec_alloc_context3(codec); if (!codecContext) { qDebug() << "Failed to allocate codec context"; avformat_close_input(&formatContext); return; } if (avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar) < 0) { qDebug() << "Failed to copy codec parameters to context"; avcodec_free_context(&codecContext); avformat_close_input(&formatContext); return; } if (avcodec_open2(codecContext, codec, nullptr) < 0) { qDebug() << "Failed to open codec"; avcodec_free_context(&codecContext); avformat_close_input(&formatContext); return; } AVPacket *packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); while (av_read_frame(formatContext, packet) >= 0) { if (m_stopRequested) break; if (packet->stream_index == videoStreamIndex) { decodePacket(packet, codecContext, swsContext); } av_packet_unref(packet); } av_packet_free(&packet); av_frame_free(&frame); avcodec_free_context(&codecContext); avformat_close_input(&formatContext); sws_freeContext(swsContext); } void VideoDecoder::decodePacket(AVPacket *packet, AVCodecContext *codecContext, SwsContext *swsContext) { AVFrame *frame = av_frame_alloc(); int ret = avcodec_send_packet(codecContext, packet); if (ret < 0) { qDebug() << "Error sending packet to decoder"; av_frame_free(&frame); return; } ret = avcodec_receive_frame(codecContext, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { av_frame_free(&frame); return; } else if (ret < 0) { qDebug() << "Error receiving frame from decoder"; av_frame_free(&frame); return; } QImage image(codecContext->width, codecContext->height, QImage::Format_RGB888); uint8_t *srcData[4] = { frame->data[0], frame->data[1], frame->data[2], nullptr }; int srcLinesize[4] = { frame->linesize[0], frame->linesize[1], frame->linesize[2], 0 }; uint8_t *dstData[1] = { image.bits() }; int dstLinesize[1] = { image.bytesPerLine() }; sws_scale(swsContext, srcData, srcLinesize, 0, codecContext->height, dstData, dstLinesize); emit frameDecoded(image); av_frame_free(&frame); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_openglWidget(new MyOpenGLWidget(this)), m_videoDecoder(new VideoDecoder(this)), m_timer(new QTimer(this)) { setCentralWidget(m_openglWidget); connect(m_videoDecoder, &VideoDecoder::frameDecoded, this, &MainWindow::onFrameDecoded); connect(m_timer, &QTimer::timeout, this, &MainWindow::onTimerTimeout); // 设置RTSP流的URL QString rtspUrl = "rtsp://example.com/stream"; m_videoDecoder->setUrl(rtspUrl); m_videoDecoder->start(); // 设置定时器来刷新OpenGLWidget int frameRate = 30; // 帧率 int timerInterval = 1000 / frameRate; m_timer->start(timerInterval); } MainWindow::~MainWindow() { m_videoDecoder->stop(); m_videoDecoder->wait(); } void MainWindow::onFrameDecoded(const QImage &image) { m_openglWidget->update(); // 触发OpenGLWidget的重绘事件 } void MainWindow::onTimerTimeout() { // 在OpenGLWidget的paintGL()函数中绘制当前帧 QMutexLocker locker(m_videoDecoder->getMutex()); QImage image = m_videoDecoder->getImage(); if (!image.isNull()) { // 将图像数据复制到OpenGLWidget中 // ... // 更新OpenGLWidget m_openglWidget->update(); } } ``` 这只是一个简单的示例,具体的实现可能会根据你的需求有所调整。你可以根据实际情况修改代码以适应你的应用程序。同时,你还需要根据QtFFmpeg的文档进行更详细的学习和了解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 73
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Barry__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值