天天看点

speex库处理音频编解码in即时通讯开发

最近在做一个PC端即时通讯软件,类似RTX,在音频数据处理的过程中用到了Speex库。

 最近需要做一个基于udp的实时语音聊天的应用,语音流的压缩方面,我选择了使用speex。

       Speex是一套主要针对语音的开源免费,无专利保护的音频压缩格式。Speex工程着力于通过提供一个可以替代高性能语音编解码来降低语音应用输入门槛 。另外,相对于其它编解码器,Speex也很适合网络应用,在网络应用上有着自己独特的优势。同时,Speex还是GNU工程的一部分,在改版的BSD协议中得到了很好的支持。        上面这些都是度娘说的。        然后,看了一下speex手册和speex的api文档,写了一个简单的例程。        还是先介绍一下speex的简单使用吧~~ 一、speex api的简单介绍 1. 编码:        a) 定义一个SpeexBits类型变量ebits和一个Speex编码器状态变量enc_state。        b) 调用speex_bits_init(&ebits)初始化。        c) 调用speex_encoder_init(&speex_nb_mode)来初始化enc_state。其中speex_nb_mode是SpeexMode类型的变量,表示的是窄带模式。还有speex_wb_mode表示宽带模式、speex_uwb_mode表示超宽带模式。        d) 调用函数int speex_encoder_ ctl(void *state, int request, void *ptr)来设定编码器的参数,其中参数state表示编码器的状态;参数request表示要定义的参数类型,如SPEEX_ GET_ FRAME_SIZE表示设置帧大小,SPEEX_ SET_QUALITY表示量化大小,这决定了编码的质量;参数ptr表示要设定的值。        e) 初始化完毕后,对每一帧声音作如下处理:调用函数speex_bits_reset(&ebits)再次设定SpeexBits,然后调用函数speex_encode_int(enc_state, input_frame, &ebits),参数ebits中保存编码后的数据流。        f) 编码结束后,调用函数speex_bits_destroy (&ebits),speex_encoder_destroy (enc_state)来销毁编码器 2. 解码        接口与编码类似,这里就不多说了~~

二、配置安装        在使用speex之前,首先当然要配置一下speex的环境,到官网下载  speex源码,我使用的 是1.2rc1版本。        tar zxvf speex-1.2rc1.tar.gz        cd speex-1.2rc1        ./configure --prefix=/home/yzf/lib/speex   (路径改成自己喜欢的)        make && make install        编译安装后,把/home/yzf/lib/speex/include 下的文件拷贝到 /usr/include下          把/home/yzf/lib/speex/lib/libspeex.so.1.5.0 拷贝到 /usr/lib下        并重命名为libspeex.so        并建立该文件的软链接 libspeex.so.1  :  ln -s libspeex.so libspeex.so.1        因为有些系统-lspeex使用的是 libspeex.so.1,比如我用的一个服务器的redhat

三、例程: 下面是我写的一个例程,我用“伪单例模式”封装了一下speex的接口,方便自己使用~~ voice.h

#ifndef VOICE_H
#define VOICE_H

/*
 * 初始化和销毁
 */
void voice_encode_init();
void voice_encode_release();
void voice_decode_init();
void voice_decode_release();
/*
 * 压缩编码
 * short lin[] 语音数据
 * int size 语音数据长度
 * char encoded[] 编码后保存数据的数组
 * int max_buffer_size 保存编码数据数组的最大长度
 */
int voice_encode(short in[], int size, 
        char encoded[], int max_buffer_size);
/*
 * 解码
 * char encoded[] 编码后的语音数据
 * int size 编码后的语音数据的长度
 * short output[] 解码后的语音数据
 * int max_buffer_size 保存解码后的数据的数组的最大长度
 */
int voice_decode(char encoded[], int size, 
        short output[], int max_buffer_size); 
#endif //define VOICE_H
      

voice.cpp

#include <speex/speex.h>
#include <cstring>
#include <cstdio>
#include "voice.h"

static int enc_frame_size;//压缩时的帧大小
static int dec_frame_size;//解压时的帧大小

static void *enc_state;
static SpeexBits ebits;
static bool is_enc_init = false;

