音频基本概念

1. 采样率

    采样设备每秒抽取样本的次数,每秒多少个sample;倒数表示每个sample多少秒。

    声音是连续的模拟信号,采样设备每秒采样48k次,相当于做了量化处理,对一个音频曲线在1秒区间内划分48k个子区间,每个区间取一个点。划分区间越多,越接近一条完整的曲线。量化操作会损失一部分信号。

2. 音频格式及量化精度(位宽)

    每种音频格式有不同的量化精度(位宽),位数越多,表示值就越精确,声音表现自然就越精准。图像颜色空间同样有位宽,例如RGB888,每个像素有rgb三种颜色,每种颜色值用8bit表示。

FFMpeg中音频格式有以下几种,每种格式有其占用的字节数信息:

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits,packet打包格式,LRLRLR
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar平面格式,LLLRRR
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

 3. 分片(plane)和打包(packed)

    以双声道为例,带P(plane)的数据格式在存储时,其左声道和右声道的数据是分开存储的,左声道的数据存储在data[0],右声道的数据存储在data[1],每个声道的所占用的字节数为linesize[0]和linesize[1];

    不带P(packed)的音频数据在存储时,是按照LRLRLR...的格式交替存储在data[0]中,linesize[0]表示总的数据量。

4. 声道

       声道指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,声道数也就是声音录制时的音源数量或回放时相应独立发声的扬声器数量。

       简单来说,a.b.c声道音响系统就是由a个前后左右环绕的扬声器+b个低音炮+c个天空扬声器,共计a+b+c个扬声器组成的总计a+b+c个声道的系统。其中,a位置代表的是a个环绕扬声器;b位置代表的是b个专门设计的超低音声道,需要单独的低音炮来发声;c位置代表的是c个天空扬声器,装在顶上。

例如7.1.4声道音响就是中置、左右前置、左右环绕、左右后置7个扬声器加上1个低音炮和4个天空扬声器,共计12个扬声器组成的12声道系统,而2.0声道音响是有左右两个扬声器,没有低音炮也没有上方扬声器的双声道系统。

单声道:即一个声音通道,用一个传声器拾取声音,用一个扬声器进行放音的过程,称之为单声道。单声道只能听得到所有声音包括人声、枪声、汽车声等都从一个地方来,听不出声音的定位、移动等,效果相对于真实的自然声来说,是简单化、失真的。随着时代的发展,单声道已经越来越无法满足人们的影音欣赏和声音体验需求,产品很少,也不建议小伙伴们选购。

立体声:2.0声道、2.1声道都是立体声,立体声能够听到声音的方位和变化,相比单声道系统,音质有很大改善、临场感也大大加强,在重现声源的定位方面有很大改进。

2.1声道比2.0声道多一个低音炮,用2.1声道音响系统在家看电影的时候,电影的左右声道声音就会分别从左右音箱传出来,而低音部分会但单独从低音炮中传出,除呈现声音的立体声效果清晰辩位外,声音的高中低部分也能有更好的呈现,用户能够享受沉醉低音和真实立体声效果。但真实的环绕感和现场感,立体声就完全做不到了。

环绕声:常见的环绕声包括5.1、7.1声道,在家庭影院方面,环绕声具有立体声不可比拟的优势,三维空间感能给观众一种鲜活的、置身其中的临场感,可以感知到前所未有的细微声音移动,拥有真实震撼的观影体验。其中,7.1声道要更加强大,它在5.1的基础上又增加了左后和右后两个发音点(扬声器),以求达到声音的完美境界。能够实现全方位全包围环绕,更加逼真、更具现场感的极佳观影体验。
全景声:杜比全景声(Dolby Atmos)是杜比实验室研发的3D环绕声技术,全景声是一种颠覆性改变,它将基于声道来混音的技术上升为基于对象的音频处理技术,声音不再按照声道预设好的来输出,而是根据影片情节灵活处理,通过解码计算之后按照算法来指定具体的某些喇叭输出。在效果上,杜比全景声能够实现以影片情节来设定的动态声音效果,更真实地营造出声音的方位感和临场感,通过更多扬声器展现更多声音细节,达到一种动态化的震撼完美声音效果。

