解码的大致逻辑和编码的反着来:
AAC源文件 ==> (AVPacket)输入缓冲区 ==> (AVCodec)解码器 ==> (AVFrame)输出缓冲区 ==> 输出文件
对于FFMPEG解码音视频的一般来讲,都是直接从媒体容器文件(网络码流或者封装文件)中,读取出AVPaket传给解码器。但一般音视频解码并不是在这样的场景下,而是直接给解码器传送裸码流(AAC、h264等),此时我们需要知道每次传给解码器的音视频数据大小,即每帧音频/视频大小。AVCodecParser可通过音视频裸码流解析出每帧的大小等信息。
也就是
AAC源文件 ==> AVCodecParser ==> AVPacket ==> 解码器 ==> AVFrame ==> 输出文件
Win环境下, 使用ffmpeg解码 :
ffmpeg -c:a libfdk_aac -i in.aac -f s16le out.pcm
Mac环境下, 使用ffmpeg解码 :
ffmpeg -c:a libfdk_aac -i in.aac -f f32le out.pcm
in.aac 输入aac文件
out.pcm aac解码后得到的pcm文件
核心函数AVCodecParser
/**
* Parse a packet.
*
* @param s parser context.
* @param avctx codec context.
* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.
* @param buf input buffer.
* @param buf_size buffer size in bytes without the padding. I.e. the full buffer
size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
To signal EOF, this should be 0 (so that the last frame
can be output).
* @param pts input presentation timestamp.
* @param dts input decoding timestamp.
* @param pos input byte position in stream.
* @return the number of bytes of the input bitstream used.
* @return 输入流用了多少返回多少字节
*
* Example:
* @code
* while(in_len){
* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
* in_data, in_len,
* pts, dts, pos);
* in_data += len;
* in_len -= len;
*
* if(size)
* decode_frame(data, size);
* }
* @endcode
*/
解码需要一个解析器, 这个解析器读取aac文件中的数据, 传递到AVPacket中, 也就是packet->data , 和packet->size
* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.
代码实现 Demo
#include "aacDecodeThread.h"
#include
#include
extern "C" {
#include
#include
#include
#include
#include
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
#define CHECK_IF_ERROR_BUF_END(ret, funcStr) \
if (ret) { \
ERROR_BUF(ret); \
qDebug() << #funcStr << " error :" << errbuf; \
goto end; \
}
#ifdef Q_OS_WIN
#define IN_AAC_FILEPATH "G:/Resource/pcm_to_aac.aac"
#define OUT_PCM_FILEPATH "G:/Resource/aac_decode_to_pcm.pcm"
#else
#define IN_AAC_FILEPATH "/Users/liliguang/Desktop/pcm_to_aac.aac"
#define OUT_PCM_FILEPATH "/Users/liliguang/Desktop/aac_decode_to_pcm.pcm"
#endif
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096
// 音频解码
// 返回负数:中途出现了错误
// 返回0:解码操作正常完成
static int decode(AVCodecContext *ctx,
AVFrame *frame,
AVPacket *pkt,
QFile &outFile) {
// 发送数据到解码
int ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_send_frame error" << errbuf;
return ret;
}
// 从解码器中获取到数据到frame
ret = avcodec_receive_frame(ctx, frame);
if (ret == AVERROR(EAGAIN) ) {
qDebug() << "ret == AVERROR(EAGAIN)" << ret ;
return ret;
} else if (ret == AVERROR_EOF) {
// 全部获取完毕
qDebug() << "ret == AVERROR_EOF" << ret ;
return ret;
} else if (ret < 0) {
qDebug() << "ret < 0" << ret ;
return ret;
}
qDebug() << "ret == 0" << ret ;
// 成功从编码器拿到编码后的数据
// 将编码后的数据写入文件
outFile.write((char *) frame->data[0], frame->linesize[0]);
return 0;
}
void AACDecodeThread::run() {
qDebug() << "AACEncodeThread run ";
// 输入输出文件
const char *infilename;
const char *outfilename;
// 解码器
const AVCodec *codec = nullptr;
// 解码器上下文
AVCodecContext *codecCtx = nullptr;
// FFMPEG解码音视频的一般来讲,都是直接从媒体容器文件(网络码流或者封装文件)中,读取出AVPaket传个解码器。
// 但一般音视频解码并不是在这样的场景下,而是直接给解码器传送裸码流(AAC、h264等),
// 此时我们需要知道每次传给解码器的音视频数据大小,即每帧音频/视频大小。
// AVCodecParser可通过音视频裸码流解析出每帧的大小等信息。
//解析器上下文
AVCodecParserContext *codecParserCtx = nullptr;
// 源文件数据源存储结构指针
AVFrame *frame;
// 编码文件数据源存储结构指针
AVPacket *pkt;
int avcodec_open2_Ret;
int infileOpen_Ret;
int outfileOpen_Ret;
infilename = IN_AAC_FILEPATH;
outfilename = OUT_PCM_FILEPATH;
QFile inFile(infilename);
QFile outFile(outfilename);
// 加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界.
char inDataArray[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE]; // 输入缓冲区
char *inData = inDataArray; // 指向输入缓冲区指针
int inDataLen = 0; // 读取到文件的数据大小
bool readMoreReachEnd = false;// 加载更多文件数据是否已经结束
int inParserRet = 0;
int decode_ret;
// ============================================================
// 解码逻辑 源文件 ==> 解析器 ==> (AVPacket)输入缓冲区 ==> 解码器 ==> (AVFrame)输出缓冲区 ==> 输出文件
// 输入文件
infileOpen_Ret = inFile.open(QFile::ReadOnly);
CHECK_IF_ERROR_BUF_END(!infileOpen_Ret, "inFile.open");
// 输出文件
outfileOpen_Ret = outFile.open(QFile::WriteOnly);
CHECK_IF_ERROR_BUF_END(!outfileOpen_Ret, "outFile.open");
// 解码器
codec = avcodec_find_decoder_by_name("libfdk_aac");
CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_decoder");
// 解析器上下文
codecParserCtx = av_parser_init(codec->id);
CHECK_IF_ERROR_BUF_END(!codecParserCtx, "av_parser_init");
// 解码器上下文
codecCtx = avcodec_alloc_context3(codec);
CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");
// 打开解码器
avcodec_open2_Ret = avcodec_open2(codecCtx, codec, nullptr);
CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");
// 创建输入Packet
pkt = av_packet_alloc();
CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");
// 创建输出rame
frame = av_frame_alloc();
CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");
// 读取文件到解析器中。
// 解析完成之后存放到pkt
// pkt送到解码器
// 输出
// 先读取一次, 如果有值,解析数据
//
// 总大小 20480 + AV_INPUT_BUFFER_PADDING_SIZE
// -------------------------------
// | 【20480 】 +【64】 |
// -------------------------------
// 每次读取4096, 4096的内容中, 用解析器解析, 解析器每次解析180-200左右,直到解析完毕
// inDataLen 每次读取文件的长度
inDataLen = inFile.read(inDataArray, AUDIO_INBUF_SIZE);
CHECK_IF_ERROR_BUF_END(inDataLen <= 0, "inFile.read");
inData = inDataArray;
// 每一次解析内容大小
while(inDataLen > 0) {
//the number of bytes of the input bitstream used.
inParserRet = av_parser_parse2(codecParserCtx,
codecCtx,
&pkt->data,
&pkt->size,
(uint8_t *)inData,//输入缓冲区。
inDataLen,//buf_size + AV_INPUT_BUFFER_PADDING_SIZE。
AV_NOPTS_VALUE,
AV_NOPTS_VALUE,
0);
CHECK_IF_ERROR_BUF_END(inParserRet < 0, "av_parser_parse2");
// 指针位置偏移,跳过已经解析的数据
inData += inParserRet;
//读取的大小减去已经解析的大小
inDataLen -= inParserRet;
if ( inParserRet > 0) {
decode_ret = decode(codecCtx, frame, pkt, outFile);
CHECK_IF_ERROR_BUF_END( decode_ret < 0, "decode_ret");
}
// 每次读取 200左右, 如果当前已经小于AUDIO_REFILL_THRESH, 那就重新读取一遍文件
if (inDataLen < AUDIO_REFILL_THRESH && !readMoreReachEnd) {
memmove(inDataArray, inData, inDataLen);
inData = inDataArray;
// 读取新的数据到后面
int readMoreLen = inFile.read(inDataArray + inDataLen, AUDIO_INBUF_SIZE - inDataLen );
qDebug() << "readMoreLen" << readMoreLen;
if ( readMoreLen > 0 ) {
inDataLen += readMoreLen;
readMoreReachEnd = false;
} else {
readMoreReachEnd = true;
}
}
}
qDebug() << "AACEncodeThread while end ";
// 冲刷最后一次缓冲区
pkt->data = NULL;
pkt->size = 0;
decode_ret = decode(codecCtx, frame, nullptr, outFile);
qDebug() << "AACEncodeThread Last Decode ";
end:
// 关闭文件
inFile.close();
outFile.close();
// 释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&codecCtx);
av_parser_close(codecParserCtx);
qDebug() << "AACEncodeThread end ";
}
代码的大体逻辑和编码的很像,因为是发过来而已, 这里值得注意的是, 读取文件的逻辑和编码不一样。
值得注意的是:源文件record_to_pcm.pcm==> 编码 pcm_to_aac.aac ==> 解码aac_decode_to_pcm.pcm, 经过了一轮编解码之后, 最后的PCM文件的大小会有发生变化, 这个跟比特率的设置有关系。
总结 :
AAC编码的简略逻辑 : 源文件 ==》 AVPacket ==》编码器 ==》AVFrame ==> 输出文件
细致化:
源文件
inFile.open(QFile::ReadOnly) 打开文件读取文件内容到缓冲区
Parser 解析器读取输入文件缓冲区的内容, 读取后传给AVPacket
AVPacket 发送处理avcodec_send_packet
AVCodec 解码器
AVPacket 接受处理avcodec_receive_frame
outFile.open(QFile::WriteOnly) 从缓冲区读取数据写入文件中去
输出文件
编码完成