├── Demos ├── AndroidSpeech │ ├── AndroidSpeech.Client.pas │ ├── AndroidSpeech.Grpc.pas │ ├── AndroidSpeech.Impl.pas │ ├── AndroidSpeech.dpr │ ├── AndroidSpeech.dproj │ ├── BroadcastReceiver.pas │ ├── Google.API.pas │ ├── README.md │ ├── Speech.General.pas │ ├── Speech.Google.pas │ ├── SpeechServer.dpr │ ├── SpeechServer.dproj │ ├── Superobject.pas │ ├── dAndroidRecorder.dfm │ ├── dAndroidRecorder.pas │ ├── dAudioProcessing.dfm │ ├── dAudioProcessing.pas │ ├── dBaseRecorder.dfm │ ├── dBaseRecorder.pas │ ├── dStyle.dfm │ ├── dStyle.pas │ ├── dTethering.dfm │ ├── dTethering.pas │ ├── fAndroidRecord.fmx │ ├── fAndroidRecord.pas │ └── google.cloud.speech.v1.Speech.pas ├── GoogleSpeechPlain │ ├── Google.API.pas │ ├── Speech.General.pas │ ├── Speech.Google.pas │ ├── Superobject.pas │ ├── TranslateFile.dpr │ ├── Unit5.dfm │ ├── Unit5.pas │ ├── Win32 │ │ └── Debug │ │ │ ├── audio.raw │ │ │ ├── audio.wav │ │ │ ├── libeay32.dll │ │ │ ├── nghttp2.dll │ │ │ └── ssleay32.dll │ └── google.cloud.speech.v1.Speech.pas ├── README.md └── SimpleDemo │ ├── delphi │ ├── AndroidGrpc.dpr │ ├── AndroidGrpc.dproj │ ├── LinuxClient.dpr │ ├── LinuxClient.dproj │ ├── Mobile.fmx │ ├── Mobile.pas │ ├── Superobject.pas │ ├── TestService.client.pas │ ├── TestService.grpc.pas │ ├── TestService.impl.pas │ ├── TestService.proto.pas │ ├── Win32 │ │ └── Debug │ │ │ ├── libeay32.dll │ │ │ ├── nghttp2.dll │ │ │ └── ssleay32.dll │ ├── client.dpr │ ├── client.dproj │ ├── clients_and_server.groupproj │ ├── fClient.fmx │ ├── fClient.pas │ ├── server.dpr │ └── server.dproj │ ├── golang │ ├── client │ │ ├── client.go │ │ └── test_service_client.go │ ├── server │ │ ├── server.go │ │ └── test_service_impl.go │ └── testservice │ │ ├── generate.go │ │ └── test_service.pb.go │ └── proto │ └── test_service.proto ├── GrijjyFoundation ├── Grijjy.BinaryCoding.pas ├── Grijjy.Bson.IO.pas ├── Grijjy.Bson.pas ├── Grijjy.Collections.pas ├── Grijjy.DateUtils.pas ├── Grijjy.Http.pas ├── Grijjy.JWT.pas ├── Grijjy.MemoryPool.pas ├── Grijjy.OpenSSL.API.pas ├── Grijjy.OpenSSL.pas ├── Grijjy.ProtocolBuffers.pas ├── Grijjy.SocketPool.Win.pas ├── Grijjy.SysUtils.pas ├── Grijjy.Winsock2.pas └── Grijjy.inc ├── LICENSE ├── README.md ├── Test ├── Test.gRPC.pas ├── gRPCtest.dpr └── gRPCtest.dproj ├── gRPC ├── Grijjy.Http2.pas ├── Grijjy.SocketPool.Dummy.pas ├── Ultraware.Grpc.pas └── Ultraware.Grpc.ws.pas ├── ngHttp2 ├── HTTP.md ├── LINUX.md ├── License.txt ├── Nghttp2.pas ├── README.md ├── libeay32.dll ├── nghttp2.dll └── ssleay32.dll └── sgcWebSockets └── README.md /Demos/AndroidSpeech/AndroidSpeech.Client.pas: -------------------------------------------------------------------------------- 1 | unit AndroidSpeech.Client; 2 | 3 | interface 4 | 5 | uses 6 | Ultraware.Grpc, 7 | AndroidSpeech.grpc; 8 | 9 | type 10 | TTestService_Client = class(TGrpcClientHandler, ISpeechService_Client) 11 | protected 12 | {ISpeechService_Client} 13 | function UploadFile(const aFile: TFile): TText; 14 | function Ping(const aTime: TTime): TTime; 15 | function StreamFile(const aResponseCallback: TTextCallback): IStreamFile_Send; 16 | end; 17 | 18 | implementation 19 | 20 | uses 21 | System.SysUtils; 22 | 23 | { TTestService_Client } 24 | 25 | function TTestService_Client.Ping(const aTime: TTime): TTime; 26 | var 27 | recv: TBytes; 28 | begin 29 | if Client.DoRequest(aTime.Serialize(), C_TestService_Path + 'Ping', recv) then 30 | Result.Deserialize(recv); 31 | end; 32 | 33 | function TTestService_Client.StreamFile(const aResponseCallback: TTextCallback): IStreamFile_Send; 34 | var 35 | request: IGrpcStream; 36 | callback: TGrpcCallback; 37 | begin 38 | if Assigned(aResponseCallback) then 39 | callback := 40 | procedure(const aData: TBytes; aIsStreamClosed: Boolean) 41 | var 42 | t: TText; 43 | begin 44 | if aData <> nil then 45 | t.Deserialize(aData); 46 | aResponseCallback(t, aData <> nil, aIsStreamClosed); 47 | end 48 | else 49 | callback := nil; 50 | 51 | request := Client.DoRequest(nil, C_TestService_Path + 'StreamFile', callback); 52 | if request <> nil then 53 | Result := TStreamFile_Client_Send.Create(request); 54 | end; 55 | 56 | function TTestService_Client.UploadFile(const aFile: TFile): TText; 57 | var 58 | recv: TBytes; 59 | begin 60 | if Client.DoRequest(aFile.Serialize(), C_TestService_Path + 'UploadFile', recv) then 61 | Result.Deserialize(recv); 62 | end; 63 | 64 | end. 65 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/AndroidSpeech.Grpc.pas: -------------------------------------------------------------------------------- 1 | unit AndroidSpeech.Grpc; 2 | 3 | interface 4 | 5 | uses 6 | System.Types, System.SysUtils, Ultraware.Grpc, 7 | Grijjy.ProtocolBuffers; 8 | 9 | const 10 | C_TestService_Path = '/speechproject.SpeechService/'; 11 | 12 | type 13 | TFile = record 14 | public 15 | // [Serialize(1)] sec: UInt32; //note: int32 will give double results in golang? 16 | // [Serialize(2)] msg: string; 17 | // [Serialize(3)] msec: UInt32; //note: int32 will give double results in golang? 18 | [Serialize(1)] filename: string; 19 | [Serialize(2)] data: TBytes; 20 | [Serialize(3)] sampleRate: UInt32; 21 | public 22 | function Serialize: TBytes; 23 | procedure Deserialize(const aData: TBytes); 24 | // function ToString: string; 25 | end; 26 | 27 | TTime = record 28 | [Serialize(1)] hour: UInt32; 29 | [Serialize(2)] sec: UInt32; 30 | [Serialize(3)] msec: UInt32; 31 | public 32 | function Serialize: TBytes; 33 | procedure Deserialize(const aData: TBytes); 34 | end; 35 | 36 | TText = record 37 | [Serialize(1)] Text: string; 38 | public 39 | function Serialize: TBytes; 40 | procedure Deserialize(const aData: TBytes); 41 | end; 42 | 43 | TTextCallback = reference to procedure(const aText: TText; aHasData, aClosed: Boolean); 44 | 45 | IStreamFile_Send = interface(IGrpcStream) 46 | ['{9CA82679-0414-413C-B7A1-7C99C23CAC7C}'] 47 | procedure Send(const aFile: TFile); 48 | procedure CloseSend; 49 | end; 50 | 51 | IStreamFile_Result_Send = interface 52 | procedure Send(const aText: TText); 53 | procedure CloseSend; 54 | end; 55 | 56 | // Client 57 | ISpeechService_Client = interface 58 | ['{2332ACB3-8163-4CCE-A611-B5374C8860C3}'] 59 | function UploadFile(const aFile: TFile): TText; 60 | function Ping(const aTime: TTime): TTime; 61 | function StreamFile(const aResponseCallback: TTextCallback): IStreamFile_Send; 62 | end; 63 | 64 | TStreamFile_Client_Send = class(TGrpcStream, IStreamFile_Send) 65 | protected 66 | {IStreamFile_Send} 67 | procedure Send(const aFile: TFile); 68 | end; 69 | 70 | // Server 71 | IStreamFile_Server_Recv = interface(IGrpcMemStream) 72 | ['{2189D418-EE2B-4659-A283-01B589341911}'] 73 | function Recv(out aFile: TFile; aWaitTimeout: Integer): TGrpcWaitResult; 74 | end; 75 | 76 | ISpeechService_Server = interface 77 | ['{029D569D-274F-4667-AC19-ED8C3ACBCD33}'] 78 | function UploadFile(const aFile: TFile): TText; 79 | function Ping(const aTime: TTime): TTime; 80 | procedure StreamFile(const aInputStream: IStreamFile_Server_Recv; const aOutputStream: IStreamFile_Result_Send); 81 | end; 82 | 83 | TStreamFile_Server_Send = class(TBaseGrpcCallbackStream, IStreamFile_Result_Send) 84 | protected 85 | {IStreamFile_Result_Send} 86 | procedure Send(const aText: TText); 87 | procedure CloseSend; 88 | end; 89 | 90 | TStreamFile_Server_Recv = class(TBaseGrpcMemStream, IStreamFile_Server_Recv) 91 | protected 92 | {IStreamFile_Server_Recv} 93 | function Recv(out aFile: TFile; aWaitTimeout: Integer): TGrpcWaitResult; 94 | end; 95 | 96 | implementation 97 | 98 | { TFile } 99 | 100 | procedure TFile.Deserialize(const aData: TBytes); 101 | begin 102 | TgoProtocolBuffer.Deserialize(Self, aData); 103 | end; 104 | 105 | function TFile.Serialize: TBytes; 106 | begin 107 | Result := TgoProtocolBuffer.Serialize(Self); 108 | end; 109 | 110 | //function TFile.ToString: string; 111 | //begin 112 | // Result := Format('%ds, %dms, %s', [sec, msec, msg]); 113 | //end; 114 | 115 | { TText } 116 | 117 | procedure TText.Deserialize(const aData: TBytes); 118 | begin 119 | TgoProtocolBuffer.Deserialize(Self, aData); 120 | end; 121 | 122 | function TText.Serialize: TBytes; 123 | begin 124 | Result := TgoProtocolBuffer.Serialize(Self); 125 | end; 126 | 127 | { TStreamFile_Client_Send } 128 | 129 | procedure TStreamFile_Client_Send.Send(const aFile: TFile); 130 | begin 131 | Stream.SendData(aFile.Serialize); 132 | end; 133 | 134 | { TStreamFile_Server_Send } 135 | 136 | procedure TStreamFile_Server_Send.CloseSend; 137 | begin 138 | inherited Close; 139 | end; 140 | 141 | procedure TStreamFile_Server_Send.Send(const aText: TText); 142 | begin 143 | inherited SendData(aText.Serialize); 144 | end; 145 | 146 | { TStreamFile_Server_Recv } 147 | 148 | function TStreamFile_Server_Recv.Recv(out aFile: TFile; aWaitTimeout: Integer): TGrpcWaitResult; 149 | var b: TBytes; 150 | begin 151 | Result := inherited Recv(b, aWaitTimeout); 152 | if Result = wrData then 153 | aFile.Deserialize(b); 154 | end; 155 | 156 | { TTime } 157 | 158 | procedure TTime.Deserialize(const aData: TBytes); 159 | begin 160 | TgoProtocolBuffer.Deserialize(Self, aData); 161 | end; 162 | 163 | function TTime.Serialize: TBytes; 164 | begin 165 | Result := TgoProtocolBuffer.Serialize(Self); 166 | end; 167 | 168 | end. 169 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/AndroidSpeech.Impl.pas: -------------------------------------------------------------------------------- 1 | unit AndroidSpeech.Impl; 2 | 3 | interface 4 | 5 | uses 6 | Sysutils, System.IOUtils, 7 | Ultraware.Grpc, 8 | AndroidSpeech.grpc, Grijjy.Http2, Speech.General; 9 | 10 | type 11 | TSpeechService_Impl = class(TBaseGrpcImplementation, ISpeechService_Server) 12 | protected 13 | class var FSpeechAPI: ISpeechAPI; 14 | class function GetSpeechAPI: ISpeechAPI; static; 15 | protected 16 | {ISpeechService_Client} 17 | function UploadFile(const aFile: TFile): TText; 18 | function Ping(const aTime: TTime): TTime; 19 | procedure StreamFile(const aInputStream: IStreamFile_Server_Recv; const aOutputStream: IStreamFile_Result_Send); 20 | public 21 | class function HandleGRPC(const aPath: string; const aIn: TBytes; const aOut: TGrpcCallback; const aErrorCallback: TGrpcErrorCallback; out aCallThread: IRequestThread): Boolean; override; 22 | class property SpeechAPI: ISpeechAPI read GetSpeechAPI write FSpeechAPI; 23 | end; 24 | 25 | implementation 26 | 27 | uses 28 | System.Types, System.Math, Classes, 29 | //Wave, 30 | System.Diagnostics; 31 | 32 | { TSpeechService_Impl } 33 | 34 | class function TSpeechService_Impl.HandleGRPC(const aPath: string; const aIn: TBytes; const aOut: TGrpcCallback; const aErrorCallback: TGrpcErrorCallback; 35 | out aCallThread: IRequestThread): Boolean; 36 | var 37 | f: TFile; 38 | t: TText; 39 | t2: TTime; 40 | impl: ISpeechService_Server; 41 | StreamFile_Server_Recv: IStreamFile_Server_Recv; 42 | StreamFile_Send: IStreamFile_Result_Send; 43 | begin 44 | Result := False; 45 | 46 | if aPath = C_TestService_Path + 'UploadFile' then 47 | begin 48 | Result := True; 49 | f.Deserialize(aIn); 50 | 51 | impl := TSpeechService_Impl.Create; 52 | t := impl.UploadFile(f); 53 | aOut(t.Serialize, True); 54 | end 55 | else if aPath = C_TestService_Path + 'Ping' then 56 | begin 57 | Result := True; 58 | t2.Deserialize(aIn); 59 | impl := TSpeechService_Impl.Create; 60 | t2 := impl.Ping(t2); 61 | aOut(t2.Serialize, True); 62 | end 63 | else if aPath = C_TestService_Path + 'StreamFile' then 64 | begin 65 | Result := True; 66 | StreamFile_Server_Recv := TStreamFile_Server_Recv.Create; 67 | StreamFile_Server_Recv.AddReceivedData(aIn); 68 | StreamFile_Send := TStreamFile_Server_Send.Create(aOut, aErrorCallback); 69 | aCallThread := TGrpcStreamCallThread.Create(StreamFile_Server_Recv, 70 | procedure 71 | var impl: ISpeechService_Server; 72 | begin 73 | impl := TSpeechService_Impl.Create; 74 | impl.StreamFile(StreamFile_Server_Recv, StreamFile_Send); 75 | end); 76 | end; 77 | end; 78 | 79 | function TSpeechService_Impl.Ping(const aTime: TTime): TTime; 80 | var watch: TStopwatch; 81 | begin 82 | watch := TStopwatch.Create; 83 | watch.Start; 84 | Sleep(aTime.msec); 85 | watch.Stop; 86 | Result.hour := 0; 87 | Result.sec := 0; 88 | Result.msec := watch.ElapsedMilliseconds; 89 | end; 90 | 91 | function ExtractFileName(const aFile: string): string; 92 | var parts: TArray; 93 | begin 94 | parts := aFile.Split(['/']); 95 | Result := parts[Length(parts)-1]; 96 | end; 97 | 98 | class function TSpeechService_Impl.GetSpeechAPI: ISpeechAPI; 99 | begin 100 | Assert(FSpeechAPI <> nil); 101 | Result := FSpeechAPI; 102 | end; 103 | 104 | procedure TSpeechService_Impl.StreamFile(const aInputStream: IStreamFile_Server_Recv; const aOutputStream: IStreamFile_Result_Send); 105 | 106 | function _WaitForData(out aFile: TFile): Boolean; 107 | begin 108 | Result := False; 109 | repeat 110 | case aInputStream.Recv(aFile, 10) of 111 | wrData: Exit(True); 112 | TGrpcWaitResult.wrTimeout, wrNoData: Continue; 113 | wrClosed: Exit; 114 | end; 115 | until False; 116 | end; 117 | 118 | var 119 | f: TFile; 120 | t: TText; 121 | speechSession: ISpeechSession; 122 | wait: TBoolean; 123 | strm: TMemoryStream; 124 | // w: TWave; 125 | b: TBytes; 126 | begin 127 | //wait for first data 128 | if not _WaitForData(f) then 129 | Exit; 130 | 131 | wait := TBoolean.Create; 132 | strm := TMemoryStream.Create; 133 | try 134 | wait.Value := False; 135 | if f.sampleRate <= 0 then 136 | f.sampleRate := 16000; 137 | 138 | speechSession := SpeechAPI.CreateSpeechSession( 139 | procedure(const aResult: string; aEndOfUtterance: boolean) 140 | var t: TText; 141 | begin 142 | t.Text := aResult; 143 | aOutputStream.Send(t); 144 | 145 | if aEndOfUtterance then 146 | begin 147 | aOutputStream.CloseSend; 148 | wait.Value := True; 149 | end; 150 | end, 151 | f.SampleRate, True{interim result}); 152 | 153 | repeat 154 | strm.WriteData(f.data, Length(f.data)); 155 | speechSession.SendRawAudio(f.data); 156 | if not _WaitForData(f) then 157 | break; 158 | until False; 159 | 160 | speechSession.CloseSend; 161 | // {$MESSAGE WARN 'TODO: disable raw audio logging?'} 162 | // strm.Position := 0; 163 | // w := TWave.Create(1, f.sampleRate); 164 | // SetLength(b, strm.Size); 165 | // strm.Read(b, strm.Size); 166 | // w.SaveWaveFile(FormatDateTime('yyyymmdd_hhmmss', now) + '.wav', b); 167 | // strm.SaveToFile(FormatDateTime('yyyymmdd_hhmmss', now) + '.raw'); 168 | 169 | wait.Wait(5*1000, True); 170 | finally 171 | aOutputStream.CloseSend; 172 | wait.Free; 173 | strm.Free; 174 | end; 175 | end; 176 | 177 | function TSpeechService_Impl.UploadFile(const aFile: TFile): TText; 178 | var 179 | speechSession: ISpeechSession; 180 | wait: TBoolean; 181 | sResult: string; 182 | begin 183 | Writeln(aFile.filename); 184 | System.IOUtils.TFile.WriteAllBytes( ExtractFileName(aFile.filename), aFile.data); 185 | 186 | wait := TBoolean.Create; 187 | try 188 | wait.Value := False; 189 | speechSession := SpeechAPI.CreateSpeechSession( 190 | procedure(const aResult: string; aEndOfUtterance: boolean) 191 | begin 192 | wait.Value := True; 193 | sResult := aResult; 194 | end, 195 | IfThen(aFile.SampleRate <= 0, 16000, aFile.SampleRate), False {single result}); 196 | speechSession.SendRawAudio(aFile.data); 197 | speechSession.CloseSend; 198 | wait.Wait(15 * 1000, True); 199 | finally 200 | speechSession := nil; 201 | wait.Free; 202 | end; 203 | 204 | Result.Text := sResult; 205 | //Writeln(sResult); 206 | end; 207 | 208 | end. 209 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/AndroidSpeech.dpr: -------------------------------------------------------------------------------- 1 | program AndroidSpeech; 2 | 3 | uses 4 | System.StartUpCopy, 5 | FMX.Forms, 6 | fAndroidRecord in 'fAndroidRecord.pas' {Form4}, 7 | AndroidSpeech.Client in 'AndroidSpeech.Client.pas', 8 | AndroidSpeech.Grpc in 'AndroidSpeech.Grpc.pas', 9 | sgcWebSocket in '..\..\..\Componenten\sgcWebSockets\Source\sgcWebSocket.pas', 10 | dStyle in 'dStyle.pas' {dmStyle: TDataModule}, 11 | dBaseRecorder in 'dBaseRecorder.pas' {dmBaseRecorder: TDataModule}, 12 | dTethering in 'dTethering.pas' {dmTethering: TDataModule}; 13 | // Wave in 'Wave.pas'; 14 | 15 | {$R *.res} 16 | 17 | begin 18 | Application.Initialize; 19 | Application.CreateForm(TdmStyle, dmStyle); 20 | dmTethering := TdmTethering.Create(Application); 21 | dmTethering.EnableClient; 22 | 23 | Application.CreateForm(TForm4, Form4); 24 | Application.Run; 25 | end. 26 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/BroadcastReceiver.pas: -------------------------------------------------------------------------------- 1 | unit BroadcastReceiver; 2 | 3 | interface 4 | Uses 5 | System.Classes 6 | ,System.SysUtils 7 | {$IFDEF ANDROID} 8 | ,Androidapi.JNI.Embarcadero 9 | ,Androidapi.JNI.GraphicsContentViewText 10 | ,Androidapi.helpers 11 | ,Androidapi.JNIBridge 12 | ,FMX.Helpers.Android 13 | {$ENDIF} 14 | ; 15 | 16 | type 17 | 18 | {$IFNDEF ANDROID} 19 | JIntent = class end; 20 | JContext = class end; 21 | {$ENDIF} 22 | 23 | TBroadcastReceiver= class; 24 | TOnReceive = procedure (Context: JContext; Intent: JIntent)of object; 25 | 26 | {$IFDEF ANDROID} 27 | TListener = class(TJavaLocal, JFMXBroadcastReceiverListener) 28 | private 29 | FOwner: TBroadcastReceiver; 30 | public 31 | constructor Create(AOwner: TBroadcastReceiver); 32 | procedure onReceive(context: JContext; intent: JIntent); cdecl; 33 | end; 34 | {$ENDIF} 35 | 36 | 37 | TBroadcastReceiver = class(TComponent) 38 | private 39 | {$IFDEF ANDROID} 40 | FReceiver: JBroadcastReceiver; 41 | FListener : TListener; 42 | {$ENDIF} 43 | FOnReceive: TOnReceive; 44 | FItems: TStringList; 45 | function GetItem(const Index: Integer): String; 46 | 47 | public 48 | constructor Create(AOwner: TComponent); override; 49 | destructor Destroy; override; 50 | procedure SendBroadcast(Value: String); 51 | procedure Add(Value: String); 52 | procedure Delete(Index: Integer); 53 | procedure Clear; 54 | function Remove(const Value: String): Integer; 55 | function First: String; 56 | function Last: String; 57 | function HasPermission(const Permission: string): Boolean; 58 | procedure RegisterReceive; 59 | property Item[const Index: Integer]: String read GetItem; default; 60 | property Items: TStringList read FItems write FItems; 61 | published 62 | property onReceive: TOnReceive read FOnReceive write FOnReceive; 63 | end; 64 | 65 | procedure Register; 66 | 67 | implementation 68 | 69 | uses 70 | Androidapi.JNI.App, Androidapi.JNI.JavaTypes; 71 | 72 | procedure Register; 73 | begin 74 | RegisterComponents('Android', [TBroadcastReceiver]); 75 | end; 76 | 77 | { TBroadcastReceiver } 78 | 79 | procedure TBroadcastReceiver.Add(Value: String); 80 | {$IFDEF ANDROID} 81 | var 82 | Filter: JIntentFilter; 83 | {$ENDIF} 84 | begin 85 | {$IFDEF ANDROID} 86 | if (FListener = nil) or (FReceiver = nil) then 87 | begin 88 | Raise Exception.Create('First use RegisterReceive!'); 89 | Exit; 90 | end; 91 | {$ENDIF} 92 | 93 | if FItems <> nil then 94 | if FItems.IndexOf(Value) = -1 then 95 | begin 96 | {$IFDEF ANDROID} 97 | filter := TJIntentFilter.Create; 98 | filter.addAction(StringToJString(Value)); 99 | TAndroidHelper.Context.registerReceiver(FReceiver,filter); 100 | {$ENDIF} 101 | FItems.Add(Value); 102 | end; 103 | end; 104 | 105 | procedure TBroadcastReceiver.Clear; 106 | begin 107 | FItems.Clear; 108 | end; 109 | 110 | constructor TBroadcastReceiver.Create(AOwner: TComponent); 111 | begin 112 | inherited; 113 | FItems := TStringList.Create; 114 | end; 115 | 116 | procedure TBroadcastReceiver.Delete(Index: Integer); 117 | begin 118 | if FItems <> nil then 119 | begin 120 | FItems.Delete(Index); 121 | {$IFDEF ANDROID} 122 | TAndroidHelper.Context.UnregisterReceiver(FReceiver); 123 | RegisterReceive; 124 | {$ENDIF} 125 | end; 126 | end; 127 | 128 | destructor TBroadcastReceiver.Destroy; 129 | begin 130 | FItems.Free; 131 | {$IFDEF ANDROID} 132 | if FReceiver <> nil then 133 | TAndroidHelper.Context.UnregisterReceiver(FReceiver); 134 | {$ENDIF} 135 | inherited; 136 | end; 137 | 138 | function TBroadcastReceiver.First: String; 139 | begin 140 | Result := FItems[0]; 141 | end; 142 | 143 | function TBroadcastReceiver.GetItem(const Index: Integer): String; 144 | begin 145 | Result := FItems[Index]; 146 | end; 147 | 148 | function TBroadcastReceiver.HasPermission(const Permission: string): Boolean; 149 | {$IFDEF ANDROID} 150 | begin 151 | //Permissions listed at http://d.android.com/reference/android/Manifest.permission.html 152 | Result := TAndroidHelper.Context.checkCallingOrSelfPermission( 153 | StringToJString(Permission)) = 154 | TJPackageManager.JavaClass.PERMISSION_GRANTED 155 | {$ELSE} 156 | begin 157 | Result := False; 158 | {$ENDIF} 159 | end; 160 | 161 | function TBroadcastReceiver.Last: String; 162 | begin 163 | Result := FItems[FItems.Count]; 164 | end; 165 | 166 | procedure TBroadcastReceiver.RegisterReceive; 167 | {$IFDEF ANDROID} 168 | var 169 | I: Integer; 170 | begin 171 | if FListener = nil then 172 | FListener := TListener.Create(Self); 173 | if FReceiver = nil then 174 | FReceiver := TJFMXBroadcastReceiver.JavaClass.init(FListener); 175 | if FItems <> nil then 176 | if FItems.Count > 0 then 177 | for I := 0 to FItems.Count -1 do 178 | Add(FItems[I]); 179 | {$ELSE} 180 | begin 181 | {$ENDIF} 182 | end; 183 | 184 | function TBroadcastReceiver.Remove(const Value: String): Integer; 185 | begin 186 | Result := FItems.IndexOf(Value); 187 | if Result > -1 then 188 | FItems.Delete(Result); 189 | end; 190 | 191 | procedure TBroadcastReceiver.SendBroadcast(Value: String); 192 | {$IFDEF ANDROID} 193 | var 194 | Inx: JIntent; 195 | begin 196 | Inx := TJIntent.Create; 197 | Inx.setAction(StringToJString(Value)); 198 | TAndroidHelper.Context.sendBroadcast(Inx); 199 | {$ELSE} 200 | begin 201 | {$ENDIF} 202 | end; 203 | 204 | {$IFDEF ANDROID} 205 | constructor TListener.Create(AOwner: TBroadcastReceiver); 206 | begin 207 | inherited Create; 208 | FOwner := AOwner; 209 | end; 210 | 211 | procedure TListener.onReceive(context: JContext; intent: JIntent); 212 | begin 213 | if Assigned(FOwner.onReceive) then 214 | FOwner.onReceive(Context, Intent); 215 | end; 216 | 217 | {$ENDIF} 218 | 219 | end. 220 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/Google.API.pas: -------------------------------------------------------------------------------- 1 | unit Google.API; 2 | 3 | { Google Cloud Platform APIs for Google Compute Engine instances } 4 | 5 | interface 6 | 7 | uses 8 | Grijjy.Http; 9 | 10 | type 11 | TgoGoogle = class 12 | private 13 | FAccessToken: String; 14 | FLastToken: TDateTime; 15 | FTokenExpiresInSec: Int64; 16 | private 17 | { Google Cloud Account } 18 | FOAuthScope: String; 19 | FServiceAccount: String; 20 | FPrivateKey: String; 21 | protected 22 | function ClaimSet(const AScope: String; const ADateTime: TDateTime): String; 23 | function GetAccessToken: String; 24 | procedure SetOAuthScope(const AValue: String); 25 | procedure SetPrivateKey(const AValue: String); 26 | procedure SetServiceAccount(const AValue: String); 27 | public 28 | constructor Create; 29 | destructor Destroy; override; 30 | public 31 | { Post a request to the Google Cloud APIs } 32 | function Post(const AUrl, ARequest: String; out AResponseHeaders, AResponseContent: String; 33 | const ARecvTimeout: Integer = TIMEOUT_RECV): Integer; 34 | public 35 | { Returns the current access token } 36 | property AccessToken: String read GetAccessToken; 37 | 38 | { Get or set the current engine scope } 39 | property OAuthScope: String read FOAuthScope write SetOAuthScope; 40 | 41 | { Get or set the current service account } 42 | property ServiceAccount: String read FServiceAccount write SetServiceAccount; 43 | 44 | { Get or set the current private key } 45 | property PrivateKey: String read FPrivateKey write SetPrivateKey; 46 | end; 47 | 48 | implementation 49 | 50 | uses 51 | System.SysUtils, 52 | System.DateUtils, 53 | System.NetEncoding, 54 | Grijjy.JWT, 55 | Grijjy.Bson; 56 | 57 | { TGoogle } 58 | 59 | constructor TgoGoogle.Create; 60 | begin 61 | FLastToken := -1; 62 | FTokenExpiresInSec := 0; 63 | end; 64 | 65 | destructor TgoGoogle.Destroy; 66 | begin 67 | inherited; 68 | end; 69 | 70 | procedure TgoGoogle.SetOAuthScope(const AValue: String); 71 | begin 72 | FOAuthScope := AValue; 73 | FLastToken := -1; { create new access token on next request } 74 | end; 75 | 76 | procedure TgoGoogle.SetServiceAccount(const AValue: String); 77 | begin 78 | FServiceAccount := AValue; 79 | FLastToken := -1; { create new access token on next request } 80 | end; 81 | 82 | procedure TgoGoogle.SetPrivateKey(const AValue: String); 83 | begin 84 | FPrivateKey := AValue; 85 | FLastToken := -1; { create new access token on next request } 86 | end; 87 | 88 | { See: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests for more details } 89 | function TgoGoogle.ClaimSet(const AScope: String; const ADateTime: TDateTime): String; 90 | var 91 | Doc: TgoBsonDocument; 92 | begin 93 | Doc := TgoBsonDocument.Create; 94 | Doc['iss'] := FServiceAccount; 95 | Doc['scope'] := AScope; 96 | Doc['aud'] := 'https://www.googleapis.com/oauth2/v4/token'; 97 | Doc['exp'] := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(IncSecond(ADateTime, 3600))); { expires in one hour } 98 | Doc['iat'] := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(ADateTime)); 99 | Result := Doc.ToJson; 100 | end; 101 | 102 | function TgoGoogle.GetAccessToken: String; 103 | var 104 | HTTP: TgoHTTPClient; 105 | Response: String; 106 | Doc: TgoBsonDocument; 107 | JWT: String; 108 | begin 109 | if (FLastToken = -1) or (Now >= IncSecond(FLastToken, FTokenExpiresInSec - 5)) then { padding of 5 seconds } 110 | begin 111 | { new token } 112 | FLastToken := Now; 113 | FAccessToken := ''; 114 | if JavaWebToken( 115 | BytesOf(FPrivateKey), 116 | JWT_RS256, 117 | ClaimSet(FOAuthScope, FLastToken), 118 | JWT) then 119 | begin 120 | HTTP := TgoHTTPClient.Create; 121 | try 122 | HTTP.ContentType := 'application/x-www-form-urlencoded'; 123 | HTTP.RequestBody := 124 | 'grant_type=' + TNetEncoding.URL.Encode('urn:ietf:params:oauth:grant-type:jwt-bearer') + '&' + 125 | 'assertion=' + TNetEncoding.URL.Encode(JWT); 126 | Response := HTTP.Post('https://www.googleapis.com/oauth2/v4/token'); 127 | if HTTP.ResponseStatusCode = 200 then 128 | begin 129 | Doc := TgoBsonDocument.Parse(Response); 130 | FTokenExpiresInSec := Doc['expires_in']; 131 | FAccessToken := Doc['access_token']; 132 | end; 133 | finally 134 | HTTP.Free; 135 | end; 136 | end; 137 | end; 138 | Result := FAccessToken; 139 | end; 140 | 141 | function TgoGoogle.Post(const AUrl, ARequest: String; out AResponseHeaders, AResponseContent: String; 142 | const ARecvTimeout: Integer): Integer; 143 | var 144 | HTTP: TgoHTTPClient; 145 | begin 146 | HTTP := TgoHTTPClient.Create; 147 | try 148 | HTTP.RequestBody := ARequest; 149 | HTTP.Authorization := 'Bearer ' + AccessToken; 150 | AResponseContent := HTTP.Post(AUrl, ARecvTimeout); 151 | AResponseHeaders := HTTP.ResponseHeaders.AsString; 152 | Result := HTTP.ResponseStatusCode; 153 | finally 154 | HTTP.Free; 155 | end; 156 | end; 157 | 158 | end. 159 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/README.md: -------------------------------------------------------------------------------- 1 | # Service account key 2 | Note: you need to download the OAuth private key, see https://developers.google.com/identity/protocols/OAuth2ServiceAccount 3 | Name it 'My First Project.json' and put it in the 'Win32\Debug\' folder. -------------------------------------------------------------------------------- /Demos/AndroidSpeech/Speech.General.pas: -------------------------------------------------------------------------------- 1 | unit Speech.General; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils; 7 | 8 | type 9 | TSpeechResultCallback = reference to procedure(const aResult: string; aEndOfUtterance: boolean); 10 | 11 | ISpeechSession = interface; 12 | ISpeechAPI = interface 13 | ['{B6DAE071-75D2-48F0-9D5B-907709175347}'] 14 | function CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer = 16000; aInterimResults: Boolean = false): ISpeechSession; 15 | end; 16 | 17 | ISpeechSession = interface 18 | ['{0624A369-DBAA-4CD6-96EA-5B10EB6CE3F7}'] 19 | procedure SendRawAudio(const aData: TBytes); 20 | procedure CloseSend; 21 | end; 22 | 23 | TLogNotify = reference to procedure(const aData: string); 24 | 25 | procedure Log(const aData: string); 26 | procedure RegisterLogger(const aLogger: TLogNotify); 27 | 28 | implementation 29 | 30 | uses 31 | System.Classes; 32 | 33 | var 34 | FLogger: TLogNotify; 35 | 36 | procedure RegisterLogger(const aLogger: TLogNotify); 37 | begin 38 | FLogger := aLogger; 39 | end; 40 | 41 | procedure Log(const aData: string); 42 | begin 43 | if Assigned(FLogger) then 44 | FLogger(aData) 45 | else 46 | TThread.Queue(nil, 47 | procedure 48 | begin 49 | Writeln(aData); 50 | end); 51 | end; 52 | 53 | end. 54 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/Speech.Google.pas: -------------------------------------------------------------------------------- 1 | unit Speech.Google; 2 | 3 | interface 4 | 5 | uses 6 | Google.API, google.cloud.Speech.v1.Speech, Grijjy.Http2, System.SysUtils, Speech.General; 7 | 8 | type 9 | TGoogleSpeechAPI = class(TInterfacedObject, ISpeechAPI) 10 | protected 11 | g: TgoGoogle; 12 | function Token: string; 13 | protected 14 | FSpeech: ISpeech; 15 | function SpeechAPI: ISpeech; 16 | protected 17 | {ISpeechAPI} 18 | function CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer = 16000; aInterimResults: Boolean = false): ISpeechSession; 19 | end; 20 | 21 | TSpeechSession = class(TInterfacedObject, ISpeechSession) 22 | protected 23 | FSpeech: ISpeech; 24 | FCallback: TSpeechResultCallback; 25 | FStream: IStreamingRecognizeRequest_Send; 26 | FSampleRate: Integer; 27 | FInterimResults: Boolean; 28 | //FResult: string; 29 | //FDone: TBoolean; 30 | function SpeechStream: IStreamingRecognizeRequest_Send; 31 | protected 32 | {ISpeechSession} 33 | procedure SendRawAudio(const aData: TBytes); 34 | procedure CloseSend; 35 | public 36 | constructor Create(const aSpeechAPI: ISpeech; const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean); 37 | destructor Destroy; override; 38 | end; 39 | 40 | implementation 41 | 42 | uses 43 | superobject, System.IOUtils, Ultraware.Grpc; 44 | 45 | { TGoogleSpeechAPI } 46 | 47 | function TGoogleSpeechAPI.CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean): ISpeechSession; 48 | begin 49 | Result := TSpeechSession.Create(SpeechAPI, aCallback, aSampleRate, aInterimResults); 50 | end; 51 | 52 | function TGoogleSpeechAPI.SpeechAPI: ISpeech; 53 | var 54 | http2client: TGrpcHttp2Client; 55 | begin 56 | if FSpeech = nil then 57 | begin 58 | http2client := TGrpcHttp2Client.Create('speech.googleapis.com', 443, True); 59 | http2client.Http2Client.Authority := 'speech.googleapis.com:443'; 60 | http2client.Http2Client.Authorization := Token; 61 | FSpeech := TSpeech_Client.Create(http2client); 62 | end; 63 | Result := FSpeech; 64 | end; 65 | 66 | function TGoogleSpeechAPI.Token: string; 67 | var serviceaccount: ISuperObject; 68 | begin 69 | //https://github.com/grijjy/DelphiGoogleAPI 70 | 71 | if g = nil then 72 | begin 73 | //note: you need to download the OAuth private key, see https://developers.google.com/identity/protocols/OAuth2ServiceAccount 74 | serviceaccount := SO( TFile.ReadAllText('My First Project.json') ); 75 | 76 | g := TgoGoogle.Create; 77 | g.ServiceAccount := serviceaccount.S['client_email']; 78 | g.PrivateKey := serviceaccount.S['private_key']; 79 | g.OAuthScope := 'https://www.googleapis.com/auth/cloud-platform'; 80 | end; 81 | 82 | if g.AccessToken = '' then 83 | raise Exception.Create('Could not create access token'); 84 | 85 | Result := 'Bearer ' + g.AccessToken; 86 | end; 87 | 88 | { TSpeechSession } 89 | 90 | procedure TSpeechSession.CloseSend; 91 | begin 92 | if FStream <> nil then 93 | FStream.CloseSend; 94 | end; 95 | 96 | constructor TSpeechSession.Create(const aSpeechAPI: ISpeech; const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean); 97 | begin 98 | FSpeech := aSpeechAPI; 99 | FCallback := aCallback; 100 | FSampleRate := aSampleRate; 101 | FInterimResults := aInterimResults; 102 | end; 103 | 104 | destructor TSpeechSession.Destroy; 105 | begin 106 | FSpeech := nil; 107 | FCallback := nil; 108 | inherited; 109 | end; 110 | 111 | procedure TSpeechSession.SendRawAudio(const aData: TBytes); 112 | var 113 | req2: TStreamingRecognizeRequest2; 114 | b: TBytes; 115 | begin 116 | if SpeechStream.IsRequestClosed then 117 | Assert(False); 118 | 119 | b := aData; 120 | while b <> nil do 121 | begin 122 | req2.audio_content := Copy(b, 0, 1024); 123 | b := Copy(b, 1024, Length(b)); 124 | 125 | (SpeechStream as TGrpcStream).Stream.SendData(req2.Serialize); 126 | end; 127 | end; 128 | 129 | function TSpeechSession.SpeechStream: IStreamingRecognizeRequest_Send; 130 | var 131 | config: TStreamingRecognitionConfig; 132 | req: TStreamingRecognizeRequest; 133 | lastResult: string; 134 | begin 135 | if FStream = nil then 136 | begin 137 | config.single_utterance := True; 138 | config.interim_results := FInterimResults; 139 | config.config.encoding := TAudioEncoding.LINEAR16; 140 | config.config.max_alternatives := 1; 141 | config.config.sample_rate_hertz := FSampleRate; //16000; 142 | config.config.language_code := 'nl'; //'en-US'; 143 | config.config.profanity_filter := False; 144 | config.config.enable_word_time_offsets := False; 145 | req.streaming_config := config; 146 | 147 | FStream := FSpeech.StreamingRecognize( 148 | req, 149 | procedure(const aStreamingRecognizeResponse: TStreamingRecognizeResponse; aHasData, aClosed: Boolean) 150 | var 151 | resp: TStreamingRecognizeResponse; 152 | r: TStreamingRecognitionResult; a: TSpeechRecognitionAlternative; 153 | sResult: string; 154 | bFinal: Boolean; 155 | begin 156 | resp := aStreamingRecognizeResponse; 157 | case resp.speech_event_type of 158 | SPEECH_EVENT_UNSPECIFIED: Log('SPEECH_EVENT_UNSPECIFIED'); 159 | END_OF_SINGLE_UTTERANCE : Log('END_OF_SINGLE_UTTERANCE'); 160 | else 161 | Assert(False); 162 | end; 163 | 164 | if (resp.error.code <> 0) and 165 | (resp.error.message <> '') 166 | then 167 | Log(Format('%d: %s', [resp.error.code, resp.error.message])); 168 | 169 | bFinal := False; 170 | if (resp.results <> nil) then 171 | begin 172 | sResult := resp.results[0].alternatives[0].transcript; 173 | lastResult := sResult; 174 | 175 | for r in resp.results do 176 | begin 177 | for a in r.alternatives do 178 | begin 179 | Log(Format('Final: %s = %s (%2.1f%%)', [BoolToStr(r.is_final, True{full}), a.transcript, a.confidence * 100])); 180 | bFinal := r.is_final; 181 | Break; 182 | end; 183 | Break; 184 | end; 185 | end 186 | else 187 | sResult := lastResult; 188 | 189 | FCallback(sResult, {(resp.speech_event_type = END_OF_SINGLE_UTTERANCE)}bFinal or aClosed); 190 | end); 191 | end; 192 | Result := FStream; 193 | end; 194 | 195 | initialization 196 | //test if token can be made 197 | with TGoogleSpeechAPI.Create do 198 | try 199 | Token(); 200 | finally 201 | Free; 202 | end; 203 | 204 | end. 205 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/SpeechServer.dpr: -------------------------------------------------------------------------------- 1 | program SpeechServer; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | System.SysUtils, 9 | Ultraware.Grpc.ws in '..\..\gRPC\Ultraware.Grpc.ws.pas', 10 | System.Classes, 11 | IdStack, 12 | AndroidSpeech.Grpc in 'AndroidSpeech.Grpc.pas', 13 | AndroidSpeech.Impl in 'AndroidSpeech.Impl.pas', 14 | dAudioProcessing in 'dAudioProcessing.pas' {dmAudioProcessing: TDataModule}, 15 | google.cloud.speech.v1.Speech in 'google.cloud.speech.v1.Speech.pas', 16 | Speech.Google in 'Speech.Google.pas', 17 | Speech.General in 'Speech.General.pas', 18 | VCL.Forms, 19 | dTethering in 'dTethering.pas' {dmTethering: TDataModule}; 20 | 21 | var 22 | _WsServer: TGrpcWsServer; 23 | str: TStrings; s: string; 24 | begin 25 | try 26 | dmTethering := TdmTethering.Create(nil); 27 | 28 | _WsServer := TGrpcWsServer.Create('', 1001); 29 | _WsServer.RegisterImplementation(C_TestService_Path, TSpeechService_Impl); 30 | _WsServer.StartListen; 31 | Writeln('gRPC WS server ready on *:1001'); 32 | 33 | str := TStringlist.Create; 34 | GStack.AddLocalAddressesToList(str); 35 | for s in str do 36 | Writeln('Server IP: ' + s); 37 | 38 | if str.Count > 0 then 39 | dmTethering.ServerIp := str[str.Count-1]; 40 | dmTethering.EnableServer; 41 | 42 | TSpeechService_Impl.SpeechAPI := TGoogleSpeechAPI.Create; 43 | //WS needs mainthread? 44 | repeat 45 | CheckSynchronize(10); 46 | until False; 47 | //Readln(Input); 48 | except 49 | on E: Exception do 50 | Writeln(E.ClassName, ': ', E.Message); 51 | end; 52 | end. 53 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dAndroidRecorder.dfm: -------------------------------------------------------------------------------- 1 | inherited dmAndroidRecorder: TdmAndroidRecorder 2 | OldCreateOrder = True 3 | end 4 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dAndroidRecorder.pas: -------------------------------------------------------------------------------- 1 | unit dAndroidRecorder; 2 | 3 | interface 4 | 5 | uses 6 | Androidapi.JNI.Net, 7 | Androidapi.JNIBridge, 8 | Androidapi.JNI.JavaTypes, 9 | Androidapi.JNI.GraphicsContentViewText, 10 | Androidapi.JNI.Media, 11 | Androidapi.Helpers, Androidapi.JNI.App, BroadcastReceiver, 12 | 13 | System.SysUtils, System.Classes, dBaseRecorder, FMX.Types, AndroidSpeech.Grpc; 14 | 15 | type 16 | TdmAndroidRecorder = class(TdmBaseRecorder) 17 | procedure tmrFetchTimer(Sender: TObject); 18 | private 19 | Audio: JAudioManager; 20 | AudioRecorder: JAudioRecord; 21 | AudioStr: TJavaArray; 22 | 23 | FBlueToothDevice: JAudioDeviceInfo; 24 | BroadcastReceiver2: TBroadcastReceiver; 25 | FBluetoothOn: Boolean; 26 | 27 | channelConfig: Integer; 28 | audioFormat: Integer; 29 | minBufSize: Integer; 30 | procedure BroadcastReceiver2Receive(Context: JContext; Intent: JIntent); 31 | public 32 | procedure InitAndroid; 33 | 34 | procedure Start(const aCallback: TTextCallback); override; 35 | procedure Stop; override; 36 | 37 | function IsBluetoothOn: Boolean; 38 | end; 39 | 40 | implementation 41 | 42 | {%CLASSGROUP 'FMX.Controls.TControl'} 43 | 44 | uses 45 | Androidapi.Jni; 46 | 47 | {$R *.dfm} 48 | 49 | procedure TdmAndroidRecorder.BroadcastReceiver2Receive(Context: JContext; Intent: JIntent); 50 | var state: Integer; 51 | begin 52 | try 53 | state := intent.getIntExtra(TJAudioManager.JavaClass.EXTRA_SCO_AUDIO_STATE, -1); 54 | if (state = TJAudioManager.JavaClass.SCO_AUDIO_STATE_CONNECTED) then 55 | begin 56 | FBluetoothOn := True; 57 | Log(TLogType.ltDebug, 'BT Recording is Ready'); 58 | if Assigned(OnStateChange) then 59 | OnStateChange(Self); 60 | 61 | if AudioRecorder <> nil then 62 | if not AudioRecorder.setPreferredDevice(FBlueToothDevice) then 63 | Log(ltInfo, 'Could not set: ' + JStringToString(FBlueToothDevice.getProductName.toString)); 64 | end 65 | else if (state = TJAudioManager.JavaClass.SCO_AUDIO_STATE_CONNECTING) then 66 | Log(ltDebug, 'BT connecting...') 67 | else if (state = TJAudioManager.JavaClass.SCO_AUDIO_STATE_ERROR) then 68 | Log(ltDebug, 'BT error!') 69 | else if (state = TJAudioManager.JavaClass.SCO_AUDIO_STATE_DISCONNECTED) then 70 | begin 71 | FBluetoothOn := False; 72 | Log(ltDebug, 'BT Recording Disabled'); 73 | if Assigned(OnStateChange) then 74 | OnStateChange(Self); 75 | Audio.setBluetoothScoOn(True); 76 | Audio.startBluetoothSco(); 77 | end; 78 | except 79 | on E:Exception do 80 | Log(ltInfo, e.Message); 81 | end; 82 | end; 83 | 84 | procedure TdmAndroidRecorder.InitAndroid; 85 | var 86 | AudioObj: JObject; 87 | devices: TJavaObjectArray; 88 | device: JAudioDeviceInfo; 89 | i: Integer; 90 | AUDIO_SERVICE: JString; 91 | sDevice: string; 92 | begin 93 | //https://stackoverflow.com/questions/29626247/how-to-record-audio-via-bluetooth-mic 94 | AUDIO_SERVICE := TJContext.JavaClass.AUDIO_SERVICE; 95 | AudioObj:= TAndroidHelper.Context.getSystemService(AUDIO_SERVICE); 96 | Audio := TJAudioManager.Wrap((AudioObj as ILocalObject).GetObjectID); 97 | 98 | BroadcastReceiver2 := TBroadcastReceiver.Create(Self); 99 | BroadcastReceiver2.onReceive := BroadcastReceiver2Receive; 100 | BroadcastReceiver2.RegisterReceive; 101 | BroadcastReceiver2.Add(JStringToString(TJAudioManager.JavaClass.ACTION_SCO_AUDIO_STATE_UPDATED)); 102 | 103 | if not Audio.isBluetoothScoAvailableOffCall then 104 | Log(ltInfo, 'No BT recording available'); 105 | 106 | //if not Audio.isBluetoothScoOn then 107 | begin 108 | FBluetoothOn := False; 109 | Audio.setBluetoothScoOn(True); 110 | //https://developer.android.com/reference/android/media/AudioManager.html#startBluetoothSco() 111 | Audio.startBluetoothSco(); 112 | end; 113 | 114 | channelConfig:= TJAudioFormat.JavaClass.CHANNEL_IN_MONO; 115 | audioFormat:= TJAudioFormat.JavaClass.ENCODING_PCM_16BIT; 116 | // minBufSize = 1024 Bytes 117 | minBufSize:= TJAudioRecord.JavaClass.getMinBufferSize(sampleRate, channelConfig, audioFormat); 118 | // AudioRecover = 1024*4 = 4096 119 | AudioStr:= TJavaArray.Create(minBufSize*4); 120 | 121 | devices := Audio.getDevices(TJAudioManager.JavaClass.GET_DEVICES_INPUTS); 122 | for i := 0 to devices.Length - 1 do 123 | begin 124 | device := devices.Items[i]; 125 | sDevice := JStringToString(device.getProductName.toString); 126 | Log(ltDebug, sDevice); 127 | if sDevice.Contains('B350') then 128 | FBlueToothDevice := device; 129 | end; 130 | end; 131 | 132 | function TdmAndroidRecorder.IsBluetoothOn: Boolean; 133 | begin 134 | Result := FBluetoothOn; 135 | end; 136 | 137 | procedure TdmAndroidRecorder.Start(const aCallback: TTextCallback); 138 | begin 139 | if FBluetoothOn then 140 | begin 141 | Audio.setMode(TJAudioManager.JavaClass.MODE_IN_CALL); 142 | AudioRecorder:= TJAudioRecord.JavaClass.init(TJMediaRecorder_AudioSource.JavaClass.VOICE_COMMUNICATION, 143 | sampleRate, 144 | channelConfig, 145 | audioFormat, 146 | minBufSize*4); 147 | if FBlueToothDevice <> nil then 148 | if not AudioRecorder.setPreferredDevice(FBlueToothDevice) then 149 | raise Exception.Create('Could not set: ' + JStringToString(FBlueToothDevice.getProductName.toString)); 150 | end 151 | else 152 | AudioRecorder:= TJAudioRecord.JavaClass.init(TJMediaRecorder_AudioSource.JavaClass.VOICE_RECOGNITION, 153 | sampleRate, 154 | channelConfig, 155 | audioFormat, 156 | minBufSize*4); 157 | 158 | audioRecorder.startRecording(); 159 | inherited; 160 | end; 161 | 162 | procedure TdmAndroidRecorder.Stop; 163 | begin 164 | audioRecorder.stop(); 165 | inherited; 166 | end; 167 | 168 | procedure TdmAndroidRecorder.tmrFetchTimer(Sender: TObject); 169 | var 170 | NewCount: Integer; 171 | bRaw: TBytes; 172 | begin 173 | inherited; 174 | 175 | repeat 176 | if FStream = nil then 177 | Exit; 178 | 179 | // Read from the AudioRecover 180 | NewCount:= (AudioRecorder as JAudioRecord).read(AudioStr, 0, AudioStr.Length); 181 | // The read command does not read 4096, just 2048 182 | SetLength(bRaw, NewCount); 183 | Move(AudioStr.Data^, bRaw[0], NewCount); 184 | 185 | ProcessAudio(bRaw); 186 | until (NewCount <= 0) or tmrFetch.Enabled; 187 | end; 188 | 189 | end. 190 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dAudioProcessing.dfm: -------------------------------------------------------------------------------- 1 | object dmAudioProcessing: TdmAudioProcessing 2 | OldCreateOrder = False 3 | OnCreate = DataModuleCreate 4 | Height = 410 5 | Width = 672 6 | object Timer1: TTimer 7 | Interval = 2000 8 | OnTimer = Timer1Timer 9 | Left = 320 10 | Top = 88 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dAudioProcessing.pas: -------------------------------------------------------------------------------- 1 | unit dAudioProcessing; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, 7 | google.cloud.speech.v1.Speech, Google.API, Speech.General, 8 | Vcl.ExtCtrls, Grijjy.Http2; 9 | 10 | type 11 | TdmAudioProcessing = class(TDataModule) 12 | Timer1: TTimer; 13 | procedure DataModuleCreate(Sender: TObject); 14 | procedure Timer1Timer(Sender: TObject); 15 | private 16 | FSampleRate: Integer; 17 | FGoogleSpeechAPI: ISpeechAPI; 18 | FSpeechSession: ISpeechSession; 19 | FResult: string; 20 | FDone: TBoolean; 21 | function GoogleSpeechAPI: ISpeechAPI; 22 | procedure SendAudioBuffer(const aData: TBytes); 23 | protected 24 | public 25 | function TranslateData(const aData: TBytes): string; 26 | end; 27 | 28 | var 29 | dmAudioProcessing: TdmAudioProcessing; 30 | 31 | implementation 32 | 33 | uses 34 | Ultraware.Grpc, System.IOUtils, superobject, Speech.Google; 35 | 36 | {%CLASSGROUP 'System.Classes.TPersistent'} 37 | 38 | {$R *.dfm} 39 | 40 | procedure TdmAudioProcessing.DataModuleCreate(Sender: TObject); 41 | begin 42 | FDone := TBoolean.Create; 43 | FSampleRate := 16000; 44 | end; 45 | 46 | function TdmAudioProcessing.GoogleSpeechAPI: ISpeechAPI; 47 | begin 48 | if FGoogleSpeechAPI = nil then 49 | FGoogleSpeechAPI := TGoogleSpeechAPI.Create; 50 | Result := FGoogleSpeechAPI; 51 | end; 52 | 53 | procedure TdmAudioProcessing.SendAudioBuffer(const aData: TBytes); 54 | begin 55 | FSpeechSession.SendRawAudio(aData); 56 | end; 57 | 58 | procedure TdmAudioProcessing.Timer1Timer(Sender: TObject); 59 | begin 60 | if FSpeechSession <> nil then 61 | FSpeechSession.CloseSend; 62 | end; 63 | 64 | function TdmAudioProcessing.TranslateData(const aData: TBytes): string; 65 | var 66 | b: TBytes; 67 | begin 68 | FDone.Value := False; 69 | 70 | if FSpeechSession = nil then 71 | FSpeechSession := GoogleSpeechAPI.CreateSpeechSession( 72 | procedure(const aResult: string; aEndOfUtterance: boolean) 73 | begin 74 | FResult := aResult; 75 | FDone.Value := True; 76 | end, 77 | FSampleRate, False {single result}); 78 | 79 | if (aData <> nil) then 80 | Exit; 81 | 82 | if (FSpeechSession <> nil) then 83 | begin 84 | FSpeechSession.CloseSend; 85 | end; 86 | 87 | FDone.Wait(5 * 1000); 88 | Result := FResult; 89 | FSpeechSession := nil; 90 | end; 91 | 92 | end. 93 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dBaseRecorder.dfm: -------------------------------------------------------------------------------- 1 | object dmBaseRecorder: TdmBaseRecorder 2 | OldCreateOrder = False 3 | OnCreate = DataModuleCreate 4 | Height = 150 5 | Width = 215 6 | object tmrFetch: TTimer 7 | Enabled = False 8 | Interval = 50 9 | OnTimer = tmrFetchTimer 10 | Left = 104 11 | Top = 48 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dBaseRecorder.pas: -------------------------------------------------------------------------------- 1 | unit dBaseRecorder; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, AndroidSpeech.Grpc, FMX.Types; 7 | 8 | type 9 | TLogType = (ltDebug, ltInfo); 10 | TLogCallback = procedure(const aType: TLogType; const aData: string); 11 | 12 | TdmBaseRecorder = class(TDataModule) 13 | tmrFetch: TTimer; 14 | procedure tmrFetchTimer(Sender: TObject); 15 | procedure DataModuleCreate(Sender: TObject); 16 | protected 17 | FOnLog: TLogCallback; 18 | procedure Log(const aType: TLogType; const aData: string); 19 | public 20 | property OnLog: TLogCallback read FOnLog write FOnLog; 21 | protected 22 | FSampleRate: Integer; 23 | FPrevData: Tbytes; 24 | FStream: IStreamFile_Send; 25 | procedure CreateStream(const aCallback: TTextCallback); 26 | protected 27 | FVUValue: Integer; 28 | FVUMax: Integer; 29 | FOnVUChanged: TNotifyEvent; 30 | procedure ProcessVU(const aRaw: TBytes); 31 | procedure SetOnVUChanged(const Value: TNotifyEvent); 32 | protected 33 | FOwnUtteranceDetected: Boolean; 34 | FOwnUtteranceEndDetected: Boolean; 35 | FSpeechStart, 36 | FSilenceStart: TDateTime; 37 | procedure ProcessAudio(const aRaw: TBytes); 38 | private 39 | FOnStateChange: TNotifyEvent; 40 | protected 41 | FClient: ISpeechService_Client; 42 | function Client: ISpeechService_Client; 43 | public 44 | function UploadFile(const aFile: TFile): TText; 45 | function Ping(const aTime: TTime): TTime; 46 | procedure Stream(const aFile: TFile); 47 | procedure StopStream; 48 | public 49 | procedure Start(const aCallback: TTextCallback); virtual; 50 | procedure Stop; virtual; 51 | 52 | property SampleRate: Integer read FSampleRate write FSampleRate; 53 | 54 | function IsRecording: Boolean; 55 | function StartOfSpeech: TDateTime; 56 | property OnStateChange: TNotifyEvent read FOnStateChange write FOnStateChange; 57 | 58 | property VUValue: Integer read FVUValue write FVUValue; 59 | property VUMax: Integer read FVUMax write FVUMax; 60 | property OnVUChanged: TNotifyEvent read FOnVUChanged write SetOnVUChanged; 61 | end; 62 | 63 | implementation 64 | 65 | {%CLASSGROUP 'FMX.Controls.TControl'} 66 | 67 | uses System.DateUtils, AndroidSpeech.Client, Ultraware.Grpc.Ws, dTethering; 68 | 69 | {$R *.dfm} 70 | 71 | { TdmBaseRecorder } 72 | 73 | function TdmBaseRecorder.Client: ISpeechService_Client; 74 | begin 75 | if FClient = nil then 76 | begin 77 | FClient := TTestService_Client.Create( TGrpcWsClient.Create(dmTethering.ServerIp, 1001) ); 78 | end; 79 | Result := FClient; 80 | end; 81 | 82 | procedure TdmBaseRecorder.CreateStream(const aCallback: TTextCallback); 83 | begin 84 | FStream := Client.StreamFile( 85 | procedure(const aText: TText; aHasData, aClosed: Boolean) 86 | begin 87 | if Assigned(aCallback) then 88 | aCallback(aText, aHasData, aClosed); 89 | end); 90 | 91 | if FStream = nil then 92 | raise Exception.Create('No connection'); 93 | end; 94 | 95 | procedure TdmBaseRecorder.DataModuleCreate(Sender: TObject); 96 | begin 97 | SampleRate:= 16000; 98 | end; 99 | 100 | function TdmBaseRecorder.IsRecording: Boolean; 101 | begin 102 | Result := tmrFetch.Enabled; 103 | end; 104 | 105 | procedure TdmBaseRecorder.Log(const aType: TLogType; const aData: string); 106 | begin 107 | if Assigned(FOnLog) then 108 | FOnLog(aType, aData); 109 | end; 110 | 111 | function TdmBaseRecorder.Ping(const aTime: TTime): TTime; 112 | begin 113 | Result := Client.Ping(aTime); 114 | end; 115 | 116 | procedure TdmBaseRecorder.ProcessAudio(const aRaw: TBytes); 117 | var 118 | bThreshold: boolean; 119 | f: AndroidSpeech.Grpc.TFile; 120 | begin 121 | ProcessVU(aRaw); 122 | 123 | f.sampleRate := sampleRate; 124 | f.data := aRaw; 125 | 126 | //silence detection 127 | bThreshold := FVUValue > FVUMax div 2; 128 | if not FOwnUtteranceDetected then 129 | begin 130 | if bThreshold then 131 | begin 132 | FOwnUtteranceDetected := True; 133 | FSilenceStart := 0; 134 | FSpeechStart := Now; 135 | f.data := FPrevData + f.data; 136 | Log(ltDebug, 'new utterance'); 137 | end; 138 | end 139 | else 140 | begin 141 | if not bThreshold then 142 | bThreshold := FVUValue > FVUMax div 4; //lower threshold for stopping 143 | if not bThreshold then 144 | begin 145 | if FSilenceStart <= 0 then 146 | FSilenceStart := Now; 147 | if not FOwnUtteranceEndDetected and 148 | //( (MilliSecondsBetween(Now, FSpeechStart) > 1500) and (MilliSecondsBetween(Now, FSilenceStart) > 2000{1500})) or //sentence: longer silence allowed 149 | //( (MilliSecondsBetween(Now, FSpeechStart) < 1500) and (MilliSecondsBetween(Now, FSilenceStart) > 1000{200})) then //short command: 200ms silence 150 | (MilliSecondsBetween(Now, FSilenceStart) > 1500) then //tijdelijk langere tijd ivm veld test demo 151 | begin 152 | FOwnUtteranceEndDetected := True; 153 | Log(ltDebug, 'end of utterance'); 154 | end; 155 | end 156 | else 157 | FSilenceStart := 0; 158 | end; 159 | 160 | FPrevData := aRaw; 161 | 162 | {$MESSAGE WARN 'TODO: send in background thread, so UI does not hang, and store in memstream for offline working'} 163 | {$MESSAGE WARN 'TODO: split in seperate part in case of offline'} 164 | if FStream <> nil then 165 | begin 166 | if (FStream.IsResponseClosed or FStream.IsRequestClosed or FOwnUtteranceEndDetected) and tmrFetch.Enabled then 167 | Self.Stop; 168 | if FStream <> nil then 169 | if not FStream.IsRequestClosed and (aRaw <> nil) then 170 | if FOwnUtteranceDetected then 171 | FStream.Send(f); 172 | end 173 | else if tmrFetch.Enabled then //recursion check 174 | Self.Stop; 175 | end; 176 | 177 | procedure TdmBaseRecorder.ProcessVU(const aRaw: TBytes); 178 | var 179 | bRaw: TBytes; 180 | bAudio: TArray absolute bRaw; 181 | i, iAvg: Integer; 182 | iSum: Int64; 183 | begin 184 | bRaw := aRaw; 185 | 186 | //simple VU meter calc 187 | iSum := 0; 188 | if bRaw <> nil then 189 | begin 190 | for i := 0 to Length(bRaw) div 2 do //audio is 16bit = 2 bytes 191 | iSum := iSum + Abs(bAudio[i]); //negative wave 192 | iAvg := Round(iSum / (Length(bRaw)div 2)); //int16 max = 32767 193 | end 194 | else 195 | iAvg := 0; 196 | if (iAvg > FVUMax) then 197 | FVUMax := iAvg; 198 | FVUValue := iAvg; 199 | 200 | //fire event 201 | if Assigned(OnVUChanged) then 202 | OnVUChanged(Self); 203 | end; 204 | 205 | procedure TdmBaseRecorder.SetOnVUChanged(const Value: TNotifyEvent); 206 | begin 207 | FOnVUChanged := Value; 208 | end; 209 | 210 | procedure TdmBaseRecorder.Start(const aCallback: TTextCallback); 211 | begin 212 | Log(ltDebug, 'starting...'); 213 | CreateStream(aCallback); 214 | FPrevData := nil; 215 | FOwnUtteranceDetected := False; 216 | FOwnUtteranceEndDetected := False; 217 | FSpeechStart := 0; 218 | FSilenceStart := 0; 219 | FVUMax := High(Int16) div 512; //32767 220 | FVUValue := 0; 221 | tmrFetch.Enabled := True; 222 | Log(ltDebug, 'Recording...'); 223 | if Assigned(OnStateChange) then 224 | OnStateChange(Self); 225 | end; 226 | 227 | function TdmBaseRecorder.StartOfSpeech: TDateTime; 228 | begin 229 | Result := FSpeechStart; 230 | end; 231 | 232 | procedure TdmBaseRecorder.Stop; 233 | begin 234 | if Fstream <> nil then 235 | Log(ltDebug, 'stopping...'); 236 | 237 | tmrFetch.Enabled := False; 238 | tmrFetchTimer(nil); 239 | 240 | if Fstream <> nil then 241 | begin 242 | FStream.CloseSend; 243 | Fstream := nil; 244 | Log(ltDebug, 'stopped'); 245 | end; 246 | 247 | if Assigned(OnStateChange) then 248 | OnStateChange(Self); 249 | end; 250 | 251 | procedure TdmBaseRecorder.StopStream; 252 | begin 253 | FStream.CloseSend; 254 | FStream := nil; 255 | end; 256 | 257 | procedure TdmBaseRecorder.Stream(const aFile: TFile); 258 | begin 259 | Assert(Fstream <> nil,'Stream not created. use "Start"'); 260 | FStream.Send(aFile); 261 | end; 262 | 263 | procedure TdmBaseRecorder.tmrFetchTimer(Sender: TObject); 264 | begin 265 | // 266 | end; 267 | 268 | function TdmBaseRecorder.UploadFile(const aFile: TFile): TText; 269 | begin 270 | Result := Client.UploadFile(aFile); 271 | end; 272 | 273 | end. 274 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dStyle.pas: -------------------------------------------------------------------------------- 1 | unit dStyle; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, FMX.Types, FMX.Controls; 7 | 8 | type 9 | TdmStyle = class(TDataModule) 10 | StyleBook1: TStyleBook; 11 | private 12 | public 13 | end; 14 | 15 | var 16 | dmStyle: TdmStyle; 17 | 18 | implementation 19 | 20 | {%CLASSGROUP 'FMX.Controls.TControl'} 21 | 22 | {$R *.dfm} 23 | 24 | end. 25 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dTethering.dfm: -------------------------------------------------------------------------------- 1 | object dmTethering: TdmTethering 2 | OldCreateOrder = False 3 | OnCreate = DataModuleCreate 4 | OnDestroy = DataModuleDestroy 5 | Height = 242 6 | Width = 336 7 | object TetheringAppProfile1: TTetheringAppProfile 8 | Manager = TetheringManager1 9 | Text = 'TetheringAppProfile1' 10 | Group = 'demo' 11 | Actions = <> 12 | Resources = < 13 | item 14 | Name = 'test' 15 | IsPublic = True 16 | end> 17 | Left = 184 18 | Top = 58 19 | end 20 | object TetheringManager1: TTetheringManager 21 | OnEndManagersDiscovery = TetheringManager1EndManagersDiscovery 22 | OnEndProfilesDiscovery = TetheringManager1EndProfilesDiscovery 23 | OnNewManager = TetheringManager1NewManager 24 | Password = 'test' 25 | Text = 'TetheringManagerDemo' 26 | Enabled = False 27 | AllowedAdapters = 'Network' 28 | Left = 64 29 | Top = 58 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/dTethering.pas: -------------------------------------------------------------------------------- 1 | unit dTethering; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, IPPeerClient, IPPeerServer, System.Tether.Manager, System.Tether.AppProfile, 7 | System.Types, 8 | dBaseRecorder; 9 | 10 | type 11 | TdmTethering = class(TDataModule) 12 | TetheringAppProfile1: TTetheringAppProfile; 13 | TetheringManager1: TTetheringManager; 14 | procedure TetheringManager1EndManagersDiscovery(const Sender: TObject; 15 | const ARemoteManagers: TTetheringManagerInfoList); 16 | procedure TetheringManager1EndProfilesDiscovery(const Sender: TObject; 17 | const ARemoteProfiles: TTetheringProfileInfoList); 18 | procedure TetheringManager1NewManager(const Sender: TObject; const AManagerInfo: TTetheringManagerInfo); 19 | procedure DataModuleDestroy(Sender: TObject); 20 | procedure DataModuleCreate(Sender: TObject); 21 | private 22 | FServerIp: string; 23 | FOnServerIpChanged: TNotifyEvent; 24 | private 25 | FOnLog: TLogCallback; 26 | procedure Log(const aType: TLogType; const aData: string); 27 | procedure SetServerIp(const Value: string); 28 | public 29 | procedure EnableServer; 30 | procedure EnableClient; 31 | 32 | function ServerIps: TStringDynArray; 33 | 34 | property ServerIp: string read FServerIp write SetServerIp; 35 | property OnServerIpChanged: TNotifyEvent read FOnServerIpChanged write FOnServerIpChanged; 36 | 37 | property OnLog: TLogCallback read FOnLog write FOnLog; 38 | end; 39 | 40 | var 41 | dmTethering: TdmTethering; 42 | 43 | implementation 44 | 45 | uses 46 | {$IFDEF WIN32} 47 | IdStack, 48 | IpHlpApi, IpTypes, Winapi.Windows, //fDragonControls3, 49 | {$ENDIF} 50 | FMX.DialogService.Async; 51 | 52 | {%CLASSGROUP 'System.Classes.TPersistent'} 53 | 54 | {$R *.dfm} 55 | 56 | {$IFDEF WIN32} 57 | procedure RetrieveLocalAdapterInformation(strings: TStrings; aOnlyConnected: Boolean; out aIpAdress: string); 58 | var 59 | pAdapterInfo{, pTempAdapterInfo}: PIP_ADAPTER_INFO; 60 | // AdapterInfo: IP_ADAPTER_INFO; 61 | BufLen: Cardinal; 62 | Status: Cardinal; 63 | strMAC: String; 64 | i: Integer; 65 | begin 66 | strings.Clear; 67 | 68 | BufLen:= sizeof(IP_ADAPTER_INFO); 69 | // pAdapterInfo:= @AdapterInfo; 70 | 71 | // Status:= GetAdaptersInfo(nil, BufLen); 72 | pAdapterInfo:= AllocMem(BufLen); 73 | try 74 | Status:= GetAdaptersInfo(pAdapterInfo, BufLen); 75 | 76 | if (Status <> ERROR_SUCCESS) then 77 | begin 78 | case Status of 79 | ERROR_NOT_SUPPORTED: 80 | strings.Add('GetAdaptersInfo is not supported by the operating ' + 81 | 'system running on the local computer.'); 82 | ERROR_NO_DATA: 83 | strings.Add('No network adapter on the local computer.'); 84 | else 85 | strings.Add('GetAdaptersInfo failed with error #' + IntToStr(Status)); 86 | end; 87 | Dispose(pAdapterInfo); 88 | Exit; 89 | end; 90 | 91 | while (pAdapterInfo <> nil) do 92 | begin 93 | if aOnlyConnected then 94 | if (pAdapterInfo^.DhcpServer.IpAddress.S = '') or (pAdapterInfo^.DhcpServer.IpAddress.S = '0.0.0.0') then 95 | begin 96 | pAdapterInfo:= pAdapterInfo^.Next; 97 | Continue; 98 | end; 99 | 100 | strings.Add('Description: ' + pAdapterInfo^.Description); 101 | strings.Add('Name: ' + pAdapterInfo^.AdapterName); 102 | 103 | strMAC := ''; 104 | for I := 0 to pAdapterInfo^.AddressLength - 1 do 105 | strMAC := strMAC + '-' + IntToHex(pAdapterInfo^.Address[I], 2); 106 | 107 | Delete(strMAC, 1, 1); 108 | strings.Add('MAC address: ' + strMAC); 109 | strings.Add('IP address: ' + pAdapterInfo^.IpAddressList.IpAddress.S); 110 | strings.Add('IP subnet mask: ' + pAdapterInfo^.IpAddressList.IpMask.S); 111 | strings.Add('Gateway: ' + pAdapterInfo^.GatewayList.IpAddress.S); 112 | strings.Add('DHCP enabled: ' + IntTOStr(pAdapterInfo^.DhcpEnabled)); 113 | strings.Add('DHCP: ' + pAdapterInfo^.DhcpServer.IpAddress.S); 114 | strings.Add('Have WINS: ' + BoolToStr(pAdapterInfo^.HaveWins,True)); 115 | strings.Add('Primary WINS: ' + pAdapterInfo^.PrimaryWinsServer.IpAddress.S); 116 | strings.Add('Secondary WINS: ' + pAdapterInfo^.SecondaryWinsServer.IpAddress.S); 117 | 118 | aIpAdress := string(pAdapterInfo^.IpAddressList.IpAddress.S); 119 | 120 | // pTempAdapterInfo := pAdapterInfo; 121 | pAdapterInfo:= pAdapterInfo^.Next; 122 | // if assigned(pAdapterInfo) then Dispose(pTempAdapterInfo); 123 | end; 124 | finally 125 | Dispose(pAdapterInfo); 126 | end; 127 | end; 128 | {$ENDIF} 129 | 130 | procedure TdmTethering.DataModuleCreate(Sender: TObject); 131 | begin 132 | TetheringManager1.Enabled := False; 133 | TetheringAppProfile1.Enabled := False; 134 | end; 135 | 136 | procedure TdmTethering.DataModuleDestroy(Sender: TObject); 137 | begin 138 | TetheringManager1.CancelDiscoverManagers; 139 | TetheringManager1.Enabled := False; 140 | TetheringAppProfile1.Enabled := False; 141 | end; 142 | 143 | procedure TdmTethering.EnableClient; 144 | begin 145 | {$IFDEF WIN32} 146 | FServerIp := 'localhost'; 147 | {$ENDIF} 148 | 149 | TetheringManager1.Enabled := True; 150 | TetheringManager1.DiscoverManagers(); 151 | end; 152 | 153 | procedure TdmTethering.EnableServer; 154 | begin 155 | TetheringManager1.Enabled := False; 156 | TetheringManager1.Text := 'TetheringManagerDemo' + '@' + FServerIp; 157 | Log(ltDebug, TetheringManager1.Text); 158 | TetheringManager1.Enabled := True; 159 | end; 160 | 161 | procedure TdmTethering.Log(const aType: TLogType; const aData: string); 162 | begin 163 | if Assigned(FOnLog) then 164 | FOnLog(aType, aData) 165 | end; 166 | 167 | function TdmTethering.ServerIps: TStringDynArray; 168 | var 169 | str: tstrings; 170 | s: string; 171 | begin 172 | Result := nil; 173 | str := TStringlist.Create; 174 | try 175 | {$IFDEF WIN32} 176 | GStack.AddLocalAddressesToList(str); 177 | {$ENDIF} 178 | for s in str do 179 | Result := Result + [s]; 180 | finally 181 | str.Free; 182 | end; 183 | end; 184 | 185 | procedure TdmTethering.SetServerIp(const Value: string); 186 | begin 187 | FServerIp := Value; 188 | 189 | if TetheringManager1.Enabled then 190 | begin 191 | EnableServer; 192 | end; 193 | end; 194 | 195 | procedure TdmTethering.TetheringManager1EndManagersDiscovery(const Sender: TObject; 196 | const ARemoteManagers: TTetheringManagerInfoList); 197 | var manager: TTetheringManagerInfo; 198 | begin 199 | if ARemoteManagers = nil then Exit; 200 | 201 | for manager in ARemoteManagers do 202 | begin 203 | if manager.ManagerText.Contains('Demo') then 204 | begin 205 | Log(ltInfo, Format('Managers: %s, %s, %s, %s, %s, %d', [manager.ManagerIdentifier, manager.ManagerName, manager.ManagerText, manager.ConnectionString, manager.ManagerAdapters, manager.Version])); 206 | 207 | if manager.ManagerText.Contains('@') then 208 | begin 209 | FServerIp := manager.ManagerText.Split(['@'])[1]; 210 | Log(ltInfo, 'Server ip = ' + FServerIp); 211 | 212 | if Assigned(OnServerIpChanged) then 213 | OnServerIpChanged(Self); 214 | end; 215 | end; 216 | end; 217 | end; 218 | 219 | procedure TdmTethering.TetheringManager1EndProfilesDiscovery(const Sender: TObject; 220 | const ARemoteProfiles: TTetheringProfileInfoList); 221 | var prof: TTetheringProfileInfo; 222 | begin 223 | if ARemoteProfiles = nil then Exit; 224 | 225 | for prof in ARemoteProfiles do 226 | begin 227 | Log(ltDebug, Format('Profiles: %s, %s, %s, %s, %s, %d', [prof.ManagerIdentifier, prof.ProfileIdentifier, prof.ProfileText, prof.ProfileGroup, prof.ProfileType, prof.ProfileVersion])); 228 | if prof.ProfileGroup = Self.TetheringAppProfile1.Group then 229 | TetheringAppProfile1.Connect(prof); 230 | end; 231 | end; 232 | 233 | procedure TdmTethering.TetheringManager1NewManager(const Sender: TObject; const AManagerInfo: TTetheringManagerInfo); 234 | begin 235 | Log(ltDebug, format('New manager: %s, %s, %s, %s, %s', [AManagerInfo.ManagerIdentifier, AManagerInfo.ManagerName, AManagerInfo.ManagerText, AManagerInfo.ConnectionString, AManagerInfo.ManagerAdapters])); 236 | end; 237 | 238 | end. 239 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/fAndroidRecord.fmx: -------------------------------------------------------------------------------- 1 | object Form4: TForm4 2 | Left = 0 3 | Top = 0 4 | Caption = 'Form4' 5 | ClientHeight = 480 6 | ClientWidth = 254 7 | StyleBook = dmStyle.StyleBook1 8 | FormFactor.Width = 320 9 | FormFactor.Height = 480 10 | FormFactor.Devices = [Desktop] 11 | OnCreate = FormCreate 12 | OnShow = FormShow 13 | DesignerMasterStyle = 0 14 | object Memo1: TMemo 15 | Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] 16 | DataDetectorTypes = [] 17 | ReadOnly = True 18 | Align = Client 19 | Size.Width = 254.000000000000000000 20 | Size.Height = 411.000000000000000000 21 | Size.PlatformDefault = False 22 | TabOrder = 1 23 | Viewport.Width = 246.000000000000000000 24 | Viewport.Height = 403.000000000000000000 25 | end 26 | object Panel1: TPanel 27 | Align = Bottom 28 | Position.Y = 440.000000000000000000 29 | Size.Width = 254.000000000000000000 30 | Size.Height = 40.000000000000000000 31 | Size.PlatformDefault = False 32 | TabOrder = 2 33 | object ProgressBar1: TProgressBar 34 | Align = Top 35 | Orientation = Horizontal 36 | Size.Width = 254.000000000000000000 37 | Size.Height = 10.000000000000000000 38 | Size.PlatformDefault = False 39 | Value = 50.000000000000000000 40 | end 41 | object chkDebug: TCheckBox 42 | Align = Left 43 | Position.Y = 10.000000000000000000 44 | Size.Width = 89.000000000000000000 45 | Size.Height = 30.000000000000000000 46 | Size.PlatformDefault = False 47 | TabOrder = 3 48 | Text = 'Debug' 49 | OnChange = chkDebugChange 50 | end 51 | end 52 | object Panel2: TPanel 53 | Align = Top 54 | Size.Width = 254.000000000000000000 55 | Size.Height = 29.000000000000000000 56 | Size.PlatformDefault = False 57 | TabOrder = 3 58 | object GridPanelLayout1: TGridPanelLayout 59 | Align = Client 60 | Size.Width = 254.000000000000000000 61 | Size.Height = 29.000000000000000000 62 | Size.PlatformDefault = False 63 | TabOrder = 1 64 | ColumnCollection = < 65 | item 66 | Value = 50.000000000000000000 67 | end 68 | item 69 | Value = 50.000000000000000000 70 | end> 71 | ControlCollection = < 72 | item 73 | Column = 0 74 | Control = btnStart 75 | Row = 0 76 | end 77 | item 78 | Column = 1 79 | Control = btnStop 80 | Row = 0 81 | end> 82 | RowCollection = < 83 | item 84 | Value = 100.000000000000000000 85 | end 86 | item 87 | SizeStyle = Auto 88 | end> 89 | object btnStart: TButton 90 | Align = Client 91 | Size.Width = 127.000000000000000000 92 | Size.Height = 29.000000000000000000 93 | Size.PlatformDefault = False 94 | TabOrder = 1 95 | Text = 'Start' 96 | OnClick = btnStartClick 97 | end 98 | object btnStop: TButton 99 | Align = Client 100 | Enabled = False 101 | Size.Width = 127.000000000000000000 102 | Size.Height = 29.000000000000000000 103 | Size.PlatformDefault = False 104 | TabOrder = 0 105 | Text = 'Stop' 106 | OnClick = btnStopClick 107 | end 108 | end 109 | end 110 | object btnPing: TButton 111 | Position.X = 8.000000000000000000 112 | Position.Y = 384.000000000000000000 113 | TabOrder = 5 114 | Text = 'Ping' 115 | OnClick = btnPingClick 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /Demos/AndroidSpeech/fAndroidRecord.pas: -------------------------------------------------------------------------------- 1 | unit fAndroidRecord; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 7 | FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Media, 8 | AndroidSpeech.Client, AndroidSpeech.Grpc, FMX.ScrollBox, FMX.Memo, FMX.Layouts, dBaseRecorder; 9 | 10 | type 11 | TForm4 = class(TForm) 12 | Memo1: TMemo; 13 | Panel1: TPanel; 14 | Panel2: TPanel; 15 | GridPanelLayout1: TGridPanelLayout; 16 | btnStart: TButton; 17 | btnStop: TButton; 18 | ProgressBar1: TProgressBar; 19 | btnPing: TButton; 20 | chkDebug: TCheckBox; 21 | procedure btnStartClick(Sender: TObject); 22 | procedure btnStopClick(Sender: TObject); 23 | procedure FormCreate(Sender: TObject); 24 | // procedure btnRawClick(Sender: TObject); 25 | // procedure btnPartsClick(Sender: TObject); 26 | procedure btnPingClick(Sender: TObject); 27 | procedure FormShow(Sender: TObject); 28 | procedure chkDebugChange(Sender: TObject); 29 | private 30 | procedure GetTextCallback(const aText: TText; aHasData, aClosed: Boolean); 31 | protected 32 | FRecorder: TdmBaseRecorder; 33 | function SampleRate: Integer; 34 | procedure VUChanged(Sender: TObject); 35 | 36 | procedure Start; 37 | procedure Stop; 38 | end; 39 | 40 | procedure Log(const aType: TLogType; const aData: string); 41 | 42 | var 43 | Form4: TForm4; 44 | 45 | implementation 46 | 47 | uses 48 | {$IFDEF ANDROID} 49 | dAndroidRecorder, 50 | {$ENDIF} 51 | System.IOUtils, Ultraware.Grpc.Ws, dStyle, System.Diagnostics, System.DateUtils, dTethering, 52 | FMX.DialogService.Async; 53 | 54 | {$R *.fmx} 55 | {$R *.Windows.fmx MSWINDOWS} 56 | 57 | procedure Log(const aType: TLogType; const aData: string); 58 | begin 59 | if ((aType in [ltDebug]) and Form4.Chkdebug.IsChecked) or 60 | (aType in [ltInfo]) then 61 | begin 62 | Form4.Memo1.Lines.Add(aData); 63 | Form4.Memo1.GoToTextEnd(); 64 | end; 65 | end; 66 | 67 | procedure TForm4.btnStartClick(Sender: TObject); 68 | begin 69 | if dmTethering.ServerIp = '' then 70 | begin 71 | dmTethering.EnableClient; 72 | fmx.DialogService.Async.TDialogServiceAsync.ShowMessage('Searching...'); 73 | end 74 | else 75 | Start; 76 | end; 77 | 78 | procedure TForm4.btnStopClick(Sender: TObject); 79 | begin 80 | Stop; 81 | end; 82 | 83 | procedure TForm4.chkDebugChange(Sender: TObject); 84 | begin 85 | btnPing.Visible := chkDebug.IsChecked; 86 | end; 87 | 88 | procedure TForm4.GetTextCallback(const aText: TText; aHasData, aClosed: Boolean); 89 | begin 90 | if aHasData then 91 | Log(ltInfo, aText.Text); 92 | if aClosed then 93 | begin 94 | Stop; 95 | end; 96 | end; 97 | 98 | procedure TForm4.btnPingClick(Sender: TObject); 99 | var 100 | t, t2: TTime; 101 | watch: TStopwatch; 102 | begin 103 | t.hour := 0; 104 | t.sec := 0; 105 | t.msec := 100; 106 | watch := TStopwatch.Create; 107 | watch.Start; 108 | t2 := FRecorder.Ping(t); 109 | watch.Stop; 110 | Log(ltDebug, Format('Ping-pong, client = %d, server = %d, total = %dms',[100, t2.msec, watch.ElapsedMilliseconds])); 111 | end; 112 | 113 | procedure TForm4.VUChanged(Sender: TObject); 114 | begin 115 | ProgressBar1.Max := FRecorder.VUMax; 116 | ProgressBar1.Value := FRecorder.VUValue; 117 | end; 118 | 119 | procedure TForm4.FormCreate(Sender: TObject); 120 | begin 121 | ProgressBar1.Value := 0; 122 | {$IFDEF DEBUG} 123 | chkDebug.IsChecked := True; 124 | {$ENDIF} 125 | chkDebugChange(nil); 126 | end; 127 | 128 | procedure TForm4.FormShow(Sender: TObject); 129 | begin 130 | {$IFDEF ANDROID} 131 | FRecorder := TdmAndroidRecorder.Create(Self); 132 | (FRecorder as TdmAndroidRecorder).InitAndroid; 133 | {$ELSE} 134 | FRecorder := TdmDummyRecorder.Create(Self); 135 | {$ENDIF} 136 | FRecorder.SampleRate := sampleRate; 137 | FRecorder.OnLog := Log; 138 | FRecorder.SampleRate := sampleRate; 139 | FRecorder.OnVUChanged := VUChanged; 140 | end; 141 | 142 | function TForm4.SampleRate: Integer; 143 | begin 144 | Result := FRecorder.SampleRate; 145 | end; 146 | 147 | procedure TForm4.Start; 148 | begin 149 | application.ProcessMessages; 150 | 151 | try 152 | ProgressBar1.Value := 0; 153 | ProgressBar1.Max := FRecorder.VUMax;; 154 | 155 | FRecorder.Start(GetTextCallback); 156 | btnStart.Enabled := False; 157 | btnStop.Enabled := True; 158 | except 159 | on E:Exception do 160 | begin 161 | Log(ltInfo, e.Message); 162 | raise; 163 | end; 164 | end; 165 | end; 166 | 167 | procedure TForm4.Stop; 168 | begin 169 | FRecorder.Stop; 170 | btnStart.Enabled := True; 171 | btnStop.Enabled := False; 172 | end; 173 | 174 | end. 175 | -------------------------------------------------------------------------------- /Demos/GoogleSpeechPlain/Google.API.pas: -------------------------------------------------------------------------------- 1 | unit Google.API; 2 | 3 | { Google Cloud Platform APIs for Google Compute Engine instances } 4 | 5 | interface 6 | 7 | uses 8 | Grijjy.Http; 9 | 10 | type 11 | TgoGoogle = class 12 | private 13 | FAccessToken: String; 14 | FLastToken: TDateTime; 15 | FTokenExpiresInSec: Int64; 16 | private 17 | { Google Cloud Account } 18 | FOAuthScope: String; 19 | FServiceAccount: String; 20 | FPrivateKey: String; 21 | protected 22 | function ClaimSet(const AScope: String; const ADateTime: TDateTime): String; 23 | function GetAccessToken: String; 24 | procedure SetOAuthScope(const AValue: String); 25 | procedure SetPrivateKey(const AValue: String); 26 | procedure SetServiceAccount(const AValue: String); 27 | public 28 | constructor Create; 29 | destructor Destroy; override; 30 | public 31 | { Post a request to the Google Cloud APIs } 32 | function Post(const AUrl, ARequest: String; out AResponseHeaders, AResponseContent: String; 33 | const ARecvTimeout: Integer = TIMEOUT_RECV): Integer; 34 | public 35 | { Returns the current access token } 36 | property AccessToken: String read GetAccessToken; 37 | 38 | { Get or set the current engine scope } 39 | property OAuthScope: String read FOAuthScope write SetOAuthScope; 40 | 41 | { Get or set the current service account } 42 | property ServiceAccount: String read FServiceAccount write SetServiceAccount; 43 | 44 | { Get or set the current private key } 45 | property PrivateKey: String read FPrivateKey write SetPrivateKey; 46 | end; 47 | 48 | implementation 49 | 50 | uses 51 | System.SysUtils, 52 | System.DateUtils, 53 | System.NetEncoding, 54 | Grijjy.JWT, 55 | Grijjy.Bson; 56 | 57 | { TGoogle } 58 | 59 | constructor TgoGoogle.Create; 60 | begin 61 | FLastToken := -1; 62 | FTokenExpiresInSec := 0; 63 | end; 64 | 65 | destructor TgoGoogle.Destroy; 66 | begin 67 | inherited; 68 | end; 69 | 70 | procedure TgoGoogle.SetOAuthScope(const AValue: String); 71 | begin 72 | FOAuthScope := AValue; 73 | FLastToken := -1; { create new access token on next request } 74 | end; 75 | 76 | procedure TgoGoogle.SetServiceAccount(const AValue: String); 77 | begin 78 | FServiceAccount := AValue; 79 | FLastToken := -1; { create new access token on next request } 80 | end; 81 | 82 | procedure TgoGoogle.SetPrivateKey(const AValue: String); 83 | begin 84 | FPrivateKey := AValue; 85 | FLastToken := -1; { create new access token on next request } 86 | end; 87 | 88 | { See: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests for more details } 89 | function TgoGoogle.ClaimSet(const AScope: String; const ADateTime: TDateTime): String; 90 | var 91 | Doc: TgoBsonDocument; 92 | begin 93 | Doc := TgoBsonDocument.Create; 94 | Doc['iss'] := FServiceAccount; 95 | Doc['scope'] := AScope; 96 | Doc['aud'] := 'https://www.googleapis.com/oauth2/v4/token'; 97 | Doc['exp'] := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(IncSecond(ADateTime, 3600))); { expires in one hour } 98 | Doc['iat'] := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(ADateTime)); 99 | Result := Doc.ToJson; 100 | end; 101 | 102 | function TgoGoogle.GetAccessToken: String; 103 | var 104 | HTTP: TgoHTTPClient; 105 | Response: String; 106 | Doc: TgoBsonDocument; 107 | JWT: String; 108 | begin 109 | if (FLastToken = -1) or (Now >= IncSecond(FLastToken, FTokenExpiresInSec - 5)) then { padding of 5 seconds } 110 | begin 111 | { new token } 112 | FLastToken := Now; 113 | FAccessToken := ''; 114 | if JavaWebToken( 115 | BytesOf(FPrivateKey), 116 | JWT_RS256, 117 | ClaimSet(FOAuthScope, FLastToken), 118 | JWT) then 119 | begin 120 | HTTP := TgoHTTPClient.Create; 121 | try 122 | HTTP.ContentType := 'application/x-www-form-urlencoded'; 123 | HTTP.RequestBody := 124 | 'grant_type=' + TNetEncoding.URL.Encode('urn:ietf:params:oauth:grant-type:jwt-bearer') + '&' + 125 | 'assertion=' + TNetEncoding.URL.Encode(JWT); 126 | Response := HTTP.Post('https://www.googleapis.com/oauth2/v4/token'); 127 | if HTTP.ResponseStatusCode = 200 then 128 | begin 129 | Doc := TgoBsonDocument.Parse(Response); 130 | FTokenExpiresInSec := Doc['expires_in']; 131 | FAccessToken := Doc['access_token']; 132 | end; 133 | finally 134 | HTTP.Free; 135 | end; 136 | end; 137 | end; 138 | Result := FAccessToken; 139 | end; 140 | 141 | function TgoGoogle.Post(const AUrl, ARequest: String; out AResponseHeaders, AResponseContent: String; 142 | const ARecvTimeout: Integer): Integer; 143 | var 144 | HTTP: TgoHTTPClient; 145 | begin 146 | HTTP := TgoHTTPClient.Create; 147 | try 148 | HTTP.RequestBody := ARequest; 149 | HTTP.Authorization := 'Bearer ' + AccessToken; 150 | AResponseContent := HTTP.Post(AUrl, ARecvTimeout); 151 | AResponseHeaders := HTTP.ResponseHeaders.AsString; 152 | Result := HTTP.ResponseStatusCode; 153 | finally 154 | HTTP.Free; 155 | end; 156 | end; 157 | 158 | end. 159 | -------------------------------------------------------------------------------- /Demos/GoogleSpeechPlain/Speech.General.pas: -------------------------------------------------------------------------------- 1 | unit Speech.General; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils; 7 | 8 | type 9 | TSpeechResultCallback = reference to procedure(const aResult: string; aEndOfUtterance: boolean); 10 | 11 | ISpeechSession = interface; 12 | ISpeechAPI = interface 13 | ['{B6DAE071-75D2-48F0-9D5B-907709175347}'] 14 | function CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer = 16000; aInterimResults: Boolean = false): ISpeechSession; 15 | end; 16 | 17 | ISpeechSession = interface 18 | ['{0624A369-DBAA-4CD6-96EA-5B10EB6CE3F7}'] 19 | procedure SendRawAudio(const aData: TBytes); 20 | procedure CloseSend; 21 | end; 22 | 23 | TLogNotify = reference to procedure(const aData: string); 24 | 25 | procedure Log(const aData: string); 26 | procedure RegisterLogger(const aLogger: TLogNotify); 27 | 28 | implementation 29 | 30 | uses 31 | System.Classes; 32 | 33 | var 34 | FLogger: TLogNotify; 35 | 36 | procedure RegisterLogger(const aLogger: TLogNotify); 37 | begin 38 | FLogger := aLogger; 39 | end; 40 | 41 | procedure Log(const aData: string); 42 | begin 43 | if Assigned(FLogger) then 44 | FLogger(aData) 45 | else 46 | TThread.Queue(nil, 47 | procedure 48 | begin 49 | Writeln(aData); 50 | end); 51 | end; 52 | 53 | end. 54 | -------------------------------------------------------------------------------- /Demos/GoogleSpeechPlain/Speech.Google.pas: -------------------------------------------------------------------------------- 1 | unit Speech.Google; 2 | 3 | interface 4 | 5 | uses 6 | Google.API, google.cloud.Speech.v1.Speech, Grijjy.Http2, System.SysUtils, Speech.General; 7 | 8 | type 9 | TGoogleSpeechAPI = class(TInterfacedObject, ISpeechAPI) 10 | protected 11 | g: TgoGoogle; 12 | function Token: string; 13 | protected 14 | FSpeech: ISpeech; 15 | function SpeechAPI: ISpeech; 16 | protected 17 | {ISpeechAPI} 18 | function CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer = 16000; aInterimResults: Boolean = false): ISpeechSession; 19 | end; 20 | 21 | TSpeechSession = class(TInterfacedObject, ISpeechSession) 22 | protected 23 | FSpeech: ISpeech; 24 | FCallback: TSpeechResultCallback; 25 | FStream: IStreamingRecognizeRequest_Send; 26 | FSampleRate: Integer; 27 | FInterimResults: Boolean; 28 | //FResult: string; 29 | //FDone: TBoolean; 30 | function SpeechStream: IStreamingRecognizeRequest_Send; 31 | protected 32 | {ISpeechSession} 33 | procedure SendRawAudio(const aData: TBytes); 34 | procedure CloseSend; 35 | public 36 | constructor Create(const aSpeechAPI: ISpeech; const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean); 37 | destructor Destroy; override; 38 | end; 39 | 40 | implementation 41 | 42 | uses 43 | superobject, System.IOUtils, Ultraware.Grpc; 44 | 45 | { TGoogleSpeechAPI } 46 | 47 | function TGoogleSpeechAPI.CreateSpeechSession(const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean): ISpeechSession; 48 | begin 49 | Result := TSpeechSession.Create(SpeechAPI, aCallback, aSampleRate, aInterimResults); 50 | end; 51 | 52 | function TGoogleSpeechAPI.SpeechAPI: ISpeech; 53 | var 54 | http2client: TGrpcHttp2Client; 55 | begin 56 | if FSpeech = nil then 57 | begin 58 | http2client := TGrpcHttp2Client.Create('speech.googleapis.com', 443, True); 59 | http2client.Http2Client.Authority := 'speech.googleapis.com:443'; 60 | http2client.Http2Client.Authorization := Token; 61 | FSpeech := TSpeech_Client.Create(http2client); 62 | end; 63 | Result := FSpeech; 64 | end; 65 | 66 | function TGoogleSpeechAPI.Token: string; 67 | var serviceaccount: ISuperObject; 68 | begin 69 | //https://github.com/grijjy/DelphiGoogleAPI 70 | 71 | if g = nil then 72 | begin 73 | //note: you need to download the OAuth private key, see https://developers.google.com/identity/protocols/OAuth2ServiceAccount 74 | serviceaccount := SO( TFile.ReadAllText('My First Project.json') ); 75 | 76 | g := TgoGoogle.Create; 77 | g.ServiceAccount := serviceaccount.S['client_email']; 78 | g.PrivateKey := serviceaccount.S['private_key']; 79 | g.OAuthScope := 'https://www.googleapis.com/auth/cloud-platform'; 80 | end; 81 | 82 | if g.AccessToken = '' then 83 | raise Exception.Create('Could not create access token'); 84 | 85 | Result := 'Bearer ' + g.AccessToken; 86 | end; 87 | 88 | { TSpeechSession } 89 | 90 | procedure TSpeechSession.CloseSend; 91 | begin 92 | if FStream <> nil then 93 | FStream.CloseSend; 94 | end; 95 | 96 | constructor TSpeechSession.Create(const aSpeechAPI: ISpeech; const aCallback: TSpeechResultCallback; aSampleRate: Integer; aInterimResults: Boolean); 97 | begin 98 | FSpeech := aSpeechAPI; 99 | FCallback := aCallback; 100 | FSampleRate := aSampleRate; 101 | FInterimResults := aInterimResults; 102 | end; 103 | 104 | destructor TSpeechSession.Destroy; 105 | begin 106 | FSpeech := nil; 107 | FCallback := nil; 108 | inherited; 109 | end; 110 | 111 | procedure TSpeechSession.SendRawAudio(const aData: TBytes); 112 | var 113 | req2: TStreamingRecognizeRequest2; 114 | b: TBytes; 115 | begin 116 | if SpeechStream.IsRequestClosed then 117 | Assert(False); 118 | 119 | b := aData; 120 | while b <> nil do 121 | begin 122 | req2.audio_content := Copy(b, 0, 1024); 123 | b := Copy(b, 1024, Length(b)); 124 | 125 | (SpeechStream as TGrpcStream).Stream.SendData(req2.Serialize); 126 | end; 127 | end; 128 | 129 | function TSpeechSession.SpeechStream: IStreamingRecognizeRequest_Send; 130 | var 131 | config: TStreamingRecognitionConfig; 132 | req: TStreamingRecognizeRequest; 133 | lastResult: string; 134 | begin 135 | if FStream = nil then 136 | begin 137 | config.single_utterance := True; 138 | config.interim_results := FInterimResults; 139 | config.config.encoding := TAudioEncoding.LINEAR16; 140 | config.config.max_alternatives := 1; 141 | config.config.sample_rate_hertz := FSampleRate; //16000; 142 | config.config.language_code := 'nl'; //'en-US'; 143 | config.config.profanity_filter := False; 144 | config.config.enable_word_time_offsets := False; 145 | req.streaming_config := config; 146 | 147 | FStream := FSpeech.StreamingRecognize( 148 | req, 149 | procedure(const aStreamingRecognizeResponse: TStreamingRecognizeResponse; aHasData, aClosed: Boolean) 150 | var 151 | resp: TStreamingRecognizeResponse; 152 | r: TStreamingRecognitionResult; a: TSpeechRecognitionAlternative; 153 | sResult: string; 154 | bFinal: Boolean; 155 | begin 156 | resp := aStreamingRecognizeResponse; 157 | case resp.speech_event_type of 158 | SPEECH_EVENT_UNSPECIFIED: Log('SPEECH_EVENT_UNSPECIFIED'); 159 | END_OF_SINGLE_UTTERANCE : Log('END_OF_SINGLE_UTTERANCE'); 160 | else 161 | Assert(False); 162 | end; 163 | 164 | if (resp.error.code <> 0) and 165 | (resp.error.message <> '') 166 | then 167 | Log(Format('%d: %s', [resp.error.code, resp.error.message])); 168 | 169 | bFinal := False; 170 | if (resp.results <> nil) then 171 | begin 172 | sResult := resp.results[0].alternatives[0].transcript; 173 | lastResult := sResult; 174 | 175 | for r in resp.results do 176 | begin 177 | for a in r.alternatives do 178 | begin 179 | Log(Format('Final: %s = %s (%2.1f%%)', [BoolToStr(r.is_final, True{full}), a.transcript, a.confidence * 100])); 180 | bFinal := r.is_final; 181 | Break; 182 | end; 183 | Break; 184 | end; 185 | end 186 | else 187 | sResult := lastResult; 188 | 189 | FCallback(sResult, {(resp.speech_event_type = END_OF_SINGLE_UTTERANCE)}bFinal or aClosed); 190 | end); 191 | end; 192 | Result := FStream; 193 | end; 194 | 195 | initialization 196 | //test if token can be made 197 | with TGoogleSpeechAPI.Create do 198 | try 199 | Token(); 200 | finally 201 | Free; 202 | end; 203 | 204 | end. 205 | -------------------------------------------------------------------------------- /Demos/GoogleSpeechPlain/TranslateFile.dpr: -------------------------------------------------------------------------------- 1 | program TranslateFile; 2 | 3 | uses 4 | Vcl.Forms, 5 | Unit5 in 'Unit5.pas' {Form5}; 6 | 7 | {$R *.res} 8 | 9 | begin 10 | Application.Initialize; 11 | Application.MainFormOnTaskbar := True; 12 | Application.CreateForm(TForm5, Form5); 13 | Application.Run; 14 | end. 15 | -------------------------------------------------------------------------------- /Demos/GoogleSpeechPlain/Unit5.dfm: -------------------------------------------------------------------------------- 1 | object Form5: TForm5 2 | Left = 0 3 | Top = 0 4 | Caption = 'Form5' 5 | ClientHeight = 316 6 | ClientWidth = 649 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | OldCreateOrder = False 14 | OnCreate = FormCreate 15 | DesignSize = ( 16 | 649 17 | 316) 18 | PixelsPerInch = 96 19 | TextHeight = 13 20 | object Button1: TButton 21 | Left = 8 22 | Top = 32 23 | Width = 137 24 | Height = 25 25 | Caption = 'Translate' 26 | TabOrder = 0 27 | OnClick = Button1Click 28 | end 29 | object Edit1: TEdit 30 | Left = 8 31 | Top = 5 32 | Width = 633 33 | Height = 21 34 | Anchors = [akLeft, akTop, akRight] 35 | TabOrder = 1 36 | Text = 'audio.raw' 37 | TextHint = '