来源 | OSCHINA 社区
作者 | 声网
原文链接:https://my.oschina.net/agora/blog/8367471
与以往的统计模型不行,ChatGPT 不是那种「一切都从语料统计里学习」的 AI,相反 ChatGPT 具备有临场学习的能力,业内称之为 in-context learning ,这也是为什么 ChatGPT 可以在上下文中学习的原因。ChatGPT 属于 AI 领域在商用技术上的重大突破,当然,本篇我们不是要讨论 ChatGPT 的实现逻辑,而是 ChatGPT 会怎么样加速我们的开发?
PS:在此之前有人通过指示在 ChatGPT 界面下实现了一个虚拟机,虽然这是一个极端的例子,但是可以很直观地感受到:「ChatGPT 对我们开发的影响是肉眼可见」。那 ChatGPT 在实际工作中是如何影响我们的开发?为了更直观,下面我们用一个开发场景来模拟这个流程。
「开发一个直播 app,是使用第三方 SDK 好还是自己从 0 开发好」?如下图所示,从回答上可以看到,AI 建议我们根据团队实际情况去选择,而在知晓「我的团队只有 5 个人」的情况后,它建议我选择采用 “接入第三方 SDK” 的方式更合理。那么选择 “接入第三方 SDK” ,接下来的问题就是:「选择做直播,在中国推荐使用哪些厂家的 SDK」?如下图所示,这个问题 ChatGPT 同样提供了多个选项,从选项里看 * 声网、腾讯云和阿里云 * 好像都符合我们要求,而在接着的「优势问题」对比上看,这三个选项都 “不相伯仲”,那我们就在再细化问题。假设我们希望直播可以有更多 “互动能力”,那么把问题修改为 「做互动直播,更推荐使用哪一个厂家的 SDK」 ,截图如下图所示,这次我们得到了更明确的答复,看来声网的 SDK 会更贴合我们的需求。为了更放心这个选择,我们通过 「声网 SDK 的优势」 和 「什么产品使用了声网 SDK」 两个问题进行提问,如下图所示,从回复上看声网作为一个全球化的厂家,在音视频领域还是值得相信。同时,还有包括小米、陌陌等产品都使用了声网的服务。那么就按照 AI 的建议选择声网 SDK 吧。
有没有发现,在获取资料的检索方式上,ChatGPT 确实比搜索引擎更直观且高效。那么敲定完 SDK ,接下来我们需要选择应用的开发框架,我们把需求限定在 Android 和 iOS,更好是能兼容 Web,覆盖整个移动端 ,因为团队人数不多,所以我们希望采用跨平台开发来节约成本,那么问题就是:
「移动端哪个跨平台框架更适合做直播」?如下图所示,得到的答案有 React Native 和 Flutter ,而恰好在 Flutter 回复里可以看到声网 SDK 的存在,所以我们可以敲定 App 开发框架就选 Flutter 了。最后,在开发之前,我们还需要继续提问 「如何获取声网 SDK 」 和 「使用声网 SDK 需要做什么」,这样我们就可以在开始开发之前提前准备好需要的东西。
关于注册获取 App ID 等步骤这里就省略了,毕竟目前这部分 ChatGPT 也无能为力。
为什么关键词是「视频通话」?因为它比直播场景更精准简单,生成的代码更靠谱(经过提问测试),而基于视频通话部分,后面我们可以快速拓展为互动直播场景;而指定版本是为了避免 AI 使用旧版本 API。从上门的代码生成可以看到,ChatGPT 生产的代码是自带中文注释,更贴心的是,如下图所示,在生成的代码末尾还给你解释了这段代码的实现逻辑,就像一个 “知心大姐姐”。
从这里也可以感觉到 ,ChatGPT 不是一个单纯的完全只会基于语料答复整合的 AI 。当然,直接复制生成的代码后会发现这段代码会报错,这和 ChatGPT 目前的模型数据版本有一定关系,所以针对生成的代码我们需要做一定手动调整,比如:
class VideoCallPage extends StatefulWidget {
final String channelName;
const VideoCallPage({Key? key, required this.channelName}) : super(key: key);
@override
_VideoCallPageState createState() => _VideoCallPageState();
}
class _VideoCallPageState extends State<VideoCallPage> {
late RtcEngine _engine;
bool _localUserJoined = false;
bool _remoteUserJoined = false;
int? rUid;
@override
void initState() {
super.initState();
initAgora();
}
@override
void dispose() {
_engine.leaveChannel();
super.dispose();
}
Future<void> initAgora() async {
await [Permission.microphone, Permission.camera].request();
_engine = createAgoraRtcEngine();
await _engine.initialize(RtcEngineContext(
appId: config.appId,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
_engine.registerEventHandler(RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {
_localUserJoined = true;
});
},
onUserJoined: (connection, remoteUid, elapsed) {
setState(() {
_remoteUserJoined = true;
rUid = remoteUid;
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
setState(() {
_remoteUserJoined = false;
rUid = null;
});
},
));
await _engine.enableVideo();
await _engine.startPreview();
await _engine.joinChannel(
token: config.token,
channelId: widget.channelName,
uid: config.uid,
options: const ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("VideoCallPage"),),
body: Center(
child: Stack(
children: [
_remoteUserJoined ? _remoteVideoView(rUid) : _placeholderView(),
_localUserJoined ? _localVideoView() : _placeholderView(),
],
),
),
);
}
Widget _placeholderView() {
return Container(
color: Colors.black,
);
}
Widget _remoteVideoView(id) {
return AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: id),
connection: RtcConnection(channelId: widget.channelName),
),
);
}
Widget _localVideoView() {
return Positioned(
right: 16,
bottom: 16,
width: 100,
height: 160,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
),
),
);
}
}
接下来,如下图所示,在将项目运行到手机和 PC 端之后,可以看到我们就完成了最简单的直播视频场景,而基于我们打算做直播的念头仅仅过去了 40 分钟,这其中还包含了注册声网账号和申请 App ID 的过程,我们通过简单的提问、复制、粘贴、修改,就完成了一个直播需求的 demo。
红色方块是后期加上的打码~那么到这里,虽然目前为止 demo 项目还不是互动直播,但是基于这个 demo 实现互动直播场景不会太难,因为你已经跑通了整个 SDK 的链路流程了。
这里为什么还强制写 agora_rtc_engine 6.1.0 ?因为如果不写,默认可能会输出 4.x 版本的老 API。尽管得到的答案并不是 Dart 代码而是 OC ,但是关键词 registerEventHandler 和 Message 我们捕抓到了,简单对比一下,就是 Flutter SDK 里的 registerEventHandler 对象,可以发现平替的接口就是 onStreamMessage 回调。那么接着就是弹出什么内容,因为我们没有素材,假设还没有设计师,那不如就让 ChatGPT 帮我们画一只兔子吧,不过测试结果并不好,如下图所示,从输出结果上看 ,这并不是我们想要的。
这里是我自己加的粉色,不然都是白色会糊成一坨,不得不说 ChatGPT 在绘制能力上 “很抽象”。所以 ChatGPT 有时候也不是很智能,可能目前在绘画理解上它还没那么成熟, 但是没问题, ChatGPT 是可以通过上下文学习 “调教” 的,比如我们觉得兔子的耳朵形状太离谱,那么我们可以让 ChatGPT 给我们调整。如下所示,虽然调整之后依然不对,但是比起一开始是不是好很多了?
这就是 ChatGPT 在每次会话上下文里学习的表现。然后我们在兔子耳朵的基础上再让 ChatGPT 补全兔子头,虽然最终的效果依然不理想,但是比起一开始已经进步了很多。同时我们还让 ChatGPT 给我们画了一个 “星星”,然后结合这两个 Canvas 绘制的素材,我们在代码里设置接收到 "兔子" 和 星星 文本的时候,就弹出一个放大动画效果。最终运行后效果如下 GIF 所示,看起来很简陋,但是要知道,我们只是经过了简单的复制 / 粘贴就完成了这样的效果,这难道不是开发效率的极大提高?
源码在后面。来自 ChatGPT 的兔子头代码:
class StarPaint extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: HeartPainter(),
size: Size(50, 50),
);
}
}
class StarPaint extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final path = Path();
final halfWidth = size.width / 2;
final halfHeight = size.height / 2;
final radius = halfWidth;
path.moveTo(halfWidth, halfHeight + radius);
path.arcToPoint(
Offset(halfWidth + radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight - radius),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth - radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight + radius),
radius: Radius.circular(radius),
clockwise: true,
);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant StarPaint oldDelegate) {
return false;
}
}
来自 ChatGPT 的星星代码:
class StarPaint extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: HeartPainter(),
size: Size(50, 50),
);
}
}
class StarPaint extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final path = Path();
final halfWidth = size.width / 2;
final halfHeight = size.height / 2;
final radius = halfWidth;
path.moveTo(halfWidth, halfHeight + radius);
path.arcToPoint(
Offset(halfWidth + radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight - radius),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth - radius, halfHeight),
radius: Radius.circular(radius),
clockwise: true,
);
path.arcToPoint(
Offset(halfWidth, halfHeight + radius),
radius: Radius.circular(radius),
clockwise: true,
);
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant StarPaint oldDelegate) {
return false;
}
}
自己补充的监听文本、发送文本和动画效果代码:
onStreamMessage: (RtcConnection connection, int remoteUid, int streamId,
Uint8List data, int length, int sentTs) {
var message = utf8.decode(data);
if (message == "兔子") {
showDialog(
context: context,
builder: (context) {
return AnimaWidget(Rabbit());
});
} else if (message == "星星") {
showDialog(
context: context,
builder: (context) {
return Center(
child: AnimaWidget(StarPaint()),
);
});
}
Future.delayed(Duration(seconds: 3), () {
Navigator.pop(context);
});
},
Future<void> _onPressSend() async {
try {
final streamId = await _engine.createDataStream(
const DataStreamConfig(syncWithAudio: false, ordered: false));
var txt = (Random().nextInt(10) % 2 == 0) ? "星星" : "兔子";
final data = Uint8List.fromList(utf8.encode(txt));
await _engine.sendStreamMessage(
streamId: streamId, data: data, length: data.length);
} catch (e) {
print(e);
}
}
class AnimaWidget extends StatefulWidget {
final Widget child;
const AnimaWidget(this.child);
@override
State<AnimaWidget> createState() => _AnimaWidgetState();
}
class _AnimaWidgetState extends State<AnimaWidget> {
double animaScale = 1;
@override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 1), () {
animaScale = 5;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: animaScale,
duration: Duration(seconds: 1),
curve: Curves.bounceIn,
child: Container(child: widget.child));
}
}
相信到这里大家应该可以感受到 ChatGPT 提高开发效率的魅力,甚至你还可以把 ChatGPT 集成到你的直播场景里,通过 Flutter 上的 chatgpt_api_client 插件,你可以在 App 里直接向 ChatGPT 提问,比如通过 OpenAI 的 API 实现一个可以互动的虚拟主播。
我怎么知道这个插件?肯定也是问 ChatGPT 的啊~
「当你抱怨 ChatGPT 鬼话连篇满嘴跑火车的时候,这可能有点像你看到一只猴子在沙滩上用石头写下 1+1=3。它确实算错了,但这不是重点。它有一天会算对的。」我相信 AI 并不是直接取代人类的方式,因为它对社会的挤压不是从水平上碾压,而是劣币驱逐良币,比如有位大佬就说过:「乙方最讨厌甲方什么都不懂还 bb,但乙方的议价权恰恰来源于甲方什么都不懂还 bb」 ,而现在 ChatGPT 在慢慢消磨掉整个议价权。总的来说「ChatGPT 只是一个产品,它不代表的整个技术的 “上限” ,它代表的是技术已经到达商用的临界点」。现在,它在慢慢成为开发圈子里的习惯,和曾经的 Copilot 一样,而同时它在其他领域如文字编排等的能力,甚至远超它在开发领域的价值。
END
这里有最新开源资讯、软件更新、技术干货等内容
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