0%

[原创] 自定义编译用于 Android 的 ffmpeg 并解码 ADPCM IMA QT 音频(仅编译 adpcm_ima_qt 解码器)

编译环境

  • 操作系统:macOS 10.15.5

  • NDK:22.1.7171670

  • ffmpeg:4.4 “Rao”(截止 20210609)

  • cmake: 3.20.3

  • gcc:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/c++/4.2.1

    Apple clang version 12.0.0 (clang-1200.0.32.29)

    Target: x86_64-apple-darwin19.5.0

    Thread model: posix

    InstalledDir: /Library/Developer/CommandLineTools/usr/bin

下载

从官网下载源码压缩包。截止本文章发布时,最新版为 4.4 "Rao" 该版本发布于 2021-04-08。

1
wget https://www.ffmpeg.org/releases/ffmpeg-4.4.tar.bz2

若要编译 x86 架构,需要下载如下工具:

1
brew install yasm

编译

编写编译脚本

解压缩上面的文件,进入下载的文件夹,并创建编译脚本:

1
2
cd ffmpeg-4.4
vim build_android.sh

内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/bin/bash

NDK_PATH=~/Library/Android/sdk/ndk/22.1.7171670
# linux-x86_64
HOST_TAG=darwin-x86_64
MIN_SDK_VER=21

# ==================================

TOOLCHAINS=${NDK_PATH}/toolchains/llvm/prebuilt/${HOST_TAG}
SYSROOT=${TOOLCHAINS}/sysroot

function build_one
{
if [ $ARCH == "arm" ]
then
CROSS_PREFIX=$TOOLCHAINS/bin/arm-linux-androideabi-
elif [ $ARCH == "aarch64" ]
then
CROSS_PREFIX=$TOOLCHAINS/bin/aarch64-linux-android-
elif [ $ARCH == "x86_32" ]
then
CROSS_PREFIX=$TOOLCHAINS/bin/i686-linux-android-
else
CROSS_PREFIX=$TOOLCHAINS/bin/x86_64-linux-android-
fi

./configure \
--prefix=$PREFIX \
--extra-cflags="$OPTIMIZE_CFLAGS" \
--cross-prefix=$CROSS_PREFIX \
--sysroot=$SYSROOT \
--enable-cross-compile \
--target-os=android \
--arch=$ARCH \
--cc=${CC} \
--cxx=${CC}++ \
--ld=${CC} \
--ar=${TOOLCHAINS}/bin/llvm-ar \
--as=${CC} \
--nm=${TOOLCHAINS}/bin/llvm-nm \
--ranlib=${TOOLCHAINS}/bin/llvm-ranlib \
--strip=${TOOLCHAINS}/bin/llvm-strip \
--disable-everything \
--disable-x86asm \
--disable-inline-asm \
--enable-decoder=adpcm_ima_qt \
--disable-static \
--enable-shared \
--enable-small \
--enable-pic

# --disable-avformat \

make clean
make -j6
make install
}

#armeabi-v7a
ARCH=arm
OPTIMIZE_CFLAGS="-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security -Oz -DNDEBUG -fPIC --target=armv7-none-linux-androideabi$MIN_SDK_VER --gcc-toolchain=$TOOLCHAINS"
PREFIX=`pwd`/prebuilt/armeabi-v7a
export CC=$TOOLCHAINS/bin/armv7a-linux-androideabi$MIN_SDK_VER-clang
export CXX=$TOOLCHAINS/bin/armv7a-linux-androideabi$MIN_SDK_VER-clang++
build_one

#arm64-v8a
ARCH=aarch64
OPTIMIZE_CFLAGS="-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O2 -DNDEBUG -fPIC --target=aarch64-none-linux-android$MIN_SDK_VER --gcc-toolchain=$TOOLCHAINS"
PREFIX=`pwd`/prebuilt/arm64-v8a
export CC=$TOOLCHAINS/bin/aarch64-linux-android$MIN_SDK_VER-clang
export CXX=$TOOLCHAINS/bin/aarch64-linux-android$MIN_SDK_VER-clang++
build_one

