Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bluetooth SCO on Android 14 #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ buildscript {
support_library_version = '25.3.1'
}
repositories {
google()
jcenter()
mavenLocal()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
classpath 'com.android.tools.build:gradle:8.2.1'
}
}

repositories {
google()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you are at it, it might make sense to remove jcenter() (it no longer exists, see https://jfrog.com/blog/jcenter-sunset/) and mavenLocal() and add mavenCentral() instead.

jcenter()
mavenLocal()
}
Expand All @@ -24,26 +26,23 @@ def versionBuild = 0
apply plugin: 'com.android.application'

dependencies {
compile "com.android.support:support-v4:$support_library_version"
compile "com.android.support:appcompat-v7:$support_library_version"
compile "com.android.support:design:$support_library_version"
implementation 'androidx.appcompat:appcompat:1.6.1'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove those other libraries?

}

android {
compileSdkVersion 24
buildToolsVersion '25.0.3'
compileSdk 34

defaultConfig {
minSdkVersion 19
targetSdkVersion 22
minSdkVersion 28
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you raise the minSdkVersion? And why to 28, specificially?

targetSdkVersion 34

versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
sourceCompatibility 11
targetCompatibility 11
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you raise the Java version to 11?

}

lintOptions {
Expand All @@ -58,8 +57,7 @@ android {
zipAlignEnabled true
}
}
}

task wrapper(type: Wrapper) {
gradleVersion = '3.5'
namespace 'com.example.audiorecord'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does that do?

}

3 changes: 1 addition & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#Thu Jun 08 09:08:50 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip
6 changes: 4 additions & 2 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package="com.example.audiorecord">

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- Both permissions are required for the Bluetooth HFP recording to work -->
<uses-permission android:name="android.permission.BLUETOOTH" />
Expand All @@ -13,11 +12,14 @@
android:icon="@drawable/launcher_icon"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity android:name=".AudioRecordActivity">
<activity
android:name=".BluetoothRecordActivity"
android:exported="true">
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert. Audio recording, which is demonstrated by AudioRecordActivity, is the easier case. Using Bluetooth is only for people who want it.

<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand Down
96 changes: 67 additions & 29 deletions src/main/java/com/example/audiorecord/BluetoothRecordActivity.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
package com.example.audiorecord;

import static android.media.AudioManager.GET_DEVICES_INPUTS;

import static com.example.audiorecord.RecordPermission.PERMISSIONS_REQUEST_CODE_RECORD_AUDIO;
import static com.example.audiorecord.RecordPermission.RECORDING_PERMISSIONS;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.media.AudioDeviceInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -99,7 +111,7 @@ private void handleBluetoothStateChange(BluetoothState state) {

private Button stopButton;

private Button bluetoothButton;
private String savedFilePath;

@Override
public void onCreate(Bundle savedInstanceState) {
Expand All @@ -119,30 +131,30 @@ public void onClick(View v) {
@Override
public void onClick(View v) {
stopRecording();
playback();
}
});

audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

bluetoothButton = (Button) findViewById(R.id.btnBluetooth);
bluetoothButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
activateBluetoothSco();
}
});
}

@Override
protected void onResume() {
super.onResume();

bluetoothButton.setEnabled(calculateBluetoothButtonState());
if (!RecordPermission.checkRecordingPermission(this) ) {
ActivityCompat.requestPermissions(this, RECORDING_PERMISSIONS, PERMISSIONS_REQUEST_CODE_RECORD_AUDIO);
}


startButton.setEnabled(calculateStartRecordButtonState());
stopButton.setEnabled(calculateStopRecordButtonState());

registerReceiver(bluetoothStateReceiver, new IntentFilter(
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));

audioManager.setBluetoothScoOn(true);
audioManager.startBluetoothSco();
}

