首先一个完整的MP4文件解封装之后,得到了压缩的音频数据,这个数据是不能直接拿去播放的,我们需要解码成原始的PCM数据才能够播放,解码音频数据,如下图所示,把MP3或者AAC数据解码成原始的数据pcm。
音频解码是将编码的音频数据(如MP3, AAC, OGG等格式)转换为可以播放的PCM(脉冲编码调制)数据的过程。这个过程通常涉及以下步骤:
-
解封装(Demuxing):
- 从容器格式(如MP4, MKV, AVI等)中分离出音频数据流。
- 读取音频流的元数据,包括编码类型、采样率、通道数、比特率等。
-
解码准备:
- 初始化解码器。找到与音频流匹配的解码器(例如:libmp3lame解码MP3数据流)。
- 打开解码器,准备开始解码。
-
循环解码:
- 从分离出的音频数据流中读取编码的音频数据包(packet)。
- 将编码的数据包发送到解码器进行解码。
- 从解码器中接收解码后的帧数据(解码器可能需要多个数据包才能生成一个完整的帧)。
-
帧处理:
- 将解码出来的帧(PCM数据)进行可能的后处理,例如重采样(如果需要改变采样率)、声道转换(比如立体声到单声道)、音量调整等。
- 处理后的帧数据准备播放或进一步处理。
-
同步和播放:
- 如果需要与视频同步,采取相应的机制确保音频和视频能够同步播出。
- 将解码、处理后的音频数据送至音频输出设备播放。
-
流的结束:
- 处理音频流的结束,这可能涉及刷新解码器以输出最后几帧,关闭解码器和清理资源。
ffmpeg解码音频的数据步骤:首先前面的解封装步骤不能少。
Qt+FFmpeg+opengl从零制作视频播放器-3.解封装
查找解码器,根据音频流的codec_id找到解码器。
cpp
//找到解码器
AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
申请AVCodecContenxt上下文。
cpp
//申请AVCodecContext
AVCodecContext* m_pCodecCtx= nullptr;
m_pCodecCtx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
exit(1);
}
配置解码器上下文参数。
cpp
///配置解码器上下文参数
avcodec_parameters_to_context(m_pCodecCtx, para);
打开解码器。
cpp
int ret = avcodec_open2(m_pCodecCtx, 0, 0);
if (ret != 0)
{
avcodec_free_context(&m_pCodecCtx);
cout << "avcodec_open2 failed! :" << buf << endl;
}
然后通过while循环,不停的读取数据,解码。
cpp
av_read_frame(inputFmtCtx, pkt)
avcodec_send_packet(m_pCodecCtx, pkt);
avcodec_receive_frame(m_pCodecCtx, frame);
执行结束后关闭输入文件,释放资源。
cpp
//关闭
avformat_close_input(&inputFmtCtx);
//释放
avformat_free_context(inputFmtCtx);
//释放资源
av_packet_free(&pkt);
AVCodecParameters 用于保存音视频流的基本参数信息,音频相关的成员变量解析:
- codec_type:这是一个枚举类型AVMediaType,用于指定编解码器的类型。对于音频来说,可以是AVMEDIA_TYPE_AUDIO,表示音频数据。
- codec_id:这个枚举类型的成员变量指定了编码格式,例如MP3、AAC等音频编码格式。
- format:这个成员变量对于音频来说指的是采样格式,如16位PCM、32位浮点等。
- channels:这个成员变量表示音频的通道数,即单声道、立体声或多声道等。
- sample_rate:这个成员变量表示音频的采样率,即每秒钟采样的次数,通常以Hz为单位。
- channel_layout:这个成员变量指定了音频通道的布局,如立体声、环绕声等。
cpp
int ret = avformat_open_input(&m_pFormatCtx, url, NULL, &opts);
...
...
...
m_sampleRate = m_pFormatCtx->streams[m_audioIndex]->codec->sample_rate;
m_channels = m_pFormatCtx->streams[m_audioIndex]->codec->channels;
m_aTimeBase = r2d(m_pFormatCtx->streams[m_audioIndex]->time_base);
cout << "=======================================================" << endl;
cout << m_audioIndex << " audio info" << endl;
cout << "codec_id = " << m_pFormatCtx->streams[m_audioIndex]->codecpar->codec_id << endl;
cout << "format = " << m_pFormatCtx->streams[m_audioIndex]->codecpar->format << endl;
cout << "sample_rate = " << m_sampleRate << endl;
cout << "channels = " << m_channels << endl;
cout << "=======================================================" << endl;
源码示例:保存音频前200帧数据。
cpp
#include <QtCore/QCoreApplication>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//av_register_all();
avformat_network_init();
AVFormatContext* ifmt_ctx = NULL;
const char* inputUrl = "F:/1920x1080.mp4";
///打开输入的流
int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
//查找流信息
if (avformat_find_stream_info(ifmt_ctx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
//找到音频流索引
int audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
AVStream* st = ifmt_ctx->streams[audio_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
//申请AVCodecContext
AVCodecContext* codec_ctx = nullptr;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
exit(1);
}
avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[audio_index]->codecpar);
//打开解码器
if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
{
return -1;
}
AVPacket* pkt = av_packet_alloc();
//av_init_packet(pkt);
AVFrame *frame = av_frame_alloc();
char fileName[20] = "test.pcm";
//保存pcm文件
FILE* f;
f = fopen(fileName, "wb");
static int frameCount = 0;
//不断读取数据
while (av_read_frame(ifmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == audio_index)
{
int ret = avcodec_send_packet(codec_ctx, pkt);
if (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
continue;
}
else if (ret < 0)
{
continue;
}
//保存200帧数据
if(frameCount >= 200)
break;
//获取数据大小
int data_size = av_get_bytes_per_sample(codec_ctx->sample_fmt);
if (data_size < 0)
{
continue;
}
for (int i = 0; i < frame->nb_samples; i++)
{
for (int ch = 0; ch < codec_ctx->channels; ch++)
{
fwrite(frame->data[ch] + data_size * i, 1, data_size, f);
}
}
}
frameCount++;
}
}
fclose(f);
printf("write finished\n");
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
avformat_close_input(&ifmt_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
return a.exec();
}
使用pcm数据工具,用于播放pcm文件。
pcm工具pcm工具pcm工具-C++文档类资源-CSDN下载
使用pcm工具播放 保存好的pcm文件。
选择导入原始数据,点击Detect按钮,自动获取pcm的格式。
FFmpeg是一个多功能的多媒体处理工具,它用于转换、编码、解码、转码等多种任务。以下是一些常用的FFmpeg命令:
获取视频信息:`ffmpeg -i [输入文件名]`来获取视频的详细信息。
视频格式转换:例如,将MP4格式的视频转换为FLV格式,可以使用命令`ffmpeg -i input.mp4 -f flv output.flv`。
音频格式转换:将MP3格式的音频转换为PCM格式,可以使用命令`ffmpeg -i input.mp3 -f s16be -ar 16000 -ac 1 -acodec pcm_s16be output.pcm`。
音视频分离:使用命令`ffmpeg -i input.mp4 -vcodec copy -an output.mp4`来去除音频,只保留视频。
截取视频:使用命令`ffmpeg -i input.mp4 -ss 8 -t 2 -s 1280x720 -codec copy -f flv output.flv`来截取一段视频。
音视频同步:使用命令`ffmpeg -i input.mp4 -ss 8 -t 2 -i input.aac -c copy -map 0:v:0 -map 1:a:0 output.mp4`来将视频和音频同步。
音视频编码:使用命令`ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast -crf 22 -c:a aac -b:a 128k output.mp4`来对视频和音频进行编码。
完整工程: