본문 바로가기
Android

아고라 플랫폼을 이용한 안드로이드 라이브스트리밍(RTSP) - 2

by 일용직 코딩노동자 2022. 5. 12.
728x90
반응형

우선 메니페스트에 권한들 먼저 넣어줄게요.

 

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

그리고 라이브러리 추가를 해주겠습니다.

implementation 'gun0912.ted:tedpermission:2.2.3'
implementation files('libs/agora-rtc-sdk.jar')
implementation files('libs/commons-codec-1.15.jar')
implementation 'io.agora.rtm:rtm-sdk:1.4.9'

권한 처리를 위한 테드퍼미션과 아고라 관련된 라이브러리 및 나중에 라이브토큰 발급을위한 라이브러리입니다.

 

agora-rtc-sdk.jar
0.84MB

 

commons-codec-1.15.jar
0.34MB

해당 jar파일을 libs 폴더에 넣어주시면 됩니다.

 

그 다음에 Application 클래스를 하나 생성해줄게요.

 

public class AgoraApplication extends Application {
    private RtcEngine mRtcEngine;
    private EngineConfig mGlobalConfig = new EngineConfig();
    private AgoraEventHandler mHandler = new AgoraEventHandler();
    private StatsManager mStatsManager = new StatsManager();

    private static AgoraApplication sInstance;
    private ChatManager mChatManager;

    public static AgoraApplication the() {
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        try {
            mRtcEngine = RtcEngine.create(getApplicationContext(), "앱 아이디", mHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }

        initConfig();

        sInstance = this;

        mChatManager = new ChatManager(this);
        mChatManager.init();
    }

    public ChatManager getChatManager() {
        return mChatManager;
    }

    private void initConfig() {
        SharedPreferences pref = PrefManager.getPreferences(getApplicationContext());
        mGlobalConfig.setVideoDimenIndex(pref.getInt(
                Constants.PREF_RESOLUTION_IDX, Constants.DEFAULT_PROFILE_IDX));

        boolean showStats = pref.getBoolean(Constants.PREF_ENABLE_STATS, false);
        mGlobalConfig.setIfShowVideoStats(showStats);
        mStatsManager.enableStats(showStats);

        mGlobalConfig.setMirrorLocalIndex(pref.getInt(Constants.PREF_MIRROR_LOCAL, 0));
        mGlobalConfig.setMirrorRemoteIndex(pref.getInt(Constants.PREF_MIRROR_REMOTE, 0));
        mGlobalConfig.setMirrorEncodeIndex(pref.getInt(Constants.PREF_MIRROR_ENCODE, 0));
    }

    public EngineConfig engineConfig() {
        return mGlobalConfig;
    }

    public RtcEngine rtcEngine() {
        return mRtcEngine;
    }

    public StatsManager statsManager() {
        return mStatsManager;
    }

    public void registerEventHandler(EventHandler handler) {
        mHandler.addHandler(handler);
    }

    public void removeEventHandler(EventHandler handler) {
        mHandler.removeHandler(handler);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        RtcEngine.destroy();
    }
}

Application 클래스를 사용 하실 때는 메니페스트에

 

android:name=".AgoraApplication"

한줄 추가해주세요.

 

그다음에는 본격 라이브 소스를 구현하기에 앞서 기본 클래스들을 셋팅해주겠습니다.

 

public class Constants {
    public static final String PREF_NAME = "io.agora.openlive";
    public static final int DEFAULT_PROFILE_IDX = 2;
    public static final String PREF_RESOLUTION_IDX = "pref_profile_index";
    public static final String PREF_ENABLE_STATS = "pref_enable_stats";
    public static final String PREF_MIRROR_LOCAL = "pref_mirror_local";
    public static final String PREF_MIRROR_REMOTE = "pref_mirror_remote";
    public static final String PREF_MIRROR_ENCODE = "pref_mirror_encode";

    public static final String KEY_CLIENT_ROLE = "key_client_role";

    public static int[] VIDEO_MIRROR_MODES = new int[]{
            io.agora.rtc.Constants.VIDEO_MIRROR_MODE_AUTO,
            io.agora.rtc.Constants.VIDEO_MIRROR_MODE_ENABLED,
            io.agora.rtc.Constants.VIDEO_MIRROR_MODE_DISABLED,
    };

