首页 > 编程语言 >Qt/C++ 音视频开发 - FFmpeg 安卓版

Qt/C++ 音视频开发 - FFmpeg 安卓版

时间:2024-09-30 14:54:00浏览次数:12  
标签:codecContext FFmpeg 示例 C++ 音视频 include LOCAL

Qt/C++ 音视频开发 - FFmpeg 安卓版

介绍

FFmpeg 是一个开源的多媒体框架,它可以用来录制、转换和流式传输音视频。在 Qt/C++ 开发中,FFmpeg 可以用于处理各种音视频任务,例如转码、推流等。将 FFmpeg 集成到安卓平台上,可以实现强大的移动端音视频处理功能。

应用使用场景

  • 视频播放:在安卓设备上播放多种格式的视频文件。
  • 视频编辑:对视频进行剪切、拼接、添加滤镜等编辑操作。
  • 实时推流:将设备的视频流实时推送到流媒体服务器。
  • 视频采集:从摄像头或麦克风采集视频/音频,并保存到文件或进行实时处理。
  • 格式转换:将视频从一种格式转换为另一种格式。

下面是关于在安卓设备上使用 FFmpeg 和 Qt 实现的视频播放、视频编辑、实时推流、视频采集和格式转换的代码实例。

视频播放

示例说明

这段示例代码展示了如何使用 FFmpeg 在安卓设备上播放多种格式的视频文件。使用 Qt 进行界面显示,FFmpeg 用于解码视频帧并将其呈现在 QLabel 中。

示例代码

Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
main.cpp
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QLabel>
#include <QThread>
#include <QImage>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

class VideoPlayer : public QWidget {
    Q_OBJECT

public:
    VideoPlayer(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);
        openButton = new QPushButton("Open Video", this);
        videoLabel = new QLabel(this);

        layout->addWidget(openButton);
        layout->addWidget(videoLabel);

        connect(openButton, &QPushButton::clicked, this, &VideoPlayer::openVideo);

        setLayout(layout);
    }

private slots:
    void openVideo() {
        QString fileName = QFileDialog::getOpenFileName(this, "Open Video File", "", "Videos (*.mp4 *.avi)");
        if (!fileName.isEmpty()) {
            playVideo(fileName);
        }
    }

    void playVideo(const QString &filePath) {
        AVFormatContext *formatContext = avformat_alloc_context();
        if (avformat_open_input(&formatContext, filePath.toStdString().c_str(), nullptr, nullptr) != 0) {
            qDebug() << "Failed to open video file";
            return;
        }

        AVCodec *codec = nullptr;
        AVCodecContext *codecContext = nullptr;
        int videoStreamIndex = -1;

        for (unsigned int i = 0; i < formatContext->nb_streams; ++i) {
            if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                codec = avcodec_find_decoder(formatContext->streams[i]->codecpar->codec_id);
                codecContext = avcodec_alloc_context3(codec);
                avcodec_parameters_to_context(codecContext, formatContext->streams[i]->codecpar);
                videoStreamIndex = i;
                break;
            }
        }

        if (!codec || !codecContext) {
            qDebug() << "Failed to find video codec";
            return;
        }

        avcodec_open2(codecContext, codec, nullptr);

        AVFrame *frame = av_frame_alloc();
        AVPacket packet;
        struct SwsContext *sws_ctx = 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 (packet.stream_index == videoStreamIndex) {
                if (avcodec_send_packet(codecContext, &packet) == 0) {
                    if (avcodec_receive_frame(codecContext, frame) == 0) {
                        AVFrame *rgbFrame = av_frame_alloc();
                        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 32);
                        uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
                        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 32);

                        sws_scale(sws_ctx, frame->data, frame->linesize, 0, codecContext->height, rgbFrame->data, rgbFrame->linesize);

                        QImage img(rgbFrame->data[0], codecContext->width, codecContext->height, QImage::Format_RGB888);
                        videoLabel->setPixmap(QPixmap::fromImage(img));

                        av_free(buffer);
                        av_frame_free(&rgbFrame);
                    }
                }
            }
            av_packet_unref(&packet);
            QThread::msleep(30);  // 控制播放速度
        }

        sws_freeContext(sws_ctx);
        av_frame_free(&frame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
    }

private:
    QPushButton *openButton;
    QLabel *videoLabel;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    VideoPlayer player;
    player.show();

    return app.exec();
}

#include "main.moc"

视频编辑

示例说明

这段示例代码展示了如何使用 FFmpeg 进行视频剪切和拼接。通过调用 FFmpeg 的命令行工具,可以方便地实现视频编辑操作。

示例代码

main.cpp
#include <QCoreApplication>
#include <QProcess>
#include <QStringList>
#include <QDebug>

