Android实现录屏直播(二)需求才是硬道理之产品功能调研

请尊重分享成果,转载请注明出处,本文来自Coder包子哥,原文链接:http://blog.csdn.net/zxccxzzxz/article/details/54254244

前面的Android实现录屏直播(一)ScreenRecorder的简单分析一文中我们对 ScreenRecorder 这个开源 Demo 中的实现机制大概有了了解,但在继续写这个系列文章的时候发现每一个细节都太紧密了,稍微不注意就会深入每个知识点的细节导致文章又臭又长还表述不清晰,于是我决定把这7天实现该功能的整个流程重新梳理一遍,按照我开发和研究学习的步骤来写,大致过程如下:

  1. 产品功能调研
  2. Bilibili 的反编译及 UI 的编写
  3. ScreenRecorder 等 Demo 的代码分析
  4. 对 H264 结构、FLV 格式封装的研究学习
  5. sps pps avcc 关键帧等视频封装原理的学习与分析
  6. MediaProjection 实现录屏中 MediaCodec 的详细用法
  7. 编码后的帧进行推流

产品功能调研

我们作为技术开发人员,任务下发的时候首先要与产品经理进行需求的深入了解,只有了解对方想要的是什么后我们功能实现才能达到他们最大的期望值。当然一旦确定需求后把菜刀亮出来,然后就轻松愉快的写代码吧😜。嗯,本次任务就是尽可能的还原Bilibili的录屏直播功能,汗颜,无需设计,无需讨论,我自己研究吧,反正项目一直都是我一人开发,也习惯了(PS: 尽管是Bilibili的忠实用户,当然不能在工作的时候看番剧啦),废话不多说,瞄准几个功能:

  • 直播悬浮窗
  • 直播提示通知栏
  • 录屏直播的实现机制

直播悬浮窗

说起悬浮窗不得不感谢郭神的博客 Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果,就是懒直接拿来修修改改就行(郭神别打我~),实现效果如图

直播悬浮窗

悬浮窗包含一个自定义的View(继承自FrameLayout)、服务和自定义WindowManager,更多细节请见源码,代码就不贴出来了,太占地方。值得注意的是,这里的悬浮窗我添加了一个弹幕显示的ListView面板用于显示用户的发言。

直播提示通知栏

由于直接录制桌面涉及到用户的个人隐私问题,如果没有一个标识让用户看着那问题就麻烦了(亲测过通过这种录屏方式可以捕获桌面所有的内容,无论是键盘输入状态、摄像头拍照摄像及输入银行卡密码等等),所以Android系统虽然会提示需要进行屏幕录像,但还是有一定的风险!通知栏属于常驻类型,内容如下:

录屏通知栏

通知栏的实现

先看看下面这个服务类,主要功能就是开启通知并创建悬浮窗两个功能,注意创建悬浮窗时判断当前设备是否为我们的APP授权了悬浮窗权限这个功能我还没有来得及实现,小米、魅族华为等系统需要手动去APP设置中授权,之后有时间再补上。如需此服务与Activity之间有通信机制的话,还需要自定义Binder,在启动服务的Activity中通过bindService()与ServiceConnection来实现数据的传递,这里先挖一个坑,后续我完善功能后再回来填上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.os.Binder;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class ScreenRecordListenerService extends Service {
private static final String TAG = "ScreenRecordListenerService, ";
private static final int PENDING_REQUEST_CODE = 0x01;
private static final int NOTIFICATION_ID = 3;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder builder;
private Handler handler = new Handler();

@Override
public void onCreate() {
super.onCreate();
initNotification();
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗。
if (!MyWindowManager.isWindowShowing()) {
handler.post(new Runnable() {
@Override
public void run() {
MyWindowManager.createSmallWindow(getApplicationContext());
}
});
}
return super.onStartCommand(intent, flags, startId);
}


private void initNotification() {
builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(getResources().getString(R.string.app_name))
.setContentText("您正在录制视频内容哦")
.setOngoing(true)
.setDefaults(Notification.DEFAULT_VIBRATE);

Intent backIntent = new Intent(this, RecordActivityV2.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, PENDING_REQUEST_CODE, backIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotificationManager.notify(NOTIFICATION_ID, builder.build());
}

@Override
public void onDestroy() {
super.onDestroy();
if (mNotificationManager != null) {
mNotificationManager.cancel(NOTIFICATION_ID);
}
MyWindowManager.removeSmallWindow(getApplicationContext());
}
}

上面的代码我们可以看出服务启动后进行了Notification的初始化,我这里按照Bilibili的样式去实现的。在Activity中当启动直播时,Bilibili是通过moveTaskToBack(true)直接回到桌面的,所以我猜想开启通知栏和悬浮窗服务应该是在Activity生命周期的onStop()启动和onResume()关闭,当然这些都是业务逻辑可自己定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
protected void onResume() {
super.onResume();
if (isRecording) stopScreenRecordService();
}

@Override
protected void onStop() {
super.onStop();
if (isRecording) startScreenRecordService();
}
private void startScreenRecordService() {
if (mRecorder != null && mRecorder.getStatus()) {
Intent runningServiceIT = new Intent(this, ScreenRecordListenerService.class);
// bindService(runningServiceIT, connection, BIND_AUTO_CREATE);
startService(runningServiceIT);
}
}

private void stopScreenRecordService() {
Intent runningServiceIT = new Intent(this, ScreenRecordListenerService.class);
stopService(runningServiceIT);
if (mRecorder != null && mRecorder.getStatus()) {
Toast.makeText(this, "现在正在进行录屏直播哦", Toast.LENGTH_SHORT).show();
}
}

最后别忘了在AndroidManifest.xml中进行Service的注册和对悬浮窗的授权。

1
2
3
4
5
6
7
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
<service
android:name=".service.ScreenRecordListenerService"
android:enabled="true"
android:exported="false" />
</application>

关于悬浮窗与通知栏差不多就介绍到这里了,更多细节请查看源码,本Demo源码都是基于上篇博客提到的ScreenRecorder实现的。

我们来看看最终效果吧:

效果图

录屏直播的实现机制

使用MediaProjection与VirtualDisplay等Android 5.0以上的API实现的,在此就不多说了,请看上篇博客吧。

尾语

果然还是有个提纲要好下笔的多,至少知道写些什么。因为最近文章写的都有点赶,有疑问的朋友还请留言指正!接下来准备写一下MediaProjection 实现录屏中 MediaCodec 的详细用法,至于反编译Bilibili的过程就不写了,反编译为的是参考别人的思路,并不是为了做代码小偷,这点职业操守还是有的,如果说业务上有盗窃之意请出门左转找我们的产品经理,我是无辜的码农[笑]。之后会在别的文章中提及一下我个人使用反编译时的一些心得和小技巧吧。别的主题都还需要花些时间再看看,毕竟都是工作时候囫囵吞枣接触的,理论知识可不能瞎糊弄。

Demo源码

Demo源码都在我的GitHub仓库中,有需要的朋友可以去 clonefork,如对您有帮助还请给个Star,十分感谢~

参考文档