FFmpeg音频处理——音频混合、拼接、剪切、转码

2023-10-26

接触FFmpeg有一段时间了,它是音视频开发的开源库,几乎其他所有播放器、直播平台都基于FFmpeg进行二次开发。本篇文章来总结下采用FFmpeg进行音频处理:音频混合、音频剪切、音频拼接与音频转码。

采用android studio进行开发,配置build.gradle文件:

 

defaultConfig {
        ......
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            abiFilters "armeabi-v7a"
        }
    }

另外指定cmake文件路径:

 

 

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            jni.srcDirs = []
        }
    }

从FFmpeg官网下载源码,编译成ffmpeg.so动态库,并且导入相关源文件与头文件:

然后配置cMakeLists文件:

 

add_library( # Sets the name of the library.
             audio-handle

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/ffmpeg_cmd.c
             src/main/cpp/cmdutils.c
             src/main/cpp/ffmpeg.c
             src/main/cpp/ffmpeg_filter.c
             src/main/cpp/ffmpeg_opt.c)

add_library( ffmpeg
             SHARED
             IMPORTED )
set_target_properties( ffmpeg
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../libs/armeabi-v7a/libffmpeg.so )

include_directories(src/main/cpp/include)

find_library( log-lib
              log )

target_link_libraries( audio-handle
                       ffmpeg
                       ${log-lib} )

调用FFmpeg命令行进行音频处理:

 

 

    /**
     * 调用ffmpeg处理音频
     * @param handleType handleType
     */
    private void doHandleAudio(int handleType){
        String[] commandLine = null;
        switch (handleType){
            case 0://转码
                String transformFile = PATH + File.separator + "transform.aac";
                commandLine = FFmpegUtil.transformAudio(srcFile, transformFile);
                break;
            case 1://剪切
                String cutFile = PATH + File.separator + "cut.mp3";
                commandLine = FFmpegUtil.cutAudio(srcFile, 10, 15, cutFile);
                break;
            case 2://合并
                String concatFile = PATH + File.separator + "concat.mp3";
                commandLine = FFmpegUtil.concatAudio(srcFile, appendFile, concatFile);
                break;
            case 3://混合
                String mixFile = PATH + File.separator + "mix.aac";
                commandLine = FFmpegUtil.mixAudio(srcFile, appendFile, mixFile);
                break;
            default:
                break;
        }
        executeFFmpegCmd(commandLine);
    }

其中,音频混音、合并、剪切和转码的FFmpeg命令行的拼接如下:

 

 

    /**
     * 使用ffmpeg命令行进行音频转码
     * @param srcFile 源文件
     * @param targetFile 目标文件(后缀指定转码格式)
     * @return 转码后的文件
     */
    public static String[] transformAudio(String srcFile, String targetFile){
        String transformAudioCmd = "ffmpeg -i %s %s";
        transformAudioCmd = String.format(transformAudioCmd, srcFile, targetFile);
        return transformAudioCmd.split(" ");//以空格分割为字符串数组
    }

    /**
     * 使用ffmpeg命令行进行音频剪切
     * @param srcFile 源文件
     * @param startTime 剪切的开始时间(单位为秒)
     * @param duration 剪切时长(单位为秒)
     * @param targetFile 目标文件
     * @return 剪切后的文件
     */
    @SuppressLint("DefaultLocale")
    public static  String[] cutAudio(String srcFile, int startTime, int duration, String targetFile){
        String cutAudioCmd = "ffmpeg -i %s -ss %d -t %d %s";
        cutAudioCmd = String.format(cutAudioCmd, srcFile, startTime, duration, targetFile);
        return cutAudioCmd.split(" ");//以空格分割为字符串数组
    }

    /**
     * 使用ffmpeg命令行进行音频合并
     * @param srcFile 源文件
     * @param appendFile 待追加的文件
     * @param targetFile 目标文件
     * @return 合并后的文件
     */
    public static  String[] concatAudio(String srcFile, String appendFile, String targetFile){
        String concatAudioCmd = "ffmpeg -i concat:%s|%s -acodec copy %s";
        concatAudioCmd = String.format(concatAudioCmd, srcFile, appendFile, targetFile);
        return concatAudioCmd.split(" ");//以空格分割为字符串数组
    }

    /**
     * 使用ffmpeg命令行进行音频混合
     * @param srcFile 源文件
     * @param mixFile 待混合文件
     * @param targetFile 目标文件
     * @return 混合后的文件
     */
    public static  String[] mixAudio(String srcFile, String mixFile, String targetFile){
        String mixAudioCmd = "ffmpeg -i %s -i %s -filter_complex amix=inputs=2:duration=first -strict -2 %s";
        mixAudioCmd = String.format(mixAudioCmd, srcFile, mixFile, targetFile);
        return mixAudioCmd.split(" ");//以空格分割为字符串数组
    }

