Opus介绍及编译

https://blog.csdn.net/grandgrandpa/article/details/96438388

opus是一个有损声音编码的格式,由IETF开发,没有任何专利或限制,适用于网络上的实时声音传输,标准格式为RFC 6716,其技术来源于Skype的SILK及Xiph.Org的CELT编码

     主要特性如下:

  •     6 kb /秒到510 kb / s的比特率
  •     采样率从8 kHz(窄带)到48 kHz(全频)
  •     帧大小从2.5毫秒到60毫秒
  •     支持恒定比特率(CBR)和可变比特率(VBR)
  •     从窄带到全频段的音频带宽
  •     支持语音和音乐
  •     支持单声道和立体声
  •     支持多达255个频道(多数据流的帧)
  •     可动态调节比特率,音频带宽和帧大小
  •     良好的鲁棒性丢失率和数据包丢失隐藏(PLC)
  •     浮点和定点实现

     基于OPUS强大的PLC能力以及良好的VOIP音质, 我们决定在我们的视频会议中引入OPUS编码,用于Android/iOS/Windows视频会议客户端,以及视频会议媒体服务器中.
 

首先需要在opus官网上下载opus相关的源码资料

http://www.opus-codec.org/

在downloads里面可以看到全部的源码下载

这里我们需要下载

opus-tools-0.2.1.tar.gzopus-1.3.1.tar.gz

下载后可以在ubuntu里解压

然后

./configure

(如果是其余平台如Mips或Arm,需要添加 --host=(交叉编译链),在ARM和mips平台推荐使用--enable-fixed-point命令关闭浮点运算)

然后 make && make install

之后,会出现一堆供测试用的可执行文件


之前笔者犯了一个错误,就是直接用opus_demo文件对MP3和wav格式的音频进行编码,结果导致出错

 
  1. = Compiling libopus ==

  2. To build from a distribution tarball, you only need to do the following:

  3. % ./configure

  4. % make

  5. To build from the git repository, the following steps are necessary:

  6. 0) Set up a development environment:

  7. On an Ubuntu or Debian family Linux distribution:

  8. % sudo apt-get install git autoconf automake libtool gcc make

  9. On a Fedora/Redhat based Linux:

  10. % sudo dnf install git autoconf automake libtool gcc make

  11. Or for older Redhat/Centos Linux releases:

  12. % sudo yum install git autoconf automake libtool gcc make

  13. On Apple macOS, install Xcode and brew.sh, then in the Terminal enter:

  14. % brew install autoconf automake libtool

在README里面我们可以看到


input and output are little-endian signed 16-bit PCM files or opus
bitstreams with simple opus_demo proprietary framing.

所以更换了pcm格式的文件,我们便可以进行编码

编码的命令为:

./opus_demo -e voip 48000 2 128000 xxx.pcm xxx.opus

之后便生成你参数指定的opus文件

其中-e指的事编码,voip是编码格式,还有audio和restricted-lowdelay两种格式,48000是采样率,2是指双通道,128000是比特率,随后是输入文件和输出文件

这些输入./opus_demo --help都会有提示

随后我们可以对生成的opus文件解码

./opus_demo -d 48000 2  xxx.opus xxx.pcm

之后会解码生成pcm文件

当然,如果想直接将wav,flac格式的音频文件,编码成可播放的opus文件

需要使用opus_tools

同样是./configure make && install之后

然后使用

./opus_enc xxx.wav xxx.opus命令

生成的opus文件便可以播放啦

使用步骤:

1.创建opus解码器:

 
  1. OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create(

  2. opus_int32 Fs,//采样率,可以设置的大小为8000, 12000, 16000, 24000, 48000.

  3. int channels,//声道数,网络中实时音频一般为单声道

  4. int *error//是否创建失败,返回0表示创建成功

  5. );

2.解码:

 
  1. OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode(

  2. OpusDecoder *st, //上一步的返回值

  3. const unsigned char *data,//要解码的数据

  4. opus_int32 len, //数据长度

  5. opus_int16 *pcm, //解码后的数据,注意是一个以16位长度为基本单位的数组

  6. int frame_size, //每个声道给pcm数组的长度

  7. int decode_fec //是否需要fec,设置0为不需要,1为需要

  8. ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);

3.把解码数据放入字符数组中

