多媒体框架 FFmpeg 和 GStreamer

前言

多媒体框架是一种软件框架,它在电脑上处理媒体数据并通过网络进行传播。一个良好的多媒体框架提供了直观的API和模块化的架构,这使得它易于添加对新的音频、视频和容器格式以及传输协议的支持。

多媒体框架不仅可以支持媒体播放器和音视频编辑器等程序,还可以用于编译视频会议程序、媒体转换器及其他多媒体工具。

多媒体框架通常包括以下几个层级:

  • 编解码层:负责对音视频数据进行编码和解码。
  • 封装层:处理音视频数据的压缩和封装。
  • 协议层:管理网络数据的传输和通信协议。
  • 应用层:整合整个流程,并进行统筹管理。

常见的多媒体框架包括:FFmpeg、GStreamer、DirectShow(微软)、AVFoundation(苹果)、OpenMax等,下面以 FFmpeg 和 GStreamer 为例介绍。

一、FFmpeg

FFmpeg是一个非常强大的开源多媒体处理框架,它提供了一系列用于处理音频、视频和多媒体流的工具和库。它也是最流行且应用最广泛的框架!

官网地址:

FFmpeg 的主要特点和功能:

  1. 编解码器支持:FFmpeg 支持众多音视频编解码器,包括常见的 H.264、H.265、AAC、MP3 等,也支持一些不常见的编解码器。

  2. 格式支持:它支持多种多媒体格式的解析和封装,包括 AVI、MP4、MKV、FLV、MOV 等。

  3. 转码和处理:FFmpeg 可以进行音视频的转码、裁剪、拼接、水印添加等处理操作,使其在不同格式、分辨率和编码方式之间进行转换。

  4. 流媒体处理:它支持从摄像头、文件或网络流等源接收多媒体流,并能进行实时处理和转发,用于流媒体直播和视频会议等场景。

  5. 滤镜和特效:FFmpeg 提供了丰富的滤镜和特效,可以实现图像处理、色彩调整、模糊、锐化等效果。

  6. 音频处理:它能够进行音频的分割、合并、音量调整、混音等操作。

  7. 跨平台性:FFmpeg 是跨平台的,可以在 Windows、MacOS、Linux 等操作系统上运行。

  8. 开源和免费:FFmpeg 是完全开源的,可以免费使用,并且具有活跃的社区支持和持续的更新和改进。

1.1 安装FFmpeg

更新软件包列表:

shell 复制代码
sudo apt update

安装 FFmpeg:

shell 复制代码
sudo apt install ffmpeg -y

验证是否成功安装:

shell 复制代码
ffmpeg -version

安装 FFmpeg 拓展开发包:

shell 复制代码
sudo apt install libavcodec-dev -y
sudo apt install libavformat-dev -y
sudo apt install libavcodec-extra -y

1.2 使用命令行执行ffmpeg

转换视频格式:

  • 将输入的 MP4 文件转换为 AVI 格式。
shell 复制代码
ffmpeg -i input.mp4 output.avi

播放视频:

shell 复制代码
ffplay input.mp4
ffplay output.avi

合并视频和音频:

  • 将一个视频文件和一个音频文件合并为一个 MP4 文件,视频流不变,音频重新编码为 AAC 格式。
shell 复制代码
ffmpeg -i video.mp4 -i audio.mp3 -c:v copy -c:a aac -strict experimental output.mp4
ffplay output.mp4

调整视频大小:

  • 将输入的 MP4 文件调整为 1280x720 分辨率的输出。
shell 复制代码
ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4
ffplay output.mp4

改变视频帧率:

  • 将输入的 MP4 文件的帧率改为 24 帧每秒。
shell 复制代码
ffmpeg -i input.mp4 -r 10 output.mp4
ffplay output.mp4

提取视频中的帧:

  • 将从输入的 MP4 文件中提取第 100 帧,并将其保存为 PNG 图像文件。
shell 复制代码
ffmpeg -i input.mp4 -vf "select=eq(n\,100)" -vsync vfr output.png
ffplay output.png

添加水印:

  • 将一个水印图像叠加到输入的 MP4 文件的左上角。
shell 复制代码
ffmpeg -i input.mp4 -i test.jpg -filter_complex "overlay=10:10" output.mp4
ffplay output.mp4

合并多个视频:

shell 复制代码
echo "file 'Project.mp4'" > filelist.txt
echo "file 'OTA.mp4'" >> filelist.txt
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output_safe.mp4
ffplay output.mp4

