ffmpeg解码音频planar模式和packed模式

转载:原文地址: FFmpeg连载4-音频解码-阿里云开发者社区ffmpeg连载系列icon-default.png?t=N7T8https://developer.aliyun.com/article/1197520

转载的,看到了,留着备份一下

导读

前面我们介绍了使用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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/327485.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++进阶】心心念念的红黑树,它来了!

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

C++多线程学习[三]:成员函数作为线程入口

一、成员函数作为线程入口 #include<iostream> #include<thread> #include<string>using namespace std;class Mythread { public:string str;void Test(){cout << str << endl;} }; int main() {Mythread test;test.str "Test";thr…

Mybatis的一些问答记录

0、delegate属性即装饰器,调用cache中的任意一个方法都会沿着链条往下依次执行。 1、JDBC的PreparedStatement(一次编译多次执行)每执行一次executeQuery()就会清空上一次的参数&#xff0c;执行完executeQuery()后就可以获取结果。 ReuseExecutor可重用执行器可以指定使用JDB…

105、Zero-1-to-3: Zero-shot One Image to 3D Object

简介 官网  使用合成数据集来学习相对摄像机视点的控制&#xff0c;这允许在指定的摄像机变换下生成相同对象的新图像&#xff0c;用于从单个图像进行三维重建的任务。 实现流程 输入图像 x ∈ R H W 3 x \in \R^{H \times W \times 3} x∈RHW3&#xff0c;所需视点的相…

无人机视角、多模态、模型剪枝、国产AI芯片部署

无人机视角、多模态、模型剪枝、国产AI芯片部署是当前无人机技术领域的重要研究方向&#xff0c;其原理和应用价值在以下几个方面进行详细讲述。 一、无人机视角&#xff1a;无人机视角是指在无人机上搭载摄像头等设备&#xff0c;通过航拍图像获取环境信息&#xff0c;并进行…

生产力与生产关系 —— 浅析爱泼斯坦事件 之 弱电控制强电原理

据网络文字与视频资料&#xff0c;爱泼斯坦事件是犹太精英阶层&#xff0c;为了掌控美国国家机器为犹太利益集团服务&#xff0c;而精心设下的一个局。本文先假设这个结论成立&#xff0c;并基于此展开讨论。 我们知道&#xff0c;弱电管理强电是电气工程中的一门专门学问&…

6 - 常用工具类

目录 1. Scanner 扫描控制台输入 1.1 扫描控制台输入 1&#xff09;nextLine 2&#xff09;nextInt 3&#xff09;其他方法 1.2 扫描文件 1.3 查找匹配项 2. Arrays 数组工具 2.1 创建数组 1&#xff09;copyOf 2&#xff09;copyOfRange 3&#xff09;fill 2.2 比…

GPT的版本发展历史及特点

版本介绍 GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一系列基于Transformer架构的预训练语言模型&#xff0c;由OpenAI推出。以下是GPT的版本发展、特点和区别&#xff1a; GPT-1 GPT-1是最早发布的版本&#xff0c;于2018年发布。它具有1.17亿个参数&…

【Spring 篇】走进SpringMVC的世界:舞动Web的激情

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇关于SpringMVC的博客&#xff0c;让我们一起探索这个舞动Web的框架&#xff0c;感受它带来的激情和便利。在这个世界里&#xff0c;我们将学到SpringMVC的概述、开发步骤以及如何快速入门&#xff0c;一切都是如此的令人兴奋…

科研绘图(五)玫瑰图

柱状图的高级平替可视化 “玫瑰图”&#xff0c;通常也被称为“科克斯图”。它类似于饼图&#xff0c;但不同之处在于每个部分&#xff08;或“花瓣”&#xff09;的角度相同&#xff0c;半径根据它表示的值而变化。这种可视化工具对于周期性地显示信息非常有用&#xff0c;比…

bash shell基础命令(一)

1.shell启动 shell提供了对Linux系统的交互式访问&#xff0c;通常在用户登录终端时启动。系统启动的shell程序取决于用户账户的配置。 /etc/passwd/文件包含了所有用户的基本信息配置&#xff0c; $ cat /etc/passwd root:x:0:0:root:/root:/bin/bash ...例如上述root账户信…

使用 Apache POI 更新/覆盖 特定的单元格

使用 Apache POI 更新特定的单元格 一. 需求二. 实现三. 效果 一. 需求 将以下表中第4行&#xff0c;第4列的单元格由“张宇”更新为“汤家凤”&#xff0c;并将更行后的结果写入新的Excel文件中&#xff1b; 二. 实现 使用Apache POI&#xff0c;可以精确定位到需要更改的单…

非递归实现归并排序

目录 非递归的归并排序 非递归的归并排序 实现流程参考图&#xff1a; 1、像递归实现归并排序一样&#xff0c;开辟n个空间大小的临时数组 2、利用变量gap模仿递归的过程&#xff0c;gap表示归并时的每组数据的个数 3、利用while循环实现归并&#xff0c;并且每一次我们要的…

鸿蒙开发笔记(三):页面和自定义组件生命周期

先明确自定义组件和页面的关系&#xff1a; 自定义组件&#xff1a;Component装饰的UI单元&#xff0c;可以组合多个系统组件实现UI的复用。 页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成&#xff0c;Entry装饰的自定义组件为页面的入口组件&#xff0c…

Linux环境基础开发工具的使用(下)

文章目录 Linux编译器 - gcc/ggcc/g如何使用预处理阶段编译阶段汇编阶段链接阶段gcc选项汇总静态库与动态库gdb命令汇总 Linux项目自动化构建工具 - make/Makefilemake/Makefile的意义使用make/makefile原理 Linux编译器 - gcc/g 背景知识 我们知道一个代码写完要变为可执行程…

OpenHarmony—编译构建指导

概述 OpenHarmony编译子系统是以GN和Ninja构建为基座&#xff0c;对构建和配置粒度进行部件化抽象、对内建模块进行功能增强、对业务模块进行功能扩展的系统&#xff0c;该系统提供以下基本功能&#xff1a; 以部件为最小粒度拼装产品和独立编译。支持轻量、小型、标准三种系…

大厂咋做支付系统的核对?

核对是保障资金安全的重要机制。 时效角度&#xff0c;主要有&#xff1a; &#xff08;准&#xff09;实时核对 准确性不如离线核对&#xff0c;且需相应实时核对平台建设 离线核对&#xff08;如 T1 核对&#xff09; 主要问题是发现问题的时机较为后置&#xff0c;部分场景…

微信小程序-----全局配置与页面配置

目录 前言 全局配置文件 一、window 1. 小程序窗口的组成部分 2. window 节点常用的配置项 3. 设置导航栏的标题 4. 设置导航栏的背景色 5. 设置导航栏的标题颜色 6. 全局开启下拉刷新功能 7. 设置下拉刷新时窗口的背景色 8. 设置下拉刷新时 loading 的样式 9. 设置…

蓝桥杯备赛 | 洛谷做题打卡day2

​ 蓝桥杯备赛 | 洛谷做题打卡day2 嵌套循环yyds&#xff01;&#xff01; 题目来源&#xff1a;洛谷P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n n n 行 m m m 列的雷区中有一些格子含有地雷&am…

跨域请求的API接口调用流程

在Web开发中&#xff0c;前端和后端相互通信是非常常见的需求。通常情况下&#xff0c;前端通过发送HTTP请求调用后端的API接口来获取数据或执行某些操作。然而&#xff0c;由于同源策略的限制&#xff0c;浏览器默认情况下不允许跨域请求&#xff0c;即前端不能直接从一个域名…
最新文章