在音頻開發(fā)中,音頻重采樣是一個比較復雜的操作。假設有一個采樣率為44100的音頻,將其轉換成采樣率為32000的音頻,這個操作就稱為音頻重采樣。
采樣率:每秒從連續(xù)信號中提取并組成離散信號的采樣個數(shù)。
1. 編譯FFmpeg
具體編譯過程看這里:
編譯成功后,得到下面這些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. 構建工程
參考“向您的項目添加 C 和 C++ 代碼”,為已存在的工程添加C/C++支持,也可以在創(chuàng)建新工程的時候勾選“Include C++ support”。
-
指定ABI
ndk { abiFilters 'armeabi' } -
在src/main/目錄下創(chuàng)建文件夾jniLibs,并將編譯好的庫文件和頭文件放進去,結構如下圖所示:文件結構
-
在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. 重采樣流程
-
流程圖重采樣流程圖.jpg
-
創(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; } -
計算輸出采樣數(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; } -
重采樣
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; } -
關閉并回收內存
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; }

