天天看點

Webassembly 學習3 -- 打造web端的aac 播放器

  1、引言

           aac 是很常見的音頻格式,壓縮率比mp3 還高,H5 支援從audio 标簽檔案讀取aac 檔案并播放,但不支援從網絡流中直接讀取。這裡借助webassembly 技術,将aac 轉碼成pcm碼流,再借助web audio api 實作aac音頻播放。主要用到的開源庫有faad、pcm-player

  2、編譯

   進入faad 官網,http://www.linuxfromscratch.org/blfs/view/svn/multimedia/faad2.html,下載下傳faad源碼包并解壓,cd 到工程目錄

cd faad2-2_10_0/
mkdir dist
emconfigure ./configure --prefix=$(pwd)/dist --disable-shared --without-xmms --without-drm --without-mpeg4ip
emmake make
make install
           

  make install 完成後會在dist 目錄生成lib、include 等目錄,裡面存放了faad 頭檔案和庫,複制到路徑備用。

3、faad 使用

    一般來說,我不會直接js 調用faad 庫api,而是在外面封裝一層,隻暴露一些必要的接口。

 NeAACDecOpen

 建立一個編碼器并傳回一個NeAACDecHandle類型句柄

NeAACDecHandle NeAACDecOpen(void)

NeAACDecClose(NeAACDecHandle );
           

  NeAACDecInit2

  初始化函數,小于0表示失敗

NEAACDECAPI char NeAACDecInit2(NeAACDecHandle hDecoder,
                               unsigned char *pBuffer,
                               unsigned long SizeOfDecoderSpecificInfo,
                               unsigned long *samplerate,
                               unsigned char *channels);
           

參數

hDecoder 

    NeAACDecOpen 建立的句柄

pBuffer

    指向緩存空間,用于儲存轉換後的pcm 資料

SizeOfDecoderSpecificInfo

   輸入aac 音頻的大小

sampleRate

    音頻采樣率

channels 

   音頻通道數

NeAACDecDecode

void* NeAACDecDecode(NeAACDecHandle hpDecoder,
                                 NeAACDecFrameInfo *hInfo,
                                 unsigned char *buffer,
                                 unsigned long buffer_size)
           

hpDecoder 

    NeAACDecOpen建立的句柄

NeAACDecFrameInfo

     NeAACDecFrameInfo 指針

buffer

    指向輸入AAC音頻的緩存區

buffer_size

   輸入AAC音頻大小

NeAACDecClose

void NeAACDecClose(NeAACDecHandle hpDecoder)
           

C端完整代碼

#include <memory.h>
#include <stdlib.h>
#include "faad.h"
#include <stdbool.h>
#include <string.h>
#include <emscripten.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <unistd.h>

bool hasInit = false;

NeAACDecHandle decoder = 0;
NeAACDecFrameInfo frame_info;

void PrintArry(unsigned char *buffer, unsigned int size)
{
	int i;
	char data[1024*1024];
	
	for(i = 0;i < size;i++)
	{
		data[i] = buffer[i];
	}
	
	data[i + 1] = '\0';
}

int init_decoder(unsigned char* inBuffer, size_t size)
{  
    unsigned char channels;
    unsigned long sampleRate;
    
    memset(&frame_info, 0, sizeof(frame_info));
    decoder = NeAACDecOpen();
    NeAACDecInit(decoder, inBuffer, size, &sampleRate, &channels);
    //printf("init_decoder初始化完畢\n");
    hasInit = true;
    return 0;
}

int feedData(unsigned char* out_data, unsigned char* buffer, unsigned int size)
{
	int ret = 0;
	
    if (!hasInit)
    {
        init_decoder(buffer, size);
    }

    unsigned char *out_buffer = (unsigned char*)NeAACDecDecode(decoder, &frame_info, buffer, size);
	//printf("frame_info.error %d\n",frame_info.error);

    if (frame_info.error > 0)
    {		
        return frame_info.error;
    }
    else if(out_buffer && frame_info.samples > 0)//解碼成功
    {
		ret = frame_info.samples * frame_info.channels;
		for(int i = 0;i < ret;i++)
		{
			 out_data[i] = out_buffer[i];
		}
    }

    return ret;
}

