【Unity中Pico设备(Android平台)实现录音和回放功能的解决方案】

Unity调起手机的麦克风进行录音并保存为wav 1、对麦克风的调用在Unity里主要是用到了MicroPhone这个类,此类里面有几个方法可以方便我们实现功能。// 开始录音,获取AudioClip。// 合并WAV文件头和音频数据。// 获取手机麦克风的名称。// 保存录音到WAV文件。// 等待麦克风准备好。// 录音60秒后停止。// 创建WAV文件头。 阅读详情

由于项目需求,需要做个在Pico里录音和回放的功能,但没要求持久化存储,那么直接放内存里一次性;
最后使用Unity的Microphone类和AudioSource组件实现
UI部分根据代码建立即可,我这就随便建了一个很简单的
在这里插入图片描述
在这里插入图片描述

在做的过程中还是出现了一些bug
以下是优化完之后的代码,有兴趣的朋友可以在我的基础上再优化扩展

using UnityEngine;
using UnityEngine.Android;
using UnityEngine.UI;
using System.Collections;

public class OptimizedAudioRecorder : MonoBehaviour
{
    [Header("UI References")]
    public Button recordButton;
    public Button stopRecordButton;
    public Button playButton;
    public Button stopPlayButton;
    public Text durationText;         // 显示当前设置的录音时长
    public Text elapsedTimeText;      // 显示已录音时长
    public Text statusText;

    [Header("Settings")]
    public float minDuration = 1f;    // 最小录音时长(秒)
    public float maxDuration = 60f;   // 最大录音时长(秒)
    public float updateInterval = 0.1f; // 时长更新频率(秒)

    private AudioSource audioSource;
    private AudioClip recordedClip;
    private bool isRecording = false;
    private bool isPlaying = false;
    private string microphoneDevice;
    private float currentDuration = 10f; // 当前设置的录音时长
    private float recordingStartTime;   // 录音开始时间
    private Coroutine recordingTimerCoroutine; // 录音计时协程
    private Coroutine playbackTimerCoroutine;  // 播放计时协程
    private float actualRecordedDuration = 0f; // 实际录音时长

    void Start()
    {
        audioSource = gameObject.AddComponent<AudioSource>();
        audioSource.playOnAwake = false;

        currentDuration = Mathf.Clamp(currentDuration, minDuration, maxDuration);

        InitializeUI();

        CheckMicrophonePermission();

        if (Microphone.devices.Length > 0)
        {
            microphoneDevice = Microphone.devices[0];
            statusText.text = "准备就绪";
        }
        else
        {
            statusText.text = "未检测到麦克风";
            SetButtonsInteractable(false);
        }

        UpdateDurationDisplay();
    }

    /// <summary>
    /// 初始化UI组件和事件监听
    /// </summary>
    void InitializeUI()
    {
        recordButton.onClick.AddListener(() => StartRecording(currentDuration));
        stopRecordButton.onClick.AddListener(StopRecording);
        playButton.onClick.AddListener(PlayRecording);
        stopPlayButton.onClick.AddListener(StopPlayback);

        stopRecordButton.interactable = false;
        stopPlayButton.interactable = false;
        playButton.interactable = false;

        elapsedTimeText.text = "已录音: 0.0s";
    }

    /// <summary>
    /// 检查并请求麦克风权限
    /// </summary>
    void CheckMicrophonePermission()
    {
        if (!Permission.HasUserAuthorizedPermission(Permission.Microphone))
        {
            Permission.RequestUserPermission(Permission.Microphone);
            statusText.text = "请求麦克风权限...";
        }
    }

    /// <summary>
    /// 更新时长显示
    /// </summary>
    void UpdateDurationDisplay()
    {
        durationText.text = $"设置时长: {currentDuration:F1}秒";
    }

