初识 Speex 语音压缩

文章目录

  • 初识 Speex 语音压缩
    • Speex 简介
    • Speex 性能
    • Speex 与 Opus
    • Speex 的 JNI 实现

初识 Speex 语音压缩

Speex 简介

Speex 是一个声音编码格式,目标是用于网络电话、在线广播使用的语音编码,基于 CELP(一种语音编码算法)开发,Speex 宣称可以免费使用,以 BSD 授权条款开放源代码。

Speex 的开发者将这个格式视为 Vorbis(通用音频压缩格式)的补充。

Speex 是一种有损格式,这意味着使用此格式的音频,质量将会永久性地降低以减少文件的大小。

开发 Speex 的 Xiph.org 基金会已经宣布废弃 Speex,建议改用 Opus 取代。

Speex 是针对网际协议通话技术(VoIP)和基于文件的压缩。Speex 的设计目标是开发一个保有高质量语音的同时降低其比特率(bit rate)的编码器。 为了实现前述目标,Speex 编码器使用多位比特率(multiple bit rates),并支持超宽频 (32 kHz 采样率),宽带 (16 kHz 采样率) 和窄带 (电话通话质量,8 kHz 采样率)。 由于 Speex 是设计用于 VoIP 而不是手机,因此 Speex 编码器必须能容忍丢失数据包(lost packets),但不能数据包是损坏的。基于上述的要求,选择 CELP 算法作为 Speex 的编码技术。使用 CELP 的主要原因之一是,CELP 早已证明,它可以同时做到低比特率和高比特率。

Speex 的主要特性归纳如下:

  • 自由软件 / 开源,无专利保护且使用无需版税。
  • 集窄带和宽带在同一比特流(bit-stream)。
  • 比特率可选择的范围很广 (从 2 kbit/s 至 44 kbit/s)。
  • 动态交换的比特率和可变比特率 (VBR, variable bit-rate)。
  • 语音动态的检测 (VAD,与 VBR 集成)(1.2 版开始废弃)。

Speex 性能

目前只测了部分压缩的性能(设备:一加3T【高通骁龙821】)

初识 Speex 语音压缩_第1张图片

Speex 与 Opus

Opus 是一个有损声音编码的格式,由 Xiph.Org 基金会开发,之后由互联网工程任务组进行标准化,目标是希望用单一格式包含声音和语音,取代 Speex 和 Vorbis,且适用于网络上低延迟的即时声音传输,标准格式定义于 RFC 6716 文件。Opus 格式是一个 开放格式,使用上没有任何专利或限制。

Opus 集成了两种声音编码的技术:以语音编码为导向的 SILK 和低延迟的 CELT。Opus 可以无缝调节高低 比特率。在编码器内部它在较低比特率时使用线性预测编码在高比特率时候使用变换编码(在高低比特率交界处也使用两者结合的编码方式)。Opus 具有非常低的算法延迟(默认为 22.5 ms)[3],非常适合用于低延迟语音通话的编码,像是网络上的即时声音流、即时同步声音旁白等等,此外 Opus 也可以透过降低编码比特率,达成更低的算法延迟,最低可以到 5 ms。在多个听觉盲测中,Opus 都比 MP3、AAC、HE-AAC 等常见格式,有更低的延迟和更好的声音压缩率。

初识 Speex 语音压缩_第2张图片

初识 Speex 语音压缩_第3张图片

所以,临时用用可以考虑 Speex。如果要正式上线,还是考虑使用 Opus

Speex 的 JNI 实现

Speex 代码可以从 Speex 官网 下载,目前最新版本为 v1.2.0 (可能以后都不会更新了)

Speex 源码是 C 语音版本的。

自己使用的时候,加了一套 API 方便调用。

// speex_codec.h
#include "speex/speex.h"

namespace speex {
    class SpeexCodec {
    public:
        enum CodecMode {
            ENCODE,         // 编码,压缩
            DECODE,         // 解码,解压
        };

        enum Quality {
            // [1 - 15]
            QUALITY_START,
            QUALITY_1,  QUALITY_2,  QUALITY_3,  QUALITY_4,  QUALITY_5,
            QUALITY_6,  QUALITY_7,  QUALITY_8,  QUALITY_9,  QUALITY_10,
            QUALITY_11, QUALITY_12, QUALITY_13, QUALITY_14, QUALITY_15,
            QUALITY_END,
        };