channel通常有单声道和双声道等;channel_layout是声道布局,例如左前,右前,中间,左后,右后。

        声道布局在FFmpeg\libavutil\channel_layout.h中有定义,一般来说用的比较多的是AV_CH_LAYOUT_STEREO(双声道)和AV_CH_LAYOUT_SURROUND(三声道),这两者的定义如下:

#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)
#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)

5. 音频帧的数据量计算

    一帧音频的数据量=channel数 * nb_samples样本数 * 每个样本占用的字节数

    如果该音频帧是FLTP格式的PCM数据,包含1024个样本,双声道,那么该音频帧包含的音频数据量是2*1024*4=8192字节。

6. 音频播放时间计算

         以采样率44100Hz来计算,每秒44100个sample,而AAC一帧为1024个sample(MP3一帧是1152个sample),可知每帧播放时间/1024=1000ms/44100,得到每帧播放时间=1024*1000/44100=23.2ms。

7. 音频重采样(resample)

         对于音频原始采样格式和目标要求的格式不匹配时,通常会进行音频重采样进行适配。重采样分为上采样和下采样,下采样时需要对信号进行抽取(可能会导致尾部丢失音频数据),上采样时需要对信号进行插值(可能会有噪音)。在实现有理数级重采样时,则是将上采样和下采样做结合(例如48kHz 转 44.1kHz时,将44.1kHz近似为44kHz,将48kHz下采样到4kHz,再上采样至44kHz来实现)。

        由数字信号处理中,时域信号和频域信号的时-频对偶特性可知:时域的抽取,对应频域的延拓;时域的插值,对应频域的压缩。如果对信号的频率成分不做限制的话,频域的延拓可能会引发频谱混迭;频域的压缩来引起频谱镜像相应。因此在下采样前,要经过滤波器滤波来防止混迭,即抗混迭(antialiasing filter)滤波;上采样后也要经过滤波处理,即抗镜像(anti-image filter)滤波。
 

FFMpeg自带的resample例子:FFmpeg\doc\examples\resampling_audio.c,这里把最核心的resample代码贴一下,在工程中使用时,注意设置的各种参数,给定的输入数据都不能错。