    /// <summary>
    /// 开始录音
    /// </summary>
    /// <param name="duration">录音时长(秒)</param>
    public void StartRecording(float duration)
    {
        if (isRecording) return;

        if (!Permission.HasUserAuthorizedPermission(Permission.Microphone))
        {
            statusText.text = "麦克风权限未授予!";
            return;
        }

        if (recordedClip != null)
        {
            Destroy(recordedClip);
            recordedClip = null;
        }

        try
        {
            recordedClip = Microphone.Start(
                microphoneDevice,
                false,
                Mathf.CeilToInt(duration),
                44100
            );

            isRecording = true;
            recordingStartTime = Time.time;
            actualRecordedDuration = 0f; 

            if (recordingTimerCoroutine != null)
                StopCoroutine(recordingTimerCoroutine);
            recordingTimerCoroutine = StartCoroutine(UpdateRecordingTime());

            statusText.text = $"录音中... (最长: {duration:F1}秒)";
            recordButton.interactable = false;
            stopRecordButton.interactable = true;
            playButton.interactable = false;
            stopPlayButton.interactable = false;
        }
        catch (System.Exception e)
        {
            statusText.text = $"录音启动失败: {e.Message}";
            isRecording = false;
            recordButton.interactable = true;
            stopRecordButton.interactable = false;
        }
    }

    /// <summary>
    /// 停止录音
    /// </summary>
    public void StopRecording()
    {
        if (!isRecording) return;

        try
        {
            Microphone.End(microphoneDevice);
            isRecording = false;

            if (recordingTimerCoroutine != null)
            {
                StopCoroutine(recordingTimerCoroutine);
                recordingTimerCoroutine = null;
            }

            statusText.text = $"录音已停止 (实际: {actualRecordedDuration:F1}秒)";
            recordButton.interactable = true;
            stopRecordButton.interactable = false;
            playButton.interactable = true;

            elapsedTimeText.text = $"已录音: {actualRecordedDuration:F1}s";
        }
        catch (System.Exception e)
        {
            statusText.text = $"停止录音失败: {e.Message}";
        }
    }

    /// <summary>
    /// 播放录音
    /// </summary>
    public void PlayRecording()
    {
        if (recordedClip == null)
        {
            statusText.text = "没有可播放的录音";
            return;
        }

        try
        {
            audioSource.Stop();
            audioSource.clip = recordedClip;
            audioSource.Play();
            isPlaying = true;

            if (playbackTimerCoroutine != null)
                StopCoroutine(playbackTimerCoroutine);
            playbackTimerCoroutine = StartCoroutine(UpdatePlaybackTime());

            statusText.text = "播放中...";
            playButton.interactable = false;
            stopPlayButton.interactable = true;
            recordButton.interactable = false;
        }
        catch (System.Exception e)
        {
            statusText.text = $"播放失败: {e.Message}";
        }
    }

    /// <summary>
    /// 停止播放
    /// </summary>
    public void StopPlayback()
    {
        if (!isPlaying) return;

        try
        {
            audioSource.Stop();
            isPlaying = false;

            if (playbackTimerCoroutine != null)
            {
                StopCoroutine(playbackTimerCoroutine);
                playbackTimerCoroutine = null;
            }

            statusText.text = "播放已停止";
        }
        catch (System.Exception e)
        {
            statusText.text = $"停止播放失败: {e.Message}";
        }
        finally
        {
            playButton.interactable = true;
            stopPlayButton.interactable = false;
            recordButton.interactable = true;
        }
    }

    /// <summary>
    /// 更新录音时长的协程
    /// </summary>
    private IEnumerator UpdateRecordingTime()
    {
        float startTime = Time.time;

        while (isRecording)
        {
            float elapsedTime = Time.time - startTime;
            actualRecordedDuration = elapsedTime;
            Debug.Log(actualRecordedDuration);

            elapsedTimeText.text = $"已录音: {elapsedTime:F1}s";

            if (elapsedTime >= currentDuration)
            {
                StopRecording();
                yield break;
            }

            yield return new WaitForSeconds(updateInterval);
        }
    }

    /// <summary>
    /// 更新播放进度的协程
    /// </summary>
    private IEnumerator UpdatePlaybackTime()
    {
        float startTime = Time.time;
        float playbackDuration = actualRecordedDuration > 0 ? actualRecordedDuration : recordedClip.length;

        while (isPlaying && audioSource.isPlaying)
        {
            float elapsedTime = Time.time - startTime;

            elapsedTimeText.text = $"播放中: {elapsedTime:F1}s / {playbackDuration:F1}s";

            // 检查是否播放完成
            if (elapsedTime >= playbackDuration)
            {
                elapsedTimeText.text = $"播放中: {playbackDuration:F1}s / {playbackDuration:F1}s";
                StopPlayback();
                yield break;
            }

            yield return new WaitForSeconds(updateInterval);
        }

        // 如果播放被外部中断(如用户停止)
        if (isPlaying)
        {
            StopPlayback();
        }
    }

