项目中需要使用外接摄像头录制音频和视频,需要切换到OTG模式,测试是发现视频是可以录制的,使用的是开源库androidusbcamera,但是只有视频,没有音频,经过排查发现Android4.4不支持外接USB MIC,所以无法通过应用层接口直接录制。
通过讨论发现,安卓系统自带的tinycap是可以录制USB MIC音频,于是参考tinycap.c代码,通过JNI方式成功录制了USB MIC上的音频。
tinycap介绍
tinycap是tinyalsa中的一个录音模块,参考tinyalsa的使用
关键基于系统的C接口#include <sound/asound.h>
主要涉及pcm_open()
、pcm_read()
、pcm_start()
它位于源码目录aosp/external/tinyalsa$
系统权限
tinycap录音需要系统权限,因为USB声卡只能由系统应用去访问。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:sharedUserId="android.uid.system" package="com.flyscale.testusbmic2">
JNI接口
java
package com.android.usbmic; import android.util.Log; public class USBMicManager { private static final String TAG = "USBMicManager"; private static USBAudioRecordListener mListener; static { System.loadLibrary("USBMic"); } public native static String getUSBMicDeviceName(); public native static int startRecord(int sampleRate, int channel); public native static int stopRecord(); public native static int isUSBMicAvailable(); /** * 音频数据回调 与startRecord同一线程 * * @param data * @param size */ public static void onPCMDataCallback(final byte[] data, final int size) { Log.d(TAG, "onPCMDataCallback size=" + size); if (mListener != null) { mListener.onDataArrived(data, size); } } public static void setUSBAudioRecordListener(USBAudioRecordListener listener) { mListener = listener; } }
cpp
jbyteArray ConvertCharsToJByteArray(JNIEnv *env, char *chars, int length) { jbyte jbytes[length]; memset(&jbytes, 0, length); memcpy(&jbytes, chars, length); //below is the return 's bytearray lens jbyteArray jarray = env->NewByteArray(length); env->SetByteArrayRegion(jarray, 0, length, jbytes); return jarray; } void pcmCallback(char *data, unsigned int count) { LOGD("pcmCallback, size=%d", count); int size = count; for (int i = 0; i < count; i++) { LOGD("%d=%d", i, data[i]); } // jbyteArray result = NULL; //找到需要调用的方法ID jmethodID javaCallback = local_env->GetStaticMethodID(local_clazz, "onPCMDataCallback", "([BI)V"); jbyteArray result = ConvertCharsToJByteArray(local_env, data, size); local_env->CallStaticVoidMethod(local_clazz, javaCallback, result, size); local_env->DeleteLocalRef(result);//要及时删除本地引用 } JNIEXPORT jstring JNICALL Java_com_android_usbmic_USBMicManager_getUSBMicDeviceName (JNIEnv *env, jclass clazz) { return env->NewStringUTF("USB_DEVICE_INPUT_DEVICE"); } JNIEXPORT jint JNICALL Java_com_android_usbmic_USBMicManager_startRecord (JNIEnv *env, jclass clazz, jint sample_rate, jint channel) { local_env = env; local_clazz = clazz; char *params[] = {"/sdcard/record.wav", "0", "1", "16000", "16", "4"}; //tinycap /data/rec.wav -D 4 -d 0 -c 1 -r 16000 -b 16 -T 10 return startRecord("/mnt/sdcard/usbrecordtest.wav", 0, channel, sample_rate, 16, 4, pcmCallback); } JNIEXPORT jint JNICALL Java_com_android_usbmic_USBMicManager_stopRecord (JNIEnv *env, jclass clazz) { sigint_handler(0); return 0; } JNIEXPORT jint JNICALL Java_com_android_usbmic_USBMicManager_isUSBMicAvailable (JNIEnv *env, jclass clazz) { char fn[256]; unsigned int card = 4; unsigned int device = 0; unsigned int flags = PCM_IN; snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p'); if (access(fn, 0) == 0) { LOGD("USB声卡 %s 存在", fn); if (access(fn, R_OK) == 0) { LOGD("USB声卡 %s 可读", fn); return 0; } else { LOGE("USB声卡 %s 不可读!", fn); return -2; } } else { LOGE("USB声卡 %s 不存在", fn); return -1; } }
修改后的tinycap.c
#include "include/tinyalsa/asoundlib.h" #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <signal.h> #include <string.h> #include <android/log.h> #include<errno.h> #include <unistd.h> #include <pthread.h> #include "tinycap.h" #define ID_RIFF 0x46464952 #define ID_WAVE 0x45564157 #define ID_FMT 0x20746d66 #define ID_DATA 0x61746164 #define FORMAT_PCM 1 #define TAG "USBMicManager-JNI" // 这个是自定义的LOG的标识 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/ struct wav_header { uint32_t riff_id; uint32_t riff_sz; uint32_t riff_fmt; uint32_t fmt_id; uint32_t fmt_sz; uint16_t audio_format; uint16_t num_channels; uint32_t sample_rate; uint32_t byte_rate; uint16_t block_align; uint16_t bits_per_sample; uint32_t data_id; uint32_t data_sz; }; int capturing = 0; unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, unsigned int rate, enum pcm_format format, unsigned int period_size, unsigned int period_count, void(*callback)(char *, unsigned int)); void sigint_handler(int sig) { LOGD("sigint_handler stop..."); capturing = 0; } int startRecord(char* path, int dev, int channel, int sample_rate, int foramt, int src_card, void(*callback)(char *, unsigned int)) { LOGD("startRecord..."); pthread_mutex_lock(&mutex); LOGD("get lock success"); capturing = 0; FILE *file; struct wav_header header; unsigned int card = src_card; //声卡 unsigned int device = dev; //设备 unsigned int channels = channel; //通道 unsigned int rate = sample_rate; //比特率 unsigned int bits = foramt; // unsigned int frames; unsigned int period_size = 1024; unsigned int period_count = 4; enum pcm_format format; file = fopen(path, "wb"); if (!file) { fprintf(stderr, "Unable to create file '%s'\n", path); LOGE("Unable to create file '%s'\n", path); LOGE("open file err:%d\n", errno); capturing = 0; pthread_mutex_unlock(&mutex); return 1; } // char buff[] = {249, 254, 249, 88, 251, 78, 252, 229, 0, 104}; // callback(buff, 10); if (0) { capturing = 0; pthread_mutex_unlock(&mutex); return 1; } if (capturing == 1) { LOGE("recording already started!"); pthread_mutex_unlock(&mutex); return -2; } capturing = 1; // int i = 0; // while (i < argc && capturing) { // LOGD("param[%d]=%s,capturing=%d", i, *argv, capturing); //// callback(NULL, 10); // i++; // argv++; //// sleep(2); // } // device = atoi(*argv); // channels = atoi(*argv); // rate = atoi(argv[3]); // bits = atoi(argv[4]); // card = atoi(argv[5]); // device = 0; // channels = 1; // rate = 16000; // bits = 16; // card = 4; header.riff_id = ID_RIFF; header.riff_sz = 0; header.riff_fmt = ID_WAVE; header.fmt_id = ID_FMT; header.fmt_sz = 16; header.audio_format = FORMAT_PCM; header.num_channels = channels; header.sample_rate = rate; switch (bits) { case 32: format = PCM_FORMAT_S32_LE; break; case 24: format = PCM_FORMAT_S24_LE; break; case 16: format = PCM_FORMAT_S16_LE; break; default: fprintf(stderr, "%d bits is not supported.\n", bits); LOGE("%d bits is not supported.\n", bits); return 1; } header.bits_per_sample = pcm_format_to_bits(format); header.byte_rate = (header.bits_per_sample / 8) * channels * rate; header.block_align = channels * (header.bits_per_sample / 8); header.data_id = ID_DATA; /* leave enough room for header */ fseek(file, sizeof(struct wav_header), SEEK_SET); /* install signal handler and begin capturing */ // signal(SIGINT, sigint_handler); frames = capture_sample(file, card, device, header.num_channels, header.sample_rate, format, period_size, period_count, callback); printf("Captured %d frames\n", frames); LOGD("Captured %d frames\n", frames); /* write header now all information is known */ header.data_sz = frames * header.block_align; header.riff_sz = header.data_sz + sizeof(header) - 8; fseek(file, 0, SEEK_SET); fwrite(&header, sizeof(struct wav_header), 1, file); fclose(file); pthread_mutex_unlock(&mutex); return 0; } unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels, unsigned int rate, enum pcm_format format, unsigned int period_size, unsigned int period_count, void(*callback)(char *, unsigned int)) { LOGD("capture_sample"); struct pcm_config config; struct pcm *pcm; char *buffer; unsigned int size; unsigned int bytes_read = 0; config.channels = channels; config.rate = rate; config.period_size = period_size; config.period_count = period_count; config.format = format; config.start_threshold = 0; config.stop_threshold = 0; config.silence_threshold = 0; pcm = pcm_open(card, device, PCM_IN, &config); if (!pcm || !pcm_is_ready(pcm)) { LOGE("Unable to open PCM device (%s)\n", pcm_get_error(pcm)); return 0; } size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); buffer = malloc(size); if (!buffer) { LOGE("Unable to allocate %d bytes\n", size); free(buffer); pcm_close(pcm); return 0; } LOGD("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate, pcm_format_to_bits(format)); while (capturing && !pcm_read(pcm, buffer, size)) { callback(buffer, size); /*if (fwrite(buffer, 1, size, file) != size) { LOGE("Error capturing sample\n"); break; }*/ bytes_read += size; LOGD("capture %d bytes", size); } free(buffer); pcm_close(pcm); return pcm_bytes_to_frames(pcm, bytes_read); } int stopRecord() { LOGD("stopRecord"); capturing = 0; pthread_mutex_unlock(&mutex); return 0; }