Android:使用FFmpeg对音频进行重采样

在音频开发中,音频重采样是一个比较复杂的操作。假设有一个采样率为44100的音频,将其转换成采样率为32000的音频,这个操作就称为音频重采样。

采样率:每秒从连续信号中提取并组成离散信号的采样个数。

1. 编译FFmpeg

具体编译过程看这里:

  1. 使用Android Studio开发FFmpeg的正确姿势
  2. FFPlayerDemo

编译成功后,得到下面这些so库文件:

  • libavcodec.so
  • libavdevice.so
  • libavfilter.so
  • libavformat.so
  • libavresample.so
  • libavutil.so
  • libswresample.so
  • libswscale.so

其中重采样用到的是libswresample.so和libavutil.so。由于armeabi已经适用于大多数的手机,所以我只编译了armeabi的库。

#!/bin/sh

PREFIX=android-build
NDK_HOME=/Users/kidonliang/Library/Android/android-ndk-r15c
NDK_HOST_PLATFORM=darwin-x86_64

COMMON_OPTIONS="\
    --prefix=android/ \
    --target-os=android \
    --disable-static \
    --enable-shared \
    --enable-small \
    --disable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-doc \
    --enable-avresample \
    --disable-symver \
    --disable-asm \
    --disable-armv5te \
    "

function build_android {
 ./configure \
    --libdir=${PREFIX}/libs/armeabi \
    --incdir=${PREFIX}/includes/armeabi \
    --pkgconfigdir=${PREFIX}/pkgconfig/armeabi \
    --arch=arm \
    --cpu=armv6 \
    --cross-prefix="${NDK_HOME}/toolchains/arm-linux-androideabi-4.9/prebuilt/${NDK_HOST_PLATFORM}/bin/arm-linux-androideabi-" \
    --sysroot="${NDK_HOME}/platforms/android-15/arch-arm/" \
    --extra-ldexeflags=-pie \
    ${COMMON_OPTIONS}
    make clean
    make -j8 && make install
}
build_android

2. 构建工程

  1. 参考“向您的项目添加 C 和 C++ 代码”,为已存在的工程添加C/C++支持,也可以在创建新工程的时候勾选“Include C++ support”。

  2. 指定ABI

    ndk {
        abiFilters 'armeabi'
    }
    
  3. 在src/main/目录下创建文件夹jniLibs,并将编译好的库文件和头文件放进去,结构如下图所示:
    文件结构
  4. 在CMakeLists.txt中添加库:

    cmake_minimum_required(VERSION 3.4.1)
    
    add_library( soundeditor
                 SHARED
                 src/main/jni/resampler.c )
    
    find_library( log-lib
                  log )
    
    add_library(avutil
                SHARED
                IMPORTED )
    set_target_properties(
        avutil
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavutil.so
        )
    
    add_library(swresample
                SHARED
                IMPORTED )
    set_target_properties(
        swresample
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswresample.so
        )
    
    include_directories(${CMAKE_SOURCE_DIR}/src/main/jniLibs/includes)
    
    target_link_libraries( soundeditor
                           avutil
                           swresample
                           ${log-lib} )
    

3. 重采样流程

  1. 流程图
    重采样流程图.jpg
  2. 创建和初始化,并分配缓冲区

    /**
     * 初始化
     */
    JNIEXPORT jint JNICALL
    Java_com_lkdont_sound_edit_Resampler_initResampler(JNIEnv *env, jclass type, jint in_nb_samples,
                                                       jint in_ch_layout, jint out_ch_layout,
                                                       jint in_rate, jint out_rate,
                                                       jint in_sample_fmt, jint out_sample_fmt) {
        swr_ctx = swr_alloc();
        if (!swr_ctx) {
            LOGE("Could not allocate resampler context\n");
            close();
            return 1;
        }
    
        src_nb_samples = in_nb_samples;
        src_ch_layout = get_channel_layout(in_ch_layout);
        dst_ch_layout = get_channel_layout(out_ch_layout);
        src_rate = in_rate;
        dst_rate = out_rate;
        src_sample_fmt = get_sample_fmt(in_sample_fmt);
        dst_sample_fmt = get_sample_fmt(out_sample_fmt);
    
        /* set options */
        av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
        av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
        av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
        av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
        av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
        av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
    
        /* initialize the resampling context */
        if (swr_init(swr_ctx) < 0) {
            LOGE("Failed to initialize the resampling context\n");
            close();
            return 1;
        }
    
        /* allocate source and destination samples buffers */
        src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
        int ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels,
                                                     src_nb_samples, src_sample_fmt, 0);
        if (ret < 0) {
            LOGE("Could not allocate source samples\n");
            close();
            return 1;
        }
    
        /* compute the number of converted samples: buffering is avoided
         * ensuring that the output buffer will contain at least all the
         * converted input samples */
        max_dst_nb_samples = dst_nb_samples =
                av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
        /* buffer is going to be directly written to a rawaudio file, no alignment */
        dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
        ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
                                                 dst_nb_samples, dst_sample_fmt, 0);
        if (ret < 0) {
            LOGE("Could not allocate destination samples\n");
            close();
            return 1;
        }
    
        return 0;
    }
    
  3. 计算输出采样数,假如采样数大于最大输出采样数,则重新分配输出缓冲区,防止数组越界。

    int compute_destination_nb_samples() {
        /* compute destination number of samples */
        dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_rate) +
                                        src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
        if (dst_nb_samples > max_dst_nb_samples) {
            av_freep(&dst_data[0]);
            if (av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples,
                                 dst_sample_fmt, 1) < 0) {
                LOGE("resample: Error while av_samples_alloc\n");
                return -1;
            }
            max_dst_nb_samples = dst_nb_samples;
        }
        return dst_nb_samples;
    }
    
  4. 重采样

    JNIEXPORT jint JNICALL
    Java_com_lkdont_sound_edit_Resampler_resample(JNIEnv *env, jobject instance, jbyteArray input_,
                                                  jint inLen, jbyteArray output_) {
    
        if (!swr_ctx) {
            LOGE("SwrContext还没有初始化\n");
            return -1;
        }
    
        jbyte *input = (*env)->GetByteArrayElements(env, input_, NULL);
        jbyte *output = (*env)->GetByteArrayElements(env, output_, NULL);
    
        // 将输入数据复制到src_data中
        memcpy(src_data[0], input, inLen);
    
        /* convert to destination format */
        int ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **) src_data,
                          src_nb_samples);
        if (ret < 0) {
            LOGE("resample: Error while swr_convert\n");
            return -1;
        }
        ret = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
                                         ret, dst_sample_fmt, 1);
    
        // 将结果复制到output中
        memcpy(output, dst_data[0], ret);
    
        (*env)->ReleaseByteArrayElements(env, input_, input, 0);
        (*env)->ReleaseByteArrayElements(env, output_, output, 0);
    
        return ret;
    }
    
  5. 关闭并回收内存

    void close() {
        if (src_data)
            av_freep(&src_data[0]);
        av_freep(&src_data);
        if (dst_data)
            av_freep(&dst_data[0]);
        av_freep(&dst_data);
        swr_free(&swr_ctx);
        swr_ctx = NULL;
    }
    

4. 代码

  1. 本文工程源代码
  2. FFmpeg音频重采样例子

5. 参考

  1. 音频转码, 设置音频数据格式-sample_fmt
  2. FFmpeg学习—ffmpeg 利用 swr_convert 函数将AV_SAMPLE_FMT_S16 转 AV_SAMPLE_FMT_FLTP
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容