From aa3358bbb6ba784b0b9b892234217f8ad29efbe9 Mon Sep 17 00:00:00 2001 From: LiuQiang Date: Wed, 29 Sep 2021 10:36:53 +0800 Subject: [PATCH] [FEAT] NMS-811 is done. --- .../customaudio/AudioRecordService.java | 160 +++++----- .../customaudio/CustomAudioSource.java | 292 +++++++++++------- .../layout/fragment_custom_audiorecord.xml | 74 ++++- .../app/src/main/res/values-zh/strings.xml | 3 + .../app/src/main/res/values/strings.xml | 4 + 5 files changed, 324 insertions(+), 209 deletions(-) diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/AudioRecordService.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/AudioRecordService.java index 81045090e..afd1cf895 100644 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/AudioRecordService.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/AudioRecordService.java @@ -3,12 +3,12 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; +import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.util.Log; @@ -16,51 +16,59 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; -import io.agora.api.example.MainActivity; +import io.agora.rtc.Constants.AudioExternalSourcePos; +import io.agora.rtc.RtcEngine; -public class AudioRecordService extends Service -{ +public class AudioRecordService extends Service { private static final String TAG = AudioRecordService.class.getSimpleName(); - private RecordThread thread; - private volatile boolean stopped; - - @Nullable + private Thread recordThread; + private RecordBinder recordBinder; + + /** + * Since v3.5.1 + * 根据实际需求,你可以将外部音频帧推送到音频采集后、编码前或本地播放前的位置。 + * 你可以多次调用该方法,将一个音频帧推送到多个位置或者将多个音频帧推送到不同位置。 + * 例如,在 KTV 场景中,你可以将歌声推送到音频采集后的位置,让歌声经过 SDK 音频模块的处理, + * 从而获取优质的音频体验;将伴奏推送到音频编码前的位置,让伴奏不受 SDK 音频模块的影响。 + * + * {@link io.agora.rtc.Constants.AudioExternalSourcePos} + * AUDIO_EXTERNAL_PLAYOUT_SOURCE(0) + * AUDIO_EXTERNAL_RECORD_SOURCE_PRE_PROCESS(1), + * AUDIO_EXTERNAL_RECORD_SOURCE_POST_PROCESS(2); + * + */ + public volatile int currentSourcePos = AudioExternalSourcePos.getValue(AudioExternalSourcePos.AUDIO_EXTERNAL_PLAYOUT_SOURCE); + + public RtcEngine engine = null; @Override - public IBinder onBind(Intent intent) - { - return null; + public void onCreate() { + super.onCreate(); + recordThread = new RecordThread(); + recordBinder = new RecordBinder(); + startForeground(); } + @Nullable @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - startForeground(); - startRecording(); - return Service.START_STICKY; + public IBinder onBind(Intent intent) { + return recordBinder; } - private void startForeground() - { - createNotificationChannel(); - Intent notificationIntent = new Intent(this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(this, - 0, notificationIntent, 0); + private void startForeground() { + createNotificationChannel(); Notification notification = new NotificationCompat.Builder(this, TAG) .setContentTitle(TAG) - .setContentIntent(pendingIntent) .build(); startForeground(1, notification); } - private void createNotificationChannel() - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - { + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel serviceChannel = new NotificationChannel( - TAG, TAG, NotificationManager.IMPORTANCE_DEFAULT + TAG, TAG, NotificationManager.IMPORTANCE_HIGH ); NotificationManager manager = getSystemService(NotificationManager.class); @@ -68,35 +76,27 @@ private void createNotificationChannel() } } - private void startRecording() - { - thread = new RecordThread(); - thread.start(); - } - - private void stopRecording() - { - stopped = true; + public void startRecording() { + recordThread.start(); } @Override - public void onDestroy() - { - stopRecording(); + public void onDestroy() { super.onDestroy(); + recordThread.interrupt(); } - public class RecordThread extends Thread - { - private AudioRecord audioRecord; - public static final int DEFAULT_SAMPLE_RATE = 16000; - /**1 corresponds to AudioFormat.CHANNEL_IN_MONO; - * 2 corresponds to AudioFormat.CHANNEL_IN_STEREO*/ + public class RecordThread extends Thread { + private final AudioRecord audioRecord; + public static final int DEFAULT_SAMPLE_RATE = 44100; + /** + * 1 corresponds to AudioFormat.CHANNEL_IN_MONO; + * 2 corresponds to AudioFormat.CHANNEL_IN_STEREO + */ public static final int DEFAULT_CHANNEL_COUNT = 1, DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO; private byte[] buffer; - RecordThread() - { + RecordThread() { int bufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG, AudioFormat.ENCODING_PCM_16BIT); audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_COUNT, @@ -105,46 +105,24 @@ public class RecordThread extends Thread } @Override - public void run() - { - try - { - audioRecord.startRecording(); - while (!stopped) - { - int result = audioRecord.read(buffer, 0, buffer.length); - if (result >= 0) - { - /**Pushes the external audio frame to the Agora SDK for encoding. - * @param data External audio data to be pushed. - * @param timeStamp Timestamp of the external audio frame. It is mandatory. - * You can use this parameter for the following purposes: - * 1:Restore the order of the captured audio frame. - * 2:Synchronize audio and video frames in video-related - * scenarios, including scenarios where external video sources are used. - * @return - * 0: Success. - * < 0: Failure.*/ - CustomAudioSource.engine.pushExternalAudioFrame( - buffer, System.currentTimeMillis()); - } - else - { - logRecordError(result); - } - Log.d(TAG, "byte size is :" + result); + public void run() { + audioRecord.startRecording(); + while (!isInterrupted()) { + int result = audioRecord.read(buffer, 0, buffer.length); + if (result > 0 && engine != null) { + engine.pushExternalAudioFrame(buffer, System.currentTimeMillis(), DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_COUNT + , AudioFormat.ENCODING_PCM_16BIT, AudioRecordService.this.currentSourcePos); + } else { + logRecordError(result); } - release(); + Log.d(TAG, "byte size is :" + result); } - catch (Exception e) - {e.printStackTrace();} + release(); } - private void logRecordError(int error) - { + private void logRecordError(int error) { String message = ""; - switch (error) - { + switch (error) { case AudioRecord.ERROR: message = "generic operation failure"; break; @@ -161,13 +139,19 @@ private void logRecordError(int error) Log.e(TAG, message); } - private void release() - { - if (audioRecord != null) - { + private void release() { + if (audioRecord != null) { audioRecord.stop(); - buffer = null; + audioRecord.release(); } + buffer = null; + } + } + + public class RecordBinder extends Binder { + + public AudioRecordService getService(){ + return AudioRecordService.this; } } } diff --git a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java index b500607ea..eb06e7ba1 100755 --- a/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java +++ b/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java @@ -1,12 +1,15 @@ package io.agora.api.example.examples.advanced.customaudio; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.media.AudioFormat; import android.media.AudioManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -14,6 +17,8 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; +import android.widget.RadioGroup; +import android.widget.SeekBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,6 +31,7 @@ import io.agora.api.example.common.BaseFragment; import io.agora.api.example.utils.CommonUtil; import io.agora.rtc.Constants; +import io.agora.rtc.Constants.AudioExternalSourcePos; import io.agora.rtc.IRtcEngineEventHandler; import io.agora.rtc.RtcEngine; import io.agora.rtc.models.ChannelMediaOptions; @@ -34,7 +40,11 @@ import static io.agora.api.example.examples.advanced.customaudio.AudioRecordService.RecordThread.DEFAULT_CHANNEL_COUNT; import static io.agora.api.example.examples.advanced.customaudio.AudioRecordService.RecordThread.DEFAULT_SAMPLE_RATE; -/**This demo demonstrates how to make a one-to-one voice call*/ +import java.util.Locale; + +/** + * This demo demonstrates how to make a one-to-one voice call + */ @Example( index = 8, group = ADVANCED, @@ -42,58 +52,104 @@ actionId = R.id.action_mainFragment_to_CustomAudioSource, tipsId = R.string.customaudio ) -public class CustomAudioSource extends BaseFragment implements View.OnClickListener -{ +public class CustomAudioSource extends BaseFragment implements View.OnClickListener, SeekBar.OnSeekBarChangeListener { private static final String TAG = CustomAudioSource.class.getSimpleName(); private EditText et_channel; private Button mute, join; - private int myUid; private boolean joined = false; - public static RtcEngine engine; + public RtcEngine engine; private static final Integer SAMPLE_RATE = 44100; private static final Integer SAMPLE_NUM_OF_CHANNEL = 2; private AudioPlayer mAudioPlayer; - private int bufferSize = 88200; - private byte[] data = new byte[bufferSize]; + private final int bufferSize = 88200; + private final byte[] data = new byte[bufferSize]; + + private RadioGroup groupAudioSource; + private SeekBar localSlider; + private SeekBar preProcessSlider; + private SeekBar postProcessSlider; + + private AudioRecordService recordService = null; + private boolean isServiceBind = false; + private final ServiceConnection recordConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + recordService = ((AudioRecordService.RecordBinder) service).getService(); + changeAudioSourcePos(groupAudioSource,groupAudioSource.getCheckedRadioButtonId()); + recordService.engine = engine; + recordService.startRecording(); + isServiceBind = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + isServiceBind = false; + } + }; @Override - public void onCreate(@Nullable Bundle savedInstanceState) - { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new Handler(); } @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.fragment_custom_audiorecord, container, false); - return view; + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_custom_audiorecord, container, false); } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) - { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); join = view.findViewById(R.id.btn_join); et_channel = view.findViewById(R.id.et_channel); view.findViewById(R.id.btn_join).setOnClickListener(this); mute = view.findViewById(R.id.btn_mute); mute.setOnClickListener(this); + + localSlider = view.findViewById(R.id.volume_local_slider_fg_custom_audio); + preProcessSlider = view.findViewById(R.id.volume_pre_process_slider_fg_custom_audio); + postProcessSlider = view.findViewById(R.id.volume_post_process_slider_fg_custom_audio); + + groupAudioSource = view.findViewById(R.id.group_source_local_fg_custom_audio); + + groupAudioSource.setOnCheckedChangeListener(this::changeAudioSourcePos); + localSlider.setOnSeekBarChangeListener(this); + preProcessSlider.setOnSeekBarChangeListener(this); + postProcessSlider.setOnSeekBarChangeListener(this); + } + + private void changeAudioSourcePos(RadioGroup group, int checkedId) { + AudioExternalSourcePos desiredPos; + switch (group.indexOfChild(group.findViewById(checkedId))) { + case 0: + desiredPos = AudioExternalSourcePos.AUDIO_EXTERNAL_PLAYOUT_SOURCE; + break; + case 1: + desiredPos = AudioExternalSourcePos.AUDIO_EXTERNAL_RECORD_SOURCE_PRE_PROCESS; + break; + default: + desiredPos = AudioExternalSourcePos.AUDIO_EXTERNAL_RECORD_SOURCE_POST_PROCESS; + break; + } + if (recordService != null) + recordService.currentSourcePos = getValue(desiredPos); + } + + private int getValue(AudioExternalSourcePos sourcePos) { + return AudioExternalSourcePos.getValue(sourcePos); } @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) - { + public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Check if the context is valid Context context = getContext(); - if (context == null) - { + if (context == null) { return; } - try - { + try { /**Creates an RtcEngine instance. * @param context The context of Android Activity * @param appId The App ID issued to you by Agora. See @@ -104,49 +160,41 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) iRtcEngineEventHandler); // Notify the SDK that you want to use the external audio sink. - engine.setExternalAudioSink( - true, // Enable the external audio sink. - SAMPLE_RATE, // Set the audio sample rate as 8k, 16k, 32k, 44.1k or 48kHz. - SAMPLE_NUM_OF_CHANNEL // Number of channels. The maximum number is 2. - ); - mAudioPlayer = new AudioPlayer(AudioManager.STREAM_VOICE_CALL, SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL, AudioFormat.ENCODING_PCM_16BIT); - } - catch (Exception e) - { +// engine.setExternalAudioSink( +// true, // Enable the external audio sink. +// SAMPLE_RATE, // Set the audio sample rate as 8k, 16k, 32k, 44.1k or 48kHz. +// SAMPLE_NUM_OF_CHANNEL // Number of channels. The maximum number is 2. +// ); +// mAudioPlayer = new AudioPlayer(AudioManager.STREAM_VOICE_CALL, SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL, AudioFormat.ENCODING_PCM_16BIT); + } catch (Exception e) { e.printStackTrace(); - getActivity().onBackPressed(); + requireActivity().onBackPressed(); } } @Override - public void onDestroy() - { + public void onDestroy() { super.onDestroy(); stopAudioRecord(); /**leaveChannel and Destroy the RtcEngine instance*/ - if(engine != null) - { + if (engine != null) { engine.leaveChannel(); } - handler.post(RtcEngine::destroy); + handler.postAtFrontOfQueue(RtcEngine::destroy); engine = null; - mAudioPlayer.stopPlayer(); - playerTask.cancel(true); +// mAudioPlayer.stopPlayer(); +// playerTask.cancel(true); } @Override - public void onClick(View v) - { - if (v.getId() == R.id.btn_join) - { - if (!joined) - { - CommonUtil.hideInputBoard(getActivity(), et_channel); + public void onClick(View v) { + if (v.getId() == R.id.btn_join) { + if (!joined) { + CommonUtil.hideInputBoard(requireActivity(), et_channel); // call when join button hit String channelId = et_channel.getText().toString(); // Check permission - if (AndPermission.hasPermissions(this, Permission.Group.STORAGE, Permission.Group.MICROPHONE, Permission.Group.CAMERA)) - { + if (AndPermission.hasPermissions(this, Permission.Group.STORAGE, Permission.Group.MICROPHONE, Permission.Group.CAMERA)) { joinChannel(channelId); return; } @@ -159,9 +207,7 @@ public void onClick(View v) // Permissions Granted joinChannel(channelId); }).start(); - } - else - { + } else { joined = false; stopAudioRecord(); /**After joining a channel, the user must call the leaveChannel method to end the @@ -186,9 +232,7 @@ public void onClick(View v) mute.setText(getString(R.string.closemicrophone)); mute.setEnabled(false); } - } - else if (v.getId() == R.id.btn_mute) - { + } else if (v.getId() == R.id.btn_mute) { mute.setActivated(!mute.isActivated()); mute.setText(getString(mute.isActivated() ? R.string.openmicrophone : R.string.closemicrophone)); /**Turn off / on the microphone, stop / start local audio collection and push streaming.*/ @@ -198,9 +242,9 @@ else if (v.getId() == R.id.btn_mute) /** * @param channelId Specify the channel name that you want to join. - * Users that input the same channel name join the same channel.*/ - private void joinChannel(String channelId) - { + * Users that input the same channel name join the same channel. + */ + private void joinChannel(String channelId) { /** Sets the channel profile of the Agora RtcEngine. CHANNEL_PROFILE_COMMUNICATION(0): (Default) The Communication profile. Use this profile in one-on-one calls or group calls, where all users can talk freely. @@ -230,8 +274,7 @@ private void joinChannel(String channelId) * A token generated at the server. This applies to scenarios with high-security requirements. For details, see * https://docs.agora.io/en/cloud-recording/token_server_java?platform=Java*/ String accessToken = getString(R.string.agora_access_token); - if (TextUtils.equals(accessToken, "") || TextUtils.equals(accessToken, "<#YOUR ACCESS TOKEN#>")) - { + if (TextUtils.equals(accessToken, "") || TextUtils.equals(accessToken, "<#YOUR ACCESS TOKEN#>")) { accessToken = null; } /** Allows a user to join a channel. @@ -241,8 +284,7 @@ private void joinChannel(String channelId) option.autoSubscribeAudio = true; option.autoSubscribeVideo = false; int res = engine.joinChannel(accessToken, channelId, "Extra Optional Data", 0, option); - if (res != 0) - { + if (res != 0) { // Usually happens with invalid parameters // Error code description can be found at: // en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html @@ -254,60 +296,60 @@ private void joinChannel(String channelId) join.setEnabled(false); } - private void startAudioRecord() - { + private void startAudioRecord() { Intent intent = new Intent(getContext(), AudioRecordService.class); - getActivity().startService(intent); + requireActivity().bindService(intent, recordConnection, Context.BIND_AUTO_CREATE); } - private void stopAudioRecord() - { - Intent intent = new Intent(getContext(), AudioRecordService.class); - getActivity().stopService(intent); + private void stopAudioRecord() { + if (isServiceBind) { + isServiceBind = false; + requireActivity().unbindService(recordConnection); + } } - private final AsyncTask playerTask = new AsyncTask() { - @Override - protected Object doInBackground(Object[] objects) { - while (true) { - if (engine != null) { - /** - * Pulls the remote audio frame. - * Before calling this method, call the setExternalAudioSink(enabled: true) method to enable and set the external audio sink. - * After a successful method call, the app pulls the decoded and mixed audio data for playback. - * @Param data: The audio data that you want to pull. The data format is in byte[]. - * @Param lengthInByte: The data length (byte) of the external audio data. The value of this parameter is related to the audio duration, - * and the values of the sampleRate and channels parameters that you set in setExternalAudioSink. Agora recommends setting the audio duration no shorter than 10 ms. - * The formula for lengthInByte is: - * lengthInByte = sampleRate/1000 × 2 × channels × audio duration (ms). - */ - if(engine.pullPlaybackAudioFrame(data, bufferSize) == 0){ - mAudioPlayer.play(data, 0, data.length); - } - } - } - } - }; +// private final AsyncTask playerTask = new AsyncTask() { +// @Override +// protected Object doInBackground(Object[] objects) { +// while (true) { +// if (engine != null) { +// /** +// * Pulls the remote audio frame. +// * Before calling this method, call the setExternalAudioSink(enabled: true) method to enable and set the external audio sink. +// * After a successful method call, the app pulls the decoded and mixed audio data for playback. +// * @Param data: The audio data that you want to pull. The data format is in byte[]. +// * @Param lengthInByte: The data length (byte) of the external audio data. The value of this parameter is related to the audio duration, +// * and the values of the sampleRate and channels parameters that you set in setExternalAudioSink. Agora recommends setting the audio duration no shorter than 10 ms. +// * The formula for lengthInByte is: +// * lengthInByte = sampleRate/1000 × 2 × channels × audio duration (ms). +// */ +// if (engine.pullPlaybackAudioFrame(data, bufferSize) == 0) { +// mAudioPlayer.play(data, 0, data.length); +// } +// } +// } +// } +// }; - /**IRtcEngineEventHandler is an abstract class providing default implementation. - * The SDK uses this class to report to the app on SDK runtime events.*/ - private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() - { + /** + * IRtcEngineEventHandler is an abstract class providing default implementation. + * The SDK uses this class to report to the app on SDK runtime events. + */ + private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler() { /**Reports a warning during SDK runtime. * Warning code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_warn_code.html*/ @Override - public void onWarning(int warn) - { + public void onWarning(int warn) { Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn))); } /**Reports an error during SDK runtime. * Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/ @Override - public void onError(int err) - { - Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err))); - showAlert(String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err))); + public void onError(int err) { + String msg = String.format(Locale.getDefault(), "onError code %d message %s", err, RtcEngine.getErrorDescription(err)); + Log.e(TAG, msg); + showAlert(msg); } /**Occurs when the local user joins a specified channel. @@ -317,21 +359,18 @@ public void onError(int err) * @param uid User ID * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/ @Override - public void onJoinChannelSuccess(String channel, int uid, int elapsed) - { - Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid)); - showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid)); - myUid = uid; + public void onJoinChannelSuccess(String channel, int uid, int elapsed) { + String msg = String.format(Locale.getDefault(), "onJoinChannelSuccess channel %s uid %d", channel, uid); + Log.i(TAG, msg); + showLongToast(msg); joined = true; - handler.post(new Runnable() - { - @Override - public void run() - { - mute.setEnabled(true); - join.setEnabled(true); - join.setText(getString(R.string.leave)); - } + handler.post(() -> { + mute.setEnabled(true); + join.setEnabled(true); + join.setText(getString(R.string.leave)); + localSlider.setProgress(localSlider.getMax()); + preProcessSlider.setProgress(preProcessSlider.getMax()); + postProcessSlider.setProgress(postProcessSlider.getMax()); }); startAudioRecord(); } @@ -369,8 +408,7 @@ public void run() * @param elapsed Time elapsed (ms) from the local user calling the joinChannel method * until the SDK triggers this callback.*/ @Override - public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapsed) - { + public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapsed) { super.onRemoteAudioStateChanged(uid, state, reason, elapsed); Log.i(TAG, "onRemoteAudioStateChanged->" + uid + ", state->" + state + ", reason->" + reason); } @@ -378,7 +416,29 @@ public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapse @Override public void onUserJoined(int uid, int elapsed) { mAudioPlayer.startPlayer(); - playerTask.execute(); +// playerTask.execute(); } }; + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + int value; + if (seekBar == localSlider) + value = getValue(AudioExternalSourcePos.AUDIO_EXTERNAL_PLAYOUT_SOURCE); + else if (seekBar == preProcessSlider) + value = getValue(AudioExternalSourcePos.AUDIO_EXTERNAL_RECORD_SOURCE_PRE_PROCESS); + else + value = getValue(AudioExternalSourcePos.AUDIO_EXTERNAL_RECORD_SOURCE_POST_PROCESS); + engine.setExternalAudioSourceVolume(value, seekBar.getProgress()); + } } diff --git a/Android/APIExample/app/src/main/res/layout/fragment_custom_audiorecord.xml b/Android/APIExample/app/src/main/res/layout/fragment_custom_audiorecord.xml index 2b5d101c0..a1844c731 100644 --- a/Android/APIExample/app/src/main/res/layout/fragment_custom_audiorecord.xml +++ b/Android/APIExample/app/src/main/res/layout/fragment_custom_audiorecord.xml @@ -6,16 +6,80 @@ android:fitsSystemWindows="true" tools:context=".examples.basic.JoinChannelAudio"> + + + + + + + + + + + + + + + + android:text="@string/closemicrophone" /> 音效控制 音效音量 混音 + 本地 + SDK处理前 + SDK处理后 \ No newline at end of file diff --git a/Android/APIExample/app/src/main/res/values/strings.xml b/Android/APIExample/app/src/main/res/values/strings.xml index e2edb2ca8..206a9b9d4 100644 --- a/Android/APIExample/app/src/main/res/values/strings.xml +++ b/Android/APIExample/app/src/main/res/values/strings.xml @@ -144,4 +144,8 @@ Effect Control Audio Effects Vol Audio Mixing + + PlayOut + PreProcess + PostProcess