2026年7月9日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

利用 Gemini 2.0 多模态实时 API 构建实时屏幕共享助手

发布日期:2025-02-07 20:49:23 浏览次数: 3687
作者:barry的异想世界

微信搜一搜,关注“barry的异想世界”

推荐语

掌握 Gemini 2.0 实时 API,构建高效屏幕共享助手。

核心内容:
1. Gemini 2.0 多模态实时 API 的优势与应用场景
2. 实时屏幕共享助手的前端与后端架构设计
3. 服务器代码实现及 Python 库的安装与使用

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

Gemini 开发教程 V3

在 Gemini 2.0 系列的最后几期教程中,我们建立了一个自托管的实时语音和视频聊天机器人的核心功能,并为其添加了功能调用特性,以便它能够调用外部工具和 API。这些都是具有快速响应、人性化互动和增强推理能力的实用应用,得益于 Gemini 2.0 多模态实时 API。

在本教程中,我们将重点关注模型的另一个实用应用,您可能已经在 Google AI Studio 中尝试过,并对其性能和用户体验感到惊讶。没错,我们将构建一个实时屏幕共享助手,它可以通过语音互动与您合作,并深入探讨前端和后端架构设计及代码实现。

Google AI Studio 为实验 Gemini 2.0 的多模态能力提供了一个很好的起点。在“实时流”功能中,“共享您的屏幕”块允许同时进行文本、音频和屏幕互动。然而,为了实现真正的可定制性,我们必须使用底层 API 构建自己的应用程序。

让我们现在开始吧!

架构

首先,让我们看看应用程序的整体架构。

我们的架构与之前一样,涉及双向 WebSocket 连接:一个在客户端和服务器之间,另一个在服务器和 Gemini API 之间。服务器充当中介,转发消息并管理实时流。更具体地说,服务器的代码与我们开发的基本多模态聊天机器人的前一个视频几乎相同。因此,如果您已经阅读过它,可以跳过这个快速回顾,直接进入客户端开发部分。

代码讲解 — 服务器

服务器使用 Python 实现,负责两个主要任务:处理客户端的 WebSocket 连接和管理 Gemini API 连接。

您需要安装并导入 WebSockets 和 google-genai 库。为模型 gemini-2.0-flash_exp 设置 API 密钥,并使用 API 版本 v1alpha 创建一个 Gemini 客户端。

#### pip install --upgrade google-genai==0.3.0##
import asyncio
import json
import os
import websockets
from google import genai
import base64

### 从环境中加载 API 密钥
os.environ['GOOGLE_API_KEY'] = ''
MODEL = "gemini-2.0-flash-exp"# 使用您的模型 ID

client = genai.Client(
  http_options={
    'api_version''v1alpha',
  }
)

在代码底部,我们定义了一个 websockets.serve 函数,以在指定端口上建立服务器。每个来自客户端的 WebSocket 连接都会触发处理程序 gemini_session_handler

async def main() -> None:
    async with websockets.serve(gemini_session_handler, "localhost"9083):
        print("正在运行 websocket 服务器 localhost:9083...")
        await asyncio.Future()  # 保持服务器无限运行


if __name__ == "__main__":
    asyncio.run(main())

在 gemini_session_handler 中,我们使用 client.aio.live.connect() 函数与 Gemini API 建立连接,配置数据包括来自客户端第一条消息的 response_modalities 和我们设置的 system_instruction,以指示模型充当屏幕共享助手。

之后,处理程序将专注于消息转发操作:

  1. 1. send_to_gemini 函数捕获来自客户端的消息,提取音频和图像数据,并将其发送到 Gemini API。
  2. 2. receive_from_gemini 函数监听来自 Gemini API 的响应,并解包文本或音频数据以发送给客户端。

为了实现真正的实时交互和中断启用,所有这些任务都在两个并行线程中异步处理。以下是代码:

