ffmpeg(十一)aac和h264软解码
ffmpeg(十一)aac和h264软解码
前言
音视频解码是一个很常用的需求场景,同时它也是一个非常耗时的过程。压缩的音视频数据aac音频流,h264视频流等等,常常需要先解码为未压缩数据才能进行播放,ffmpeg为音视频的软解码提供了统一的接口,使用起来非常方便。
软解码相关流程
软解码相关函数
- AVPacket结构体
该结构体用于存储压缩的音频或者视频
对应初始化函数和释放函数
av_packet_alloc();
av_packet_free();
av_packet_unref();
- AVFrame结构体
存储未压缩数据成员字段解析:
uint8_t *data[AV_NUM_DATA_POINTERS];
int linesize[AV_NUM_DATA_POINTERS];
对于音频则由planner和packet两种存储方式。视频则根据自身格式对应进行存储
对于planner格式的音频数据,以双声道为例,其它声道类推,其数据存储方式为
data[0]:LLLLLLL...
data[1]:RRRRR....
linesize[0]:值为L数据个数
linesize[1]:值为R数据个数
对于packet格式的音频数据,以双声道为例,其它声道类推,其数据存储方式为
data[0]:LRLRLR..
linesize[0]:值音频数据个数
对于类似YUV420P每个通道大小不一致的视频频数据,以YUV420P为例,其它类推,其数据存储方式为
data[0]:yyyy....
data[1]:uuuu....
data[2]:vvvv.....
linesize[0]:存储所有y数据字节对齐的视频宽(大于等于实际视频宽)
linesize[1]:存储所有u数据字节对齐的视频宽(大于等于实际视频宽)
linesize[2]:存储所有v数据字节对齐的视频宽(大于等于实际视频宽)
对于类似RGB每个通道大小一致的视频频数据,以RGB24为例,其它类推,其数据存储方式为
data[0]:RGBRGB....
linesize[0]:存储所有RGB数据字节对齐的视频宽(大于等于实际视频宽)
对应初始化函数和释放函数
av_frame_alloc();
该函数不会分配用于存储数据的内存内存。
av_frame_free();
- 1、AVCodec *avcodec_find_decoder(enum AVCodecID id);
创建解码器 - 2、AVCodecContext avcodec_alloc_context3(const AVCodec codec);
创建解码上下文 - 3、int avcodec_parameters_to_context(AVCodecContext codec,
const AVCodecParameters par);
设置解码相关参数 - 4、int avcodec_open2(AVCodecContext avctx, const AVCodec codec, AVDictionary **options);
初始化解码上下文 - 5、int avcodec_send_packet(AVCodecContext avctx, const AVPacket avpkt);
输送压缩数据给解码器上下文 - 6、int avcodec_receive_frame(AVCodecContext avctx, AVFrame frame);
从解码器上下文获取解码数据
对于解码器来说,AVFrame可以不用为其分配内存,avcodec_receive_frame内部会自动为其分配内存。
实现代码
头文件
//
// SoftDecoder.hpp
// video_encode_decode
//
// Created by apple on 2020/4/20.
// Copyright © 2020 apple. All rights reserved.
//
#ifndef SoftDecoder_hpp
#define SoftDecoder_hpp
#include <stdio.h>
#include <string>
#include "cppcommon/CLog.h"
using namespace std;
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
class SoftDecoder
{
public:
SoftDecoder();
~SoftDecoder();
// 解码本地MP4等封装后的格式文件
void doDecode();
// 解码aac/h264流的文件
void doDecode2();
};
#endif /* SoftDecoder_hpp */
void doDecode();实现了解码本地MP4等封装文件的解码流程
void doDecode2();实现了解码aac流等流式数据的解码流程
实现文件
SoftDecoder::SoftDecoder()
{
}
SoftDecoder::~SoftDecoder()
{
}
// 为了防止多次调用造成的crash,这里用指针的指针作为参数
static void releaseSources(AVFormatContext **fmtCtx,AVCodecContext **codecCtx1,AVCodecContext **codecCtx2)
{
if (fmtCtx && *fmtCtx != NULL) {
avformat_close_input(fmtCtx);
}
if (codecCtx1 && *codecCtx1 != NULL) {
avcodec_free_context(codecCtx1);
}
if (codecCtx1 && *codecCtx1 != NULL) {
avcodec_free_context(codecCtx2);
}
}
static void decode(AVCodecContext *codecCtx,AVPacket *packet,AVFrame *frame,string str)
{
if (codecCtx == NULL) return;
// 由于解码器内部会维护一个缓冲区,所以送入解码器的packet并不是立马就能获取到解码数据,所以这里采取如下机制
int ret = 0;
avcodec_send_packet(codecCtx,packet);
while (true) {
ret = avcodec_receive_frame(codecCtx, frame);
// 解码器上下文会有一个解码缓冲区,送入的packet并不是立马能够解码的,如果返回EAGAIN
// 则代表正在解码中,需要继续送入packet即可。
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
LOGD("%s avcodec_receive_frame %d",str.c_str(),ret);
return;
} else if (ret < 0) {
LOGD("%s decodec %d fail",str.c_str(),packet->stream_index,ret);
return;
}
// 解码成功
LOGD("%s decode sucess",str.c_str());
}
}
void SoftDecoder::doDecode()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("1-video_encode_decode");
if (pos == string::npos) {
LOGD("find fail");
return;
}
string srcDic = curFile.substr(0,pos) + "filesources/";
string srcPath = srcDic + "test_1280x720_3.mp4";
// 创建解封装上下文
AVFormatContext *in_fmtCtx = NULL;
AVCodecContext *a_decoderCtx = NULL,*v_decoderCtx = NULL;
int a_stream_index = -1,v_stream_index = -1;
int ret = 0;
if ((ret = avformat_open_input(&in_fmtCtx,srcPath.c_str(),NULL,NULL)) < 0) {
LOGD("avformat_open_input fail %d",ret);
return;
}
// 初始化封装相关参数(期间会进行解码尝试)
if ((ret = avformat_find_stream_info(in_fmtCtx,NULL)) < 0) {
LOGD("avformat_find_stream_info fail");
return;
}
// 读取文件中对应的音视频流,都只取一路
for (int i = 0;i<in_fmtCtx->nb_streams;i++) {
AVStream *stream = in_fmtCtx->streams[i];
enum AVCodecID codeId = stream->codecpar->codec_id;
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && a_stream_index == -1) {
a_stream_index = i;
// 通过输入文件的编码方式创建解码器
AVCodec *codec = avcodec_find_decoder(codeId);
a_decoderCtx = avcodec_alloc_context3(codec);
if (a_decoderCtx == NULL) {
LOGD("avcodec_alloc_contex3 audio fail");
releaseSources(&in_fmtCtx,NULL,NULL);
return;
}
/** 遇到问题:音频解码失败
* 分析原因:未设置解码相关参数
* 解决方案:通过输入音频流的编码参数来设置解码参数
*/
avcodec_parameters_to_context(a_decoderCtx, stream->codecpar);
if ((ret = avcodec_open2(a_decoderCtx,codec,NULL)) < 0) {
LOGD("audio avcodec_open2() fail %d",ret);
releaseSources(&in_fmtCtx,NULL,NULL);
return;
}
}
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && v_stream_index == -1) {
v_stream_index = i;
AVCodec *codec = avcodec_find_decoder(codeId);
v_decoderCtx = avcodec_alloc_context3(codec);
if (v_decoderCtx == NULL) {
LOGD("avcodec_alloc_context3 video fail");
releaseSources(&in_fmtCtx, &a_decoderCtx, NULL);
return;
}
/** 遇到问题:视频解码失败
* 分析原因:未设置解码相关参数
* 解决方案:通过输入视频流的编码参数来设置解码参数
*/
avcodec_parameters_to_context(v_decoderCtx, stream->codecpar);
if ((ret = avcodec_open2(v_decoderCtx,codec,NULL)) < 0) {
LOGD("video avcodec_open2() fail %d",ret);
releaseSources(&in_fmtCtx,&a_decoderCtx,NULL);
return;
}
}
}
LOGD("===begin av_dump_format ==");
av_dump_format(in_fmtCtx,0,srcPath.c_str(),0);
LOGD("===end av_dump_format===");
/** 说明:
* 1、AVFrame 存储的是未压缩的数据(即解码后的数据),av_frame_alloc()只创建了AVFframe的引用,并没有分配内存
* 2、AVPacket 存储的是压缩的数据(即解码前的数据),av_packet_alloc();只是创建了AVPacket的引用,并没有分配内存
*/
AVFrame *aframe = av_frame_alloc();
AVFrame *vframe = av_frame_alloc();
AVPacket *packet = av_packet_alloc();
// av_read_frame()函数每次调用时内部都会为AVPacket分配内存用于存储未压缩音视频数据,所以AVPacket用完
// 后要释放掉相应内存
while (av_read_frame(in_fmtCtx,packet) >= 0) {
// 根据AVPacket中的stream_index区分对应的是音频数据还是视频数据;
// 然后将AVPacket送入对应的解码器进行解码
AVCodecContext *codecCtx = NULL;
AVFrame *frame = NULL;
if (packet->stream_index == a_stream_index) {
codecCtx = a_decoderCtx;
frame = aframe;
} else if (packet->stream_index == v_stream_index) {
codecCtx = v_decoderCtx;
frame = vframe;
}
if (codecCtx != NULL) {
decode(codecCtx,packet,frame,packet->stream_index == a_stream_index ? "audio":"video");
}
// 释放内存
av_packet_unref(packet);
}
// 刷新缓冲区
decode(a_decoderCtx,NULL,aframe,"audio");
decode(v_decoderCtx,NULL,vframe,"video");
releaseSources(&in_fmtCtx, &a_decoderCtx, &v_decoderCtx);
}
// 为了防止多次调用造成的crash,这里用指针的指针作为参数
static void releaseSources2(AVFormatContext **fmtCtx,AVCodecContext **codecCtx,AVCodecParserContext **parserCtx)
{
if (fmtCtx && *fmtCtx != NULL) {
avformat_close_input(fmtCtx);
}
if (codecCtx && *codecCtx != NULL) {
avcodec_free_context(codecCtx);
}
if (parserCtx && *parserCtx != NULL) {
av_parser_close(*parserCtx);
}
}
/** 熟悉av_parser_parser2()函数的用法
* 1、同一个AVCodecParserContext只能解析一路流
*/
#define Parser_Buffer_Size 1024*100
void SoftDecoder::doDecode2()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("1-video_encode_decode");
if (pos == string::npos) {
LOGD("find string fail");
return;
}
string srcDic = curFile.substr(0,pos) + "filesources/";
/** 遇到问题:解析本地MP4或者MP3等文件时失败
* 分析原因:av_parser_parser2()只适合解析aac流,h264流(这种流一般用于网络播放时,比如基于RTSP,RTMP协议的),并不
* 适合本地MP4直接读取后的解析(本地得还得用AVFormatContext解封装)
*/
string srcPath = srcDic + "test_441_f32le_2.aac";
FILE *in_file = NULL;
uint8_t in_buff[Parser_Buffer_Size+AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = NULL;
bool isAudio = true;
int data_size;
data = in_buff;
data_size = Parser_Buffer_Size;
if ((in_file = fopen(srcPath.c_str(), "rb")) == NULL){
LOGD("fopen fail");
return;
}
AVFormatContext *fmt_Ctx = NULL;
AVCodecContext *decoderCtx = NULL;
AVCodecParserContext *parserCtx = NULL;
AVCodec *codec = NULL;
if (avformat_open_input(&fmt_Ctx, srcPath.c_str(), NULL, NULL) < 0) {
LOGD("avformat_open_input fail");
return;
}
enum AVCodecID codeId = fmt_Ctx->streams[0]->codecpar->codec_id;
isAudio = fmt_Ctx->streams[0]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO;
codec = avcodec_find_decoder(codeId);
decoderCtx = avcodec_alloc_context3(codec);
if (decoderCtx == NULL) {
LOGD("avcodec_alloc_context3 fail");
releaseSources2(&fmt_Ctx,NULL, NULL);
return;
}
if (avcodec_open2(decoderCtx,codec,NULL) < 0) {
LOGD("avcodec_open2 fail");
releaseSources2(&fmt_Ctx,NULL, NULL);
return;
}
parserCtx = av_parser_init(codeId);
if (parserCtx == NULL) {
LOGD("av_parser_init fail");
releaseSources2(&fmt_Ctx, &decoderCtx, NULL);
return;
}
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
size_t ret = 0;
while ((ret = fread(data, 1, data_size, in_file)) > 0) {
if (ret < data_size) {
data_size = (int)ret;
}
while (data_size > 0) {
int len = av_parser_parse2(parserCtx, decoderCtx, &packet->data, &packet->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (len < 0) {
LOGD("av_parser_parser2() fail %d",len);
break;
}
data += len;
data_size -= len;
if (packet->size > 0) {
decode(decoderCtx, packet, frame, isAudio?"audio":"video");
av_packet_unref(packet);
}
}
//位置复位 重新读取
data = in_buff;
data_size = Parser_Buffer_Size;
}
decode(decoderCtx, NULL, frame, isAudio?"audio":"video");
releaseSources2(&fmt_Ctx, &decoderCtx, &parserCtx);
}
遇到问题
1、音频解码失败
分析原因:未设置解码相关参数
解决方案:通过输入音频流的编码参数来设置解码参数
2、遇到问题:视频解码失败
分析原因:未设置解码相关参数
解决方案:通过输入视频流的编码参数来设置解码参数
- 分享
- 举报
-
浏览量:4566次2021-04-27 16:33:22
-
浏览量:703次2023-10-30 15:15:38
-
浏览量:1395次2024-02-27 17:03:43
-
浏览量:987次2023-11-01 11:26:42
-
浏览量:2199次2020-05-22 19:32:20
-
浏览量:3090次2018-05-07 16:22:35
-
浏览量:2872次2020-08-10 09:24:28
-
浏览量:834次2023-06-30 10:11:29
-
浏览量:747次2023-06-30 09:18:17
-
浏览量:680次2023-06-12 14:34:57
-
浏览量:668次2023-10-23 17:56:00
-
浏览量:1178次2023-06-12 14:34:40
-
浏览量:5524次2020-08-20 14:18:11
-
浏览量:4284次2021-04-25 16:34:01
-
浏览量:4948次2018-11-13 10:03:09
-
浏览量:2317次2024-01-18 15:01:07
-
浏览量:1104次2023-12-21 17:20:27
-
浏览量:4655次2021-04-23 14:10:09
-
浏览量:1488次2023-06-12 14:35:30
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
这把我C
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明