web audio living

总结网页音频直播的方案和遇到的问题。

代码:(github,待整理)

结果: 使用opus音频编码,web audio api 播放,可以达到100ms以内延时,高质量,低流量的音频直播。

背景: VDI(虚拟桌面) h264网页版预研,继h264视频直播方案解决之后的又一个对延时有高要求的音频直播方案(交互性,音视频同步)。

前提: flexVDI开源项目对音频的支持只实现了对未编码压缩的PCM音频数据。并且效果不好,要么卡顿,要么延时,流量在2~3Mbps(根据缓冲的大小)。

解决方案: 在spice server端对音频采用opus进行编码,flexVDI playback通道拿到opus packet数据后,调用opus js解码库解码成PCM数据,喂给audioContext进行播放。

流程简介:flexVDI palyback通道接收opus音频数据,调用libopus.js解码得到PCM数据,保存到buffer。创建scriptProcessorNode, 在onaudioprocess函数中从buffer里面拿到PCM数据,

     按声道填充outputBuffer, 把scriptProcessorNode连接到audioContext.destination进行播放。具体代码见后文或者github。

opus编解码接口介绍:

参考:http://opus-codec.org/docs/opus_api-1.2/index.html

一、下面是我用opus c库解码opus音频,再用ffplay播放PCM数据的一个demo,可以看看opus解码接口是怎么使用的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "opus.h"

/*
static void int_to_char(opus_uint32 i, unsigned char ch[4])
{
    ch[0] = i>>24;
    ch[1] = (i>>16)&0xFF;
    ch[2] = (i>>8)&0xFF;
    ch[3] = i&0xFF;
}*/

static opus_uint32 char_to_int(unsigned char ch[4])
{
    return ((opus_uint32)ch[0]<<24) | ((opus_uint32)ch[1]<<16)
         | ((opus_uint32)ch[2]<< 8) |  (opus_uint32)ch[3];
}

int main(int argc, char** argv)
{
    opus_int32 sampleRate = 0;
    int channels = 0, err = 0, len = 0;
    int max_payload_bytes = 1500;
    int max_frame_size = 48000*2;
    OpusDecoder*  dec = NULL;
    sampleRate = (opus_int32)atol(argv[1]);
    channels = atoi(argv[2]);
    FILE*  fin = fopen(argv[3], "rb");
    FILE*  fout = fopen(argv[4], "wb+");

    short *out;
    unsigned char* fbytes, *data;
    //in = (short*)malloc(max_frame_size*channels*sizeof(short));
    out = (short*)malloc(max_frame_size*channels*sizeof(short));
    /* We need to allocate for 16-bit PCM data, but we store it as unsigned char. */
    fbytes = (unsigned char*)malloc(max_frame_size*channels*sizeof(short));
    data   = (unsigned char*)calloc(max_payload_bytes, sizeof(unsigned char));
    dec = opus_decoder_create(sampleRate, channels, &err);
    int nBytesRead = 0;
    opus_uint64 tot_out = 0;
    while(1){
     unsigned char ch[4] = {0};
        nBytesRead = fread(ch, 1, 4, fin);
        if(nBytesRead != 4)
            break;
        len = char_to_int(ch);
        nBytesRead = fread(data, 1, len, fin);
        if(nBytesRead != len)
            break;

        opus_int32 output_samples = max_frame_size;
        output_samples = opus_decode(dec, data, len, out, output_samples, 0);
        int i;
        for(i=0; i < output_samples*channels; i++)
        {
            short s;
            s=out[i];
            fbytes[2*i]=s&0xFF;
            fbytes[2*i+1]=(s>>8)&0xFF;
        }
        if (fwrite(fbytes, sizeof(short)*channels, output_samples, fout) != (unsigned)output_samples){
            fprintf(stderr, "Error writing.\n");
            return EXIT_FAILURE;
        }
        tot_out += output_samples;
    }

    printf("tot_out: %llu \n", tot_out);

    return 0;
}    

这个程序对opus packets组成的文件(简单的length+packet格式)解码后得到PCM数据,再用ffplay播放PCM数据,看能否正常播放:

ffplay -f f32le -ac 1 -ar 48000 input_audio      // 播放float32型PCM数据

