chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Paraformer online
|
||||
|
||||
该项目是一个基于iOS端的Paraformer流式识别的demo。该项目是基于onnx的推理c++版本。不需要依赖其他库和额外的配置,直接在xcode上编译运行即可。
|
||||
|
||||
## 获取模型
|
||||
1. 通过命令 `git clone https://www.modelscope.cn/damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx.git` 下载模型
|
||||
2. 下载的是量化后的模型,该iOS项目需要用到四个文件,`model_quant.onnx`,`decoder_quant.onnx`,`am.mvn`和`config.yaml`
|
||||
3. 将上面四个文件拖入xcode项目中的`model`文件夹下,建议在打开xcode项目后再拖入。
|
||||
|
||||
## 识别流程
|
||||
1. mac设备上确保安装xcode;
|
||||
2. 由于onnx是xcode的一个cocopod包,需要先依赖onnx环境,打开终端,去到包含Podfile文件的文件夹下,执行`pod install`,拉取onnx环境
|
||||
3. mac连接iPhone设备,双击`paraformer_online.xcworkspace`,自动打开xcode,直接运行该xcode工程。
|
||||
|
||||
## 未来工作
|
||||
* coreml支持
|
||||
* 加入流式标点
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
platform :ios, '12.0'
|
||||
target 'paraformer_online' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'onnxruntime-c'
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
uuid = "A434E096-73C6-4C03-86F5-FAA8A72D79B0"
|
||||
type = "1"
|
||||
version = "2.0">
|
||||
</Bucket>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>paraformer_online.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AppDelegate.h
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AppDelegate.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for customization after application launch.
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UISceneSession lifecycle
|
||||
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||
}
|
||||
|
||||
|
||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AudioCapture.h
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^SpeechRecognitionBlock)(NSString *result);
|
||||
|
||||
@interface AudioCapture : NSObject
|
||||
|
||||
@property (nonatomic,copy) SpeechRecognitionBlock resultBlock;
|
||||
|
||||
- (id)initWithOnnxModel:(BOOL)onnxModel;
|
||||
|
||||
- (void)startRecorder;
|
||||
- (void)stopRecorder;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AudioCapture.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import "AudioCapture.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#include <thread>
|
||||
|
||||
#import "AudioRecorder.h"
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#define Recorder_Sample_Rate 16000
|
||||
#define Samples_Per_Frame (Recorder_Sample_Rate/100)
|
||||
|
||||
#define k_input_frames 10
|
||||
#define k_left_padding_frames 5
|
||||
#define k_right_padding_frames 5
|
||||
#define k_input_samples 960 // (60ms)
|
||||
|
||||
|
||||
static AudioCapture *selfClass = nil;
|
||||
|
||||
@interface AudioCapture ()<AVCaptureAudioDataOutputSampleBufferDelegate>
|
||||
|
||||
@property (nonatomic, strong) AVCaptureSession *capture;
|
||||
|
||||
@property (nonatomic, copy) NSMutableData *sampleData;
|
||||
|
||||
@property (nonatomic, strong) AudioRecorder *audioRecorder;
|
||||
|
||||
@property (nonatomic, assign) BOOL isRecording;
|
||||
|
||||
@property (nonatomic, strong) NSLock *lock;
|
||||
|
||||
@end
|
||||
|
||||
using namespace funasr;
|
||||
|
||||
@implementation AudioCapture
|
||||
{
|
||||
const char* output_path;
|
||||
void* denoiser;
|
||||
void* resampler;
|
||||
|
||||
// Paraformer_stream *stream_;
|
||||
|
||||
FUNASR_HANDLE asr_handle_;
|
||||
FUNASR_HANDLE online_handle_;
|
||||
|
||||
bool is_onnx;
|
||||
|
||||
char *speech_buff;
|
||||
}
|
||||
int packetIndex = 0;
|
||||
|
||||
- (id)initWithOnnxModel:(BOOL)onnxModel
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
is_onnx = onnxModel ? true : false;
|
||||
|
||||
if (is_onnx) {
|
||||
[self initASROnnx];
|
||||
} else {
|
||||
// [self initASR];
|
||||
}
|
||||
self.isRecording = NO;
|
||||
|
||||
NSLog(@"model init done!");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
void S16ToFloatS16_1(const int16_t* src, size_t size, float* dest) {
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
dest[i] = (float)src[i];
|
||||
}
|
||||
|
||||
void CharToFloat(const char* src, size_t size, float* dst) {
|
||||
const int16_t* sample_data = reinterpret_cast<const int16_t*>(src);
|
||||
S16ToFloatS16_1(sample_data, size/2, dst);
|
||||
}
|
||||
|
||||
void CharToFloat_1(const char* src, size_t size, float* dst) {
|
||||
const int16_t* sample_data = reinterpret_cast<const int16_t*>(src);
|
||||
// length = length/2;
|
||||
float data_f[Samples_Per_Frame] = {0.0};
|
||||
S16ToFloatS16_1(sample_data, size/2, data_f);
|
||||
|
||||
// float data_f_norm[480];
|
||||
for (int i = 0; i < Samples_Per_Frame; i++) {
|
||||
dst[i] = data_f[i] / 32767.0;
|
||||
}
|
||||
}
|
||||
|
||||
void CharToS16(const char* src, size_t src_size, short* dst) {
|
||||
const int16_t* sample_data = reinterpret_cast<const int16_t*>(src);
|
||||
for (int i = 0; i < src_size/2; i++) {
|
||||
dst[i] = sample_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)initASROnnx {
|
||||
NSString *model_file_path = [[NSBundle mainBundle] pathForResource:@"config" ofType:@".yaml" inDirectory:@"model"];
|
||||
model_file_path = [model_file_path stringByReplacingOccurrencesOfString:@"config.yaml" withString:@""];
|
||||
const char* model_dir= [model_file_path UTF8String];
|
||||
|
||||
std::map<std::string, std::string> model_path;
|
||||
model_path.insert({MODEL_DIR, model_dir});
|
||||
model_path.insert({QUANTIZE, "true"});
|
||||
|
||||
struct timeval start, end;
|
||||
// gettimeofday(&start, NULL);
|
||||
int thread_num = 1;
|
||||
asr_handle_ = FunASRInit(model_path, thread_num, ASR_ONLINE);
|
||||
|
||||
if (!asr_handle_)
|
||||
{
|
||||
std::cout << "FunVad init failed" << std::endl;
|
||||
}
|
||||
|
||||
// gettimeofday(&end, NULL);
|
||||
long seconds = (end.tv_sec - start.tv_sec);
|
||||
long modle_init_micros = ((seconds * 1000000) + end.tv_usec) - (start.tv_usec);
|
||||
std::cout << "Model initialization takes " << (double)modle_init_micros / 1000000 << " s" << std::endl;
|
||||
|
||||
string default_id = "wav_default_id";
|
||||
|
||||
// init online features
|
||||
online_handle_ = FunASROnlineInit(asr_handle_);
|
||||
|
||||
}
|
||||
|
||||
//static FILE *pf_file_out = NULL;
|
||||
//int audio_index = 0;
|
||||
- (void)startRecorder {
|
||||
selfClass = self;
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[self.sampleData setLength:0];
|
||||
// audio record call-back
|
||||
// float speech[10 * 960];
|
||||
speech_buff = (char *)calloc(10*960*2, sizeof(char));
|
||||
__block int speech_idx = 0;
|
||||
self.audioRecorder.inputBlock = ^(NSData *speechData) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (weakSelf.isRecording) {
|
||||
// [weakSelf appendPCMData:speechData]; // DUBUG USE: append audio data, Memory increment
|
||||
const char* buffer = (const char*)speechData.bytes;
|
||||
int length = (int)speechData.length;
|
||||
|
||||
if (strongSelf->is_onnx) {
|
||||
// char speech_buff[9600*2];
|
||||
int step = 9600*2;
|
||||
|
||||
memcpy(speech_buff+length*speech_idx, buffer, length*sizeof(char));
|
||||
|
||||
// FIX: You can change it to a ring buffer
|
||||
if (speech_idx == k_input_frames*6-1) {
|
||||
FUNASR_RESULT result = FunASRInferBuffer(strongSelf->online_handle_, speech_buff, step, RASR_NONE, NULL, false, 16000);
|
||||
|
||||
memset(speech_buff, 0, step * sizeof(char));
|
||||
speech_idx = 0;
|
||||
|
||||
if (result)
|
||||
{
|
||||
string msg = FunASRGetResult(result, 0);
|
||||
// std::cout<<msg << std::endl;
|
||||
FunASRFreeResult(result);
|
||||
|
||||
if (strongSelf.resultBlock) {
|
||||
strongSelf.resultBlock([NSString stringWithUTF8String:msg.c_str()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
speech_idx++;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
[self.audioRecorder start];
|
||||
self.isRecording = YES;
|
||||
}
|
||||
|
||||
- (void)pushData {
|
||||
}
|
||||
|
||||
- (void)stopRecorder {
|
||||
self.isRecording = NO;
|
||||
if (is_onnx) {
|
||||
FunASRUninit(asr_handle_);
|
||||
FunASRUninit(online_handle_);
|
||||
}
|
||||
|
||||
free(speech_buff);
|
||||
|
||||
[self.audioRecorder stop];
|
||||
[self writeData];
|
||||
|
||||
/////
|
||||
// const char* buffer = (const char*)self.sampleData.bytes;
|
||||
// int length = (int)self.sampleData.length;
|
||||
//
|
||||
// float *input_data = (float *)malloc(sizeof(float) * (length/2));
|
||||
// CharToFloat(buffer, length, input_data);
|
||||
//
|
||||
// std::string msg = stream_->Process(input_data, length/2);
|
||||
// NSString *result = [NSString stringWithUTF8String:msg.c_str()];
|
||||
//
|
||||
// if (self.resultBlock) {
|
||||
// self.resultBlock(result);
|
||||
// }
|
||||
//
|
||||
// free(input_data);
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)writeData {
|
||||
return [self.sampleData writeToFile:[self getPCMPath] atomically:NO];
|
||||
}
|
||||
|
||||
- (void)appendPCMData:(NSData *)pcmData {
|
||||
[self audioProcessing:pcmData];
|
||||
}
|
||||
|
||||
- (void)audioProcessing:(NSData *)data {
|
||||
[self.sampleData appendData:data];
|
||||
}
|
||||
|
||||
- (NSMutableData *)sampleData {
|
||||
if (!_sampleData) {
|
||||
_sampleData = [NSMutableData data];
|
||||
}
|
||||
|
||||
return _sampleData;
|
||||
}
|
||||
|
||||
- (AudioRecorder *)audioRecorder {
|
||||
if (!_audioRecorder) {
|
||||
_audioRecorder = [[AudioRecorder alloc] init];
|
||||
}
|
||||
|
||||
return _audioRecorder;
|
||||
}
|
||||
|
||||
- (NSLock *)lock {
|
||||
if (!_lock) {
|
||||
_lock = [NSLock new];
|
||||
}
|
||||
|
||||
return _lock;
|
||||
}
|
||||
|
||||
- (NSString *)getPCMPath {
|
||||
NSString *directoryS = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSString *directory = [directoryS stringByAppendingPathComponent:@"mic_ori.pcm"];
|
||||
return directory;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AudioRecorder.h
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define RECORDER_NOTIFICATION_CALLBACK_NAME @"recorderNotificationCallBackName"
|
||||
#define kNumberAudioQueueBuffers 3 //缓冲区设定3个
|
||||
#define kDefaultSampleRate 16000 //采样率
|
||||
#define kBufferByteSize (kDefaultSampleRate/100*2)//960
|
||||
#define kDataSize 2048
|
||||
|
||||
typedef void (^AudioRecorder_inputBlock)(NSData *speechData);
|
||||
|
||||
@interface AudioRecorder : NSObject
|
||||
{
|
||||
AudioQueueRef _audioQueue;
|
||||
AudioStreamBasicDescription audioFormat;
|
||||
AudioQueueBufferRef _audioBuffers[kNumberAudioQueueBuffers];
|
||||
char data[kDataSize];
|
||||
int offset;
|
||||
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) BOOL isRecording;
|
||||
@property (atomic, assign) NSUInteger sampleRate;
|
||||
|
||||
@property (nonatomic,copy) AudioRecorder_inputBlock inputBlock;
|
||||
|
||||
-(void)start;
|
||||
|
||||
-(void)stop;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// AudioRecorder.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import "AudioRecorder.h"
|
||||
|
||||
|
||||
|
||||
@implementation AudioRecorder
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
self.sampleRate = kDefaultSampleRate;
|
||||
|
||||
//设置录音 初始化录音参数
|
||||
[self setupAudioFormat:kAudioFormatLinearPCM SampleRate:(int)self.sampleRate];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
//设置录音 初始化录音参数
|
||||
- (void)setupAudioFormat:(UInt32)inFormatID SampleRate:(int)sampeleRate
|
||||
{
|
||||
|
||||
memset(&audioFormat, 0, sizeof(audioFormat));
|
||||
|
||||
audioFormat.mSampleRate = sampeleRate;//采样率
|
||||
audioFormat.mChannelsPerFrame = 1;//单声道
|
||||
|
||||
audioFormat.mFormatID = inFormatID;//采集pcm 格式
|
||||
audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
|
||||
audioFormat.mBitsPerChannel = 16;//每个通道采集2个byte
|
||||
audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame = (audioFormat.mBitsPerChannel / 8) * audioFormat.mChannelsPerFrame;
|
||||
audioFormat.mFramesPerPacket = 1;
|
||||
|
||||
}
|
||||
|
||||
//回调函数 不断采集声音。
|
||||
void inputHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
|
||||
{
|
||||
|
||||
AudioRecorder *recorder = (__bridge AudioRecorder*)inUserData;
|
||||
|
||||
int k = 0;
|
||||
if (inBuffer->mAudioDataByteSize > 0) {
|
||||
// NSLog(@"size: %d", inBuffer->mAudioDataByteSize);
|
||||
memcpy(recorder->data+recorder->offset,inBuffer->mAudioData,inBuffer->mAudioDataByteSize);//通过recorder->offset 偏移把语音数据存入recorder->data
|
||||
|
||||
recorder->offset+=inBuffer->mAudioDataByteSize;//记录语音数据的大小
|
||||
|
||||
k = recorder->offset/kBufferByteSize;//计算语音数据有多个960个字节语音
|
||||
|
||||
for(int i = 0; i <k; i++)
|
||||
{
|
||||
|
||||
NSData *SpeechData = [[NSData alloc]initWithBytes:recorder->data+i*kBufferByteSize length:kBufferByteSize];//把recorder->data 数据以960个字节分切放入 传出的数组中。
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RECORDER_NOTIFICATION_CALLBACK_NAME object:SpeechData];
|
||||
if (recorder.inputBlock) {
|
||||
recorder.inputBlock(SpeechData);
|
||||
}
|
||||
}
|
||||
|
||||
// NSLog(@"sampleRate: %lu", (unsigned long)recorder->_sampleRate);
|
||||
memcpy(recorder->data,recorder->data+k*kBufferByteSize,recorder->offset-(k*kBufferByteSize));//把剩下的语音数据放入原来的数组中
|
||||
|
||||
recorder->offset-=(k*kBufferByteSize);//计算剩下的语音数据大小
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (recorder.isRecording) {
|
||||
|
||||
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//开始录音
|
||||
-(void)start
|
||||
{
|
||||
NSError *error = nil;
|
||||
|
||||
//录音的设置和初始化
|
||||
BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
|
||||
if (!ret) {
|
||||
return;
|
||||
}
|
||||
|
||||
//启用audio session
|
||||
ret = [[AVAudioSession sharedInstance] setActive:YES error:&error];
|
||||
if (!ret)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//初始化缓冲语音数据数组
|
||||
memset(data,0,kDataSize); // 清空
|
||||
offset = 0;
|
||||
audioFormat.mSampleRate = self.sampleRate;
|
||||
//初始化音频输入队列
|
||||
AudioQueueNewInput(&audioFormat, inputHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);
|
||||
|
||||
int bufferByteSize = kBufferByteSize; //设定采集一帧960个字节
|
||||
|
||||
//创建缓冲器
|
||||
for (int i = 0; i < kNumberAudioQueueBuffers; ++i){
|
||||
AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]);
|
||||
AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);
|
||||
}
|
||||
|
||||
//开始录音
|
||||
AudioQueueStart(_audioQueue, NULL);
|
||||
self.isRecording = YES;
|
||||
}
|
||||
|
||||
//结束录音
|
||||
-(void)stop
|
||||
{
|
||||
if (self.isRecording) {
|
||||
self.isRecording = NO;
|
||||
AudioQueueStop(_audioQueue, true);
|
||||
AudioQueueDispose(_audioQueue, true);
|
||||
[[AVAudioSession sharedInstance] setActive:NO error:nil];
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategorySoloAmbient error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="LNx-YM-oyd">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="LKx-p6-aa7">
|
||||
<objects>
|
||||
<viewController id="LNx-YM-oyd" customClass="ViewController" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="f9i-bJ-nwR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="a90-mD-VNo">
|
||||
<rect key="frame" x="20" y="79" width="353" height="549"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="22"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="LNx-YM-oyd" id="y7d-6A-9hP"/>
|
||||
</connections>
|
||||
</textView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eHG-b8-QrU">
|
||||
<rect key="frame" x="136.66666666666666" y="648" width="119.99999999999997" height="120"/>
|
||||
<color key="backgroundColor" systemColor="systemYellowColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="120" id="bNz-tU-Gr3"/>
|
||||
<constraint firstAttribute="height" constant="120" id="juZ-Sc-VF7"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="22"/>
|
||||
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" title="开始说话">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<state key="selected" title="停止说话"/>
|
||||
<connections>
|
||||
<action selector="startButtonClicked:" destination="LNx-YM-oyd" eventType="touchUpInside" id="09b-nJ-q4t"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="9Vr-PE-hwK"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="a90-mD-VNo" firstAttribute="top" secondItem="9Vr-PE-hwK" secondAttribute="top" constant="20" id="6EP-DI-QiI"/>
|
||||
<constraint firstItem="a90-mD-VNo" firstAttribute="leading" secondItem="9Vr-PE-hwK" secondAttribute="leading" constant="20" id="MyE-dD-mYG"/>
|
||||
<constraint firstItem="9Vr-PE-hwK" firstAttribute="bottom" secondItem="eHG-b8-QrU" secondAttribute="bottom" constant="50" id="gSD-5y-y1j"/>
|
||||
<constraint firstItem="9Vr-PE-hwK" firstAttribute="trailing" secondItem="a90-mD-VNo" secondAttribute="trailing" constant="20" id="psn-ga-kSC"/>
|
||||
<constraint firstItem="eHG-b8-QrU" firstAttribute="top" secondItem="a90-mD-VNo" secondAttribute="bottom" constant="20" id="rvq-Xo-Qv7"/>
|
||||
<constraint firstItem="eHG-b8-QrU" firstAttribute="centerX" secondItem="f9i-bJ-nwR" secondAttribute="centerX" id="vPV-bu-rny"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="resultView" destination="a90-mD-VNo" id="ghN-4g-qXC"/>
|
||||
<outlet property="startButton" destination="eHG-b8-QrU" id="SLc-gU-csB"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="vs1-EQ-4px" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="139.69465648854961" y="3.5211267605633805"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemYellowColor">
|
||||
<color red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// SceneDelegate.h
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow * window;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// SceneDelegate.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import "SceneDelegate.h"
|
||||
|
||||
@interface SceneDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation SceneDelegate
|
||||
|
||||
|
||||
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidDisconnect:(UIScene *)scene {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidBecomeActive:(UIScene *)scene {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneWillResignActive:(UIScene *)scene {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneWillEnterForeground:(UIScene *)scene {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidEnterBackground:(UIScene *)scene {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// ViewController.h
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface ViewController : UIViewController
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
//
|
||||
// ViewController.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
|
||||
#include "AudioCapture.h"
|
||||
|
||||
@interface ViewController ()<UITextViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) AudioCapture *audioCapture;
|
||||
@property (nonatomic, copy) NSString *pre_partial_text;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UITextView *resultView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *startButton;
|
||||
|
||||
@property (strong, nonatomic) UIView *coverView;
|
||||
@property (strong, nonatomic) UIButton *launchOnnxButton;
|
||||
@property (strong, nonatomic) UIButton *launchButton;
|
||||
@property (strong, nonatomic) UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
{
|
||||
bool is_mic_;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
self.startButton.layer.cornerRadius = 60;
|
||||
self.startButton.layer.masksToBounds = YES;
|
||||
self.startButton.selected = NO;
|
||||
self.resultView.editable = NO;
|
||||
|
||||
is_mic_ = true;
|
||||
|
||||
[self.view addSubview:self.coverView];
|
||||
[self initEngine:YES];
|
||||
}
|
||||
|
||||
- (UIView *)coverView {
|
||||
if (!_coverView) {
|
||||
NSInteger screenWidth = self.view.bounds.size.width;
|
||||
NSInteger screenHeight = self.view.bounds.size.height;
|
||||
_coverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenWidth, screenHeight)];
|
||||
_coverView.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
NSInteger x = 30;
|
||||
NSInteger heigh = 60;
|
||||
NSInteger y = screenHeight/2-120;
|
||||
NSInteger width = (screenWidth-30*2);
|
||||
|
||||
UILabel *tip = [[UILabel alloc] initWithFrame:CGRectMake(x, y, width, heigh)];
|
||||
tip.backgroundColor = [UIColor clearColor];
|
||||
tip.textColor = [UIColor grayColor];
|
||||
tip.font = [UIFont systemFontOfSize:20];
|
||||
tip.textAlignment = NSTextAlignmentCenter;
|
||||
tip.text = @"正在初始化模型...";
|
||||
[_coverView addSubview:tip];
|
||||
_coverView.alpha = 0.8;
|
||||
}
|
||||
|
||||
return _coverView;
|
||||
}
|
||||
|
||||
- (void)initEngine:(BOOL)onnxModel {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSInteger screenWidth = self.view.bounds.size.width;
|
||||
NSInteger screenHeight = self.view.bounds.size.height;
|
||||
NSInteger activityIndicatorWith = screenWidth/2;
|
||||
NSInteger activityIndicatorHeight = activityIndicatorWith;
|
||||
// UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake((screenWidth-activityIndicatorWith)/2, (screenHeight-activityIndicatorHeight)/2, activityIndicatorWith, activityIndicatorHeight)];
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
|
||||
self.activityIndicator.frame = CGRectMake((screenWidth-activityIndicatorWith)/2, (screenHeight-activityIndicatorHeight)/2, activityIndicatorWith, activityIndicatorHeight);
|
||||
[self.view addSubview:self.activityIndicator];
|
||||
[self.activityIndicator startAnimating];
|
||||
|
||||
|
||||
});
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self initASR:onnxModel];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)initASR:(BOOL)onnxModel {
|
||||
if (is_mic_) {
|
||||
self.pre_partial_text = @"";
|
||||
self.audioCapture = [[AudioCapture alloc] initWithOnnxModel:onnxModel];
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
self.audioCapture.resultBlock = ^(NSString *result) {
|
||||
if ([result length] == 0) {
|
||||
return;
|
||||
}
|
||||
NSLog(@"%@", result);
|
||||
NSString *text = nil;
|
||||
text = [NSString stringWithFormat:@"%@%@", weakSelf.pre_partial_text, result];
|
||||
weakSelf.pre_partial_text = text;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
weakSelf.resultView.text = text;
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.activityIndicator stopAnimating];
|
||||
[self.activityIndicator removeFromSuperview];
|
||||
[self.coverView removeFromSuperview];
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)startButtonClicked:(id)sender {
|
||||
self.startButton.enabled = NO;
|
||||
if (!self.startButton.selected) {
|
||||
if (is_mic_) [self.audioCapture startRecorder];
|
||||
} else {
|
||||
if (is_mic_) [self.audioCapture stopRecorder];
|
||||
}
|
||||
|
||||
self.startButton.selected = !self.startButton.selected;
|
||||
self.startButton.enabled = YES;
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView {
|
||||
[textView scrollRangeToVisible:NSMakeRange([textView.text length] - 1, 1)];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// main.m
|
||||
// paraformer_online
|
||||
//
|
||||
// Created by 邱威 on 2023/6/6.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
NSString * appDelegateClassName;
|
||||
@autoreleasepool {
|
||||
// Setup code that might create autoreleased objects goes here.
|
||||
appDelegateClassName = NSStringFromClass([AppDelegate class]);
|
||||
}
|
||||
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
|
||||
}
|
||||
Reference in New Issue
Block a user