    public static VideoEncoderConfiguration.VideoDimensions[] VIDEO_DIMENSIONS = new VideoEncoderConfiguration.VideoDimensions[]{
            VideoEncoderConfiguration.VD_320x240,
            VideoEncoderConfiguration.VD_480x360,
            VideoEncoderConfiguration.VD_640x360,
            VideoEncoderConfiguration.VD_640x480,
            new VideoEncoderConfiguration.VideoDimensions(960, 540),
            VideoEncoderConfiguration.VD_1280x720
    };
}

----------------------------------------------------------------------------------------------------------------------

public class LocalStatsData extends StatsData {
    private static final String FORMAT = "Local(%d)\n\n" +
            "%dx%d %dfps\n" +
            "LastMile delay: %d ms\n" +
            "Video tx/rx (kbps): %d/%d\n" +
            "Audio tx/rx (kbps): %d/%d\n" +
            "CPU: app/total %.1f%%/%.1f%%\n" +
            "Quality tx/rx: %s/%s\n" +
            "Loss tx/rx: %d%%/%d%%";

    private int lastMileDelay;
    private int videoSend;
    private int videoRecv;
    private int audioSend;
    private int audioRecv;
    private double cpuApp;
    private double cpuTotal;
    private int sendLoss;
    private int recvLoss;

    @Override
    public String toString() {
        return String.format(Locale.getDefault(), FORMAT,
                getUid(),
                getWidth(), getHeight(), getFramerate(),
                getLastMileDelay(),
                getVideoSendBitrate(), getVideoRecvBitrate(),
                getAudioSendBitrate(), getAudioRecvBitrate(),
                getCpuApp(), getCpuTotal(),
                getSendQuality(), getRecvQuality(),
                getSendLoss(), getRecvLoss());
    }

    public int getLastMileDelay() {
        return lastMileDelay;
    }

    public void setLastMileDelay(int lastMileDelay) {
        this.lastMileDelay = lastMileDelay;
    }

    public int getVideoSendBitrate() {
        return videoSend;
    }

    public void setVideoSendBitrate(int videoSend) {
        this.videoSend = videoSend;
    }

    public int getVideoRecvBitrate() {
        return videoRecv;
    }

    public void setVideoRecvBitrate(int videoRecv) {
        this.videoRecv = videoRecv;
    }

    public int getAudioSendBitrate() {
        return audioSend;
    }

    public void setAudioSendBitrate(int audioSend) {
        this.audioSend = audioSend;
    }

    public int getAudioRecvBitrate() {
        return audioRecv;
    }

    public void setAudioRecvBitrate(int audioRecv) {
        this.audioRecv = audioRecv;
    }

    public double getCpuApp() {
        return cpuApp;
    }

    public void setCpuApp(double cpuApp) {
        this.cpuApp = cpuApp;
    }

    public double getCpuTotal() {
        return cpuTotal;
    }

    public void setCpuTotal(double cpuTotal) {
        this.cpuTotal = cpuTotal;
    }

    public int getSendLoss() {
        return sendLoss;
    }

    public void setSendLoss(int sendLoss) {
        this.sendLoss = sendLoss;
    }

    public int getRecvLoss() {
        return recvLoss;
    }

    public void setRecvLoss(int recvLoss) {
        this.recvLoss = recvLoss;
    }

}

----------------------------------------------------------------------------------------------------------------------

public class RemoteStatsData extends StatsData {
    private static final String FORMAT = "Remote(%d)\n\n" +
            "%dx%d %dfps\n" +
            "Quality tx/rx: %s/%s\n" +
            "Video delay: %d ms\n" +
            "Audio net delay/jitter: %dms/%dms\n" +
            "Audio loss/quality: %d%%/%s";

    private int videoDelay;
    private int audioNetDelay;
    private int audioNetJitter;
    private int audioLoss;
    private String audioQuality;

    @Override
    public String toString() {
        return String.format(Locale.getDefault(), FORMAT,
                getUid(),
                getWidth(), getHeight(), getFramerate(),
                getSendQuality(), getRecvQuality(),
                getVideoDelay(),
                getAudioNetDelay(), getAudioNetJitter(),
                getAudioLoss(), getAudioQuality());
    }

    public static String getFORMAT() {
        return FORMAT;
    }

    public int getVideoDelay() {
        return videoDelay;
    }

    public void setVideoDelay(int videoDelay) {
        this.videoDelay = videoDelay;
    }

    public int getAudioNetDelay() {
        return audioNetDelay;
    }

    public void setAudioNetDelay(int audioNetDelay) {
        this.audioNetDelay = audioNetDelay;
    }

    public int getAudioNetJitter() {
        return audioNetJitter;
    }

