文档章节

H5实时解码音频并播放

一个灰
 一个灰
发布于 2018/10/18 08:55
字数 1037
阅读 180
收藏 0

音视频的格式是一个有歧义的说法。我们熟知的诸如Flv、Mp4、Mov啥的都是包装格式,可以理解为一种容器,就像一个盒子。里面放到是经过编码的音视频数据,而这些音视频数据都有自己的编码格式,如AAC、H264、H265等等。 今天要展示的是从直播流中获取到的音频编码数据进行解码并使用H5的音频API进行播放的过程。

这些格式分别是

  1. speex
  2. aac
  3. mp3

这些格式都有开源的解码库,不过都是c库,在H5中需要通过emscripten编译成js执行。

引入头文件

#ifdef USE_SPEEX
#include <speex/speex.h>
#endif
#ifdef USE_AAC
#include "aacDecoder/include/neaacdec.h"
// #include "libfdk-aac/libAACdec/include/aacdecoder_lib.h"
#endif
#ifdef USE_MP3
#include "libmad/mad.h"
//#include "libid3tag/tag.h"
#endif

定义变量

int bufferLength;
int bufferFilled;
u8 *outputBuffer;

#ifdef USE_AAC
	faacDecHandle faacHandle;
#endif
#ifdef USE_SPEEX
    i16 *audioOutput;
    void *speexState;
    SpeexBits speexBits;
#endif
#ifdef USE_MP3
    MP3Decoder mp3Decoder;
#endif

bufferLength 用于指定缓冲区的长度,bufferFilled用于指示缓冲中没有使用的数据,outputBuffer用来存放解码后的数据。 MP3Decoder是自己写的一个类,需要定义这几个成员

mad_stream inputStream;
mad_frame frame;
mad_synth synth;

初始化

	outputBuffer = (u8 *)malloc(bufferLength);
#ifdef USE_SPEEX
	audioOutput = (i16 *)malloc(640);
	auto mode = speex_lib_get_mode(SPEEX_MODEID_WB);
	speexState = speex_decoder_init(mode);
	speex_bits_init(&speexBits);
#endif
#ifdef USE_AAC
	faacHandle = faacDecOpen();
#endif

mp3的初始化

mad_stream_init(&inputStream);
mad_frame_init(&frame);
mad_synth_init(&synth);

解码

input对象中包含了经过协议拆包后的原始音频数据(RTMP协议或Flv格式中的格式)缓冲大小虽然是自己定义,但必须遵循下面的规则 aac:1024的倍数(AAC一帧的播放时间是= 10241000/44100= 22.32ms) speex:320的倍数(3201000/16000 = 20ms) MP3:576的倍数(双声道1152 * 1000 /44100 = 26.122ms) 根据这些数据可以估算缓冲大小引起的音频的延时,然后需要和视频的延迟进行同步。

#ifdef USE_SPEEX
	if (input.length() <= 11)
	{
	    memset(output, 0, 640);
	}
	else
	{
	    speex_bits_read_from(&speexBits, (const char *)input, 52);
	    speex_decode_int(speexState, &speexBits, audioOutput);
	    memcpy(output, audioOutput, 640);
	}
	return 640;
#endif
#ifdef USE_AAC
	//0 = AAC sequence header ,1 = AAC raw 
	if (input.readB<1, u8>())
	{
	    faacDecFrameInfo frame_info;
	    auto pcm_data = faacDecDecode(faacHandle, &frame_info, (unsigned char *)input.point(), input.length());
	    if (frame_info.error > 0)
	    {
			emscripten_log(1, "!!%s\n", NeAACDecGetErrorMessage(frame_info.error));
	    }
	    else
	    {
		int samplesBytes = frame_info.samples << 1;
		memcpy(output, pcm_data, samplesBytes);
		return samplesBytes;
	    }
	}
	else
	{
	    unsigned long samplerate;
		unsigned char channels;
		auto config = faacDecGetCurrentConfiguration(faacHandle);
		config->defObjectType = LTP;
		faacDecSetConfiguration(faacHandle,config);
	    faacDecInit2(faacHandle, (unsigned char *)input.point(), 4, &samplerate, &channels);
	    emscripten_log(0, "aac samplerate:%d channels:%d", samplerate, channels);
	}
#endif

mp3 比较复杂,这里不贴代码了,主要是mad库不能直接调用其提供的API,直播流中的MP3数据和mp3文件的格式有所不同导致。如果本文火的话,我就详细说明。

释放资源

#ifdef USE_AAC
	faacDecClose(faacHandle);
#endif
#ifdef USE_SPEEX
	speex_decoder_destroy(speexState);
	speex_bits_destroy(&speexBits);
	free(audioOutput);
#endif
	free(outputBuffer);

mp3

mad_synth_finish(&synth);
mad_frame_finish(&frame);

播放

创建AudioContext对象

window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new window.AudioContext();

创建audioBuffer

var audioBuffers = []
var audioBuffer = context.createBuffer(channels, frameCount, samplerate);