async defgemini_session_handler(client_websocket: websockets.WebSocketServerProtocol):
    """在 websocket 会话中处理与 Gemini API 的交互。

    参数:
        client_websocket: 与客户端的 websocket 连接。
    """

    try:
        config_message = await client_websocket.recv()
        config_data = json.loads(config_message)
        config = config_data.get("setup", {})
        config["system_instruction"] = """您是屏幕共享会话的有用助手。您的角色是:
                                        1) 分析并描述共享屏幕上的内容
                                        2) 回答有关共享内容的问题
                                        3) 提供与所显示内容相关的信息和背景
                                        4) 协助处理与屏幕共享相关的技术问题
                                        5) 保持专业和乐于助人的语气。专注于简洁明了地回答。"""
     

        asyncwith client.aio.live.connect(model=MODEL, config=config) as session:
            print("已连接到 Gemini API")

            asyncdefsend_to_gemini():
                """将来自客户端 websocket 的消息发送到 Gemini API。"""
                try:
                  asyncfor message in client_websocket:
                      try:
                          data = json.loads(message)
                          if"realtime_input"in data:
                              for chunk in data["realtime_input"]["media_chunks"]:
                                  if chunk["mime_type"] == "audio/pcm":
                                      await session.send({"mime_type""audio/pcm""data": chunk["data"]})
                                      
                                  elif chunk["mime_type"] == "image/jpeg":
                                      await session.send({"mime_type""image/jpeg""data": chunk["data"]})
                                      
                      except Exception as e:
                          print(f"发送到 Gemini 时出错: {e}")
                  print("客户端连接关闭(发送)")
                except Exception as e:
                     print(f"发送到 Gemini 时出错: {e}")
                finally:
                   print("send_to_gemini 关闭")

            asyncdefreceive_from_gemini():
                """接收来自 Gemini API 的响应并将其转发给客户端,循环直到回合完成。"""
                try:
                    whileTrue:
                        try:
                            print("从 Gemini 接收")
                            asyncfor response in session.receive():
                                if response.server_content isNone:
                                    print(f'未处理的服务器消息! - {response}')
                                    continue

                                model_turn = response.server_content.model_turn
                                if model_turn:
                                    for part in model_turn.parts:
                                        ifhasattr(part, 'text'and part.text isnotNone:
                                            await client_websocket.send(json.dumps({"text": part.text}))
                                        elifhasattr(part, 'inline_data'and part.inline_data isnotNone:
                                            print("音频 mime_type:", part.inline_data.mime_type)
                                            base64_audio = base64.b64encode(part.inline_data.data).decode('utf-8')
                                            await client_websocket.send(json.dumps({
                                                "audio": base64_audio,
                                            }))
                                            print("音频已接收")

                                if response.server_content.turn_complete:
                                    print('\n<回合完成>')
                        except websockets.exceptions.ConnectionClosedOK:
                            print("客户端连接正常关闭(接收)")
                            break# 如果连接关闭,则退出循环
                        except Exception as e:
                            print(f"接收来自 Gemini 时出错: {e}")
                            break

                except Exception as e:
                      print(f"接收来自 Gemini 时出错: {e}")
                finally:
                      print("Gemini 连接关闭(接收)")

            # 启动发送循环
            send_task = asyncio.create_task(send_to_gemini())
            # 将接收循环作为后台任务启动
            receive_task = asyncio.create_task(receive_from_gemini())
            await asyncio.gather(send_task, receive_task)

    except Exception as e:
        print(f"Gemini 会话中出错: {e}")
    finally:
        print("Gemini 会话关闭。")

代码讲解 — 客户端

对于HTML和Javascript中的客户端开发,我们将重点关注上一个教程中原始前端代码的主要更改,您可以在我的 GitHub 仓库 中找到原始代码和修改后的代码。

图像处理

我们将首先探索此版本中的主要新功能,该功能通过实现 startScreenShare 函数,替代了之前使用网络摄像头的实现,改为屏幕共享功能。

startScreenShare

async function startScreenShare() {
            try {
                stream = await navigator.mediaDevices.getDisplayMedia({
                    video: {
                        width: { max640 },
                        height: { max480 },
                    },
                });

                video.srcObject = stream;
                await new Promise(resolve => {
                    video.onloadedmetadata = () => {
                        console.log("video loaded metadata");
                        resolve();
                    }
                });

            } catch (err) {
                console.error("Error accessing the screen: ", err);
            }
        }

这个异步函数利用 navigator.mediaDevices.getDisplayMedia() 方法获取屏幕捕获流。然后,将 HTML 视频元素的源设置为此流,并等待视频的元数据加载,确保后续操作可以安全地访问视频尺寸。

接下来,让我们检查 captureImage() 函数,该函数负责定期捕获视频帧并将其转换为 base64 编码的数据,以便传输到服务器。

captureImage

function captureImage() {
            if (stream && video.videoWidth > 0 && video.videoHeight > 0 && context) {
                canvas.width = 640;
                canvas.height = 480;
                context.drawImage(video, 00, canvas.width, canvas.height);
                const imageData = canvas.toDataURL("image/jpeg").split(",")[1].trim();
                currentFrameB64 = imageData;
            }
            else {
                console.log("no stream or video metadata not loaded");
            }
        }

此函数已修改为包含流检查,并在调用 drawImage() 方法之前验证视频元数据是否已加载。宽度和高度现在固定为 640x480。然后,我们将视频转换为 jpeg 的 base64 表示形式,以便发送到服务器。

定义了这两个函数后,以下是我们如何初始化屏幕共享功能和与 WebSocket 服务器的连接。

window.addEventListener("load"async () => {
  await startScreenShare();
  setInterval(captureImage, 3000);
  connect();
});

此事件监听器调用 startScreenShare 来设置用户显示器的初始视频流,设置一个每三秒调用一次 captureImage 的间隔,当然,您可以更改为更小的值,以根据屏幕操作频率获得更频繁的更新,并调用 WebSocket 连接功能,该功能基本保持不变。

音频处理

initializeAudioContext

async function initializeAudioContext() {
            if (initialized) return;

            audioInputContext = new (window.AudioContext ||
                window.webkitAudioContext)({
                sampleRate: 24000
            });
            await audioInputContext.audioWorklet.addModule("pcm-processor.js");
            workletNode = new AudioWorkletNode(audioInputContext, "pcm-processor");
            workletNode.connect(audioInputContext.destination);
            initialized = true;
        }

对于音频部分,我们还展示了音频工作单元初始化函数,该函数与之前版本保持不变,使用相同的 sampleRate 和 pcm-processor.js 文件中的 PCM 处理函数。

sendVoiceMessage

 function sendVoiceMessage(b64PCM) {
            if (webSocket == null) {
                console.log("websocket not initialized");
                return;
            }

            payload = {
                realtime_input: {
                    media_chunks: [{
                        mime_type: "audio/pcm",
                        data: b64PCM,
                    },
                    {
                        mime_type: "image/jpeg",
                        data: currentFrameB64,
                    },
                    ],
                },
            };

            webSocket.send(JSON.stringify(payload));
            console.log("sent: ", payload);
        }

sendVoiceMessage() 函数在将音频和图像 base64 数据发送到服务器之前进行打包。

receiveMessage

function receiveMessage(event) {
            const messageData = JSON.parse(event.data);
            const response = new Response(messageData);

            if (response.text) {
                displayMessage("GEMINI: " + response.text);
            }
            if (response.audioData) {
                injestAudioChuckToPlay(response.audioData);
            }
        }

客户端从服务器接收 JSON 格式的消息,解析后根据内容显示文本或播放音频。

sendInitialSetupMessage

function sendInitialSetupMessage() {

            console.log("sending setup message");
            setup_client_message = {
                setup: {
                    generation_config: { response_modalities: ["AUDIO"] },
                },
            };

            webSocket.send(JSON.stringify(setup_client_message));
        }

请不要忘记在客户端的第一次配置消息中选择您首选的响应方式。在这里,我选择了 AUDIO,当然您也可以选择 TEXT,以便在网页上查看文本输出。但请特别注意,即使它允许列表参数。目前,仅支持单个方式,如果您同时放入文本和音频,模型将只会返回错误。

现在,我们已经成功从网络摄像头实现转向屏幕共享实现,这使我们能够捕获可以发送给 Gemini 的屏幕流,以获得适当的响应。

运行应用程序

这种屏幕共享助手的最佳使用方式是帮助您完成日常工作,例如记笔记、浏览网页,甚至玩游戏。对我来说,最有趣的应用是帮助我进行日常论文研究工作。

让我们开始吧!

通过运行 Python 文件启动服务器。WebSocket 服务器将在我们在代码中定义的 8093 端口上运行。

通过运行以下命令启动客户端:

python -m http.server

现在我们可以在 8000 端口访问本地服务器。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