    public void setAudioNetJitter(int audioNetJitter) {
        this.audioNetJitter = audioNetJitter;
    }

    public int getAudioLoss() {
        return audioLoss;
    }

    public void setAudioLoss(int audioLoss) {
        this.audioLoss = audioLoss;
    }

    public String getAudioQuality() {
        return audioQuality;
    }

    public void setAudioQuality(String audioQuality) {
        this.audioQuality = audioQuality;
    }
}

----------------------------------------------------------------------------------------------------------------------

public class StatsData {
    private long uid;
    private int width;
    private int height;
    private int framerate;
    private String recvQuality;
    private String sendQuality;

    public long getUid() {
        return uid;
    }

    public void setUid(long uid) {
        this.uid = uid;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getFramerate() {
        return framerate;
    }

    public void setFramerate(int framerate) {
        this.framerate = framerate;
    }

    public String getRecvQuality() {
        return recvQuality;
    }

    public void setRecvQuality(String recvQuality) {
        this.recvQuality = recvQuality;
    }

    public String getSendQuality() {
        return sendQuality;
    }

    public void setSendQuality(String sendQuality) {
        this.sendQuality = sendQuality;
    }
}

----------------------------------------------------------------------------------------------------------------------

public class StatsManager {
    private List<Integer> mUidList = new ArrayList<>();
    private Map<Integer, StatsData> mDataMap = new HashMap<>();
    private boolean mEnable = false;

    public void addUserStats(int uid, boolean ifLocal) {
        if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
            return;
        }

        StatsData data = ifLocal
                ? new LocalStatsData()
                : new RemoteStatsData();
        // in case 32-bit unsigned integer uid is received
        data.setUid(uid & 0xFFFFFFFFL);

        if (ifLocal) mUidList.add(0, uid);
        else mUidList.add(uid);

        mDataMap.put(uid, data);
    }

    public void removeUserStats(int uid) {
        if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
            mUidList.remove((Integer) uid);
            mDataMap.remove(uid);
        }
    }

    public StatsData getStatsData(int uid) {
        if (mUidList.contains(uid) && mDataMap.containsKey(uid)) {
            return mDataMap.get(uid);
        } else {
            return null;
        }
    }

    public String qualityToString(int quality) {
        switch (quality) {
            case io.agora.rtc.Constants.QUALITY_EXCELLENT:
                return "Exc";
            case io.agora.rtc.Constants.QUALITY_GOOD:
                return "Good";
            case io.agora.rtc.Constants.QUALITY_POOR:
                return "Poor";
            case io.agora.rtc.Constants.QUALITY_BAD:
                return "Bad";
            case io.agora.rtc.Constants.QUALITY_VBAD:
                return "VBad";
            case Constants.QUALITY_DOWN:
                return "Down";
            default:
                return "Unk";
        }
    }

    public void enableStats(boolean enabled) {
        mEnable = enabled;
    }

    public boolean isEnabled() {
        return mEnable;
    }

    public void clearAllData() {
        mUidList.clear();
        mDataMap.clear();
    }
}

----------------------------------------------------------------------------------------------------------------------

public class WindowUtil {
    public static void hideWindowStatusBar(Window window) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }
        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    public static int getSystemStatusBarHeight(Context context) {
        int id = context.getResources().getIdentifier(
                "status_bar_height", "dimen", "android");
        return id > 0 ? context.getResources().getDimensionPixelSize(id) : id;
    }
}

이렇게 기본 클래스들 셋팅을해주세요 (아고라 문서에서 공식제공해주는 소스입니다.)

 

다음은 영상처리에 있는 이벤트를 처리해줄 핸들러를 작성해줄게요.

 

public class AgoraEventHandler extends IRtcEngineEventHandler {
    private ArrayList<EventHandler> mHandler = new ArrayList<>();

    public void addHandler(EventHandler handler) {
        mHandler.add(handler);
    }