        enum Complexity {
            // [1 - 8]
            COMPLEXITY_START,
            COMPLEXITY_1, COMPLEXITY_2, COMPLEXITY_3, COMPLEXITY_4,
            COMPLEXITY_5, COMPLEXITY_6, COMPLEXITY_7, COMPLEXITY_8,
            COMPLEXITY_END,
        };

        enum ModeId {
            MODEID_NB  = SPEEX_MODEID_NB,   // narrowband 窄宽带模式
            MODEID_WB  = SPEEX_MODEID_WB,   // wideband 宽带模式
            MODEID_UWB = SPEEX_MODEID_UWB,  // wideband 超宽带模式
        };

        SpeexCodec(CodecMode  codecMode,
                   int        decSize,
                   int        encSize,
                   Quality    quality,
                   Complexity complexity,
                   ModeId     modeId);
        ~SpeexCodec();

        int encode(short * dec, int decSize, int * useDecSize, char * enc);
        int decode(char * enc, int encSize, int * useEncSize, short * dec);
        void reset();

        CodecMode  getCodecMode();
        Quality    getQuality();
        Complexity getComplexity();
        ModeId     getModeId();
        int        getDecSize();
        int        getEncSize();

    private:
        SpeexCodec(){}
        SpeexCodec(const SpeexCodec&){}
        SpeexCodec(const SpeexCodec&&){}
        SpeexCodec& operator = (const SpeexCodec&){ return *this; }

        CodecMode  mCodecMode;
        Quality    mQuality;
        Complexity mComplexity;
        ModeId     mModeId;
        int        mDecSize;
        int        mEncSize;
        void *     mState;
        SpeexBits  mBits;
    };
}
// speex_codec.cpp
#include "speex_codec.h"

namespace speex {
    SpeexCodec::SpeexCodec(CodecMode  codecMode,
                           int        decSize,
                           int        encSize,
                           Quality    quality,
                           Complexity complexity,
                           ModeId     modeId) {
        this->mCodecMode  = codecMode;
        this->mDecSize    = decSize;
        this->mEncSize    = encSize;
        this->mQuality    = quality;
        this->mComplexity = complexity;
        this->mModeId     = modeId;

        if ((this->mQuality <= QUALITY_START || this->mQuality >= QUALITY_END)
            || (this->mDecSize <= 0)) {
            ASSERT_EXIT();
        }

        switch (this->mCodecMode) {
            case ENCODE:
                if (this->mComplexity <= COMPLEXITY_START || this->mComplexity >= COMPLEXITY_END) {
                    ASSERT_EXIT();
                }
                this->mState = speex_encoder_init(speex_lib_get_mode(this->mModeId));
                speex_encoder_ctl(this->mState, SPEEX_SET_QUALITY, &this->mQuality);
                speex_encoder_ctl(this->mState, SPEEX_SET_COMPLEXITY, &this->mComplexity);
                break;
            case DECODE:
                this->mState = speex_decoder_init(speex_lib_get_mode(this->mModeId));
                break;
            default:
                ASSERT_EXIT();
        }

        speex_bits_init(&this->mBits);
    }

    SpeexCodec::~SpeexCodec() {
        speex_bits_destroy(&this->mBits);
        switch (this->mCodecMode) {
            case ENCODE:
                speex_encoder_destroy(this->mState);
                break;
            case DECODE:
                speex_decoder_destroy(this->mState);
                break;
            default:
                ASSERT_EXIT();
        }
    }

    int SpeexCodec::encode(short * dec, int decSize, int * useDecSize, char * enc) {
        if (useDecSize != nullptr) {
            *useDecSize = 0;
        }
        if (dec == nullptr || enc == nullptr || decSize <= 0 || this->mDecSize <= 0
            || this->mCodecMode != ENCODE) {
            return -1;
        }
        int encLen = 0;
        {
            int n = decSize / this->mDecSize;
            if (n > 0) {
                int max_nbytes = (this->mEncSize > 0) ? this->mEncSize : this->mDecSize;
                for (int i = 0; i < n; ++ i) {
                    speex_bits_reset(&this->mBits);
                    speex_encode_int(this->mState, dec + i * this->mDecSize, &this->mBits);
                    encLen += speex_bits_write(&this->mBits, enc + encLen, max_nbytes);
                }
            }

            if (useDecSize != nullptr) {
                *useDecSize = n * this->mDecSize;
            }
        }
        return encLen;
    }

