FFmpeg连载4-音频解码

简介: ffmpeg连载系列

导读

前面我们介绍了使用FFmpeg解码视频,今天我们使用FFmpeg解码音频。我们的目标将mp4中的音频文件解码成PCM数据,并输出到本地文件,然后使用ffplay播放验证。

音频的解码过程就是将经过压缩后的数据重新还原成原始的PCM声音信号的过程。对于音频解码所用到的API和视频解码是一样的。

PCM基础知识

PCM是指未经过压缩的原始声音脉冲信号数据,它主要通过采样率、采样格式(比如每个采样点是8位、16位、32位等)、声道数来描述。

在FFmpeg中有两种表示PCM数据包的模式,分别是planer和packed模式,那么它们有什么区别呢?
其中packed又叫做交错模式,而planer又叫平面模式,所谓交错或平面就是不同声道的声音信号排列储存的方式,例如对于一个双声道的PCM数据来说,
用packed模式表示是这样子的:

// 我们用L表示左声道数据,用R表示右声道数据
LRLRLRLRLRLRLRLR

而用laner模式表示的话,则是这样子的:

// 我们用L表示左声道数据,用R表示右声道数据
LLLLLLLL RRRRRRRR

在FFmpeg中,packed模式的格式有:

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

它的数据只存在于AVFrame的data[0]中。

而planer模式一般是FFmpeg内部储存音频所使用的模式,例如通过一般planar模式的后面都有字母P标识,planar模式的格式有:

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

例如对于一帧planar格式的双声道的音频数据,AVFrame中的data[0]表示左声道的数据,data[1]表示的是右声道的数据。

在FFmpeg中我们可以使用函数av_sample_fmt_is_planar来判断采样格式是planar模式还是packed模式。

需要注意的一点是planar仅仅是FFmpeg内部使用的储存模式,我们实际中所使用的音频都是packed模式的,也就是说我们使用FFmpeg解码出音频PCM数据后,如果需要写入到输出文件,应该将其转为packed模式的输出。

我们可以使用ffplay播放PCM原始音频数据,命令是:

// -ar 表示采样率
// -ac 表示音频通道数
// -f 表示 pcm 格式,sample_fmts + le(小端)或者 be(大端)  f32le表示的是 AV_SAMPLE_FMT_FLTP 的小端模式
// sample_fmts可以通过ffplay -sample_fmts来查询
// -i 表示输入文件,这里就是 pcm 文件
ffplay -ar 44100 -ac 2 -f f32le -i pcm文件路径

音频解码

直接上代码吧,有注释:

class AudioDecoder {

public:
    AudioDecoder();

    ~AudioDecoder();

    void decode_audio(std::string media_path,std::string pcm_path);

};

以下是实现文件:

#include "AudioDecoder.h"

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}

AudioDecoder::AudioDecoder() {

}

AudioDecoder::~AudioDecoder() {

}

