Android 网易云信直播

Android集成网易云信直播

网易云官网
Android 网易云信直播_第1张图片Android 网易云信直播_第2张图片

集成Android端推流sdk

Android 网易云信直播_第3张图片
Android 网易云信直播_第4张图片

概述

设备要求:
	支持Android 4.3及以上系统
	
特性要求:
	支持推流到主流 RTMP 服务器
	支持 H.264 和 AAC 编码
	支持纯音频或者纯视频推流
	支持直播中伴音
	支持直播中单独暂停音频或者视频
	支持MP4录制
	支持显示推流统计信息
	支持自定义YUV、PCM输入
	支持YUV、PCM回调
	支持直播前测速
	支持音视频采集,编码,打包,传输
	支持 armv7、arm64 架构
	资源占用率低,库文件小
	画质清晰,延时低
	支持闪光灯开启操作(Flash)
	支持摄像头缩放操作(Zoom)
	支持前后置摄像头动态切换
	支持分辨率动态切换
	支持自动对焦
	支持视频本地预览镜像操作(主播)
	支持视频编码镜像操作(观众)
	支持视频截图
	支持怀旧、干净、自然、健康、复古等多款滤镜
	支持磨皮强度调节
	支持美白强度调节
	支持曝光度调节
	支持添加静态水印
	支持添加动态水印
	支持添加动态涂鸦
	支持关闭本地预览静态水印
	支持关闭本地预览动态水印
	支持清除水印
	支持视频流中带入时间戳(直播答题方案能力支持)
	支持时间戳透传设置开关以及获取当前音视频时间戳的接口

准备工作

已注册网易云信视频官网,并且已经申请开通云直播服务。
通过网易云信视频服务端接口调用,或官网管理控制台,创建频道并获取视频云直播推流地址。
下载最新版的直播推流SDK

集成SDK

Android 网易云信直播_第5张图片

网易Demo介绍
demo部分: 里面包含一个示例工程,实现了音视频直播推流功能,有完整的源代码。
docs目录: 包括 Livestreaming.jar 依赖包的 javadoc 文档,该文档详细说明了播放器 SDK 各接口 API 类的用法。
lib目录: 存放推流 SDK 的 Java 依赖包和底层动态链接库(支持armeabi-v7a、arm64-v8a平台架构),文件列表如下:
assets/filter目录: 存放滤镜资源文件,包括healthy_mask_1.jpg、filter_map_first.png 等图片文件
将这些文件拷贝到你的工程的对应目录下,同时将 Livestreaming-x.x.x.jar、VideoEffect-x.x.x.jar 加入工程,即可完成配			置。


libs:
├── armeabi-v7a
│   ├── liblivestreaming.so
│   ├── libvideoeffect.so
├── arm64-v8a
│   ├── liblivestreaming.so
│   ├── libvideoeffect.so
│
├── VideoEffect-x.x.x.jar (采集以及滤镜Java层代码)
└── Livestreaming-x.x.x.jar (编码推流Java层代码)

Android 网易云信直播_第6张图片

<!-- 权限声明 -->   
<!-- 允许挂载和反挂载文件系统 -->   
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />   
<!-- 允许程序向外部存储设备写数据 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 允许程序打开网络套接字 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 允许程序获取网络相关信息 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 允许程序向外部存储设备写数据 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 允许程序写音频数据 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 允许程序使用PowerManager WakeLocks以防止处理器休眠或者屏幕锁屏 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 允许程序获取Wifi网络状态信息 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 允许程序使用设备的camera -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 允许程序使用闪光灯 -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<!-- 允许程序获得设备信息 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-feature android:name="android.hardware.camera.autofocus"/>

<!-- 声明程序使用camera和自动对焦功能 -->
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>

public class WYCode {
     