void cutVideo(const QString &inputFile, const QString &outputFile, int startTime, int duration) {
    QStringList arguments;
    arguments << "-i" << inputFile
              << "-ss" << QString::number(startTime)
              << "-t" << QString::number(duration)
              << "-c" << "copy"
              << outputFile;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished();
    qDebug() << process.readAllStandardOutput();
}

void mergeVideos(const QStringList &inputFiles, const QString &outputFile) {
    QFile concatFile("concat.txt");
    if (concatFile.open(QIODevice::WriteOnly)) {
        QTextStream out(&concatFile);
        for (const QString &file : inputFiles) {
            out << "file '" << file << "'\n";
        }
        concatFile.close();
    }

    QStringList arguments;
    arguments << "-f" << "concat"
              << "-safe" << "0"
              << "-i" << "concat.txt"
              << "-c" << "copy"
              << outputFile;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished();
    qDebug() << process.readAllStandardOutput();
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 视频剪切
    cutVideo("/path/to/input.mp4", "/path/to/output_cut.mp4", 10, 20);

    // 视频拼接
    QStringList inputFiles = {"/path/to/input1.mp4", "/path/to/input2.mp4"};
    mergeVideos(inputFiles, "/path/to/output_merged.mp4");

    return a.exec();
}

实时推流

示例说明

这段示例代码展示了如何使用 FFmpeg 将设备的视频流实时推送到流媒体服务器。

示例代码

main.cpp
#include <QCoreApplication>
#include <QProcess>
#include <QStringList>
#include <QDebug>

void streamToServer(const QString &inputDevice, const QString &serverUrl) {
    QStringList arguments;
    arguments << "-f" << "dshow"
              << "-i" << inputDevice
              << "-vcodec" << "libx264"
              << "-preset" << "ultrafast"
              << "-f" << "flv"
              << serverUrl;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished(-1); // 无限等待,直到推流结束
    qDebug() << process.readAllStandardOutput();
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 实时推流
    streamToServer("video=Integrated Camera", "rtmp://your.streaming.server/live/streamkey");

    return a.exec();
}

视频采集

示例说明

这段示例代码展示了如何使用 FFmpeg 从摄像头或麦克风采集视频和音频,并保存到文件中。

示例代码

main.cpp
#include <QCoreApplication>
#include <QProcess>
#include <QStringList>
#include <QDebug>

void captureVideo(const QString &outputFile) {
    QStringList arguments;
    arguments << "-f" << "dshow"
              << "-i" << "video=IntegratedCamera"
              << "-c:v" << "libx264"
              << "-preset" << "ultrafast"
              << outputFile;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished(-1); // 无限等待,直到采集结束
    qDebug() << process.readAllStandardOutput();
}

void captureAudio(const QString &outputFile) {
    QStringList arguments;
    arguments << "-f" << "dshow"
              << "-i" << "audio=Microphone (Realtek High Definition Audio)"
              << "-c:a" << "aac"
              << outputFile;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished(-1); // 无限等待,直到采集结束
    qDebug() << process.readAllStandardOutput();
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 视频采集
    captureVideo("/path/to/output_video.mp4");

    // 音频采集
    captureAudio("/path/to/output_audio.aac");

    return a.exec();
}

格式转换

示例说明

这段示例代码展示了如何使用 FFmpeg 将视频从一种格式转换为另一种格式。

示例代码

main.cpp
#include <QCoreApplication>
#include <QProcess>
#include <QStringList>
#include <QDebug>

void convertFormat(const QString &inputFile, const QString &outputFile) {
    QStringList arguments;
    arguments << "-i" << inputFile
              << "-c:v" << "libx264"
              << "-preset" << "fast"
              << "-c:a" << "aac"
              << outputFile;

    QProcess process;
    process.start("ffmpeg", arguments);
    process.waitForFinished();
    qDebug() << process.readAllStandardOutput();
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 格式转换
    convertFormat("/path/to/input.avi", "/path/to/output.mp4");

    return a.exec();
}

原理解释

FFmpeg 原理

FFmpeg 由多个库组成,包括 libavcodec(编解码器)、libavformat(格式解析)、libavfilter(滤镜)等。它通过调用这些底层库,提供强大的音视频处理能力。

基本流程

  1. 初始化:加载 FFmpeg 库,设置必要的参数。
  2. 打开输入文件或流:读取音视频文件或流数据。
  3. 解码:将压缩的数据解码为原始的音频或视频帧。
  4. 处理:对音视频帧进行处理,例如滤镜效果、编码等。
  5. 输出:将处理后的帧写入文件或流。

算法原理流程图

