在音频开发中,音频重采样是一个比较复杂的操作。假设有一个采样率为44100的音频,将其转换成采样率为32000的音频,这个操作就称为音频重采样。
采样率:每秒从连续信号中提取并组成离散信号的采样个数。
1. 编译FFmpeg
具体编译过程看这里:
- 使用Android Studio开发FFmpeg的正确姿势
- 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. 构建工程
参考“向您的项目添加 C 和 C++ 代码”,为已存在的工程添加C/C++支持,也可以在创建新工程的时候勾选“Include C++ support”。
-
指定ABI
ndk { abiFilters 'armeabi' }
- 在src/main/目录下创建文件夹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. 重采样流程
- 流程图
-
创建和初始化,并分配缓冲区
/** * 初始化 */ 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; }
-
计算输出采样数,假如采样数大于最大输出采样数,则重新分配输出缓冲区,防止数组越界。
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); // 将输入数据复制到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; }
4. 代码
- 本文工程源代码
- FFmpeg音频重采样例子
5. 参考
- 音频转码, 设置音频数据格式-sample_fmt
- FFmpeg学习—ffmpeg 利用 swr_convert 函数将AV_SAMPLE_FMT_S16 转 AV_SAMPLE_FMT_FLTP