导读

后面咱们介绍了应用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 bitsAV_SAMPLE_FMT_S16,         ///< signed 16 bitsAV_SAMPLE_FMT_S32,         ///< signed 32 bitsAV_SAMPLE_FMT_FLT,         ///< floatAV_SAMPLE_FMT_DBL,         ///< double

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

而planer模式个别是FFmpeg外部贮存音频所应用的模式,例如通过个别planar模式的前面都有字母P标识,planar模式的格局有:

AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planarAV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planarAV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planarAV_SAMPLE_FMT_FLTP,        ///< float, planarAV_SAMPLE_FMT_DBLP,        ///< double, planarAV_SAMPLE_FMT_S64,         ///< signed 64 bitsAV_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!!!