chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:10 +08:00
commit c397331b1e
3684 changed files with 990993 additions and 0 deletions
+18
View File
@@ -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支持
* 加入流式标点
+7
View File
@@ -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
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "A434E096-73C6-4C03-86F5-FAA8A72D79B0"
type = "1"
version = "2.0">
</Bucket>
@@ -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
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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);
}