    /// <summary>
    /// 设置按钮交互状态
    /// </summary>
    private void SetButtonsInteractable(bool state)
    {
        recordButton.interactable = state;
        stopRecordButton.interactable = false;
        playButton.interactable = false;
        stopPlayButton.interactable = false;
    }

    void OnDestroy()
    {
        if (isRecording)
        {
            Microphone.End(microphoneDevice);
        }

        if (isPlaying)
        {
            audioSource.Stop();
        }

        if (recordedClip != null)
        {
            Destroy(recordedClip);
        }
    }
}

如何为运行在 PICO 4 Ultra 设备上的项目设置外部文件读写权限? PICO 4 Ultra 系列设备使用的安卓操作系统为 Android 14。当项目的 Write Permission 为 Externa (SDCard) 且 Android API Level 大于 32 时,Unity 提供的外部文件读取方式在 PICO 4 Ultra 设备上将失效。此问题提供两种解决方法,按实际情况选取。 阅读详情

相关推荐

Unity开发PICO VR应用:从环境配置到交互实现全流程指南

虚拟现实(VR)技术通过模拟三维环境,为用户提供沉浸式体验,其核心原理在于实时渲染、空间定位与自然交互。在工程实践中,Unity引擎因其强大的跨平台能力和完善的XR支持,成为VR应用开发的主流选择。对于PICO等主流VR设备,开发者需要掌握SDK集成、输入系统配置与性能优化等关键技术,以实现流畅的交互体验。本文聚焦于PICO VR应用开发,详细解析了从Unity环境搭建、PICO SDK集成、XR Interaction Toolkit配置,到传送、抓取等核心交互逻辑的实现路径,并涵盖了打包部署与性能调优的

weixin_30376509的博客 450

Unity实现Pico内置语音转文字指令控制

主要实现在Unity中接入Pico SDK使用内置的“语音转文字” 服务,应用了自动语音识别技术(Automatic Speech Recognition,ASR),可以实时识别语音信息,然后将其转换为文本信息

qq_39262683的博客 339

Unity集成讯飞实时语音转写WebAPI:跨平台语音交互实战指南

语音识别技术作为人机交互的重要分支,其核心原理是将连续的音频信号转换为可处理的文本信息。该技术通过声学模型、语言模型和解码器协同工作,实现对语音内容的精准理解。在工程实践中,实时语音转写技术为应用带来了自然、高效的交互体验,尤其在VR培训、智能助手、会议记录等场景中价值显著。本文聚焦于Unity引擎,探讨如何通过集成讯飞实时语音转写WebAPI,构建跨平台的语音交互解决方案。文中详细剖析了WebAPI相较于SDK方案在跨平台统一性、维护成本和包体控制方面的优势,并深入讲解了音频采集、PCM格式转换、WebS

weixin_33672400的博客 412

[PICO VR]Unity如何往PICO VR眼镜里写持久化数据txt/json文本

最近在用PICO VR做用户实验,需要将用户实验的数据记录到PICO头盔的存储空间里,记录一下整个过程。

Bartender_VA11的博客 1400

情感科技实践:基于ESP32与传感器的响应式记忆灯箱设计与实现

物联网与嵌入式系统通过传感器感知环境,结合微控制器进行数据处理与逻辑判断,最终驱动执行器实现智能响应,这一技术框架在智能家居、环境监测等领域具有广泛应用价值。其核心在于将物理信号转化为数字信息,并通过编程赋予设备特定的行为模式,从而创造有价值的交互体验。本文聚焦于一个具体的情感科技应用场景——构建一个能感知环境光线与声音模式,并通过灯光、声音等反馈传递情感的“响应式记忆灯箱”。项目详细阐述了如何利用ESP32微控制器、BH1750光照传感器、MAX9814麦克风模块等硬件,结合精心设计的情感状态机软件逻辑,

cuanmo8775的博客 408