//注意是大端传入,所以应该转为小端
//注:frameSize是上一步的返回值,即每个声道返回的以2字节为单位的数组长度
//pcm是解码后的数据,即上一步的参数4

 
  1. char *pcmData = new char[frameSize * channels * sizeof(opus_int16)];

  2. for (int i = 0; i < channels * frameSize; ++i)

  3. {

  4. pcmData[i * 2] = pcm[i] & 0xFF;

  5. pcmData[i * 2 + 1] = (pcm[i] >> 8) & 0xFF;

  6. }

4.释放解码器

OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);

注意事项:

1.对于连续的一段声音,一定只能用一个解码器(不能创建之后释放再去创建解码器)

2.如果使用fec,那么记得要自己检查seq确认丢失再使用fec,如果每一个包都使用fec,每一个都会给你解码出来数据的

接口应用实例:

 
  1. #include <opus_types.h>

  2. #include <opus.h>

  3. #include <cstring>

  4. #include <memory>

  5. #include <vector>

  6. #define DR_WAV_IMPLEMENTATION

  7. // https://github.com/mackron/dr_libs/blob/master/dr_wav.h

  8. #include "dr_wav.h"

  9. //需要dr_wav库

  10. #define FRAME_SIZE 480

  11. #define MAX_FRAME_SIZE (6*FRAME_SIZE)

  12. #define MAX_CHANNELS 1

  13. #define MAX_PACKET_SIZE (3*1276)

  14. #pragma pack(push)

  15. #pragma pack(1)

  16. struct WavInfo {

  17. uint16_t channels;

  18. uint32_t sampleRate;

  19. uint32_t bitsPerSample;

  20. };

  21. #pragma pack(pop)

  22. #ifndef nullptr

  23. #define nullptr NULL

  24. #endif

  25. class FileStream {

  26. public:

  27. FileStream() {

  28. cur_pos = 0;

  29. }

  30. void Append(const char *data, size_t size) {

  31. if (cur_pos + size > Size()) {

  32. vec.resize(cur_pos + size);

  33. }

  34. memcpy(vec.data() + cur_pos, data, size);

  35. cur_pos += size;

  36. }

  37. void AppendU32(uint32_t val) {

  38. Append((char *) (&val), sizeof(val));

  39. }

  40. char *Data() {

  41. return vec.data();

  42. }

  43. size_t Size() {

  44. return vec.size();

  45. }

  46. size_t Read(void *buff, size_t elemSize, size_t elemCount) {

  47. size_t readed = std::min((vec.size() - cur_pos), (elemCount * elemSize)) / elemSize;

  48. if (readed > 0) {

  49. memcpy(buff, vec.data() + cur_pos, readed * elemSize);

  50. cur_pos += readed * elemSize;

  51. }

  52. return readed;

  53. }

  54. bool SeekCur(int offset) {

  55. if (cur_pos + offset > vec.size()) {

  56. cur_pos = !vec.empty() ? (vec.size() - 1) : 0;

  57. return false;

  58. } else {

  59. cur_pos += offset;

  60. return true;

  61. }

  62. }

  63. bool SeekBeg(int offset = 0) {

  64. cur_pos = 0;

  65. return SeekCur(offset);

  66. }

  67. bool WriteToFile(const char *filename) {

  68. FILE *fin = fopen(filename, "wb");

  69. if (!fin) {

  70. return false;

  71. }

  72. fseek(fin, 0, SEEK_SET);

  73. fwrite(vec.data(), sizeof(char), vec.size(), fin);

  74. fclose(fin);

  75. return true;

  76. }

  77. bool ReadFromFile(const char *filename) {

  78. FILE *fin = fopen(filename, "rb");

  79. if (!fin) {

  80. return false;

  81. }

  82. fseek(fin, 0, SEEK_END);

  83. long fileSize = ftell(fin);

  84. vec.resize(static_cast<unsigned long long int>(fileSize));

  85. fseek(fin, 0, SEEK_SET);

  86. fread(vec.data(), sizeof(char), vec.size(), fin);

  87. fclose(fin);

  88. return true;

  89. }

  90. private:

  91. std::vector<char> vec;

  92. size_t cur_pos;

  93. };

  94. bool Wav2Opus(FileStream *input, FileStream *output);

  95. bool Opus2Wav(FileStream *input, FileStream *output);

  96. bool wav2stream(char *input, FileStream *output);

  97. bool stream2wav(FileStream *input, char *output);

  98. bool wavWrite_int16(char *filename, int16_t *buffer, int sampleRate, uint32_t totalSampleCount) {

  99. drwav_data_format format = {};

  100. format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64.

  101. format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes.

  102. format.channels = 1;

  103. format.sampleRate = (drwav_uint32) sampleRate;

  104. format.bitsPerSample = 16;

  105. drwav *pWav = drwav_open_file_write(filename, &format);

  106. if (pWav) {

  107. drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer);

  108. drwav_uninit(pWav);

  109. if (samplesWritten != totalSampleCount) {

  110. fprintf(stderr, "ERROR\n");

  111. return false;

  112. }

  113. return true;

  114. }

  115. return false;

  116. }

  117. int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount) {

  118. unsigned int channels;

  119. int16_t *buffer = drwav_open_and_read_file_s16(filename, &channels, sampleRate, totalSampleCount);

  120. if (buffer == nullptr) {

  121. fprintf(stderr, "ERROR\n");

  122. return nullptr;

  123. }

  124. if (channels != 1) {

  125. drwav_free(buffer);

  126. buffer = nullptr;

  127. *sampleRate = 0;

  128. *totalSampleCount = 0;

  129. }

  130. return buffer;

  131. }

  132. bool wav2stream(char *input, FileStream *output) {

  133. uint32_t sampleRate = 0;

  134. uint64_t totalSampleCount = 0;

  135. int16_t *wavBuffer = wavRead_int16(input, &sampleRate, &totalSampleCount);

  136. if (wavBuffer == nullptr) return false;

  137. WavInfo info = {};

  138. info.bitsPerSample = 16;

  139. info.sampleRate = sampleRate;

  140. info.channels = 1;

  141. output->SeekBeg();

  142. output->Append((char *) &info, sizeof(info));

  143. output->Append((char *) wavBuffer, totalSampleCount * sizeof(int16_t));

  144. free(wavBuffer);

  145. return true;

  146. }

  147. bool stream2wav(FileStream *input, char *output) {

  148. WavInfo info = {};

  149. input->SeekBeg();

  150. size_t read = input->Read(&info, sizeof(info), 1);

  151. if (read != 1) {

  152. return false;

  153. }

  154. size_t totalSampleCount = (input->Size() - sizeof(info)) / 2;

  155. return wavWrite_int16(output, (int16_t *) (input->Data() + sizeof(info)), info.sampleRate,

  156. static_cast<uint32_t>(totalSampleCount));

  157. }

  158. bool Wav2Opus(FileStream *input, FileStream *output) {

  159. WavInfo in_info = {};

  160. input->SeekBeg();

  161. size_t read = input->Read(&in_info, sizeof(in_info), 1);

  162. if (read != 1) {

  163. return false;

  164. }

  165. uint32_t bitsPerSample = in_info.bitsPerSample;

  166. uint32_t sampleRate = in_info.sampleRate;

  167. uint16_t channels = in_info.channels;

  168. int err = 0;

  169. if (channels > MAX_CHANNELS) {

  170. return false;

  171. }

  172. OpusEncoder *encoder = opus_encoder_create(sampleRate, channels, OPUS_APPLICATION_AUDIO, &err);

  173. if (!encoder || err < 0) {

  174. fprintf(stderr, "failed to create an encoder: %s\n", opus_strerror(err));

  175. if (!encoder) {

  176. opus_encoder_destroy(encoder);

  177. }

  178. return false;

  179. }

  180. const uint16_t *data = (uint16_t *) (input->Data() + sizeof(in_info));

  181. size_t size = (input->Size() - sizeof(in_info)) / 2;

  182. opus_int16 pcm_bytes[FRAME_SIZE * MAX_CHANNELS];

  183. size_t index = 0;

  184. size_t step = static_cast<size_t>(FRAME_SIZE * channels);

  185. FileStream encodedData;

  186. unsigned char cbits[MAX_PACKET_SIZE];

  187. size_t frameCount = 0;

  188. size_t readCount = 0;

  189. while (index < size) {

  190. memset(&pcm_bytes, 0, sizeof(pcm_bytes));

  191. if (index + step <= size) {

  192. memcpy(pcm_bytes, data + index, step * sizeof(uint16_t));

  193. index += step;

  194. } else {

  195. readCount = size - index;

  196. memcpy(pcm_bytes, data + index, (readCount) * sizeof(uint16_t));

  197. index += readCount;

  198. }

  199. int nbBytes = opus_encode(encoder, pcm_bytes, channels * FRAME_SIZE, cbits, MAX_PACKET_SIZE);

  200. if (nbBytes < 0) {

  201. fprintf(stderr, "encode failed: %s\n", opus_strerror(nbBytes));

  202. break;

  203. }

  204. ++frameCount;

  205. encodedData.AppendU32(static_cast<uint32_t>(nbBytes));

  206. encodedData.Append((char *) cbits, static_cast<size_t>(nbBytes));

  207. }

  208. WavInfo info = {};

  209. info.bitsPerSample = bitsPerSample;

  210. info.sampleRate = sampleRate;

  211. info.channels = channels;

  212. output->SeekBeg();

  213. output->Append((char *) &info, sizeof(info));

  214. output->Append(encodedData.Data(), encodedData.Size());

  215. opus_encoder_destroy(encoder);

  216. return true;

  217. }

  218. bool Opus2Wav(FileStream *input, FileStream *output) {

  219. WavInfo info = {};

  220. input->SeekBeg();

  221. size_t read = input->Read(&info, sizeof(info), 1);

  222. if (read != 1) {

  223. return false;

  224. }

  225. int channels = info.channels;

  226. if (channels > MAX_CHANNELS) {

  227. return false;

  228. }

  229. output->SeekBeg();

  230. output->Append((char *) &info, sizeof(info));

  231. int err = 0;

  232. OpusDecoder *decoder = opus_decoder_create(info.sampleRate, channels, &err);

  233. if (!decoder || err < 0) {

  234. fprintf(stderr, "failed to create decoder: %s\n", opus_strerror(err));

  235. if (!decoder) {

  236. opus_decoder_destroy(decoder);

  237. }

  238. return false;

  239. }

  240. unsigned char cbits[MAX_PACKET_SIZE];

  241. opus_int16 out[MAX_FRAME_SIZE * MAX_CHANNELS];

  242. int frameCount = 0;

  243. while (true) {

  244. uint32_t nbBytes;

  245. size_t readed = input->Read(&nbBytes, sizeof(uint32_t), 1);

  246. if (readed == 0) {

  247. break;

  248. }

  249. if (nbBytes > sizeof(cbits)) {

  250. fprintf(stderr, "nbBytes > sizeof(cbits)\n");

  251. break;

  252. }

  253. readed = input->Read(cbits, sizeof(char), nbBytes);

  254. if (readed != nbBytes) {

  255. fprintf(stderr, "readed != nbBytes\n");

  256. break;

  257. }

  258. int frame_size = opus_decode(decoder, cbits, nbBytes, out, MAX_FRAME_SIZE, 0);

  259. if (frame_size < 0) {

  260. fprintf(stderr, "decoder failed: %s\n", opus_strerror(frame_size));

  261. break;

  262. }

  263. ++frameCount;

  264. output->Append((char *) out, channels * frame_size * sizeof(out[0]));

  265. }

  266. opus_decoder_destroy(decoder);

  267. return true;

  268. }

  269. void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) {

  270. const char *end;

  271. const char *p;

  272. const char *s;

  273. if (path[0] && path[1] == ':') {

  274. if (drv) {

  275. *drv++ = *path++;

  276. *drv++ = *path++;

  277. *drv = '\0';

  278. }

  279. } else if (drv)

  280. *drv = '\0';

  281. for (end = path; *end && *end != ':';)

  282. end++;

  283. for (p = end; p > path && *--p != '\\' && *p != '/';)

  284. if (*p == '.') {

  285. end = p;

  286. break;

  287. }

  288. if (ext)

  289. for (s = end; (*ext = *s++);)

  290. ext++;

  291. for (p = end; p > path;)

  292. if (*--p == '\\' || *p == '/') {

  293. p++;

  294. break;

  295. }

  296. if (name) {

  297. for (s = p; s < end;)

  298. *name++ = *s++;

  299. *name = '\0';

  300. }

  301. if (dir) {

  302. for (s = path; s < p;)

  303. *dir++ = *s++;

  304. *dir = '\0';

  305. }

  306. }

  307. void opus2wav(const char *in_file, char *out_file) {

  308. FileStream input;

  309. FileStream output;

  310. input.ReadFromFile(in_file);

  311. Opus2Wav(&input, &output);

  312. stream2wav(&output, out_file);

  313. }

  314. void wav2opus(char *in_file, char *out_file) {

  315. FileStream input;

  316. FileStream output;

  317. wav2stream(in_file, &input);

  318. Wav2Opus(&input, &output);

  319. output.WriteToFile(out_file);

  320. }

  321. int main(int argc, char *argv[]) {

  322. printf("Opus Demo\n");

  323. if (argc < 2)

  324. return -1;

  325. char *in_file = argv[1];

  326. char drive[3];

  327. char dir[256];

  328. char fname[256];

  329. char ext[256];

  330. char out_file[1024];

  331. splitpath(in_file, drive, dir, fname, ext);

  332. if (memcmp(".wav", ext, strlen(ext)) == 0) {

  333. sprintf(out_file, "%s%s%s.out", drive, dir, fname);

  334. wav2opus(in_file, out_file);

  335. } else if (memcmp(".out", ext, strlen(ext)) == 0) {

  336. sprintf(out_file, "%s%s%s_out.wav", drive, dir, fname);

  337. opus2wav(in_file, out_file);

  338. }

  339. printf("done.\n");

  340. printf("press any key to exit.\n");

  341. getchar();

  342. return 0;

  343. }

