LC3 frame结构
编解码器的帧结构由以下四部分组成:
辅助信息(Side information
)包含关于帧数据的配置的静态位。该数据块从帧的末尾开始并向后读取。它包括关于音频带宽、全局增益、噪声电平、TNS活动、LTPF、SNS数据、最后一条非零谱线的索引以及部分量化谱的信息。准确的比特流定义可以在部分。
算术编码的动态数据块,包含TNS和量化频谱的分数部分。此块从帧的开头向结尾读取。
具有符号和量化频谱的最低有效位部分的动态数据块。该块从静态侧信息位的末尾向后读取。
残差数据位于两个动态数据块之间,包含量化频谱的细化。从具有频谱符号和频谱LSB的动态数据块之后立即向后读取。
每帧字节数的比特率示例:
LC3原始库
lc的子目录tools 带有dlc3和elc3,可以用来-Dtools=true
启用编译。
git clone https://github.com/google/liblc3.git
meson build -Dtools=true --reconfigure
cd build
ninja
sudo ninja install
编译后,定义了DESTDIR,就将编译产物复制到了``pwd/inst
目录下,去掉环境变量DESTDIR,安装到系统默认目录下。
elc3和dlc3的用法
./elc3 <in.wav> -b <bitrate> | ./dlc3 > <out.wav>
./elc3 <in.wav> -b <bitrate> | ./dlc3 | aplay
编码解码:
$ ./elc3 mono-Front_Right.wav -b 1000 mono-front_right.lc3
00:01 Encoded in 0.14 seconds
$ ./dlc3 mono-front_right.lc3 | aplay
Playing WAVE 'stdin' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
00:01 Decoded in 0.312 seconds
$ ./dlc3 mono-front_right.lc3 mono.wav
debug得出的lc3的数据:
nch 1 int
nsamples 73473 int
nsec 0 int
pcm_fmt LC3_PCM_FORMAT_S16 enum lc3_pcm_format
pcm_samples 73473 int
pcm_sbits 16 int
pcm_sbytes 2 int
pcm_srate_hz 48000 int
srate_hz 48000 int
2ch数据
./elc3 2ch_self_test_16k.wav -b 2000 2ch_self_test_16k.lc3
./elc3 2ch_self_test_16k.wav -b 1000 2ch_self_test_16k.lc3 ./dlc3 | aplay
./elc3 mono-Front_Right.wav -b 1000 mono-Front_Right.wav.lc3 ./dlc3 | aplay
lc3的数据
其中:14代表nbytes
是,十进制是20,存储的是frame的size,14后面紧跟frame的数据。
# header
1c cc 12 00 e0 01 0a 00 01 00 e8 03 00 00 01 1f 01 00
# data
14 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 93 e5 28 34 00 00 04
14 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 93 e5 28 34 00 00 04
14 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 93 e5 28 34 00 00 04
14 00 7d a9 f6 56 f8 63 d7 3f a6 59 3c 7c f8 6a 0a e2 34 3a c0 34
截图如下:
这个可以用来判断demuxer中read packet中读出来数据是否正确。
如果是2 channnel数据,nbytes是40时,对应28,这个在解码插件读取packet的时候判断数据是否正确非常方便,可以gdb跟踪。
nbytes & bitrate
Table 3.1 shows the main features of LC3 coding one audio channel.
lc3解码的理解
frame_us = 10000 us
pcm_srate_hz = 48000
pcm_sbytes = 2
1s的PCM数据为:1(channel) * 48000 * 2(bytes) = 96000 byte = 96k
1s = 1000000 us
一个frame解码后的size为:
lc3_decoder_size(frame_us, pcm_srate_hz)
即:10000/1000000 * 48000 * 2 = 0.01(s) * 48000 * 2 = 960 byte
所以:1 channel/48000/s16格式,的lc3 decode pcm size是固定的960字节。
lc3_decode函数参数
-
decoder:每个channel有一个decoder
-
in:输入数据,每个channel是连续的数据块
-
pcm:输出buffer
-
这个从代码
pcm + ich * pcm_sbytes
可以判断,不同channel的数据是交错在一起的(ich等于0和ich等于1的时候,pcm只是偏移pcm_sbytes为2个字节,刚好是一个采样的字节数) -
对应的av format就是
AV_SAMPLE_FMT_S16
,这个在lc3_read_header
中,如果初始化为AV_SAMPLE_FMT_S16P
,后面就会碰到内存问题,在解码阶段分配的avframe的buffer大小不够放解码后的pcm数据问题,而且不好检查到这个错误。对应的decoder插件中的sample_fmts
也要设置为AV_SAMPLE_FMT_S16
。
-
int lc3_decode(struct lc3_decoder *decoder, const void *in, int nbytes,
enum lc3_pcm_format fmt, void *pcm, int stride);
LC3编码器算法压缩有效载荷范围
文档: 2.1 Overview
基于外部设置的比特率,LC3编码器算法压缩每个通道的单个 PCM(脉冲编码调制)帧,并为每个通道(有效载荷)提供源编码位,而无需在此有效载荷之上添加任何传输通道错误保护。
单个通道的有效载荷大小范围为每帧20字节到400字节,对应于10毫秒帧的总压缩比特率范围为16,000 bps 到 320,000 bps,对于 7.5 毫秒帧总压缩比特率范围为21,334bps 到 426,667bps。
对于的10.884ms的帧,采样率为44.1kHz,相应的比特率范围是14,700bps到294,000bps,对于7.5ms帧,比特率范围是19,600 bps 到 392,000 bps。
LC3 可以以恒定比特率或外部控制的可变比特率运行。
lc3采样率和frame duration
sample rate: 8000, 16000, 24000, 32000, 48000
frame samples in 10ms: 80, 160, 240, 320, 480
frame samples in 7.5ms: 60, 120, 180, 240, 360
计算nsamples
已知:
- duration
- sample_rate
那么:
one_sample_duration = 1000000/sample_rate
nsamples = duration_us / one_sample_duration
所以:
int nsamples = par->sample_rate * duration / AV_TIME_BASE;
lc3bin_read_data
int frame_bytes = lc3bin_read_data(fp_in, nch, in);
从fp的文件中读取nch的一帧数据,frame_bytes是一帧数据的大小。在dlc3中,frame_bytes是20
lc3 meson编译错误
$ sudo ninja -C build install
ninja: Entering directory `build'
[0/1] Installing files.
ERROR: Build directory has been generated with Meson version 0.62.2, which is incompatible with the current version 0.61.2.
FAILED: meson-install
/home/hui/.local/bin/meson install --no-rebuild
ninja: build stopped: subcommand failed.
安装meson0.61.2就可以了:pip3 install meson==0.61.2
Table 3.1 shows the main features of LC3 coding one audio channel.
直接比较计算p->buf前两个字节的值也是可以的,这里(p->buf[0] | p->buf[1])
一定要加括号,否则会带来灾难。
if ((p->buf[0] | p->buf[1]) << 8 == LC3_FILE_ID)
init decoder中frame_us的计算
frame_us
在lc3中记录的是lc3的frame duration
,lc3 read header时候能得到,是lc3的header字段。
codecpar
存储frame_size,所以可以通过frame_size计算frame_us:
- 因为
codecpar->frame_size
记录的是audio的frame samples
,即frame的采样数,所以frame_us和sample rate的关系如下:
frame_us = 10000 us
sample rate = 48000
1s = 1000000 us
=>
frame_us / 1000000(1s) * 48000 = frame_samples
frame_samples / 48000 * 1000000(1s) = frame_us
在read_header
函数中可以这么计算:
st->codecpar->frame_size = frame_us / AV_TIME_BASE * sample_rate;
=>
st->codecpar->frame_size = sample_rate * frame_us / AV_TIME_BASE;
这样在decoder init中就可以通过frame_size
和sample_rate
计算得出frame_us
:
int frame_us = AV_TIME_BASE * avctx->frame_size / avctx->sample_rate;
同样地,muxer中计算frame_us,因为codecpar->frame_size
记录的是audio的frame samples,所以还是用到这个计算,frame_size
在ffmpeg命令行中可以通过frame_size
参数指定:
int frame_us = AV_TIME_BASE * avctx->frame_size / avctx->sample_rate;
比如:
ffmpeg -y -i ../data/2ch16k.wav -v 56 -ac 1 -b:a 2000 -sample_fmt s16 -frame_size 160 2ch16k.lc3
FFmpeg相关
AV_RL16 & AV_RB16
- AV_RL16 - 小端
- AV_RB16 - 大端
- 不存在文件指针的移动
// #include "libavutil/intreadwrite.h"
// little endian
# define AV_RL16(x) \
((((const uint8_t*)(x))[1] << 8) | \
((const uint8_t*)(x))[0])
// big endian
# define AV_RB16(x) \
((((const uint8_t*)(x))[0] << 8) | \
((const uint8_t*)(x))[1])
avio_rl16 & avio_rb16
-
是函数,不是宏定义
-
原型
unsigned int avio_rl16(AVIOContext *s)
,参数是AVIOContext
类型 -
读完函数指针会移动两个字节
avcodec_options - frame_size
static const AVOption avcodec_options[] = {
{"b", "set bitrate (in bits/s)", OFFSET(bit_rate), AV_OPT_TYPE_INT64, {.i64 = AV_CODEC_DEFAULT_BITRATE }, 0, INT64_MAX, A|V|E},
{"ab", "set bitrate (in bits/s)", OFFSET(bit_rate), AV_OPT_TYPE_INT64, {.i64 = 128*1000 }, 0, INT_MAX, A|E},
{"frame_size", NULL, OFFSET(frame_size), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, 0, INT_MAX, A|E},
{"frame_number", NULL, OFFSET(frame_number), AV_OPT_TYPE_INT, {.i64 = DEFAULT }, INT_MIN, INT_MAX},
avcodec_options定义了全部的codec option选项,通过ffmpeg –help
是看不到这么多的。avcodec_options里面定义的都可以在ffmpeg命令行中指定,比如frame_size指定audio frame的采样数。
ffmpeg -y -i data/2ch_16k.wav -v 56 -ac 1 -b:a 2000 -sample_fmt s16 -frame_size=480 2ch_16k.lc3
avio_read
avio_read读数据后,文件指针会自动后移,不需要调用avio_seek操作,后面直接用av_get_packet即可,如果不清楚,可以通过avio_tell获取当前的文件指针位置验证。
if (avio_feof(s->pb)) {
return AVERROR_EOF;
}
avio_read(s->pb, &nbytes, sizeof(uint16_t));
if (nbytes > nchannels * LC3_MAX_FRAME_BYTES || nbytes % nchannels) {
av_log(s, AV_LOG_WARNING, "nbytes invalid.");
return AVERROR_EOF;
}
av_get_packet(s->pb, pkt, nbytes);
avmallocz & av_freep
-
avmallocz分配内存后会memset为0
-
用av_freep后,data会赋值为NULL,而用av_free,则data为野指针,没有赋值。
void *av_mallocz(size_t size)
{
void *ptr = av_malloc(size);
if (ptr)
memset(ptr, 0, size);
return ptr;
}
void av_freep(void *arg)
{
void *val;
memcpy(&val, arg, sizeof(val));
memcpy(arg, &(void *){ NULL }, sizeof(val));
av_free(val);
}
probe参考实现
rm_probe
直接比较内存内容
static int rm_probe(const AVProbeData *p)
{
/* check file header */
if ((p->buf[0] == '.' && p->buf[1] == 'R' &&
p->buf[2] == 'M' && p->buf[3] == 'F' &&
p->buf[4] == 0 && p->buf[5] == 0) ||
(p->buf[0] == '.' && p->buf[1] == 'r' &&
p->buf[2] == 'a' && p->buf[3] == 0xfd))
return AVPROBE_SCORE_MAX;
else
return 0;
}
ads_probe
直接比较内存和字符串
static int ads_probe(const AVProbeData *p)
{
if (memcmp(p->buf, "SShd", 4) ||
memcmp(p->buf+32, "SSbd", 4))
return 0;
return AVPROBE_SCORE_MAX / 3 * 2;
}
aax_probe
AV_RB32和avio_rb32的区别是avio_rb32读完之后,文件指针会更新读位置。
static int aax_probe(const AVProbeData *p)
{
if (AV_RB32(p->buf) != MKBETAG('@','U','T','F'))
return 0;
if (AV_RB32(p->buf + 4) == 0)
return 0;
if (AV_RB16(p->buf + 8) > 1)
return 0;
if (AV_RB32(p->buf + 28) < 1)
return 0;
return AVPROBE_SCORE_MAX;
}
lc3_probe
static int lc3_probe(const AVProbeData *p)
{
if (AV_RL16(p->buf) == LC3_FILE_ID)
return AVPROBE_SCORE_MAX;
return 0;
}
未完待续