前言
七牛雲直播SDK疊代快,但是官方文檔跟不上疊代速度,導緻快速開始這部分文檔的還沒更新,很多被廢棄的類、方法還在文檔中,本文是基于2.3.0版本進行的快速開發,由于是第一次接觸直播,存在着許多不足歡迎批評指正。
- 官方文檔位址:https://developer.qiniu.com/pili/sdk/3718/PLDroidMediaStreaming-quick-start
- 官方Demo位址:https://github.com/pili-engineering/PLDroidMediaStreaming
- SDK位址:https://github.com/pili-engineering/PLDroidMediaStreaming/tree/master/releases
- 本文代碼位址:https://github.com/infoSafe/LiveBroadcastDemo
快速開始
- 建立項目(軟編要求 MinimumSDK API 15 ; 硬編要求 MinimumSDK API 18)本文使用的是18
- 導入SDK,在 app/src/main 目錄下建立 jniLibs 目錄。将檔案導入對應的目錄(如下圖)。
- 添加相關權限并注冊 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輸入推流位址
推流位址擷取方法
手機運作app,as列印推流位址