-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RSDK-8714: add audioinput wrappers (#68)
- Loading branch information
1 parent
a7daf51
commit 724085a
Showing
7 changed files
with
466 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
core/sdk/src/main/kotlin/com/viam/sdk/core/component/audioinput/AudioInput.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.viam.sdk.core.component.audioinput | ||
|
||
import com.viam.common.v1.Common.ResourceName | ||
import com.viam.component.audioinput.v1.Audioinput.ChunksResponse | ||
import com.viam.component.audioinput.v1.Audioinput.PropertiesResponse | ||
import com.viam.sdk.core.component.Component | ||
import com.viam.sdk.core.resource.Resource | ||
import com.viam.sdk.core.resource.Subtype | ||
import com.viam.sdk.core.robot.RobotClient | ||
|
||
typealias AudioStream = Iterator<ChunksResponse> | ||
typealias Properties = PropertiesResponse | ||
|
||
/** | ||
* AudioInput represents a component that can capture audio. | ||
*/ | ||
abstract class AudioInput(name: String) : Component(SUBTYPE, named(name)) { | ||
|
||
companion object { | ||
@JvmField | ||
val SUBTYPE = Subtype(Subtype.NAMESPACE_RDK, Subtype.RESOURCE_TYPE_COMPONENT, "audioInput") | ||
|
||
/** | ||
* Get the ResourceName of the component | ||
* @param name the name of the component | ||
* @return the component's ResourceName | ||
*/ | ||
@JvmStatic | ||
fun named(name: String): ResourceName { | ||
return Resource.named(SUBTYPE, name) | ||
} | ||
|
||
/** | ||
* Get the component with the provided name from the provided robot. | ||
* @param robot the RobotClient | ||
* @param name the name of the component | ||
* @return the component | ||
*/ | ||
@JvmStatic | ||
fun fromRobot(robot: RobotClient, name: String): AudioInput { | ||
return robot.getResource(AudioInput::class.java, named(name)) | ||
} | ||
} | ||
|
||
/** | ||
* Stream audio samples from the audio input of the underlying robot | ||
*/ | ||
abstract fun stream(): AudioStream | ||
|
||
/** | ||
* Get the properties of the audio input of the underlying robot | ||
*/ | ||
abstract fun getProperties(): Properties | ||
|
||
} |
52 changes: 52 additions & 0 deletions
52
core/sdk/src/main/kotlin/com/viam/sdk/core/component/audioinput/AudioInputRPCClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.viam.sdk.core.component.audioinput | ||
|
||
import com.google.protobuf.Struct | ||
import com.google.protobuf.Value | ||
import com.viam.common.v1.Common | ||
import com.viam.common.v1.Common.GetGeometriesRequest | ||
import com.viam.component.audioinput.v1.AudioInputServiceGrpc | ||
import com.viam.component.audioinput.v1.AudioInputServiceGrpc.AudioInputServiceBlockingStub | ||
import com.viam.component.audioinput.v1.Audioinput | ||
import com.viam.sdk.core.rpc.Channel | ||
import java.util.* | ||
import kotlin.jvm.optionals.getOrDefault | ||
|
||
class AudioInputRPCClient(name: String, channel: Channel) : AudioInput(name) { | ||
private val client: AudioInputServiceBlockingStub | ||
|
||
init { | ||
val client = AudioInputServiceGrpc.newBlockingStub(channel) | ||
if (channel.callCredentials.isPresent) { | ||
this.client = client.withCallCredentials(channel.callCredentials.get()) | ||
} else { | ||
this.client = client | ||
} | ||
} | ||
|
||
override fun stream(): AudioStream { | ||
val request = Audioinput.ChunksRequest.newBuilder().setName(this.name.name) | ||
.setSampleFormat(Audioinput.SampleFormat.SAMPLE_FORMAT_FLOAT32_INTERLEAVED).build() | ||
val response = this.client.chunks(request) | ||
return response | ||
} | ||
|
||
override fun getProperties(): Properties { | ||
val request = Audioinput.PropertiesRequest.newBuilder().setName(this.name.name).build() | ||
return this.client.properties(request) | ||
} | ||
|
||
override fun doCommand(command: Map<String, Value>?): Struct { | ||
val request = Common.DoCommandRequest.newBuilder().setName(this.name.name) | ||
.setCommand(Struct.newBuilder().putAllFields(command).build()).build() | ||
val response = this.client.doCommand(request) | ||
return response.result | ||
} | ||
|
||
override fun getGeometries(extra: Optional<Struct>): List<Common.Geometry> { | ||
val request = GetGeometriesRequest.newBuilder().setName(this.name.name) | ||
.setExtra(extra.getOrDefault(Struct.getDefaultInstance())).build() | ||
val response = this.client.getGeometries(request) | ||
return response.geometriesList | ||
} | ||
|
||
} |
68 changes: 68 additions & 0 deletions
68
core/sdk/src/main/kotlin/com/viam/sdk/core/component/audioinput/AudioInputRPCService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.viam.sdk.core.component.audioinput | ||
|
||
import com.google.api.HttpBody | ||
import com.viam.common.v1.Common.* | ||
import com.viam.component.audioinput.v1.AudioInputServiceGrpc | ||
import com.viam.component.audioinput.v1.Audioinput | ||
import com.viam.sdk.core.resource.ResourceManager | ||
import com.viam.sdk.core.resource.ResourceRPCService | ||
import io.grpc.stub.StreamObserver | ||
import java.util.* | ||
|
||
internal class AudioInputRPCService(private val manager: ResourceManager) : | ||
AudioInputServiceGrpc.AudioInputServiceImplBase(), | ||
ResourceRPCService<AudioInput> { | ||
|
||
override fun chunks( | ||
request: Audioinput.ChunksRequest, | ||
responseObserver: StreamObserver<Audioinput.ChunksResponse> | ||
) { | ||
val audioInput = getResource(AudioInput.named(request.name)) | ||
val response = audioInput.stream() | ||
for (chunk in response) { | ||
responseObserver.onNext(chunk) | ||
} | ||
responseObserver.onCompleted() | ||
} | ||
|
||
override fun properties( | ||
request: Audioinput.PropertiesRequest, | ||
responseObserver: StreamObserver<Audioinput.PropertiesResponse> | ||
) { | ||
val audioInput = getResource(AudioInput.named(request.name)) | ||
val result = audioInput.getProperties() | ||
responseObserver.onNext(result) | ||
responseObserver.onCompleted() | ||
} | ||
|
||
override fun record(request: Audioinput.RecordRequest?, responseObserver: StreamObserver<HttpBody>?) { | ||
throw UnsupportedOperationException() | ||
} | ||
|
||
override fun doCommand( | ||
request: DoCommandRequest, responseObserver: StreamObserver<DoCommandResponse> | ||
) { | ||
val audioInput = getResource(AudioInput.named(request.name)) | ||
val result = audioInput.doCommand(request.command.fieldsMap) | ||
responseObserver.onNext(DoCommandResponse.newBuilder().setResult(result).build()) | ||
responseObserver.onCompleted() | ||
} | ||
|
||
override fun getGeometries( | ||
request: GetGeometriesRequest, responseObserver: StreamObserver<GetGeometriesResponse> | ||
) { | ||
val audioInput = getResource(AudioInput.named(request.name)) | ||
val result = audioInput.getGeometries(Optional.of(request.extra)) | ||
responseObserver.onNext(GetGeometriesResponse.newBuilder().addAllGeometries(result).build()) | ||
responseObserver.onCompleted() | ||
} | ||
|
||
override fun getResourceClass(): Class<AudioInput> { | ||
return AudioInput::class.java | ||
} | ||
|
||
override fun getManager(): ResourceManager { | ||
return this.manager | ||
} | ||
|
||
} |
104 changes: 104 additions & 0 deletions
104
core/sdk/src/test/kotlin/com/viam/sdk/core/component/audioinput/AudioInputRPCClientTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package com.viam.sdk.core.component.audioinput | ||
|
||
import com.google.protobuf.ByteString | ||
import com.google.protobuf.Duration | ||
import com.google.protobuf.Struct | ||
import com.google.protobuf.Value | ||
import com.viam.common.v1.Common.Geometry | ||
import com.viam.component.audioinput.v1.Audioinput | ||
import com.viam.component.audioinput.v1.Audioinput.ChunksResponse | ||
import com.viam.component.audioinput.v1.Audioinput.PropertiesResponse | ||
import com.viam.sdk.core.resource.ResourceManager | ||
import com.viam.sdk.core.rpc.BasicManagedChannel | ||
import io.grpc.inprocess.InProcessChannelBuilder | ||
import io.grpc.inprocess.InProcessServerBuilder | ||
import io.grpc.testing.GrpcCleanupRule | ||
import org.junit.Rule | ||
import org.junit.jupiter.api.Assertions.assertEquals | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
import org.mockito.Mockito.* | ||
import java.util.* | ||
|
||
class AudioInputRPCClientTest { | ||
private lateinit var audioInput: AudioInput | ||
private lateinit var client: AudioInputRPCClient | ||
|
||
@JvmField | ||
@Rule | ||
val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() | ||
|
||
@BeforeEach | ||
fun setup() { | ||
audioInput = mock( | ||
AudioInput::class.java, withSettings().useConstructor("mock-audioInput").defaultAnswer( | ||
CALLS_REAL_METHODS | ||
) | ||
) | ||
val resourceManager = ResourceManager(listOf(audioInput)) | ||
val service = AudioInputRPCService(resourceManager) | ||
val serviceName = InProcessServerBuilder.generateName() | ||
grpcCleanupRule.register( | ||
InProcessServerBuilder.forName(serviceName).directExecutor().addService(service).build().start() | ||
) | ||
val channel = grpcCleanupRule.register(InProcessChannelBuilder.forName(serviceName).directExecutor().build()) | ||
client = AudioInputRPCClient("mock-audioInput", BasicManagedChannel(channel)) | ||
} | ||
|
||
@Test | ||
fun stream() { | ||
|
||
fun createChunks(): MutableList<ChunksResponse> { | ||
val chunks: MutableList<ChunksResponse> = mutableListOf() | ||
for (i in 0..5) { | ||
val chunk = | ||
Audioinput.AudioChunk.newBuilder().setData(ByteString.copyFromUtf8(i.toString())).setLength(2) | ||
.build() | ||
val info = Audioinput.AudioChunkInfo.newBuilder().setChannels(4) | ||
.setSampleFormat(Audioinput.SampleFormat.SAMPLE_FORMAT_FLOAT32_INTERLEAVED).setSamplingRate(1000L) | ||
.build() | ||
val audioChunk = ChunksResponse.newBuilder().setChunk(chunk).setInfo(info).build() | ||
chunks.add(audioChunk) | ||
} | ||
return chunks | ||
} | ||
|
||
val expected = createChunks() | ||
|
||
`when`(audioInput.stream()).thenReturn(expected.iterator()) | ||
val chunks = client.stream() | ||
verify(audioInput).stream() | ||
for ((index, value) in chunks.withIndex()) { | ||
assertEquals(expected[index], value) | ||
} | ||
|
||
} | ||
|
||
@Test | ||
fun getProperties() { | ||
val properties = | ||
PropertiesResponse.newBuilder().setLatency(Duration.newBuilder().setSeconds(3000L).build()).setSampleRate(2) | ||
.setSampleSize(3).setIsFloat(true).setChannelCount(4).setIsBigEndian(true).setIsInterleaved(true) | ||
.build() | ||
`when`(audioInput.getProperties()).thenReturn(properties) | ||
val response = client.getProperties() | ||
verify(audioInput).getProperties() | ||
assertEquals(properties, response) | ||
} | ||
|
||
@Test | ||
fun doCommand() { | ||
val command = mapOf("foo" to Value.newBuilder().setStringValue("bar").build()) | ||
doReturn(Struct.newBuilder().putAllFields(command).build()).`when`(audioInput).doCommand(anyMap()) | ||
val response = client.doCommand(command) | ||
verify(audioInput).doCommand(command) | ||
assertEquals(command, response.fieldsMap) | ||
} | ||
|
||
@Test | ||
fun getGeometries() { | ||
doReturn(listOf<Geometry>()).`when`(audioInput).getGeometries(any()) | ||
client.getGeometries(Optional.empty()) | ||
verify(audioInput).getGeometries(any()) | ||
} | ||
} |
Oops, something went wrong.