启动程序 初始化FFmpeg 打开输入文件或流 解码音视频帧 处理音视频帧 编码音视频帧 输出到文件或流 清理和结束

算法原理解释

  1. 初始化 FFmpeg:调用 av_register_all() 等函数,初始化 FFmpeg 库。
  2. 打开输入文件或流:使用 avformat_open_input() 打开输入源,获取格式上下文。
  3. 解码音视频帧:读取压缩的音视频数据,并使用解码器将其解码为原始帧。
  4. 处理音视频帧:对原始帧进行处理,例如应用滤镜、调整参数等。
  5. 编码音视频帧:将处理后的帧重新编码为指定格式。
  6. 输出到文件或流:将编码后的数据写入输出文件或推送到流媒体服务器。
  7. 清理和结束:释放所有资源,关闭文件或流。

实际应用代码示例实现

示例代码

以下是一个简单的示例代码,将 FFmpeg 集成到 Qt/C++ 中,并实现视频播放功能:

Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := ffmpeg/lib/armeabi-v7a/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
main.cpp
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QLabel>
#include <QThread>
#include <QDebug>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}

class VideoPlayer : public QWidget {
    Q_OBJECT

public:
    VideoPlayer(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);
        openButton = new QPushButton("Open Video", this);
        videoLabel = new QLabel(this);

        layout->addWidget(openButton);
        layout->addWidget(videoLabel);

        connect(openButton, &QPushButton::clicked, this, &VideoPlayer::openVideo);

        setLayout(layout);
    }

private slots:
    void openVideo() {
        QString fileName = QFileDialog::getOpenFileName(this, "Open Video File", "", "Videos (*.mp4 *.avi)");
        if (!fileName.isEmpty()) {
            playVideo(fileName);
        }
    }

    void playVideo(const QString &filePath) {
        AVFormatContext *formatContext = avformat_alloc_context();
        if (avformat_open_input(&formatContext, filePath.toStdString().c_str(), nullptr, nullptr) != 0) {
            qDebug() << "Failed to open video file";
            return;
        }

        AVCodec *codec = nullptr;
        AVCodecContext *codecContext = nullptr;
        int videoStreamIndex = -1;

        for (unsigned int i = 0; i < formatContext->nb_streams; ++i) {
            if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                codec = avcodec_find_decoder(formatContext->streams[i]->codecpar->codec_id);
                codecContext = avcodec_alloc_context3(codec);
                avcodec_parameters_to_context(codecContext, formatContext->streams[i]->codecpar);
                videoStreamIndex = i;
                break;
            }
        }

        if (!codec || !codecContext) {
            qDebug() << "Failed to find video codec";
            return;
        }

        avcodec_open2(codecContext, codec, nullptr);

        AVFrame *frame = av_frame_alloc();
        AVPacket packet;
        struct SwsContext *sws_ctx = 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 (packet.stream_index == videoStreamIndex) {
                if (avcodec_send_packet(codecContext, &packet) == 0) {
                    if (avcodec_receive_frame(codecContext, frame) == 0) {
                        AVFrame *rgbFrame = av_frame_alloc();
                        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 32);
                        uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
                        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 32);

                        sws_scale(sws_ctx, frame->data, frame->linesize, 0, codecContext->height, rgbFrame->data, rgbFrame->linesize);

                        QImage img(rgbFrame->data[0], codecContext->width, codecContext->height, QImage::Format_RGB888);
                        videoLabel->setPixmap(QPixmap::fromImage(img));

                        av_free(buffer);
                        av_frame_free(&rgbFrame);
                    }
                }
            }
            av_packet_unref(&packet);
            QThread::msleep(30);  // 控制播放速度
        }

        sws_freeContext(sws_ctx);
        av_frame_free(&frame);
        avcodec_free_context(&codecContext);
        avformat_close_input(&formatContext);
    }

private:
    QPushButton *openButton;
    QLabel *videoLabel;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    VideoPlayer player;
    player.show();

    return app.exec();
}

#include "main.moc"

测试代码

  1. 将上述代码保存到项目文件中。
  2. 确保在 Android.mk 文件中正确配置了 FFmpeg 库路径。
  3. 使用 Qt Creator 创建新项目并添加这些文件。
  4. 配置好 Android 的 NDK 和 SDK。
  5. 编译并部署到安卓设备。

部署场景

  1. 开发环境部署

    • 安装 Qt 和 Android 工具链,包括 Android NDK 和 SDK。
    • 下载并编译 FFmpeg 库,以适应安卓平台。
    • 配置 Qt 工程,以使用 FFmpeg 库。
  2. 生产环境部署

    • 将编译好的 APK 部署到安卓设备。
    • 确保设备有足够的权限访问摄像头以及读写文件的权限。
    • 进行全面测试,确保所有功能都能正常工作。

