前段时间,在firefly3399开发板上实现了基于思必驰的语音识别功能。关于这个开发板的资料请查看官方网站http://www.t-/product/rk3399.html。
使用的是思必驰的多麦技术,6声道的音频(4个mic+2个回升参考音)直接进主控rk3399。讯飞也有专门的6麦克风阵列模块做语音处理直接出2声道音频给主控音频。我使用的这种多声道直接进主控的方式能够进一步省成本。
下面分享在android下如何获取多声道的音频数据。其实很简单,alsa本身已经支持多声道的录音了,android的tinycap也已经支持,我把tinycap封装成jni接口,应用程序使用我这个jni接口就直接能进行多声道录音了。
/* tinycap.c
**
** Copyright , The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of The Android Open Source Project nor the names of
** its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LOGD(...) \
__android_log_print(ANDROID_LOG_DEBUG, "AudioHubNative", __VA_ARGS__)
#define UNUSED __attribute__((unused))
#ifndef false
#define false 0
#define true 1
#endif
static JavaVM* java_vm = NULL;
static jclass com_andorid_audiohub_AudioCapture = NULL;
static jmethodID com_andorid_audiohub_AudioCapture_read;
int capturing = 1;
static void volume_process(const void *buffer, size_t length, float volume, unsigned int channels) {
short * buffer_end = (short*)buffer + (length/2);
short * pcmData = (short *)buffer;
unsigned int i = 1;
while (pcmData < buffer_end) {
if(i >= channels-1){
*pcmData = (short)((float)*pcmData * 0.6);
}
else
*pcmData = (short)((float)*pcmData * volume);
if(*pcmData > 32767)
*pcmData = 32767;
++pcmData;
if(i == channels){
i = 1;
}else{
i++;
}
}
}
unsigned int do_capture(unsigned int card, unsigned int device,
unsigned int channels, unsigned int rate,
enum pcm_format format, unsigned int period_size,
unsigned int period_count)
{
struct pcm_config config;
struct pcm *pcm;
char *buffer;
unsigned int size;
unsigned int bytes_read = 0;
memset(&config, 0, sizeof(config));
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)) {
fprintf(stderr, "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) {
fprintf(stderr, "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));
JNIEnv * env;
void * void_env;
bool had_to_attach = false;
jint status = (*java_vm)->GetEnv(java_vm, &void_env, JNI_VERSION_1_6);
if (status == JNI_EDETACHED) {
(*java_vm)->AttachCurrentThread(java_vm, &env, NULL);
had_to_attach = true;
} else {
env = void_env;
}
jbyteArray audioByteArray = (*env)->NewByteArray(env, size);
capturing = 1;
while (capturing && !pcm_read(pcm, buffer, size)) {
volume_process(buffer, size, 4.0, channels);
// Call write()
(*env)->SetByteArrayRegion(env, audioByteArray, 0, size, (jbyte *)buffer);
(*env)->CallStaticVoidMethod(env, com_andorid_audiohub_AudioCapture,
com_andorid_audiohub_AudioCapture_read, audioByteArray);
bytes_read += size;
}
(*env)->DeleteLocalRef(env, audioByteArray);
if (had_to_attach) {
(*java_vm)->DetachCurrentThread(java_vm);
}
free(buffer);
pcm_close(pcm);
return pcm_bytes_to_frames(pcm, bytes_read);
}
static void* read_thread()
{
do_capture(0, 0, 6, 16000, PCM_FORMAT_S16_LE, 512, 6);
return NULL;
}
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved UNUSED)
{
LOGD("libAudioHub: loaded");
java_vm = vm;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved UNUSED)
{
JNIEnv * env;
void * void_env;
(*java_vm)->GetEnv(vm, &void_env, JNI_VERSION_1_6);
env = void_env;
(*env)->DeleteGlobalRef(env, com_andorid_audiohub_AudioCapture);
LOGD("libAudioHub: unloaded");
}
JNIEXPORT jboolean JNICALL
Java_com_android_audiohub_AudioHub_setup(JNIEnv* env UNUSED, jobject foo UNUSED)
{
// Get write callback handle
jclass clazz = (*env)->FindClass(env, "com/android/audiohub/AudioCapture");
if (!clazz) {
LOGD("Could not find AudioCapture");
return false;
}
com_andorid_audiohub_AudioCapture = (*env)->NewGlobalRef(env, clazz);
com_andorid_audiohub_AudioCapture_read = (*env)->GetStaticMethodID(env,
com_andorid_audiohub_AudioCapture, "read", "([B)V");
if (!com_andorid_audiohub_AudioCapture_read) {
LOGD("Could not find com.android.audiohub.AudioCapture");
(*env)->DeleteGlobalRef(env, com_andorid_audiohub_AudioCapture);
return false;
}
// Good to go
LOGD("Starting capture");
int ret;
pthread_t id;
pthread_attr_t attr;
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&id, &attr, read_thread, NULL);
return true;
}
JNIEXPORT void JNICALL
Java_com_android_audiohub_AudioHub_close(JNIEnv* env UNUSED, jobject foo UNUSED) {
capturing = 0;
}
上面的jni使用在android平台都是通用的,具体到不同的开发板只要修改不同的参数即可。
对于firefly3399的开发板,i2s0可支持最多8声道录音。我直接利用了hdmi这张声卡,这样少写代码。
diff --git a/kernel/sound/soc/codecs/hdmi-codec.c b/kernel/sound/soc/codecs/hdmi-code
old mode 100644
new mode 100755
index ad750f4..8dc0857
--- a/kernel/sound/soc/codecs/hdmi-codec.c
+++ b/kernel/sound/soc/codecs/hdmi-codec.c
@@ -302,7 +302,7 @@ static const struct snd_soc_dai_ops hdmi_dai_ops = {
};
-#define HDMI_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+#define HDMI_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
SNDRV_PCM_RATE_192000)
@@ -337,7 +337,14 @@ static struct snd_soc_dai_driver hdmi_i2s_dai = {
.formats = I2S_FORMATS,
.sig_bits = 24,
},
- .ops = &hdmi_dai_ops,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = I2S_FORMATS,
+ },
+// .ops = &hdmi_dai_ops,
};
diff --git a/kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-core.dtsi b/kernel/ar
old mode 100644
new mode 100755
index 39f2be7..f621794
--- a/kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-core.dtsi
+++ b/kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-core.dtsi
@@ -109,7 +109,7 @@
simple-audio-card,name = "rockchip,hdmi";
simple-audio-card,cpu {
- sound-dai = ;
+ sound-dai = ;
};
simple-audio-card,codec {
sound-dai = ;