基于Unity和PICO XR SDK的PICO开发入门

基于Unity,导入PICO XR SDK,进行样例场景的编译运行

weixin_44390344的博客 1094

手把手教你用Unity实现语音识别应用的麦克风录音功能

本文提供了一份在Unity中构建高可用麦克风录音系统的实战指南。详细讲解了从权限管理、设备选择到核心录音控制的全流程,并重点实现了实时音量检测与音频数据处理。内容涵盖跨平台(iOS/Android)适配与性能优化,帮助开发者为语音识别等应用打下坚实基础。

weixin_27943395的博客 396

【Unity 语音控制插件】Speech Control Plugin for VR聚焦于语音与 VR 融合体验的实用插件,支持多种识别引擎和自定义控制逻辑,帮助开发者打造更自然、沉浸式的人机交互体验

Speech Control Plugin for VR 是一款专为虚拟现实(VR)场景设计的语音控制插件,它允许开发者通过语音命令与 VR 环境进行自然交互。该插件支持离线识别与部分在线服务(如 Windows Speech API、Google Speech、PocketSphinx 等),兼容常见 VR 头显设备(如 Oculus、HTC Vive、PICO),适用于提升沉浸式体验的人机交互系统、游戏控制、UI 操作和无手柄输入设计。

2403_88403568的博客 448

Unity3D整合讯飞实时语音转写WebAPI:跨平台语音交互实战指南

语音识别作为人机交互的核心技术之一,其原理是将声音信号转化为计算机可处理的文本信息。该技术通过声学模型、语言模型等算法,实现对连续语音的实时解码。在游戏开发、虚拟现实(VR)和在线教育等领域,语音交互能极大提升用户体验与沉浸感。Unity3D作为主流的跨平台开发引擎,整合第三方语音服务是实现此类功能的关键。本文聚焦于通过WebSocket协议调用讯飞开放平台的实时语音转写服务,详细解析了在Unity中实现音频采集、PCM编码、流式传输与结果解析的全流程。方案规避了原生SDK的跨平台兼容性问题,适用于Wind

weixin_34320159的博客 351

unity集成android原生录音(可后台)并实现上传

unity集成android原生录音(可后台)并实现上传,资源为项目代码压缩包,并非项目工程文件,可根据个人需求自行集成

PICO麦克风测试2222

1

Unity热更新进阶:从Lua转向HybridCLR的纯C#方案实战解析

热更新是游戏开发中提升迭代效率的关键技术,其核心在于在运行时加载并执行新逻辑,从而绕过应用商店的审核限制。传统方案多依赖Lua脚本,通过内置解释器将动态代码视为数据处理,但存在C#与Lua双语言维护成本。HybridCLR另辟蹊径,基于IL2CPP的AOT机制,在运行时通过补充元数据加载热更新DLL,让纯C#代码以解释模式执行,有效解决AOT泛型缺失和动态加载难题。该方案性能接近原始AOT,且无需切换开发语言,尤其适合代码量庞大、团队熟悉C#的重度Unity项目。实际接入还需关注Addressables资源

weixin_29001327的博客 318

Unity调取移动端的麦克风进行录音并播放

主要为大家详细介绍了Unity调取移动端的麦克风进行录音并播放,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

文字即指挥官:语义驱动的多模态内容编排引擎

多模态内容生成已从简单的‘文本转图像’升级为以文字为中枢的跨模态协同表达。其核心在于语义理解而非统计映射——通过表层切片、隐喻映射与一致性校验三层解析,将自然语言转化为可执行的视觉、听觉、触觉等物理参数。这种语义增强型编排能力显著提升内容生产效率与终端体验一致性,广泛应用于数字营销、知识付费、智能硬件交互及AR/VR内容开发。尤其在需兼顾文化适配、物理真实与终端差异的工业级落地场景中,‘From Text to Beyond Words’正成为新一代内容基建的关键范式。

weixin_30908941的博客 345

泛微Ecology流程数据表结构解析:workflow_requestbase和workflow_currentoperator到底怎么关联查询?

本文深入解析泛微Ecology系统中workflow_requestbase和workflow_currentoperator表的结构与关联机制,提供SQL查询语句示例,帮助开发者高效查询流程数据。涵盖待办事项、已办事项、办结事项和流程监控等典型场景,并分享性能优化技巧和常见问题排查指南。

