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

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介绍及编译_第1张图片


之前笔者犯了一个错误,就是直接用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::vector 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(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;
};

 

你可能感兴趣的:(Audio,Opus)