FFmpeg处理混音的公式如下,其中sample1为源文件采样率、sample2为待混合文件采样率:

 

混音公式:value = sample1 + sample2 - (sample1 * sample2 / (pow(2, 16-1) - 1))

开启子线程,调用native方法进行音频处理:

 

    public static void execute(final String[] commands, final OnHandleListener onHandleListener){
        new Thread(new Runnable() {
            @Override
            public void run() {
                if(onHandleListener != null){
                    onHandleListener.onBegin();
                }
                //调用ffmpeg进行处理
                int result = handle(commands);
                if(onHandleListener != null){
                    onHandleListener.onEnd(result);
                }
            }
        }).start();
    }
    private native static int handle(String[] commands);

关键的native方法,是把java传入的字符串数组转成二级指针数组,然后调用FFmpeg源码中的run方法:

 

 

JNIEXPORT jint JNICALL Java_com_frank_ffmpeg_FFmpegCmd_handle
(JNIEnv *env, jclass obj, jobjectArray commands){
    int argc = (*env)->GetArrayLength(env, commands);
    char **argv = (char**)malloc(argc * sizeof(char*));
    int i;
    int result;
    for (i = 0; i < argc; i++) {
        jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
        char* temp = (char*) (*env)->GetStringUTFChars(env, jstr, 0);
        argv[i] = malloc(1024);
        strcpy(argv[i], temp);
        (*env)->ReleaseStringUTFChars(env, jstr, temp);
    }
    //执行ffmpeg命令
    result =  run(argc, argv);
    //释放内存
    for (i = 0; i < argc; i++) {
        free(argv[i]);
    }
    free(argv);
    return result;
}

关于FFmpeg的run方法的源码如下,中间有部分省略:

int run(int argc, char **argv)
{
    /****************省略********************/
    //注册各个模块
    avcodec_register_all();
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avfilter_register_all();
    av_register_all();
    avformat_network_init();
    show_banner(argc, argv, options);
    term_init();
    /****************省略********************/
    //解析命令选项与打开输入输出文件
    int ret = ffmpeg_parse_options(argc, argv);
    if (ret < 0)
        exit_program(1);
    /****************省略********************/
    //文件转换
    if (transcode() < 0)
        exit_program(1);
    /****************省略********************/
    //退出程序操作:关闭文件、释放内存
    exit_program(received_nb_signals ? 255 : main_return_code);
    ffmpeg_cleanup(0);
}

其中,最关键的是文件转换部分,源码如下:

 

 

static int transcode(void)
{
    int ret, i;
    AVFormatContext *os;
    OutputStream *ost;
    InputStream *ist;
    int64_t timer_start;
    int64_t total_packets_written = 0;
    //转码方法初始化
    ret = transcode_init();
    if (ret < 0)
        goto fail;

    if (stdin_interaction) {
        av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n");
    }
    timer_start = av_gettime_relative();

#if HAVE_PTHREADS
    if ((ret = init_input_threads()) < 0)
        goto fail;
#endif
    //transcode循环处理
    while (!received_sigterm) {
        int64_t cur_time= av_gettime_relative();

        //如果遇到"q"命令,则退出循环
        if (stdin_interaction)
            if (check_keyboard_interaction(cur_time) < 0)
                break;

        //判断是否还有输出流
        if (!need_output()) {
            av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n");
            break;
        }

        ret = transcode_step();
        if (ret < 0 && ret != AVERROR_EOF) {
            char errbuf[128];
            av_strerror(ret, errbuf, sizeof(errbuf));

            av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", errbuf);
            break;
        }

        //打印音视频流信息
        print_report(0, timer_start, cur_time);
    }
#if HAVE_PTHREADS
    free_input_threads();
#endif

    //文件末尾最后一个stream,刷新解码器buffer
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];
        if (!input_files[ist->file_index]->eof_reached && ist->decoding_needed) {
            process_input_packet(ist, NULL, 0);
        }
    }
    flush_encoders();
    term_exit();

    //写文件尾,关闭文件
    for (i = 0; i < nb_output_files; i++) {
        os = output_files[i]->ctx;
        if ((ret = av_write_trailer(os)) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s", os->filename, av_err2str(ret));
            if (exit_on_error)
                exit_program(1);
        }
    }

    //关闭所有编码器
    for (i = 0; i < nb_output_streams; i++) {
        ost = output_streams[i];
        if (ost->encoding_needed) {
            av_freep(&ost->enc_ctx->stats_in);
        }
        total_packets_written += ost->packets_written;
    }

    if (!total_packets_written && (abort_on_flags & ABORT_ON_FLAG_EMPTY_OUTPUT)) {
        av_log(NULL, AV_LOG_FATAL, "Empty output\n");
        exit_program(1);
    }

    //关闭所有解码器
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];
        if (ist->decoding_needed) {
            avcodec_close(ist->dec_ctx);
            if (ist->hwaccel_uninit)
                ist->hwaccel_uninit(ist->dec_ctx);
        }
    }

    //省略最后的释放内存
    return ret;
}