void destroyDecoder()
{
	hasInit = false;
    NeAACDecClose(decoder);
}
           

4、html 調用

 再次進行編譯,将C編譯成 最終目标wasm

export TOTAL_MEMORY=10485760
rm *.js *.wasm

export EXPORTED_FUNCTIONS="[  	\
	'_malloc' \
	,'_free' \
	,'_destroyDecoder' \
	,'_feedData'   \
]"

export LIBRARY_FUNCTIONS="[\
    'malloc', \
    'free'	  \
]"

#
emcc aac.c  \
-O3 \
-s WASM=1 \
-I /usr/local -lfaad -lm -L ./lib  \
-s TOTAL_MEMORY=${TOTAL_MEMORY} \
-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE="${LIBRARY_FUNCTIONS}" \
-s EXPORTED_FUNCTIONS="${EXPORTED_FUNCTIONS}" \
-o aac.js
           

最後html 調用

<html>
	<head>
		<link rel="shortcut icon" href="#" target="_blank" rel="external nofollow" >
	</head>
	<script type="text/javascript" src="pcm-player.js"></script>
	<script type="text/javascript" src="helper.js"></script>
	<script src="https://cdn.bootcss.com/vConsole/3.2.0/vconsole.min.js"></script>
	
	<body>
		請點選一下螢幕,才能播放聲音
	</body>
	<script>

		var decodeCount = 1;
	    var isFinish = false;
		var socketURL = "ws://192.168.11.66:9101";
		socketURL = "ws://127.0.0.1:8080";
		
		let vConsole = new VConsole();
		
		var player = new PCMPlayer({
        encoding: '16bitInt',
        channels: 2,
        sampleRate: 44100,
        flushingTime: 22,
		debug:false
		});
		
		FaadModule = {};
		FaadModule.onRuntimeInitialized = function() 
		{
			console.log("Wasm 加載成功!")
			isFinish = true;
		}
	
		function closeDecoder()//關閉解碼器
		{
			FaadModule._destroyDecoder();
		}
		
		function decodeAAC(data)
		{
			var retPtr = FaadModule._malloc(4 * 5 * 1024);//接收的資料
			var inputPtr = FaadModule._malloc(4 * data.length);//輸入資料
			
			for( i =0;i < data.length;i++)
			{
				FaadModule.HEAPU8[(inputPtr)+i] = data[i];//轉換為堆資料
			}
			
			var pcmLen = FaadModule._feedData(retPtr, inputPtr, data.length);
			
			if(pcmLen >= 0)
			{
				//console.log("%d幀 aac 解碼成功, %d", decodeCount, pcmLen);
				var pcmData = new Uint8Array(pcmLen);		
				for(i = 0;i < pcmLen;i++)
				{
					pcmData[i] = FaadModule.HEAPU8[(retPtr)+i]
				}
				
				player.feed(pcmData);
			}
			else
			{
				console.log("%d幀 aac 解碼失敗, %d", decodeCount, pcmLen);
			}
			
			decodeCount++;
			FaadModule._free(inputPtr);
			FaadModule._free(retPtr);
		}
					
		var ws = new WebSocket(socketURL);
		ws.binaryType = 'arraybuffer';
		
		ws.addEventListener('open', function (event) {
		console.log("發送配置幀");
		ws.send(ConfigChannel("RK3923C1201900139"));
	});
		
		ws.addEventListener('message',function(event) 
		{	
			var input = new Uint8Array(event.data);
			if(input[0] == 0xff)
			{
				//console.log("音頻資料");
				if(isFinish)
				{
					var time = new Date().getTime();
					decodeAAC(input);
					//console.log("音頻解碼耗時:%d ms", new Date().getTime() - time);
				}
				
			}
			else
			{
				//console.log("視訊資料");
			}
			
		}
		);
	
	</script>
	<script type="text/javascript" src="aac.js"></script>
</html>
           

 注: chrome 浏覽器可能需要點選以下才有聲音,跟web-audio api有關 有關。

附件:web端播放aac音頻

繼續閱讀