音頻編碼之opus
來源:程序員人生 發(fā)布時間:2014-12-08 08:19:03 閱讀次數(shù):7015次
最近項目中用到了語音編碼opus,在網(wǎng)上搜了1下,資料非常少,而且沒有1個完全的教程,現(xiàn)在簡單記錄下來opus的使用方法。
首先介紹1下opus
Opus
Opus編碼器 是1個有損聲音編碼的格式,由互聯(lián)網(wǎng)工程任務組(IETF)進來開發(fā),適用于網(wǎng)絡上的實時聲音傳輸,標準格式為RFC 6716。Opus
格式是1個開放格式,使用上沒有任何專利或限制。
特性
Opus的前身是celt編碼器。在現(xiàn)今的有損音頻格式爭取上,具有眾多不同編碼器的AAC格式打敗了一樣很有潛力的Musepack、Vorbis等格式,而在Opus格式誕生后,情況似乎不同了。通過諸多的對照測試,低碼率下Opsu完勝曾優(yōu)勢明顯的HE
AAC,中碼率就已可以媲敵碼率高出30%左右的AAC格式,而高碼率下更接近原始音頻。
以上來自百度百科(PS:百度百科對opus的介紹都很少)
簡單來講,opus是1個高保真的合適在網(wǎng)絡中傳輸?shù)拈_源的語音編碼格式,相對其他編碼格式來說,保真性更好,但體積會略微大1些。官網(wǎng)地址:http://www.opus-codec.org/
怎樣用呢?
首先你可使用編譯好的so庫直接使用,或也能夠使用源碼自己根據(jù)需求生成so庫來使用,固然,你也能夠直接將源碼使用到自己工程各中,這就是開源的好處。好了下面介紹如何編譯。
我是通過Eclipse來編譯的,首先在opus官網(wǎng)下載源代碼,解壓。
編碼工作需要ndk編程所以需要1些NDK編程的知識。
在工程中創(chuàng)建OpusTool類,該類用于調(diào)用native層的方法。
package com.ione.opustool;
public class OpusTool {
public native String nativeGetString();
public native int encode_wav_file(String in_path, String out_path);
public native int decode_opus_file(String in_path, String out_path);
}
其中nativeGetString()方法是用來測試jni調(diào)用是不是成功的測試方法,encode_wav_file(String in_path, String out_path);和 decode_opus_file(String in_path, String out_path);分別是用來編解碼。以上3個方法均需聲明為native,用來調(diào)用jni的c函數(shù)。然后在項目根目錄下打開命令行,使用javah命令生成.h文件,即:
javah -classpath .inclasses -d jni com.ione.opustool.OpusTool
其中.inclasses為指定OpusTool.class的路徑,-d jni為在當前目錄下生成jni文件夾,用來寄存native層代碼。回車以后便在工程的根目錄下生成了jni文件夾和com_ione_opustool_OpusTool.h文件。如:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ione_opustool_OpusTool */
#ifndef _Included_com_ione_opustool_OpusTool
#define _Included_com_ione_opustool_OpusTool
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ione_opustool_OpusTool
* Method: nativeGetString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
(JNIEnv *, jobject);
/*
* Class: com_ione_opustool_OpusTool
* Method: encode_wav_file
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file
(JNIEnv *, jobject, jstring, jstring);
/*
* Class: com_ione_opustool_OpusTool
* Method: decode_opus_file
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file
(JNIEnv *, jobject, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
接下來復制1份
com_ione_opustool_OpusTool.h文件到jni目錄,修改名稱為com_ione_opustool_OpusTool.c修改內(nèi)容為:
#include <com_ione_opustool_OpusTool.h>
JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
JNIEnv * env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello Opus");
}
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file(
JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
return 0;
}
JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file(
JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
return 0;
}
然后創(chuàng)建并配置makefile和android.mk文件,后面會給出。記得配置NDK_Builder。
此時可以調(diào)用
OpusTool類的nativeGetString()方法查看返回數(shù)據(jù)是不是正常,若為Hello Opus 則jni調(diào)用成功。可以繼續(xù)下面的工作。
在jni目錄下創(chuàng)建libopus文件夾,將Opus源碼粘貼到該文件夾下,即celt、include、silk、src文件夾和config文件,固然不是所有的文件都用的上,可以根據(jù)自記得需求進行拷貝。配置好makefile等配置文件后便可編譯工程,如果編譯順利,則說明配置文件沒有問題,繼續(xù)操作。在src文件加下創(chuàng)建opus_tool.c文件用來進行音頻文件的編解碼的c實現(xiàn)。
opus_tool.c
/*****************************************************************************
# -*- coding:utf⑻ -*-
# author: ione
# create date: 2014⑴1⑵7
*****************************************************************************/
#include "android_log.h"
#include "opus.h"
#include "opus_types.h"
#include "opus_multistream.h"
#define SAMPLE_RATE 16000
#define CHANNEL_NUM 1
#define BIT_RATE 16000
#define BIT_PER_SAMPLE 16
#define WB_FRAME_SIZE 320
#define DATA_SIZE 1024 * 1024 * 4
int encode(char* in, int len, unsigned char* opus, int* opus_len) {
int err = 0;
opus_int32 skip = 0;
OpusEncoder *enc = opus_encoder_create(SAMPLE_RATE, CHANNEL_NUM,
OPUS_APPLICATION_VOIP, &err);
if (err != OPUS_OK) {
fprintf(stderr, "cannnot create opus encoder: %s
",
opus_strerror(err));
enc = NULL;
return ⑴;
}
opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));
opus_encoder_ctl(enc, OPUS_SET_BITRATE(BIT_RATE));
opus_encoder_ctl(enc, OPUS_SET_VBR(1));
opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(10));
opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(0));
opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));
opus_encoder_ctl(enc, OPUS_SET_DTX(0));
opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(0));
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip));
opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16));
short frame_size = WB_FRAME_SIZE;
int frame_bytes = (frame_size << 1);
opus_int16 *frame = (opus_int16 *) in;
unsigned char *cbits = opus;
while (len > frame_bytes) {
int nbytes = opus_encode(enc, frame, frame_size, cbits + sizeof(char),
640 - sizeof(short));
if (nbytes > frame_size * 2 || nbytes < 0) {
return ⑴;
}
cbits[0] = nbytes;
frame += WB_FRAME_SIZE;
cbits += nbytes + sizeof(char);
len -= frame_bytes;
*opus_len += nbytes + sizeof(char);
}
opus_encoder_destroy(enc);
return 0;
}
int decode(unsigned char* in, int len, short* out, int* out_len) {
int err = 0;
opus_int32 skip = 0;
*out_len = 0;
OpusDecoder *dec = opus_decoder_create(SAMPLE_RATE, 1, &err);
if (err != OPUS_OK) {
fprintf(stderr, "cannnot decode opus: %s
", opus_strerror(err));
dec = NULL;
return ⑴;
}
short frame_size = WB_FRAME_SIZE;
opus_int16 *frame = (opus_int16 *) in;
while (len > 0) {
int nbytes = in[0];
if (nbytes <= 0) {
return ⑴;
}
int decode_len = opus_decode(dec, in + sizeof(char), nbytes, out,
frame_size, 0);
if (decode_len != frame_size) {
return ⑴;
}
in += sizeof(char) + nbytes;
out += frame_size;
len -= nbytes - sizeof(char);
*out_len += frame_size;
}
opus_decoder_destroy(dec);
return 0;
}
int encode_wav_file(char *in_file_path, char *out_file_path) {
FILE *fin = fopen(in_file_path, "rb");
if (fin == NULL || fin == 0) {
return ⑴;
}
char *in = (char*) malloc(DATA_SIZE);
memset(in, 0, DATA_SIZE);
int len = fread(in, 1, DATA_SIZE, fin);
if (len == 0) {
return ⑴;
}
FILE *fout = fopen(out_file_path, "wb");
if (fout == NULL || fout == 0) {
return ⑴;
}
unsigned char *out = (unsigned char*) malloc(DATA_SIZE);
memset(out, 0, DATA_SIZE);
int out_len = 0;
encode(in, len, out, &out_len);
if (len < 0) {
return ⑴;
}
fwrite(out, 1, out_len * sizeof(unsigned char), fout);
free(in);
free(out);
fclose(fin);
fclose(fout);
return len;
}
int make_wav_header(FILE *out, int len) {
int size = 0;
int *sz = &size;
int number;
int * nm = &number;
// RIFF 4 bytes
fseek(out, 0, SEEK_SET);
fputs("RIFF", out);
// len 4 bytes
len = (len + 44 - 8);
fwrite(&len, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// WAVE 4 bytes + "fmt " 4 bytes
fputs("WAVEfmt ", out);
// size1 4 bytes
number = 16;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// format tag 2 bytes
number = 1;
fwrite(nm, 2, 1, out);
// channel 2 bytes
number = CHANNEL_NUM;
fwrite(nm, 2, 1, out);
// sample rate 4 bytes
number = SAMPLE_RATE;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
//byte per seconds 4 bytes
number = 22664;
fwrite(nm, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
// block align 2 bytes
number = CHANNEL_NUM * BIT_PER_SAMPLE / 8;
fwrite(nm, 2, 1, out);
// bitPerSample 2 bytes
number = 16;
fwrite(nm, 2, 1, out);
// "data" 4 bytes
fputs("data", out);
// size2 4 bytes
size = (size - 36);
fwrite(sz, 2, 1, out);
number = 0;
fwrite(nm, 2, 1, out);
return 0;
}
int decode_opus_file(char *in_file_path, char *out_file_path) {
printf("%s
", in_file_path);
FILE *fin = fopen(in_file_path, "rb");
if (fin == NULL || fin == 0) {
return ⑴;
}
unsigned char *in = (unsigned char *) malloc(DATA_SIZE);
memset(in, 0, DATA_SIZE);
int len = fread(in, 1, DATA_SIZE, fin);
FILE *fout = fopen(out_file_path, "wb");
if (fout == NULL || fout == 0) {
return ⑴;
}
short *out = (short *) malloc(DATA_SIZE);
memset(out, 0, DATA_SIZE);
int out_len = 0;
out += 44;
decode(in, len, (short *) out, &out_len);
if (len < 0) {
return ⑴;
}
fwrite(out, 1, out_len * sizeof(short), fout);
int err = make_wav_header(fout, out_len);
free(in);
free(out);
fclose(fin);
fclose(fout);
return out_len;
}
配置makefile文件添加opus_tool.c文件,然后編譯,便可在libs目錄下生成.so文件

至此,native層操作已完成,so庫也已通過編譯得到。
下1篇將會介紹如何使用該so庫。
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學習有所幫助,可以手機掃描二維碼進行捐贈