LC3解码开发记录

LC3 frame结构

编解码器的帧结构由以下四部分组成:

辅助信息(Side information)包含关于帧数据的配置的静态位。该数据块从帧的末尾开始并向后读取。它包括关于音频带宽、全局增益、噪声电平、TNS活动、LTPF、SNS数据、最后一条非零谱线的索引以及部分量化谱的信息。准确的比特流定义可以在部分。

算术编码的动态数据块,包含TNS和量化频谱的分数部分。此块从帧的开头向结尾读取。

具有符号和量化频谱的最低有效位部分的动态数据块。该块从静态侧信息位的末尾向后读取。

残差数据位于两个动态数据块之间,包含量化频谱的细化。从具有频谱符号和频谱LSB的动态数据块之后立即向后读取。

LC3解码开发记录_第1张图片

每帧字节数的比特率示例:

LC3解码开发记录_第2张图片

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  -b  | ./dlc3 > 
./elc3  -b  | ./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

截图如下:

LC3解码开发记录_第3张图片
这个可以用来判断demuxer中read packet中读出来数据是否正确。

如果是2 channnel数据,nbytes是40时,对应28,这个在解码插件读取packet的时候判断数据是否正确非常方便,可以gdb跟踪。

LC3解码开发记录_第4张图片

nbytes & bitrate

Table 3.1 shows the main features of LC3 coding one audio channel.

LC3解码开发记录_第5张图片

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

未完待续

你可能感兴趣的:(ffmpeg,音视频)