void AudioDecoder::decode_audio(std::string media_path, std::string pcm_path) {
    AVFormatContext *avFormatContext = avformat_alloc_context();
    avformat_open_input(&avFormatContext, media_path.c_str(), nullptr, nullptr);
    int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (audio_index < 0) {
        std::cout << "没有找到可用的音频流" << std::endl;
        // todo 如果找不到可以遍历 avFormatContext->streams的codec type是否是音频来再次寻找
    } else {
        // 打印媒体信息
        av_dump_format(avFormatContext, 0, media_path.c_str(), 0);

        // 初始化解码器相关
        const AVCodec *audio_codec = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id);
        if(nullptr == audio_codec){
            std::cout << "没找到对应的解码器:"  << std::endl;
            return;
        }
        AVCodecContext *codec_ctx = avcodec_alloc_context3(audio_codec);
        // 如果不加这个可能会 报错Invalid data found when processing input
        avcodec_parameters_to_context(codec_ctx,avFormatContext->streams[audio_index]->codecpar);

        // 打开解码器
        int ret = avcodec_open2(codec_ctx, audio_codec, NULL);
        if (ret < 0) {
            std::cout << "解码器打开失败:"  << std::endl;
            return;
        }
        // 初始化包和帧数据结构
        AVPacket *avPacket = av_packet_alloc();
        av_init_packet(avPacket);

        AVFrame *frame = av_frame_alloc();


        std::cout << "sample_fmt:"  << codec_ctx->sample_fmt << std::endl;
        std::cout << "AV_SAMPLE_FMT_U8:"  << AV_SAMPLE_FMT_U8 << std::endl;
        std::cout << "采样率sample_fmt:"  << codec_ctx->sample_fmt << std::endl;

        FILE *audio_pcm = fopen(pcm_path.c_str(), "wb");
        while (true) {
            ret = av_read_frame(avFormatContext, avPacket);
            if (ret < 0) {
                std::cout << "音频读取完毕" << std::endl;
                break;
            } else if(audio_index == avPacket->stream_index){ // 过滤音频
                ret = avcodec_send_packet(codec_ctx, avPacket);
                if(ret == AVERROR(EAGAIN)) {
                    std::cout << "发送解码EAGAIN:" << std::endl;
                } else if(ret < 0) {
                    char error[1024];
                    av_strerror(ret,error,1024);
                        std::cout << "发送解码失败:"  << error << std::endl;
                        return;
                }
                while (true) {
                    ret = avcodec_receive_frame(codec_ctx, frame);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                        break;
                    } else if (ret < 0) {
                        std::cout << "音频解码失败:" << std::endl;
                        return;
                    }
                    // 每帧音频数据量的大小
                    int data_size = av_get_bytes_per_sample(codec_ctx->sample_fmt);
                    /**
                     * P表示Planar(平面),其数据格式排列方式为 :
                       LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
                       而不带P的数据格式(即交错排列)排列方式为:
                       LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
                       播放范例:   ffplay -ar 44100 -ac 2 -f f32le pcm文件路径
                       并不是每一种都是这样的格式
                     */

                    /**
                     * ffplay -ar 44100 -ac 2 -f f32le -i pcm文件路径
                        -ar 表示采样率
                        -ac 表示音频通道数
                        -f 表示 pcm 格式,sample_fmts + le(小端)或者 be(大端)
                        sample_fmts可以通过ffplay -sample_fmts来查询
                        -i 表示输入文件,这里就是 pcm 文件
                     *
                     */
                    const char *fmt_name = av_get_sample_fmt_name(codec_ctx->sample_fmt);
                    AVSampleFormat pack_fmt = av_get_packed_sample_fmt(codec_ctx->sample_fmt);
                    std::cout << "fmt_name:" << fmt_name << std::endl;
                    std::cout << "pack_fmt:" << pack_fmt << std::endl;
                    std::cout << "frame->format:" << frame->format << std::endl;
                    if (av_sample_fmt_is_planar(codec_ctx->sample_fmt)) {
                        std::cout << "pcm planar模式" << std::endl;
                        for (int i = 0; i < frame->nb_samples; i++) {
                            for (int ch = 0; ch < codec_ctx->channels; ch++) {
                                // 需要储存为pack模式
                                fwrite(frame->data[ch] + data_size * i, 1, data_size, audio_pcm);
                            }
                        }
                    } else {
                        std::cout << "pcm Pack模式" << std::endl;
                        fwrite(frame->data[0], 1, frame->linesize[0], audio_pcm);
                    }
                }
            } else{
                av_packet_unref(avPacket); // 减少引用计数
            }
        }
    }
}

todo

析构函数释放资源,时间篇幅问题,就不写了。。。

推荐阅读

FFmpeg连载1-开发环境搭建
FFmpeg连载2-分离视频和音频
FFmpeg连载3-视频解码

关注我,一起进步,人生不止coding!!!

