WayneShao 的博客

记录精彩的程序人生

.Net 百度语音 Demo(语音识别、语音合成)

百度语音,面向广大开发者永久免费开放语音合成技术。所采用的离在线融合技术,根据当前网络状况,自动判断使用本地引擎或者云端引擎,进行语音合成,再也不用担心流量消耗了!

本Demo将使用官方提供的C#版本RestApi SDK制作一个Winfrom软件,实现以下两个功能。

  • TTS语音合成:可选择语速、音调、音量及发言人
  • ASR语音识别:使用NAudio进行语音录制并识别

Prepare

在正式使用之前,我们需要在百度语音获取API key以及SDK文件。

获取API Key

在百度语音应用管理页面创建新应用。
按照创建引导一步步来即可,注意在选择服务时同时勾选语音识别和语音合成,这样API Key就可以同时用于TTS和ASR了。

下载离线SDK

下载C#版本的RestApi SDK。

Coding

使用Nuget安装Newtonsoft.Json和NAudio、手动引用官方提供的ApiSdk.dll文件。

接下来查看官方Demo中有关语音的SpeechDemo.cs文件

using System;
using System.Collections.Generic;
using System.IO;
using Baidu.Aip.Speech;

namespace Baidu.Aip.Demo
{
    class SpeechDemo
    {
        private readonly Asr _asrClient;
        private readonly Tts _ttsClient;

        public SpeechDemo()
        {
            _asrClient = new Asr("Api Key", "Secret Key");
            _ttsClient = new Tts("Api Key", "Secret Key");
        }

        // 识别本地文件
        public void AsrData()
        {
            var data = File.ReadAllBytes("语音pcm文件地址");
            var result = _asrClient.Recognize(data, "pcm", 16000);
            Console.Write(result);
        }

        // 识别URL中的语音文件
        public void AsrUrl()
        {
            var result = _asrClient.Recoginze(
                "http://xxx.com/待识别的pcm文件地址", 
                "http://xxx.com/识别结果回调地址", 
                "pcm", 
                16000);
            Console.WriteLine(result);
        }

        // 合成
        public void Tts()
        {
            // 可选参数
            var option = new Dictionary<string, object>()
            {
                {"spd", 5}, // 语速
                {"vol", 7}, // 音量
                {"per", 4}  // 发音人,4:情感度丫丫童声
            };
            var result = _ttsClient.Synthesis("众里寻他千百度", option);

            if (result.ErrorCode == 0)  // 或 result.Success
            {
                File.WriteAllBytes("合成的语音文件本地存储地址.mp3", result.Data);
            }
        }
    }
}

从代码示例中,我们可以看出语音识别API需要的源语音文件为pcm格式,我们使用NAudio来获取麦克风数据并保存为pcm格式以使用API,同样的也使用NAudio实时预览录制的数据和播放TTS合成的数据。

将官方Demo封装成SpeechHelper

using System.Collections.Generic;
using System.IO;
using Baidu.Aip.Speech;

namespace BaiduSpeechDemo
{
    static class SpeechHelper
    {
        private static readonly Asr AsrClient;
        private static readonly Tts TtsClient;

        static SpeechHelper()
        {
            AsrClient = new Asr("BWf8AWrvS5h6Y45NAOP3zaGp", "490737eca7a6ff4d20375d1696c7e548");
            TtsClient = new Tts("BWf8AWrvS5h6Y45NAOP3zaGp", "490737eca7a6ff4d20375d1696c7e548");
        }

        // 识别本地文件
        public static AsrResult AsrData(string path)
        {
            var data = File.ReadAllBytes(path);
            var result = AsrClient.Recognize(data, "pcm", 8000);
            return result.ToObject<AsrResult>();
        }

        // 识别URL中的语音文件
        public static AsrResult AsrUrl(string url, string callback = "")
        {
            var result = AsrClient.Recoginze(
                url,
                callback,
                "pcm",
                16000);
            return result.ToObject<AsrResult>();
        }