    public static final int MSG_INIT_LIVESTREAMING_ERROR = 0;//初始化直播出错
    public static final int MSG_INIT_LIVESTREAMING_VIDEO_ERROR = 1; //初始化视频直播出错
    public static final int MSG_INIT_LIVESTREAMING_AUDIO_ERROR = 2; //初始化音频直播出错
    public static final int MSG_START_LIVESTREAMING_ERROR = 3;//开始直播出错
    public static final int MSG_STOP_LIVESTREAMING_ERROR = 4;//停止直播出错
    public static final int MSG_AUDIO_PROCESS_ERROR = 5;//音频编码打包出错
    public static final int MSG_VIDEO_PROCESS_ERROR = 6; //视频编码打包出错
    public static final int MSG_START_PREVIEW_ERROR = 7;//打开视频预览失败
    public static final int MSG_RTMP_URL_ERROR = 8; //RTMP URL连接出错,会进一步调用网络信息报警service,弹出悬浮窗
    public static final int MSG_URL_NOT_AUTH = 9; //RTMP URL非法
    public static final int MSG_SEND_STATICS_LOG_ERROR = 10; //发送统计日志出错
    public static final int MSG_SEND_HEARTBEAT_LOG_ERROR = 11;//发送心跳日志出错
    public static final int MSG_AUDIO_RECORD_ERROR = 12;//音频录制权限打开失败
    public static final int MSG_AUDIO_SAMPLE_RATE_NOT_SUPPORT_ERROR = 13;//设置的音频采样率不支持
    public static final int MSG_AUDIO_PARAMETER_NOT_SUPPORT_BY_HARDWARE_ERROR = 14;//设置的音频硬件编码参数不支持
    public static final int MSG_NEW_AUDIORECORD_INSTANCE_ERROR = 15;//音频采集实例创建失败
    public static final int MSG_AUDIO_START_RECORDING_ERROR = 16;//音频采集失败
    public static final int MSG_QOS_TO_STOP_LIVESTREAMING = 17;//网络QoS较差   
    public static final int MSG_HW_VIDEO_PACKET_ERROR = 18; //视频硬件编码出错
    public static final int MSG_WATERMARK_INIT_ERROR = 19; //视频水印初始化出错
    public static final int MSG_WATERMARK_PIC_OUT_OF_VIDEO_ERROR = 20; //视频水印超出原始视频
    public static final int MSG_WATERMARK_PARA_ERROR = 21; //视频水印参数出错
    public static final int MSG_CAMERA_PREVIEW_SIZE_NOT_SUPPORT_ERROR = 22; //摄像头不支持设置的preview size
    public static final int MSG_START_PREVIEW_FINISHED = 23; //开始preview完成
    public static final int MSG_START_LIVESTREAMING_FINISHED = 24; //开始直播完成
    public static final int MSG_STOP_LIVESTREAMING_FINISHED = 25; //停止直播完成
    public static final int MSG_STOP_VIDEO_CAPTURE_FINISHED = 26; //停止视频采集完成
    public static final int MSG_STOP_AUDIO_CAPTURE_FINISHED = 28; //停止音频采集完成
    public static final int MSG_SWITCH_CAMERA_FINISHED = 30; //切换摄像头完毕
    public static final int MSG_SEND_STATICS_LOG_FINISHED = 31; //发送统计信息完毕
    public static final int MSG_SERVER_COMMAND_STOP_LIVESTREAMING = 32; //服务器下发停止直播的命令
    public static final int MSG_SEND_HEARTBEAT_LOG_FINISHED = 33; //发送心跳信息完毕
    public static final int MSG_CAMERA_NOT_SUPPORT_FLASH = 34; //用户所设置的采集分辨率,摄像头并不支持
    public static final int MSG_GET_STATICS_INFO = 35; //获得统计信息完毕
    public static final int MSG_BAD_NETWORK_DETECT = 36; //连续一分钟视频帧率和码率都是0的消息
    public static final int MSG_SCREENSHOT_FINISHED = 37; //直播中视频截图完成消息
    public static final int MSG_SET_CAMERA_ID_ERROR = 38; //设置camera id出错(单摄像头设备常见)
    public static final int MSG_SET_GRAFFITI_ERROR = 39; //设置视频涂鸦出错
    public static final int MSG_MIX_AUDIO_FINISHED = 40; //伴音一首MP3文件结束
    public static final int MSG_URL_FORMAT_NOT_RIGHT = 41; //推流URL格式不正确(例如使用拉流url进行推流)
    public static final int MSG_URL_IS_EMPTY = 42; //推流URL为空
    public static final int MSG_VIDEO_CROP_ERROR = 43;  //视频剪裁失败
    public static final int MSG_SPEED_CALC_SUCCESS = 44; //测速成功
    public static final int MSG_SPEED_CALC_FAIL = 45; //测速失败
}


