最近朋友圈以及身邊很多朋友都在研究GPT開發,做了各種各樣的小工具小Demo,AI工具用起來是真的香!在他們的影響下,我也繼續搗鼓GPT Demo,希望更多的開發者加入一起多多交流。
上一篇結合即時通 IM SDK搗鼓了一個Demo ChatGPT群聊機器人Demo ,也收到了一些還不錯的反饋,有夥伴用於客服場景解放了一部分人工重複工作提升了效率。趁著週末我搗鼓了一個ChatGPT虛擬人直播,結合了同樣熱門的元宇宙技術。
本文將通過詳細開發流程來為大家舉例如何快速實現一個虛擬人直播,可用於無人直播場景。ChatGPT虛擬人可抓取直播中粉絲彈幕和評論並回復來進行使用者互動,提升直播間的活躍與使用者體驗。
最終實現效果如下所示:
GPT虛擬人直播
ChatGPT4.0虛擬人直播,首先得要有2個最基本的能力:ChatGPT4.0介面和虛擬人互動能力,這兩塊我們後面具體講。整個流程如下圖所示:
1.需要有具備文字驅動能力的虛擬人。具體來說,需要將文字轉為語音,然後虛擬人能根據語音對口型。這裡推薦使用即構Avatar,即構Avatar可以捏臉客製化,同時具備文字驅動口型能力。
2. 直播推流。抖音、快手可以用直播伴侶實時截圖推流。
3. 獲取實時彈幕/評論。虛擬人根據觀眾發的彈幕或者評論文字資料傳送給ChatGPT,可以去github找開原始碼。這裡提供一個最簡單的思路:在網頁版注入JS程式碼,js實時讀取網頁上的彈幕。
4. 呼叫ChatGPT。將實時彈幕/評論資料傳送給ChatGPT並獲取回覆。
5. 虛擬人播報ChatGPT的回覆。
目前openAI提供的介面是ChatGPT3.5,可以直接呼叫。bing在最新版Edge瀏覽器提供ChatGPT4.0體驗,有一些開源庫對其做了二次封裝,只需提供Cookie即可體驗。這裡我們把兩種方式都講解。
首先註冊bing賬號:
1.前往https://www.microsoft.com/zh-cn/edge/download?ch&form=MA13FJ 下載最新的Edge瀏覽器。
2. 註冊使用者或使用已有賬號登入,點選右上角「設定」圖示,將所在區域設定如下
3. 開啟Edge,輸入網址https://www.bing.com/search?form=MY0291&OCID=MY0291&q=Bing+AI&showconv=1
接下來在程式碼裡面封裝ChatGPT4.0介面,先安裝第三方基於Edge瀏覽器的庫:
npm i @waylaidwanderer/chatgpt-api
接下來二次封裝
import { BingAIClient } from '@waylaidwanderer/chatgpt-api';
export class BingGPT {
/*
* http_proxy, apiKey
**/
constructor(http_proxy, userCookie) {
this.api = this.init(http_proxy, userCookie);
}
init(http_proxy, userCookie) {
console.log(http_proxy, userCookie)
const options = {
// Necessary for some people in different countries, e.g. China (https://cn.bing.com)
host: 'https://www.bing.com',
// "_U" cookie from bing.com
userToken: userCookie,
// If the above doesn't work, provide all your cookies as a string instead
cookies: '',
// A proxy string like "http://<ip>:<port>"
proxy: http_proxy,
// (Optional) Set to true to enable `console.debug()` logging
debug: false,
};
return new BingAIClient(options);
}
//呼叫chatpgt
chat(text, cb) {
var res=""
var that = this;
console.log("正在向bing傳送提問", text )
this.api.sendMessage(text, {
toneStyle: 'balanced',
onProgress: (token) => {
if(token.length==2 && token.charCodeAt(0)==55357&&token.charCodeAt(1)==56842){
cb(true, res);
}
res+=token;
}
});
}
}
注意到,除了需要提供vpn地址以外,還需要提供cookie資料。cookie可以在Edge瀏覽器中按F12開啟開發工具,點選應用程式Tab,從左側Cookie中找到_U的value即為想要的cookie,如下圖所示:
因為ChatGPT4.0基於瀏覽器,非官方提供的API,整體會有些不太穩定。而OpenAI官方提供了ChatGPT3.5,相比之下更穩定。
首先要有OpenAI的ChatGPT賬號,在這裡我們不細講,不瞭解的同學可以參考我另一篇文章,連結: 《當我把ChatGPT拉進群聊裡,我的朋友都玩瘋了》接下來只需安裝ChatGPT庫:
npm install chatgpt
注意要使用nodejs 18.0以上版本安裝,因為國內的IP無法使用ChatGPT,因此必須要有VPN代理。再安裝一個代理庫:
npm install https-proxy-agent node-fetch
接下來,二次封裝一下:
import { ChatGPTAPI } from "chatgpt";
import proxy from "https-proxy-agent";
import nodeFetch from "node-fetch";
export class ChatGPT {
constructor(http_proxy, apiKey) {
this.api = this.init(http_proxy, apiKey);
this.conversationId = null;
this.ParentMessageId = null;
}
init(http_proxy, apiKey) {
console.log(http_proxy, apiKey)
return new ChatGPTAPI({
apiKey: apiKey,
fetch: (url, options = {}) => {
const defaultOptions = {
agent: proxy(http_proxy),
};
const mergedOptions = {
...defaultOptions,
...options,
};
return nodeFetch(url, mergedOptions);
},
});
}
//呼叫chatpgt
chat(text, cb) {
let that = this
console.log("正在向ChatGPT傳送提問:", text)
that.api.sendMessage(text, {
conversationId: that.ConversationId,
parentMessageId: that.ParentMessageId
}).then(
function (res) {
that.ConversationId = res.conversationId
that.ParentMessageId = res.id
cb && cb(true, res.text)
}
).catch(function (err) {
console.log(err)
cb && cb(false, err);
});
}
}
注意到除了需要提供VPN地址,還需要提供apiKey。登入OpenAI後,開啟連結https://platform.openai.com/account/api-keys 即可獲取apiKey,如下圖所示
虛擬人提供商有很多,由於目前我們不僅僅需要有虛擬形象,還需要虛擬人能夠說話(ChatGPT提供邏輯思維能力)。因此,我們這裡直接選用 即構Avatar,它內建了文字驅動能力,可以很方便將ChatGPT的回覆文字內容直接讀出來,並有口型匹配。非常適合我們這個場景。
官方提供了非常詳細的教學:官方教學地址,本文簡單講解一些關鍵實現,具體完整程式碼可以參考文末資源附件。
設定虛擬人形象:
private void setCharacter(User user) {
String sex = ZegoCharacterHelper.MODEL_ID_MALE;
if (!user.isMan) sex = ZegoCharacterHelper.MODEL_ID_FEMALE;
// 建立 helper 簡化呼叫
// base.bundle 是頭模, human.bundle 是全身人模
mCharacterHelper = new ZegoCharacterHelper(FileUtils.getPhonePath(mApp, "human.bundle", "assets"));
mCharacterHelper.setExtendPackagePath(FileUtils.getPhonePath(mApp, "Packages", "assets"));
// 設定形象設定
mCharacterHelper.setDefaultAvatar(sex);
// 角色上屏, 必須在 UI 執行緒, 必須設定過avatar形象後才可呼叫(用 setDefaultAvatar 或者 setAvatarJson 都可以)
mCharacterHelper.setCharacterView(user.avatarView, () -> {
});
mCharacterHelper.setViewport(ZegoAvatarViewState.half);
//設定頭髮、衣服等
mCharacterHelper.setPackage("ZEGO_Girl_Hair_0001");
mCharacterHelper.setPackage("ZEGO_Girl_Tshirt_0001_0002");
mCharacterHelper.setPackage("facepaint5");
mCharacterHelper.setPackage("irises2");
initTextApi();
updateUser(user);
}
可以看到,只需簡單幾行程式碼即可客製化虛擬人「面板」,使用非常友好。在文字驅動虛擬人說話方面就更簡單:
public void playText(String text) {
if (mTextApi == null) return;
mTextApi.playTextExpression(text);
}
當然了,這裡展示的是最關鍵程式碼。要想使用即構avatar還需要做一些權鑑認證、引擎初始化等工作,直接複用本文文末提供的程式碼附件即可。
需要注意的是,官方提供的Demo原始碼中只有基礎資源。換句話說,還有很多非常酷炫的「面板」可以使用。但是因為資源太大,不方便打包。如果讀者有更多資源需求,可以直接去官網找客服索要。
下面列出一些官方能提供的虛擬形象「面板」範例:
還有很多捏臉能力,這裡不一一描述,感興趣的同學可以直接去官網 (點選這裡)去下載體驗app,直接感受一下即構Avatar的強大。
對於B站、抖音、快手等第三方平臺,想要做虛擬直播得藉助官方提供的推流工具,如直播伴侶等。但官方沒有提供實時獲取直播間評論(或彈幕)的介面,當然了,你可以用一些技術手段來獲取,這裡不過多講解。接下來我們講解不用第三方平臺,自己簡單幾行程式碼實現搭建直播平臺。
搭建虛擬直播平臺有2個思路:
1.主播實時傳輸手機畫面,觀眾實時接收主播傳送的內容(影象),即構實時音視訊RTC 該方案實現 可檢視,RTC+Avatar方案可檢視。
我之前基於抖音平臺做的虛擬人直播Demo, B站、快手等第三方直播平臺的實現原理類似。
2.基於即構ZIM,實時傳輸文字(文字)
虛擬人直播與其他直播有一個差別是虛擬人是在使用者端上使用固定演演算法渲染出來的,而不是主播實時拍攝的畫面。因此我們完全不需要主播實時傳輸畫面(影象),只需通過廣播文字內容(相當於群組聊天),觀眾接收到文字內容後實時渲染虛擬人表情即可。這樣可以大大節省頻寬成本。
即構ZIM提供全面的IM能力,支援單聊/群聊/房間聊天,可根據實際業務自由組合IM能力。群聊訊息穩定可靠、延遲低,全球任何一個地區都有接入服務的節點保障訊息到達。 瞭解更多
從上面兩個方案可以看到,對於虛擬直播間來說,使用文字實時傳輸到每個端上,再本地渲染虛擬人畫面,可以做到最低成本。
具體來說,一個直播間可以通過一個房間(room)來管理,在即構IM的房間內,可以使用彈幕功能來實現收發文字。相比傳送聊天訊息,傳送彈幕訊息不會被儲存,更適合直播間評論功能。使用者建立直播間時,底層程式碼建立的是一個房間,並自動加入ChatGPT這個群成員。ChatGPT在群裡接收到資料時,呼叫ChatGPT介面,得到文字回覆後,再轉發到群裡。每個觀眾接收到ChatGPT回覆的文字後,自動渲染虛擬人播報文字內容。
同樣的,這裡我們只展示關鍵程式碼。建立房間、加入房間、收到實時評論(彈幕)。
建立房間、加入房間:
//建立房間
public void createRoom(String masterId, String roomId, String roomName, CB cb) {
ZIMRoomInfo groupInfo = new ZIMRoomInfo();
groupInfo.roomID = roomId;
groupInfo.roomName = roomName;
zim.createRoom(groupInfo, new ZIMRoomCreatedCallback() {
@Override
public void onRoomCreated(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
if (errorInfo.code == ZIMErrorCode.SUCCESS) {
inviteJoinRoom(masterId, roomId, CHATGPT_ID, cb);//這裡把chagpt的使用者id寫死
} else {
cb.complete(false, "房號已存在,請更換一個房間號!");
}
}
});
}
//加入房間
public void joinRoom(String roomId, CB cb) {
zim.joinRoom(roomId, new ZIMRoomJoinedCallback() {
@Override
public void onRoomJoined(ZIMRoomFullInfo roomInfo, ZIMError errorInfo) {
Log.e(TAG, ">>" + errorInfo.code);
if (errorInfo.code == ZIMErrorCode.ROOM_DOES_NOT_EXIST) {
cb.complete(false, "房間不存在!");
} else if (errorInfo.code == ZIMErrorCode.SUCCESS || errorInfo.code == ZIMErrorCode.THE_ROOM_ALREADY_EXISTS) {
cb.complete(true, roomInfo.baseInfo.roomName);
}
}
});
}
接收到訊息
//接收到文字訊息
@Override
public void onRcvMsg(Msg msg) {
switch (msg.proto) {//判斷訊息型別
case ChatGPT: {//來自ChatGPT的訊息,語音播報文字
mZegoMngr.getAvatarMngr().playText(msg.msg);
sendDM(msg.msg.replace("\n", ""), 0);
break;
}
case DanMu: {//收到彈幕訊息,直接在螢幕上展示
sendDM(msg.msg, msg.extInt);
break;
}
case DismissRoom: {//收到解散房間訊息,退出房間
mZegoMngr.getZIMMngr().leaveRoom(msg.msg, new CB() {
@Override
public void complete(boolean succ, String msg) {
ShowUtils.alert(RoomActivity.this, "提示", "房間已解散!", new ShowUtils.OnClickOkListener() {
@Override
public void onOk() {
back();
}
});
}
});
break;
}
}
}