好了,使用FFmpeg进行音频剪切、混音、拼接与转码介绍完毕。如果各位有什么问题或者建议,欢迎交流。

源码:https://github.com/xufuji456/FFmpegAndroid。如果对您有帮助,麻烦fork和star。

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FFmpeg音频处理——音频混合、拼接、剪切、转码 的相关文章

  • 企业级音视频会议实战之webrtc服务器janus品尝实战

    1 前言 之前写过单纯用webrtc和springboot实现单人 多人 屏幕分享等功能的一系列文章了 心疼各位giegie 已将例子放在下面 不麻烦你们去找了 webrtc实现视频群聊系列文章 一 之基础入门 webrtc实现视频群聊系列
  • 海思his35xx安防芯片音视频媒控驱动基础篇MIPI RX模块(一)

    先给自己打个广告 本人的微信公众号正式上线了 搜索 张笑生的地盘 主要关注嵌入式软件开发 股票基金定投 足球等等 希望大家多多关注 有问题可以直接留言给我 一定尽心尽力回答大家的问题 二维码如下 一 背景介绍 海思芯片的应用方向大致分为如下
  • 音视频开发(13)---视频监控系统必须知道的一些基础知识

    视频监控系统必须知道的一些基础知识 前言 在视频监控系统中 视频文件的传输带宽很重要 那视频文件的传输带宽怎么计算呢 首先给大家介绍几个名词 正文 1 比特率 比特率是指每秒传送的比特 bit 数 单位为bps BitPerSecond 比
  • FFmpeg源码分析:av_register_all()注册封装器与解封装器

    FFmpeg中av register all 函数用于注册所有muxers demuxers与protocols FFmpeg4 0以前是用链表存储muxer demuxer FFmpeg4 0以后改为数组存储 并且av register
  • moviepy音视频开发:音频剪辑基类AudioClip详解

    前往老猿Python博文目录 一 背景知识介绍 1 1 声音三要素 音调 人耳对声音高低的感觉称为音调 也叫音频 音调主要与声波的频率有关 声波的频率高 则音调也高 音量 也就是响度 人耳对声音强弱的主观感觉称为响度 响度和声波振动的幅度有
  • Windows下搭建nginx和rtspToWebRTC以及Windows程序添加为服务开机启动和后台运行

    1 前言 之前的rtsp转webrtc的服务很好用 https blog csdn net weixin 39510813 article details 123718363 spm 1001 2014 3001 5502 测试使用效果都很
  • iOS 中集成 FFmpeg

    FFmpeg是一套可以用来记录 转换数字音频 视频 并能将其转化为流的开源计算机程序 它提供了录制 转换以及流化音视频的完整解决方案 ffmpeg的代码是包括两部分的 一部分是library 一部分是tool api都是在library里面
  • RTSP和SDP协议学习

    RTSP和SDP协议学习 版本说明 版本 作者 日期 备注 0 1 loon 2019 2 14 初稿 目录 文章目录 RTSP和SDP协议学习 版本说明 目录 第一部分 RTSP协议 一 RTSP协议概述 二 RTSP协议与HTTP协议区
  • 浏览器播放rtsp视频流:2、ffmpeg转hls播放(go后端利用hls做简单视频直播)

    浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 文章目录 浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 1 前言 2 wsl安装ffmpeg并转换rtsp
  • Moviepy时间变换time_mirror再遇‘OSError: MoviePy error: failed to read the first frame of video file‘解决示例代码

    老猿Python博文目录 https blog csdn net LaoYuanPython 一 引言 在 https blog csdn net LaoYuanPython article details 106478711 moviep
  • live555在Ubuntu上的编译及对于armLinux的交叉编译

    live555在Ubuntu上的编译及对于armLinux的交叉编译 版本说明 版本 作者 日期 备注 0 1 ZY 2019 3 7 初稿 目录 文章目录 live555在Ubuntu上的编译及对于armLinux的交叉编译 版本说明 目
  • 浏览器播放rtsp视频流:1、开源方案

    浏览器播放rtsp视频流 1 开源方案 文章目录 浏览器播放rtsp视频流 1 开源方案 1 方案一 html5 websocket rtsp proxy 实现视频流直播 1 1 实现原理 1 2 实现步骤 1 3 优缺点 1 4 参考链接
  • m3u8 文件格式详解

    简介 M3U8 是 Unicode 版本的 M3U 用 UTF 8 编码 M3U 和 M3U8 文件都是苹果公司使用的 HTTP Live Streaming HLS 协议格式的基础 这种协议格式可以在 iPhone 和 Macbook 等
  • Android平台RTSP

    1 背景 我们在做Android平台RTSP或者RTMP播放器开发的时候 需要注意的点非常多 以下 以大牛直播SDK 官方 的接口为例 大概介绍下相关接口设计 2 接口设计 2 1 Open 接口 Open接口的目的 主要是创建实例 正常返
  • Qt+SDL播放YUV视频文件

    播放一个yuv420p格式的文件 yuv的格式与rgb格式的存储方式不同 yuv数据是 yyyyyyyy uu vv 的存储 rgb是rgba rgba rgba 存储 同样一帧图像 yuv420会比rgb数据更小 读取时就要注意数据存储格
  • 视频编解码(一):ffmpeg编码H.264帧类型判断

    本文主要讲述ffmpeg编码过程中是如何设置I帧 B帧及P帧的 以及如何通过代码判断帧类型 之前看过很多网上的文章 讲述如何判断I帧 B帧 P帧 然而都是停留在H 264官方文档中的定义 如果不结合ffmpeg 就仿佛纸上谈兵 有点不切实际
  • (ffmpeg)ffmpeg+SDL的简单播放器(雷霄骅)更新版

    代码源自雷神 一个是播放音频的demo 可以播放MP3和AAC 但是MP3应该是没有封面的 另一个是播放ts格式的视频 没有声音 源码可以到雷神博客下载 但是因为ffmpeg库的更新问题 并不能直接在ubuntu下直接运行 笔者做了修改 在
  • 基于google升级版c++代码规范指南

    有些团队所有成员写的代码都一致 10个人写的代码像1个人写的 正因为有代码规范 使得代码可读性强 方便代码review 利于后期维护 这体现了代码规范的重要性 接下来 在参考google的代码规范基础上 详细列举代码规范细节 1 文件描述
  • 封装一个OpenH264 编解码H264视频文件的类

    下面是一个更新后的代码 增加了 H 264 编码的支持 在这个示例中 我使用了 OpenH264 的 ISVCEncoder 接口进行编码 请确保在项目中正确链接 OpenH264 库 并根据你的项目需要调整代码 include
  • 嵌入式开发、C++后端开发、C++音视频开发怎么选择?

    嵌入式开发 C 后端开发和C 音视频开发的选择问题 近年来 随着互联网和物联网的快速发展 嵌入式开发 C 后端开发和C 音视频开发等职业领域也逐渐受到广泛关注 对于有志于从事这些领域的人来说 如何做出选择呢 下面将从前景 薪酬和职业稳定性等

