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(滤镜)等。它通过调用这些底层库,提供强大的音视频处理能力。
基本流程
- 初始化:加载 FFmpeg 库,设置必要的参数。
- 打开输入文件或流:读取音视频文件或流数据。
- 解码:将压缩的数据解码为原始的音频或视频帧。
- 处理:对音视频帧进行处理,例如滤镜效果、编码等。
- 输出:将处理后的帧写入文件或流。
算法原理流程图
算法原理解释
- 初始化 FFmpeg:调用
av_register_all()
等函数,初始化 FFmpeg 库。 - 打开输入文件或流:使用
avformat_open_input()
打开输入源,获取格式上下文。 - 解码音视频帧:读取压缩的音视频数据,并使用解码器将其解码为原始帧。
- 处理音视频帧:对原始帧进行处理,例如应用滤镜、调整参数等。
- 编码音视频帧:将处理后的帧重新编码为指定格式。
- 输出到文件或流:将编码后的数据写入输出文件或推送到流媒体服务器。
- 清理和结束:释放所有资源,关闭文件或流。
实际应用代码示例实现
示例代码
以下是一个简单的示例代码,将 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"
测试代码
- 将上述代码保存到项目文件中。
- 确保在
Android.mk
文件中正确配置了 FFmpeg 库路径。 - 使用 Qt Creator 创建新项目并添加这些文件。
- 配置好 Android 的 NDK 和 SDK。
- 编译并部署到安卓设备。
部署场景
-
开发环境部署:
- 安装 Qt 和 Android 工具链,包括 Android NDK 和 SDK。
- 下载并编译 FFmpeg 库,以适应安卓平台。
- 配置 Qt 工程,以使用 FFmpeg 库。
-
生产环境部署:
- 将编译好的 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