Opus介绍及编译

it2022-05-05  99

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.gz和opus-1.3.1.tar.gz

下载后可以在ubuntu里解压

然后

./configure

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

然后 make && make install

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

 

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

= Compiling libopus == To build from a distribution tarball, you only need to do the following: % ./configure % make To build from the git repository, the following steps are necessary: 0) Set up a development environment: On an Ubuntu or Debian family Linux distribution: % sudo apt-get install git autoconf automake libtool gcc make On a Fedora/Redhat based Linux: % sudo dnf install git autoconf automake libtool gcc make Or for older Redhat/Centos Linux releases: % sudo yum install git autoconf automake libtool gcc make On Apple macOS, install Xcode and brew.sh, then in the Terminal enter: % 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解码器:

OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( opus_int32 Fs,//采样率,可以设置的大小为8000, 12000, 16000, 24000, 48000. int channels,//声道数,网络中实时音频一般为单声道 int *error//是否创建失败,返回0表示创建成功 );

2.解码:

OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( OpusDecoder *st, //上一步的返回值 const unsigned char *data,//要解码的数据 opus_int32 len, //数据长度 opus_int16 *pcm, //解码后的数据,注意是一个以16位长度为基本单位的数组 int frame_size, //每个声道给pcm数组的长度 int decode_fec //是否需要fec,设置0为不需要,1为需要 ) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4);

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

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

char *pcmData = new char[frameSize * channels * sizeof(opus_int16)]; for (int i = 0; i < channels * frameSize; ++i) { pcmData[i * 2] = pcm[i] & 0xFF; pcmData[i * 2 + 1] = (pcm[i] >> 8) & 0xFF; }

4.释放解码器

OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);

注意事项:

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

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

 

接口应用实例:

#include <opus_types.h> #include <opus.h> #include <cstring> #include <memory> #include <vector> #define DR_WAV_IMPLEMENTATION // https://github.com/mackron/dr_libs/blob/master/dr_wav.h #include "dr_wav.h" //需要dr_wav库 #define FRAME_SIZE 480 #define MAX_FRAME_SIZE (6*FRAME_SIZE) #define MAX_CHANNELS 1 #define MAX_PACKET_SIZE (3*1276) #pragma pack(push) #pragma pack(1) struct WavInfo { uint16_t channels; uint32_t sampleRate; uint32_t bitsPerSample; }; #pragma pack(pop) #ifndef nullptr #define nullptr NULL #endif class FileStream { public: FileStream() { cur_pos = 0; } void Append(const char *data, size_t size) { if (cur_pos + size > Size()) { vec.resize(cur_pos + size); } memcpy(vec.data() + cur_pos, data, size); cur_pos += size; } void AppendU32(uint32_t val) { Append((char *) (&val), sizeof(val)); } char *Data() { return vec.data(); } size_t Size() { return vec.size(); } size_t Read(void *buff, size_t elemSize, size_t elemCount) { size_t readed = std::min((vec.size() - cur_pos), (elemCount * elemSize)) / elemSize; if (readed > 0) { memcpy(buff, vec.data() + cur_pos, readed * elemSize); cur_pos += readed * elemSize; } return readed; } bool SeekCur(int offset) { if (cur_pos + offset > vec.size()) { cur_pos = !vec.empty() ? (vec.size() - 1) : 0; return false; } else { cur_pos += offset; return true; } } bool SeekBeg(int offset = 0) { cur_pos = 0; return SeekCur(offset); } bool WriteToFile(const char *filename) { FILE *fin = fopen(filename, "wb"); if (!fin) { return false; } fseek(fin, 0, SEEK_SET); fwrite(vec.data(), sizeof(char), vec.size(), fin); fclose(fin); return true; } bool ReadFromFile(const char *filename) { FILE *fin = fopen(filename, "rb"); if (!fin) { return false; } fseek(fin, 0, SEEK_END); long fileSize = ftell(fin); vec.resize(static_cast<unsigned long long int>(fileSize)); fseek(fin, 0, SEEK_SET); fread(vec.data(), sizeof(char), vec.size(), fin); fclose(fin); return true; } private: std::vector<char> vec; size_t cur_pos; }; bool Wav2Opus(FileStream *input, FileStream *output); bool Opus2Wav(FileStream *input, FileStream *output); bool wav2stream(char *input, FileStream *output); bool stream2wav(FileStream *input, char *output); bool wavWrite_int16(char *filename, int16_t *buffer, int sampleRate, uint32_t totalSampleCount) { drwav_data_format format = {}; format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. format.channels = 1; format.sampleRate = (drwav_uint32) sampleRate; format.bitsPerSample = 16; drwav *pWav = drwav_open_file_write(filename, &format); if (pWav) { drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer); drwav_uninit(pWav); if (samplesWritten != totalSampleCount) { fprintf(stderr, "ERROR\n"); return false; } return true; } return false; } int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount) { unsigned int channels; int16_t *buffer = drwav_open_and_read_file_s16(filename, &channels, sampleRate, totalSampleCount); if (buffer == nullptr) { fprintf(stderr, "ERROR\n"); return nullptr; } if (channels != 1) { drwav_free(buffer); buffer = nullptr; *sampleRate = 0; *totalSampleCount = 0; } return buffer; } bool wav2stream(char *input, FileStream *output) { uint32_t sampleRate = 0; uint64_t totalSampleCount = 0; int16_t *wavBuffer = wavRead_int16(input, &sampleRate, &totalSampleCount); if (wavBuffer == nullptr) return false; WavInfo info = {}; info.bitsPerSample = 16; info.sampleRate = sampleRate; info.channels = 1; output->SeekBeg(); output->Append((char *) &info, sizeof(info)); output->Append((char *) wavBuffer, totalSampleCount * sizeof(int16_t)); free(wavBuffer); return true; } bool stream2wav(FileStream *input, char *output) { WavInfo info = {}; input->SeekBeg(); size_t read = input->Read(&info, sizeof(info), 1); if (read != 1) { return false; } size_t totalSampleCount = (input->Size() - sizeof(info)) / 2; return wavWrite_int16(output, (int16_t *) (input->Data() + sizeof(info)), info.sampleRate, static_cast<uint32_t>(totalSampleCount)); } bool Wav2Opus(FileStream *input, FileStream *output) { WavInfo in_info = {}; input->SeekBeg(); size_t read = input->Read(&in_info, sizeof(in_info), 1); if (read != 1) { return false; } uint32_t bitsPerSample = in_info.bitsPerSample; uint32_t sampleRate = in_info.sampleRate; uint16_t channels = in_info.channels; int err = 0; if (channels > MAX_CHANNELS) { return false; } OpusEncoder *encoder = opus_encoder_create(sampleRate, channels, OPUS_APPLICATION_AUDIO, &err); if (!encoder || err < 0) { fprintf(stderr, "failed to create an encoder: %s\n", opus_strerror(err)); if (!encoder) { opus_encoder_destroy(encoder); } return false; } const uint16_t *data = (uint16_t *) (input->Data() + sizeof(in_info)); size_t size = (input->Size() - sizeof(in_info)) / 2; opus_int16 pcm_bytes[FRAME_SIZE * MAX_CHANNELS]; size_t index = 0; size_t step = static_cast<size_t>(FRAME_SIZE * channels); FileStream encodedData; unsigned char cbits[MAX_PACKET_SIZE]; size_t frameCount = 0; size_t readCount = 0; while (index < size) { memset(&pcm_bytes, 0, sizeof(pcm_bytes)); if (index + step <= size) { memcpy(pcm_bytes, data + index, step * sizeof(uint16_t)); index += step; } else { readCount = size - index; memcpy(pcm_bytes, data + index, (readCount) * sizeof(uint16_t)); index += readCount; } int nbBytes = opus_encode(encoder, pcm_bytes, channels * FRAME_SIZE, cbits, MAX_PACKET_SIZE); if (nbBytes < 0) { fprintf(stderr, "encode failed: %s\n", opus_strerror(nbBytes)); break; } ++frameCount; encodedData.AppendU32(static_cast<uint32_t>(nbBytes)); encodedData.Append((char *) cbits, static_cast<size_t>(nbBytes)); } WavInfo info = {}; info.bitsPerSample = bitsPerSample; info.sampleRate = sampleRate; info.channels = channels; output->SeekBeg(); output->Append((char *) &info, sizeof(info)); output->Append(encodedData.Data(), encodedData.Size()); opus_encoder_destroy(encoder); return true; } bool Opus2Wav(FileStream *input, FileStream *output) { WavInfo info = {}; input->SeekBeg(); size_t read = input->Read(&info, sizeof(info), 1); if (read != 1) { return false; } int channels = info.channels; if (channels > MAX_CHANNELS) { return false; } output->SeekBeg(); output->Append((char *) &info, sizeof(info)); int err = 0; OpusDecoder *decoder = opus_decoder_create(info.sampleRate, channels, &err); if (!decoder || err < 0) { fprintf(stderr, "failed to create decoder: %s\n", opus_strerror(err)); if (!decoder) { opus_decoder_destroy(decoder); } return false; } unsigned char cbits[MAX_PACKET_SIZE]; opus_int16 out[MAX_FRAME_SIZE * MAX_CHANNELS]; int frameCount = 0; while (true) { uint32_t nbBytes; size_t readed = input->Read(&nbBytes, sizeof(uint32_t), 1); if (readed == 0) { break; } if (nbBytes > sizeof(cbits)) { fprintf(stderr, "nbBytes > sizeof(cbits)\n"); break; } readed = input->Read(cbits, sizeof(char), nbBytes); if (readed != nbBytes) { fprintf(stderr, "readed != nbBytes\n"); break; } int frame_size = opus_decode(decoder, cbits, nbBytes, out, MAX_FRAME_SIZE, 0); if (frame_size < 0) { fprintf(stderr, "decoder failed: %s\n", opus_strerror(frame_size)); break; } ++frameCount; output->Append((char *) out, channels * frame_size * sizeof(out[0])); } opus_decoder_destroy(decoder); return true; } void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) { const char *end; const char *p; const char *s; if (path[0] && path[1] == ':') { if (drv) { *drv++ = *path++; *drv++ = *path++; *drv = '\0'; } } else if (drv) *drv = '\0'; for (end = path; *end && *end != ':';) end++; for (p = end; p > path && *--p != '\\' && *p != '/';) if (*p == '.') { end = p; break; } if (ext) for (s = end; (*ext = *s++);) ext++; for (p = end; p > path;) if (*--p == '\\' || *p == '/') { p++; break; } if (name) { for (s = p; s < end;) *name++ = *s++; *name = '\0'; } if (dir) { for (s = path; s < p;) *dir++ = *s++; *dir = '\0'; } } void opus2wav(const char *in_file, char *out_file) { FileStream input; FileStream output; input.ReadFromFile(in_file); Opus2Wav(&input, &output); stream2wav(&output, out_file); } void wav2opus(char *in_file, char *out_file) { FileStream input; FileStream output; wav2stream(in_file, &input); Wav2Opus(&input, &output); output.WriteToFile(out_file); } int main(int argc, char *argv[]) { printf("Opus Demo\n"); if (argc < 2) return -1; char *in_file = argv[1]; char drive[3]; char dir[256]; char fname[256]; char ext[256]; char out_file[1024]; splitpath(in_file, drive, dir, fname, ext); if (memcmp(".wav", ext, strlen(ext)) == 0) { sprintf(out_file, "%s%s%s.out", drive, dir, fname); wav2opus(in_file, out_file); } else if (memcmp(".out", ext, strlen(ext)) == 0) { sprintf(out_file, "%s%s%s_out.wav", drive, dir, fname); opus2wav(in_file, out_file); } printf("done.\n"); printf("press any key to exit.\n"); getchar(); return 0; }

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

class HandlerOpusImpl { public: //在构造函数中创建解码器 HandlerOpusImpl() : _perSecNum(0) { int sampleRate = 48000; int channels = 1; int err; _decoder = opus_decoder_create(sampleRate, channels, &err); } //传入要解码的数据srcData, 以string类型返回解码后的数据result bool DecodeData(std::string srcData, std::string &result) { int frameSize; int channels = CHANNELS; int sampleRate = SAMPLE_RATE; opus_int16 *out; char *pcmData; out = new opus_int16[SAMPLE_RATE / 50 * CHANNELS]; //解码,如果frameSize小于0,那么说明解码失败 frameSize = opus_decode(_decoder, (const unsigned char *)srcData.data(), srcData.size(), out, SAMPLE_RATE / 50 * CHANNELS, 0); if (frameSize <= 0) { return false; } pcmData = new char[frameSize * channels * sizeof(opus_int16)]; ++_perSecNum; for (int i = 0; i < channels * frameSize; ++i) { pcmData[i * 2] = out[i] & 0xFF; pcmData[i * 2 + 1] = (out[i] >> 8) & 0xFF; } //把数据赋值给result std::string(pcmData, sizeof(opus_int16) * channels * frameSize).swap(result); delete[]out; delete[]pcmData; return true; } //使用fec找回丢失的包,函数内容获取解码数据函数内容类似,不过fec标记位需要设置为1 bool HandleLosePack(std::string srcData, std::string &result) { int frameSize; int channels = CHANNELS; int sampleRate = SAMPLE_RATE; opus_int16 *out; char *pcmData; out = new opus_int16[SAMPLE_RATE / 50 * CHANNELS]; frameSize = opus_decode(_decoder, NULL, 0, out, SAMPLE_RATE / 50 * CHANNELS, 1); if (frameSize <= 0) { return false; } pcmData = new char[frameSize * channels * sizeof(opus_int16)]; ++_perSecNum; for (int i = 0; i < channels * frameSize; ++i) { pcmData[i * 2] = out[i] & 0xFF; pcmData[i * 2 + 1] = (out[i] >> 8) & 0xFF; } std::string(pcmData, sizeof(opus_int16) * channels * frameSize).swap(result); delete[]out; delete[]pcmData; return true; } void DeleteCodec() { opus_decoder_destroy(_decoder); _decoder = NULL; } private: int _perSecNum; OpusDecoder *_decoder; };

 


最新回复(0)