public class MainActivity extends AppCompatActivity {
     

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        boolean premisssion = checkPublishPermission();
    }


    /**   6.0权限处理     **/
    private boolean bPermission = false;
    private final int WRITE_PERMISSION_REQ_CODE = 100;
    private boolean checkPublishPermission() {
     
        if (Build.VERSION.SDK_INT >= 23) {
     
            List<String> permissions = new ArrayList<>();
            if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
     
                permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
            if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)) {
     
                permissions.add(Manifest.permission.CAMERA);
            }
            if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO)) {
     
                permissions.add(Manifest.permission.RECORD_AUDIO);
            }
            if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE)) {
     
                permissions.add(Manifest.permission.READ_PHONE_STATE);
            }
            if (permissions.size() != 0) {
     
                ActivityCompat.requestPermissions(MainActivity.this,
                        (String[]) permissions.toArray(new String[0]),
                        WRITE_PERMISSION_REQ_CODE);
                return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
     
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
     
            case WRITE_PERMISSION_REQ_CODE:
                for (int ret : grantResults) {
     
                    if (ret != PackageManager.PERMISSION_GRANTED) {
     
                        return;
                    }
                }
                bPermission = true;
                break;
            default:
                break;
        }
    }


    public void goToLive(View view) {
     
        startActivity(new Intent(this,MainActivity2.class));
    }
}
public class MainActivity2 extends AppCompatActivity {
     

    private lsMediaCapture mLSMediaCapture;
    private lsMediaCapture.LiveStreamingPara mLiveStreamingPara;
    private AppCompatSeekBar filter;
    private AppCompatSeekBar microdermabrasion;
    private float filerProgress = 0;
    private int microdermabrasionProgress = 0;
    private Button switch_camera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        filter = findViewById(R.id.filter);
        microdermabrasion = findViewById(R.id.microdermabrasion);
        switch_camera = findViewById(R.id.switch_camera);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        WindowManager.LayoutParams params = getWindow().getAttributes();
        params.screenBrightness = 0.7f;
        getWindow().setAttributes(params);


        initLiveSdk();