##x86_32
#ARCH=x86_32
#OPTIMIZE_CFLAGS="-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -mstackrealign -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O2 -DNDEBUG -fPIC --target=i686-none-linux-android$MIN_SDK_VER --gcc-toolchain=$TOOLCHAINS"
#PREFIX=`pwd`/prebuilt/x86
#export CC=$TOOLCHAINS/bin/i686-linux-android$MIN_SDK_VER-clang
#export CXX=$TOOLCHAINS/bin/i686-linux-android$MIN_SDK_VER-clang++
#build_one
#
##x86_64
#ARCH=x86_64
#OPTIMIZE_CFLAGS="-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -O2 -DNDEBUG -fPIC --target=x86_64-none-linux-android$MIN_SDK_VER --gcc-toolchain=$TOOLCHAINS"
#PREFIX=`pwd`/prebuilt/x86_64
#export CC=$TOOLCHAINS/bin/x86_64-linux-android$MIN_SDK_VER-clang
#export CXX=$TOOLCHAINS/bin/x86_64-linux-android$MIN_SDK_VER-clang++
#build_one

说明:

  • 设置 cflags 的技巧,可以参考我的这篇文章
  • 添加 --disable-x86asm--disable-inline-asm 可以解决如下问题:./libavutil/x86/timer.h:39:24: error: invalid output constraint '=a' in asm
  • 添加 --disable-programs 的话,则不会生成可执行文件。(即,不会生成 bin 文件夹)
  • 还可以添加如下参数,从而不生成对应的文件:
    • –disable-ffmpeg
    • –disable-ffprobe
    • –disable-ffplay

开始编译

添加执行权限:

1
chmod +x build_android.sh

执行编译脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
% ./build_android.sh
install prefix /Users/yhz61010/Downloads/temp/ffmpeg-4.4/prebuilt/armeabi-v7a
source path .
C compiler /Users/yhz61010/Library/Android/sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi21-clang
C library bionic
host C compiler gcc
host C library
ARCH arm (armv7-a)
big-endian no
runtime cpu detection yes
ARMv5TE enabled yes
ARMv6 enabled yes

...
...
...
INSTALL libavutil/tea.h
INSTALL libavutil/tx.h
INSTALL libavutil/film_grain_params.h
INSTALL libavutil/avconfig.h
INSTALL libavutil/ffversion.h
INSTALL libavutil/libavutil.pc

查看编译后的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
% cd prebuilt
% tree -h -L 3
.
├── [ 192] arm64-v8a
│   ├── [ 128] bin
│   │   ├── [235K] ffmpeg
│   │   └── [134K] ffprobe
│   ├── [ 288] include
│   │   ├── [ 832] libavcodec
│   │   ├── [ 128] libavdevice
│   │   ├── [ 192] libavfilter
│   │   ├── [ 160] libavformat
│   │   ├── [2.8K] libavutil
│   │   ├── [ 128] libswresample
│   │   └── [ 128] libswscale
│   ├── [ 320] lib
│   │   ├── [305K] libavcodec.so
│   │   ├── [ 10K] libavdevice.so
│   │   ├── [115K] libavfilter.so
│   │   ├── [192K] libavformat.so
│   │   ├── [374K] libavutil.so
│   │   ├── [ 73K] libswresample.so
│   │   ├── [323K] libswscale.so
│   │   └── [ 288] pkgconfig
│   └── [ 128] share
│   ├── [ 288] ffmpeg
│   └── [ 128] man
└── [ 192] armeabi-v7a
├── [ 128] bin
│   ├── [185K] ffmpeg
│   └── [106K] ffprobe
├── [ 288] include
│   ├── [ 832] libavcodec
│   ├── [ 128] libavdevice
│   ├── [ 192] libavfilter
│   ├── [ 160] libavformat
│   ├── [2.8K] libavutil
│   ├── [ 128] libswresample
│   └── [ 128] libswscale
├── [ 320] lib
│   ├── [220K] libavcodec.so
│   ├── [7.7K] libavdevice.so
│   ├── [ 84K] libavfilter.so
│   ├── [149K] libavformat.so
│   ├── [289K] libavutil.so
│   ├── [ 66K] libswresample.so
│   ├── [254K] libswscale.so
│   └── [ 288] pkgconfig
└── [ 128] share
├── [ 288] ffmpeg
└── [ 128] man

