前言
七牛云直播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打印推流地址