1212import android .widget .CompoundButton ;
1313import android .widget .EditText ;
1414import android .widget .Switch ;
15+ import android .os .Process ;
1516
1617import androidx .annotation .NonNull ;
1718import androidx .annotation .Nullable ;
3536
3637import static io .agora .api .example .common .model .Examples .ADVANCED ;
3738
38- /**This demo demonstrates how to make a one-to-one voice call*/
39+ /**
40+ * This demo demonstrates how to make a one-to-one voice call
41+ */
3942@ Example (
4043 index = 8 ,
4144 group = ADVANCED ,
4245 name = R .string .item_customaudiosource ,
4346 actionId = R .id .action_mainFragment_to_CustomAudioSource ,
4447 tipsId = R .string .customaudio
4548)
46- public class CustomAudioSource extends BaseFragment implements View .OnClickListener , CompoundButton .OnCheckedChangeListener
47- {
49+ public class CustomAudioSource extends BaseFragment implements View .OnClickListener , CompoundButton .OnCheckedChangeListener {
4850 private static final String TAG = CustomAudioSource .class .getSimpleName ();
4951 private EditText et_channel ;
5052 private Button join ;
@@ -54,17 +56,19 @@ public class CustomAudioSource extends BaseFragment implements View.OnClickListe
5456 private Switch mic , pcm ;
5557 private ChannelMediaOptions option = new ChannelMediaOptions ();
5658 private static final String AUDIO_FILE = "output.raw" ;
57- private static final Integer SAMPLE_RATE = 16000 ;
58- private static final Integer SAMPLE_NUM_OF_CHANNEL = 1 ;
59- private static final Integer SAMPLES = 1024 ;
60- private static final Integer BUFFER_SIZE = SAMPLES * SAMPLE_NUM_OF_CHANNEL * 2 ;
59+ private static final Integer SAMPLE_RATE = 44100 ;
60+ private static final Integer SAMPLE_NUM_OF_CHANNEL = 2 ;
61+ private static final Integer BITS_PER_SAMPLE = 16 ;
62+ private static final Integer SAMPLES = 441 ;
63+ private static final Integer BUFFER_SIZE = SAMPLES * BITS_PER_SAMPLE / 8 * SAMPLE_NUM_OF_CHANNEL ;
64+ private static final Integer PUSH_INTERVAL = SAMPLES * 1000 / SAMPLE_RATE ;
6165
6266 private InputStream inputStream ;
6367 private Thread pushingTask = new Thread (new PushingTask ());
68+ private boolean pushing = false ;
6469
6570 @ Override
66- public void onCreate (@ Nullable Bundle savedInstanceState )
67- {
71+ public void onCreate (@ Nullable Bundle savedInstanceState ) {
6872 super .onCreate (savedInstanceState );
6973 handler = new Handler ();
7074 initMediaOption ();
@@ -79,27 +83,27 @@ private void initMediaOption() {
7983 option .enableAudioRecordingOrPlayout = true ;
8084 }
8185
82- private void openAudioFile (){
86+ private void openAudioFile () {
8387 try {
8488 inputStream = this .getResources ().getAssets ().open (AUDIO_FILE );
8589 } catch (IOException e ) {
8690 e .printStackTrace ();
8791 }
8892 }
8993
90- private void closeAudioFile (){
94+ private void closeAudioFile () {
9195 try {
9296 inputStream .close ();
9397 } catch (IOException e ) {
9498 e .printStackTrace ();
9599 }
96100 }
97101
98- private byte [] readBuffer (){
102+ private byte [] readBuffer () {
99103 int byteSize = BUFFER_SIZE ;
100104 byte [] buffer = new byte [byteSize ];
101105 try {
102- if (inputStream .read (buffer ) < 0 ){
106+ if (inputStream .read (buffer ) < 0 ) {
103107 inputStream .reset ();
104108 return readBuffer ();
105109 }
@@ -111,15 +115,13 @@ private byte[] readBuffer(){
111115
112116 @ Nullable
113117 @ Override
114- public View onCreateView (@ NonNull LayoutInflater inflater , @ Nullable ViewGroup container , @ Nullable Bundle savedInstanceState )
115- {
118+ public View onCreateView (@ NonNull LayoutInflater inflater , @ Nullable ViewGroup container , @ Nullable Bundle savedInstanceState ) {
116119 View view = inflater .inflate (R .layout .fragment_custom_audiorecord , container , false );
117120 return view ;
118121 }
119122
120123 @ Override
121- public void onViewCreated (@ NonNull View view , @ Nullable Bundle savedInstanceState )
122- {
124+ public void onViewCreated (@ NonNull View view , @ Nullable Bundle savedInstanceState ) {
123125 super .onViewCreated (view , savedInstanceState );
124126 join = view .findViewById (R .id .btn_join );
125127 et_channel = view .findViewById (R .id .et_channel );
@@ -131,17 +133,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
131133 }
132134
133135 @ Override
134- public void onActivityCreated (@ Nullable Bundle savedInstanceState )
135- {
136+ public void onActivityCreated (@ Nullable Bundle savedInstanceState ) {
136137 super .onActivityCreated (savedInstanceState );
137138 // Check if the context is valid
138139 Context context = getContext ();
139- if (context == null )
140- {
140+ if (context == null ) {
141141 return ;
142142 }
143- try
144- {
143+ try {
145144 RtcEngineConfig config = new RtcEngineConfig ();
146145 /**
147146 * The context of Android Activity
@@ -166,22 +165,18 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState)
166165 config .mAudioScenario = Constants .AudioScenario .getValue (Constants .AudioScenario .HIGH_DEFINITION );
167166 engine = (RtcEngineEx ) RtcEngine .create (config );
168167 openAudioFile ();
169- }
170- catch (Exception e )
171- {
168+ } catch (Exception e ) {
172169 e .printStackTrace ();
173170 getActivity ().onBackPressed ();
174171 }
175172 }
176173
177174 @ Override
178- public void onDestroy ()
179- {
175+ public void onDestroy () {
180176 super .onDestroy ();
177+ pushing = false ;
181178 /**leaveChannel and Destroy the RtcEngine instance*/
182- pushingTask .interrupt ();
183- if (engine != null )
184- {
179+ if (engine != null ) {
185180 engine .leaveChannel ();
186181 }
187182 handler .post (RtcEngine ::destroy );
@@ -192,22 +187,17 @@ public void onDestroy()
192187
193188 @ Override
194189 public void onCheckedChanged (CompoundButton compoundButton , boolean b ) {
195- if (compoundButton .getId () == R .id .microphone )
196- {
197- if (b ){
190+ if (compoundButton .getId () == R .id .microphone ) {
191+ if (b ) {
198192 option .publishAudioTrack = true ;
199- }
200- else {
193+ } else {
201194 option .publishAudioTrack = false ;
202195 }
203196 engine .updateChannelMediaOptions (option );
204- }
205- else if (compoundButton .getId () == R .id .localAudio )
206- {
207- if (b ){
197+ } else if (compoundButton .getId () == R .id .localAudio ) {
198+ if (b ) {
208199 option .publishCustomAudioTrack = true ;
209- }
210- else {
200+ } else {
211201 option .publishCustomAudioTrack = false ;
212202 }
213203 engine .updateChannelMediaOptions (option );
@@ -216,18 +206,14 @@ else if(compoundButton.getId() == R.id.localAudio)
216206
217207
218208 @ Override
219- public void onClick (View v )
220- {
221- if (v .getId () == R .id .btn_join )
222- {
223- if (!joined )
224- {
209+ public void onClick (View v ) {
210+ if (v .getId () == R .id .btn_join ) {
211+ if (!joined ) {
225212 CommonUtil .hideInputBoard (getActivity (), et_channel );
226213 // call when join button hit
227214 String channelId = et_channel .getText ().toString ();
228215 // Check permission
229- if (AndPermission .hasPermissions (this , Permission .Group .STORAGE , Permission .Group .MICROPHONE , Permission .Group .CAMERA ))
230- {
216+ if (AndPermission .hasPermissions (this , Permission .Group .STORAGE , Permission .Group .MICROPHONE , Permission .Group .CAMERA )) {
231217 joinChannel (channelId );
232218 return ;
233219 }
@@ -240,9 +226,7 @@ public void onClick(View v)
240226 // Permissions Granted
241227 joinChannel (channelId );
242228 }).start ();
243- }
244- else
245- {
229+ } else {
246230 joined = false ;
247231 /**After joining a channel, the user must call the leaveChannel method to end the
248232 * call before joining another channel. This method returns 0 if the user leaves the
@@ -262,6 +246,7 @@ public void onClick(View v)
262246 * 2:If you call the leaveChannel method during CDN live streaming, the SDK
263247 * triggers the removeInjectStreamUrl method.*/
264248 engine .leaveChannel ();
249+ pushing = false ;
265250 join .setText (getString (R .string .join ));
266251 mic .setEnabled (false );
267252 pcm .setEnabled (false );
@@ -271,9 +256,9 @@ public void onClick(View v)
271256
272257 /**
273258 * @param channelId Specify the channel name that you want to join.
274- * Users that input the same channel name join the same channel.*/
275- private void joinChannel ( String channelId )
276- {
259+ * Users that input the same channel name join the same channel.
260+ */
261+ private void joinChannel ( String channelId ) {
277262 /**In the demo, the default is to enter as the anchor.*/
278263 engine .setClientRole (IRtcEngineEventHandler .ClientRole .CLIENT_ROLE_BROADCASTER );
279264 /**Sets the external audio source.
@@ -289,23 +274,21 @@ private void joinChannel(String channelId)
289274 * 0: Success.
290275 * < 0: Failure.
291276 * PS: Ensure that you call this method before the joinChannel method.*/
292- engine .setExternalAudioSource (true , SAMPLE_RATE , SAMPLE_NUM_OF_CHANNEL );
277+ engine .setExternalAudioSource (true , SAMPLE_RATE , SAMPLE_NUM_OF_CHANNEL , 2 , false , true );
293278 /**Please configure accessToken in the string_config file.
294279 * A temporary token generated in Console. A temporary token is valid for 24 hours. For details, see
295280 * https://docs.agora.io/en/Agora%20Platform/token?platform=All%20Platforms#get-a-temporary-token
296281 * A token generated at the server. This applies to scenarios with high-security requirements. For details, see
297282 * https://docs.agora.io/en/cloud-recording/token_server_java?platform=Java*/
298283 String accessToken = getString (R .string .agora_access_token );
299- if (TextUtils .equals (accessToken , "" ) || TextUtils .equals (accessToken , "<#YOUR ACCESS TOKEN#>" ))
300- {
284+ if (TextUtils .equals (accessToken , "" ) || TextUtils .equals (accessToken , "<#YOUR ACCESS TOKEN#>" )) {
301285 accessToken = null ;
302286 }
303287 /** Allows a user to join a channel.
304288 if you do not specify the uid, we will generate the uid for you*/
305289
306290 int res = engine .joinChannel (accessToken , channelId , 0 , option );
307- if (res != 0 )
308- {
291+ if (res != 0 ) {
309292 // Usually happens with invalid parameters
310293 // Error code description can be found at:
311294 // 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
@@ -317,23 +300,22 @@ private void joinChannel(String channelId)
317300 join .setEnabled (false );
318301 }
319302
320- /**IRtcEngineEventHandler is an abstract class providing default implementation.
321- * The SDK uses this class to report to the app on SDK runtime events.*/
322- private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler ()
323- {
303+ /**
304+ * IRtcEngineEventHandler is an abstract class providing default implementation.
305+ * The SDK uses this class to report to the app on SDK runtime events.
306+ */
307+ private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler () {
324308 /**Reports a warning during SDK runtime.
325309 * 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*/
326310 @ Override
327- public void onWarning (int warn )
328- {
311+ public void onWarning (int warn ) {
329312 Log .w (TAG , String .format ("onWarning code %d message %s" , warn , RtcEngine .getErrorDescription (warn )));
330313 }
331314
332315 /**Reports an error during SDK runtime.
333316 * 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*/
334317 @ Override
335- public void onError (int err )
336- {
318+ public void onError (int err ) {
337319 Log .e (TAG , String .format ("onError code %d message %s" , err , RtcEngine .getErrorDescription (err )));
338320 showAlert (String .format ("onError code %d message %s" , err , RtcEngine .getErrorDescription (err )));
339321 }
@@ -345,21 +327,19 @@ public void onError(int err)
345327 * @param uid User ID
346328 * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
347329 @ Override
348- public void onJoinChannelSuccess (String channel , int uid , int elapsed )
349- {
330+ public void onJoinChannelSuccess (String channel , int uid , int elapsed ) {
350331 Log .i (TAG , String .format ("onJoinChannelSuccess channel %s uid %d" , channel , uid ));
351332 showLongToast (String .format ("onJoinChannelSuccess channel %s uid %d" , channel , uid ));
352333 myUid = uid ;
353334 joined = true ;
354- handler .post (new Runnable ()
355- {
335+ handler .post (new Runnable () {
356336 @ Override
357- public void run ()
358- {
337+ public void run () {
359338 mic .setEnabled (true );
360339 pcm .setEnabled (true );
361340 join .setEnabled (true );
362341 join .setText (getString (R .string .leave ));
342+ pushing = true ;
363343 pushingTask .start ();
364344 }
365345 });
@@ -368,11 +348,24 @@ public void run()
368348 };
369349
370350 class PushingTask implements Runnable {
351+ long number = 0 ;
371352
372353 @ Override
373354 public void run () {
374- while (true ){
375- engine .pushExternalAudioFrame (readBuffer (), System .currentTimeMillis ());
355+ Process .setThreadPriority (Process .THREAD_PRIORITY_URGENT_AUDIO );
356+ while (pushing ) {
357+ Log .i (TAG , "pushExternalAudioFrame times:" + number ++);
358+ long before = System .currentTimeMillis ();
359+ engine .pushExternalAudioFrame (readBuffer (), 0 );
360+ long now = System .currentTimeMillis ();
361+ long consuming = now - before ;
362+ if (consuming < PUSH_INTERVAL ){
363+ try {
364+ Thread .sleep (PUSH_INTERVAL - consuming );
365+ } catch (InterruptedException e ) {
366+ Log .e (TAG , "PushingTask Interrupted" );
367+ }
368+ }
376369 }
377370 }
378371 }
0 commit comments