材料链接

总结

通过将 FFmpeg 集成到 Qt/C++ 项目中,我们可以实现强大且灵活的音视频处理功能。本文介绍了在安卓平台上使用 FFmpeg 的基本步骤,包括初始化、解码、处理和编码等。实际应用代码示例展示了如何在安卓设备上播放视频。

未来展望

随着移动端计算能力的提升,越来越多的复杂音视频处理任务可以在移动设备上完成。未来我们可以期待以下发展:

  • 更高效的编解码器:如 AV1 等新型编解码器,可以提供更高的压缩率和更好的画质。
  • 实时处理:结合 AI 和机器学习技术,实现实时的视频分析和处理,如对象检测、动作识别等。
  • 跨平台统一开发:使用 Qt 可以更容易地在多个平台间共享代码,提高开发效率。
  • 增强现实(AR)和虚拟现实(VR):结合 AR 和 VR 技术,提供沉浸式的音视频体验。

通过不断创新和优化,可以进一步提升音视频处理系统的性能和用户体验,为各种应用场景提供更加丰富和智能的解决方案。

标签:codecContext,FFmpeg,示例,C++,音视频,include,LOCAL
From: https://blog.csdn.net/feng1790291543/article/details/141852256

相关文章

  • 南沙C++信奥赛陈老师解一本通题 2005:【20CSPJ普及组】直播获奖
    ​ 【题目描述】NOI2130即将举行。为了增加观赏性,CCF决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为 w%w%,即当前排名前 w%w% 的选手的最低成绩就是即时的分数线。更具体地,若当前已评出了 pp 个选手的成绩,则当前计划获奖人数为 max(1,⌊p∗w%......
  • 南沙C++信奥赛陈老师解一本通题1965:【14NOIP普及组】珠心算测验
    ​ 【题目描述】珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正整数集合,集合中的数各不......
  • C++入门
    第1节:开发环境的搭建与配置1.1目标在本节课中,学生将学习如何在Windows上搭建一个现代化的C++开发环境,并使用VSCode和CMake工具进行C++程序的开发与调试。学生将掌握以下内容:安装VSCode及C++插件安装MinGW或其他C++编译器安装并配置CMake创建并编译第一个C++项目使用VSCod......
  • 南沙C++信奥赛陈老师解一本通题:1945:【09NOIP普及组】多项式输出
    ​ 【题目描述】一元 nn 次多项式可用如下的表达式表示: f(x)=anxn+an−1xn−1+...+a1x+a0,an≠0f(x)=anxn+an−1xn−1+...+a1x+a0,an≠0 其中,aixii 称为i次项,ai称为ii次项的系数。给出一个一元多项式各项的次数和系数,请按照如下规定的格式要求输出该多项式:1.多项式中......
  • 分享C++程序员面试八股文(十四)
    以下是C++常见八股文(十四):一、C++中的智能指针高级用法(AdvancedUsageofSmartPointers)解释unique_ptr、shared_ptr和weak_ptr的循环引用问题及解决方法循环引用问题:当使用shared_ptr进行相互引用时,可能会导致循环引用问题。例如,两个对象相互持有对方的shared_pt......
  • c++:引用
    一、引用概念是什么?引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"类型&引用变量名(对象名)=引用实体;voidTestRef(){inta=10;int......
  • C++发邮件:如何轻松实现邮件自动化发送?
    C++发邮件的详细步骤与教程指南?如何在C++中发邮件?无论是定期发送报告、通知客户还是管理内部沟通,自动化邮件系统都能显著提升工作效率。AokSend将详细介绍如何使用C++发邮件,实现邮件自动化发送,帮助您轻松管理邮件通信。C++发邮件:配置环境在选择了合适的C++发邮件库之后,接下......
  • 南沙C++信奥赛陈老师解一本通题 1269:【例9.13】庆功会
    ​ 【题目描述】为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。【输入】第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。接......
  • C++ struct和class的异同、C中和C++中struct的异同
    一、前言C++中的struct结构体和C语言中的struct结构体差异较大。C++中的struct结构体和C++中的class类极为相似。二、C++的struct和class1.相同点      (1)成员     struct和class都可以在主体中定义成员变量和成员函数!两者在定义成员变量和成员函数上......
  • C++ string的基本运用详细解剖
    string的基本操作一.与C语言中字符串的区别二.标准库中的string三.string中常用接口的介绍1.string中常用的构造函数2.string类对象的容量操作函数3.string类对象的访问及遍历操作4.string类对象的修改操作5.string类的非成员函数6.string中的其他一些操作一.与C语言......