Skip to content

Commit a74f2cf

Browse files
authored
Merge pull request #50 from paulparkinson/main
Java and GCP Speech AIHolo impl and fixes to Python example
2 parents f34cfb8 + 23acbcf commit a74f2cf

File tree

97 files changed

+7099
-3495
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+7099
-3495
lines changed

java-ai/auth_and_run.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
# token itself expires after 1 hour, but it is automatically refreshed as long as the stored credentials remain valid.
4+
# provides long-lived authentication (~1 week) via Application Default Credentials (ADC).
5+
gcloud auth application-default login
6+
mvn clean package
7+
java -jar .\target\oracleai-0.0.1-SNAPSHOT.jar

java-ai/pom.xml

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,29 @@
1616

1717
<properties>
1818
<oci.sdk.version>3.52.1</oci.sdk.version>
19+
<oracle.jdbc.version>21.7.0.0</oracle.jdbc.version>
1920
</properties>
2021

22+
23+
<dependencyManagement>
24+
<dependencies>
25+
<dependency>
26+
<groupId>com.google.cloud</groupId>
27+
<artifactId>libraries-bom</artifactId>
28+
<version>26.32.0</version>
29+
<type>pom</type>
30+
<scope>import</scope>
31+
</dependency>
32+
</dependencies>
33+
</dependencyManagement>
34+
35+
36+
37+
38+
39+
40+
41+
2142
<dependencies>
2243
<dependency>
2344
<groupId>org.springframework.boot</groupId>
@@ -40,7 +61,7 @@
4061
<dependency>
4162
<groupId>com.oracle.cloud.spring</groupId>
4263
<artifactId>spring-cloud-oci-starter</artifactId>
43-
<version>1.3.0</version>
64+
<version>1.4.0</version>
4465
</dependency>
4566
<dependency>
4667
<groupId>com.oracle.oci.sdk</groupId>
@@ -99,6 +120,112 @@
99120
<artifactId>service</artifactId>
100121
<version>0.12.0</version>
101122
</dependency>
123+
124+
<dependency>
125+
<groupId>org.springframework.boot</groupId>
126+
<artifactId>spring-boot-starter-security</artifactId>
127+
</dependency>
128+
129+
130+
<!-- <dependency>
131+
<groupId>com.oracle.database.spring</groupId>
132+
<artifactId>oracle-spring-boot-starter-ucp</artifactId>
133+
<version>23.4.0</version>
134+
</dependency> -->
135+
<!-- <dependency>
136+
<groupId>com.oracle.database.spring</groupId>
137+
<artifactId>oracle-spring-boot-starter-wallet</artifactId>
138+
<version>23.4.0</version>
139+
</dependency> -->
140+
141+
<dependency>
142+
<groupId>com.oracle.database.jdbc</groupId>
143+
<artifactId>ojdbc8</artifactId>
144+
<version>${oracle.jdbc.version}</version>
145+
</dependency>
146+
<dependency>
147+
<groupId>com.oracle.database.jdbc</groupId>
148+
<artifactId>ucp</artifactId>
149+
<version>${oracle.jdbc.version}</version>
150+
</dependency>
151+
<dependency>
152+
<groupId>com.oracle.database.security</groupId>
153+
<artifactId>oraclepki</artifactId>
154+
<version>${oracle.jdbc.version}</version>
155+
</dependency>
156+
<dependency>
157+
<groupId>com.oracle.database.security</groupId>
158+
<artifactId>osdt_core</artifactId>
159+
<version>${oracle.jdbc.version}</version>
160+
</dependency>
161+
<dependency>
162+
<groupId>com.oracle.database.security</groupId>
163+
<artifactId>osdt_cert</artifactId>
164+
<version>${oracle.jdbc.version}</version>
165+
</dependency>
166+
167+
<dependency>
168+
<groupId>com.google.cloud</groupId>
169+
<artifactId>google-cloud-texttospeech</artifactId>
170+
</dependency>
171+
<dependency>
172+
<groupId>com.google.cloud</groupId>
173+
<artifactId>google-cloud-speech</artifactId>
174+
</dependency>
175+
<dependency>
176+
<groupId>net.sourceforge.argparse4j</groupId>
177+
<artifactId>argparse4j</artifactId>
178+
<version>0.9.0</version>
179+
</dependency>
180+
181+
182+
<dependency>
183+
<groupId>org.springframework.boot</groupId>
184+
<artifactId>spring-boot-starter-websocket</artifactId>
185+
</dependency>
186+
187+
188+
<dependency>
189+
<groupId>commons-cli</groupId>
190+
<artifactId>commons-cli</artifactId>
191+
<version>1.6.0</version>
192+
</dependency>
193+
194+
<dependency>
195+
<groupId>com.google.auth</groupId>
196+
<artifactId>google-auth-library-oauth2-http</artifactId>
197+
<version>1.18.0</version>
198+
</dependency>
199+
200+
<dependency>
201+
<groupId>jakarta.websocket</groupId>
202+
<artifactId>jakarta.websocket-api</artifactId>
203+
<version>2.2.0</version>
204+
<!-- <version>2.1.1</version> -->
205+
</dependency>
206+
207+
<dependency>
208+
<groupId>org.apache.tomcat.embed</groupId>
209+
<artifactId>tomcat-embed-websocket</artifactId>
210+
</dependency>
211+
212+
<!-- <dependency>
213+
<groupId>org.apache.tomcat</groupId>
214+
<artifactId>tomcat-websocket</artifactId>
215+
<version>10.1.14</version>
216+
</dependency> -->
217+
<dependency>
218+
<groupId>org.glassfish.tyrus</groupId>
219+
<artifactId>tyrus-server</artifactId>
220+
<version>2.1.3</version>
221+
</dependency>
222+
223+
<dependency>
224+
<groupId>org.glassfish.tyrus</groupId>
225+
<artifactId>tyrus-container-servlet</artifactId>
226+
<version>2.1.3</version>
227+
</dependency>
228+
102229
</dependencies>
103230
<build>
104231
<plugins>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package oracleai;
2+
3+
import jakarta.websocket.server.ServerEndpointConfig;
4+
5+
public class BinaryServerConfigurator extends ServerEndpointConfig.Configurator {
6+
@Override
7+
public boolean checkOrigin(String originHeaderValue) {
8+
System.out.println("✅ WebSocket checkOrigin originHeaderValue: " + originHeaderValue);
9+
return true; // Allow all origins for WebSocket
10+
}
11+
}

