音频解码
一、初始化阶段
- avformat_open_input
打开输入媒体文件。
- avformat_find_stream_info
读取媒体流信息,查找音频流。
- avcodec_find_decoder
查找对应的解码器(如 AAC、MP3 解码器)。
- avcodec_alloc_context3
分配解码器上下文。
- avcodec_open2
打开解码器。
- av_packet_alloc
分配 AVPacket
用于读取压缩数据。
- av_frame_alloc
分配 AVFrame
用于接收解码后的原始音频帧。
二、解码循环阶段
- av_read_frame
从输入文件中读取一个压缩包(Packet)。
- 读取判断
- 如果读取成功(true):
- avcodec_send_packet:将压缩数据送入解码器。
- avcodec_receive_frame:从解码器取出解码后的音频帧。
- 写文件:将解码后的音频帧保存或播放处理。
- 如果读取失败(false):
cpp
复制代码
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
int main()
{
const char inFileName[] = "output.aac"; // 输入文件名(AAC编码的音频)
const char outFileName[] = "test.pcm"; // 输出文件名(解码后的PCM原始音频)
// 打开输出文件,用于写入PCM数据
FILE *file = fopen(outFileName, "wb+");
if (!file) {
qDebug() << "Cannot open output file";
return -1;
}
AVFormatContext *fmtCtx = avformat_alloc_context(); // 格式上下文
AVCodecContext *codecCtx = NULL; // 编码器上下文
AVPacket *pkt = av_packet_alloc(); // 用于读取压缩数据包
AVFrame *frame = av_frame_alloc(); // 用于存储解码后的音频帧
int aStreamIndex = -1; // 音频流索引
do {
// 打开输入音频文件
if (avformat_open_input(&fmtCtx, inFileName, NULL, NULL) < 0) {
qDebug() << "Cannot open input file";
return -1;
}
// 获取流信息
if (avformat_find_stream_info(fmtCtx, NULL) < 0) {
qDebug() << "Cannot find any stream in file";
return -1;
}
// 查找音频流索引
for (size_t i = 0; i < fmtCtx->nb_streams; i++) {
if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
aStreamIndex = (int)i;
break;
}
}
if (aStreamIndex == -1) {
qDebug() << "Cannot find audio stream";
return -1;
}
// 获取音频编解码参数
AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
// 查找解码器
const AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
if (!codec) {
qDebug() << "Cannot find any codec for audio";
return -1;
}
// 分配解码器上下文
codecCtx = avcodec_alloc_context3(codec);
// 使用 codecpar 初始化 codecCtx
if (avcodec_parameters_to_context(codecCtx, aCodecPara) < 0) {
qDebug() << "Cannot alloc codec context";
return -1;
}
// 设置时间基(用于同步处理)
codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;
// 打开解码器
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
qDebug() << "Cannot open audio codec";
return -1;
}
// ========== 解码循环 ==========
while (av_read_frame(fmtCtx, pkt) >= 0) {
if (pkt->stream_index == aStreamIndex) {
// 向解码器送入压缩数据包
if (avcodec_send_packet(codecCtx, pkt) >= 0) {
// 不断接收解码后的音频帧
while (avcodec_receive_frame(codecCtx, frame) >= 0) {
// 判断是否为平面格式(如FLTP)
if (av_sample_fmt_is_planar(codecCtx->sample_fmt)) {
int numBytes = av_get_bytes_per_sample(codecCtx->sample_fmt);
// PCM播放需要交错格式(LRLRLR...),手动交错保存
for (int i = 0; i < frame->nb_samples; i++) {
for (int ch = 0; ch < codecCtx->channels; ch++) {
fwrite((char*)frame->data[ch] + numBytes * i, 1, numBytes, file);
}
}
}
// 非平面格式可以直接 fwrite(frame->data[0], ...)
}
}
}
av_packet_unref(pkt); // 清空包,为下一次读取做准备
}
} while (0);
// 释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_close(codecCtx);
avcodec_free_context(&codecCtx);
avformat_free_context(fmtCtx);
fclose(file);
return 0;
}
视频解码
cpp
复制代码
int main(int argc, char *argv[]) {
const char *inputFileName = "C:\\Users\\edoYun\\Videos\\output.h264"; // 输入文件路径
const char *outputFileName = "out.yuv"; // 输出文件路径
// 打开输入文件
AVFormatContext *formatCtx = avformat_alloc_context(); // 创建AVFormatContext对象
if (avformat_open_input(&formatCtx, inputFileName, NULL, NULL) != 0) {
qDebug() << "Error: could not open input file"; // 如果打开文件失败,输出错误信息
return -1;
}
// 获取流信息
if (avformat_find_stream_info(formatCtx, NULL) < 0) {
qDebug() << "Error: could not find stream information"; // 如果找不到流信息,输出错误信息
avformat_close_input(&formatCtx); // 关闭输入文件
return -1;
}
// 查找视频流
int videoStream = -1; // 初始化视频流索引
for (int i = 0; i < formatCtx->nb_streams; i++) { // 遍历所有流
if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // 找到视频流
videoStream = i;
break;
}
}
if (videoStream == -1) { // 如果没有找到视频流,输出错误信息
qDebug() << "Error: could not find video stream";
avformat_close_input(&formatCtx); // 关闭输入文件
return -1;
}
// 获取视频解码器
AVCodecParameters *codecParams = formatCtx->streams[videoStream]->codecpar; // 获取视频流的解码参数
const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id); // 根据codec_id查找对应的解码器
if (!codec) {
qDebug() << "Error: unsupported codec"; // 如果找不到解码器,输出错误信息
avformat_close_input(&formatCtx); // 关闭输入文件
return -1;
}
// 为解码器创建上下文
AVCodecContext *codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx, codecParams); // 将解码参数转换为上下文
if (avcodec_open2(codecCtx, codec, NULL) < 0) { // 打开解码器
qDebug() << "Error: could not open codec"; // 如果解码器打开失败,输出错误信息
avformat_close_input(&formatCtx); // 关闭输入文件
return -1;
}
// 打开输出文件
FILE *outputFile = fopen(outputFileName, "wb"); // 以二进制方式打开输出文件
if (!outputFile) {
qDebug() << "Error: could not open output file"; // 如果打开输出文件失败,输出错误信息
avcodec_close(codecCtx); // 关闭解码器
avformat_close_input(&formatCtx); // 关闭输入文件
return -1;
}
// 解码并输出 YUV420 文件
AVFrame *frame = av_frame_alloc(); // 创建一个AVFrame来保存解码后的帧数据
AVPacket packet; // 存放压缩数据包
av_init_packet(&packet); // 初始化数据包
int ret = 0;
// 循环读取帧并解码
while (av_read_frame(formatCtx, &packet) >= 0) { // 循环读取数据包
if (packet.stream_index == videoStream) { // 如果数据包是视频流
ret = avcodec_send_packet(codecCtx, &packet); // 将数据包发送到解码器
if (ret < 0) {
qDebug() << "Error: sending packet to decoder"; // 如果发送失败,输出错误信息
break;
}
// 获取解码后的帧
while (ret >= 0) {
ret = avcodec_receive_frame(codecCtx, frame); // 从解码器接收帧
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) // 如果需要更多数据或到达文件末尾,跳出循环
break;
else if (ret < 0) {
qDebug() << "Error: receiving frame from decoder"; // 如果接收失败,输出错误信息
break;
}
// 将解码后的 YUV420 数据写入文件
for (int i = 0; i < frame->height; i++) {
fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, outputFile); // 写入Y分量
}
for (int i = 0; i < frame->height / 2; i++) {
fwrite(frame->data[1] + i * frame->linesize[1], 1, frame->width / 2, outputFile); // 写入U分量
}
for (int i = 0; i < frame->height / 2; i++) {
fwrite(frame->data[2] + i * frame->linesize[2], 1, frame->width / 2, outputFile); // 写入V分量
}
}
}
av_packet_unref(&packet); // 释放数据包
}
// 释放资源
fclose(outputFile); // 关闭输出文件
av_frame_free(&frame); // 释放帧
avcodec_close(codecCtx); // 关闭解码器
avformat_close_input(&formatCtx); // 关闭输入文件
qDebug() << "Finished decoding and writing YUV420 file"; // 解码并写入文件完成
}