static void *dec_state;
static SpeexBits dbits;
static bool is_dec_init = false;
//初始话压缩器
void voice_encode_init() {
    printf("enc init\n");
    int quality = 8;
    speex_bits_init(&ebits);
    enc_state = speex_encoder_init(&speex_nb_mode);
    speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &quality);
    speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &enc_frame_size);
    is_enc_init = true;
}
//销毁压缩器
void voice_encode_release() {
    printf("enc release\n");
    speex_bits_destroy(&ebits);
    speex_encoder_destroy(enc_state);
    is_enc_init = false;
}
//初始化解压器
void voice_decode_init() {
    printf("dec init\n");
    int enh = 1;
    speex_bits_init(&dbits);
    dec_state = speex_decoder_init(&speex_nb_mode);
    speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &dec_frame_size);
    speex_decoder_ctl(dec_state, SPEEX_SET_ENH, &enh);
    is_dec_init = true;
}
//销毁解压器
void voice_decode_release() {
    printf("dec release\n");
    speex_bits_destroy(&dbits);
    speex_decoder_destroy(dec_state);
    is_dec_init = false;
}
//压缩语音流
int voice_encode(short in[], int size, 
        char encoded[], int max_buffer_size) {

    if (! is_enc_init) {
        voice_encode_init();
    }

    short buffer[enc_frame_size];
    char output_buffer[1024 + 4];
    int nsamples = (size - 1) / enc_frame_size + 1;
    int tot_bytes = 0;
    for (int i = 0; i < nsamples; ++ i) {
        speex_bits_reset(&ebits);
        memcpy(buffer, in + i * enc_frame_size, 
                    enc_frame_size * sizeof(short));

        speex_encode_int(enc_state, buffer, &ebits);
        int nbBytes = speex_bits_write(&ebits, output_buffer + 4,
                                1024 - tot_bytes);
        memcpy(output_buffer, &nbBytes, 4);

        int len = 
                max_buffer_size >= tot_bytes + nbBytes + 4 ? 
                    nbBytes + 4 : max_buffer_size - tot_bytes;

        memcpy(encoded + tot_bytes, output_buffer, len * sizeof(char));
        
        tot_bytes += nbBytes + 4;
    }
    return tot_bytes;
}
//解压语音流
int voice_decode(char encoded[], int size, 
        short output[], int max_buffer_size) {

    if (! is_dec_init) {
        voice_decode_init();
    }

    char* buffer = encoded;
    short output_buffer[1024];
    int encoded_length = size;
    int decoded_length = 0;
    int i;

    for (i = 0; decoded_length < encoded_length; ++ i) {
        speex_bits_reset(&dbits);
        int nbBytes = *(int*)(buffer + decoded_length);
        speex_bits_read_from(&dbits, (char *)buffer + decoded_length + 4, 
                nbBytes);
        speex_decode_int(dec_state, &dbits, output_buffer);
        
        decoded_length += nbBytes + 4;
        int len = (max_buffer_size >= dec_frame_size * (i + 1)) ? 
                        dec_frame_size : max_buffer_size - dec_frame_size * i;
        memcpy(output + dec_frame_size * i, output_buffer, len * sizeof(short));
    }
    return dec_frame_size * i;
}


      

main.cpp 主程序

#include "voice.h"
#include <cstdio>

#define FRAME_SIZE 160
#define HEAD_SIZE 44

int main(int argc, char **argv) {

    char head[HEAD_SIZE];
    short in[FRAME_SIZE];
    char encoded[FRAME_SIZE * 2];
    short decoded[FRAME_SIZE];

    size_t read_count;
    size_t encoded_count;
    size_t decoded_count;

    FILE *fp = fopen("female.wav", "r");
    FILE *fp2 = fopen("encoded", "w");
    FILE *fp3 = fopen("decoded.wav", "w");
    //把wav的头信息写到解压后的文件中去,压缩和解压都是对纯语音数据进行操作的
    fread(head, sizeof(char), HEAD_SIZE, fp);
    fwrite(head, sizeof(char), HEAD_SIZE, fp3);

    voice_encode_init();
    voice_decode_init();
    while (true) {
        read_count = fread(in, sizeof(short), FRAME_SIZE, fp);
        if (feof(fp)) {
            break;
        }
        encoded_count = voice_encode(in, read_count, encoded, FRAME_SIZE * 2);
        decoded_count = voice_decode(encoded, encoded_count, decoded, FRAME_SIZE);
        fwrite(encoded, sizeof(char), encoded_count, fp2);
        fwrite(decoded, sizeof(short), decoded_count, fp3);
    }
    voice_encode_release();
    voice_decode_release();

    fclose(fp);
    fclose(fp2);
    fclose(fp3);

    return 0;
}
      

编译运行: g++ main.cpp voice.cpp -lspeex -lm -Wall -o speex ./speex 运行后当前目录生成了encoded(压缩后的数据)和decoded.wav文件,另外 female.wav是在speex官网下载的一个语音文件。decoded.wav播放起来,有很多的杂音。压缩效果的话,按我所设置的参数,是将160个short(320个字节)压缩成38个字节,因为除了加密数据外,解压时还需要用到每块加密数据的字节数,在这里是38(int),所以总共占用42个字节,感觉压缩效果并不是很好,有很多杂音。等待高人解决.........

继续阅读