java-ai/src/main/java/oracleai/EchoController.java

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package oracleai;
2+
3+
import jakarta.websocket.server.HandshakeRequest;
4+
import jakarta.websocket.HandshakeResponse;
5+
import jakarta.websocket.server.ServerEndpointConfig;
6+
7+
public class SpeechWebSocketConfigurator extends ServerEndpointConfig.Configurator {
8+
@Override
9+
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
10+
sec.getUserProperties().put("org.apache.tomcat.websocket.binaryBufferSize", 1024 * 1024); // Enable binary message support
11+
}
12+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package oracleai;
2+
3+
import com.google.api.gax.rpc.BidiStreamingCallable;
4+
import com.google.api.gax.rpc.ApiStreamObserver;
5+
import com.google.cloud.speech.v1.*;
6+
import com.google.protobuf.ByteString;
7+
8+
import jakarta.websocket.*;
9+
import jakarta.websocket.server.ServerEndpoint;
10+
import java.io.*;
11+
import java.nio.ByteBuffer;
12+
import java.util.concurrent.Executors;
13+
import java.util.concurrent.ScheduledExecutorService;
14+
import javax.sound.sampled.*;
15+
16+
import org.springframework.stereotype.Component;
17+
18+
@ServerEndpoint(value = "/speech", configurator = SpeechWebSocketConfigurator.class)
19+
@Component
20+
public class SpeechWebSocketServer {
21+
private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
22+
private static SpeechClient speechClient;
23+
private ApiStreamObserver<StreamingRecognizeRequest> requestObserver;
24+
25+
static {
26+
try {
27+
speechClient = SpeechClient.create();
28+
} catch (IOException e) {
29+
throw new RuntimeException("❌ Failed to initialize SpeechClient", e);
30+
}
31+
}
32+
33+
@OnOpen
34+
public void onOpen(Session session) {
35+
System.out.println("✅ WebSocket Connected: " + session.getId());
36+
session.setMaxBinaryMessageBufferSize(1024 * 1024); // Allow large audio messages
37+
38+
ApiStreamObserver<StreamingRecognizeResponse> responseObserver = new ApiStreamObserver<>() {
39+
@Override
40+
public void onNext(StreamingRecognizeResponse response) {
41+
for (StreamingRecognitionResult result : response.getResultsList()) {
42+
if (result.getAlternativesCount() > 0) {
43+
String transcript = result.getAlternatives(0).getTranscript().trim();
44+
if (!transcript.isEmpty()) {
45+
System.out.println("📝 Transcription: " + transcript);
46+
try {
47+
session.getBasicRemote().sendText(transcript);
48+
} catch (IOException e) {
49+
e.printStackTrace();
50+
}
51+
}
52+
}
53+
}
54+
}
55+
56+
@Override
57+
public void onError(Throwable t) {
58+
System.err.println("❌ Google API Error: " + t.getMessage());
59+
}
60+
61+
@Override
62+
public void onCompleted() {
63+
System.out.println("✅ Streaming completed.");
64+
}
65+
};
66+
67+
// Initialize Streaming to Google Speech API
68+
BidiStreamingCallable<StreamingRecognizeRequest, StreamingRecognizeResponse> callable =
69+
speechClient.streamingRecognizeCallable();
70+
requestObserver = callable.bidiStreamingCall(responseObserver);
71+
72+
requestObserver.onNext(StreamingRecognizeRequest.newBuilder()
73+
.setStreamingConfig(StreamingRecognitionConfig.newBuilder()
74+
.setConfig(RecognitionConfig.newBuilder()
75+
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
76+
.setSampleRateHertz(16000)
77+
.setLanguageCode("en-US")
78+
.setAudioChannelCount(1)
79+
.setEnableAutomaticPunctuation(true)
80+
.build())
81+
.setInterimResults(true)
82+
.setSingleUtterance(false)
83+
.build())
84+
.build());
85+
}
86+
87+
/**
88+
* 🔹 **Handles Incoming Binary Audio Data (From WebSocket)**
89+
* This method now **reads a WAV file** instead of processing real-time audio streaming.
90+
*/
91+
@OnMessage
92+
public void onMessage(Session session) {
93+
String filePath = "C:/Users/opc/Downloads/audio_logs/sample.wav"; // Change to your WAV file path
94+
byte[] audioBytes;
95+
96+
try {
97+
audioBytes = readWavFile(filePath);
98+
if (audioBytes == null || audioBytes.length == 0) {
99+
System.out.println("⚠️ WAV file is empty or could not be read.");
100+
return;
101+
}
102+
} catch (IOException | UnsupportedAudioFileException e) {
103+
System.err.println("❌ Error reading WAV file: " + e.getMessage());
104+
return;
105+
}
106+
107+
System.out.println("✅ Sending Audio Data from WAV file: " + audioBytes.length + " bytes");
108+
109+
if (requestObserver != null) {
110+
requestObserver.onNext(StreamingRecognizeRequest.newBuilder()
111+
.setAudioContent(ByteString.copyFrom(audioBytes))
112+
.build());
113+
}
114+
}
115+
116+
@OnClose
117+
public void onClose(Session session) {
118+
System.out.println("🔴 WebSocket Closed: " + session.getId());
119+
if (requestObserver != null) {
120+
requestObserver.onCompleted();
121+
}
122+
}
123+
124+
@OnError
125+
public void onError(Session session, Throwable throwable) {
126+
System.err.println("⚠️ WebSocket error: " + throwable.getMessage());
127+
}
128+
129+
/**
130+
* **🔹 Reads WAV File and Extracts PCM Data**
131+
* - Converts **WAV file** to **raw PCM data**.
132+
* - Ensures it is in the **correct format** (16-bit mono PCM).
133+
*/
134+
private byte[] readWavFile(String filePath) throws IOException, UnsupportedAudioFileException {
135+
File file = new File(filePath);
136+
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
137+
AudioFormat format = audioInputStream.getFormat();
138+
139+
System.out.println("🎵 WAV File Format: " + format);
140+
141+
// Convert to PCM Signed if necessary
142+
if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
143+
AudioFormat pcmFormat = new AudioFormat(
144+
AudioFormat.Encoding.PCM_SIGNED,
145+
format.getSampleRate(),
146+
16, // Force 16-bit audio
147+
format.getChannels(),
148+
format.getChannels() * 2,
149+
format.getSampleRate(),
150+
false // Little-endian
151+
);
152+
audioInputStream = AudioSystem.getAudioInputStream(pcmFormat, audioInputStream);
153+
}
154+
155+
// Read raw audio data
156+
byte[] audioBytes = audioInputStream.readAllBytes();
157+
audioInputStream.close();
158+
return audioBytes;
159+
}
160+
}

java-ai/src/main/java/oracleai/WebConfig.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
77

88
@Configuration
9-
public class WebConfig implements WebMvcConfigurer {
10-
@Override
11-
public void addCorsMappings(CorsRegistry registry) {
12-
registry.addMapping("/**") // This will apply to all routes
13-
.allowedOrigins("https://130.61.51.75:4884") // Allow this origin
14-
.allowedMethods("GET", "POST", "PUT", "DELETE") // Allowed methods
15-
.allowedHeaders("*") // Allowed headers
16-
.allowCredentials(true); // Allow credentials
9+
public class WebConfig {
10+
@Bean
11+
public WebMvcConfigurer corsConfigurer() {
12+
return new WebMvcConfigurer() {
13+
@Override
14+
public void addCorsMappings(CorsRegistry registry) {
15+
registry.addMapping("/**") // Apply to all endpoints
16+
.allowedOriginPatterns("*") // ✅ Use allowedOriginPatterns instead of "*"
17+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
18+
.allowedHeaders("*")
19+
.allowCredentials(true); // ✅ Keep credentials enabled
20+
}
21+
};
1722
}
1823
}

0 commit comments

Comments
 (0)