ffplay -f s16le -ac 1 -ar 48000 input_audio    //播放short16型PCM数据

ac表示声道数, ar表示采样率, input_audio是PCM音频文件。

二、要获取PCM数据文件,首先要得到opus packet二进制文件, 所以这里涉及到浏览器如何保存二进制文件到本地的问题:

参考代码:

var saveFile = (function(){
        var a  = document.createElement("a");
        document.body.appendChild(a);
        a.style = "display:none";
        return function(data, name){
                var blob = new Blob([data]);
                var url = window.URL.createObjectURL(blob);
                a.href = url;
                a.download = name;
                a.click();
                window.URL.revokeObjectURL(url);
        };
}());
saveFile(data, ‘test.pcm‘);

说明:首先把二进制数据写到typedArray中,然后用这个buffer构造Blob对象,生成URL, 再使用a标签把这个blob下载到本地。

三、利用audioContext播放PCM音频数据的两种方案:

(1)flexVDI的实现

参考:https://github.com/flexVDI/spice-web-client

 function play(buffer, dataTimestamp) {
        // Each data packet is 16 bits, the first being left channel data and the second being right channel data (LR-LR-LR-LR...)
        //var audio = new Int16Array(buffer);
        var audio = new Float32Array(buffer);

        // We split the audio buffer in two channels. Float32Array is the type required by Web Audio API
        var left = new Float32Array(audio.length / 2);
        var right = new Float32Array(audio.length / 2);
        var channelCounter = 0;
        var audioContext = this.audioContext;
        var len = audio.length;

        for (var i = 0; i < len; ) {
          //because the audio data spice gives us is 16 bits signed int (32768) and we wont to get a float out of it (between -1.0 and 1.0)
          left[channelCounter] = audio[i++] / 32768;
          right[channelCounter] = audio[i++] / 32768;
          channelCounter++;
        }

        var source = audioContext[‘createBufferSource‘](); // creates a sound source
        var audioBuffer = audioContext[‘createBuffer‘](2, channelCounter, this.frequency);
        audioBuffer[‘getChannelData‘](0)[‘set‘](left);
        audioBuffer[‘getChannelData‘](1)[‘set‘](right);
        source[‘buffer‘] = audioBuffer;
        source[‘connect‘](this.audioContext[‘destination‘]);
        source[‘start‘](0);
}

注: buffer中保存的是short 型PCM数据,这里为了简单,去掉了对时间戳的处理,因为source.start(0)表示立即播放。如果是float型数据,不需要除以32768.

(2)ws-audio-api的实现

参考:https://github.com/Ivan-Feofanov/ws-audio-api

var bufL = new Float32Array(this.config.codec.bufferSize);
var bufR = new Float32Array(this.config.codec.bufferSize);
this.scriptNode = audioContext.createScriptProcessor(this.config.codec.bufferSize, 0, 2);
if (typeof AudioBuffer.prototype.copyToChannel === "function") {
     this.scriptNode.onaudioprocess = function(e) {
          var buf = e.outputBuffer;
          _this.process(bufL, bufR);  //获取PCM数据到bufL, bufR
          buf.copyToChannel(bufL, 0);
          buf.copyToChannel(bufR, 1);
     };
} else {
     this.scriptNode.onaudioprocess = function(e) {
          var buf = e.outputBuffer;
          _this.process(bufL, bufR);
          buf.getChannelData(0).set(bufL);
          buf.getChannelData(1).set(bufR);
     };
}
this.scriptNode.connect(audioContext.destination);

延时卡顿的问题:audioContext有的浏览器默认是48000采样率,有的浏览器默认是44100的采样率,如果喂给audioContext的PCM数据的采样率不匹配,就会产生延时和卡顿的问题。

时间: 2024-10-19 19:48:22

web audio living的相关文章

[Javascript] Intro to the Web Audio API

An introduction to the Web Audio API. In this lesson, we cover creating an audio context and an oscillator node that actually plays a sound in the browser, and different ways to alter that sound. var context = new window.AudioContext() || new window.

Web Audio API DEMO