目录
相关文章
|
7月前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
124 0
|
2月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
82 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
5月前
|
数据采集 大数据 Python
FFmpeg 在爬虫中的应用案例:流数据解码详解
在大数据背景下,网络爬虫与FFmpeg结合,高效采集小红书短视频。需准备FFmpeg、Python及库如Requests和BeautifulSoup。通过设置User-Agent、Cookie及代理IP增强隐蔽性,解析HTML提取视频链接,利用FFmpeg下载并解码视频流。示例代码展示完整流程,强调代理IP对避免封禁的关键作用,助你掌握视频数据采集技巧。
FFmpeg 在爬虫中的应用案例:流数据解码详解
|
5月前
|
语音技术 C语言 Windows
语音识别------ffmpeg的使用01,ffmpeg的安装,会做PPT很好,ffmpeg不具备直接使用,只可以操作解码数据,ffmpeg用C语言写的,得学C语言,ffmpeg的安装
语音识别------ffmpeg的使用01,ffmpeg的安装,会做PPT很好,ffmpeg不具备直接使用,只可以操作解码数据,ffmpeg用C语言写的,得学C语言,ffmpeg的安装
|
6月前
|
Linux 编解码 Python
FFmpeg开发笔记(二十四)Linux环境给FFmpeg集成AV1的编解码器
AV1是一种高效免费的视频编码标准,由AOM联盟制定,相比H.265压缩率提升约27%。各大流媒体平台倾向使用AV1。本文介绍了如何在Linux环境下为FFmpeg集成AV1编解码库libaom、libdav1d和libsvtav1。涉及下载源码、配置、编译和安装步骤,包括设置环境变量以启用这三个库。
307 3
FFmpeg开发笔记(二十四)Linux环境给FFmpeg集成AV1的编解码器
|
6月前
|
Java Linux
ffmpeg音频格式转换、合成、速率调整
ffmpeg音频格式转换、合成、速率调整
126 2
|
7月前
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
《FFmpeg开发实战》一书中,第10章示例程序playaudio.c原本仅支持mp3和aac音频播放。为支持ogg、amr、wma等非固定帧率音频,需进行三处修改:1)当frame_size为0时,将输出采样数量设为512;2)遍历音频帧时,计算实际采样位数以确定播放数据大小;3)在SDL音频回调函数中,确保每次发送len字节数据。改进后的代码在chapter10/playaudio2.c,可编译运行播放ring.ogg测试,成功则显示日志并播放铃声。
129 1
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
|
7月前
|
缓存 编解码
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
FFmpeg在视频流重编码和音频重采样中使用缓存机制。在音频文件格式转换时,特别是对于帧长度不固定的格式如ogg、amr、wma,需处理重采样缓存。通过调用`swr_convert`,传入空输入和0大小来清空缓存。在`swrmp3.c`中,修改帧样本数处理,并在循环结束后添加代码以冲刷缓存。编译并运行程序,将ogg文件重采样为MP3,日志显示操作成功,播放转换后的文件确认功能正常。
154 7
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
|
6月前
|
编解码 Python
音频剪裁大师:使用 Python 和 ffmpeg 分割音频的完整指南
使用 Python 和 ffmpeg 进行音频文件分割。通过 `subprocess` 模块调用 ffmpeg 命令,定义 `split_audio` 函数,输入参数包括音频文件、起始时间、持续时间和输出文件名。函数构建命令行指令进行分割,然后执行。运行脚本,即可按指定时间从音频中提取片段。简单易用,适用于多种音频处理场景。
|
7月前
|
编解码 5G Linux
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器
AVS3是中国首个8K及5G视频编码标准,相比AVS2和HEVC性能提升约30%。解码器libuavs3d支持8K/60P视频实时解码,兼容多种平台。《FFmpeg开发实战》书中介绍了在Windows环境下如何集成libuavs3d到FFmpeg。集成步骤包括下载源码、使用Visual Studio 2022编译、调整配置、安装库文件和头文件,以及重新配置和编译FFmpeg以启用libuavs3d。
123 0
FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器