30 directories, 18 files

验证编译结果

arm64-v8a 为例,将编译生成的所有文件拷贝到手机 /data/local/tmp/ffmpeg 目录下(若目录不存在,请先自行创建):

1
2
3
4
5
6
% adb push arm64-v8a/* /data/local/tmp/ffmpeg
arm64-v8a/bin/: 2 files pushed, 0 skipped. 13.3 MB/s (378000 bytes in 0.027s)
arm64-v8a/include/: 123 files pushed, 0 skipped. 11.2 MB/s (1118409 bytes in 0.095s)
arm64-v8a/lib/: 14 files pushed, 0 skipped. 22.1 MB/s (1429255 bytes in 0.062s)
arm64-v8a/share/: 51 files pushed, 0 skipped. 23.8 MB/s (5007082 bytes in 0.200s)
190 files pushed, 0 skipped. 19.3 MB/s (7932746 bytes in 0.391s)

使用 adb shell 进入手机 shell 模式,并进入 bin 中文件夹:

1
2
% adb shell
$ cd /data/local/tmp/ffmpeg/bin

此时,若直接运行 ./ffmpeg 会报如下错:

1
CANNOT LINK EXECUTABLE "./ffmpeg": library "libavdevice.so" not found

因为我们刚刚编译的是动态库,所以要想运行 ffmpeg 的话,需要添加必要设置:

1
export LD_LIBRARY_PATH=/data/local/tmp/ffmpeg/lib

之后再运行 ./ffmpeg,确认是否仅编译了 adpcm_ima_qt 解码器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ./ffmpeg -decoders
ffmpeg version 4.4 Copyright (c) 2000-2021 the FFmpeg developers
built with Android (7155654, based on r399163b1) clang version 11.0.5 (https://android.googlesource.com/toolchain/llvm-project 87f1315dfbea7c137aa2e6d362dbb457e388158d)
...
...
...
libavutil 56. 70.100 / 56. 70.100
libavcodec 58.134.100 / 58.134.100
libavformat 58. 76.100 / 58. 76.100
libavdevice 58. 13.100 / 58. 13.100
libavfilter 7.110.100 / 7.110.100
libswscale 5. 9.100 / 5. 9.100
libswresample 3. 9.100 / 3. 9.100
Decoders:
V..... = Video
A..... = Audio
S..... = Subtitle
.F.... = Frame-level multithreading
..S... = Slice-level multithreading
...X.. = Codec is experimental
....B. = Supports draw_horiz_band
.....D = Supports direct rendering method 1
------
A....D adpcm_ima_qt

直接编译在 Android 上可执行的 ffmpeg

刚刚编译得到的 ffmpeg 命令并不能直接运行,那么如果需要编译能直接运行的 ffmpeg 命令的话,需要对脚本做如下修改:

  • 删除 --disable-programs
  • --disable-static 修改为 --enable-static
  • --enable-shared 修改为 --disable-shared

之后再重新执行编译脚本的话,其生成的 bin 文件夹下的 ffmpeg 命令就可以直接在 Android 运行了。这里就不再演示了。

动态库生成的 ffmpeg 大小为 235K。静态库生成的可执行的 ffmpeg 大小为 1.2M。

生成 Android 可用的 so 文件

编写 Application.mk 文件

在 Android 项目 app module 的 src/main/jni 目录下创建 Application.mk 文件:

1
2
3
4
5
APP_STL := c++_shared
# x86 armeabi-v7a arm64-v8a
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-21
APP_OPTIM := release

编写 Android.mk 文件

在 Android 项目 app module 的 src/main/jni 目录下创建 Android.mk 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
LOCAL_PATH := $(call my-dir)

MY_PREBUILT := $(LOCAL_PATH)/prebuilt/$(TARGET_ARCH_ABI)

include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)

#include $(CLEAR_VARS)
#LOCAL_MODULE := libswresample
#LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libswresample.so
#include $(PREBUILT_SHARED_LIBRARY)
#
#include $(CLEAR_VARS)
#LOCAL_MODULE := libavdevice
#LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libavdevice.so
#include $(PREBUILT_SHARED_LIBRARY)
#
#include $(CLEAR_VARS)
#LOCAL_MODULE := libavfilter
#LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libavfilter.so
#include $(PREBUILT_SHARED_LIBRARY)
#
#include $(CLEAR_VARS)
#LOCAL_MODULE := libavformat
#LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libavformat.so
#include $(PREBUILT_SHARED_LIBRARY)
#
#include $(CLEAR_VARS)
#LOCAL_MODULE := libswscale
#LOCAL_SRC_FILES := $(MY_PREBUILT)/lib/libswscale.so
#include $(PREBUILT_SHARED_LIBRARY)

# ==================================

include $(CLEAR_VARS)
LOCAL_MODULE := adpcm-ima-qt
LOCAL_SRC_FILES := libadpcm_ima_qt_jni.cpp
LOCAL_CFLAGS :=
LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib
LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) $(MY_PREBUILT)/include
# The following libraries will be generated in src/main/lib folder
#LOCAL_SHARED_LIBRARIES := libavdevice libavcodec libavfilter libavformat libavutil libswresample
LOCAL_SHARED_LIBRARIES := libavcodec libavutil
LOCAL_DISABLE_FORMAT_STRING_CHECKS := true
LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true
include $(BUILD_SHARED_LIBRARY)

编写 cpp 文件

在 Android 项目 app module 的 src/main/jni 目录下创建 libadpcm_ima_qt_jni.cpp 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <jni.h>
#include <string>
#include <android/log.h>

extern "C"
{
#include <libavcodec/avcodec.h>
}

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "adpcm_jni", __VA_ARGS__))

//#define GET_ARRAY_LEN(array, len) {len = (sizeof(array) / sizeof(array[0]));}
#define ADPCM_PACKAGE_BASE "com/leovp/ffmpeg/"

AVCodecContext *ctx = nullptr;
AVFrame *frame = nullptr;
AVPacket *pkt = nullptr;

JNIEXPORT jint JNICALL init(JNIEnv *env, jobject obj, jint sampleRate, jint channels) {
LOGE("ADPCM init. sampleRate: %d, channels: %d\n", sampleRate, channels);

const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_ADPCM_IMA_QT);
ctx = avcodec_alloc_context3(codec);
ctx->sample_rate = sampleRate;
ctx->channels = channels;
ctx->channel_layout = av_get_default_channel_layout(ctx->channels);

int ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
LOGE("avcodec_open2 error. code=%d\n", ret);
return ret;
}

frame = av_frame_alloc();
pkt = av_packet_alloc();
return ret;
}

JNIEXPORT jint JNICALL chunkSize(JNIEnv *env, jobject obj) {
return 34 * ctx->channels;
}

JNIEXPORT void JNICALL release(JNIEnv *env, jobject obj) {
if (pkt != nullptr) {
av_packet_free(&pkt);
pkt = nullptr;
}
if (frame != nullptr) {
av_frame_free(&frame);
frame = nullptr;
}
if (ctx != nullptr) {
avcodec_free_context(&ctx);
ctx = nullptr;
}
LOGE("ADPCM released!");
}

JNIEXPORT jbyteArray JNICALL decode(JNIEnv *env, jobject obj, jbyteArray adpcmByteArray) {
int adpcmLen = env->GetArrayLength(adpcmByteArray);
if (adpcmLen != ctx->channels * 34) {
LOGE("ADPCM bytes must be %d", ctx->channels * 34);
return nullptr;
}
auto *adpcm_unit8_t_array = new uint8_t[adpcmLen];
env->GetByteArrayRegion(adpcmByteArray, 0, adpcmLen, reinterpret_cast<jbyte *>(adpcm_unit8_t_array));
// or you can do it like this:
// auto *temp = (jbyte *) env->GetByteArrayElements(adpcmByteArray, nullptr);
// auto *adpcm_unit8_t_array = new uint8_t[adpcmLen];
// memcpy(adpcm_unit8_t_array, temp, adpcmLen);
// env->ReleaseByteArrayElements(adpcmByteArray, temp, 0);

pkt->data = adpcm_unit8_t_array;
pkt->size = adpcmLen;
int ret;
if ((ret = avcodec_send_packet(ctx, pkt)) < 0) {
LOGE("avcodec_send_packet() error. code=%d", ret);
return nullptr;
}
if ((ret = avcodec_receive_frame(ctx, frame)) < 0) {
LOGE("avcodec_receive_frame() error. code=%d", ret);
return nullptr;
}

int each_channel_length = frame->linesize[0];
uint8_t *left_channel_data = frame->data[0];

int pcmSize = each_channel_length * ctx->channels;

if (ctx->channels > 1) { // For stereo
auto *pcm_all_channel_data = new uint8_t[pcmSize];
uint8_t *right_channel_data = frame->data[1];
int subI = 0;
for (int k = 0; k < pcmSize; k += 4) {
pcm_all_channel_data[k] = left_channel_data[subI]; // Left channel lower 8 bits
pcm_all_channel_data[k + 1] = left_channel_data[subI + 1]; // Left channel higher 8 bits
pcm_all_channel_data[k + 2] = right_channel_data[subI]; // Right channel lower 8 bits
pcm_all_channel_data[k + 3] = right_channel_data[subI + 1]; // Right channel higher 8 bits
subI += 2;
}

jbyteArray pcm_byte_array = env->NewByteArray(pcmSize);
env->SetByteArrayRegion(pcm_byte_array, 0, pcmSize, reinterpret_cast<const jbyte *>(pcm_all_channel_data));
return pcm_byte_array;
} else { // For mono
jbyteArray pcm_byte_array = env->NewByteArray(pcmSize);
env->SetByteArrayRegion(pcm_byte_array, 0, pcmSize, reinterpret_cast<const jbyte *>(left_channel_data));
return pcm_byte_array;
}
}

JNIEXPORT jstring JNICALL getVersion(JNIEnv *env, jobject thiz) {
return env->NewStringUTF("0.1.0");
}

// =============================

static JNINativeMethod methods[] = {
{"init", "(II)I", (void *) init},
{"release", "()V", (void *) release},
{"chunkSize", "()I", (void *) chunkSize},
{"decode", "([B)[B", (void *) decode},
{"getVersion", "()Ljava/lang/String;", (void *) getVersion},
};

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;

if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("JNI_OnLoad GetEnv error.");
return JNI_ERR;
}

jclass clz = env->FindClass(ADPCM_PACKAGE_BASE"audio/adpcm/AdpcmImaQtDecoder");
if (clz == nullptr) {
LOGE("JNI_OnLoad FindClass error.");
return JNI_ERR;
}

if (env->RegisterNatives(clz, methods, sizeof(methods) / sizeof(methods[0]))) {
LOGE("JNI_OnLoad RegisterNatives error.");
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

使用 NDK 编译生成 libadpcm-ima-qt.so 文件

jni 目录下执行 ndk-build,生成所需的 libadpcm-ima-qt.so 文件:

1
2
3
4
5
6
7
8
9
10
% ndk-build
[armeabi-v7a] Install : libadpcm-ima-qt.so => libs/armeabi-v7a/libadpcm-ima-qt.so
[armeabi-v7a] Install : libavcodec.so => libs/armeabi-v7a/libavcodec.so
[armeabi-v7a] Install : libavutil.so => libs/armeabi-v7a/libavutil.so
[armeabi-v7a] Install : libc++_shared.so => libs/armeabi-v7a/libc++_shared.so
[arm64-v8a] Install : libadpcm-ima-qt.so => libs/arm64-v8a/libadpcm-ima-qt.so
[arm64-v8a] Install : libavcodec.so => libs/arm64-v8a/libavcodec.so
[arm64-v8a] Install : libavutil.so => libs/arm64-v8a/libavutil.so
[arm64-v8a] Install : libc++_shared.so => libs/arm64-v8a/libc++_shared.so

生成的 so 位于如下目录:src/main/libs/

arm64-v8a 为例,将生成的 src/main/libs/arm64-v8a 拷贝到自己的项目里,就可以使用 libadpcm-ima-qt.so 了。

好了所有的编译工作,到这里终于大功告成!!!

编写 JNI 文件

com.leovp.ffmpeg.audio.adpcm 包下,创建 AdpcmImaQtDecoder 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.leovp.ffmpeg.audio.adpcm

/**
* Author: Michael Leo
* Date: 2021/6/11 09:57
*/
class AdpcmImaQtDecoder private constructor() {
companion object {
init {
System.loadLibrary("adpcm-ima-qt")
System.loadLibrary("avcodec")
System.loadLibrary("avutil")
}
}

constructor(sampleRate: Int, channels: Int) : this() {
init(sampleRate, channels)
}

private external fun init(sampleRate: Int, channels: Int): Int
external fun release()

/**
* In QuickTime, IMA is encoded by chunks of 34 bytes (=64 samples).
* Channel data is interleaved per-chunk.
*
* @param adpcmBytes The length of this parameter is 34 bytes * channels
*/
external fun decode(adpcmBytes: ByteArray): ByteArray
external fun getVersion(): String

/**
* In QuickTime, IMA is encoded by chunks of 34 bytes (=64 samples).
* Channel data is interleaved per-chunk.
*
* The return result is 34 bytes * channels
*/
external fun chunkSize(): Int
}

