最近项目中用到了语音编码opus,在网上搜了一下,资料非常少,而且没有一个完整的教程,现在简单记录下来opus的使用方法。
首先介绍一下opus
Opus
Opus编码器 是一个有损声音编码的格式,由互联网工程任务组(IETF)进来开发,适用于网络上的实时声音传输,标准格式为RFC 6716。Opus 格式是一个开放格式,使用上没有任何专利或限制。
特性
Opus的前身是celt编码器。在当今的有损音频格式争夺上,拥有众多不同编码器的AAC格式打败了同样颇有潜力的Musepack、Vorbis等格式,而在Opus格式诞生后,情况似乎不同了。通过诸多的对比测试,低码率下Opsu完胜曾经优势明显的HE AAC,中码率就已经可以媲敌码率高出30%左右的AAC格式,而高码率下更接近原始音频。
以上来自百度百科(PS:百度百科对opus的介绍都很少)
简单来说,opus是一个高保真的适合在网络中传输的开源的语音编码格式,相对于其他编码格式来讲,保真性更好,但体积会稍微大一些。官网地址:http://www.opus-codec.org/
怎么用呢?
首先你可以使用编译好的so库直接使用,或者也可以使用源码自己根据需求生成so库来使用,当然,你也可以直接将源码使用到自己工程各中,这就是开源的好处。好了下面介绍如何编译。
我是通过Eclipse来编译的,首先在opus官网下载源代码,解压。
编码工作需要ndk编程所以需要一些NDK编程的知识。
在工程中创建OpusTool类,该类用于调用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);
-
}
javah -classpath .\bin\classes -d jni com.ione.opustool.OpusTool
其中.\bin\classes为指定OpusTool.class的路径,-d jni为在当前目录下生成jni文件夹,用来存放native层代码。回车之后便在工程的根目录下生成了jni文件夹以及com_ione_opustool_OpusTool.h文件。如:
-
/* DO NOT EDIT THIS FILE - it is machine generated */
-
#include
-
/* 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
-
#include
-
-
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;
-
}
然后创建并配置makefile和android.mk文件,后面会给出。记得配置NDK_Builder。
在jni目录下创建libopus文件夹,将Opus源码粘贴到该文件夹下,即celt、include、silk、src文件夹以及config文件,当然不是所有的文件都用的上,可以根据自记得需求进行拷贝。配置好makefile等配置文件后即可编译工程,如果编译顺利,则说明配置文件没有问题,继续操作。在src文件加下创建opus_tool.c文件用来进行音频文件的编解码的c实现。
opus_tool.c
-
/*****************************************************************************
-
# -*- coding:utf-8 -*-
-
# author: ione
-
# create date: 2014-11-27
-
*****************************************************************************/
-
#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\n",
-
opus_strerror(err));
-
enc =
NULL;
-
return
-1;
-
}
-
-
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
-1;
-
}
-
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\n", opus_strerror(err));
-
dec =
NULL;
-
return
-1;
-
}
-
-
short frame_size = WB_FRAME_SIZE;
-
-
opus_int16 *frame = (opus_int16 *) in;
-
-
while (len >
0) {
-
int nbytes = in[
0];
-
if (nbytes <=
0) {
-
return
-1;
-
}
-
int decode_len = opus_decode(dec, in +
sizeof(
char), nbytes, out,
-
frame_size,
0);
-
if (decode_len != frame_size) {
-
return
-1;
-
}
-
-
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
-1;
-
}
-
char *in = (
char*)
malloc(DATA_SIZE);
-
memset(in,
0, DATA_SIZE);
-
int len = fread(in,
1, DATA_SIZE, fin);
-
if (len ==
0) {
-
return
-1;
-
}
-
FILE *fout = fopen(out_file_path,
"wb");
-
-
if (fout ==
NULL || fout ==
0) {
-
return
-1;
-
}
-
-
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
-1;
-
}
-
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\n", in_file_path);
-
FILE *fin = fopen(in_file_path,
"rb");
-
if (fin ==
NULL || fin ==
0) {
-
return
-1;
-
}
-
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
-1;
-
}
-
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
-1;
-
}
-
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库也已经通过编译得到。
下一篇将会介绍如何使用该so库。
音频编码之opus(二)