speex 编码简介

Speex 技术介绍

1、Speex 介绍

        speex是近年来开发出的一套功能强大的语音引擎,能够实现高质量和低比特率的编码。它不仅提供了基于码激励线性预测(CELP)算法的编/解码模块,而且在其最新发布的版本中还提供了声音预处理和声学回声消除模块,为保障IP网络中的语音通信质量提供了技术手段。此外,Speex还具有压缩后的比特率低(2.15~44.2kbps)的特点,并支持多种比特率。这些特点使得Speex特别适合VoIP, 音视频系统

宽带和超宽带下的比特率

2、Speex特点

   (1)开源,纯C开发,跨平台,兼容ARM,编译简单

   (2)占用比特率小,提供消除回声功能

   (3)API使用简单

     (4)  码率支持 : 2.15~44.2kbps

   (5)采样率支持:8khz 、16khz、32khz

3、 Speex 和其它语音编码的对比

编码方式

打包时间

带宽

G711

20ms

90.4 Kbit/s

G729

20ms

34.4 Kbit/s

G723

30ms

22.9 Kbit/s

speex

20ms

19.6 Kbit/s

从对比来看,speex占用的带宽是最小的,比较适合在在窄带宽下使用

4、 语音带宽的计算公式:

网络包数据  +  数据包数据  )  /  采样时间

网络包数据 :  58 bytes  由以下五个部分组成

  1. 网络 CRC: 4 bytes
           (2)mac 地址: 14 bytes
           (3)IP 头: 20 bytes
           (4)UDP 头: 8 bytes
           (5)RTP 头: 12 bytes

数据包数据: 20bytes 

采样时间:20ms

Speex 传输率

如果每个rtp包只传输1个数据包:

(58 +20)*8 /20*1000 = 31.2kb/s

如果每个rtp包只传输2个数据包:

(58 +40)*8 /40*1000 = 19.6 kb/s

如果每个rtp包只传输3个数据包:

(58 +60)*8 /60*1000 = 15.7 kb/s

如果每个rtp包只传输3个数据包:

(58 +80)*8 /80*1000 = 13.8 kb/s

码率计算公式:

 码率 = 采样率 * 位声 * 声道数 * 压缩率

 压缩率 = encoded audio size / pcm size

5、Speex  RTP 中的传输

5.1、RTP Speex 头部

        Payload Type (PT): 本格式的负责类型号。

        Marker (M) bit: 此位被用来标志一段无声后有声的开始。打在有声数据的第一个包上。 Speex支持声音检测,可以在无声时不产生帧数据。所以包可能是非连续传输的。

       Extension (X) bit: 见RTP的规定。

       Timestamp: 一个32位的整数,表示一个包中第一帧的采样时间。

5.2、Speex的RTP负载格式

    Speex的RTP负载如图1所示。本格式没有附加的头部,所以只什用标准的RTP头部  头部之后是一个或多个负载数据库(speex帧)。包尾部可能需要一些填补数据。

5.3、 Speex 负载

为了把编码后的数据打包进RTP,我们只需要考虑Speex编码器输出的比特流必须以相同的顺序出现在解码端。此处所说的负载格式保持了这个顺序。

一个典型的Speex帧,最大编码码率大约是110个字节。一个包中所有的Speex帧的总字节数应小用路径MTU以避免被分割。Speex帧绝不能被分割!

必须按时间序把帧打到包里。

一个RTP包中可能包含相同码率的帧也可能包含不同码率的帧。然而码率是在带内传送的,每帧中包含了自己的码率,所以打包时不必在意它。

编码和解码算法可以以20毫秒的帧为边界改变码率。 码率改变的通知是在带内传送的。每个帧都包含采样率(窄带, 宽带, 超宽带)"模式"(码率)信息。所以不需要带外数据通知解码器处理那些变化。

采样率必须是8000 Hz, 16000 Hz, 32000 Hz之一。

RTP负载必须被填补数据以保证能提供整数个字节的数据,这些填补位是LSB(最低有效位)-对齐的并且按网络字节序放置,它是由一个0跟着一群1组成的。填补数据仅被包中最后一个帧所需要。并且仅仅为了保证一个包的内容按字节边界结束。

5.4、 Speex RTP包的例子

下面的示例中,我们的包中一个Speex帧,还有5位的填补数据来保证包的大小是字节对齐的。

5.5、多Speex帧的RTP包

下面的例子演示的是一个RTP包中有两个Speex帧。这个例子中的Speex帧的长度是字节对齐的,所以不需要填补数据。

Speex解码器可以从负载侦测码率,并负责在各帧之间检查20毫秒的的帧界限。

5.6、 媒体类型

媒体类型名字: audio

媒体字类型名字: speex

所需参数:

rate: RTP 时间戳时钟频率,等于采样率Hz。采样率必须是8000, 16000, 或 32000。

可选的参数:

ptime: 必须能被20毫秒整除 [RFC4566]

