您的当前位置:首页>关注 > 正文

【天天时快讯】CSDN博客_opus源码|音频编码之Opus(一)

来源:CSDN 时间:2023-03-01 14:00:38

Opus介绍及编译_grandgrandpa的博客-CSDN博客_opus源码


(相关资料图)

音频编码之opus(一)

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相关的源码资料

Opus Codec

在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

#include

#include

#include

#include

#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(fileSize));

fseek(fin, 0, SEEK_SET);

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

fclose(fin);

return true;

}

private:

std::vectorvec;

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(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(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(nbBytes));

encodedData.Append((char *) cbits, static_cast(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;

};

标签:

最新新闻:

新闻放送
Top