去掉视频中的音频:

shell 复制代码
ffmpeg -i output_safe.mp4 -c:v copy -an output.mp4

1.3 代码实现视频格式转换

步骤:

  • 初始化编码器
  • 打开输入文件,查找视频流
  • 初始化编解码设置
  • 打开编解码器,分配编码输出格式
  • 打开输出文件,写入文件头
  • 循环读取并写入帧
  • 写入输出文件尾,关闭文件
  • 释放资源

示例代码:

c++ 复制代码
#include <iostream>
#include <string>
#include <cstdlib>

// FFmpeg库的头文件
extern "C" {
#include <libavformat/avformat.h> // 包含用于处理多媒体格式的函数
#include <libavcodec/avcodec.h> // 包含用于编解码的函数
#include <libavutil/opt.h> // 包含用于设置选项的辅助函数
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " input.mp4 output.avi" << std::endl;
        return 1;
    }

    const char* input_filename = argv[1];// 获取输入文件名
    const char* output_filename = argv[2];// 获取输出文件名

    av_register_all();// 注册所有的文件格式和编解码器

    AVFormatContext* format_ctx = nullptr; // 初始化一个用于存储格式配置的指针
    if ((&format_ctx, input_filename, nullptr, nullptr) != 0) {
        std::cerr << "Error: Could not open input file" << std::endl;
        return 1;
    }

    //// 寻找视频流信息
    if (avformat_find_stream_info(format_ctx, nullptr) < 0) {
        std::cerr << "Error: Could not find stream information" << std::endl;
        avformat_close_input(&format_ctx);// 获取流信息失败,打印错误信息并退出程序
        return 1;
    }

    AVCodec* codec = nullptr;// 初始化一个用于存储编解码器的指针
    AVCodecContext* codec_ctx = nullptr;// 初始化一个用于存储编解码器配置的指针

    // 查找最佳的视频流,并检查是否是HEVC编码
    int video_stream_index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, AV_CODEC_ID_HEVC);
    if (video_stream_index < 0) {
        std::cerr << "Error: Could not find HEVC video stream in input file" << std::endl;
        avformat_close_input(&format_ctx);
        return 1;
    }

    codec_ctx = avcodec_alloc_context3(codec);// 为找到的编解码器分配配置
    if (!codec_ctx) {
        std::cerr << "Error: Could not allocate codec context" << std::endl;
        avformat_close_input(&format_ctx);
        return 1;
    }
    
    // 将视频流的参数复制到编解码器配置中
    if (avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar) < 0) {
        std::cerr << "Error: Could not copy codec parameters to context" << std::endl;
        avcodec_free_context(&codec_ctx); // 释放编解码器配置
        avformat_close_input(&format_ctx); // 关闭输入文件
        return 1;
    }

    // 打开编解码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
        std::cerr << "Error: Could not open codec" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return 1;
    }

    AVFormatContext* output_format_ctx = nullptr;// 初始化一个用于存储输出格式配置的指针
    if (avformat_alloc_output_context2(&output_format_ctx, nullptr, nullptr, output_filename) < 0) {
        std::cerr << "Error: Could not allocate output context" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        return 1;
    }

    AVStream* out_stream = avformat_new_stream(output_format_ctx, codec);// 在输出格式配置中创建新的流
    if (!out_stream) {
        std::cerr << "Error: Could not allocate output stream" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        avformat_free_context(output_format_ctx);// 释放输出格式配置
        return 1;
    }

    // 将编解码器配置的参数复制到输出流中
    if (avcodec_parameters_from_context(out_stream->codecpar, codec_ctx) < 0) {
        std::cerr << "Error: Could not copy codec parameters from context" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        avformat_free_context(output_format_ctx);
        return 1;
    }

    // 如果输出格式不是AVFMT_NOFILE类型,则打开输出文件
    if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_format_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
            std::cerr << "Error: Could not open output file" << std::endl;
            avcodec_free_context(&codec_ctx);
            avformat_close_input(&format_ctx);
            avformat_free_context(output_format_ctx);
            return 1;
        }
    }

    //写入文件头
    if (avformat_write_header(output_format_ctx, nullptr) < 0) {
        std::cerr << "Error: Could not write output file header" << std::endl;
        avcodec_free_context(&codec_ctx);
        avformat_close_input(&format_ctx);
        avformat_free_context(output_format_ctx);
        return 1;
    }

    AVPacket pkt;// 初始化一个用于存储压缩数据的数据包
    av_init_packet(&pkt);// 初始化数据包
    pkt.data = nullptr;// 设置数据包的数据指针为空
    pkt.size = 0;// 设置数据包的大小为0

    // 循环读取输入文件中的帧
    while (av_read_frame(format_ctx, &pkt) >= 0) {
        if (pkt.stream_index == video_stream_index) { // 检查当前帧是否属于视频流
            pkt.stream_index = out_stream->index; // 将帧的流索引设置为输出流的索引
            av_interleaved_write_frame(output_format_ctx, &pkt); // 将帧写入输出文件
        }
        av_packet_unref(&pkt);// 减少引用计数并可能释放数据包
    }

    av_write_trailer(output_format_ctx);// 写入输出文件的尾部信息

    if (output_format_ctx && !(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_format_ctx->pb);// 如果输出文件被打开了,关闭它
    }

    avcodec_free_context(&codec_ctx);// 释放编解码器配置
    avformat_close_input(&format_ctx);// 关闭输入文件
    avformat_free_context(output_format_ctx);// 释放输出格式配置

    return 0;
}

