音频编码之opus(一)
信息来源:双赞工控|时间:2017-03-19 19:38|浏览量:
首先介绍一下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);
- }
其中nativeGetString()方法是用来测试jni调用是否成功的测试方法,encode_wav_file(String in_path, String out_path);和 decode_opus_file(String in_path, String out_path);分别是用来编解码。以上三个方法均需声明为native,用来调用jni的c函数。然后在项目根目录下打开命令行,使用javah命令生成.h文件,即:
javah -classpath .\bin\classes -d jni com.ione.opustool.OpusTool
其中.\bin\classes为指定OpusTool.class的路径,-d jni为在当前目录下生成jni文件夹,用来存放native层代码。回车之后便在工程的根目录下生成了jni文件夹以及com_ione_opustool_OpusTool.h文件。如:
-
- #include <jni.h>
-
-
- #ifndef _Included_com_ione_opustool_OpusTool
- #define _Included_com_ione_opustool_OpusTool
- #ifdef __cplusplus
- extern "C" {
- #endif
-
-
-
-
-
- JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
- (JNIEnv *, jobject);
-
-
-
-
-
-
- JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file
- (JNIEnv *, jobject, jstring, jstring);
-
-
-
-
-
-
- JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file
- (JNIEnv *, jobject, jstring, jstring);
-
- #ifdef __cplusplus
- }
- #endif
- #endif
接下来复制一份com_ione_opustool_OpusTool.h文件到jni目录,修改名称为com_ione_opustool_OpusTool.c修改内容为:
- #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;
- }
然后创建并配置makefile和android.mk文件,后面会给出。记得配置NDK_Builder。
此时可以调用OpusTool类的nativeGetString()方法查看返回数据是否正常,若为Hello Opus 则jni调用成功。可以继续下面的工作。
在jni目录下创建libopus文件夹,将Opus源码粘贴到该文件夹下,即celt、include、silk、src文件夹以及config文件,当然不是所有的文件都用的上,可以根据自记得需求进行拷贝。配置好makefile等配置文件后即可编译工程,如果编译顺利,则说明配置文件没有问题,继续操作。在src文件加下创建opus_tool.c文件用来进行音频文件的编解码的c实现。
opus_tool.c
-
-
-
-
-
- #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;
-
-
- fseek(out, 0, SEEK_SET);
- fputs("RIFF", out);
-
-
- len = (len + 44 - 8);
- fwrite(&len, 2, 1, out);
- number = 0;
- fwrite(nm, 2, 1, out);
-
-
- fputs("WAVEfmt ", out);
-
-
- number = 16;
- fwrite(nm, 2, 1, out);
- number = 0;
- fwrite(nm, 2, 1, out);
-
-
- number = 1;
- fwrite(nm, 2, 1, out);
-
-
- number = CHANNEL_NUM;
- fwrite(nm, 2, 1, out);
-
-
- number = SAMPLE_RATE;
- fwrite(nm, 2, 1, out);
- number = 0;
- fwrite(nm, 2, 1, out);
-
-
- number = 22664;
- fwrite(nm, 2, 1, out);
- number = 0;
- fwrite(nm, 2, 1, out);
-
-
- number = CHANNEL_NUM * BIT_PER_SAMPLE / 8;
- fwrite(nm, 2, 1, out);
-
-
- number = 16;
- fwrite(nm, 2, 1, out);
-
-
- fputs("data", out);
-
-
- 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(二)