Skip to content

Commit

Permalink
Merge pull request #1571 from pedroSG94/feature/audio-mix-source
Browse files Browse the repository at this point in the history
creating AudioMixSource
  • Loading branch information
pedroSG94 authored Sep 13, 2024
2 parents 168ce4b + 4280dd0 commit 46be00e
Show file tree
Hide file tree
Showing 24 changed files with 423 additions and 174 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
tools:ignore="DiscouragedApi" />

<service android:name="com.pedro.streamer.screen.ScreenService"
android:foregroundServiceType="mediaProjection"
android:foregroundServiceType="mediaProjection|microphone|camera"
/>
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.pedro.streamer.rotation
import android.graphics.Bitmap
import android.graphics.Paint
import android.graphics.SurfaceTexture
import android.util.Log
import android.view.Surface
import androidx.core.graphics.scale
import com.pedro.library.util.sources.video.VideoSource
Expand Down
34 changes: 15 additions & 19 deletions app/src/main/java/com/pedro/streamer/rotation/RotationActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.pedro.streamer.rotation

import android.annotation.SuppressLint
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
Expand All @@ -31,8 +32,8 @@ import com.pedro.library.util.sources.video.Camera1Source
import com.pedro.library.util.sources.video.Camera2Source
import com.pedro.streamer.R
import com.pedro.streamer.utils.FilterMenu
import com.pedro.streamer.utils.setColor
import com.pedro.streamer.utils.toast
import com.pedro.streamer.utils.updateMenuColor


