Skip to content

Commit b47327a

Browse files
committed
[Android]1.perfect media file reader code;2.add MutliVideoSourceTracks module.
1 parent 47b088d commit b47327a

11 files changed

Lines changed: 828 additions & 192 deletions

File tree

Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.

Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/PushExternalVideoYUV.java

Lines changed: 13 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,13 @@
2323
import com.yanzhenjie.permission.AndPermission;
2424
import com.yanzhenjie.permission.runtime.Permission;
2525

26-
import java.io.IOException;
27-
import java.io.InputStream;
28-
2926
import io.agora.api.example.MainApplication;
3027
import io.agora.api.example.R;
3128
import io.agora.api.example.annotation.Example;
3229
import io.agora.api.example.common.BaseFragment;
3330
import io.agora.api.example.utils.CommonUtil;
3431
import io.agora.api.example.utils.TokenUtils;
35-
import io.agora.base.JavaI420Buffer;
36-
import io.agora.base.VideoFrame;
32+
import io.agora.api.example.utils.VideoFileReader;
3733
import io.agora.rtc2.ChannelMediaOptions;
3834
import io.agora.rtc2.Constants;
3935
import io.agora.rtc2.IRtcEngineEventHandler;
@@ -53,24 +49,14 @@
5349
public class PushExternalVideoYUV extends BaseFragment implements View.OnClickListener {
5450
private static final String TAG = PushExternalVideoYUV.class.getSimpleName();
5551

56-
private final String RAW_VIDEO_PATH = "sample.yuv";
57-
private final int RAW_VIDEO_WIDTH = 320;
58-
private final int RAW_VIDEO_HEIGHT = 180;
59-
private final int RAW_VIDEO_FRAME_SIZE = RAW_VIDEO_WIDTH * RAW_VIDEO_HEIGHT / 2 * 3;
60-
private final int RAW_VIDEO_FRAME_RATE = 15;
61-
private final long RAW_VIDEO_FRAME_INTERVAL_NS = 1000 * 1000 * 1000 / RAW_VIDEO_FRAME_RATE;
62-
6352
private FrameLayout fl_local, fl_remote;
6453
private Button join;
6554
private EditText et_channel;
6655
private RtcEngineEx engine;
6756
private int myUid;
6857
private volatile boolean joined = false;
6958

70-
private Thread pushingThread;
71-
private volatile boolean pushing = false;
72-
73-
private InputStream inputStream;
59+
private VideoFileReader videoFileReader;
7460

7561
@Nullable
7662
@Override
@@ -143,15 +129,8 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
143129

144130
@Override
145131
public void onDestroy() {
146-
pushing = false;
147-
if (pushingThread != null) {
148-
try {
149-
pushingThread.join();
150-
} catch (InterruptedException e) {
151-
e.printStackTrace();
152-
} finally {
153-
pushingThread = null;
154-
}
132+
if(videoFileReader != null){
133+
videoFileReader.stop();
155134
}
156135

157136
/**leaveChannel and Destroy the RtcEngine instance*/
@@ -206,15 +185,8 @@ public void onClick(View v) {
206185
} else {
207186
joined = false;
208187
join.setText(getString(R.string.join));
209-
pushing = false;
210-
if (pushingThread != null) {
211-
try {
212-
pushingThread.join();
213-
} catch (InterruptedException e) {
214-
e.printStackTrace();
215-
} finally {
216-
pushingThread = null;
217-
}
188+
if(videoFileReader != null){
189+
videoFileReader.stop();
218190
}
219191
fl_remote.removeAllViews();
220192
fl_local.removeAllViews();
@@ -339,11 +311,14 @@ public void run() {
339311
join.setEnabled(true);
340312
join.setText(getString(R.string.leave));
341313

342-
pushing = true;
343-
if (pushingThread == null) {
344-
pushingThread = new Thread(new PushingTask());
345-
pushingThread.start();
314+
if (videoFileReader == null) {
315+
videoFileReader = new VideoFileReader(requireContext(), videoFrame -> {
316+
if(joined && engine != null){
317+
engine.pushExternalVideoFrame(videoFrame);
318+
}
319+
});
346320
}
321+
videoFileReader.start();
347322

348323
}
349324
});
@@ -407,51 +382,4 @@ public void run() {
407382
}
408383
};
409384

410-
private class PushingTask implements Runnable {
411-
412-
@Override
413-
public void run() {
414-
try {
415-
inputStream = getContext().getAssets().open(RAW_VIDEO_PATH);
416-
} catch (IOException e) {
417-
e.printStackTrace();
418-
}
419-
420-
byte[] buffer = new byte[RAW_VIDEO_FRAME_SIZE];
421-
while (pushing) {
422-
long start = System.nanoTime();
423-
try {
424-
int read = inputStream.read(buffer);
425-
while (read < 0) {
426-
inputStream.reset();
427-
read = inputStream.read(buffer);
428-
}
429-
} catch (IOException e) {
430-
e.printStackTrace();
431-
}
432-
JavaI420Buffer i420Buffer = JavaI420Buffer.allocate(RAW_VIDEO_WIDTH, RAW_VIDEO_HEIGHT);
433-
i420Buffer.getDataY().put(buffer, 0, i420Buffer.getDataY().limit());
434-
i420Buffer.getDataU().put(buffer, i420Buffer.getDataY().limit(), i420Buffer.getDataU().limit());
435-
i420Buffer.getDataV().put(buffer, i420Buffer.getDataY().limit() + i420Buffer.getDataU().limit(), i420Buffer.getDataV().limit());
436-
engine.pushExternalVideoFrame(new VideoFrame(i420Buffer, 0, System.nanoTime()));
437-
long consume = System.nanoTime() - start;
438-
439-
try {
440-
Thread.sleep(Math.max(0, (RAW_VIDEO_FRAME_INTERVAL_NS - consume) / 1000 / 1000));
441-
} catch (InterruptedException e) {
442-
e.printStackTrace();
443-
}
444-
}
445-
446-
if (inputStream != null) {
447-
try {
448-
inputStream.close();
449-
} catch (IOException e) {
450-
e.printStackTrace();
451-
} finally {
452-
inputStream = null;
453-
}
454-
}
455-
}
456-
}
457385
}

Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/SwitchCameraScreenShare.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.agora.api.example.examples.advanced;
22

3-
import static io.agora.api.example.common.model.Examples.ADVANCED;
43
import static io.agora.rtc2.video.VideoCanvas.RENDER_MODE_HIDDEN;
54
import static io.agora.rtc2.video.VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15;
65
import static io.agora.rtc2.video.VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_ADAPTIVE;
@@ -42,7 +41,6 @@
4241

4342
import io.agora.api.example.MainApplication;
4443
import io.agora.api.example.R;
45-
import io.agora.api.example.annotation.Example;
4644
import io.agora.api.example.common.BaseFragment;
4745
import io.agora.api.example.utils.CommonUtil;
4846
import io.agora.api.example.utils.TokenUtils;
@@ -61,13 +59,13 @@
6159
* This example demonstrates how video can be flexibly switched between the camera stream and the
6260
* screen share stream during an audio-video call.
6361
*/
64-
@Example(
65-
index = 10,
66-
group = ADVANCED,
67-
name = R.string.item_cameraorscreen,
68-
actionId = R.id.action_mainFragment_to_SwitchCameraScreenShare,
69-
tipsId = R.string.switchcamerascreen
70-
)
62+
//@Example(
63+
// index = 10,
64+
// group = ADVANCED,
65+
// name = R.string.item_cameraorscreen,
66+
// actionId = R.id.action_mainFragment_to_SwitchCameraScreenShare,
67+
// tipsId = R.string.switchcamerascreen
68+
//)
7169
public class SwitchCameraScreenShare extends BaseFragment implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
7270
private static final String TAG = SwitchCameraScreenShare.class.getSimpleName();
7371
private static final int DEFAULT_SHARE_FRAME_RATE = 15;

Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/customaudio/CustomAudioSource.java

Lines changed: 22 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import android.content.Context;
66
import android.os.Bundle;
77
import android.os.Handler;
8-
import android.os.Process;
98
import android.util.Log;
109
import android.view.LayoutInflater;
1110
import android.view.View;
@@ -21,14 +20,12 @@
2120
import com.yanzhenjie.permission.AndPermission;
2221
import com.yanzhenjie.permission.runtime.Permission;
2322

24-
import java.io.IOException;
25-
import java.io.InputStream;
26-
2723
import io.agora.api.example.MainApplication;
2824
import io.agora.api.example.R;
2925
import io.agora.api.example.annotation.Example;
3026
import io.agora.api.example.common.BaseFragment;
3127
import io.agora.api.example.common.widget.AudioSeatManager;
28+
import io.agora.api.example.utils.AudioFileReader;
3229
import io.agora.api.example.utils.CommonUtil;
3330
import io.agora.api.example.utils.TokenUtils;
3431
import io.agora.rtc2.ChannelMediaOptions;
@@ -57,19 +54,9 @@ public class CustomAudioSource extends BaseFragment implements View.OnClickListe
5754
public static RtcEngineEx engine;
5855
private Switch mic, pcm;
5956
private ChannelMediaOptions option = new ChannelMediaOptions();
60-
private static final String AUDIO_FILE = "output.raw";
61-
private static final Integer SAMPLE_RATE = 44100;
62-
private static final Integer SAMPLE_NUM_OF_CHANNEL = 2;
63-
private static final Integer BITS_PER_SAMPLE = 16;
64-
private static final Integer SAMPLES = 441;
65-
private static final Integer BUFFER_SIZE = SAMPLES * BITS_PER_SAMPLE / 8 * SAMPLE_NUM_OF_CHANNEL;
66-
private static final Integer PUSH_INTERVAL = SAMPLES * 1000 / SAMPLE_RATE;
67-
68-
private InputStream inputStream;
69-
private Thread pushingTask;
70-
private boolean pushing = false;
7157

7258
private AudioSeatManager audioSeatManager;
59+
private AudioFileReader audioPushingHelper;
7360

7461
@Override
7562
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -87,36 +74,6 @@ private void initMediaOption() {
8774
option.enableAudioRecordingOrPlayout = true;
8875
}
8976

90-
private void openAudioFile() {
91-
try {
92-
inputStream = this.getResources().getAssets().open(AUDIO_FILE);
93-
} catch (IOException e) {
94-
e.printStackTrace();
95-
}
96-
}
97-
98-
private void closeAudioFile() {
99-
try {
100-
inputStream.close();
101-
} catch (IOException e) {
102-
e.printStackTrace();
103-
}
104-
}
105-
106-
private byte[] readBuffer() {
107-
int byteSize = BUFFER_SIZE;
108-
byte[] buffer = new byte[byteSize];
109-
try {
110-
if (inputStream.read(buffer) < 0) {
111-
inputStream.reset();
112-
return readBuffer();
113-
}
114-
} catch (IOException e) {
115-
e.printStackTrace();
116-
}
117-
return buffer;
118-
}
119-
12077
@Nullable
12178
@Override
12279
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -193,7 +150,12 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
193150
+ "\"appVersion\":\"" + RtcEngine.getSdkVersion() + "\""
194151
+ "}"
195152
+ "}");
196-
openAudioFile();
153+
154+
audioPushingHelper = new AudioFileReader(requireContext(), (buffer, timestamp) -> {
155+
if(joined && engine != null){
156+
engine.pushExternalAudioFrame(buffer, timestamp);
157+
}
158+
});
197159
} catch (Exception e) {
198160
e.printStackTrace();
199161
getActivity().onBackPressed();
@@ -203,34 +165,27 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
203165
@Override
204166
public void onDestroy() {
205167
super.onDestroy();
206-
pushing = false;
168+
if(audioPushingHelper != null){
169+
audioPushingHelper.stop();
170+
}
207171
/**leaveChannel and Destroy the RtcEngine instance*/
208172
if (engine != null) {
209173
engine.leaveChannel();
210174
}
211175
handler.post(RtcEngine::destroy);
212176
engine = null;
213-
closeAudioFile();
214177
}
215178

216179

217180
@Override
218-
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
181+
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
219182
if (compoundButton.getId() == R.id.microphone) {
220-
if (b) {
221-
option.publishMicrophoneTrack = true;
222-
} else {
223-
option.publishMicrophoneTrack = false;
224-
}
183+
option.publishMicrophoneTrack = checked;
225184
engine.updateChannelMediaOptions(option);
226185
} else if (compoundButton.getId() == R.id.localAudio) {
227-
if (b) {
228-
option.publishCustomAudioTrack = true;
229-
} else {
230-
option.publishCustomAudioTrack = false;
231-
}
186+
option.publishCustomAudioTrack = checked;
232187
engine.updateChannelMediaOptions(option);
233-
engine.enableCustomAudioLocalPlayback(0, true);
188+
engine.enableCustomAudioLocalPlayback(0, checked);
234189
}
235190
}
236191