maxptime: 必须能被20毫秒整除 [RFC4566]

vbr: 可变码率 - 可为 'on', 'off', 或 'vad' (默认是'off')。如果是'on',可变码率被使用。如果是 'off',则不被使用。如果是'vad',那么固定码率被使用,但是无声时段将被编码为特殊的短帧来表明那段时间没有声音。这个参数

用于编码器。

cng: 产生舒适噪音 - 可为 'on' 或 'off' (默认是'off')。如果为 'off',无声帧就是无声,如果为'on',那么这些帧将被以舒适噪音填充。此参数被用于编码器。

mode: 以逗号分隔的多个speex支持的解码模式,按优先度排列。第一个具有最优先级,剩余的依次排列。对于窄带和宽带可用的模式值不一样,见以下定义:

* {1,2,3,4,5,6,7,8,any} 用于窄带

* {0,1,2,3,4,5,6,7,8,9,10,any} 用于宽带

mode'参数可能包含多个值。这此情况下,远端的编码器必须被配置成能支持模式列表中的第一个值。 当 'any' 被使用时,表明自己支持所有的解码模式。'mode' 参数必须永远有值。如果'mode' 没有出现,那么mode的值被置为:在

窄带下是'mode="3,any"',在宽带和超宽带下是'mode="8,any"' 。注意每个包含mode(或码率)的Speex帧必须被解码。因此,一个应用程序必须能解码任何Speex帧,除非在SDP中明确指明某些模式不被支持(例如,不含'mode="any"')。

解码端指定支持哪些模式意味着编码端也支持那些模式。

6 、speex 编码和解码

6.1 、speex 编码流程

1、定义一个SpeexBits类型变量bits和一个Speex编码器状态变量enc_state

2 调用speex_bits_init(&bits)初始化bits

3 调用speex_encoder_init(&speex_nb_mode)来初始化enc_state。其中speex_nb_modeSpeexMode类型的变量,表示的是窄带模式。还有speex_wb_mode表示宽带模式、speex_uwb_mode表示超宽带模式。

4  调用函数int speex_encoder_ ctl(void *state, int request, void *ptr)来设定编码器的参数,其中参数state表示编码器的状态;参数request表示要定义的参数类型,如SPEEX_ GET_ FRAME_SIZE表示设置帧大小,SPEEX_ SET_QUALITY表示量化大小,这决定了编码的质量;参数ptr表示要设定的值。

   可通过speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &frame_size) speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &quality)来设定编码器的参数。

5 初始化完毕后,对每一帧声音作如下处理:调用函数speex_bits_reset(&bits)再次设定SpeexBits,然后调用函数speex_encode(enc_state, input_frame, &bits),参数bits中保存编码后的数据流。

6 编码结束后,调用函数speex_bits_destroy (&bits)speex_encoder_destroy (enc_state)

编码demo 代码

#include <speex/speex.h>
#include <stdio.h>
#include <string.h>

/* 定义帧大小,对于窄带模式,帧大小通常是160 */
#define FRAME_SIZE 160

int main(int argc, char **argv) {
    char *inFile;
    FILE *fin;
    short in[FRAME_SIZE];
    float input[FRAME_SIZE];
    char cbits[200];
    int nbBytes;

    /* 初始化编码器状态 */
    void *state = speex_encoder_init(&speex_nb_mode);

    /* 设置编码质量为8(15 kbps) */
    int quality = 8;
    speex_encoder_ctl(state, SPEEX_SET_QUALITY, &quality);

    /* 检查命令行参数 */
    if (argc != 2) {
        printf("Usage: %s <input pcm file>\n", argv[0]);
        return 1;
    }

    inFile = argv[1];
    fin = fopen(inFile, "rb");
    if (!fin) {
        printf("Error opening input file: %s\n", inFile);
        speex_encoder_destroy(state);
        return 1;
    }

    /* 打开输出文件,用于存储编码后的Speex数据 */
    FILE *fout = fopen("encoded_speex.spx", "wb");
    if (!fout) {
        printf("Error opening output file\n");
        fclose(fin);
        speex_encoder_destroy(state);
        return 1;
    }

    /* 初始化位处理结构 */
    SpeexBits bits;
    speex_bits_init(&bits);

    /* 循环读取、编码并写入数据 */
    while (1) {
        /* 读取一个16位/sample的音频帧 */
        size_t bytesRead = fread(in, sizeof(short), FRAME_SIZE, fin);
        if (bytesRead != FRAME_SIZE) {
            if (feof(fin)) {
                break; // 文件结束
            } else {
                printf("Error reading input file\n");
                speex_bits_destroy(&bits);
                fclose(fout);
                fclose(fin);
                speex_encoder_destroy(state);
                return 1;
            }
        }

        /* 将16位值复制到浮点数,以便Speex处理 */
        for (int i = 0; i < FRAME_SIZE; i++) {
            input[i] = in[i];
        }

        /* 重置位处理结构,以便编码新帧 */
        speex_bits_reset(&bits);

        /* 编码帧 */
        speex_encode(state, input, &bits);

        /* 将位复制到字符数组中,以便写入 */
        nbBytes = speex_bits_write(&bits, cbits, 200);

        /* 写入帧大小 */
        fwrite(&nbBytes, sizeof(int), 1, fout);
        /* 写入压缩数据 */
        fwrite(cbits, 1, nbBytes, fout);
    }

    /* 清理资源 */
    speex_bits_destroy(&bits);
    fclose(fout);
    fclose(fin);
    speex_encoder_destroy(state);

    return 0;
}

