使用javacv录像,同时进行讯飞声纹认证
由于最近的demo中需要在活体检测的同时进行音视频录制 , 尝试使用MediaRecord和camera来录制视频 , 然而Camera.onPreviewFrame 不能与 MediaRecord同时调用。活体检测的原理其实是把camera的预览回调onPreviewFrame(byte[] data, Camera camera) 中的图片数据data作为参数传递到活体检测引擎中去拿返回的检测结果码,由于种种原因 , 不能使用Camera2实现 , 于是通过谷歌了解到javacv这个库可以录制视频 , 下了几个demo , 感觉不仅满足需求 , 录制的视频质量也还可以。使用javacv中的FrameRecorder进行录像,录像的时候,调用record方法写帧数据和音频数据,这时候我们有一个需求,录像的同时,要把声音实时拿过来进行声纹认证。由此产生了2个问题:
问题1:
语音识别用的是讯飞的SDK,要求声音采样率8k或16k。而设置FrameRecorder.setSampleRate(8000)后,再FrameRecorder.start()会报错,报错如下:
avcodec_encode_audio2() error 2: Could not encode audio packet.
问题2:
javacv官方录制demo中,从AudioRecord中read到的是ShortBuffer,而讯飞SDK方法要求传入byte,他的方法如下:
public void writeAudio(byte[] data, int start, int length)
百度谷歌无果,只好自己研究。
使用javacv进行录像
下面是使用javacv进行录像的示例代码:
1. 初始化 ffmpeg_recorder
public void initRecorder() {
String ffmpeg_link = parentPath + "/" + "video.mp4";
Log.w(LOG_TAG, "init recorder");
if (yuvIplimage == null) {
yuvIplimage = IplImage.create(cameraManager.getDefaultSize().width,
cameraManager.getDefaultSize().height, IPL_DEPTH_8U, 2);
Log.i(LOG_TAG, "create yuvIplimage");
}
Log.i(LOG_TAG, "ffmpeg_url: " + ffmpeg_link);
recorder = new FFmpegFrameRecorder(ffmpeg_link,
cameraManager.getDefaultSize().width,
cameraManager.getDefaultSize().height, 1);
recorder.setFormat("mp4");
recorder.setSampleRate(sampleAudioRateInHz);
// Set in the surface changed method
recorder.setFrameRate(frameRate);
Log.i(LOG_TAG, "recorder initialize success");
audioRecordRunnable = new AudioRecordRunnable();
audioThread = new Thread(audioRecordRunnable);
try {
recorder.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
audioThread.start();
}
2. 捕捉摄像头视频数据:
public void onPreviewFrame(byte[] data, Camera camera) {
int during = checkIfMax(new Date().getTime());
/* get video data */
if (yuvIplimage != null && isStart) {
yuvIplimage.getByteBuffer().put(data);
//yuvIplimage = rotateImage(yuvIplimage.asCvMat(), 90).asIplImage();
Log.v(LOG_TAG, "Writing Frame");
try {
System.out.println(System.currentTimeMillis() - videoStartTime);
if (during < 6000) {
recorder.setTimestamp(1000 * during);
recorder.record(yuvIplimage);
}
} catch (FFmpegFrameRecorder.Exception e) {
Log.v(LOG_TAG, e.getMessage());
e.printStackTrace();
}
}
}
3. 捕捉声音数据:
class AudioRecordRunnable implements Runnable {
@Override
public void run() {
android.os.Process
.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
// Audio
int bufferSize;
short[] audioData;
int bufferReadResult;
bufferSize = AudioRecord.getMinBufferSize(sampleAudioRateInHz,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
sampleAudioRateInHz,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, bufferSize);
audioData = new short[bufferSize];
Log.d(LOG_TAG, "audioRecord.startRecording()");
audioRecord.startRecording();
/* ffmpeg_audio encoding loop */
while (!isFinished) {
// Log.v(LOG_TAG,"recording? " + recording);
bufferReadResult = audioRecord.read(audioData, 0,
audioData.length);
if (bufferReadResult > 0) {
// Log.v(LOG_TAG, "bufferReadResult: " + bufferReadResult);
// If "recording" isn't true when start this thread, it
// never get's set according to this if statement...!!!
// Why? Good question...
if (isStart) {
try {
Buffer[] barray = new Buffer[1];
barray[0] = ShortBuffer.wrap(audioData, 0,
bufferReadResult);
recorder.record(barray);
// Log.v(LOG_TAG,"recording " + 1024*i + " to " +
// 1024*i+1024);
} catch (FFmpegFrameRecorder.Exception e) {
Log.v(LOG_TAG, e.getMessage());
e.printStackTrace();
}
}
}
}
Log.v(LOG_TAG, "AudioThread Finished, release audioRecord");
/* encoding finish, release recorder */
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
audioRecord = null;
Log.v(LOG_TAG, "audioRecord released");
}
}
}
解决问题1:
demo中默认设置FrameRecorder.setSampleRate(44100)没问题,想到一个办法,这个地方设置44100,在语音采集的地方设置8000,最后成功了。不过这个计算时间的方法要修改:
public static int getTimeStampInNsFromSampleCounted(int paramInt) {
// return (int) (paramInt / 0.0441D);
return (int) (paramInt / 0.0080D);
}
解决问题2:
short数组转byte数组,注意数组长度变为原来的2倍
public static byte[] short2byte(short[] sData) {
int shortArrsize = sData.length;
byte[] bytes = new byte[shortArrsize * 2];
for (int i = 0; i < shortArrsize; i++) {
bytes[i * 2] = (byte) (sData[i] & 0x00FF);
bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
sData[i] = 0;
}
return bytes;
}
录制音频源码:
/**
* 录制音频的线程
*/
class AudioRecordRunnable implements Runnable {
short[] audioData;
private final AudioRecord audioRecord;
private int mCount = 0;
int sampleRate = Constants.AUDIO_SAMPLING_RATE; private AudioRecordRunnable() {
int bufferSize = AudioRecord.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
audioData = new short[bufferSize]; } /**
* 包含了音频的数据和起始位置
*
* @param buffer
*/
private void record(Buffer buffer) {
synchronized (mAudioRecordLock) {
this.mCount += buffer.limit();
if (!mIsPause) {
try {
if (mRecorder != null) {
mRecorder.record(sampleRate, new Buffer[]{buffer});
}
} catch (FrameRecorder.Exception e) {
e.printStackTrace();
}
}
}
} /**
* 更新音频的时间戳
*/
private void updateTimestamp() {
int i = Util.getTimeStampInNsFromSampleCounted(this.mCount);
if (mAudioTimestamp != i) {
mAudioTimestamp = i;
mAudioTimeRecorded = System.nanoTime();
}
} public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
if (audioRecord != null) {
//判断音频录制是否被初始化
while (this.audioRecord.getState() == 0) {
try {
Thread.sleep(100L);
} catch (InterruptedException localInterruptedException) {
}
}
this.audioRecord.startRecording();
while ((runAudioThread)) {
updateTimestamp();
int bufferReadResult = this.audioRecord.read(audioData, 0, audioData.length);
if (bufferReadResult > 0) {
if (recording || (mVideoTimestamp > mAudioTimestamp)) {
record(ShortBuffer.wrap(audioData, 0, bufferReadResult));
}
if (SpeechManager.getInstance().isListening()) {
SpeechManager.getInstance().writeAudio(Util.short2byte(audioData), 0, bufferReadResult * 2);
}
}
}
SpeechManager.getInstance().stopListener();
this.audioRecord.stop();
this.audioRecord.release();
}
}
}
使用javacv录像,同时进行讯飞声纹认证的更多相关文章
- Android 讯飞语音听写SDK快速接入(附空指针解决和修改对话框文字方法)
1.账号准备工作 首先要有一个讯飞的账号啦,为后面申请APPID.APPKey等东西做准备.顺带一提:讯飞对不同认证类型用户开 放的SDK的使用次数是有不同的,详情如下图. 账号申请完成后,需要去你自 ...
- 一百元的智能家居——Asp.Net Mvc Api+讯飞语音+Android+Arduino
大半夜的,先说些废话提提神 如今智能家居已经不再停留在概念阶段,高大上的科技公司都已经推出了自己的部分或全套的智能家居解决方案,不过就目前的现状而言,大多还停留在展厅阶段,还没有广泛的推广起来,有人说 ...
- android用讯飞实现TTS语音合成 实现中文版
Android系统从1.6版本开始就支持TTS(Text-To-Speech),即语音合成.但是android系统默认的TTS引擎:Pic TTS不支持中文.所以我们得安装自己的TTS引擎和语音包. ...
- 讯飞语音SDK Android平台使用
1. 支持功能介绍: 2. Android API主要业务接口和流程介绍 -------------------------------------------------------- 工程代码: ...
- 关于讯飞语音SDK开发学习
前奏,浑浑噩噩已经工作一年多,这一年多收获还是挺多的.逛园子应该有两年多了,工作后基本上是天天都会来园子逛逛,园子 里还是有很多牛人写了一些不错的博客,帮我解决很多问题.但是一直没写过博客,归根到底一 ...
- 基于讯飞语音API应用开发之——离线词典构建
最近实习在做一个跟语音相关的项目,就在度娘上搜索了很多关于语音的API,顺藤摸瓜找到了科大讯飞,虽然度娘自家也有语音识别.语义理解这块,但感觉应该不是很好用,毕竟之前用过百度地图的API,有问题也找不 ...
- Android讯飞语音云语音听写学习
讯飞语音云语音听写学习 这几天两个舍友都买了iPhone 6S,玩起了"Hey, Siri",我依旧对我的Nexus 5喊着"OK,Google" ...
- 关于讯飞 使用android SDK出现21001错误码的分析
21001,没有安装语音组件1.有没有使用SpeechUtility.createUtility()设置appid2.有没有将libmsc.so放到工程中,jar包有Msc.jar.Sunflower ...
- iOS: 讯飞语音的使用
一.介绍: 讯飞语音做的相当不错,容错率达到90%多,如果需要做语音方面的功能,它绝对是一个不错的选择.讯飞语音的功能很多:语音听写.语音识别.语音合成等,但我们最常用的还是语音听写.讯飞语音中包含界 ...
随机推荐
- Python学习笔记(五)—列表的学习
总结内容: 1.list的定义 2.list的取值 3.list数据的增加 4.list数据的删除 5.list数据的修改 6.list数据的查询 7.list方法的介绍 8.list的合并 9.多维 ...
- Java_如何等待子线程执行结束
工作中往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的产景, 这时候就需要使用线程了. 一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线 ...
- 正确率、召回率和F值
正确率.召回率和F值是在鱼龙混杂的环境中,选出目标的重要评价指标. 不妨看看这些指标的定义先: 正确率 = 正确识别的个体总数 / 识别出的个体总数 召回率 = 正确识别的个体总数 / 测试集中存 ...
- 2007 Audi A4 INSTRUMENT CLUSTER WIRING DIAGRAM
BOSCH RB8 8E0920 951G I found the answer by myself...... Here is what it's work for me. GREEN CONNEC ...
- datagrid在MVC中的运用04-同时添加搜索和操作区域
本文介绍在datagrid上同时添加搜索和操作区域. 仅仅是增加操作区域 □ 方法1 $('#dg').datagrid({ toolbar: '#tb' }); <div id="t ...
- MVC使用TempData跨控制器传递信息而无需记住key的名称
通常情况下,使用TempData需要记住key的名称,本篇体验:通过帮助类,实现对TempData的设置.获取.删除. 关于传递信息的类: namespace MvcApplication1.Mode ...
- 使用jQuery异步传递Model到控制器方法,并异步返回错误信息
需要通过jquery传递到控制器方法的Model为: public class Person { public string Name { get; set; } public int Age { g ...
- 汉字转拼音的Java类库——JPinyin
原文:http://blog.csdn.net/stuxuhai/article/details/8932715 [JPinyin主要特性]1.准确.完善的字库:Unicode编码从4E00-9FA5 ...
- objective-c block 详解 转
Block Apple 在C, Objective-C, C++加上Block這個延申用法.目前只有Mac 10.6 和iOS 4有支援.Block是由一堆可執行的程式組成,也可以稱做沒有名字的F ...
- Eclipse:引用一个项目作为类库(图文教程)
前言:项目TestRoid要引用Volley项目作为类库 步骤如下: 一:选择导入Android项目 二:选择Volley项目路径导入 三:右击Volley项目,点击Properties 四: ...