求方案:如何获取声音输入/录音

求方案:COCOS3.7如何获取声音输入/录音

您好 有方案?

发布到哪个平台的?如果是web平台可以考虑使用webrtc: WebRTC API - Web API | MDN (mozilla.org)

小游戏,发布到微信和抖音呢?使用平台自己提供的API吗?

这种cocos平台应该没有集成吧,这不属于引擎适配的范畴,但微信和抖音是有支持,和登录授权那些差不多,可以自己适配一下:
媒体 / 录音 / wx.getRecorderManager (qq.com)
tt.getRecorderManager_小游戏_抖音开放平台 (open-douyin.com)

感谢大佬 :pray: :pray:,我研究一下

提供个微信录音


    // 开始录音
    startRecord() {
        // 先拉取设置, 看下有没有权限
        wx.getSetting({
            success: (res) => {
                // 录音配置
                const options = {
                    duration: 20 * 1000,    // 最高20秒
                    sampleRate: 8000,
                    numberOfChannels: 1,
                    encodeBitRate: 24000,
                    format: 'mp3',
                }
                const record = res.authSetting["scope.record"];
                if (record === false) {
                    // 已经拒绝了权限
                    // app.popup.showToast("无法录音, 请通过设置界面开启录音权限");
                } else if (record === true) {
                    // 可以录音
                    this.rm.start(options);
                } else {
                    // 找不到设置权限的痕迹, 进行获取权限开启录音
                    wx.authorize({
                        scope: "scope.record",
                        success: () => {
                            this.rm.start(options);
                        },
                        fail: () => {
                            console.warn("没给予授权!");
                            // app.popup.showToast("无法录音, 请通过设置界面开启录音权限");
                        }
                    });
                }
            },
            fail: () => {
                console.error("获取微信设置失败!")
            }
        });

    }


    // 主动停止录音
    stopRecord() {
        // 如果录音超过最大时长, 会主动停止, 调用该函数将不会有任何处理
        console.warn("主动结束录音");
        this.rm.stop();
    }

    // 开始录音了
    setOnStartCallBack() {
        this.rm.onStart(() => {
            console.warn("监听到录音开始");
        });
    }

如果是多玩家, 需要通过服务器做中介来发送录音文件, 或者直接读取微信录音文件来发送. 这里采用的是读取录音文件来发送


   const OffsetIndex = 1;
   const MaxChunkSize = 40 * 1024; // 40KB

    /**
     * tempFilePath	string	录音文件的临时路径 (本地路径)
     * duration	number	录音总时长,单位:ms
     * fileSize	number	录音文件大小,单位:Byte
    */
    setOnStopCallBack() {
        this.rm.onStop((res: {
            tempFilePath: string,   // 录音文件的临时路径 (本地路径)
            duration: number,       // 录音总时长,单位:ms
            fileSize: number,       // 录音文件大小,单位:Byte
        }) => {
            console.warn("监听到录音结束:", res);
            if (res.duration < 1 * 1000) {
                // showToast("发送语音时间太短");
                return;
            }

            const time = new Date().getTime();
            // 读取二进制, 上报服务端, 通过返回结果, 进行包数监听, 监听完毕则放入队列
            wx.getFileSystemManager().readFile({
                filePath: res.tempFilePath,
                success: (readResult) => {
                    const buffer: ArrayBuffer = readResult.data as ArrayBuffer;
                    const sliceCount = Math.ceil((buffer.byteLength) / MaxChunkSize) + 1;  // 循环分包次数+余数1

                    // 切割
                    let arr: ArrayBuffer[] = [];
                    for (let i = 0; i < sliceCount; i++) {
                        const start = i * MaxChunkSize;
                        const end = Math.min(start + MaxChunkSize, buffer.byteLength);
                        const slice = buffer.slice(start, end);
                        if (slice.byteLength == 0) { continue; }
                        arr.push(slice);
                    }

                    // 发送切割的数据
                    arr.forEach(async (v, i) => {
                        await sleep(50); // 预防粘包 , 采用的是  await Promise+setTimeout

                        const voiceData = new Uint8Array(v.byteLength);
                        voiceData.set(new Uint8Array(v), 0);
                        // TODO发送数据给服务端
                        let obj = {
                            uniqueId: time,
                            totalPackageNum: arr.length,
                            curPackageIndex: i + OffsetIndex,     // 索引从1开始
                            voiceData
                        }
                    });

               
                },
                fail: (errMsg) => {
                    console.error("读取失败");
                }
            });

        });
    }

    private writeMp3(data: { uniqueId, curPackageIndex, totalPackageNum }) {
        if (!data) { console.error("数据异常"); return; }
        const id = data.uniqueId;
        if (!this.receiveData[id]) { this.receiveData[id] = []; }
        this.receiveData[id][data.curPackageIndex] = data;  // 为了确保id顺序一次, 采用index存储    
        // 检验是否收到了所有数据(减1是因为index从1开始)
        if (this.receiveData[id].length - OffsetIndex != data.totalPackageNum) { return; }

        /**
         *~ 有个问题, 发送数据是通过 Uint8Array 包含 ArrayBuffer, 收到时却变成了另一个数据 (ArrayBuffer? 看着也不像)
         *~ 不影响使用, 就不改了
         *~ 还有个无敌坑的问题: 微信开发工具发送的录音数据, 在真机无法播放! 测试时务必使用两台真机
         */
        // 合并接收到的切片数据
        const arr: ArrayBuffer[] = [];
        for (const chunk of this.receiveData[id]) {
            if (chunk?.uniqueId == null) { continue; }
            arr.push(chunk.voiceData);     //~ 收到的协议没有 chunk.voiceData.buffer
        }
        const mergedData = new Uint8Array(arr.reduce((acc: number, chunk: ArrayBuffer) => {
            return acc + chunk.length;   //~ 收到的协议没有  chunk.byteLength
        }, 0));
        let offset = 0;
        for (const chunk of arr) {
            mergedData.set(new Uint8Array(chunk), offset);
            offset += chunk.length;  //~ 收到的协议没有  chunk.byteLength
        }

        // 将合并后的数据保存为完整的 MP3 文件
        const path = wx.env.USER_DATA_PATH + `/recorder_${data.uniqueId}.mp3`;
        wx.getFileSystemManager().writeFile({
            filePath: path,
            data: mergedData.buffer,
            encoding: "binary",
            success: (res) => {
                // 确定写入成功, 加入播放队列
                console.warn("写入录音 binary", path, res);
                this.playRecord(path);  // 这里方便理解, 直接播放, 队列播放请自行实现
            },
            fail(error) {
                console.error("写入失败", error);
            }
        });

    }

    // 播放录音
    playRecord(path: string) {
        const ac = wx.createInnerAudioContext();
        this.curAudioContext = ac;
        ac.src = path;

        // 监听播放完成
        ac.onEnded(() => {
            console.log("录音播放结束");
            ac.destroy();   // 完成后记得销毁, 否则会一直持有存在内存中!
        });
        ac.onError((res) => {
            console.error("播放报错", res);
        });
        ac.onPlay(() => {
            console.log("开始播放");
        });
        ac.autoplay = true;
        ac.play();
        return ac;
    }
2赞

厉害厉害 :+1::+1::+1: 感谢大佬,膜拜