嵌入式Qt 开发一个视频播放器

news2024/9/21 22:47:40

上篇文章:嵌入式 Qt开发一个音乐播放器,使用Qt制作了一个音乐播放器,并在OK3568开发板上进行了运行测试,实际测试效果还不错。

本篇继续来实现一个Qt视频播放器软件,可以实现视频列表的显示与选择播放等,先来看下最终的效果:

本篇的Qt代码从野火开发板的例程中移植修改而来,下面分析下程序的代码结构。

1 视频播放器开发总体结构

整个Qt视频播放器项目的代码结构如下:

  • 主代码中是视频播放器相关的代码,包括:
    • 视频播放器主界面
    • 视频名列表界面:在视频播放时可以查看视频列表并切换视频
    • Qt视频播放界面:实现单纯的视频播放
    • 操作按钮界面:实现播放、暂停、继续、上一个、下一个、进度调节,音量调节
    • 播放预览列表界面:在进入视频播放之前的视频预览列表界面
  • Ui代码中使用一些Qt的基本功能,包括:
    • 一个Qt界面基类
    • 滑条功能类
    • 图标按钮显示类
    • 列表功能类
    • 工具类
    • 视频帧解析
    • 页面列表类
  • Skin中是一些图片资源和字体/皮肤定义
  • 最后是编译的中间文件和编译结果存储的目录

下面分类介绍了程序的主要代码实现。

2 通用界面代码

上篇介绍音乐播放器时,介绍过一些自定义控件的代码,本篇的视频播放器,可以复用这些代码:

  • qtwidgetbase:窗口基类,该类的一个主要功能是可以向窗口内添加图片形式的按钮
  • qtsliderbar:用于实现自定义滑条控件,分为水平滑条与竖直滑条
  • qtpixmapbutton:图片按钮,支持未按下和按下后的两种样式的图片显示
  • qtlistwidget :基础的列表显示

相比于音乐播放器,视频播放器还需要其它额外的功能,如下:

2.1 qttoolbar

工具条的基类,通过创建一个QPropertyAnimation属性动画对象,主要实现工具条的显示与隐藏的动画效果。

例如,在用户无操作一段时间后,将上方的标题栏和下方的播放工具栏隐藏。

主要代码如下:

QtToolBar::QtToolBar(QWidget *parent) : QtWidgetBase(parent)
{
    m_animation = new QPropertyAnimation(this, "pos");
    m_animation->setDuration(200);
    m_animation->setEasingCurve(QEasingCurve::Linear);
    connect(m_animation, SIGNAL(finished()), this, SIGNAL(signalFinihed()));
}

void QtToolBar::SetAnimation(const QPoint &startPos, const QPoint &endPos)
{
    m_animation->setStartValue(startPos);
    m_animation->setEndValue(endPos);
    m_animation->start();
}

2.2 qtvideowidgetsurface

视频帧解析类,继承自QAbstractVideoSurface类,这是一个抽象基类,通过实现它的派生类可以获取来自QMediaPlayer或QCamera视频的帧。

  • supportedPixelFormats 用于向Qt返回帧流的图像类型
  • present 用于获取到每一帧
class QtVideoWidgetSurface : public QAbstractVideoSurface
{
    Q_OBJECT
public:
    QtVideoWidgetSurface(QWidget *widget, QObject *parent = 0);

    QList<QVideoFrame::PixelFormat> supportedPixelFormats(
        QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
    bool isFormatSupported(const QVideoSurfaceFormat &format) const;

    bool start(const QVideoSurfaceFormat &format);
    void stop();

    bool present(const QVideoFrame &frame);

    QRect videoRect() const { return targetRect; }
    void updateVideoRect();

    void paint(QPainter *painter);

private:
    QWidget *widget;
    QImage::Format imageFormat;
    QRect targetRect;
    QSize imageSize;
    QRect sourceRect;
    QVideoFrame currentFrame;
};

2.3 qtpagelistwidget

页面形式的预览排布以及左右页面的滑动切换

2.3.1 QtPageListWidgetItem

该类用于表示列表中的每一个项,可以是字符,并且可以带有图片(图标):

class QtPageListWidgetItem
{
public:
    QtPageListWidgetItem(int id, const QStringList &args);
    QtPageListWidgetItem(int id, const QString &name);
    QtPageListWidgetItem(int id, const QString &name, const QPixmap &icon);
    QtPageListWidgetItem(int id, const QString &path, const QString &name, const QPixmap &icon);

