1、README
a. libspeex移植步骤
源码下载地址:https://xiph.org/downloads/ 或 https://www.speex.org/downloads/ 或 https://www.linuxfromscratch.org/blfs/view/svn/multimedia/speex.html
bash
tar xzf speex-1.2.1.tar.gz
cd speex-1.2.1/
./configure --prefix=$PWD/_install
make -j8
make install
strip --strip-unneeded _install/lib/*
bash
$ tree -h _install/
_install/
├── [4.0K] include
│ └── [4.0K] speex
│ ├── [6.5K] speex_bits.h
│ ├── [4.9K] speex_callbacks.h
│ ├── [ 203] speex_config_types.h
│ ├── [ 14K] speex.h
│ ├── [4.0K] speex_header.h
│ ├── [3.6K] speex_stereo.h
│ └── [3.5K] speex_types.h
├── [4.0K] lib
│ ├── [188K] libspeex.a
│ ├── [1006] libspeex.la
│ ├── [ 17] libspeex.so -> libspeex.so.1.5.2
│ ├── [ 17] libspeex.so.1 -> libspeex.so.1.5.2
│ ├── [ 98K] libspeex.so.1.5.2
│ └── [4.0K] pkgconfig
│ └── [ 358] speex.pc
└── [4.0K] share
├── [4.0K] aclocal
│ └── [3.6K] speex.m4
└── [4.0K] doc
└── [4.0K] speex
└── [429K] manual.pdf
8 directories, 15 files
b. 说明
demo现状
-
目前暂时没找到相关播放器直接播放spx格式文件;
-
将demo编码生成的spx格式文件再通过demo解码生成pcm数据是能正常播放的;
-
目前demo生成的spx格式文件是夹带着4个字节编码数据量大小,即:
[spx数据长度(4字节)]+[spx编码数据(n字节)] + [spx数据长度(4字节)]+[spx编码数据(n字节)] + ...
关于speex格式的说明
- Speex(音标[spi:ks])是一套开源免费的、无专利保护的、针对语音设计的音频压缩格式。
- Speex编解码器已经被Opus编解码器淘汰,Speex还是可以继续使用,由于Opus比Speex在各方面都更好,所以鼓励大家切换到Opus,但是Opus只支持编码和解码,不支持噪音抑制、声学回音消除等其他处理功能。
- 只支持8000Hz窄带(Narrow Band)、16000Hz宽带(Wide Band)、32000Hz超宽带(Ultra Wide Band)的三种带模式进行编解码,不支持其他采样频率。
- 只支持单声道,不支持多声道。
- 支持强化立体声编码(Intensity Stereo Encoding)。
- 支持数据包丢失隐藏(Packet Loss Concealment、PLC)。
- 支持固定比特率(Constant Bit Rate、CBR)。
- 支持可变比特率(Variable Bit Rate、VBR)。
- 支持平均比特率(Average Bit Rate、ABR)。
- 支持非连续传输(Discontinuous transmission、DTX)。
- 支持定点执行(Fixed-point implementation)。
- 支持浮点执行(Floating-point implementation)。
- 支持声学回音消除(Acoustic Echo Canceller、AEC)。
- 支持残余回音消除(Residual Echo Canceller、REC)。
- 支持噪音抑制(Noise Suppression、NS)。
- 支持混响音消除(Dereverb)。
- 支持自动增益控制(Automatic Gain Control、AGC)。
- 支持语音活动检测(Voice Activity Detection、VAD)。
- 支持多速率(multi-rate)。
- 支持嵌入式(Embedded)。
- 支持重采样(Resample)。
c. 使用方法
编译
bash
$ make clean && make
rm -rf pcm2speex speex2pcm pcm2speex_gpt speex2pcm_gpt out*
gcc main_pcm2speex.c -I./include -lspeex -L./lib -lm -o pcm2speex
gcc main_speex2pcm.c -I./include -lspeex -L./lib -lm -o speex2pcm
gcc main_pcm2speex_gpt.c -I./include -lspeex -L./lib -lm -o pcm2speex_gpt
gcc main_speex2pcm_gpt.c -I./include -lspeex -L./lib -lm -o speex2pcm_gpt
编码
bash
$ ./pcm2speex
Usage:
./pcm2speex <in-pcm-file> <sample-rate> <out-spx-file>
examples:
./pcm2speex ./audio/test_8000_16_1.pcm 8000 out_8000.spx
./pcm2speex ./audio/test_16000_16_1.pcm 16000 out_16000.spx
解码
bash
$ ./speex2pcm
Usage:
./speex2pcm <in-spx-file> <sample-rate> <out-pcm-file>
examples:
./speex2pcm out_8000.spx 8000 out_8000.pcm
./speex2pcm out_16000.spx 16000 out_16000.pcm
d. 参考文章
e. demo目录架构
bash
$ tree
.
├── audio
│ ├── test_16000_16_1.pcm
│ └── test_8000_16_1.pcm
├── docs
│ ├── speex 编码简介-CSDN博客.mhtml
│ ├── Speex详解(2019年09月25日更新) - 赤勇玄心行天道 - 博客园.pdf
│ └── 音频编解码speex库的使用方法-CSDN博客.pdf
├── include
│ └── speex
│ ├── speex_bits.h
│ ├── speex_callbacks.h
│ ├── speex_config_types.h
│ ├── speex.h
│ ├── speex_header.h
│ ├── speex_stereo.h
│ └── speex_types.h
├── lib
│ └── libspeex.a
├── main_pcm2speex.c
├── main_speex2pcm.c
├── Makefile
├── opensource
│ └── speex-1.2.1.tar.gz
├── README.md
└── reference_code
├── blog_pcm2speex.c
├── blog_speex2pcm.c
├── gpt_pcm2speex.c
└── gpt_speex2pcm.c
2、主要代码片段
main_pcm2speex.c
c
#include <speex/speex.h>
#include <stdio.h>
#include <stdlib.h>
#include "speex/speex.h"
/* 定义帧大小,对于窄带模式,帧大小通常是160 */
#define FRAME_SIZE 160
int main(int argc, char **argv)
{
char *in_pcm_file_name = NULL;
FILE *fp_in_pcm = NULL;
unsigned int sample_rate = 0;
char *out_spx_file_name = NULL;
FILE *fp_out_spx = NULL;
short *pcm_buf = NULL;
char *cbits = NULL;
int nbBytes;
int framesize = 0;
/* 检查参数 */
if(argc != 4)
{
printf("Usage: \n"
"\t %s <in-pcm-file> <sample-rate> <out-spx-file>\n"
"examples: \n"
"\t %s ./audio/test_8000_16_1.pcm 8000 out_8000.spx\n"
"\t %s ./audio/test_16000_16_1.pcm 16000 out_16000.spx\n"
, argv[0], argv[0], argv[0]);
return -1;
}
in_pcm_file_name = argv[1];
sample_rate = atoi(argv[2]);
out_spx_file_name = argv[3];
const SpeexMode *speex_mode = NULL;
switch(sample_rate)
{
case 8000:
speex_mode = &speex_nb_mode;
break;
case 16000:
speex_mode = &speex_wb_mode;
break;
case 32000:
speex_mode = &speex_uwb_mode;
break;
}
/* 初始化编码器状态 */
void *state = speex_encoder_init(speex_mode);
/* 设置编码质量为8(15 kbps) */
int quality = 8;
speex_encoder_ctl(state, SPEEX_SET_QUALITY, &quality);
speex_encoder_ctl(state, SPEEX_SET_SAMPLING_RATE, &sample_rate);
speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &framesize);
int bps = 0; // 只是用来打印一下
speex_encoder_ctl(state, SPEEX_GET_BITRATE, &bps);
printf("It will encode with [quality: %d, sample_rate: %d, framesize: %d, bps: %d].\n", quality, sample_rate, framesize, bps);
fp_in_pcm = fopen(in_pcm_file_name, "rb");
if (!fp_in_pcm) {
printf("Error opening input file: %s\n", in_pcm_file_name);
speex_encoder_destroy(state);
return 1;
}
/* 打开输出文件,用于存储编码后的Speex数据 */
fp_out_spx = fopen(out_spx_file_name, "wb");
if (!fp_out_spx) {
printf("Error opening output file\n");
fclose(fp_in_pcm);
speex_encoder_destroy(state);
return 1;
}
pcm_buf = (short *)malloc(sizeof(short) * framesize);
cbits = (char *)malloc(sizeof(char) * framesize * 1.25); // 多个1/4
/* 初始化位处理结构 */
SpeexBits bits;
speex_bits_init(&bits);
/* 循环读取、编码并写入数据 */
while (1) {
/* 读取一个16位/sample的音频帧 */
size_t bytesRead = fread(pcm_buf, sizeof(short), framesize, fp_in_pcm);
if (bytesRead != framesize) {
if (feof(fp_in_pcm)) {
break; // 文件结束
} else {
printf("Error reading input file\n");
speex_bits_destroy(&bits);
fclose(fp_out_spx);
fclose(fp_in_pcm);
speex_encoder_destroy(state);
return 1;
}
}
/* 重置位处理结构,以便编码新帧 */
speex_bits_reset(&bits);
/* 编码帧 */
speex_encode_int(state, pcm_buf, &bits);
/* 将位复制到字符数组中,以便写入 */
nbBytes = speex_bits_write(&bits, cbits, 200);
/* 写入帧大小 */
/* LRM: 这里写入接下来编码的数据大小,这里记录方便后面解码知道要读多少,不是标准的spx文件格式 */
fwrite(&nbBytes, sizeof(int), 1, fp_out_spx);
/* 写入压缩数据 */
/* LRM: 这里写入编码后的spx数据 */
fwrite(cbits, 1, nbBytes, fp_out_spx);
printf("[Speex Encode] pcm(%lu Bytes) -> spx(%d Bytes).\n", framesize * sizeof(short), nbBytes);
}
/* 清理资源 */
speex_bits_destroy(&bits);
fclose(fp_out_spx);
fclose(fp_in_pcm);
speex_encoder_destroy(state);
free(pcm_buf);
free(cbits);
return 0;
}
main_speex2pcm.c
c
#include <speex/speex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
char *in_spx_file_name = NULL;
FILE *fp_in_spx = NULL;
unsigned int sample_rate = 0;
char *out_pcm_file_name = NULL;
FILE *fp_out_pcm = NULL;
char *speex_buffer = NULL;
short *pcm_buffer = NULL;
int framesize = 0;
// 定义SpeexBits和编码状态变量
SpeexBits bits;
void *dec_state;
/* 检查参数 */
if(argc != 4)
{
printf("Usage: \n"
"\t %s <in-spx-file> <sample-rate> <out-pcm-file>\n"
"examples: \n"
"\t %s out_8000.spx 8000 out_8000.pcm\n"
"\t %s out_16000.spx 16000 out_16000.pcm\n"
, argv[0], argv[0], argv[0]);
return -1;
}
in_spx_file_name = argv[1];
sample_rate = atoi(argv[2]);
out_pcm_file_name = argv[3];
const SpeexMode *speex_mode = NULL;
switch(sample_rate)
{
case 8000:
speex_mode = &speex_nb_mode;
break;
case 16000:
speex_mode = &speex_wb_mode;
break;
case 32000:
speex_mode = &speex_uwb_mode;
break;
}
// 初始化bits
speex_bits_init(&bits);
// 初始化编码状态,这里使用窄带模式
dec_state = speex_decoder_init(speex_mode);
speex_decoder_ctl(dec_state, SPEEX_SET_SAMPLING_RATE, &sample_rate);
speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &framesize);
// 打开输入文件,这里假设输入文件是Speex编码的
fp_in_spx = fopen(in_spx_file_name, "rb");
if (!fp_in_spx) {
printf("Error opening input file\n");
return 1;
}
// 打开输出文件,用于存储解码后的PCM数据
fp_out_pcm = fopen(out_pcm_file_name, "wb");
if (!fp_out_pcm) {
printf("Error opening output file\n");
fclose(fp_in_spx);
return 1;
}
speex_buffer = malloc(framesize); // 这是编码后的,空间足够了的
pcm_buffer = (short *)malloc(sizeof(short) * framesize);
// 循环读取、解码并写入文件
while (1) {
// 读取一帧Speex编码的数据
#if 0
int bytes_read = fread(&bits.bytes, 1, sizeof(bits.bytes), fp_in_spx);
if (bytes_read == 0) {
// 如果读取的字节数为0,表示文件结束
break;
}
bits.nbytes = bytes_read;
bits.nbBits = bytes_read * 8;
#else
int spx_bytes = 0;
int bytes_read = 0;
/* LRM: 这里读出接下来的spx编码数据长度,对应于我们编码时写入的数据长度 */
bytes_read = fread(&spx_bytes, 1, sizeof(spx_bytes), fp_in_spx);
if (bytes_read == 0) {
// 如果读取的字节数为0,表示文件结束
printf("file end.\n");
break;
}
printf("will read spx bytes: %d, ", spx_bytes);
/* LRM: 这里读出编码后的spx数据 */
bytes_read = fread(speex_buffer, 1, spx_bytes, fp_in_spx);
if (bytes_read == 0) {
// 如果读取的字节数为0,表示文件结束
printf("file end(-1).\n");
break;
}
printf("decode output pcm size: %lu\n", framesize * sizeof(short));
// 将 Speex 数据填充到比特流结构体中
speex_bits_read_from(&bits, speex_buffer, bytes_read);
#endif
// 解码
speex_decode_int(dec_state, &bits, pcm_buffer);
// 写入解码后的PCM数据
fwrite(pcm_buffer, sizeof(short), framesize, fp_out_pcm);
}
// 清理资源
fclose(fp_out_pcm);
fclose(fp_in_spx);
speex_bits_destroy(&bits);
speex_decoder_destroy(dec_state);
return 0;
}