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相关的源码资料
在downloads里面可以看到全部的源码下载
这里我们需要下载
opus-tools-0.2.1.tar.gz和opus-1.3.1.tar.gz
(如果是其余平台如Mips或Arm,需要添加 --host=(交叉编译链),在ARM和mips平台推荐使用--enable-fixed-point命令关闭浮点运算)
之前笔者犯了一个错误,就是直接用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;
-
};