    int m_nId;

    QString m_strText;
    QPixmap m_pixmapIcon;
    QString m_strBaseName;
    QString m_strPath;
    QRect m_rect;

    QStringList m_strMultiParameters;
};

2…3.2 QtPageListWidget

class QtPageListWidget : public QtWidgetBase
{
    Q_OBJECT
public:
    typedef enum
    {
        None,
        LeftDirection,
        RightDirection
    } MoveDirection;
    Q_PROPERTY(int xPos READ Xpos WRITE setXPos)

    explicit QtPageListWidget(QWidget *parent = 0);
    ~QtPageListWidget();

    void AddItem(int id, QtPageListWidgetItem *item);
    void SetItems(const QMap<int, QtPageListWidgetItem *> &items);

    void SetBackground(const QPixmap &pixmap);
    void SetBackground(const QColor &color);

    void SetPageCount(int count);
    void SetItemLayut(int rows, int columns);
    void SetItemLayoutSpace(int row, int col);

    void SetLoopbackChange(bool bOk);

signals:
    void currentItemClicked(QtPageListWidgetItem *item);
    void currentPageChanged(int index);

protected:
    QMap<int, QtPageListWidgetItem *> m_listItems;
    // skin
    QColor m_backgroundColor;
    QPixmap m_pixmapWallpaper;

    int m_nPageCnt;
    int m_nCurrentPage;
    int m_nPrevPage;

    int m_nLayoutRows;
    int m_nLayoutColumns;

    qreal m_nItemWidth;
    qreal m_nItemHeight;

    int m_nDirection;

    QTimer *m_timerMove;
    bool m_bLoopbackChange;
    QPropertyAnimation *m_animationMove;

private:
    bool m_bPressed;
    QPoint m_startPos;
    int m_nStartPos;
    int m_nMoveEndValue;
    bool m_bRecovery;

private:
    void setXPos(int nValue);
    int Xpos() { return m_nStartPos; }
    void resizeRect();
private slots:

protected:
    int m_nCurrentIndex;
    int m_nHorSpace;
    int m_nVSpace;

protected:
    void mousePressEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *);
    void mouseMoveEvent(QMouseEvent *e);

    void paintEvent(QPaintEvent *);
    void drawListItem(QPainter *painter, int page, int xOffset = 0);
    virtual void drawItemInfo(QPainter *painter, QtPageListWidgetItem *item);
};

注意,该类和qtlistwidget类的功能比较相似

3 视频播放器代码

首先是整体的主界面部分,进行各项功能的初始化显示,主要包括:

  • 播放列表初始化(视频预览列表界面)
  • 视频播放界面初始化(先隐藏播放界面)

界面效果如下:

主要代码逻辑:

{
    QtWidgetTitleBar *m_widgetTitle = new QtWidgetTitleBar(this);
    m_widgetTitle->SetScalSize(Skin::m_nScreenWidth, 80);
    m_widgetTitle->SetBackground(Qt::transparent);
    m_widgetTitle->setFont(QFont(Skin::m_strAppFontNormal));
    m_widgetTitle->SetTitle(tr("视频播放器"), "#ffffff", 32);

    QVBoxLayout *verLayoutCentor = new QVBoxLayout(this);
    verLayoutCentor->setContentsMargins(0, 0, 0, 0);
    verLayoutCentor->setSpacing(0);
    verLayoutCentor->addWidget(m_widgetTitle, 1);

    // 播放列表(视频预览列表界面)
    m_videosList = new VideoListViewer(this);
    m_videosList->SetBackground(Qt::transparent);
    connect(m_videosList, SIGNAL(currentItemClicked(QtPageListWidgetItem *)), this, SLOT(SltItemClicked(QtPageListWidgetItem *)));
    verLayoutCentor->addWidget(m_videosList, 5);

    // 视频播放界面(先隐藏播放界面)
    m_videoWidget = new QtVideoWidget(this);
    m_videoWidget->hide();
}