Makefile:

Makefile 复制代码
# 编译器
CXX := g++
# 编译选项
CXXFLAGS := -Wall -O2
# 头文件路径
INCLUDES := -I/usr/include/x86_64-linux-gnu/
# 库文件路径
LIBS := -L/usr/lib/x86_64-linux-gnu/
# 需要链接的库
LDLIBS := -lavformat -lavcodec -lavutil

# 目标文件
TARGET := convert

# 源文件列表
SRCS := main.cpp

# 生成目标文件列表
OBJS := $(SRCS:.cpp=.o)

# 默认目标
all: $(TARGET)

# 生成可执行文件
$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) $(INCLUDES) $(LIBS) -o $@ $^ $(LDLIBS)

# 生成目标文件
%.o: %.cpp
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@

# 清理生成的文件
clean:
	$(RM) $(TARGET) $(OBJS)

编译运行程序后,可以看到当前文件夹下从MP4转换后的AVI文件:

shell 复制代码
./convert input.mp4 output.avi

使用ffplay播放视频

shell 复制代码
ffplay output.avi

二、GStreamer

GStreamer 是一个功能强大的开源多媒体框架,用于创建、处理和播放音频和视频流。

官方网址:

GStreamer 的主要特点和功能:

  1. 模块化架构:GStreamer 的设计是基于模块化的架构,可以根据需要添加或移除各种插件和元件,从而实现灵活的功能扩展和定制。

  2. 跨平台性:GStreamer 可以在多种操作系统上运行,包括 Linux、Windows、macOS 等。

  3. 丰富的插件支持:GStreamer 提供了大量的插件,用于处理各种多媒体格式、编解码器、滤镜、特效等,可以满足各种多媒体处理需求。

  4. 流式处理:GStreamer 支持流式处理,可以处理实时音视频流,适用于流媒体直播、视频会议等场景。

  5. 音视频编解码支持:它支持多种常见的音视频编解码器,包括 H.264、H.265、AAC、MP3 等,也支持一些不常见的编解码器。

  6. 容器格式支持:GStreamer 支持多种多媒体容器格式的解析和封装,包括 AVI、MP4、MKV、FLV、MOV 等。

  7. 图形界面和命令行工具:GStreamer 提供了图形界面和命令行工具,用于配置和管理多媒体处理流程。

  8. 多语言支持:GStreamer 可以通过各种语言的绑定进行使用,包括 C、C++、Python、Java 等。

GStreamer的核心是基于管道(Pipeline)的概念,这意味着你可以将多个处理步骤(称为元素或 Element)连接起来,以实现复杂的媒体处理任务。

2.1 安装GStreamer

更新软件包列表

shell 复制代码
sudo apt update

安装 GStreamer

安装 GStreamer 的基本运行时库和插件。你可以根据需要选择不同的插件包,如 gstreamer1.0-plugins-basegstreamer1.0-plugins-good gstreamer1.0-plugins-badgstreamer1.0-plugins-ugly

shell 复制代码
sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio -y

这些插件包括了各种常见的音视频编解码器、文件格式支持以及其他的功能扩展。

安装额外的 GStreamer 插件(可选):

如果你需要更多特定功能的插件,你可以根据需要安装额外的插件包。例如,如果你需要使用 GStreamer 的 Python 绑定,可以安装 gir1.2-gst-plugins-base-1.0 包。

