新闻内容

音频编码之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层的方法。

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.ione.opustool;  
  2.   
  3. public class OpusTool {  
  4.   
  5.     public native String nativeGetString();  
  6.   
  7.     public native int encode_wav_file(String in_path, String out_path);  
  8.   
  9.     public native int decode_opus_file(String in_path, String out_path);  
  10. }  

其中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文件。如:

 

 

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_ione_opustool_OpusTool */  
  4.   
  5. #ifndef _Included_com_ione_opustool_OpusTool  
  6. #define _Included_com_ione_opustool_OpusTool  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_ione_opustool_OpusTool 
  12.  * Method:    nativeGetString 
  13.  * Signature: ()Ljava/lang/String; 
  14.  */  
  15. JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     com_ione_opustool_OpusTool 
  20.  * Method:    encode_wav_file 
  21.  * Signature: (Ljava/lang/String;Ljava/lang/String;)I 
  22.  */  
  23. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file  
  24.   (JNIEnv *, jobject, jstring, jstring);  
  25.   
  26. /* 
  27.  * Class:     com_ione_opustool_OpusTool 
  28.  * Method:    decode_opus_file 
  29.  * Signature: (Ljava/lang/String;Ljava/lang/String;)I 
  30.  */  
  31. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file  
  32.   (JNIEnv *, jobject, jstring, jstring);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  