初始化各个界面后,还要连接对应的槽函数,如切换视频播放列表中的视频时的处理等。

3.1 视频播放

视频播放部分,主要有两部分功能:

  • 视频解码播放:使用Qt自带的媒体播放器组件进行音频播放
  • 播放时的按钮操作:实现播放、暂停、继续、上一个、下一个、进度调节,音量调节

3.1.1 QMediaPlayer播放视频

这里使用Qt自带的QMediaPlayer组件进行视频的播放,QMediaPlayer播放视频于播放音频的步骤类似。

本项目的视频播放初始化部分:

QtVideoWidget::QtVideoWidget(QWidget *parent) : QtWidgetBase(parent)
{
    m_urlMedia = QUrl();
    m_bToolBarShow = false;

    m_nDuration = 0;
    m_nPostion = 0;

    m_player = new QMediaPlayer(this);
    surface = new QtVideoWidgetSurface(this);
    m_player->setVideoOutput(surface);

    m_playList = new MediaPlayListWidget(this);
    m_playList->setVisible(false);
    m_player->setPlaylist(m_playList->palyList());

    connect(m_player, SIGNAL(durationChanged(qint64)), this, SLOT(SltDurationChanged(qint64)));
    connect(m_player, SIGNAL(positionChanged(qint64)), this, SLOT(SltPostionChanged(qint64)));

    m_titleBar = new PlayTitleBarWidget(this);
    connect(m_playList, SIGNAL(signalMediaChanged(QString)), m_titleBar, SLOT(SetText(QString)));
    connect(m_titleBar, SIGNAL(signalBack()), this, SLOT(SltBackClicked()));

    m_playBar = new PlayerBarWidget(this);
    connect(m_playBar, SIGNAL(signalPlay(bool)), this, SLOT(SltBtnPlayClicked(bool)));
    connect(m_playBar, SIGNAL(currentPostionChanged(int)), this, SLOT(SltChangePostion(int)));
    connect(m_playBar, SIGNAL(signalPrev()), m_playList->palyList(), SLOT(previous()));
    connect(m_playBar, SIGNAL(signalNext()), m_playList->palyList(), SLOT(next()));
    connect(m_playBar, SIGNAL(signalMuenList()), this, SLOT(SltShowMenuList()));
    connect(m_playBar, SIGNAL(signalVolume()), this, SLOT(SltChangeVolume()));

    m_timerShow = new QTimer(this);
    m_timerShow->setSingleShot(true);
    m_timerShow->setInterval(5000);
    connect(m_timerShow, SIGNAL(timeout()), this, SLOT(SltAutoCloseToolBar()));

    m_volumeSlider = new QtSliderBar(this);
    m_volumeSlider->SetHorizontal(false);
    m_volumeSlider->SetValue(100);
    m_volumeSlider->hide();
    connect(m_volumeSlider, SIGNAL(currentValueChanged(int)), m_player, SLOT(setVolume(int)));
}

3.1.2 播放操作按钮

视频播放时,需要用到播放、暂停、继续、上一个、下一个、进度调节、音量调节等操作按钮,这里的对应按钮使用图标显示为对应的按钮,通过加载QPixmap来实现:

void PlayerBarWidget::InitWidget()
{
    m_btns.insert(0, new QtPixmapButton(0, QRect(295, 40, 60, 60), QPixmap(":/images/video/ic_prev.png"), QPixmap(":/images/video/ic_prev_pre.png")));
    m_btns.insert(1, new QtPixmapButton(1, QRect(375, 40, 60, 60), QPixmap(":/images/video/ic_play.png"), QPixmap(":/images/video/ic_pause.png")));
    m_btns.insert(2, new QtPixmapButton(2, QRect(465, 40, 60, 60), QPixmap(":/images/video/ic_next.png"), QPixmap(":/images/video/ic_next_pre.png")));
    m_btns.insert(3, new QtPixmapButton(3, QRect(655, 40, 60, 60), QPixmap(":/images/video/ic_volume.png"), QPixmap(":/images/video/ic_volume_pre.png")));
    m_btns.insert(4, new QtPixmapButton(4, QRect(728, 40, 60, 60), QPixmap(":/images/video/ic_list.png"), QPixmap(":/images/video/ic_list_pre.png")));
    m_btns.value(1)->setCheckAble(true);

    // 进度条
    m_progressBar = new QtSliderBar(this);
    m_progressBar->SetHorizontal(true);
    m_progressBar->SetSliderSize(2, 40);
    connect(m_progressBar, SIGNAL(currentValueChanged(int)), this, SIGNAL(currentPostionChanged(int)));
}

3.2 播放列表

播放列表功能用来实现视频文件的显示,以及手动指定切换要播放的视频。

3.2.1 读取视频文件

通过读取指定目录下的文件,查找类型为mp4的视频文件,构造视频播放列表。

void MediaPlayListWidget::ScanDirMedias(const QString &path)
{
    QDir dir(path);
    if (!dir.exists())
        return;
    dir.setFilter(QDir::Dirs | QDir::Files);
    dir.setSorting(QDir::DirsFirst);
    QFileInfoList list = dir.entryInfoList();
    int index = m_mapItems.size();
    for (int i = 0; i < list.size(); i++)
    {
        QFileInfo fileInfo = list.at(i);
        if (fileInfo.fileName() == "." || fileInfo.fileName() == "..")
        {
            continue;
        }

        if (fileInfo.isDir())
        {
            ScanDirMedias(fileInfo.filePath());
        }
        else if (fileInfo.suffix() == "mp4")
        {
            QString strName = fileInfo.baseName().toLocal8Bit().constData();
            QString strPath = fileInfo.absoluteFilePath().toLocal8Bit().constData();
            m_playList->addMedia(QUrl::fromLocalFile(strPath));
            m_mapItems.insert(index, new QtListWidgetItem(index, strPath, strName, QPixmap(":/images/video/ic_video_preview.png")));
            index++;
        }
    }
}

3.2.2 视频列表界面

视频列表的具体界面实现,主要是在对应的矩形位置,显示各个视频文件的名称:

void VideoListViewer::drawItemInfo(QPainter *painter, QtPageListWidgetItem *item)
{
    painter->save();
    QFont font(Skin::m_strAppFontBold);
    font.setPixelSize(18);
    painter->setFont(font);

    QPixmap pixmap = item->m_pixmapIcon;
    int nXoffset = (item->m_rect.width() - pixmap.width()) / 2;
    int nYoffset = (item->m_rect.height() - pixmap.height() - painter->fontMetrics().height() - 10) / 2;
    QRect rectPixmap(nXoffset + item->m_rect.left(), item->m_rect.top() + nYoffset, pixmap.width(), pixmap.height());
    if (pixmap.isNull())
    {
        painter->setPen(QColor("#7effffff"));
        painter->drawRect(item->m_rect);
    }
    else
    {
        painter->drawPixmap(rectPixmap.topLeft(), pixmap);
    }

    painter->setPen(Qt::white);
    painter->drawText(item->m_rect.left(), rectPixmap.bottom() + 10, item->m_rect.width(), 30, Qt::AlignCenter, item->m_strBaseName);
    painter->restore();
}

3.3 视频预览列表

在进入正式的视频播放前,会先有一个视频预览列表界面,在这个界面会列出指定搜索目录下的所有视频,点击任意视频图标即可进入播放界面。

此部分的列表绘制代码逻辑如下:

void QtPageListWidget::drawItemInfo(QPainter *painter, QtPageListWidgetItem *item)
{
    painter->save();

    QRect rect = item->m_rect;
    int nXoffset = (rect.width() > ICON_SIZE) ? (rect.width() - ICON_SIZE) / 2 : 0;
    QRect rectPixmap(rect.left() + nXoffset + ICON_SIZE / 2, rect.top() + ICON_SIZE / 2, ICON_SIZE, ICON_SIZE);
    if (item->m_pixmapIcon.isNull())
    {
        painter->setPen(Qt::NoPen);
        painter->setBrush(QColor("#02a7f0"));
        painter->drawEllipse(rectPixmap.topLeft(), ICON_SIZE / 2, ICON_SIZE / 2);
    }
    else
    {
        painter->drawPixmap(rect.left() + nXoffset, rect.top(), item->m_pixmapIcon);
    }

    if (m_nCurrentIndex == item->m_nId)
    {
        painter->setPen(Qt::NoPen);
        painter->setBrush(QColor("#32000000"));
        painter->drawEllipse(rectPixmap.topLeft(), ICON_SIZE / 2, ICON_SIZE / 2);
    }

    QFont font = painter->font();
    font.setPixelSize(16);
    painter->setFont(font);
    painter->setPen("#ffffff");
    int nTextHeight = painter->fontMetrics().height();
    QRect rectText(rect.left(), rect.bottom() - nTextHeight, rect.width(), nTextHeight);
    painter->drawText(rectText, Qt::AlignCenter, item->m_strText);
    painter->restore();
}

4 编译与测试

4.1 在Windows平台编译

Qt程序编写好后,可以先在Windows电脑上编译查看效果,在运行时,可能会遇到如下问题:

