Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53723c15 authored by Charlie Boutier's avatar Charlie Boutier Committed by Gerrit Code Review
Browse files

Merge changes from topic "pandora_a2dp_stable" into main

* changes:
  Pandora: Update a2dp from experimental to stable
  PandoraServer: update a2dp.proto
parents 3cf07bb5 a18189cb
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -22,8 +22,8 @@ from mmi2grpc._audio import AudioSignal
from mmi2grpc._helpers import assert_description, match_description
from mmi2grpc._proxy import ProfileProxy
from mmi2grpc._rootcanal import RootCanal
from pandora_experimental.a2dp_grpc import A2DP
from pandora_experimental.a2dp_pb2 import Sink, Source, PlaybackAudioRequest
from pandora.a2dp_grpc import A2DP
from pandora.a2dp_pb2 import Sink, Source, PlaybackAudioRequest
from pandora.host_grpc import Host
from pandora.host_pb2 import Connection

+5 −6
Original line number Diff line number Diff line
@@ -21,8 +21,8 @@ from grpc import RpcError
from mmi2grpc._audio import AudioSignal
from mmi2grpc._helpers import assert_description
from mmi2grpc._proxy import ProfileProxy
from pandora_experimental.a2dp_grpc import A2DP
from pandora_experimental.a2dp_pb2 import Sink, Source
from pandora.a2dp_grpc import A2DP
from pandora.a2dp_pb2 import Sink, Source
from pandora_experimental.avrcp_grpc import AVRCP
from pandora.host_grpc import Host
from pandora.host_pb2 import Connection
@@ -470,10 +470,9 @@ class AVRCPProxy(ProfileProxy):
        passthrough command, if the current streaming state is not relevant to
        this IUT, please press 'OK to continue.
        """
        if not self.a2dp.IsSuspended(source=self.source).is_suspended:
            return "Yes"
        else:
            return "No"

        suspended = self.a2dp.IsSuspended(source=self.source).value
        return "Yes" if not suspended else "No"

    @assert_description
    def TSC_AVRCP_mmi_iut_reject_invalid_get_capabilities(self, **kwargs):
+16 −11
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.content.Intent
import android.content.IntentFilter
import android.media.*
import android.util.Log
import com.google.protobuf.BoolValue
import com.google.protobuf.ByteString
import io.grpc.Status
import io.grpc.stub.StreamObserver
import java.io.Closeable
@@ -41,7 +43,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import pandora.A2DPGrpc.A2DPImplBase
import pandora.A2dpProto.*
import pandora.A2DPProto.*

@kotlinx.coroutines.ExperimentalCoroutinesApi
class A2dp(val context: Context) : A2DPImplBase(), Closeable {
@@ -104,7 +106,8 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
            // early.
            delay(2000L)

            val source = Source.newBuilder().setConnection(request.connection).build()
            val source =
                Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8"))
            OpenSourceResponse.newBuilder().setSource(source).build()
        }
    }
@@ -140,7 +143,8 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
            // early.
            delay(2000L)

            val source = Source.newBuilder().setConnection(request.connection).build()
            val source =
                Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8"))
            WaitSourceResponse.newBuilder().setSource(source).build()
        }
    }
@@ -150,7 +154,7 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
            if (audioTrack == null) {
                audioTrack = buildAudioTrack()
            }
            val device = request.source.connection.toBluetoothDevice(bluetoothAdapter)
            val device = bluetoothAdapter.getRemoteDevice(request.source.cookie.toString("UTF-8"))
            Log.i(TAG, "start: device=$device")

            if (bluetoothA2dp.getConnectionState(device) != BluetoothA2dp.STATE_CONNECTED) {
@@ -177,7 +181,7 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
        responseObserver: StreamObserver<SuspendResponse>
    ) {
        grpcUnary<SuspendResponse>(scope, responseObserver) {
            val device = request.source.connection.toBluetoothDevice(bluetoothAdapter)
            val device = bluetoothAdapter.getRemoteDevice(request.source.cookie.toString("UTF-8"))
            Log.i(TAG, "suspend: device=$device")

            if (bluetoothA2dp.getConnectionState(device) != BluetoothA2dp.STATE_CONNECTED) {
@@ -202,10 +206,10 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {

    override fun isSuspended(
        request: IsSuspendedRequest,
        responseObserver: StreamObserver<IsSuspendedResponse>
        responseObserver: StreamObserver<BoolValue>
    ) {
        grpcUnary<IsSuspendedResponse>(scope, responseObserver) {
            val device = request.source.connection.toBluetoothDevice(bluetoothAdapter)
        grpcUnary(scope, responseObserver) {
            val device = bluetoothAdapter.getRemoteDevice(request.source.cookie.toString("UTF-8"))
            Log.i(TAG, "isSuspended: device=$device")

            if (bluetoothA2dp.getConnectionState(device) != BluetoothA2dp.STATE_CONNECTED) {
@@ -213,13 +217,14 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
            }

            val isSuspended = bluetoothA2dp.isA2dpPlaying(device)
            IsSuspendedResponse.newBuilder().setIsSuspended(isSuspended).build()

            BoolValue.newBuilder().setValue(isSuspended).build()
        }
    }

    override fun close(request: CloseRequest, responseObserver: StreamObserver<CloseResponse>) {
        grpcUnary<CloseResponse>(scope, responseObserver) {
            val device = request.source.connection.toBluetoothDevice(bluetoothAdapter)
            val device = bluetoothAdapter.getRemoteDevice(request.source.cookie.toString("UTF-8"))
            Log.i(TAG, "close: device=$device")

            if (bluetoothA2dp.getConnectionState(device) != BluetoothA2dp.STATE_CONNECTED) {
@@ -296,7 +301,7 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable {
        responseObserver: StreamObserver<GetAudioEncodingResponse>
    ) {
        grpcUnary<GetAudioEncodingResponse>(scope, responseObserver) {
            val device = request.source.connection.toBluetoothDevice(bluetoothAdapter)
            val device = bluetoothAdapter.getRemoteDevice(request.source.cookie.toString("UTF-8"))
            Log.i(TAG, "getAudioEncoding: device=$device")

            if (bluetoothA2dp.getConnectionState(device) != BluetoothA2dp.STATE_CONNECTED) {
+5 −3
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.media.*
import android.util.Log
import com.google.protobuf.ByteString
import io.grpc.stub.StreamObserver
import java.io.Closeable
import kotlinx.coroutines.CoroutineScope
@@ -37,7 +38,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import pandora.A2DPGrpc.A2DPImplBase
import pandora.A2dpProto.*
import pandora.A2DPProto.*

@kotlinx.coroutines.ExperimentalCoroutinesApi
class A2dpSink(val context: Context) : A2DPImplBase(), Closeable {
@@ -93,14 +94,15 @@ class A2dpSink(val context: Context) : A2DPImplBase(), Closeable {
                }
            }

            val sink = Sink.newBuilder().setConnection(request.connection).build()
            val sink =
                Sink.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8"))
            WaitSinkResponse.newBuilder().setSink(sink).build()
        }
    }

    override fun close(request: CloseRequest, responseObserver: StreamObserver<CloseResponse>) {
        grpcUnary<CloseResponse>(scope, responseObserver) {
            val device = request.sink.connection.toBluetoothDevice(bluetoothAdapter)
            val device = bluetoothAdapter.getRemoteDevice(request.sink.cookie.toString("UTF-8"))
            Log.i(TAG, "close: device=$device")
            if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
                throw RuntimeException("Device is not connected, cannot close")
+0 −264
Original line number Diff line number Diff line

// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

option java_outer_classname = "A2dpProto";

package pandora;

import "pandora/host.proto";

// Service to trigger A2DP (Advanced Audio Distribution Profile) procedures.
//
// Requirements for the implementor:
// - Streams must not be automatically opened, even if discovered.
// - The `Host` service must be implemented
//
// References:
// - [A2DP] Bluetooth SIG, Specification of the Bluetooth System,
//    Advanced Audio Distribution, Version 1.3 or Later
// - [AVDTP] Bluetooth SIG, Specification of the Bluetooth System,
//    Audio/Video Distribution Transport Protocol, Version 1.3 or Later
service A2DP {
  // Open a stream from a local **Source** endpoint to a remote **Sink**
  // endpoint.
  //
  // The returned source should be in the AVDTP_OPEN state (see [AVDTP] 9.1).
  // The rpc must block until the stream has reached this state.
  //
  // A cancellation of this call must result in aborting the current
  // AVDTP procedure (see [AVDTP] 9.9).
  rpc OpenSource(OpenSourceRequest) returns (OpenSourceResponse);
  // Open a stream from a local **Sink** endpoint to a remote **Source**
  // endpoint.
  //
  // The returned sink must be in the AVDTP_OPEN state (see [AVDTP] 9.1).
  // The rpc must block until the stream has reached this state.
  //
  // A cancellation of this call must result in aborting the current
  // AVDTP procedure (see [AVDTP] 9.9).
  rpc OpenSink(OpenSinkRequest) returns (OpenSinkResponse);
  // Wait for a stream from a local **Source** endpoint to
  // a remote **Sink** endpoint to open.
  //
  // The returned source should be in the AVDTP_OPEN state (see [AVDTP] 9.1).
  // The rpc must block until the stream has reached this state.
  //
  // If the peer has opened a source prior to this call, the server will
  // return it. The server must return the same source only once.
  rpc WaitSource(WaitSourceRequest) returns (WaitSourceResponse);
  // Wait for a stream from a local **Sink** endpoint to
  // a remote **Source** endpoint to open.
  //
  // The returned sink should be in the AVDTP_OPEN state (see [AVDTP] 9.1).
  // The rpc must block until the stream has reached this state.
  //
  // If the peer has opened a sink prior to this call, the server will
  // return it. The server must return the same sink only once.
  rpc WaitSink(WaitSinkRequest) returns (WaitSinkResponse);
  // Get if the stream is suspended
  rpc IsSuspended(IsSuspendedRequest) returns (IsSuspendedResponse);
  // Start a suspended stream.
  rpc Start(StartRequest) returns (StartResponse);
  // Suspend a started stream.
  rpc Suspend(SuspendRequest) returns (SuspendResponse);
  // Close a stream, the source or sink tokens must not be reused afterwards.
  rpc Close(CloseRequest) returns (CloseResponse);
  // Get the `AudioEncoding` value of a stream
  rpc GetAudioEncoding(GetAudioEncodingRequest) returns (GetAudioEncodingResponse);
  // Playback audio by a `Source`
  rpc PlaybackAudio(stream PlaybackAudioRequest) returns (PlaybackAudioResponse);
  // Capture audio from a `Sink`
  rpc CaptureAudio(CaptureAudioRequest) returns (stream CaptureAudioResponse);
}

// Audio encoding formats.
enum AudioEncoding {
  // Interleaved stereo frames with 16-bit signed little-endian linear PCM
  // samples at 44100Hz sample rate
  PCM_S16_LE_44K1_STEREO = 0;
  // Interleaved stereo frames with 16-bit signed little-endian linear PCM
  // samples at 48000Hz sample rate
  PCM_S16_LE_48K_STEREO = 1;
}

// A Token representing a Source stream (see [A2DP] 2.2).
// It's acquired via an OpenSource on the A2DP service.
message Source {
  // Opaque value filled by the GRPC server, must not
  // be modified nor crafted.
  Connection connection = 1;
}

// A Token representing a Sink stream (see [A2DP] 2.2).
// It's acquired via an OpenSink on the A2DP service.
message Sink {
  // Opaque value filled by the GRPC server, must not
  // be modified nor crafted.
  Connection connection = 1;
}

// Request for the `OpenSource` method.
message OpenSourceRequest {
  // The connection that will open the stream.
  Connection connection = 1;
}

// Response for the `OpenSource` method.
message OpenSourceResponse {
  // Result of the `OpenSource` call:
  // - If successful: a Source
  oneof result {
    Source source = 1;
  }
}

// Request for the `OpenSink` method.
message OpenSinkRequest {
  // The connection that will open the stream.
  Connection connection = 1;
}

// Response for the `OpenSink` method.
message OpenSinkResponse {
  // Result of the `OpenSink` call:
  // - If successful: a Sink
  oneof result {
    Sink sink = 1;
  }
}

// Request for the `WaitSource` method.
message WaitSourceRequest {
  // The connection that is awaiting the stream.
  Connection connection = 1;
}

// Response for the `WaitSource` method.
message WaitSourceResponse {
  // Result of the `WaitSource` call:
  // - If successful: a Source
  oneof result {
    Source source = 1;
  }
}

// Request for the `WaitSink` method.
message WaitSinkRequest {
  // The connection that is awaiting the stream.
  Connection connection = 1;
}

// Response for the `WaitSink` method.
message WaitSinkResponse {
  // Result of the `WaitSink` call:
  // - If successful: a Sink
  oneof result {
    Sink sink = 1;
  }
}

// Request for the `IsSuspended` method.
message IsSuspendedRequest {
  // The stream on which the function will check if it's suspended
  oneof target {
    Sink sink = 1;
    Source source = 2;
  }
}

// Response for the `IsSuspended` method.
message IsSuspendedResponse {
  bool is_suspended = 1;
}

// Request for the `Start` method.
message StartRequest {
  // Target of the start, either a Sink or a Source.
  oneof target {
    Sink sink = 1;
    Source source = 2;
  }
}

// Response for the `Start` method.
message StartResponse {}

// Request for the `Suspend` method.
message SuspendRequest {
  // Target of the suspend, either a Sink or a Source.
  oneof target {
    Sink sink = 1;
    Source source = 2;
  }
}

// Response for the `Suspend` method.
message SuspendResponse {}

// Request for the `Close` method.
message CloseRequest {
  // Target of the close, either a Sink or a Source.
  oneof target {
    Sink sink = 1;
    Source source = 2;
  }
}

// Response for the `Close` method.
message CloseResponse {}

// Request for the `GetAudioEncoding` method.
message GetAudioEncodingRequest {
  // The stream on which the function will read the `AudioEncoding`.
  oneof target {
    Sink sink = 1;
    Source source = 2;
  }
}

// Response for the `GetAudioEncoding` method.
message GetAudioEncodingResponse {
  // Audio encoding of the stream.
  AudioEncoding encoding = 1;
}

// Request for the `PlaybackAudio` method.
message PlaybackAudioRequest {
  // Source that will playback audio.
  Source source = 1;
  // Audio data to playback.
  // The audio data must be encoded in the specified `AudioEncoding` value
  // obtained in response of a `GetAudioEncoding` method call.
  bytes data = 2;
}

// Response for the `PlaybackAudio` method.
message PlaybackAudioResponse {}

// Request for the `CaptureAudio` method.
message CaptureAudioRequest {
  // Sink that will capture audio
  Sink sink = 1;
}

// Response for the `CaptureAudio` method.
message CaptureAudioResponse {
  // Captured audio data.
  // The audio data is encoded in the specified `AudioEncoding` value
  // obained in response of a `GetAudioEncoding` method call.
  bytes data = 1;
}
Loading