接下来大家就可以随意玩耍了!!!

注意事项

这里强调下:解码时,每次传入的 ADPCM-IMA-QT 裸流的大小必须是 34bytes×声道数。也就是单声道每次解码 34 个字节,双声道每次解码 68 个字节。

此外需要特别说明的是,ADPCM IMA QT 解码时的音频采样格式必须是 AV_SAMPLE_FMT_S16P。下图来自 ffmpeg 官网源码

ADPCM-IMA-QT Sample format

还有一点需要特殊注意,我当时就在这个地方卡了挺长时间,在获取每个声道的数据长度时,只需要获取 frame->linesize[0] 的长度即可,这是因为每个声道数据长度是一样的。

我当时认为 frame->linesize[0] 应该为左声道的数据长度,frame->linesize[1] 为右声道的数据长度。可实际情况是通过 frame->linesize[1] 获取的大小竟然为 0。后来仔细看了下源码才明白,原来源码里有说明:对于音频而言,可能只有 frame->linesize[0] 有值。并且若是 planar 音频 的话(ADPCM IMA QT 就是这种情况),每个声道的数据长度是一样的。

最后还有一个小的提醒:Android 播放的 PCM 数据,左右声道数据是交错存储的:

Android-PCM-Saved-Format

源码

需要源码的请移至我的Github

ffmpeg 各库功能说明

  • libavutil is a library containing functions for simplifying programming, including random number generators, data structures, mathematics routines, core multimedia utilities, and much more.
  • libavcodec is a library containing decoders and encoders for audio/video codecs.
  • libavformat is a library containing demuxers and muxers for multimedia container formats.
  • libavdevice is a library containing input and output devices for grabbing from and rendering to many common multimedia input/output software frameworks, including Video4Linux, Video4Linux2, VfW, and ALSA.
  • libavfilter is a library containing media filters.
  • libswscale is a library performing highly optimized image scaling and color space/pixel format conversion operations.
  • libswresample is a library performing highly optimized audio resampling, rematrixing and sample format conversion operations.

参考文献

坚持原创及高品质技术分享,您的支持将鼓励我继续创作!