shell 复制代码
sudo apt install gir1.2-gst-plugins-base-1.0

验证安装

shell 复制代码
gst-inspect-1.0 --version

2.2 使用命令行执行GStreamer

转换视频格式:

  • 将输入的 MP4 文件转换为 MKV 格式。
shell 复制代码
gst-launch-1.0 -e filesrc location=input.mp4 ! qtdemux name=demux \
demux.video_0 ! queue ! decodebin ! videoconvert ! \
avenc_mpeg4 ! matroskamux name=mux \
demux.audio_0 ! queue ! decodebin ! audioconvert ! \
audioresample ! audioconvert ! mux. \
mux. ! filesink location=output.mkv

播放视频:

shell 复制代码
gst-play-1.0 input.mp4
gst-play-1.0 output.mkv

合并视频和音频:

  • 将一个视频文件和一个音频文件合并为一个 MP4 文件,视频流不变,音频流重新编码为 AAC 格式。
shell 复制代码
gst-launch-1.0 -e filesrc location=video.mp4 ! qtdemux name=demux \
demux.video_0 ! queue ! h264parse ! mux. \
filesrc location=audio.mp3 ! decodebin ! audioconvert ! voaacenc ! aacparse ! mux. \
qtmux name=mux ! filesink location=output.mp4

gst-play-1.0 output.mp4

调整视频大小:

  • 将输入的 MP4 文件调整为 1280x720 分辨率的输出。
shell 复制代码
gst-launch-1.0 filesrc location=input.mp4 ! decodebin ! videoscale ! video/x-raw,width=1280,height=720 ! videoconvert ! x264enc ! mp4mux ! filesink location=output.mp4

改变视频帧率:

  • 将输入的 MP4 文件的帧率改为 24 帧每秒。
shell 复制代码
gst-launch-1.0 filesrc location=input.mp4 ! decodebin ! videorate ! video/x-raw,framerate=24/1 ! videoconvert ! x264enc ! mp4mux ! filesink location=output.mp4

提取视频中的帧:

  • 将从输入的 MP4 文件中提取视频中的图像,并保存为 PNG 图像文件。
shell 复制代码
gst-launch-1.0 filesrc location=input.mp4 ! qtdemux ! decodebin ! videorate ! video/x-raw,framerate=1/1 ! videoconvert ! pngenc ! filesink location=output.png

视频叠加文字:

  • 将从输入的 MP4 视频上叠加文字并将结果保存到输出文件中。
shell 复制代码
gst-launch-1.0 filesrc location=input.mp4 ! decodebin ! videoconvert ! textoverlay text="Hello, World!" valignment=top halignment=left ! x264enc ! mp4mux ! filesink location=output.mp4

2.3 第一个GStreamer 应用程序

C代码实现 MP4 文件的播放,步骤如下:

  • 初始化 GStreamer 库
  • 构建管道
  • 播放视频
  • 等待错误 / 文件结束信号
  • 释放资源

代码示例:

c 复制代码
#include <gst/gst.h>

int tutorial_main(int argc, char *argv[]) {
    GstElement *pipeline;
    GstBus *bus;
    GstMessage *msg;

    /* Initialize GStreamer */
    gst_init(&argc, &argv);

    /* Build the pipeline */
    pipeline = gst_parse_launch("playbin uri=file:/home/ubuntu/Code/Gstreamer/GStreamer-helloWorld/input.mp4", NULL);

    /* Start playing */
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* Wait until error or EOS */
    bus = gst_element_get_bus(pipeline);
    msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* See next tutorial for proper error message handling/parsing */
    if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
        g_error("An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
                "variable set for more details.");
    }

    /* Free resources */
    gst_message_unref(msg);
    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}

int main(int argc, char *argv[]) {
    return tutorial_main(argc, argv);
}

Makefile:

Makefile 复制代码
# 编译器
CC := gcc
# 编译选项
CCFLAGS := -Wall -O2
# 头文件路径
INCLUDES := -I/usr/include/x86_64-linux-gnu/ -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
# 库文件路径
LIBS := -L/usr/lib/x86_64-linux-gnu/
# 需要链接的库
LDLIBS := -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0 

TARGET = basic-tutorial

SRCS = main.c

# 生成目标文件列表
OBJS := $(SRCS:.c=.o)

# 默认目标
all: $(TARGET)

