天天看點

七牛雲直播SDK之推流端快速開發

前言

   七牛雲直播SDK疊代快,但是官方文檔跟不上疊代速度,導緻快速開始這部分文檔的還沒更新,很多被廢棄的類、方法還在文檔中,本文是基于2.3.0版本進行的快速開發,由于是第一次接觸直播,存在着許多不足歡迎批評指正。

  1. 官方文檔位址:https://developer.qiniu.com/pili/sdk/3718/PLDroidMediaStreaming-quick-start
  2. 官方Demo位址:https://github.com/pili-engineering/PLDroidMediaStreaming
  3. SDK位址:https://github.com/pili-engineering/PLDroidMediaStreaming/tree/master/releases
  4. 本文代碼位址:https://github.com/infoSafe/LiveBroadcastDemo

快速開始

  •  建立項目(軟編要求 MinimumSDK API 15 ; 硬編要求 MinimumSDK API 18)本文使用的是18
  • 導入SDK,在 app/src/main 目錄下建立 jniLibs 目錄。将檔案導入對應的目錄(如下圖)。
七牛雲直播SDK之推流端快速開發
  • 添加相關權限并注冊 Activity
  • <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.live.demo">
    
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <uses-feature android:glEsVersion="0x00020000" android:required="true" />
    
        <application
            android:name=".StreamingApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".Activity.MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <activity
                android:name="com.qiniu.pili.droid.streaming.screen.ScreenCaptureRequestActivity"
                android:theme="@android:style/Theme.Translucent.NoTitleBar"></activity>
    
            <activity android:name=".Activity.AVStreamingActivity" />
            <activity android:name=".Activity.SWCameraStreamingActivity" />
        </application>
    
    </manifest>
               
  • 添加 happy-dns 依賴(
    compile 'com.qiniu:happy-dns:0.2.8'
               
  • 實作自己的 Application
    import android.app.Application;
    
    import com.qiniu.pili.droid.streaming.StreamingEnv;
    
    public class StreamingApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            /**
             * init must be called before any other func
             */
            StreamingEnv.init(getApplicationContext());
        }
    }
               
  • 建立主界面
    package com.live.demo.Activity;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.TextView;
    
    import com.live.demo.R;
    import com.live.demo.Util.Util;
    
    import java.util.UUID;
    
    public class MainActivity extends Activity  implements View.OnClickListener{
    
        private static final String TAG = "MainActivity";
    
        private static final String GENERATE_STREAM_TEXT = "http://api-demo.qnsdk.com/v1/live/stream/";
    
        private TextView tv_start;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
    
        public void initView(){
            tv_start = findViewById(R.id.tv_start);
            tv_start.setOnClickListener(this);
        }
    
    
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                  final String tlpath =   genPublishURL();
    //                video_Audio
                    Intent intent = new Intent(MainActivity.this, SWCameraStreamingActivity.class);
    //                傳推流位址
                    intent.putExtra("stream_publish_url", tlpath);
                    startActivity(intent);
    
                }
            }).start();
        }
    
    
        /**
         * @author: 
         * @create at: 2018/11/13  13:01
         * @Description: 擷取推流位址
         */
        private String genPublishURL() {
            String publishUrl = Util.syncRequest(GENERATE_STREAM_TEXT + UUID.randomUUID());
            return publishUrl;
        }
    }
               
  • 主界面布局檔案
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        tools:context=".demo.Activity.MainActivity">
    
        <TextView
            android:id="@+id/tv_start"
            android:text="開始推流"
            android:gravity="center"
            android:textSize="18sp"
            android:layout_marginTop="40dp"
            android:textColor="@color/textColorPrimary"
            android:background="@color/colorHeader"
            android:layout_width="160dp"
            android:layout_height="50dp" />
    
    </LinearLayout>
               
  • 建立推流界面
    package com.live.demo.Activity;
    
    import android.app.Activity;
    import android.hardware.Camera;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.TextView;
    
    import com.live.demo.R;
    import com.live.demo.ui.CameraPreviewFrameView;
    import com.qiniu.android.dns.DnsManager;
    import com.qiniu.android.dns.IResolver;
    import com.qiniu.android.dns.NetworkInfo;
    import com.qiniu.android.dns.http.DnspodFree;
    import com.qiniu.android.dns.local.AndroidDnsServer;
    import com.qiniu.android.dns.local.Resolver;
    import com.qiniu.pili.droid.streaming.CameraStreamingSetting;
    import com.qiniu.pili.droid.streaming.MediaStreamingManager;
    import com.qiniu.pili.droid.streaming.StreamStatusCallback;
    import com.qiniu.pili.droid.streaming.StreamingProfile;
    import com.qiniu.pili.droid.streaming.StreamingState;
    import com.qiniu.pili.droid.streaming.StreamingStateChangedListener;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.URISyntaxException;
    
    import static com.qiniu.pili.droid.streaming.AVCodecType.SW_VIDEO_WITH_SW_AUDIO_CODEC;
    
    public class SWCameraStreamingActivity extends Activity implements StreamingStateChangedListener,
            StreamStatusCallback  {
        private static final String TAG = "LiveAcivity";
        private MediaStreamingManager mMediaStreamingManager;
        private StreamingProfile mProfile;
        private  CameraPreviewFrameView cameraPreviewFrameView;
        private TextView mStatView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_swcamera_streaming);
            initView();
        }
    
        public void initView(){
           cameraPreviewFrameView = findViewById(R.id.cameraPreview_surfaceView);
            mStatView =  findViewById(R.id.stream_status);
            String publishURLFromServer = getIntent().getStringExtra("stream_publish_url");
            Log.d(TAG, "initView: ===>"+publishURLFromServer);
                mProfile = new StreamingProfile();
            try {
                mProfile.setVideoQuality(StreamingProfile.VIDEO_QUALITY_HIGH1)
                        .setAudioQuality(StreamingProfile.AUDIO_QUALITY_MEDIUM2)
                        .setQuicEnable(false)//RMPT or QUIC
                        .setVideoQuality(StreamingProfile.VIDEO_QUALITY_MEDIUM1)
                        .setEncodingOrientation(StreamingProfile.ENCODING_ORIENTATION.PORT )//橫豎屏
                        .setEncodingSizeLevel(StreamingProfile.VIDEO_ENCODING_HEIGHT_480)
                        .setBitrateAdjustMode(StreamingProfile.BitrateAdjustMode.Auto)//自适應碼率
                        .setEncoderRCMode(StreamingProfile.EncoderRCModes.QUALITY_PRIORITY)
                        .setDnsManager(getMyDnsManager())
                        .setStreamStatusConfig(new StreamingProfile.StreamStatusConfig(3))
                        .setSendingBufferProfile(new StreamingProfile.SendingBufferProfile(0.2f, 0.8f, 3.0f, 20 * 1000))
                        .setPublishUrl(publishURLFromServer);//設定推流位址
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
            CameraStreamingSetting setting = new CameraStreamingSetting();
                setting.setCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT) // 攝像頭切換
                        .setContinuousFocusModeEnabled(true)
                        .setFaceBeautySetting(new CameraStreamingSetting.FaceBeautySetting(1.0f, 1.0f, 1.0f))//美顔 磨皮,美白,紅潤 取值範圍為[0.0f, 1.0f]
                        .setVideoFilter(CameraStreamingSetting.VIDEO_FILTER_TYPE.VIDEO_FILTER_BEAUTY)
                        .setCameraPrvSizeLevel(CameraStreamingSetting.PREVIEW_SIZE_LEVEL.MEDIUM)
                        .setCameraPrvSizeRatio(CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_16_9);
                mMediaStreamingManager = new MediaStreamingManager(this, cameraPreviewFrameView, SW_VIDEO_WITH_SW_AUDIO_CODEC);
                mMediaStreamingManager.prepare(setting, mProfile);
                mMediaStreamingManager.setStreamingStateListener(this);
    
        }
    
        @Override
        public void onStateChanged(StreamingState streamingState, Object o) {
            switch (streamingState) {
                case PREPARING:
                    Log.d(TAG, "onStateChanged: ===>"+"準備");
                    break;
                case READY:
                    startStreaming();
                    break;
                case CONNECTING:
                    Log.d(TAG, "onStateChanged: ===>"+"連接配接");
                    break;
                case STREAMING:
                    Log.d(TAG, "onStateChanged: ===>"+"已發送");
                    break;
                case SHUTDOWN:
                    Log.d(TAG, "onStateChanged: ===>"+"推流結束");
                    break;
                case IOERROR:
                    Log.d(TAG, "onStateChanged: ===>"+"IO異常");
                    break;
                case SENDING_BUFFER_EMPTY:
                    break;
                case SENDING_BUFFER_FULL:
                    break;
                case AUDIO_RECORDING_FAIL:
                    break;
                case OPEN_CAMERA_FAIL:
                    break;
                case DISCONNECTED:
                    Log.d(TAG, "onStateChanged: ===>"+"斷開連接配接");
                    break;
            }
        }
    
        /**
         * @author: 
         * @create at: 2018/11/14  15:32
         * @Description: 開始推流
         */
        protected void startStreaming() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mMediaStreamingManager.startStreaming();
                    Log.d(TAG, "run: ===>"+"推流");
                }
            }).start();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            mMediaStreamingManager.resume();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mMediaStreamingManager.pause();
        }
    
        /**
         * @author: 
         * @create at: 2018/11/14  10:57
         * @Description: 防止 Dns 被劫持
         */
        private static DnsManager getMyDnsManager() {
            IResolver r0 = null;
            IResolver r1 = new DnspodFree();
            IResolver r2 = AndroidDnsServer.defaultResolver();
            try {
                r0 = new Resolver(InetAddress.getByName("119.29.29.29"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return new DnsManager(NetworkInfo.normal, new IResolver[]{r0, r1, r2});
        }
    
    
        @Override
        public void notifyStreamStatusChanged(final StreamingProfile.StreamStatus streamStatus) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "run:totalAVBitrate ===>"+streamStatus.totalAVBitrate);
                    Log.d(TAG, "run:audioFps ===>"+streamStatus.audioFps);
                    Log.d(TAG, "run:videoFps ===>"+streamStatus.videoFps);
                    mStatView.setText("bitrate:" + streamStatus.totalAVBitrate / 1024 + " kbps"
                            + "\naudio:" + streamStatus.audioFps + " fps"
                            + "\nvideo:" + streamStatus.videoFps + " fps");
                }
            });
        }
    
    
    }
               

推流界面布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.live.demo.Activity.SWCameraStreamingActivity">

    <com.live.demo.ui.CameraPreviewFrameView
        android:id="@+id/cameraPreview_surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left|top"
        android:gravity="center_vertical"
        android:orientation="vertical">

        <include
            layout="@layout/status"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>


</RelativeLayout>
           

啟動 APP 之後,當點選 開始推流,就可以開始推流了。

測試直播效果

1.下載下傳播放端的Demo運作輸入推流位址 :https://github.com/pili-engineering/PLDroidPlayer

2.或者掃碼下載下傳播放app輸入推流位址

七牛雲直播SDK之推流端快速開發

推流位址擷取方法

  手機運作app,as列印推流位址