Send and receive media streams
Updated
How to use Server Gateway to send media streams to Video SDK clients and receive media streams from clients.
This page introduces how to use the Server Gateway to send media streams to the client and receive media streams from the client.
Understand the tech
The following figure shows the basic workflow you implement to send and receive audio and video streams using Server Gateway:
Prerequisites
Before you begin, ensure your development environment meets the necessary hardware and software requirements and that you have downloaded the latest Server Gateway SDK. See Integrate the SDK.
Initialize and connect
Before sending and receiving media streams, complete the following steps:
Initialize the SDK
Create an AgoraService instance and call initialize. The AgoraService object persists as long as the server app is running.
The SDK supports user IDs in both integer and string formats. This page demonstrates use of user IDs in integer format where the character set consists of only digits. To learn more about using string user IDs, see Using a string user ID.
// Import SDK, AgoraService, and AgoraServiceConfig classes for initialization
import io.agora.rtc.SDK;
import io.agora.rtc.AgoraService;
import io.agora.rtc.AgoraServiceConfig;
// Creates an AgoraService object
SDK.load(); // ensure JNI library load
AgoraService service = new AgoraService();
// Initializes the AgoraServiceConfig object
AgoraServiceConfig config = new AgoraServiceConfig();
// Enables the audio processing module
config.setEnableAudioProcessor(1);
// Disables the audio device module (Normally we do not directly connect audio capture or playback devices to a server)
config.setEnableAudioDevice(0);
// Enables video
config.setEnableVideo(1);
// Sets Agora App ID
config.setAppId(appid);
// Initializes the SDK
service.initialize(config);Connect to the Agora Video SDK Channel
After initializing the SDK, follow these steps to connect to a Video SDK channel.
-
Call
agoraRtcConnCreateto create anAgoraRtcConnobject to connect to the Agora Video SDK channel:AgoraRtcConn conn = service.agoraRtcConnCreate(null); -
Call
registerObserverto listen to connection events:conn.registerObserver(new ConnObserver()); -
Call
connectto connect to an Agora Video SDK channel:conn.connect(token, "test_channel", "1");
Send media streams to the client
To send media streams to the client, refer to the following steps:
Create a media stream sender
Use the IMediaNodeFactory object to create various types of media stream senders:
AgoraAudioPcmDataSender: Sends audio data in PCM format.AgoraVideoFrameSender: Sends video data in YUV format.AgoraAudioEncodedFrameSender: Sends encoded audio data.AgoraVideoEncodedImageSender: Sends encoded video data.
-
Create an
IMediaNodeFactoryobject:AgoraMediaNodeFactory factory = service.createMediaNodeFactory(); -
According to your requirements, create an
AgoraAudioPcmDataSenderobject for sending audio in PCM format, anAgoraVideoFrameSenderobject for sending video in YUV format, anAgoraAudioEncodedFrameSenderobject for sending encoded audio, or anAgoraVideoEncodedImageSenderobject for sending encoded video:// Creates a sender for PCM audio AgoraAudioPcmDataSender audioFrameSender = factory.createAudioPcmDataSender(); // Creates a sender for YUV video AgoraVideoFrameSender videoFrameSender = factory.createVideoFrameSender(); // Creates a sender for encoded audio AgoraAudioEncodedFrameSender audioFrameSender = factory.createAudioEncodedFrameSender(); // Creates a sender for encoded video AgoraVideoEncodedImageSender imageSender = factory.createVideoEncodedImageSender(); -
Create an
AgoraLocalAudioTrackobject and anAgoraLocalVideoTrackobject, corresponding to the local audio track and local video tracks, for publishing to the channel:// Creates a custom audio track that uses a PCM audio stream sender customAudioTrack = service.createCustomAudioTrackPcm(audioFrameSender); // Creates a custom audio track that uses an encoded audio stream sender customAudioTrack = service.createCustomAudioTrackEncoded(audioFrameSender,0); // Creates a custom video track that uses a YUV video stream sender customVideoTrack = service.createCustomVideoTrackFrame(videoFrameSender); // Creates a custom video track that uses encoded video stream sender customVideoTrack = service.createCustomVideoTrackEncoded(videoFrameSender, option);
Send media streams
-
To publish the local audio and video tracks to a Video SDK channel, call the
publishAudioandpublishVideomethods of theAgoraLocalUserobject:// Enables and publishes audio and video track customAudioTrack.setEnabled(1); customVideoTrack.setEnabled(1); conn.getLocalUser().publishAudio(customAudioTrack); conn.getLocalUser().publishVideo(customVideoTrack); -
Start the sending thread, which calls the send methods of the audio and video senders:
pcmSender = new PcmSender(audioFile,audioFrameSender,numOfChannels,sampleRate); h264Sender = new H264Sender(videoFile,1000/fps,0,0,videoFrameSender); pcmSender.start(); h264Sender.start(); -
Send audio and video data. Refer to the following examples to send PCM audio data and H.264 video data:
Refer to the following code to send PCM audio data:
// audio thread // send audio data every 10 ms; class PcmSender extends FileSender { private AgoraAudioPcmDataSender audioFrameSender; private static final int INTERVAL = 10; //ms private int channels; private int samplerate; private int bufferSize = 0; private byte[] buffer; public PcmSender(String filepath, AgoraAudioPcmDataSender sender,int channels,int samplerate){ super(filepath, INTERVAL); audioFrameSender = sender; this.channels = channels; this.samplerate = samplerate; this.bufferSize = channels * samplerate * 2 * INTERVAL /1000; this.buffer = new byte[this.bufferSize]; } // sendOneFrame calls the send method of audioFrameSender to send PCM data @Override public void sendOneFrame(byte[] data) { if(data == null) return; audioFrameSender.send(data,(int)System.currentTimeMillis(),sampleRate/(1000/INTERVAL),2,channels,samplerate); } @Override public byte[] readOneFrame(FileInputStream fos) { if(fos != null ){ try { int size = fos.read(buffer,0,bufferSize); if( size <= 0){ reset(); return null; } } catch (IOException e) { e.printStackTrace(); } } return buffer; } }The following code demonstrates sending video data, using H.264 data as an example:
class H264Sender extends FileSender { private AgoraVideoEncodedImageSender imageSender; private H264Reader h264Reader; private int lastFrameType = 0; private int height; private int width; private int fps; public H264Sender(String path,int interval, int height,int width, AgoraVideoEncodedImageSender videoEncodedImageSender){ super(path,interval,false); this.imageSender = videoEncodedImageSender; this.h264Reader = new H264Reader(path); this.height = height; this.width = width; this.fps = 1000/interval; } // sendOneH264Frame calls the send method of imageSender to send H.264 data. @Override public void sendOneFrame(byte[] data) { if(data == null) return; EncodedVideoFrameInfo info = new EncodedVideoFrameInfo(); long currentTime = System.currentTimeMillis(); info.setFrameType(lastFrameType); info.setWidth(width); info.setHeight(height); info.setCodecType(Constants.VIDEO_CODEC_H264); info.setCaptureTimeMs(currentTime); info.setRenderTimeMs(currentTime); info.setFramesPerSecond(fps); info.setRotation(0); imageSender.send(data,data.length,info); } @Override public byte[] readOneFrame(FileInputStream fos) { int retry = 0; H264Reader.H264Frame frame = h264Reader.readNextFrame(); while ( frame == null && retry < 4){ h264Reader.reset(); frame = h264Reader.readNextFrame(); retry ++; } if( frame != null ) { lastFrameType = frame.frameType; return frame.data; } else { return null; } } @Override public void release() { super.release(); h264Reader.close(); } }
Set the format of the encoded video data by using the info parameter of the send method.
Disconnect and release resources
After finishing media sending tasks, follow these steps to disconnect from the channel and release resources.
It is important to follow the sequence in the sample code to release resources properly.
-
Call the
unpublishAudioandunpublishVideomethods to stop publishing audio and video:if(conn != null) { conn.getLocalUser().unpublishAudio(customAudioTrack); conn.getLocalUser().unpublishVideo(customVideoTrack); } -
Call
unregisterObserverto unregister the connection observer:conn.unregisterObserver(); -
Call
disconnectto disconnect from the Video SDK channel:int ret = conn.disconnect(); -
Release resources from created objects:
conn.destroy();
Receive media stream from the client
To receive media streams from the client, refer to the following steps:
Register video and audio frame observers
Instantiate the video and audio frame observer object, and register the observer by using the member methods of the ILocalUserObserver class. After registration is successful, the SDK triggers callbacks when audio or video frames are available.
The SampleLocalUserObserver class in the sample code not only includes observer objects from the IVideoEncodedFrameObserver class, the IAudioFrameObserverBase class, and the IVideoFrameObserver2 class, and inherits the ILocalUserObserver class as well. Use the member methods of the ILocalUserObserver class to register video and audio frame observer objects.
// The SampleLocalUserObserver class inherits the ILocalUserObserver class
localUserObserver = new SampleLocalUserObserver(conn.getLocalUser());
conn.getLocalUser().registerObserver(localUserObserver);
// The PcmFrameObserver class inherits the IAudioFrameObserver class
int ret = conn.getLocalUser().setPlaybackAudioFrameBeforeMixingParameters(numOfChannels, sampleRate);
if (ret > 0) {
System.out.printf("setPlaybackAudioFrameBeforeMixingParameters fail ret=%d\n", ret);
return;
}
// The H264FrameReceiver class inherits the IVideoEncodedImageReceiver class
h264FrameReceiver = new H264FrameReceiver(videoFile);
conn.getLocalUser().registerVideoEncodedFrameObserver(new AgoraVideoEncodedFrameObserver(h264FrameReceiver));setAudioFrameObserver and registerVideoEncodedFrameObserver are member methods of the SampleLocalUserObserver class and can be used to instantiate the IAudioFrameObserver class and the AgoraVideoEncodedFrameObserver class.
localUserObserver.setAudioFrameObserver(pcmFrameObserver);
conn.getLocalUser().registerVideoEncodedFrameObserver(new AgoraVideoEncodedFrameObserver(h264FrameReceiver));Use callbacks to receive media streams
The following code shows how to receive encoded video, video in YUV format, and audio in PCM format.
// Receives encoded video by using the onEncodedVideoFrame callback and save the video data in a file
class H264FrameReceiver extends FileWriter implements IVideoEncodedFrameObserver {
public H264FrameReceiver(String path) {
super(path);
}
@Override
public int onEncodedVideoFrame(AgoraVideoEncodedFrameObserver agora_video_encoded_frame_observer, int uid, byte[] image_buffer, long length, EncodedVideoFrameInfo video_encoded_frame_info) {
System.out.println("onEncodedVideoFrame success " + video_encoded_frame_info.getFrameType());
writeData(image_buffer, (int) length);
return 1;
}
}
// Receives YUV video by using the onFrame callback and save the video data in a file
class YuvFrameObserver extends FileWriter implements IVideoFrameObserver2 {
public YuvFrameObserver(String path) {
super(path);
}
@Override
public void onFrame(AgoraVideoFrameObserver2 agora_video_frame_observer2, String channel_id, String remote_uid, VideoFrame frame) {
System.out.println("onFrame success ");
writeData(frame.getYBuffer(), frame.getYBuffer().remaining());
writeData(frame.getUBuffer(), frame.getUBuffer().remaining());
writeData(frame.getVBuffer(), frame.getVBuffer().remaining());
return ;
}
}
// Receives PCM audio by using the onPlaybackAudioFrameBeforeMixing callback and save the audio data in a file
public static class PcmFrameObserver extends FileWriter implements IAudioFrameObserver {
public PcmFrameObserver(String outputFilePath) {
super(outputFilePath);
}
@Override
public int onRecordAudioFrame(AgoraLocalUser agora_local_user, String channel_id, AudioFrame frame) {
System.out.println("onRecordAudioFrame success");
return 1;
}
@Override
public int onPlaybackAudioFrame(AgoraLocalUser agora_local_user, String channel_id, AudioFrame frame) {
System.out.println("onPlaybackAudioFrame success");
return 1;
}
@Override
public int onMixedAudioFrame(AgoraLocalUser agora_local_user, String channel_id, AudioFrame frame) {
System.out.println("onMixedAudioFrame success");
return 1;
}
@Override
public int onPlaybackAudioFrameBeforeMixing(AgoraLocalUser agora_local_user, String channel_id, String uid, AudioFrame audioFrame) {
// Write PCM samples
int writeBytes = audioFrame.getSamplesPerChannel() * audioFrame.getChannels() * 2;
writeData(audioFrame.getBuffer(), writeBytes);
return 1;
}
}Disconnect and release resources
When the media receiving task is complete, refer to the following steps to disconnect from the channel.
It is important to follow the sequence in the sample code to release resources properly.
-
Call unset methods to release the video and audio observers.
// Release video and audio observer localUserObserver.unsetAudioFrameObserver(); localUserObserver.unsetVideoFrameObserver(); -
Call
disconnectto disconnect from the channel.// Disconnect from the Agora channel int ret = conn.disconnect(); if (ret != 0) { System.out.printf("conn.disconnect fail ret=%d\n", ret); } -
Release the created objects.
// Releases the created objects. conn.destroy(); -
When your server app stops running, release the
AgoraServiceobject.// Destroy Agora Service service.destroy();
Development considerations
- The methods mentioned on this page are all asynchronous and do not block the main thread.
- When you use the Agora Video SDK to communicate with the Server Gateway, set the channel use-case of the Video SDK to
LIVE_BROADCASTING. - When sending audio data in AAC format, you cannot set the sampling rate to 44.1 Kbps.
References
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
API reference
Refer to Server Gateway API for Java to find detailed API and parameter descriptions.
