Skip to content

Commit 39c98c0

Browse files
Merge branch 'master' into fix/issue-6174
2 parents e1425f3 + 48beefe commit 39c98c0

File tree

8 files changed

+489
-0
lines changed

8 files changed

+489
-0
lines changed

.github/workflows/gradle.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ jobs:
8686
- name: Upload Test Report to Codecov
8787
uses: codecov/[email protected]
8888
with:
89+
token: ${{ secrets.CODECOV_TOKEN }}
8990
files: webrtc-android-framework/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,webrtc-android-sample-app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
9091
fail_ci_if_error: true
9192

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.antmedia.webrtcandroidframework.utility;
2+
3+
public interface LocalAudioLevelListener {
4+
void onAudioLevelUpdated(double decibelLevel);
5+
6+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package io.antmedia.webrtcandroidframework.utility;
2+
3+
import android.Manifest;
4+
import android.app.Activity;
5+
import android.content.pm.PackageManager;
6+
import android.media.AudioFormat;
7+
import android.media.AudioRecord;
8+
import android.media.MediaRecorder;
9+
import androidx.core.app.ActivityCompat;
10+
import java.util.concurrent.Executors;
11+
import java.util.concurrent.ScheduledExecutorService;
12+
import java.util.concurrent.TimeUnit;
13+
14+
public class SoundMeter {
15+
private final int SAMPLE_RATE = 44100;
16+
private final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
17+
private final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
18+
private final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
19+
private Activity activity;
20+
private AudioRecord audioRecord;
21+
private ScheduledExecutorService executorService;
22+
private long updateAudioLevelFrequencyMs = 250L;
23+
private LocalAudioLevelListener audioLevelListener;
24+
25+
26+
public SoundMeter(long updateAudioLevelFrequencyMs, Activity activity, LocalAudioLevelListener listener) {
27+
this.activity = activity;
28+
this.audioLevelListener = listener;
29+
this.updateAudioLevelFrequencyMs = updateAudioLevelFrequencyMs;
30+
init();
31+
32+
}
33+
34+
private void init(){
35+
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
36+
throw new SecurityException("RECORD_AUDIO permission not granted.");
37+
38+
}
39+
setAudioRecord(new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE));
40+
41+
}
42+
43+
public void start(){
44+
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
45+
audioRecord.startRecording();
46+
47+
executorService = Executors.newScheduledThreadPool(1);
48+
executorService.scheduleAtFixedRate(() -> {
49+
short[] buffer = new short[BUFFER_SIZE / 2];
50+
int numSamples = audioRecord.read(buffer, 0, buffer.length);
51+
if (numSamples > 0) {
52+
double rms = calculateRMS(buffer, numSamples);
53+
double db = 20 * Math.log10(rms);
54+
audioLevelListener.onAudioLevelUpdated(db);
55+
}
56+
}, 0, updateAudioLevelFrequencyMs, TimeUnit.MILLISECONDS);
57+
58+
}
59+
60+
}
61+
62+
public void stop(){
63+
if (audioRecord != null) {
64+
if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
65+
audioRecord.stop();
66+
}
67+
audioRecord.release();
68+
audioRecord = null;
69+
70+
}
71+
if(executorService != null){
72+
executorService.shutdown();
73+
}
74+
}
75+
76+
public double calculateRMS(short[] audioData, int numSamples) {
77+
double sum = 0;
78+
for (int i = 0; i < numSamples; i++) {
79+
sum += audioData[i] * audioData[i];
80+
}
81+
double mean = sum / numSamples;
82+
return Math.sqrt(mean);
83+
}
84+
85+
public void setAudioRecord(AudioRecord audioRecord) {
86+
this.audioRecord = audioRecord;
87+
}
88+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.antmedia.webrtcandroidframework;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
import static org.mockito.Matchers.any;
7+
import static org.mockito.Matchers.anyInt;
8+
import static org.mockito.Matchers.anyLong;
9+
import static org.mockito.Mockito.doReturn;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.spy;
12+
import static org.mockito.Mockito.timeout;
13+
import static org.mockito.Mockito.verify;
14+
import static org.mockito.Mockito.when;
15+
16+
import android.Manifest;
17+
import android.app.Activity;
18+
import android.content.pm.PackageManager;
19+
import android.media.AudioRecord;
20+
21+
import org.junit.Before;
22+
import org.junit.Test;
23+
24+
import io.antmedia.webrtcandroidframework.utility.LocalAudioLevelListener;
25+
import io.antmedia.webrtcandroidframework.utility.SoundMeter;
26+
27+
public class SoundMeterTest {
28+
29+
private Activity mockActivity;
30+
private LocalAudioLevelListener mockListener;
31+
32+
@Before
33+
public void setUp() {
34+
mockActivity = mock(Activity.class);
35+
mockListener = mock(LocalAudioLevelListener.class);
36+
}
37+
38+
@Test
39+
public void testStartAndStopRecording() {
40+
// Mock permission granted
41+
when(mockActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO)).thenReturn(PackageManager.PERMISSION_GRANTED);
42+
43+
// Mock AudioRecord
44+
AudioRecord mockAudioRecord = mock(AudioRecord.class);
45+
when(mockAudioRecord.getState()).thenReturn(AudioRecord.STATE_INITIALIZED);
46+
when(mockAudioRecord.read(any(short[].class), anyInt(), anyInt())).thenReturn(3);
47+
48+
SoundMeter soundMeter = spy(new SoundMeter(250L, mockActivity, mockListener));
49+
soundMeter.setAudioRecord(mockAudioRecord);
50+
51+
doReturn(10d).when(soundMeter).calculateRMS(any(short[].class), anyInt());
52+
53+
// Start recording
54+
soundMeter.start();
55+
56+
// Verify interactions with AudioRecord
57+
verify(mockAudioRecord, timeout(1000)).startRecording();
58+
59+
verify(mockListener, timeout(1000).atLeast(1)).onAudioLevelUpdated(anyInt());
60+
61+
// Stop recording
62+
soundMeter.stop();
63+
64+
verify(mockAudioRecord).stop();
65+
verify(mockAudioRecord).release();
66+
}
67+
68+
@Test
69+
public void testCalculateRMS() {
70+
// Given
71+
SoundMeter soundMeter = new SoundMeter(250L, mockActivity, mockListener);
72+
73+
// Test data
74+
short[] audioData = {100, 200, 300}; // Example audio data
75+
int numSamples = audioData.length;
76+
77+
// Expected RMS value
78+
double expectedRMS = Math.sqrt((100 * 100 + 200 * 200 + 300 * 300) / numSamples);
79+
80+
// When
81+
double actualRMS = soundMeter.calculateRMS(audioData, numSamples);
82+
83+
// Then
84+
assertEquals(expectedRMS, actualRMS, 0.005); // Adjust delta according to your precision needs
85+
}
86+
}