weixin_33738555的博客 807

EGO_Plannerv2代码学习(一):栅格建图之V1 V2的区别

最近在尝试将高飞老师的集群规划器部署到真实无人机上跑一下,开这个新帖记录下学习过程中的问题和想法。

qq_41069236的博客 5752

使用树莓派录音和播放音乐

目前有一个想法是用树莓派做一款智能聊天机器人的智能硬件,通过声卡录音,将语音识别成文字,然后再解析做相关操作。 所以我首先在树莓派上测试一下声卡。 我购买的是淘宝上的usb声卡,价格只要7元钱。 然后又在淘宝上花30多元够买了一个有源音箱。 实物图如下: 安装相关软件 sudo apt-get install ffmpeg omxplayer 接下来开始录音 arecord...

二毛的博客 1万+

Java:使用Java调用打印机进行打印(JPG、PDF和Word三种文件格式)

目录 一、Java的打印简介 二、Java打印实现 2.1 JPG图片文件格式打印实现 2.2 PDF文件格式打印实现 2.3 Word文件格式打印实现 2.3.1 Word文件采用jacob插件进行打印实现。 2.3.2 先将word转化为pdf文件,然后打印pdf(lz使用) 三、总结 一、Java的打印简介 在我们的实际工作中,经常需要实现打印功能。但由于历史原因,Jav...

vatxiongxiaohui的专栏 8万+

Unity+pico4开发过程及问题记录,创建一个自己的pico安卓应用

自行创建pico安卓应用的全流程,包含手柄操作视角移动旋转等

weixin_44935602的博客 9312

KVM — 与 QEMU 和 Libvirt 的关系

目录目录 前言 KVM QEMU KVM 与 QEMU qemu-kvm Libvirt Libvirt 在 OpenStack 中的应用前言如果是刚开始接触虚拟机技术的话, 对上述的概念肯定会有所混淆, 傻傻的分不清. 尤其在看虚拟化技术文档时导致理解能力下降, 所以在开始学习虚拟化技术之前对这些概念有一个整体的认识和清晰的理解, 就显得很有必要了.KVMKVM(Kernel-basedVirtu

烟云的计算 5736

自动驾驶模拟器:基于 Python + Carla,在家也能训练一个“L4 级”自动驾驶 AI 模型

市面上有很多模拟器(AirSim, LGSVL),但 Carla 是目前学术界和工业界最主流的选择。画质强:基于 UE4,光影、天气(下雨、夕阳)极其逼真,这对训练视觉模型至关重要。API 爽:完全使用 Python 控制,对 AI 工程师极度友好。资产多:内置各种车辆、行人、交通信号灯、城市地图。编写 Python 代码,让车自己在城市里跑,通过摄像头看到路况,自己决定怎么打方向盘。通过 Carla,我们把价值数百万的自动驾驶测试场搬进了电脑。

BUG猿的博客 267

PICO开发踩坑记录

在pico4 pro上开发眼动功能时,调用GetEyeOpenness获取到的返回值始终都是0和1,但是官方文档给的是0.0-1.0之间的数据,找了很多论坛和搜索引擎都没找到相关文章,这个鬼官方示例写的是自动在AndroidManifest.xml文件中写入相关权限,信了它的鬼。再看一眼这里接口的Note怎么写的,需要在AndroidManifest.xml中计入这么一行命令。

qq_40034125的博客 472

用 Docker 一键部署 SillyTavern酒馆 mac/windows/linux通用教程,我再也不手动折腾了!

本文介绍了如何使用 Docker 一键部署 SillyTavern 项目,适用于 Mac、Windows 和 Linux 系统。SillyTavern 是一个本地部署的大语言模型聊天界面,支持多种插件和扩展。文章详细说明了安装 Docker、拉取项目、启动容器、配置白名单、启用插件等步骤,并提供了常见问题的解决方法。通过 Docker 部署,用户可以避免繁琐的手动配置,快速搭建和运行 SillyTavern,适合希望简化部署流程的用户。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 2620
上一篇: 【Unity限制项目软件使用日期】
魔法师干脆面
博客等级 码龄7年 33粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔法师干脆面

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值