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,并将编译好的库文件和头文件放进去,结构如下图所示:
    Android:使用FFmpeg对音频进行重采样_第1张图片
    文件结构
  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. 流程图
    Android:使用FFmpeg对音频进行重采样_第2张图片
    重采样流程图.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

你可能感兴趣的:(Android:使用FFmpeg对音频进行重采样)