        // 合成
        public static bool Tts(string input, string path, int spd = 5, int pit = 5, int vol = 6, int per = 4)
        {
            // 可选参数
            var option = new Dictionary<string, object>
            {
                {"spd", spd}, // 语速,取值0-9,默认为5中语速
                {"pit", pit}, // 音调,取值0-9,默认为5中语调
                {"vol", vol}, // 音量,取值0-15,默认为5中音量
                {"per", per}  // 发音人选择, 0为普通女声,1为普通男生,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声
            };
            var result = TtsClient.Synthesis(input, option);

            if (result.Success) File.WriteAllBytes(path, result.Data);

            //Console.WriteLine(result.Serialize());

            return result.Success;
        }

    }
}

NAudio录制麦克风数据为pcm格式并实时预览

NAudio提供了同时进行录制和播放的Demo,其中的SavingWaveProvider可以直接拿来使用,只要传入合适的参数即可将保存文件的格式由示例中的wav改为pcm,下面是修改后的调用代码。
具体则是修改WaveIn的声明:

// 设置记录器
// WaveFormat.CreateCustomFormat 参数依次为 格式\采样率\声道\每秒平均码率\单位采样点的字节数\采样位数
_recorder = new WaveIn { WaveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Pcm, 8000, 1, 16000, 2, 16) };

下面给出修改后的开始录制和结束录制的代码

private WaveIn _recorder;
private BufferedWaveProvider _bufferedWaveProvider;
private SavingWaveProvider _savingWaveProvider;
private WaveOut _player;

private void OnStartRecordingClick(object sender, EventArgs e)
{
    // 设置记录器
    // 参数依次为 格式\采样率\声道\每秒平均码率\单位采样点的字节数\采样位数
    _recorder = new WaveIn { WaveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Pcm, 8000, 1, 16000, 2, 16) };
    _recorder.DataAvailable += RecorderOnDataAvailable;

    // 建立我们的信号链
    _bufferedWaveProvider = new BufferedWaveProvider(_recorder.WaveFormat);

    _fileName = Path.Combine("temp", Guid.NewGuid() + ".pcm");
    _savingWaveProvider = new SavingWaveProvider(_bufferedWaveProvider, _fileName);

    //设置播放
    _player = new WaveOut();
    _player.Init(_savingWaveProvider);

    // 开始播放和录制
    _player.Play();
    _recorder.StartRecording();
}

private void RecorderOnDataAvailable(object sender, WaveInEventArgs waveInEventArgs)
{
    _bufferedWaveProvider.AddSamples(waveInEventArgs.Buffer, 0, waveInEventArgs.BytesRecorded);
}

private void OnStopRecordingClick(object sender, EventArgs e)
{
    // 停止录制
    _recorder.StopRecording();
    // 停止播放
    _player.Stop();
    // 最终完成 WAV 文件
    _savingWaveProvider.Dispose();
    
    // 请求百度ASR API
    var a = SpeechHelper.AsrData(_fileName);
}

TTS完成

窗体界面拖好TTS选项的布局

完成按钮点击事件的逻辑

private void btnTts_Click(object sender, EventArgs e)
{
    // 临时保存路径
    var musicPath = Path.Combine("temp", Guid.NewGuid() + ".mp3");

    // 发言人
    var per = cbPer.SelectedIndex >= 2 ? cbPer.SelectedIndex + 1 : cbPer.SelectedIndex;

    //调用Baidu TTS Api
    if (!SpeechHelper.Tts(tbContext.Text, musicPath, (int)nudSpd.Value, (int)nudPit.Value, (int)nudVol.Value, per)) return;

    //播放请求得到的结果
    IWavePlayer waveOutDevice = new WaveOut();
    var audioFileReader = new AudioFileReader(musicPath);
    waveOutDevice.Init(audioFileReader);
    waveOutDevice.Play();

    //播放结束后销毁播放对象
    waveOutDevice.PlaybackStopped += delegate
    {
        waveOutDevice?.Stop();
        waveOutDevice?.Dispose();
        waveOutDevice = null;
    };
}

Complete

最终成品如下:

完整代码托管在GitHub

代码中我自己的Api Key并没有删除,望手下留情,别给我搞封了。

留下你的脚步