iOS解码Speex数据(包含编译好的文件)

虽说Speex已经被Opus代替了,官方也是想我们向Opus迁移,但是Speex还是可以使用的。
可以先看Speex的官方教程,还是挺简单的,但是坑也不少。
要移植到iOS上,首先把Speex的代码打包成.a的静态库,可以google有相关代码,然后也可以直接用编译好的(貌似需要关闭Bitcode),感谢作者。

在编译好的项目中有:
《iOS解码Speex数据(包含编译好的文件)》

还需要在这个工程下的TEST_Speex_001目录中找到SpeexAllHeader.h文件,他帮我们把需要的Speex的头文件导入好了。

把以上文件拷贝到你的工程中(最好新建一个Speex的文件夹 方便管理),然后引入,最终结果:

《iOS解码Speex数据(包含编译好的文件)》

《iOS解码Speex数据(包含编译好的文件)》

然后我们就可以使用了。
推荐可以看这两篇文章:Speex编解码器(原创)speex与wav格式音频文件的互相转换,后一篇虽然是安卓的,但给了我很大的启发。让我知道了Speex的每次读取20字节,然后解析成320字节,编码则是相反的。

【音频的组成】
当前,我们所说的音频,都是数字音频。数字音频由采样频率、采样精度、声音通道数三个部分组成。

  • 采样频率:既采样率,指记录声音时每秒的采样个数,它用赫兹(Hz)来表示。一般用44.1kHz,也可以是>8kHz,11.025kHz,48kHz,96kHz等。
  • 采样精度:指记录声音的动态范围,它以位(Bit)为单位,单个音频采样用得较多的是16位,当然也可以使用8位,24位,甚至32位
  • 声音通道:既声道数(1-8个),用的较多的是2声道,也有单声道,5.1声道,7.1声道。

《iOS解码Speex数据(包含编译好的文件)》
通俗点说,我们可以把声波看成是一条曲线,我们知道,曲线是由点组成的,采样率就是每秒长度(上图横轴)中点的个数。而采样精度就是动态范围(上图竖轴)中点的个数。这两个维度的定位越细,声音的真实还原度就越高,音质也就会更好,当然,音频文件也就会越大。上面那个同事遇到的客户所说的,就是SONY公司最新发布的音频格式Hi-Res Audio,是192kHz / 24bit,6通道录制的音频文件,无损格式的大小当然就会在200多兆了。
采样率根据使用类型不同大概有以下几种(k既千位符号,1khz=1000hz):

8khz:电话等使用,对于记录人声已经足够使用。
22.05khz:广播使用频率。
44.1kb:音频CD。
48khz:DVD、数字电视中使用。
96khz-192khz:DVD-Audio、蓝光高清等使用。
采样精度常用范围为8bit-32bit,而CD中一般都使用16bit。

作者:Ianlie Dark链接:
https://www.zhihu.com/questio…
来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一帧:应该是指持续采样时间,这个是很灵活的可以使用20ms,也可是200ms,一般来说时间越短延时就越少。
这样一帧的PCM数据大小就很容易计算出来:PCMBufferSize = 采样率采样时间采样位深/8*通道数 Bytes

其实上述的20 -> 320字节是有和音频有关,和Speex没啥关系。
就目前来看的确和音频的位数有关(之前不确定),PCM音频大小有这个计算公式:            
数据量(字节/秒)= (采样频率(Hz)× 采样位数(bit)× 声道数)/ 8

我的是 8000Hz采样率 16位音频 单声道(1),所以320字节:             

(8000 * 16 * 1) / 8 = 16000(字节/秒) / 1000 = 16(字节/毫秒)
然后转换成毫秒: 16(字节/毫秒) * 20毫秒 = 320(字节/毫秒)
20ms是一帧音频的长度

所以意味着8k采样率 16位音频 单声道的一帧长度(20ms)的音频大小是320字节

然后如果用的是short的数组,那么这个数组的长度应该是160,因为一个short等于两个字节。

接下来贴出代码(待完善内存释放和噪音抑制):

SpeexToPcm.h

#import <Foundation/Foundation.h>

@interface SpeexToPcm : NSObject

/**
 把20个char的speexData转换成160个shot的pcmData  320字节

 @param speexData speex的数据 
 @param pcmData 转换出来的数据 
 @return 返回转换结果 0表示ok
 */
- (int) converWith: (char*)speexData toPcm:(short*)pcmData;

@end

SpeexToPcm.m

#import "SpeexToPcm.h"
#import "SpeexAllHeader.h"

#define SPEEX_SIZE 20
#define PCM_SIZE 160

@interface SpeexToPcm() {
    
    /* 保存编码的状态 */
    void *decoder;
    /* 保存字节(也就是sppex数据 但是要经过speex的转换) 他们可以被speex常规读写 */
    SpeexBits bits;
}

@end

@implementation SpeexToPcm

- (instancetype)init
{
    self = [super init];
    if (self) {
        /**
         返回一个新创建的解码器状态结构的句柄。
         Parameters:
         mode     Speex mode (one of speex_nb_mode or speex_wb_mode)
         */
        // 其中speex_nb_mode是SpeexMode类型的变量,表示的是窄带模式。还有speex_wb_mode表示宽带模 式、speex_uwb_mode表示超宽带模式。
        decoder = speex_decoder_init(&speex_nb_mode);
        
        // 是否启用增强器
        int tmp = 1;
        /**
         像ioctl函数一样用来控制编码器参数
         Parameters:
         state         Decoder state
         request     ioctl-type request (one of the SPEEX_* macros)SPEEX_GET_ENH | SPEEX_SET_ENH
         ptr         Data exchanged to-from function
         */
        speex_decoder_ctl(decoder, SPEEX_SET_ENH, &tmp);
        
        // 为SpeexBits结构初始化和分配资源
        speex_bits_init(&bits);
    }
    return self;
}

- (int) converWith: (char*)speexData toPcm:(short*)pcmData{
    // 初始化来自存储器区域中的数据的比特流
    // void speex_bits_read_from(SpeexBits *bits, const char *bytes, int len);
    speex_bits_read_from(&bits, speexData, SPEEX_SIZE);
    
    memset(pcmData, 0, PCM_SIZE);
    
    /**
     Parameters:
     state     Decoder state
     bits     Bit-stream from which to decode the frame (NULL if the packet was lost)
     out     Where to write the decoded frame
     */
    return speex_decode_int(decoder, &bits, pcmData);
}

@end

有疑问或者纠错,欢迎评论。

    原文作者:喵了个咪
    原文地址: https://segmentfault.com/a/1190000010177383
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