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 inputavcodec_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.mzph.cn/news/612040.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 文本内容随机生成器

这段代码是一个用于生成指定长度的随机文本的函数。主要包括两个函数&#xff1a;generate_text()和generate_other_content()。 generate_text(original_text, length)函数接受两个参数&#xff1a;原始文本和生成文本的长度。该函数的作用是根据原始文本生成指定长度的文本。…

Java异常及网络编程

异常续 throws关键字 当一个方法中使用throw抛出一个非RuntimeException的异常时&#xff0c;就要在该方法上使用throws声明这个异常的抛出。此时调用该方法的代码就必须处理这个异常&#xff0c;否则编译不通过。 package exception; ​ /*** 测试异常的抛出*/ public clas…

【普中开发板】基于51单片机的简易密码锁设计( proteus仿真+程序+设计报告+讲解视频)

基于51单片机的简易密码锁设计 1.主要功能&#xff1a;资料下载链接&#xff1a; 实物图&#xff1a;2.仿真3. 程序代码4. 设计报告5. 设计资料内容清单 【普中】基于51单片机的简易密码锁设计 ( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus8.16(有低版本) 程…

vue知识-04

计算属性computed 注意&#xff1a; 1、计算属性是基于它们的依赖进行缓存的 2、计算属性只有在它的相关依赖发生改变时才会重新求值 3、计算属性就像Python中的property&#xff0c;可以把方法/函数伪装成属性 4、computed: { ... } 5、计算属性必须要有…

css的一些属性

我们在写项目的时候&#xff0c;会遇到多种多样的样式&#xff0c;大部分都是由css来实现的&#xff0c;css可以让我们的页面更美观&#xff0c;css通常是配合HTML使用&#xff0c;代码较为简单! 下面我就给大家举几个较为常用的一些css属性。 1.CSS中怎样让元素圆角化&#…

【从零开始学习微服务 | 第一篇】什么是微服务

目录 前言&#xff1a; 架构风格&#xff1a; 单体架构&#xff1a; 分布式架构&#xff1a; 微服务&#xff1a; 总结&#xff1a; 前言&#xff1a; 在当今快速发展的软件开发领域&#xff0c;构建大型应用程序已经成为一项巨大的挑战。传统的单体应用架构往往难以满足…

未完成销量任务的智己汽车突发大规模车机故障,竞争压力不小

2024年刚开年&#xff0c;智己汽车便上演了一出“开门黑”。 近日&#xff0c;不少车主在社交平台发帖&#xff0c;反映智己LS6出现大规模车机故障&#xff0c;包括但不限于主驾驶屏幕不显示车速、档位、行驶里程&#xff0c;左右转盲区显示失效&#xff0c;无转向灯、雷达提醒…

CSS-设置背景图片的大小

要设置背景图片的大小&#xff0c;您可以使用CSS的background-size属性。这个属性允许您指定背景图片的尺寸。 background-size属性可以接受不同的值&#xff0c;包括&#xff1a; auto&#xff1a;保持原始图片的尺寸。cover&#xff1a;将图片缩放到完全覆盖背景区域&#…

ThreadLocal如何使用详解

ThreadLocal概述&#xff1a; ThreadLocal是Java中的一个线程局部变量工具类&#xff0c;它提供了一种在多线程环境下&#xff0c;每个线程都可以独立访问自己的变量副本的机制。ThreadLocal中存储的数据对于每个线程来说都是独立的&#xff0c;互不干扰。 使用场景&#xff1a…

Linux最常用的几个系统管理命令

文章目录 Linux最常用的几个系统管理命令查看网络信息的原初 ifconfig默认无参数使用-s显示短列表配置IP地址修改MTU启动关闭网卡 显示进程状态 ps语法几个实例默认情况显示所有进程查找特定进程信息 任务管理器的 top常规使用显示完整命令设置信息更新次数设置信息更新时间显示…

树莓派非常实用的程序-2 vcgencmd

