Opus编解码

最近项目中用到了语音编码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层的方法。


 
 
   
   
   
   
  1. package com.ione.opustool;
  2. public class OpusTool {
  3. public native String nativeGetString();
  4. public native int encode_wav_file(String in_path, String out_path);
  5. public native int decode_opus_file(String in_path, String out_path);
  6. }

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


Opus编解码_第1张图片



 
 
   
   
   
   
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include
  3. /* Header for class com_ione_opustool_OpusTool */
  4. #ifndef _Included_com_ione_opustool_OpusTool
  5. #define _Included_com_ione_opustool_OpusTool
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class: com_ione_opustool_OpusTool
  11. * Method: nativeGetString
  12. * Signature: ()Ljava/lang/String;
  13. */
  14. JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
  15. (JNIEnv *, jobject);
  16. /*
  17. * Class: com_ione_opustool_OpusTool
  18. * Method: encode_wav_file
  19. * Signature: (Ljava/lang/String;Ljava/lang/String;)I
  20. */
  21. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file
  22. (JNIEnv *, jobject, jstring, jstring);
  23. /*
  24. * Class: com_ione_opustool_OpusTool
  25. * Method: decode_opus_file
  26. * Signature: (Ljava/lang/String;Ljava/lang/String;)I
  27. */
  28. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file
  29. (JNIEnv *, jobject, jstring, jstring);
  30. #ifdef __cplusplus
  31. }
  32. #endif
  33. #endif

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


 
 
   
   
   
   
  1. #include
  2. JNIEXPORT jstring JNICALL Java_com_ione_opustool_OpusTool_nativeGetString
  3. JNIEnv * env, jobject obj) {
  4. return (*env)->NewStringUTF(env, "Hello Opus");
  5. }
  6. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_encode_1wav_1file(
  7. JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
  8. return 0;
  9. }
  10. JNIEXPORT jint JNICALL Java_com_ione_opustool_OpusTool_decode_1opus_1file(
  11. JNIEnv *env, jobject obj, jstring wav_path, jstring opus_path) {
  12. return 0;
  13. }
然后创建并配置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


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

Opus编解码_第2张图片
至此,native层操作已经完成,so库也已经通过编译得到。

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

音频编码之opus(二)

你可能感兴趣的:(音频)