DirectShowPlayerService::doRender: Unresolved error code 0x80040266 (IDispatch error #102)

编译的时候没问题,但运行的时候报错:

这是因为Qt 中的多媒体播放,底层是使用DirectShowPlayerService,所以Windows电脑上需要先安装一个DirectShow解码器,例如LAV Filters。可以到这里(https://github.com/Nevcairiel/LAVFilters/releases)下载并安装 LAVFilters:

下载exe安装包安装即可,安装后,再运行就不会报错了,在Windows系统上运行视频播放器的效果如下:

4.2 交叉编译并在板子上运行

通过交叉编译,来测试视频播放器在OK3568-C板子上的播放效果。

使用编译音乐播放器时编写my3568build.sh的编译脚本编译即可,无需修改:

#! /bin/bash

mkdir -p build
cd build

export PATH=/home/xxpcb/myTest/OK3568/sourcecode/OK3568-linux-source/buildroot/output/OK3568/host/bin:$PATH

qmake .. && make

执行该脚本即可编译:

./my3568build.sh 

将编译成功的可执行文件VIdeoPlayer复制到OK3568-C板子中,然后在其同目录下创建一个video文件夹,里面放入若干个mp4视频文件,然后就可以测试了,此外,代码中,还指定了/userdata/media这个搜索目录,这里有板子自带的一些视频:

文件复制到板子,我这里仍然使用的ADB无线传输的方式,比较方便,具体ADB操作演示,可参考之前这篇文章: RK3568源码编译与交叉编译环境搭建最后的Qt交叉编译与测试部分

需注意的是,板子里的这个Linux系统,不支持中文的显示,视频名不要有中文。

实测效果见文末视频,整体体验播放流畅,视频切换流程,进度条调整播放进度没有音乐播放器调整的顺畅,另外不知道怎么原因,视频播放的声音外放声音特别小,用耳机插孔听是正常的。

5 总结

本篇介绍了使用Qt开发一个视频播放器,一些功能代码是复用上篇的音乐播放器的代码,使用Qt Creator编写视频播放器的代码,首先在Windows电脑上编译运行测试,然后交叉编译,在OK3568-C开发板上进行实际测试。

该视频播放器实现的功能包括基础的播放功能、暂停与继续,音量调节,视频列表显示,下一个、下一个切换,进度条调节播放进度等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/359539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【干货】如何用低代码帮助企业实施OKR?

近年来受疫情的影响&#xff0c;许多企业都开始使用 OKR来进行目标管理。OKR是一套让企业持续保持活力的有效管理工具&#xff0c;能够帮助企业实现目标、激励员工、增加团队凝聚力、减少组织内耗&#xff0c;从而进一步实现创新。但是在实际布局中&#xff0c;很多企业在使用 …

基础面试题 :大端、小端及转换方式

理解网络中大端和小端往往是一道基础面试题 &#xff0c;这里作为记录和整理&#xff0c;希望能帮到大家 目录 前言 一、字节序 二、什么小端顺序 三、什么大端顺序 四、处理器体系所属网络字节顺序 五、大小端转换 1、大端整形转换为小端 2、小端转换为小端 3、…

知乎x-zse-96 参数补环境

本文精工学习参考 目标链接 aHR0cHM6Ly93d3cuemhpaHUuY29tL3NlYXJjaD90eXBlPWNvbnRlbnQmcT1weXRob24接口分析 参数x-zse-93&#xff1a;相当于版本号 参数x-zse-96&#xff1a;看起来需要破解 参数x-zst-81&#xff1a;请求发现可以置空 综上所述x-zse-96才需要逆向。 参数…

中间件安全—Apache常见漏洞

中间件安全—Apache常见漏洞1.Apache常见漏洞1.1.Apache介绍1.2.Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;1.2.1.漏洞介绍1.2.2.漏洞环境1.2.2.1.运行漏洞环境1.2.2.2.访问漏洞环境1.2.3.漏洞复现1.2.3.1.拦截1.2.3.2.添加换行1.2.3.3.访问文件1.3.Apa…

[机器学习]卷积神经网络DLC

一、基本结构 CNN的大概模式可以总结为&#xff1a;卷积层池化层全连接层激活函数 而一些比较大型的网络如VGG一般将CNN作为构成单元进行堆叠&#xff0c;而内部卷积核和池化也可以堆叠多个。各个部分的功能如下&#xff1a; 卷积&#xff1a;特征提取 池化&#xff1a;降维和防…

硬件设备 之一 详解 JTAG、SWD 接口、软 / 硬件断点、OpenOCD、J-link

JTAG 和 SWD 在嵌入式开发中可以说是随处可见&#xff0c;他们通常被用来配合 J-Link 、ULINK、ST-LINK 等仿真器在线调试嵌入式程序。此外&#xff0c;还有飞思卡尔芯片中的 Background debug mode&#xff08;BDM&#xff09; 接口&#xff0c;Atmel 芯片中的 debugWIRE &…

文本生成图像应用指南【Stable Diffusion】

Stable Diffusion 是一种文本到图像的潜在扩散模型&#xff0c;由来自 CompVis、Stability AI 和 LAION 的研究人员和工程师创建。 它使用来自 LAION-5B 数据库子集的 512x512 图像进行训练。 稳定扩散&#xff0c;生成人脸&#xff0c;也可以在自己的机器上运行&#xff0c;如…

车载技术开发—{Android CarFrameWork}

Android Automotive平台 Android Automotive是通过Android的通用框架&#xff0c;语言和API来实现的一个全栈&#xff0c;开源&#xff0c;高度可定制的平台。 Android Automotive与整个Android生态系统的关系 Android Automotive是Android的一部分。 Android Automotive不是…

pbootcms被黑木马问题(3)

昨天经过同事告知发现了很早之间做的几个企业官方都中木马了,然后看了一下木马情况,跟之间的两次都有所不同,这里记录一下新的木马的清理过程,有遇到的朋友可以借鉴一下。&#xff08;之前有做过一些防止批量扫站的措施&#xff0c;因为嫌麻烦就没有给这些网站上进行修改&#…

【Spark分布式内存计算框架——Spark SQL】13. 自定义UDF函数

第七章 自定义UDF函数 无论Hive还是SparkSQL分析处理数据时&#xff0c;往往需要使用函数&#xff0c;SparkSQL模块本身自带很多实现公共功能的函数&#xff0c;在org.apache.spark.sql.functions中。SparkSQL与Hive一样支持定义函数&#xff1a;UDF和UDAF&#xff0c;尤其是U…

黑格尔的实践观探究

&#xff08;江苏大学马克思主义学院 212000&#xff09;一、引言人的独特性在于实践活动&#xff0c;以及由实践活动带来的人类社会的不断进化与发展。人类的实践史体现了人的全部本质。但是&#xff0c;人类从理论的高度反思自己的实践活动&#xff0c;尤其是在哲学的层面上进…

【基础算法】之 冒泡排序优化

冒泡排序思想基本思想: 冒泡排序&#xff0c;类似于水中冒泡&#xff0c;较大的数沉下去&#xff0c;较小的数慢慢冒起来&#xff08;假设从小到大&#xff09;&#xff0c;即为较大的数慢慢往后排&#xff0c;较小的数慢慢往前排。直观表达&#xff0c;每一趟遍历&#xff0c;…

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——shuffle机制

3.3.1Shuffle机制 Map方法之后&#xff0c;Reduce方法之前的数据处理过程称之为Shuffle。 3.3.2Partition分区 1、问题引出 要求将统计结果按照条件输出到不同文件中&#xff08;分区&#xff09;。比如&#xff1a;将统计结果按照手机归属地不同省份输出到不同文件中&#…

2023春季露营投影怎么选?轻薄投影极米Z6X Pro值得推荐

近年来&#xff0c;露营经济在多重因素的共同助推下快速发展&#xff0c;精致露营的攻略开始占据小红书、微博、朋友圈等各类社交平台&#xff0c;吸引着更多用户种草并加入到露营大军中&#xff0c;而露营经济的强势“破圈”给家用智能投影带来了更多的发展契机。凭借着小巧的…

探访上汽通用武汉奥特能超级工厂

上汽通用汽车在电动化和智能网联化新技术领域投入了700亿大洋&#xff0c;武汉奥特能超级工厂就是其中一个重点项目。这个工厂已经投产&#xff0c;将成为上汽通用汽车的新能源生产基地&#xff0c;加速奥特能平台车型的推出。 最近别克推出了Electra E5&#xff0c;它是别克第…

新品BCM6755A1KFEBG/MT7921LE/MT7921AU WiFi芯片

博通在WiFi市场具有相当的实力。在WiFi6上有下面这几个解决方案&#xff1a;型号&#xff1a;BCM6755 BCM6755A1KFEBG类型&#xff1a;四核1.5GHz CPU封装&#xff1a;BGA批次&#xff1a;新BCM6755和BCM6750还是A7架构&#xff0c;更多的用在中低端型号上。BCM6755和BCM6750 C…

Spark 广播变量累加器

广播变量 场景描述&#xff1a;一份数据存在Driver中&#xff0c;但是每个Executor都需要一份。 常规模式下&#xff0c;Driver会给每个分区都发送一份数据。如果在Executor中存在多个分区的情况&#xff0c;那么一个Executor会获得多份数据。 Executor是进程&#xff0c;task…

微信小程序阻止页面返回(包滑动、自动返回键)

这个场景还是挺有意思的&#xff0c;比如某多多&#xff0c;只要你点左上角的返回 好家伙&#xff0c;满满又 花不了 的优惠券就来了&#xff0c;让你拥有一种消费最划算的感觉。 如果你的场景比较简单&#xff0c;只是对左上角的返回进行监听&#xff0c;只需要关闭自带的导航…

16_FreeRTOS队列集

目录 队列集 队列集相关API函数介绍 队列集使用流程 实验源码 队列集 一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集! 作用:用于对多个队列或信号量进行“监听”其中不管哪一个消息到来&#xff0c;都…

JVM学习笔记四:运行时数据区之虚拟机栈

目录 概述 StackOverflowError测试案例 栈运行原理 栈帧的内部结构 改变栈帧大小的StackOverflowError测试案例 局部变量表 局部变量槽 操作数栈 动态链接 静态链接 动态链接 早期绑定 晚期绑定 方法返回地址 概述 与程序计数器一样&#xff0c;Java虚拟机栈也是…