    public void removeHandler(EventHandler handler) {
        mHandler.remove(handler);
    }

    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
        for (EventHandler handler : mHandler) {
            handler.onJoinChannelSuccess(channel, uid, elapsed);
        }
    }

    @Override
    public void onLeaveChannel(RtcStats stats) {
        for (EventHandler handler : mHandler) {
            handler.onLeaveChannel(stats);
        }
    }

    @Override
    public void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) {
        for (EventHandler handler : mHandler) {
            handler.onFirstRemoteVideoDecoded(uid, width, height, elapsed);
        }
    }

    @Override
    public void onUserJoined(int uid, int elapsed) {
        for (EventHandler handler : mHandler) {
            handler.onUserJoined(uid, elapsed);
        }
    }

    @Override
    public void onUserOffline(int uid, int reason) {
        for (EventHandler handler : mHandler) {
            handler.onUserOffline(uid, reason);
        }
    }

    @Override
    public void onLocalVideoStats(IRtcEngineEventHandler.LocalVideoStats stats) {
        for (EventHandler handler : mHandler) {
            handler.onLocalVideoStats(stats);
        }
    }

    @Override
    public void onRtcStats(IRtcEngineEventHandler.RtcStats stats) {
        for (EventHandler handler : mHandler) {
            handler.onRtcStats(stats);
        }
    }

    @Override
    public void onNetworkQuality(int uid, int txQuality, int rxQuality) {
        for (EventHandler handler : mHandler) {
            handler.onNetworkQuality(uid, txQuality, rxQuality);
        }
    }

    @Override
    public void onRemoteVideoStats(IRtcEngineEventHandler.RemoteVideoStats stats) {
        for (EventHandler handler : mHandler) {
            handler.onRemoteVideoStats(stats);
        }
    }

    @Override
    public void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats) {
        for (EventHandler handler : mHandler) {
            handler.onRemoteAudioStats(stats);
        }
    }

    @Override
    public void onLastmileQuality(int quality) {
        for (EventHandler handler : mHandler) {
            handler.onLastmileQuality(quality);
        }
    }

    @Override
    public void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result) {
        for (EventHandler handler : mHandler) {
            handler.onLastmileProbeResult(result);
        }
    }
}

----------------------------------------------------------------------------------------------------------------------

public class EngineConfig {
    // private static final int DEFAULT_UID = 0;
    // private int mUid = DEFAULT_UID;

    private String nickName;
    private String mChannelName;
    private boolean mShowVideoStats;
    private int mDimenIndex = Constants.DEFAULT_PROFILE_IDX;
    private int mMirrorLocalIndex;
    private int mMirrorRemoteIndex;
    private int mMirrorEncodeIndex;



    public int getVideoDimenIndex() {
        return mDimenIndex;
    }

    public void setVideoDimenIndex(int index) {
        mDimenIndex = index;
    }

    public String getChannelName() {
        return mChannelName;
    }

    public void setChannelName(String mChannel) {
        this.mChannelName = mChannel;
    }

    public void setNicklName(String nickName) {
        this.nickName = nickName;
    }

    public String getNicklName() {
        return nickName;
    }

    public boolean ifShowVideoStats() {
        return mShowVideoStats;
    }

    public void setIfShowVideoStats(boolean show) {
        mShowVideoStats = show;
    }

    public int getMirrorLocalIndex() {
        return mMirrorLocalIndex;
    }

    public void setMirrorLocalIndex(int index) {
        mMirrorLocalIndex = index;
    }

    public int getMirrorRemoteIndex() {
        return mMirrorRemoteIndex;
    }

    public void setMirrorRemoteIndex(int index) {
        mMirrorRemoteIndex = index;
    }

    public int getMirrorEncodeIndex() {
        return mMirrorEncodeIndex;
    }

    public void setMirrorEncodeIndex(int index) {
        mMirrorEncodeIndex = index;
    }
}

----------------------------------------------------------------------------------------------------------------------

public interface EventHandler {
    void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed);

    void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats);

    void onJoinChannelSuccess(String channel, int uid, int elapsed);

    void onUserOffline(int uid, int reason);

    void onUserJoined(int uid, int elapsed);

    void onLastmileQuality(int quality);

    void onLastmileProbeResult(IRtcEngineEventHandler.LastmileProbeResult result);

    void onLocalVideoStats(IRtcEngineEventHandler.LocalVideoStats stats);

    void onRtcStats(IRtcEngineEventHandler.RtcStats stats);

    void onNetworkQuality(int uid, int txQuality, int rxQuality);

    void onRemoteVideoStats(IRtcEngineEventHandler.RemoteVideoStats stats);

    void onRemoteAudioStats(IRtcEngineEventHandler.RemoteAudioStats stats);
}

----------------------------------------------------------------------------------------------------------------------

라이브를 구현하기 앞서 기본 소스셋팅을 끝냈습니다.

다음 게시글에서는 본격적으로 라이브 작업을 해보도록 하겠습니다.

 

ㄱ ㅏ   ㄱ  푸시

ㅗ o    ㅗ  푸시 :D

728x90
반응형

댓글