webrtc-android-sample-app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@
112112
<action android:name="com.google.firebase.MESSAGING_EVENT" />
113113
</intent-filter>
114114
</service>
115+
<activity android:name=".advanced.PublishActivityWithAreYouSpeaking"
116+
android:exported="true"
117+
android:theme="@style/Theme.AppCompat.DayNight"
118+
android:configChanges="orientation|keyboard|screenSize|smallestScreenSize|screenLayout"/>
115119

116120
<service
117121
android:name=".basic.MediaProjectionService"

webrtc-android-sample-app/src/main/java/io/antmedia/webrtc_android_sample_app/MainActivity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.antmedia.webrtc_android_sample_app.advanced.MP4PublishActivity;
2828
import io.antmedia.webrtc_android_sample_app.advanced.MP4PublishWithSurfaceActivity;
2929
import io.antmedia.webrtc_android_sample_app.advanced.MultiTrackPlayActivity;
30+
import io.antmedia.webrtc_android_sample_app.advanced.PublishActivityWithAreYouSpeaking;
3031
import io.antmedia.webrtc_android_sample_app.advanced.USBCameraActivity;
3132
import io.antmedia.webrtc_android_sample_app.basic.ConferenceActivity;
3233
import io.antmedia.webrtc_android_sample_app.basic.DataChannelOnlyActivity;
@@ -68,7 +69,9 @@ private void createList() {
6869
addActivity(USBCameraActivity.class, "USB Camera");
6970
addActivity(MultiTrackPlayActivity.class, "Multi Track");
7071
addActivity(CallNotificationActivity.class, "Call Notification");
72+
addActivity(PublishActivityWithAreYouSpeaking.class, "Are you talking?");
7173
addActivity(SettingsActivity.class, "Settings");
74+
addActivity(PublishActivityWithAreYouSpeaking.class, "Publish with Are You Speaking");
7275

7376
}
7477

0 commit comments

Comments
 (0)