播放音频(带缓冲)

                var playNextBuffer = function() {
                    isPlaying = false;
                    if (audioBuffers.length) {
                        playAudio(audioBuffers.shift());
                    }
                    if (audioBuffers.length > 1) audioBuffers.shift();
                    //console.log(audioBuffers.length)
                };
                var copyAudioOutputArray = resampled ? function(target) {
                    for (var i = 0; i < allFrameCount; i++) {
                        var j = i << 1;
                        target[j] = target[j + 1] = audioOutputArray[i] / 32768;
                    }
                } : function(target) {
                    for (var i = 0; i < allFrameCount; i++) {

                        target[i] = audioOutputArray[i] / 32768;
                    }
                };
                var copyToCtxBuffer = channels > 1 ? function(fromBuffer) {
                    for (var channel = 0; channel < channels; channel++) {
                        var nowBuffering = audioBuffer.getChannelData(channel);
                        if (fromBuffer) {
                            for (var i = 0; i < frameCount; i++) {
                                nowBuffering[i] = fromBuffer[i * (channel + 1)];
                            }
                        } else {
                            for (var i = 0; i < frameCount; i++) {
                                nowBuffering[i] = audioOutputArray[i * (channel + 1)] / 32768;
                            }
                        }
                    }
                } : function(fromBuffer) {
                    var nowBuffering = audioBuffer.getChannelData(0);
                    if (fromBuffer) nowBuffering.set(fromBuffer);
                    else copyAudioOutputArray(nowBuffering);
                };
                var playAudio = function(fromBuffer) {
                    if (isPlaying) {
                        var buffer = new Float32Array(resampled ? allFrameCount * 2 : allFrameCount);
                        copyAudioOutputArray(buffer);
                        audioBuffers.push(buffer);
                        return;
                    }
                    isPlaying = true;
                    copyToCtxBuffer(fromBuffer);
                    var source = context.createBufferSource();
                    source.buffer = audioBuffer;
                    source.connect(context.destination);
                    source.onended = playNextBuffer;
                    //setTimeout(playNextBuffer, audioBufferTime-audioBuffers.length*200);
                    source.start();
                };

其中playNextBuffer 函数用于从缓冲中取出数据 copyAudioOutputArray 函数用于将音频数据转化成浮点数。 copyToCtxBuffer 函数用于将音频数据拷贝进可以播放的缓冲数组中。 这些函数对单声道和双声道进行了处理

var resampled = samplerate < 22050;

对于频率小于22khz的数据,我们需要复制一份,模拟成22khz,因为H5只支持大于22khz的数据。

© 著作权归作者所有

一个灰
粉丝 28
博文 33
码字总数 21699
作品 3
南京
高级程序员
私信 提问
微信H5视频抓娃娃,没你想的那么难,看完你也会

短短两周时间,在线抓娃娃从一个默默赚钱的行业变成了风口行业,从硬件到软件架构、从盈利到投资、从运营到推广,全方位解读都有了。唯独H5抓娃娃(特指移动web、微信抓娃娃),仍然很神秘。...

Agora
2017/11/29
0
0
【腾讯Bugly干货分享】H5 视频直播那些事

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动开发技术,结交朋友,扩展人脉的社群,成员...

腾讯Bugly
2016/08/12
158
0
Android音视频之使用MediaCodec编解码AAC

现在的短视频、音视频通话都离不开编码和解码,今天就来聊一下音频的编解码。 1. 音频的基本概念 在音频开发中,有些基本概念是需要了解的。 采样率(SampleRate):每秒采集声音的数量,它用...

落英坠露
05/03
0
0
【腾讯bugly干货分享】HTML 5 视频直播一站式扫盲

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1277 视频直播这么火,再不学就 out 了。 为了紧跟潮流,本文将...

腾讯Bugly
2016/07/04
791
4
RTMP 视频直播方案--H5RtmpClient

用 HTML 5 技术播放 rtmp 视频直播流的方案,采用本人开发的 csharprtmp 作为服务器,Broadway 作为视频解码方案,speex.js 作为音频解码方案,emscripten 作为编译器的创新技术,需要浏览器...

一个灰
2016/02/14
8.1K
3

没有更多内容

加载失败,请刷新页面

加载更多

IT兄弟连 Java语法教程 Java语言的跨平台特性

什么是平台 Java是可以跨平台的编程语言,那么首先我们需要知道什么是平台,通常我们把CPU与操作系统的整体称为平台。 CPU大家都知道,是计算机的大脑,它既负责思维运算,又负责计算机中各种...

老码农的一亩三分地
8分钟前
0
0
http传值问题

这两天遇到一个问题 ,与一个渠道联调接口,http请求,展示ptf 的需求,服务方以一个二进制的方式返回。 当时我们在一开始开发的时候,我们按照读取文件的方式处理,本地存一个ptf 的方式 ,...

鬼才王
17分钟前
1
0
【面试】如果你这样回答“什么是线程安全”,面试官都会对你刮目相看

不是线程的安全 面试官问:“什么是线程安全”,如果你不能很好的回答,那就请往下看吧。 论语中有句话叫“学而优则仕”,相信很多人都觉得是“学习好了可以做官”。然而,这样理解却是错的。...

中关村的老男孩
17分钟前
4
0
5.01- Druid数据源配置

1、配置项 配置 缺省值 说明 name 无 配置这个属性的意义在于,如果存在多个数据源,监控的时候 可以通过名字来区分开来。如果没有配置,将会生成一个名字, 格式是:"DataSource-" + Syste...

静以修身2025
21分钟前
0
0
itop4412开发板-Linux内核的编译

本篇文章基于itop4412开发板 5.3.2.1源码目录 Linux 内核源码在光盘“06_源码_uboot 和 kernel”目录下,如下图所示。 5.3.2.2 编译器 内核的编译器和 uboot 的编译器一样,参考“5.3.1.2 编...

书白
26分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部