另附C++封装接口示例:

 
  1. class HandlerOpusImpl

  2. {

  3. public:

  4. //在构造函数中创建解码器

  5. HandlerOpusImpl() : _perSecNum(0)

  6. {

  7. int sampleRate = 48000;

  8. int channels = 1;

  9. int err;

  10. _decoder = opus_decoder_create(sampleRate, channels, &err);

  11. }

  12. //传入要解码的数据srcData, 以string类型返回解码后的数据result

  13. bool DecodeData(std::string srcData, std::string &result)

  14. {

  15. int frameSize;

  16. int channels = CHANNELS;

  17. int sampleRate = SAMPLE_RATE;

  18. opus_int16 *out;

  19. char *pcmData;

  20. out = new opus_int16[SAMPLE_RATE / 50 * CHANNELS];

  21. //解码,如果frameSize小于0,那么说明解码失败

  22. frameSize = opus_decode(_decoder, (const unsigned char *)srcData.data(), srcData.size(), out, SAMPLE_RATE / 50 * CHANNELS, 0);

  23. if (frameSize <= 0)

  24. {

  25. return false;

  26. }

  27. pcmData = new char[frameSize * channels * sizeof(opus_int16)];

  28. ++_perSecNum;

  29. for (int i = 0; i < channels * frameSize; ++i)

  30. {

  31. pcmData[i * 2] = out[i] & 0xFF;

  32. pcmData[i * 2 + 1] = (out[i] >> 8) & 0xFF;

  33. }

  34. //把数据赋值给result

  35. std::string(pcmData, sizeof(opus_int16) * channels * frameSize).swap(result);

  36. delete[]out;

  37. delete[]pcmData;

  38. return true;

  39. }

  40. //使用fec找回丢失的包,函数内容获取解码数据函数内容类似,不过fec标记位需要设置为1

  41. bool HandleLosePack(std::string srcData, std::string &result)

  42. {

  43. int frameSize;

  44. int channels = CHANNELS;

  45. int sampleRate = SAMPLE_RATE;

  46. opus_int16 *out;

  47. char *pcmData;

  48. out = new opus_int16[SAMPLE_RATE / 50 * CHANNELS];

  49. frameSize = opus_decode(_decoder, NULL, 0, out, SAMPLE_RATE / 50 * CHANNELS, 1);

  50. if (frameSize <= 0)

  51. {

  52. return false;

  53. }

  54. pcmData = new char[frameSize * channels * sizeof(opus_int16)];

  55. ++_perSecNum;

  56. for (int i = 0; i < channels * frameSize; ++i)

  57. {

  58. pcmData[i * 2] = out[i] & 0xFF;

  59. pcmData[i * 2 + 1] = (out[i] >> 8) & 0xFF;

  60. }

  61. std::string(pcmData, sizeof(opus_int16) * channels * frameSize).swap(result);

  62. delete[]out;

  63. delete[]pcmData;

  64. return true;

  65. }

  66. void DeleteCodec()

  67. {

  68. opus_decoder_destroy(_decoder);

  69. _decoder = NULL;

  70. }

  71. private:

  72. int _perSecNum;

  73. OpusDecoder *_decoder;

  74. };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值