        setVideoUI();
    }

    private void setVideoUI() {
     
        filter.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
     
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
     
                //当进度条发生变化时调用该方法
                MainActivity2.this.filerProgress = progress;
                //滤镜强度
                if (mLSMediaCapture != null) {
     
                    float param;
                    param = (float)progress/100;
                    mLSMediaCapture.setFilterStrength(param);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
     
                // 开始滑动时调用该方法

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
     
                //结束滑动时调用该方法
                Toast.makeText(MainActivity2.this, "设置滤镜" + filerProgress + "%", Toast.LENGTH_SHORT).show();
            }
        });
        microdermabrasion.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
     
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
     
                //当进度条发生变化时调用该方法
                MainActivity2.this.microdermabrasionProgress = progress;
                //磨皮强度
                if (mLSMediaCapture != null) {
     
                    int param;
                    param = progress/20;
                    mLSMediaCapture.setBeautyLevel(param);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
     
                // 开始滑动时调用该方法
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
     
                //结束滑动时调用该方法
                Toast.makeText(MainActivity2.this, "设置磨皮" + microdermabrasionProgress + "%", Toast.LENGTH_SHORT).show();
            }
        });


    }

    private void initLiveSdk() {
     

        lsMediaCapture.LsMediaCapturePara lsMediaCapturePara = new lsMediaCapture.LsMediaCapturePara();
        lsMediaCapturePara.setContext(getApplicationContext()); //设置SDK上下文(建议使用ApplicationContext)
        lsMediaCapturePara.setMessageHandler(new lsMessageHandler() {
     
            @Override
            public void handleMessage(int i, Object o) {
     

            }
        }); //设置SDK消息回调
        lsMediaCapturePara.setLogLevel(lsLogUtil.LogLevel.INFO); //日志级别
        lsMediaCapturePara.setUploadLog(false);//是否上传SDK日志
        mLSMediaCapture = new lsMediaCapture(lsMediaCapturePara);



        NeteaseView videoView = (NeteaseView) findViewById(R.id.videoview);
        boolean frontCamera = true; // 是否前置摄像头
        boolean mScale_16x9 = false; //是否强制16:9
        lsMediaCapture.VideoQuality videoQuality = lsMediaCapture.VideoQuality.SUPER_HIGH; //视频模板(SUPER_HIGH 1280*720、SUPER 960*540、HIGH 640*480、MEDIUM 480*360、LOW 352*288)
        mLSMediaCapture.startVideoPreview(videoView, frontCamera, true, videoQuality, mScale_16x9);

        mLSMediaCapture.setBeautyLevel(1); //磨皮强度为5,共5档,0为关闭
        mLSMediaCapture.setFilterStrength(0.3f); //滤镜强度
        mLSMediaCapture.setFilterType(VideoEffect.FilterType.none);

        addDynamicWaterMark();//涂鸦
        addGraffiti();//水印

// SDK 默认提供 /** 标清 480*360 */MEDIUM, /** 高清 640*480 */HIGH,
// /** 超清 960*540 */SUPER,/** 超高清 (1280*720) */SUPER_HIGH  四个模板,
// 用户如果需要自定义分辨率可以调用startVideoPreviewEx 接口并参考以下参数
// 码率计算公式为 width * height * fps * 9 /100;

//        lsMediaCapture.VideoPara para = new lsMediaCapture.VideoPara();
//        para.setHeight(720);
//        para.setWidth(1280);
//        para.setFps(15);
//        para.setBitrate(1200*1024);
//        mLSMediaCapture.startVideoPreviewEx(videoView,frontCamera,false,para);

    }

    public void sendServiceIo(View view) {
     
        initLiveSdk();//防止   断开推流导致视频预览失效

        //开始推流   直播

        mLiveStreamingPara = new lsMediaCapture.LiveStreamingPara();
        mLiveStreamingPara.setStreamType(AV); // 推流类型 AV、AUDIO、VIDEO
        mLiveStreamingPara.setFormatType(RTMP); // 推流格式 RTMP、MP4、RTMP_AND_MP4
        mLiveStreamingPara.setRecordPath("");//formatType 为 MP4 或 RTMP_AND_MP4 时有效
        mLiveStreamingPara.setQosOn(true);
//mLiveStreamingPara.setSyncTimestamp(true,false);//(直播答题使用)网易云信透传时间戳,不依赖CDN方式,不需要额外开通(必须包含视频流)
//mLiveStreamingPara.setStreamTimestampPassthrough(true); //(直播答题使用)网易云信透传时间戳,但完全透传功能需要联系网易云信开通,支持纯音频
        mLSMediaCapture.initLiveStream(mLiveStreamingPara,url(推流地址));
        mLSMediaCapture.startLiveStreaming();
    }

    public void closeServiceIO(View view) {
     
        mLSMediaCapture.stopLiveStreaming();
        mLSMediaCapture.stopVideoPreview();
        mLSMediaCapture.destroyVideoPreview();
        //反初始化推流实例,当它与stopLiveStreaming连续调用时,参数为false
        mLSMediaCapture.uninitLsMediaCapture(false);
        Toast.makeText(this, "直播结束", Toast.LENGTH_SHORT).show();
        finish();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
     
        getMenuInflater().inflate(R.menu.filter_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
     
        switch (item.getItemId()) {
     
            case R.id.none://无滤镜
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.none);
                break;
            case R.id.clean://干净
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.clean);
                break;
            case R.id.fairytale://童话
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.fairytale);
                break;
            case R.id.nature://自然
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.nature);
                break;
            case R.id.healthy://健康
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.healthy);
                break;
            case R.id.tender://温柔
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.tender);
                break;
            case R.id.whiten://美白
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.whiten);
                break;
            case R.id.brooklyn://怀旧
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.brooklyn);
                break;
            case R.id.pixar://复古
                mLSMediaCapture.setFilterType(VideoEffect.FilterType.pixar);
                break;

        }
        if (mLSMediaCapture != null) {
     
            mLSMediaCapture.setFilterStrength(filerProgress / 100);
            mLSMediaCapture.setBeautyLevel(microdermabrasionProgress / 20);
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onDestroy() {
     
        super.onDestroy();
        mLSMediaCapture.stopLiveStreaming();
        mLSMediaCapture.stopVideoPreview();
        mLSMediaCapture.destroyVideoPreview();
        //反初始化推流实例,当它与stopLiveStreaming连续调用时,参数为false
        mLSMediaCapture.uninitLsMediaCapture(false);
        mGraffitiThread = null;
    }


    public void switch_camera(View view) {
     
        mLSMediaCapture.switchCamera();
    }

    private Thread mGraffitiThread;
    private boolean mGraffitiOn = false;
    private void addGraffiti(){
     
        if(mGraffitiThread != null){
     
            return;
        }
        mGraffitiOn = true;
        mGraffitiThread = new Thread(){
     
            @Override
            public void run() {
     
                int x = switch_camera.getRight();
                int y = switch_camera.getTop();
                while (mGraffitiOn && bitmaps != null && mLSMediaCapture != null){
     
                    for(Bitmap bitmap:bitmaps){
     
                        if(!mGraffitiOn){
     
                            break;
                        }
                        SystemClock.sleep(1000);
                        if(mLSMediaCapture != null){
     
                            mLSMediaCapture.setGraffitiPara(bitmap,x,y);
                        }
                    }
                }
            }
        };
        mGraffitiThread.start();
    }

    Bitmap[] bitmaps;
    private void addDynamicWaterMark(){
     
        if(mLSMediaCapture != null){
     
            int x = switch_camera.getRight() - 200;
            int y = switch_camera.getTop();
            int fps = 1; //水印的帧率
            boolean looped = true; //是否循环
            String[] waters;
            try {
     
                waters = getAssets().list("dynamicWaterMark");
                bitmaps = new Bitmap[waters.length];
                for(int i = 0; i< waters.length;i++){
     
                    waters[i] = "dynamicWaterMark/" + waters[i];
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                    Bitmap tmp = BitmapFactory.decodeStream(getAssets().open(waters[i]));
                    bitmaps[i] = tmp;
                }
                mLSMediaCapture.setDynamicWaterMarkPara(bitmaps,VideoEffect.Rect.center,x,y,fps,looped);
            } catch (IOException e) {
     
                e.printStackTrace();
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity2">

    <com.netease.vcloud.video.render.NeteaseView
        android:id="@+id/videoview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"/>

    <Button
        android:id="@+id/tui"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="开始推流"
        android:onClick="sendServiceIo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/close"
        android:text="结束推流"
        app:layout_constraintBottom_toTopOf="@id/tui"
        android:onClick="closeServiceIO"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/switch_camera"
        app:layout_constraintBottom_toTopOf="@id/close"
        android:onClick="switch_camera"
        android:text="切换摄像头"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/filter"
        app:layout_constraintBottom_toTopOf="@id/switch_camera"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.appcompat.widget.AppCompatSeekBar
        android:id="@+id/microdermabrasion"
        app:layout_constraintBottom_toTopOf="@id/filter"
        android:layout_marginBottom="20dp"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>


</androidx.constraintlayout.widget.ConstraintLayout>

网易云信api文档

你可能感兴趣的:(android,直播,android)