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

creating AudioMixSource #1571

Merged
merged 7 commits into from
Sep 13, 2024
Merged
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
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
Loading