int main(int argc, char **argv)
{
    // 设置数据源src和dst声道布局
    int64_t src_ch_layout = AV_CH_LAYOUT_STEREO, dst_ch_layout = AV_CH_LAYOUT_SURROUND;
    // 设置src和dst采样率
    int src_rate = 48000, dst_rate = 44100;
    uint8_t **src_data = NULL, **dst_data = NULL;
    int src_nb_channels = 0, dst_nb_channels = 0;
    int src_linesize, dst_linesize;
    int src_nb_samples = 1024, dst_nb_samples, max_dst_nb_samples;
    // 设置src和dst音频格式
    enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_DBL, dst_sample_fmt = AV_SAMPLE_FMT_S16;
    const char *dst_filename = NULL;
    FILE *dst_file;
    int dst_bufsize;
    const char *fmt;
    // 重采样上下文,包含resample信息
    struct SwrContext *swr_ctx;
    double t;
    int ret;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s output_file\n"
                "API example program to show how to resample an audio stream with libswresample.\n"
                "This program generates a series of audio frames, resamples them to a specified "
                "output format and rate and saves them to an output file named output_file.\n",
            argv[0]);
        exit(1);
    }
    // resample后的数据保存到本地文件
    dst_filename = argv[1];

    dst_file = fopen(dst_filename, "wb");
    if (!dst_file) {
        fprintf(stderr, "Could not open destination file %s\n", dst_filename);
        exit(1);
    }

    /* create resampler context */
    swr_ctx = swr_alloc();
    if (!swr_ctx) {
        fprintf(stderr, "Could not allocate resampler context\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }

    /* set options */
    // 将resample信息写入resample上下文
    av_opt_set_int(swr_ctx, "in_channel_layout",    src_ch_layout, 0);
    av_opt_set_int(swr_ctx, "in_sample_rate",       src_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);

    av_opt_set_int(swr_ctx, "out_channel_layout",    dst_ch_layout, 0);
    av_opt_set_int(swr_ctx, "out_sample_rate",       dst_rate, 0);
    av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);

    /* initialize the resampling context */
    if ((ret = swr_init(swr_ctx)) < 0) {
        fprintf(stderr, "Failed to initialize the resampling context\n");
        goto end;
    }

    /* allocate source and destination samples buffers */

    src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
    ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels,
                                             src_nb_samples, src_sample_fmt, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate source samples\n");
        goto end;
    }

    /* compute the number of converted samples: buffering is avoided
     * ensuring that the output buffer will contain at least all the
     * converted input samples */
    max_dst_nb_samples = dst_nb_samples =
        av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);

    /* buffer is going to be directly written to a rawaudio file, no alignment */
    dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
    ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels,
                                             dst_nb_samples, dst_sample_fmt, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate destination samples\n");
        goto end;
    }

    t = 0;
    do {
        /* generate synthetic audio */
        // 这里是自行生成源数据帧,实际工程中应该将解码后的PCM数据填入src_data中
        fill_samples((double *)src_data[0], src_nb_samples, src_nb_channels, src_rate, &t);

        /* compute destination number of samples */
        dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_rate) +
                                        src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
        if (dst_nb_samples > max_dst_nb_samples) {
            av_freep(&dst_data[0]);
            ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
                                   dst_nb_samples, dst_sample_fmt, 1);
            if (ret < 0)
                break;
            max_dst_nb_samples = dst_nb_samples;
        }

        /* convert to destination format */
        // dst_nb_samples输出单个通道sample数量
        ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);
        if (ret < 0) {
            fprintf(stderr, "Error while converting\n");
            goto end;
        }
        dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
                                                 ret, dst_sample_fmt, 1);
        if (dst_bufsize < 0) {
            fprintf(stderr, "Could not get sample buffer size\n");
            goto end;
        }
        printf("t:%f in:%d out:%d\n", t, src_nb_samples, ret);
        fwrite(dst_data[0], 1, dst_bufsize, dst_file);
    } while (t < 10);

    if ((ret = get_format_from_sample_fmt(&fmt, dst_sample_fmt)) < 0)
        goto end;
    fprintf(stderr, "Resampling succeeded. Play the output file with the command:\n"
            "ffplay -f %s -channel_layout %"PRId64" -channels %d -ar %d %s\n",
            fmt, dst_ch_layout, dst_nb_channels, dst_rate, dst_filename);

end:
    fclose(dst_file);

    if (src_data)
        av_freep(&src_data[0]);
    av_freep(&src_data);

    if (dst_data)
        av_freep(&dst_data[0]);
    av_freep(&dst_data);

    swr_free(&swr_ctx);
    return ret < 0;
}

踩坑记录:

如果pInputFrame->nb_samples>pOutputFrame->nb_samples,SwrContext中会有缓存,会导致内存一直暴涨。重采样后的音频结尾无声音。

没有缓存的重采样:
ret = swr_convert(swrcontext, pOutputFrame->data,pOutputFrame->nb_samples,
        (const uint8_t**)pInputFrame->data,pInputFrame->nb_samples);

有缓存的重采样:

//如果还有缓存在swrcontext中,第二个参数要填写0才能获取到,缓存数据
int fifo_size = swr_get_out_samples(swrcontext,0);
if ( fifo_size >= pOutputFrame->nb_samples)
{
    ret = swr_convert(swrcontext, pOutputFrame->data,pOutputFrame->nb_samples,
    NULL,0);
}
即如果有缓存则先判断是否有缓存在里面,如果有则传入数据为空取出缓存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步基

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值