Skip to content

Commit

Permalink
RSDK-8714: add audioinput wrappers (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
gloriacai01 authored Oct 8, 2024
1 parent a7daf51 commit 724085a
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.viam.common.v1.Common.ResourceName;
import com.viam.component.base.v1.BaseServiceGrpc;
import com.viam.component.arm.v1.ArmServiceGrpc;
import com.viam.component.audioinput.v1.AudioInputServiceGrpc;
import com.viam.component.board.v1.BoardServiceGrpc;
import com.viam.component.camera.v1.CameraServiceGrpc;
import com.viam.component.encoder.v1.EncoderServiceGrpc;
Expand All @@ -18,6 +19,7 @@
import com.viam.sdk.core.component.base.*;
import com.viam.sdk.core.component.arm.*;
import com.viam.component.servo.v1.ServoServiceGrpc;
import com.viam.sdk.core.component.audioinput.*;
import com.viam.sdk.core.component.board.Board;
import com.viam.sdk.core.component.board.BoardRPCClient;
import com.viam.sdk.core.component.board.BoardRPCService;
Expand Down Expand Up @@ -79,6 +81,12 @@ public class ResourceManager implements Closeable {
ArmRPCService::new,
ArmRPCClient::new
));
Registry.registerSubtype(new ResourceRegistration<>(
AudioInput.SUBTYPE,
AudioInputServiceGrpc.SERVICE_NAME,
AudioInputRPCService::new,
AudioInputRPCClient::new
));
Registry.registerSubtype(new ResourceRegistration<>(
Base.SUBTYPE,
BaseServiceGrpc.SERVICE_NAME,
Expand Down
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

}
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
}

}
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
}

}
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())
}
}
Loading

0 comments on commit 724085a

Please sign in to comment.