限时福利领取


背景痛点

在Android开发中,使用AudioRecord采集PCM音频数据是常见方案,但实际应用中会遇到几个典型问题:

  • 权限限制:从Android 6.0开始需要动态申请RECORD_AUDIO权限,部分厂商ROM会默认禁用
  • 采样率兼容性:不同设备支持的采样率可能不同,强行设置不支持的参数会导致初始化失败
  • 系统延迟高:AudioRecord的Java层封装会产生额外开销,实测延迟通常在100ms以上

音频采集流程对比

技术方案对比

tinycap作为ALSA层的采集工具,相比AudioRecord有显著优势:

  1. 延迟更低:直接调用Linux音频驱动,实测延迟可控制在20ms内
  2. 兼容性更好:绕过Android音频策略限制,支持更多采样率选项
  3. 资源占用小:Native层实现避免JNI频繁调用的开销

实现细节

1. 编译环境配置

在Android.mk中添加tinycap编译模块:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := tinycap
LOCAL_SRC_FILES := tinycap.c
LOCAL_CFLAGS += -Wall
LOCAL_LDLIBS := -llog

include $(BUILD_EXECUTABLE)

2. JNI层关键代码

数据回调的线程安全实现:

JNIEXPORT void JNICALL
Java_com_example_AudioCapture_nativeStart(JNIEnv *env, jobject thiz, jstring path) {
    const char *dev = env->GetStringUTFChars(path, 0);

    // 初始化环形缓冲区
    struct circle_buffer *buf = init_buffer(4096);

    // 错误处理示例
    if(open_audio_device(dev) < 0) {
        env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), 
                     "Open audio device failed");
        return;
    }

    // 启动采集线程
    pthread_t thread;
    pthread_create(&thread, NULL, capture_thread, buf);
}

3. 环形缓冲区设计

避免数据丢失的核心结构:

typedef struct {
    uint8_t *data;
    size_t size;
    size_t wp; // 写指针
    size_t rp; // 读指针
    pthread_mutex_t lock;
} circle_buffer;

void write_buffer(circle_buffer *buf, const void *data, size_t len) {
    pthread_mutex_lock(&buf->lock);
    // 缓冲区满时丢弃最旧数据
    if(buf->wp + len > buf->size) {
        buf->wp = 0;
    }
    memcpy(buf->data + buf->wp, data, len);
    buf->wp += len;
    pthread_mutex_unlock(&buf->lock);
}

环形缓冲区示意图

性能优化

内存池管理

预先分配内存块减少动态分配:

#define POOL_SIZE 10
#define BLOCK_SIZE 1024

struct {
    void *blocks[POOL_SIZE];
    int used;
} mem_pool;

void init_pool() {
    for(int i=0; i<POOL_SIZE; i++) {
        mem_pool.blocks[i] = malloc(BLOCK_SIZE);
    }
}

采样率自适应

动态检测设备支持的最佳采样率:

int detect_best_rate(int fd) {
    int rates[] = {48000, 44100, 32000, 16000};
    for(int i=0; i<sizeof(rates)/sizeof(int); i++) {
        if(ioctl(fd, SNDRV_PCM_HW_PARAM_RATE, &rates[i]) == 0) {
            return rates[i];
        }
    }
    return 44100; // 默认值
}

避坑指南

SELinux策略修改

/system/etc/sepolicy中添加:

allow audioserver audioserver_tmpfs:file {read write execute};

内存泄漏检测

使用Android NDK的libmemunreachable

adb shell am dumpheap -n <PID> /data/local/tmp/heap.txt
adb shell memunreachable <PID>

延伸思考

采集到的PCM数据可以通过以下方式编码为AAC: 1. 使用FFmpeg的avcodec_encode_audio2 2. 集成Android MediaCodec硬编码 3. 采用第三方库如libfdk-aac

建议先通过ffprobe分析原始PCM参数,确保采样率、通道数与编码器要求匹配。

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