    int SpeexCodec::decode(char * enc, int encSize, int * useEncSize, short * dec) {
        if (useEncSize != nullptr) {
            *useEncSize = 0;
        }
        if (enc == nullptr || dec == nullptr || encSize <= 0 || this->mEncSize <= 0
            || this->mDecSize <= 0 || this->mCodecMode != DECODE) {
            return -1;
        }
        int decLen = 0;
        {
            int n = encSize / this->mEncSize;
            if (n > 0) {
                for (int i = 0; i < n; ++ i) {
                    speex_bits_reset(&this->mBits);
                    speex_bits_read_from(&this->mBits, enc + i * this->mEncSize, this->mEncSize);
                    speex_decode_int(this->mState, &this->mBits, dec + decLen);
                    decLen += this->mDecSize;
                }
            }

            if (useEncSize != nullptr) {
                *useEncSize = n * this->mEncSize;
            }
        }
        return decLen;
    }

    void SpeexCodec::reset() {
        int eph = 0;
        switch (this->mCodecMode) {
            case ENCODE:
                speex_encoder_ctl(this->mState, SPEEX_RESET_STATE, nullptr);
                break;
            case DECODE:
                speex_decoder_ctl(this->mState, SPEEX_RESET_STATE, nullptr);
                speex_decoder_ctl(this->mState, SPEEX_SET_ENH, &eph);
                break;
            default:
                ASSERT_EXIT();
        }
    }

    SpeexCodec::CodecMode SpeexCodec::getCodecMode() {
        return this->mCodecMode;
    }

    SpeexCodec::Quality SpeexCodec::getQuality() {
        return this->mQuality;
    }

    SpeexCodec::Complexity SpeexCodec::getComplexity() {
        return this->mComplexity;
    }

    SpeexCodec::ModeId SpeexCodec::getModeId() {
        return this->mModeId;
    }

    int SpeexCodec::getDecSize() {
        return this->mDecSize;
    }

    int SpeexCodec::getEncSize() {
        return this->mEncSize;
    }
}
#include <jni.h>
#include "speex_codec.h"

static speex::SpeexCodec * gmpSpeexCodecEncode = nullptr;
static speex::SpeexCodec * gmpSpeexCodecDecode = nullptr;
static bool gmSpeexCodecOpen = false;

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniOpen
  (JNIEnv *env, jclass , jint quality, jint complexity, jint modeId, jint decSize, jint encSize) {
    if (gmSpeexCodecOpen) {
        return (jint)-1;
    }

    gmSpeexCodecOpen = true;

    gmpSpeexCodecEncode = new speex::SpeexCodec(speex::SpeexCodec::ENCODE, decSize, encSize,
                                               (speex::SpeexCodec::Quality) quality,
                                               (speex::SpeexCodec::Complexity) complexity,
                                               (speex::SpeexCodec::ModeId) modeId);

    gmpSpeexCodecDecode = new speex::SpeexCodec(speex::SpeexCodec::DECODE, decSize, encSize,
                                               (speex::SpeexCodec::Quality) quality,
                                               (speex::SpeexCodec::Complexity) complexity,
                                               (speex::SpeexCodec::ModeId) modeId);

    if (gmpSpeexCodecEncode == nullptr || gmpSpeexCodecDecode == nullptr) {
        if (gmpSpeexCodecEncode != nullptr) {
            delete gmpSpeexCodecEncode;
            gmpSpeexCodecEncode = nullptr;
        }
        if (gmpSpeexCodecDecode != nullptr) {
            delete gmpSpeexCodecDecode;
            gmpSpeexCodecDecode = nullptr;
        }
        return -1;
    }

    return (jint)0;
}