@@ -276,17 +231,11 @@ public void onClick(View v) {
276231
* 2:If you call the leaveChannel method during CDN live streaming, the SDK
277232
* triggers the removeInjectStreamUrl method.*/
278233
engine.leaveChannel();
279-
pushing = false;
280234
join.setText(getString(R.string.join));
281235
mic.setEnabled(false);
282236
pcm.setEnabled(false);
283-
if(pushingTask != null){
284-
try {
285-
pushingTask.join();
286-
pushingTask = null;
287-
} catch (InterruptedException e) {
288-
// do nothing
289-
}
237+
if(audioPushingHelper != null){
238+
audioPushingHelper.stop();
290239
}
291240
audioSeatManager.downAllSeats();
292241
}
@@ -313,8 +262,9 @@ private void joinChannel(String channelId) {
313262
* 0: Success.
314263
* < 0: Failure.
315264
* PS: Ensure that you call this method before the joinChannel method.*/
316-
engine.setExternalAudioSource(true, SAMPLE_RATE, SAMPLE_NUM_OF_CHANNEL, 2, false, true);
317-
265+
engine.setExternalAudioSource(true,
266+
AudioFileReader.SAMPLE_RATE, AudioFileReader.SAMPLE_NUM_OF_CHANNEL, AudioFileReader.SAMPLE_NUM_OF_CHANNEL,
267+
false, true);
318268

319269

320270
/**Please configure accessToken in the string_config file.
@@ -379,10 +329,8 @@ public void run() {
379329
pcm.setEnabled(true);
380330
join.setEnabled(true);
381331
join.setText(getString(R.string.leave));
382-
pushing = true;
383-
if(pushingTask == null){
384-
pushingTask = new Thread(new PushingTask());
385-
pushingTask.start();
332+
if(audioPushingHelper != null){
333+
audioPushingHelper.start();
386334
}
387335
audioSeatManager.upLocalSeat(uid);
388336
}
@@ -403,26 +351,4 @@ public void onUserOffline(int uid, int reason) {
403351
}
404352
};
405353

406-
class PushingTask implements Runnable {
407-
long number = 0;
408-
409-
@Override
410-
public void run() {
411-
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
412-
while (pushing) {
413-
Log.i(TAG, "pushExternalAudioFrame times:" + number++);
414-
long before = System.currentTimeMillis();
415-
engine.pushExternalAudioFrame(readBuffer(), 0);
416-
long now = System.currentTimeMillis();
417-
long consuming = now - before;
418-
if(consuming < PUSH_INTERVAL){
419-
try {
420-
Thread.sleep(PUSH_INTERVAL - consuming);
421-
} catch (InterruptedException e) {
422-
Log.e(TAG, "PushingTask Interrupted");
423-
}
424-
}
425-
}
426-
}
427-
}
428354
}

0 commit comments

Comments
 (0)