/**
Expand Down Expand Up @@ -60,48 +61,48 @@ class RotationActivity : AppCompatActivity(), OnTouchListener {
val defaultAudioSource = menu.findItem(R.id.audio_source_microphone)
val defaultOrientation = menu.findItem(R.id.orientation_horizontal)
val defaultFilter = menu.findItem(R.id.no_filter)
currentVideoSource = updateMenuColor(currentVideoSource, defaultVideoSource)
currentAudioSource = updateMenuColor(currentAudioSource, defaultAudioSource)
currentOrientation = updateMenuColor(currentOrientation, defaultOrientation)
currentFilter = updateMenuColor(currentFilter, defaultFilter)
currentVideoSource = defaultVideoSource.updateMenuColor(this, currentVideoSource)
currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource)
currentOrientation = defaultOrientation.updateMenuColor(this, currentOrientation)
currentFilter = defaultFilter.updateMenuColor(this, currentFilter)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
try {
when (item.itemId) {
R.id.video_source_camera1 -> {
currentVideoSource = updateMenuColor(currentVideoSource, item)
currentVideoSource = item.updateMenuColor(this, currentVideoSource)
cameraFragment.genericStream.changeVideoSource(Camera1Source(applicationContext))
}
R.id.video_source_camera2 -> {
currentVideoSource = updateMenuColor(currentVideoSource, item)
currentVideoSource = item.updateMenuColor(this, currentVideoSource)
cameraFragment.genericStream.changeVideoSource(Camera2Source(applicationContext))
}
R.id.video_source_camerax -> {
currentVideoSource = updateMenuColor(currentVideoSource, item)
currentVideoSource = item.updateMenuColor(this, currentVideoSource)
cameraFragment.genericStream.changeVideoSource(CameraXSource(applicationContext))
}
R.id.video_source_bitmap -> {
currentVideoSource = updateMenuColor(currentVideoSource, item)
currentVideoSource = item.updateMenuColor(this, currentVideoSource)
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
cameraFragment.genericStream.changeVideoSource(BitmapSource(bitmap))
}
R.id.audio_source_microphone -> {
currentAudioSource = updateMenuColor(currentAudioSource, item)
currentAudioSource = item.updateMenuColor(this, currentAudioSource)
cameraFragment.genericStream.changeAudioSource(MicrophoneSource())
}
R.id.orientation_horizontal -> {
currentOrientation = updateMenuColor(currentOrientation, item)
currentOrientation = item.updateMenuColor(this, currentOrientation)
cameraFragment.setOrientationMode(false)
}
R.id.orientation_vertical -> {
currentOrientation = updateMenuColor(currentOrientation, item)
currentOrientation = item.updateMenuColor(this, currentOrientation)
cameraFragment.setOrientationMode(true)
}
else -> {
val result = filterMenu.onOptionsItemSelected(item, cameraFragment.genericStream.getGlInterface())
if (result) currentFilter = updateMenuColor(currentFilter, item)
if (result) currentFilter = item.updateMenuColor(this, currentFilter)
return result
}
}
Expand All @@ -111,6 +112,7 @@ class RotationActivity : AppCompatActivity(), OnTouchListener {
return super.onOptionsItemSelected(item)
}

@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
if (filterMenu.spriteGestureController.spriteTouched(view, motionEvent)) {
filterMenu.spriteGestureController.moveSprite(view, motionEvent)
Expand All @@ -119,10 +121,4 @@ class RotationActivity : AppCompatActivity(), OnTouchListener {
}
return false
}

private fun updateMenuColor(currentItem: MenuItem?, item: MenuItem): MenuItem {
currentItem?.setColor(this, R.color.black)
item.setColor(this, R.color.appColor)
return item
}
}
73 changes: 57 additions & 16 deletions app/src/main/java/com/pedro/streamer/screen/ScreenActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@ package com.pedro.streamer.screen
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.widget.EditText
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.pedro.common.ConnectChecker
import com.pedro.library.base.recording.RecordController
import com.pedro.library.util.sources.audio.MixAudioSource
import com.pedro.library.util.sources.audio.InternalAudioSource
import com.pedro.library.util.sources.audio.MicrophoneSource
import com.pedro.streamer.R
import com.pedro.streamer.utils.toast
import com.pedro.streamer.utils.updateMenuColor

/**
* Example code to stream the device screen.
Expand All @@ -49,8 +54,8 @@ import com.pedro.streamer.utils.toast
class ScreenActivity : AppCompatActivity(), ConnectChecker {

private lateinit var button: ImageView
private lateinit var toggleAudio: ImageView
private lateinit var etUrl: EditText
private var currentAudioSource: MenuItem? = null

private val activityResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val data = result.data
Expand All @@ -76,21 +81,7 @@ class ScreenActivity : AppCompatActivity(), ConnectChecker {
setContentView(R.layout.activity_display)
button = findViewById(R.id.b_start_stop)
etUrl = findViewById(R.id.et_rtp_url)
toggleAudio = findViewById(R.id.b_toggleAudio)
toggleAudio.setOnClickListener {
val service = ScreenService.INSTANCE
when (service?.toggleAudioSource()) {
is InternalAudioSource -> {
toggleAudio.setImageResource(R.drawable.microphone_off_icon)
toast("Using internal audio source")
}
is MicrophoneSource -> {
toggleAudio.setImageResource(R.drawable.microphone_icon)
toast("Using microphone audio source")
}
else -> {}
}
}
val bRecord = findViewById<ImageView>(R.id.b_record)
val screenService = ScreenService.INSTANCE
//No streaming/recording start service
if (screenService == null) {
Expand All @@ -101,6 +92,11 @@ class ScreenActivity : AppCompatActivity(), ConnectChecker {
} else {
button.setImageResource(R.drawable.stream_icon)
}
if (screenService != null && screenService.isRecording()) {
bRecord.setImageResource(R.drawable.stop_icon)
} else {
bRecord.setImageResource(R.drawable.record_icon)
}
button.setOnClickListener {
val service = ScreenService.INSTANCE
if (service != null) {
Expand All @@ -113,6 +109,51 @@ class ScreenActivity : AppCompatActivity(), ConnectChecker {
}
}
}
bRecord.setOnClickListener {
ScreenService.INSTANCE?.toggleRecord { state ->
when (state) {
RecordController.Status.STARTED -> {
bRecord.setImageResource(R.drawable.pause_icon)
}
RecordController.Status.STOPPED -> {
bRecord.setImageResource(R.drawable.record_icon)
}
RecordController.Status.RECORDING -> {
bRecord.setImageResource(R.drawable.stop_icon)
}
else -> {}
}
}
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.screen_menu, menu)
val defaultAudioSource = when (ScreenService.INSTANCE?.getCurrentAudioSource()) {
is MicrophoneSource -> menu.findItem(R.id.audio_source_microphone)
is InternalAudioSource -> menu.findItem(R.id.audio_source_internal)
is MixAudioSource -> menu.findItem(R.id.audio_source_mix)
else -> menu.findItem(R.id.audio_source_microphone)
}
currentAudioSource = defaultAudioSource.updateMenuColor(this, currentAudioSource)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
try {
when (item.itemId) {
R.id.audio_source_microphone, R.id.audio_source_internal, R.id.audio_source_mix -> {
val service = ScreenService.INSTANCE
if (service != null) {
service.toggleAudioSource(item.itemId)
currentAudioSource = item.updateMenuColor(this, currentAudioSource)
}
}
}
} catch (e: IllegalArgumentException) {
toast("Change source error: ${e.message}")
}
return super.onOptionsItemSelected(item)
}

override fun onDestroy() {
Expand Down
79 changes: 65 additions & 14 deletions app/src/main/java/com/pedro/streamer/screen/ScreenService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,20 @@ import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.pedro.common.ConnectChecker
import com.pedro.library.base.recording.RecordController
import com.pedro.library.generic.GenericStream
import com.pedro.library.util.sources.audio.MixAudioSource
import com.pedro.library.util.sources.audio.AudioSource
import com.pedro.library.util.sources.audio.InternalAudioSource
import com.pedro.library.util.sources.audio.MicrophoneSource
import com.pedro.library.util.sources.video.NoVideoSource
import com.pedro.library.util.sources.video.ScreenSource
import com.pedro.streamer.R
import com.pedro.streamer.utils.PathUtils
import com.pedro.streamer.utils.toast
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale


/**
Expand All @@ -47,8 +53,8 @@ class ScreenService: Service(), ConnectChecker {

companion object {
private const val TAG = "DisplayService"
private const val channelId = "rtpDisplayStreamChannel"
const val notifyId = 123456
private const val CHANNEL_ID = "DisplayStreamChannel"
const val NOTIFY_ID = 123456
var INSTANCE: ScreenService? = null
}

Expand All @@ -67,13 +73,15 @@ class ScreenService: Service(), ConnectChecker {
private val isStereo = true
private val aBitrate = 128 * 1000
private var prepared = false
private var recordPath = ""
private var selectedAudioSource: Int = R.id.audio_source_microphone

override fun onCreate() {
super.onCreate()
Log.i(TAG, "RTP Display service create")
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_HIGH)
notificationManager?.createNotificationChannel(channel)
}
genericStream = GenericStream(baseContext, this, NoVideoSource(), MicrophoneSource()).apply {
Expand All @@ -82,7 +90,10 @@ class ScreenService: Service(), ConnectChecker {
}
prepared = try {
genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) &&
genericStream.prepareAudio(sampleRate, isStereo, aBitrate)
genericStream.prepareAudio(sampleRate, isStereo, aBitrate,
echoCanceler = true,
noiseSuppressor = true
)
} catch (e: IllegalArgumentException) {
false
}
Expand All @@ -91,7 +102,7 @@ class ScreenService: Service(), ConnectChecker {
}

private fun keepAliveTrick() {
val notification = NotificationCompat.Builder(this, channelId)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setSilent(true)
.setOngoing(false)
Expand Down Expand Up @@ -123,7 +134,7 @@ class ScreenService: Service(), ConnectChecker {
fun stopStream() {
if (genericStream.isStreaming) {
genericStream.stopStream()
notificationManager?.cancel(notifyId)
notificationManager?.cancel(NOTIFY_ID)
}
}

Expand Down Expand Up @@ -155,23 +166,63 @@ class ScreenService: Service(), ConnectChecker {
//You need to call it after prepareVideo to override the default value.
genericStream.getGlInterface().setCameraOrientation(0)
genericStream.changeVideoSource(screenSource)
toggleAudioSource(selectedAudioSource)
true
} catch (ignored: IllegalArgumentException) {
false
}
}

fun toggleAudioSource(): AudioSource {
if (genericStream.audioSource is InternalAudioSource) {
fun getCurrentAudioSource(): AudioSource = genericStream.audioSource

fun toggleAudioSource(itemId: Int) {
when (itemId) {
R.id.audio_source_microphone -> {
selectedAudioSource = R.id.audio_source_microphone
if (genericStream.audioSource is MicrophoneSource) return
genericStream.changeAudioSource(MicrophoneSource())
} else {
mediaProjection?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
genericStream.changeAudioSource(InternalAudioSource(it))
}
}
R.id.audio_source_internal -> {
selectedAudioSource = R.id.audio_source_internal
if (genericStream.audioSource is InternalAudioSource) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let { genericStream.changeAudioSource(InternalAudioSource(it)) }
} else {
throw IllegalArgumentException("You need min API 29+")
}
}
R.id.audio_source_mix -> {
selectedAudioSource = R.id.audio_source_mix
if (genericStream.audioSource is MixAudioSource) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let { genericStream.changeAudioSource(MixAudioSource(it).apply {
//Using audio mix the internal audio volume is higher than microphone. Increase microphone fix it.
microphoneVolume = 2f
}) }
} else {
throw IllegalArgumentException("You need min API 29+")
}
}
}
}

fun toggleRecord(state: (RecordController.Status) -> Unit) {
if (!genericStream.isRecording) {
val folder = PathUtils.getRecordPath()
if (!folder.exists()) folder.mkdir()
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
recordPath = "${folder.absolutePath}/${sdf.format(Date())}.mp4"
genericStream.startRecord(recordPath) { status ->
if (status == RecordController.Status.RECORDING) {
state(RecordController.Status.RECORDING)
}
}
return genericStream.audioSource
state(RecordController.Status.STARTED)
} else {
genericStream.stopRecord()
state(RecordController.Status.STOPPED)
PathUtils.updateGallery(this, recordPath)
}
}

fun startStream(endpoint: String) {
Expand Down
Loading

0 comments on commit 46be00e

Please sign in to comment.