extern "C"
JNIEXPORT jlong Java_org_zone_speex_Speex_JniEncode
    (JNIEnv *env, jclass, jshortArray jdec, jint jdecSize, jbyteArray jenc) {
    if (!gmSpeexCodecOpen || gmpSpeexCodecEncode == nullptr) {
        return -1;
    }
    jshort * dec = env->GetShortArrayElements(jdec, 0);
    jbyte * enc = env->GetByteArrayElements(jenc, 0);

    int useDecSize = 0, useEncSize = 0;
    useEncSize = gmpSpeexCodecEncode->encode((short *)dec, jdecSize, &useDecSize, (char *)enc);

    env->ReleaseShortArrayElements(jdec, dec, 0);
    env->ReleaseByteArrayElements(jenc, enc, 0);
    return (jlong)((((long)useDecSize) << 32L) | ((long)useEncSize & 0xFFFFFFFFL));
}


extern "C"
JNIEXPORT jlong Java_org_zone_speex_Speex_JniDecode
        (JNIEnv *env, jclass, jbyteArray jenc, jint jencSize, jshortArray jdec) {
    if (!gmSpeexCodecOpen || gmpSpeexCodecEncode == nullptr) {
        return -1;
    }
    jshort * dec = env->GetShortArrayElements(jdec, 0);
    jbyte * enc = env->GetByteArrayElements(jenc, 0);

    int useDecSize = 0, useEncSize = 0;
    useDecSize = gmpSpeexCodecDecode->decode((char *)enc, jencSize, &useEncSize, (short *)dec);

    env->ReleaseShortArrayElements(jdec, dec, 0);
    env->ReleaseByteArrayElements(jenc, enc, 0);
    return (jlong)((((long)useEncSize) << 32L) | ((long)useDecSize & 0xFFFFFFFFL));
}


extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniClose
    (JNIEnv *env, jclass) {
    if (! gmSpeexCodecOpen) {
        LOGE("SpeexCodec not open !");
        return -1;
    }
    if (gmpSpeexCodecEncode != nullptr) {
        delete gmpSpeexCodecEncode;
        gmpSpeexCodecEncode = nullptr;
    }
    if (gmpSpeexCodecDecode == nullptr) {
        delete gmpSpeexCodecDecode;
        gmpSpeexCodecDecode = nullptr;
    }
    gmSpeexCodecOpen = false;
    return (jint)0;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniReset
        (JNIEnv *env, jclass) {
    if (! gmSpeexCodecOpen) {
        LOGE("SpeexCodec not open !");
        return -1;
    }
    if (gmpSpeexCodecEncode != nullptr) {
        gmpSpeexCodecEncode->reset();
    }
    if (gmpSpeexCodecDecode == nullptr) {
        gmpSpeexCodecDecode->reset();
    }
    return (jint)0;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniGetDecSize
        (JNIEnv *env, jclass) {
    int ret = -1;
    if (!gmSpeexCodecOpen || gmpSpeexCodecEncode == nullptr) {
        ;
    } else {
        ret = gmpSpeexCodecEncode->getDecSize();
    }
    return ret;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniGetEncSize
        (JNIEnv *env, jclass) {
    int ret = -1;
    if (! gmSpeexCodecOpen || gmpSpeexCodecEncode == nullptr) {
        ;
    } else {
        ret = gmpSpeexCodecEncode->getEncSize();
    }
    return ret;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniGetQuality
        (JNIEnv *env, jclass) {
    int ret = -1;
    if (! gmSpeexCodecOpen) {
        LOGE("SpeexCodec not open !");
    } else if (gmpSpeexCodecEncode == nullptr) {
        LOGE("SpeexCodecEncode invalid !");
    } else {
        ret = gmpSpeexCodecEncode->getQuality();
    }
    return ret;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniGetComplexity
        (JNIEnv *env, jclass) {
    int ret = -1;
    if (! gmSpeexCodecOpen) {
        LOGE("SpeexCodec not open !");
    } else if (gmpSpeexCodecEncode == nullptr) {
        LOGE("SpeexCodecEncode invalid !");
    } else {
        ret = gmpSpeexCodecEncode->getComplexity();
    }
    return ret;
}

extern "C"
JNIEXPORT jint JNICALL Java_org_zone_speex_Speex_JniGetModeId
        (JNIEnv *env, jclass) {
    int ret = -1;
    if (! gmSpeexCodecOpen) {
        LOGE("SpeexCodec not open !");
    } else if (gmpSpeexCodecEncode == nullptr) {
        LOGE("SpeexCodecEncode invalid !");
    } else {
        ret = gmpSpeexCodecEncode->getModeId();
    }
    return ret;
}

你可能感兴趣的:(Speech,Speex,语音)