6.2 、speex 解码流程

对已经编码过的音频数据进行解码要经过以下步骤:

1、定义一个SpeexBits类型变量bits和一个Speex编码状态变量enc_state。

2、调用speex_bits_init(&bits)初始化bits。

3、调用speex_decoder_init (&speex_nb_mode)来初始化enc_state。

4、调用函数speex_decoder_ctl (void *state, int request, void *ptr)来设定编码器的参数。

5、调用函数 speex_decode(void *state, SpeexBits *bits, float *out)对参数bits中的音频数             据进行解编码,参数out中保存解码后的数据流。

6、调用函数speex_bits_destroy(&bits), speex_ decoder_ destroy (void *state)来关闭和销毁              SpeexBits和解码器。

解码demo 代码

#include <speex/speex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FRAME_SIZE 160 // 对于窄带模式,帧大小通常是160

int main() {
    // 定义SpeexBits和编码状态变量
    SpeexBits bits;
    void *enc_state;

    // 初始化bits
    speex_bits_init(&bits);

    // 初始化编码状态,这里使用窄带模式
    enc_state = speex_decoder_init(&speex_nb_mode);

    // 设置编码器参数,例如质量参数
    int quality = 8; // 质量范围通常是0到10
    speex_decoder_ctl(enc_state, SPEEX_SET_QUALITY, &quality);

    // 打开输入文件,这里假设输入文件是Speex编码的
    FILE *input = fopen("encoded_speex.data", "rb");
    if (!input) {
        printf("Error opening input file\n");
        return 1;
    }

    // 打开输出文件,用于存储解码后的PCM数据
    FILE *output = fopen("decoded_pcm.pcm", "wb");
    if (!output) {
        printf("Error opening output file\n");
        fclose(input);
        return 1;
    }

    // 分配输出缓冲区
    short output_buffer[FRAME_SIZE];

    // 循环读取、解码并写入文件
    while (1) {
        // 读取一帧Speex编码的数据
        int bytes_read = fread(&bits.bytes, 1, sizeof(bits.bytes), input);
        if (bytes_read == 0) {
            // 如果读取的字节数为0,表示文件结束
            break;
        }
        bits.nbytes = bytes_read;
        bits.nbBits = bytes_read * 8;

        // 解码
        speex_decode_int(enc_state, &bits, output_buffer);

        // 写入解码后的PCM数据
        fwrite(output_buffer, sizeof(short), FRAME_SIZE, output);
    }

    // 清理资源
    fclose(output);
    fclose(input);
    speex_bits_destroy(&bits);
    speex_decoder_destroy(enc_state);

    return 0;
}

7、speex 压缩率

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要将Speex音频编解码库移植到STM32F4微控制器上,我们需要注意一些关键步骤和考虑因素。 首先,我们需要下载Speex库的源代码,并对其进行解压和配置。接下来,为了能够编写和编译Speex库的代码,我们需要一个适当的集成开发环境(IDE),如Keil或者IAR。 然后,我们需要根据我们的STM32F4开发板的规格和功能,配置库的编译选项和设置。这包括处理器的架构,时钟频率,外设等。我们还需要调整库的配置文件和头文件,以确保与STM32F4的硬件和驱动兼容。 接下来,我们需要将Speex库的源代码添加到我们的项目中,并确保正确地链接和编译。这可能涉及到修改我们的项目的编译设置和链接脚本。 然后,我们需要根据我们的应用程序的需求,编写相应的代码来初始化和配置STM32F4的音频外设接口,例如I2S或SPI。这些外设接口将与Speex库进行数据传输和处理。 在初始化完成后,我们可以使用Speex库提供的编码和解码函数来处理音频数据。我们可以将音频数据从外部来源输入到STM32F4的外设接口,并使用库来编码和解码数据。然后,我们可以将解码后的音频数据输出到外部设备,如扬声器或耳机。 最后,在使用Speex库进行音频编码和解码时,我们应该密切关注处理器的资源使用情况,以确保合理地分配和管理内存和处理能力。 总之,将Speex音频编码库移植到STM32F4微控制器上需要进行适当的配置、编译和初始化。我们还需要编写额外的代码来管理外设接口和处理音频数据。这样,我们就能够在STM32F4上成功地实现音频编码和解码功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值