安卓音视频开发-USB外接MIC录制音频
2022-05-16 本文已影响0人
DD_Dog
项目中需要使用外接摄像头录制音频和视频,需要切换到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;
}