一转眼就已经有三个月没写博客了,毕业季事情确实多,现在也终于完全毕业了,博客还是不能落下.偶尔还是要写一下. 玩HTML5的Audio API是因为之前看到博客园里有关于这个的博客,觉得挺好玩的,所以就学习了一下.本文仅作为自己的学习记录.如有错误之处请指出.   最终的效果也就如右图,楼主只是简单的做了个demo,如果要有更复杂的效果,园友们可以自己去玩一下 DEMO链接:请戳我!!!   选择音频文件后即可播放 同时,这个API目前浏览器支持度不高,若要用于生产环境,请自行斟酌. 首先,要做

【HTML5】Web Audio API打造超炫的音乐可视化效果

HTML5真是太多炫酷的东西了,其中Web Audio API算一个,琢磨着弄了个音乐可视化的demo,先上效果图: 项目演示:别说话,点我!  源码已经挂到github上了,有兴趣的同学也可以去star或者fork我,源码注释超清楚的哦~~之前看刘大神的文章和源码,感觉其他方面的内容太多了,对初学者来说可能一下子难以抓到Web Audio API的重点,所以我就从一个初学者的角度来给大家说说Web Audio API这些事吧. Web Audio API与HTML5提供的Audio标签并不是同

Web Audio介绍

Web Audio还是一个比较新的JavaScript API,它和HTML5中的<audio>是不同的,简单来说,<audio>标签是为了能在网页中嵌入音频文件,和播放器一样,具有操作界面,而Web Audio则是给了开发者对音频数据进行处理.分析的能力,例如混音.过滤等,类似于对音频数据进行PS. 一般的网站应用应该是用不倒这些API中的,但是一些游戏引擎或者在线音乐编辑等类型的网站应该用得到. Web Audio API紧紧围绕着一个概念设计:audio context,它就

audio与Web Audio

做H5游戏难免会遇到需要播放背景音乐或者音效的时候.一开始看到这个需求第一反应就是用H5中的audio标签去实现,但是在实现的过程中发现它存在很多的问题. 1.只能播放单一音频(在包括IOS在内的某些设备上).什么意思呢?就是说如果你同时在播放背景音乐的时候播放音效的话,背景音乐会被停掉.这是一个非常严肃的问题. 2.在IOS中不能预加载.这会导致H5游戏在IOS中播放音效时,只能实时的去拉去音频数据,会对性能造成一定影响.而且在加载的过程中去设置audio的某些属性会报错. 3.在包括IOS在

基于canvas和Web Audio的音频播放器

wavesurfer.js是一款基于HTML5 canvas和Web Audio的音频播放器插件.通过wavesurfer.js你可以使用它来制作各种HTML5音频播放器,它可以在各种支持 Web Audio 的浏览器中工作.包括 Firefox, Chrome, Safari, Mobile Safari 和 Opera浏览器. 在线演示:http://www.htmleaf.com/Demo/201503151525.html 下载地址:http://www.htmleaf.com/html

关于Web Audio API的入门

Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容.它允许你开发复杂的混音,音效,平移以及更多. 可以先看一下MDN的这篇文章<Web Audio API的运用>  https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API 不过,看了上述文章后可能还是不知道怎么用Web Audio API来实现一些简单的功能,比如播放一段mp3音频,文章中并没有相应的例

ASP.NET Core环境Web Audio API+SingalR+微软语音服务实现web实时语音识别

处于项目需要,我研究了一下web端的语音识别实现.目前市场上语音服务已经非常成熟了,国内的科大讯飞或是国外的微软在这块都可以提供足够优质的服务,对于我们工程应用来说只需要花钱调用接口就行了,难点在于整体web应用的开发.最开始我实现了一个web端录好音然后上传服务端进行语音识别的简单demo,但是这种结构太过简单,对浏览器的负担太重,而且响应慢,交互差:后来经过调研,发现微软的语音服务接口是支持流输入的连续识别的,因此开发重点就在于实现前后端的流式传输.参考这位国外大牛写的博文Continuou

各式 Web 前端開發工具整理

程式碼編寫工具 (Coding Tools) 工作流程/建置/組合 (Workflow/Builds/Assemblers) lumbar brunch grunt lineman yeoman Takeoff mimosa codeKit liveReload stealJS anvil.js modjs AUTOMATON Fire.app 瀏覽器套件管理員 (Browser Package Managers) (參見: Front-End Package Manager Compariso