接下来复制一份com_ione_opustool_OpusTool.h文件到jni目录,修改名称为com_ione_opustool_OpusTool.c修改内容为:

 

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #include <com_ione_opustool_OpusTool.h>  
  2.   
  3. JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString  
  4.                    JNIEnv * env, jobject obj) {  
  5.     return (*env)->NewStringUTF(env, "Hello Opus");  
  6. }  
  7.   
  8. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file(  
  9.         JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {  
  10.     return 0;  
  11. }  
  12.   
  13. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file(  
  14.         JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {  
  15.     return 0;  
  16. }  
然后创建并配置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

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /***************************************************************************** 
  2.  # -*- coding:utf-8 -*- 
  3.  # author: ione 
  4.  # create date: 2014-11-27 
  5.  *****************************************************************************/  
  6. #include "android_log.h"  
  7. #include "opus.h"  
  8. #include "opus_types.h"  
  9. #include "opus_multistream.h"  
  10.   
  11. #define SAMPLE_RATE 16000  
  12. #define CHANNEL_NUM 1  
  13. #define BIT_RATE 16000  
  14. #define BIT_PER_SAMPLE 16  
  15. #define WB_FRAME_SIZE 320  
  16. #define DATA_SIZE 1024 * 1024 * 4  
  17.   
  18. int encode(char* in, int len, unsigned char* opus, int* opus_len) {  
  19.     int err = 0;  
  20.     opus_int32 skip = 0;  
  21.   
  22.     OpusEncoder *enc = opus_encoder_create(SAMPLE_RATE, CHANNEL_NUM,  
  23.             OPUS_APPLICATION_VOIP, &err);  
  24.     if (err != OPUS_OK) {  
  25.         fprintf(stderr, "cannnot create opus encoder: %s\n",  
  26.                 opus_strerror(err));  
  27.         enc = NULL;  
  28.         return -1;  
  29.     }  
  30.   
  31.     opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND));  
  32.     opus_encoder_ctl(enc, OPUS_SET_BITRATE(BIT_RATE));  
  33.     opus_encoder_ctl(enc, OPUS_SET_VBR(1));  
  34.     opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(10));  
  35.     opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(0));  
  36.     opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));  
  37.     opus_encoder_ctl(enc, OPUS_SET_DTX(0));  
  38.     opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(0));  
  39.     opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip));  
  40.     opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16));  
  41.   
  42.     short frame_size = WB_FRAME_SIZE;  
  43.     int frame_bytes = (frame_size << 1);  
  44.   
  45.     opus_int16 *frame = (opus_int16 *) in;  
  46.     unsigned char *cbits = opus;  
  47.   
  48.     while (len > frame_bytes) {  
  49.         int nbytes = opus_encode(enc, frame, frame_size, cbits + sizeof(char),  
  50.                 640 - sizeof(short));  
  51.         if (nbytes > frame_size * 2 || nbytes < 0) {  
  52.             return -1;  
  53.         }  
  54.         cbits[0] = nbytes;  
  55.         frame += WB_FRAME_SIZE;  
  56.         cbits += nbytes + sizeof(char);  
  57.         len -= frame_bytes;  
  58.         *opus_len += nbytes + sizeof(char);  
  59.     }  
  60.     opus_encoder_destroy(enc);  
  61.     return 0;  
  62. }  
  63.   
  64. int decode(unsigned char* in, int len, short* out, int* out_len) {  
  65.     int err = 0;  
  66.     opus_int32 skip = 0;  
  67.     *out_len = 0;  
  68.   
  69.     OpusDecoder *dec = opus_decoder_create(SAMPLE_RATE, 1, &err);  
  70.     if (err != OPUS_OK) {  
  71.         fprintf(stderr, "cannnot decode opus: %s\n", opus_strerror(err));  
  72.         dec = NULL;  
  73.         return -1;  
  74.     }  
  75.   
  76.     short frame_size = WB_FRAME_SIZE;  
  77.   
  78.     opus_int16 *frame = (opus_int16 *) in;  
  79.   
  80.     while (len > 0) {  
  81.         int nbytes = in[0];  
  82.         if (nbytes <= 0) {  
  83.             return -1;  
  84.         }  
  85.         int decode_len = opus_decode(dec, in + sizeof(char), nbytes, out,  
  86.                 frame_size, 0);  
  87.         if (decode_len != frame_size) {  
  88.             return -1;  
  89.         }  
  90.   
  91.         in += sizeof(char) + nbytes;  
  92.         out += frame_size;  
  93.         len -= nbytes - sizeof(char);  
  94.         *out_len += frame_size;  
  95.     }  
  96.     opus_decoder_destroy(dec);  
  97.     return 0;  
  98. }  
  99.   
  100. int encode_wav_file(char *in_file_path, char *out_file_path) {  
  101.     FILE *fin = fopen(in_file_path, "rb");  
  102.   
  103.     if (fin == NULL || fin == 0) {  
  104.         return -1;  
  105.     }  
  106.     char *in = (char*) malloc(DATA_SIZE);  
  107.     memset(in, 0, DATA_SIZE);  
  108.     int len = fread(in, 1, DATA_SIZE, fin);  
  109.     if (len == 0) {  
  110.         return -1;  
  111.     }  
  112.     FILE *fout = fopen(out_file_path, "wb");  
  113.   
  114.     if (fout == NULL || fout == 0) {  
  115.         return -1;  
  116.     }  
  117.   
  118.     unsigned char *out = (unsigned char*) malloc(DATA_SIZE);  
  119.     memset(out, 0, DATA_SIZE);  
  120.     int out_len = 0;  
  121.     encode(in, len, out, &out_len);  
  122.     if (len < 0) {  
  123.         return -1;  
  124.     }  
  125.     fwrite(out, 1, out_len * sizeof(unsigned char), fout);  
  126.   
  127.     free(in);  
  128.     free(out);  
  129.     fclose(fin);  
  130.     fclose(fout);  
  131.     return len;  
  132. }  
  133.   
  134. int make_wav_header(FILE *out, int len) {  
  135.     int size = 0;  
  136.     int *sz = &size;  
  137.     int number;  
  138.     int * nm = &number;  
  139.   
  140.     // RIFF  4 bytes  
  141.     fseek(out, 0, SEEK_SET);  
  142.     fputs("RIFF", out);  
  143.   
  144.     // len   4 bytes  
  145.     len = (len + 44 - 8);  
  146.     fwrite(&len, 2, 1, out);  
  147.     number = 0;  
  148.     fwrite(nm, 2, 1, out);  
  149.   
  150.     // WAVE  4 bytes  + "fmt " 4 bytes  
  151.     fputs("WAVEfmt ", out);  
  152.   
  153.     // size1   4 bytes  
  154.     number = 16;  
  155.     fwrite(nm, 2, 1, out);  
  156.     number = 0;  
  157.     fwrite(nm, 2, 1, out);  
  158.   
  159.     // format tag       2 bytes  
  160.     number = 1;  
  161.     fwrite(nm, 2, 1, out);  
  162.   
  163.     // channel    2 bytes  
  164.     number = CHANNEL_NUM;  
  165.     fwrite(nm, 2, 1, out);  
  166.   
  167.     // sample rate          4 bytes  
  168.     number = SAMPLE_RATE;  
  169.     fwrite(nm, 2, 1, out);  
  170.     number = 0;  
  171.     fwrite(nm, 2, 1, out);  
  172.   
  173.     //byte per seconds   4 bytes  
  174.     number = 22664;  
  175.     fwrite(nm, 2, 1, out);  
  176.     number = 0;  
  177.     fwrite(nm, 2, 1, out);  
  178.   
  179.     // block align   2 bytes  
  180.     number = CHANNEL_NUM * BIT_PER_SAMPLE / 8;  
  181.     fwrite(nm, 2, 1, out);  
  182.   
  183.     // bitPerSample   2 bytes  
  184.     number = 16;  
  185.     fwrite(nm, 2, 1, out);  
  186.   
  187.     // "data"      4 bytes  
  188.     fputs("data", out);  
  189.   
  190.     // size2    4 bytes  
  191.     size = (size - 36);  
  192.     fwrite(sz, 2, 1, out);  
  193.     number = 0;  
  194.     fwrite(nm, 2, 1, out);  
  195.   
  196.     return 0;  
  197. }  
  198.   
  199. int decode_opus_file(char *in_file_path, char *out_file_path) {  
  200.     printf("%s\n", in_file_path);  
  201.     FILE *fin = fopen(in_file_path, "rb");  
  202.     if (fin == NULL || fin == 0) {  
  203.         return -1;  
  204.     }  
  205.     unsigned char *in = (unsigned char *) malloc(DATA_SIZE);  
  206.     memset(in, 0, DATA_SIZE);  
  207.     int len = fread(in, 1, DATA_SIZE, fin);  
  208.   
  209.     FILE *fout = fopen(out_file_path, "wb");  
  210.     if (fout == NULL || fout == 0) {  
  211.         return -1;  
  212.     }  
  213.     short *out = (short *) malloc(DATA_SIZE);  
  214.     memset(out, 0, DATA_SIZE);  
  215.   
  216.     int out_len = 0;  
  217.     out += 44;  
  218.     decode(in, len, (short *) out, &out_len);  
  219.     if (len < 0) {  
  220.         return -1;  
  221.     }  
  222.     fwrite(out, 1, out_len * sizeof(short), fout);  
  223.     int err = make_wav_header(fout, out_len);  
  224.   
  225.     free(in);  
  226.     free(out);  
  227.     fclose(fin);  
  228.     fclose(fout);  
  229.     return out_len;  
  230. }  
配置makefile文件添加opus_tool.c文件,然后编译,即可在libs目录下生成.so文件

 


至此,native层操作已经完成,so库也已经通过编译得到。

下一篇将会介绍如何使用该so库。

音频编码之opus(二)