How to use MediaProjection with AudioRecord in android Studio

61 views Asked by At

I'm developing an mobile App for my household. I want to give my family the possibilty, to send their smartphone sounds to several bluetooth speakers in the house. I thought, to send the sound from the phone via the wifi to a server in the house, which connects to the speakers and play the sound.

The connection to the server is already working, but when I accept the Streaming/Recording Dialog, the App Crashes with this error:

2023-10-02 16:12:35.160 9346-9346/de.gamingwave.homeapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: de.gamingwave.homeapp, PID: 9346
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { (has extras) }} to activity {de.gamingwave.homeapp/de.gamingwave.homeapp.musik.MusikActivity}: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4845)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
        at android.os.Parcel.createException(Parcel.java:2071)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.os.Parcel.readException(Parcel.java:1987)
        at android.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:231)
        at android.media.projection.MediaProjection.<init>(MediaProjection.java:58)
        at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:104)
        at de.gamingwave.homeapp.musik.MusikActivity.onActivityResult(MusikActivity.java:99)
        at android.app.Activity.dispatchActivityResult(Activity.java:8110)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4838)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886) 
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:214) 
        at android.app.ActivityThread.main(ActivityThread.java:7356) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:476)
        at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:135)
        at android.os.Binder.execTransactInternal(Binder.java:1021)
        at android.os.Binder.execTransact(Binder.java:994)

Here is my code:

Do you know what's wrong?

package de.gamingwave.homeapp.musik;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.*;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import de.gamingwave.homeapp.R;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class MusikActivity extends AppCompatActivity {
    private static final int SAMPLE_RATE = 44100;
    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);

    private AudioRecord audioRecord;
    private boolean isStreaming = false;
    private Socket socket;
    private OutputStream outputStream;

    private Button startButton;
    private Button stopButton;

    private static final int RECORD_AUDIO_PERMISSION_CODE = 101;

    public boolean rec = false;

    private MediaProjectionManager mediaProjectionManager;
    private MediaProjection mediaProjection;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_musik);

        Button rec_btn = findViewById(R.id.recording);





        rec_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isStreaming) {
                    new AudioStreamingTask().execute();
                    rec_btn.setText("Streaming stoppen");
                    rec_btn.setBackgroundColor(Color.RED);
                } else {
                    stopStreaming();
                    rec_btn.setText("Streaming starten");
                    rec_btn.setBackgroundColor(Color.GREEN);
                }
            }
        });

        // Überprüfen Sie die Berechtigung zur Aufnahme von Audio
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_CODE);
        }

        mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        // Starten Sie die Benutzeranforderung für die MediaProjection
        Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(permissionIntent, 1);
    }



    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);


            // Benutzer hat die Berechtigung erteilt
            mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);


            // Initialisieren Sie audioRecord hier
            audioRecord = createAudioRecord();

            // Starten Sie den MediaProjectionForegroundService und übergeben Sie die Media-Projektion
            startMediaProjection();

    }

    private void startMediaProjection() {
        // Erstellen Sie einen Intent für den Vordergrunddienst
        Intent serviceIntent = new Intent(this, MediaProjectionForegroundService.class);
        serviceIntent.setAction("start_media_projection");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }


    private void stopMediaProjection() {
        // Erstellen Sie einen Intent für den Vordergrunddienst, um die Media-Projektion zu stoppen
        Intent serviceIntent = new Intent(this, MediaProjectionForegroundService.class);
        serviceIntent.setAction("stop_media_projection");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }




    private AudioRecord createAudioRecord() {
        AudioRecord audio = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            AudioPlaybackCaptureConfiguration config;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                config = new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
                        .addMatchingUsage(AudioAttributes.USAGE_MEDIA)
                        .build();


                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
                    return null;
                }

                audio = new AudioRecord.Builder()
                        .setAudioPlaybackCaptureConfig(config)
                        .build();
            } else {
                Toast.makeText(this, "Error", Toast.LENGTH_LONG).show();
            }
        }
        return audio;
    }


    private void startStreaming() {
        if (!isStreaming) {
            try {
                socket = new Socket("IP-Address", 12345);
                outputStream = socket.getOutputStream();

                if (audioRecord != null) {
                    audioRecord.startRecording();
                    isStreaming = true;

                    // Starten Sie einen Hintergrundthread für die Echtzeitübertragung
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            sendAudioData();
                        }
                    }).start();
                } else {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MusikActivity.this, "Audioaufnahme konnte nicht initialisiert werden.", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    private void sendAudioData() {
        try {
            byte[] buffer = new byte[BUFFER_SIZE];
            while (isStreaming) {
                int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE);
                if (bytesRead != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void stopStreaming() {
        if (isStreaming) {
            isStreaming = false;
            try {
                if (audioRecord != null) {
                    audioRecord.stop();
                    audioRecord.release();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == RECORD_AUDIO_PERMISSION_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Berechtigung erteilt
            } else {
                Toast.makeText(this, "Aufnahmeberechtigung wurde verweigert.", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private class AudioStreamingTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            startStreaming();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            // Nach der Ausführung können Sie ggf. UI-Aktualisierungen hier vornehmen
        }
    }
}
0

There are 0 answers