1.在AndroidManifest.xml文件中,添加权限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.PROJECTION_SERVICE" />
并在Application中增加:android:allowAudioPlaybackCapture="true"
2、配置Service
<service
android:name=".service.MediaProjectService"
android:foregroundServiceType="mediaProjection" />
3、Service的详细代码,包括声音内录确认框,开始录音,六秒后停止录音,并将录制的pcm文件转换成wav文件
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioPlaybackCaptureConfiguration;
import android.media.AudioRecord;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.kgzn.musicrecognition.IdentifyProtocolV1;
import com.kgzn.musicrecognition.MainActivity;
import com.kgzn.musicrecognition.R;
import com.kgzn.musicrecognition.adapter.MusicItemAdapter;
import com.kgzn.musicrecognition.bean.MusicInfo;
import com.kgzn.musicrecognition.constant.ServiceConstant;
import com.kgzn.musicrecognition.dialog.MusicRecognitionDialog;
import com.kgzn.musicrecognition.util.LogUtil;
import com.kgzn.musicrecognition.util.PcmToWavConverter;
import com.kgzn.toastsdk.KgznToast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author As06898
* @Description: 声音内录
*/
public class MediaProjectService extends Service implements IdentifyProtocolV1.OnRecognitionListener{
private static final String TAG = "MediaProjectService";
private static final int NOTIFICATION_ID = 1;
private static final String CHANNEL_ID = "MediaProjectionChannel";
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
private AudioRecord audioRecord;
private FileOutputStream outputStream;
private boolean isRecording;
private Thread recordingThread;
private String fileName;
private List<MusicInfo> musicList;
private MusicRecognitionDialog musicRecognitionDialog;
private boolean isStop = false;
@Override
public void onCreate() {
super.onCreate();
musicList = new ArrayList<>();
mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
createNotificationChannel();
startForeground(NOTIFICATION_ID, createNotification());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtil.d(TAG, "onStartCommand");
if (intent != null && intent.hasExtra("requestCode") && intent.hasExtra("resultCode") && intent.hasExtra("data")) {
int requestCode = intent.getIntExtra("requestCode", -1);
int resultCode = intent.getIntExtra("resultCode", -1);
Intent data = intent.getParcelableExtra("data");
handleActivityResult(requestCode, resultCode, data);
}
return START_NOT_STICKY;
}
private void createNotificationChannel() {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Media Projection Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
private Notification createNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Media Projection Service")
.setContentText("Recording in progress...")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.build();
}
private void handleActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1234) { // 假设请求码为1234
startMediaProjection(resultCode, data);
}
}
private void startMediaProjection(int resultCode, Intent data) {
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
startRecording();
}
private void startRecording() {
if (isRecording) {
return;
}
int bufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
return;
}
AudioPlaybackCaptureConfiguration config = new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
.addMatchingUsage(AudioAttributes.USAGE_GAME)
.build();
audioRecord = new AudioRecord.Builder()
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.setBufferSizeInBytes(bufferSize)
.setAudioPlaybackCaptureConfig(config)
.build();
}
try {
String uuid = UUID.randomUUID().toString();
fileName = "audio_" + uuid;
File file = new File(getExternalFilesDir(null), fileName + ".pcm");
outputStream = new FileOutputStream(file);
} catch (IOException e) {
e.printStackTrace();
return;
}
isRecording = true;
recordingThread = new Thread(new Runnable() {
@Override
public void run() {
byte[] buffer = new byte[bufferSize];
while (isRecording) {
int readSize = audioRecord.read(buffer, 0, bufferSize);
if (readSize > 0) {
try {
outputStream.write(buffer, 0, readSize);
} catch (IOException e) {
e.printStackTrace();
}
}
}
audioRecord.stop();
audioRecord.release();
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
audioRecord.startRecording();
recordingThread.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopRecording();
}
}, 6000);
}
private void stopRecording() {
if (!isRecording) {
return;
}
isRecording = false;
try {
recordingThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
File pcmFile = new File(getExternalFilesDir(null), fileName + ".pcm");
File wavFile = new File(getExternalFilesDir(null), fileName + ".wav");
try {
if(!ServiceConstant.SERVICE_STOP){
PcmToWavConverter.convertPcmToWav(pcmFile, wavFile, 44100, 1, 16);
} catch (IOException e) {
e.printStackTrace();
}
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
4、在MainActivity的onActivityResult方法中获取requestCode和resultCode,并启动服务。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
startMediaProjectService(requestCode, resultCode, data);
} else {
// Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
// finish(); // 关闭当前 Activity
finishAffinity(); // 关闭所有相关的 Activity
System.exit(0); // 强制退出应用
}
}
}
private void startMediaProjectService(int requestCode, int resultCode, Intent data) {
Intent serviceIntent = new Intent(this, MediaProjectService.class);
serviceIntent.putExtra("requestCode", requestCode);
serviceIntent.putExtra("resultCode", resultCode);
serviceIntent.putExtra("data", data);
startService(serviceIntent);
}
5、将pcm音频文件转为wav文件的工具类
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class PcmToWavConverter {
public static void convertPcmToWav(File pcmFile, File wavFile, int sampleRate, int channelCount, int bitDepth) throws IOException {
byte[] header = generateWavHeader(sampleRate, channelCount, bitDepth, (int) pcmFile.length());
FileInputStream in = new FileInputStream(pcmFile);
FileOutputStream out = new FileOutputStream(wavFile);
// Write the WAV header
out.write(header, 0, header.length);
// Copy the PCM data
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
out.close();
}
private static byte[] generateWavHeader(int sampleRate, int channelCount, int bitDepth, int dataSize) {
int byteRate = sampleRate * channelCount * (bitDepth / 8);
long totalDataSize = 36 + dataSize;
byte[] header = new byte[44];
// RIFF chunk descriptor
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataSize & 0xff);
header[5] = (byte) ((totalDataSize >> 8) & 0xff);
header[6] = (byte) ((totalDataSize >> 16) & 0xff);
header[7] = (byte) ((totalDataSize >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
// FMT sub-chunk
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channelCount;
header[23] = 0;
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * channelCount); // block align
header[33] = 0;
header[34] = (byte) bitDepth; // bits per sample
header[35] = 0;
// Data sub-chunk
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (dataSize & 0xff);
header[41] = (byte) ((dataSize >> 8) & 0xff);
header[42] = (byte) ((dataSize >> 16) & 0xff);
header[43] = (byte) ((dataSize >> 24) & 0xff);
return header;
}
}