vcgencmd 工具用于从Raspberry Pi上的VideoCore GPU输出信息。您可以在 https://github.com/raspberrypi/userland/tree/master/host_applications/linux/apps/gencmd[Github].上找到 vcgencmd 实用程序的源代码。要获取支持的所有 vcgencmd 命令的列表&#xff0c;请使用 vcge…

vbs读取数据库值前端FlexGrid前导0出不来的原因

vbs读取数据库值前端FlexGrid前导0出不来的原因 原因 系统设置问题 解决 修改系统默认数值显示&#xff1a; 1&#xff09;控制面板找到“区域”&#xff0c;点击“更改日期、时间和数字模式”&#xff0c;在弹出窗口点击“其他设置” 2&#xff09;在数字一栏中的“显示前…

AirBrush - AI 照片编辑器

​【应用名称】&#xff1a;AirBrush - AI 照片编辑器 ​【适用平台】&#xff1a;#Android ​【软件标签】&#xff1a;#AirBrush ​【应用版本】&#xff1a;6.0.1 ​【应用大小】&#xff1a;270MB ​【软件说明】&#xff1a;谁说我们的照片不能完美&#xff1f;我们相信…

前端要了解的k8s、CI/CD、Devops概念

1&#xff0c;了解k8s 简单的理解&#xff0c;k8s就是docker容器集群的管理工具。他将容器进行更多自动化的操作&#xff0c;自动创建、自动重启、自动扩容等&#xff0c;这个过程称为容器编排。 k8s抽象了硬件资源&#xff0c;将N台物理机或云主机抽象成一个资源池&#xff…

Hello 2024

Hello 2024 A. Wallet Exchange 题意&#xff1a;Alice和Bob各有a和b枚硬币&#xff0c;每次他们可以选择交换硬币或者保留&#xff0c;然后扣除当前一枚手中的硬币&#xff0c;当一方没得扣另一方就赢了。 思路&#xff1a;Alice先手&#xff0c;所以当硬币和为奇数时Alice…

java-Exchanger详解

1.概述 java.util.concurrent.Exchanger。这在Java中作为两个线程之间交换对象的公共点。 2.Exchanger简介 Exchanger类可用于在两个类型为T的线程之间共享对象。该类仅提供了一个重载的方法exchange(T t)。 当调用exchanger时&#xff0c;它会等待成对的另一个线程也调用它…

安装pillow遇到的问题

文章目录 引言简介目的 安装Pillow基本步骤 常见问题及其解决方案1. 编译依赖不足描述解决方案 2. 权限问题描述解决方案 3. 版本冲突描述解决方案 4. 安装在错误的Python版本上描述解决方案 5. 操作系统特定的问题描述解决方案 总结 引言 简介 Pillow库是Python的一个开源库…

大学生如何当一个程序员——第三篇:职场软实力

职场软实力 1.职场软实力是什么&#xff1f;2.形象气质和社交礼仪3.声音素质4.情商5.沟通力6.说服力7.说服力之销售8.演讲力9.领导力 文章出自https://www.bjsxt.com/xiulian.html#1F 各位小伙伴想要博客相关资料的话关注公众号&#xff1a;chuanyeTry即可领取相关资料&#xf…

java 中类库的根类 Object 与 toString() 和 equals() 方法

JDK 类库的根类&#xff1a;Object 1、这个根类中的方法我们需要先研究一下&#xff0c;因为这些方法都是所有子类通用的。 任何一个类默认继承Object。就算没有直接继承&#xff0c;最终也会间接继承。 2、Object 类当中有哪些常用的方法&#xff1f; 我们去哪里找这些方法呢&…

Centos7升级openssl到openssl1.1.1

Centos7升级openssl到openssl1.1.1 1、先查看openssl版本&#xff1a;openssl version 2、Centos7升级openssl到openssl1.1.1 升级步骤 #1、更新所有现有的软件包列表并安装最新的软件包&#xff1a; $sudo yum update #2、接下来&#xff0c;我们需要从源代码编译和构建OpenS…