@Override
Expand All @@ -166,7 +178,6 @@ private void startRecording() {
recordingThread = new Thread(new RecordingRunnable(), "Recording Thread");
recordingThread.start();

bluetoothButton.setEnabled(calculateBluetoothButtonState());
startButton.setEnabled(calculateStartRecordButtonState());
stopButton.setEnabled(calculateStopRecordButtonState());
}
Expand All @@ -186,30 +197,17 @@ private void stopRecording() {

recordingThread = null;

bluetoothButton.setEnabled(calculateBluetoothButtonState());
startButton.setEnabled(calculateStartRecordButtonState());
stopButton.setEnabled(calculateStopRecordButtonState());
}

private void activateBluetoothSco() {
if (!audioManager.isBluetoothScoAvailableOffCall()) {
Log.e(TAG, "SCO ist not available, recording is not possible");
return;
}

if (!audioManager.isBluetoothScoOn()) {
audioManager.startBluetoothSco();
}
}

private void bluetoothStateChanged(BluetoothState state) {
Log.i(TAG, "Bluetooth state changed to:" + state);

if (BluetoothState.UNAVAILABLE == state && recordingInProgress.get()) {
stopRecording();
}

bluetoothButton.setEnabled(calculateBluetoothButtonState());
startButton.setEnabled(calculateStartRecordButtonState());
stopButton.setEnabled(calculateStopRecordButtonState());
}
Expand All @@ -219,18 +217,21 @@ private boolean calculateBluetoothButtonState() {
}

private boolean calculateStartRecordButtonState() {
return audioManager.isBluetoothScoOn() && !recordingInProgress.get();
return !recordingInProgress.get();
}

private boolean calculateStopRecordButtonState() {
return audioManager.isBluetoothScoOn() && recordingInProgress.get();
return recordingInProgress.get();
}

private class RecordingRunnable implements Runnable {

@Override
public void run() {
final File file = new File(Environment.getExternalStorageDirectory(), "recording.pcm");

String extDir = getFilesDir().getAbsolutePath();
savedFilePath = extDir + "/recording.pcm";
final File file = new File(savedFilePath);
final ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);

try (final FileOutputStream outStream = new FileOutputStream(file)) {
Expand Down Expand Up @@ -267,4 +268,41 @@ private String getBufferReadFailureReason(int errorCode) {
enum BluetoothState {
AVAILABLE, UNAVAILABLE
}

void playback() {

int playerBufferSize = AudioTrack.getMinBufferSize(SAMPLING_RATE_IN_HZ, AudioFormat.CHANNEL_OUT_MONO , AUDIO_FORMAT);
AudioTrack pcmTrack = new AudioTrack(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
new AudioFormat.Builder()
.setEncoding(AUDIO_FORMAT)
.setSampleRate(SAMPLING_RATE_IN_HZ)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
playerBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
);

pcmTrack.play();

try {
FileInputStream fis = new FileInputStream(savedFilePath);

byte[] readPCMbytes = new byte[fis.available()];

fis.read(readPCMbytes);

//DebugDump120Bytes(readPCMbytes);

pcmTrack.write(readPCMbytes, 0, readPCMbytes.length);
} catch (FileNotFoundException e) {
Log.e("PlaybackRecordedFile" , "FileNotFound");
} catch (IOException e) {
Log.e("PlaybackRecordedFile", "IOException");
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/example/audiorecord/RecordPermission.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.audiorecord;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;

import androidx.core.app.ActivityCompat;

public class RecordPermission {


static final int PERMISSIONS_REQUEST_CODE_RECORD_AUDIO = 2024;

static final String[] RECORDING_PERMISSIONS = {
android.Manifest.permission.RECORD_AUDIO
};

public static boolean checkRecordingPermission(Activity acti) {

boolean hasPermissions = true;
for (String permission : RECORDING_PERMISSIONS) {
if (ActivityCompat.checkSelfPermission(acti.getApplicationContext(), permission) == PackageManager.PERMISSION_DENIED) {
hasPermissions = false;
}
}

return hasPermissions;
}

}
5 changes: 0 additions & 5 deletions src/main/res/layout/bluetooth.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,4 @@
android:layout_height="wrap_content"
android:text="Stop"/>

<Button
android:id="@+id/btnBluetooth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bluetooth"/>
</LinearLayout>