随机推荐

  • 【python】爬虫篇:python使用psycopg2批量插入数据(三)

    本人菜鸡 有什么错误 还望大家批评指出 最近在更新python的爬虫系列 皿 Hiahiahia 该系列暂时总共有3篇文章 连接如下 python 爬虫篇 python连接postgresql 一 https blog csdn net l
  • 查看系统使用率命令 vmstat 输出详解!

    使用效果
  • 凸包问题的五种解法

    原文 http blog csdn net yangkunpengd article details 51336453 凸包问题的五种解法 前言 首先 什么是凸包 说凸包首先要说凸性的定义 简单点说就是平面邻域中任意两点所在的线段上的点都在
  • LR(0)文法分析(通过例题穿插讲解)

    目录 LR 0 文法的字面含义 LR 0 分析表的构造 写在最后 LR 0 文法的字面含义 LR 0 分析法是其他LR分析法构造的基础 L表示从左往右扫描 R表示反向构造出一个最右推导 k表示向前看k个字符 缺省为1 在学习LR 0 分析时
  • Linux驱动框架与LED实战

    目录 驱动框架 相关文件 案例分析 LED驱动框架源码 led class c led class attrs leds class class结构体 led classdev register 某一类的设备创建 led classdev结
  • QT获取显示当前时间和日期

    获取当前时间和日期 QT中获取时间和日期的主要是 QTime QDate 和 QDateTime 这三个类 QTime 类 通过 QTime 类中提供的时间相关的方法 可以获取到当前系统时间 时 分 秒 毫秒 需要注意的是 计时的准确性由底
  • QWidget/QDialog主窗体设置边框圆角

    1 问题 QT中窗体QWidget和QDialog为容器 不能对窗体进行边框圆角样式改变 只能通过绘图QPainter 2 设置无上边框选项窗口 this gt setWindowFlags Qt Widget Qt FramelessWi
  • CSS学习笔记八——宽高自适应

    宽高自适应 一 宽度自适应 二 高度自适应 三 浮动元素的高度自适应 四 窗口自适应 五 结语 一 宽度自适应 不写宽度或者写 width auto就表示宽度自适应 可用于横栏或导航栏 与 width 100 不同 设为100 已经固定了宽
  • MySQL之无限级分类表设计

    首先查找一下goods cates表和table goods brands数据表 分别使用命令 root localhost test gt show columns from goods cates root localhost test
  • 【Spring源码】一:整体流程

    总流程 12 个方法 Prepare this context for refreshing prepareRefresh Tell the subclass to refresh the internal bean factory Con
  • 与焦虑同行,话技术领导者成长

    解码职场焦虑 系列直播第二期来啦 技术领导者在职场跃迁中 会因为各种内外因素的变化而产生焦虑 困惑 烦躁等情绪 我们该怎样与负面情绪共处 认识到局限 接纳并不完美的自己 从而稳步前行 技术Leader成长路上会面对哪些情绪挑战 高压和忙碌状
  • html提取信息变xml,网络爬虫笔记【7】 利用 XPATH 实现 XML 和 HTML 文本信息提取

    XML Extensible Markup Language 指可扩展标记语言 被设计用来传输和存储数据 HTML指的是超文本标记语言 Hyper Text Markup Language 是WWW上用于编写网页的主要工具 详细信息请参考
  • R语言做文本挖掘 Part3文本聚类

    Part3文本聚类 发现有人转载 决定把格式什么重新整理一遍 有时间做个进阶版文本挖掘 恩 原文地址 CSDN R语言做文本挖掘 Part3文本聚类 分类和聚类算法 都是数据挖掘中最常接触到的算法 分类聚类算法分别有很多种 可以看下下面两篇
  • 万字超详细的Java图书管理系统

    生命中的每个人都是一个故事 而每个故事都值得被讲述 作者 不能再留遗憾了 专栏 Java学习 该文章主要内容 用Java实现简单的图书管理系统 文章目录 前言 基本思路 书和书架 书Book类 书架BookList类 用户身份User 父类
  • Oracle : ORA-00001: unique constraint (SHULAN_TEST.SYS_C0026496) violated

    Caused by java lang IllegalStateException Can t overwrite cause with java sql SQLIntegrityConstraintViolationException O
  • Random Vectors and the Variance-Covariance Matrix

    Random Vectors and the Variance Covariance Matrix pdf 多维变量概率论 Definition 1 随机向量 x x 1
  • jsp与html,html与web语言的交互

    jsp 又名java Server Pages 用于开发动态网页 文件扩展名为jsp 优点 1 首先jsp是一种服务端技术 提供了动态接口 用于不断更改数据并调用服务器操作 2 jsp本身是一种编译好的Servlet文件 3 jsp基于ja
  • 给本科实验室的分享PPT续-回复各种问题

    谢邀 该分享主要面向实验室的大一 大二同学
  • Neo4J(Cypher语句)初识

    欢迎各路大神临幸寒舍 以下节点标签为people friend 用户自己也可以设置成其他标签 查询时需要用到标签 这个标签可以类比为关系数据库中的表名 创建节点 关系 创建节点 小明 create n people name 小明 age
  • FFmpeg音频处理——音频混合、拼接、剪切、转码

    接触FFmpeg有一段时间了 它是音视频开发的开源库 几乎其他所有播放器 直播平台都基于FFmpeg进行二次开发 本篇文章来总结下采用FFmpeg进行音频处理 音频混合 音频剪切 音频拼接与音频转码 采用android studio进行开发