# 生成可执行文件
$(TARGET): $(OBJS)
	$(CC) $(CXXFLAGS) $(INCLUDES) $(LIBS) -o $@ $^ $(LDLIBS)

# 生成目标文件
%.o: %.c
	$(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@

# 清理生成的文件
clean:
	$(RM) $(TARGET) $(OBJS)

编译运行:

shell 复制代码
make
./basic-tutorial

2.4 创建并连接管道

C代码实现创建元素并将元素相互连接,步骤如下:

  • 初始化 GStreamer 库
  • 创建元素和管道
  • 构建管道并链接元素
  • 输入测试源属性
  • 开始播放并等待错误 / EOS
  • 释放资源

代码示例:

c 复制代码
#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
  GstElement *pipeline, *source, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  sink = gst_element_factory_make ("autovideosink", "sink");

  /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

  if (!pipeline || !source || !sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
  if (gst_element_link (source, sink) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Modify the source's properties */
  g_object_set (source, "pattern", 0, NULL);

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Wait until error or EOS */
  bus = gst_element_get_bus (pipeline);
  msg =
      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  /* Parse message */
  if (msg != NULL) {
    GError *err;
    gchar *debug_info;

    switch (GST_MESSAGE_TYPE (msg)) {
      case GST_MESSAGE_ERROR:
        gst_message_parse_error (msg, &err, &debug_info);
        g_printerr ("Error received from element %s: %s\n",
            GST_OBJECT_NAME (msg->src), err->message);
        g_printerr ("Debugging information: %s\n",
            debug_info ? debug_info : "none");
        g_clear_error (&err);
        g_free (debug_info);
        break;
      case GST_MESSAGE_EOS:
        g_print ("End-Of-Stream reached.\n");
        break;
      default:
        /* We should not reach here because we only asked for ERRORs and EOS */
        g_printerr ("Unexpected message received.\n");
        break;
    }
    gst_message_unref (msg);
  }

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  return 0;
}

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  return gst_macos_main ((GstMainFunc) tutorial_main, argc, argv, NULL);
#else
  return tutorial_main (argc, argv);
#endif
}

Makefile:

Makefile 复制代码
# 编译器
CC := gcc
# 编译选项
CCFLAGS := -Wall -O2
# 头文件路径
INCLUDES := -I/usr/include/x86_64-linux-gnu/ -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
# 库文件路径
LIBS := -L/usr/lib/x86_64-linux-gnu/
# 需要链接的库
LDLIBS := -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0

TARGET = concepts-tutorial

SRCS = main.c

# 生成目标文件列表
OBJS := $(SRCS:.c=.o)

# 默认目标
all: $(TARGET)

# 生成可执行文件
$(TARGET): $(OBJS)
	$(CC) $(CXXFLAGS) $(INCLUDES) $(LIBS) -o $@ $^ $(LDLIBS)

# 生成目标文件
%.o: %.c
	$(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@

# 清理生成的文件
clean:
	$(RM) $(TARGET) $(OBJS)

编译运行:

shell 复制代码
make
./concepts-tutorial
相关推荐
小鹿研究点东西6 小时前
AI直播系统怎么搭?
人工智能·ffmpeg·自动化·音视频·语音识别
Nightwish58 小时前
Oracle 数据库巡检检查清单
数据库·oracle·ffmpeg
luoyayun36120 小时前
Qt/QML + FFmpeg 实现多音频文件顺序拼接功能
qt·ffmpeg·音频拼接
wbcuc1 天前
ffmpeg工具把m4s合并为mp4 powershell脚本
ffmpeg·m4s
luoyayun3612 天前
Qt + FFmpeg 实战:实现音频格式转换功能
qt·ffmpeg·音频格式转换
都在酒里3 天前
【极致低延时】香橙派部署 MediaMTX 实现 WebRTC 推流,延时仅 500-800ms,比局域网 ffmpeg 拉流快近 10 倍!(附踩坑全记录)
linux·arm开发·ffmpeg·webrtc·orangepi·嵌入式软件
Empty-Filled3 天前
用 Kap + FFmpeg 把录屏转成小体积 GIF:产品操作演示图制作实践
ffmpeg·kap
矜辰所致4 天前
嵌入式语音开发应用基础说明
ffmpeg·ai 语音·嵌入式语音·语音播放·语音采样
luoyayun3614 天前
Qt + FFmpeg 实战:音频静音段检测
qt·ffmpeg·音视频·静音段检测