Android:使用FFmpeg對音頻進行重采樣

在音頻開發(fā)中,音頻重采樣是一個比較復雜的操作。假設有一個采樣率為44100的音頻,將其轉換成采樣率為32000的音頻,這個操作就稱為音頻重采樣。

采樣率:每秒從連續(xù)信號中提取并組成離散信號的采樣個數(shù)。

1. 編譯FFmpeg

具體編譯過程看這里:

  1. 使用Android Studio開發(fā)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已經(jīng)適用于大多數(shù)的手機,所以我只編譯了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++支持,也可以在創(chuàng)建新工程的時候勾選“Include C++ support”。

  2. 指定ABI

    ndk {
        abiFilters 'armeabi'
    }
    
  3. 在src/main/目錄下創(chuàng)建文件夾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. 創(chuàng)建和初始化,并分配緩沖區(qū)

    /**
     * 初始化
     */
    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. 計算輸出采樣數(shù),假如采樣數(shù)大于最大輸出采樣數(shù),則重新分配輸出緩沖區(qū),防止數(shù)組越界。

    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);
    
        // 將輸入數(shù)據(jù)復制到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. 音頻轉碼, 設置音頻數(shù)據(jù)格式-sample_fmt
  2. FFmpeg學習—ffmpeg 利用 swr_convert 函數(shù)將AV_SAMPLE_FMT_S16 轉 AV_SAMPLE_FMT_FLTP
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容