音频demo:将PCM数据和Speex数据进行相互编解码

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;
}

3、demo下载地址(任选一个)

相关推荐
通信小小昕1 天前
基于I2S的音频ADC_DAC的_FPGA的驱动
fpga开发·音频·adc·fifo·dac·i2s
limengshi1383922 天前
通信工程学习:什么是PCM脉冲编码调制、DPCM差分脉冲编码调制、ADPCM自适应差分脉冲编码调制
网络·学习·信息与通信·pcm·adpcm
gikod2 天前
【原创】edge-tts与基于mpv的edge-playback,使命令行和Python的Text To Speech唾手可得
python·音频·命令模式
C有点难。5 天前
音频原始数据PCM
音视频·pcm
楚肽生物--多肽合成定制6 天前
特异性心肌细胞靶向肽(PCM);WLSEAGPVVTVRALRGTGSW;CAS:771479-86-8
科技·学习·pcm
小李不背锅7 天前
【安卓13】解决HDMI OUT和耳机等设备接入时会解除静音问题
android·音频
跃龙客9 天前
PCM转PCMA(pcm_alaw,G711.A率)转换表 && PCM转PCMU(pcm_ulaw,G711.U率)转换表
pcm·g711
彷徨而立10 天前
音频PCM的能量dB计算
pcm
畅联云平台12 天前
美畅物联丨从模拟到数字的华丽转身:PCM及其衍生编码技术解析
pcm
2401_8566522113 天前
告别手动记录,音频转文字软件助力会议记录新高度
人工智能·音视频·音频·语音识别·实时音视频