导读
后面咱们介绍了应用 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!!!