前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android FFmpeg系列05--音频解码与播放

Android FFmpeg系列05--音频解码与播放

作者头像
雪月清
发布2022-09-21 14:55:12
1.2K0
发布2022-09-21 14:55:12
举报
文章被收录于专栏:雪月清的随笔雪月清的随笔

引言

在前面的连载系列中,我们分别用FFmpeg的软解和硬解两种方式解码了本地mp4文件的视频流并使用OpenGL渲染上屏

Android FFmpeg系列03--视频解码与渲染

Android FFmpeg系列04--FFmpeg调用MediaCodec进行硬解码

本篇文章将通过音频基础AudioTrackFFmpeg音频解码&重采样三个部分的讲解来完成对Demo中mp4文件内音频流的解码与播放功能

(48kHZ,双声道,fltp格式)

音频基础

关于音频采样率、声道、采样位数等基础可以参考Android FFmpeg系列02--音视频基础

重采样

音频重采样就是通过改变音频的采样率、采样格式、声道数等参数使之按照我们期望的音频参数输出音频数据的过程

为什么需要重采样?

因为音频文件的音频参数是多种多样的,而播放音频的设备不一定支持这些参数,这就需要通过重采样进行转换后才能正常播放;另外比如说我们需要对多段音频进行mix,需要首先确保每段音频具有相同的采样率、采样格式和声道数,这个时候也需要进行重采样

FFmpeg中的音频采样格式

FFmpeg中的音频采样格式分为两种,以P结尾的planar格式和不带P结尾的packed格式

代码语言:javascript
复制
enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16, ///< signed 16 bits
    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
    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
};

以双声道为例,planar格式在存储时,左右声道的数据分开存储,左声道在data[0],右声道数据在data[1],每个声道所占用的字节数为linesize[0]和linesize[1]

packed格式则按照LRLRLR...的格式交错存储在data[0]中,总的数据量为linesize[0]

eg:双声道的AV_SAMPLE_FMT_S16和AV_SAMPLE_FMT_FLTP

FFmpeg音频帧中的nb_samples字段

AVFrame中的nb_samples字段表示音频数据每个通道的采样数量,它与具体的码流类型和编码级别有关

nb_samples和AVCodecContext中的frame_size相同

音频帧的数据量计算

代码语言:javascript
复制
// size = nb_samples * channels * bytes_per_sample
// 以双声道,AV_SAMPLE_FMT_S16格式(2字节)为例
// AAC(nb_samples = 1024),
size = 1024 * 2 * 2 = 4096字节

// MP3(nb_samples = 1152)
size = 1152 * 2 * 2 = 4608字节

音频帧的播放时间计算

代码语言:javascript
复制
// duration = nb_samples / sample_rate
// 以采样率为44100HZ为例
// AAC(nb_samples = 1024),
duration = 1024 / 44100 = 0.02322s = 23.22ms

// MP3(nb_samples = 1152)
duration = 1152 / 44100 = 0.02612s = 26.12ms

AudioTrack

AudioTrack因为不创建解码器,所以只能用于PCM数据的播放或者播放wav文件,它提供两种播放模式

  • MODE_STATIC:预先将待播放的音频数据全部写入内存,然后进行播放
  • MODE_STREAM:边写入边播放

我们现在的场景是通过FFmpeg实时解码出音频PCM数据并播放,所以选择stream模式

创建AudioTrack

这里我们固定音频参数为双声道,采样率为44100HZ,采样格式为ENCODING_PCM_16BIT

代码语言:javascript
复制
// 计算最小buffer size
val bufferSize = AudioTrack.getMinBufferSize(
    44100, 
    AudioFormat.CHANNEL_OUT_STEREO, 
    AudioFormat.ENCODING_PCM_16BIT
)
// 创建AudioTrack实例
mAudioTrack = AudioTrack(
    AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build(),
    AudioFormat.Builder().
        setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
        .setSampleRate(44100)
        .build(),
    bufferSize, 
    AudioTrack.MODE_STREAM,
    AudioManager.AUDIO_SESSION_ID_GENERATE
)

播放

代码语言:javascript
复制
mAudioTrack!!.play()

实时写入数据

代码语言:javascript
复制
mAudioTrack?.write(audio, 0, size)

释放

代码语言:javascript
复制
mAudioTrack?.stop()
mAudioTrack?.release()

FFmpeg音频解码&重采样

音频解码的步骤和视频解码步骤是类似的

解封装&找到音频流index -> 打开解码器 -> 循环解码&重采样 -> 解码结束释放相关资源

详细的解码代码就不贴了,可以查看源码中的AudioDecoder.cpp

这里重点说说重采样的过程

初始化重采样上下文

代码语言:javascript
复制
mSwrContext = swr_alloc_set_opts(
    nullptr,
    AV_CH_LAYOUT_STEREO, // 双声道
    AV_SAMPLE_FMT_S16, // 对应到AudioTrack的AudioFormat.ENCODING_PCM_16BIT
    44100,

    mAvFrame->channel_layout, 
    AVSampleFormat(mAvFrame->format), // format = AV_SAMPLE_FMT_FLTP
    mAvFrame->sample_rate, // 48000HZ
    0,
    nullptr
);
swr_init(mSwrContext);

重采样

代码语言:javascript
复制
// 重采样后的nb_samples
int out_nb = (int) av_rescale_rnd(mAvFrame->nb_samples, 44100, mAvFrame->sample_rate, AV_ROUND_UP);

// 重采样后的channels数
int out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);

// 计算重采样后每帧的size
int size = av_samples_get_buffer_size(nullptr, out_channels, out_nb, AV_SAMPLE_FMT_S16, 1);

// 初始化重采样后的数据buffer
if (mAudioBuffer == nullptr) {
    mAudioBuffer = (uint8_t *) av_malloc(size); 
}

// 重采样
int nb = swr_convert(
    mSwrContext,
    &mAudioBuffer,
    size / out_channels, // 每个通道的samples size
    (const uint8_t**)mAvFrame->data,
    mAvFrame->nb_samples
);

mDataSize = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);

上面的mAudioBuffer和mDataSize就是透过JNI传给AudioTrack#write的数据,一些细节可以查看提供的Demo源码

https://github.com/sifutang/ffmpeg-demo

~~END~~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 雪月清的随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档