首页 > 技术知识 > 正文

ffmpeg(十一)aac和h264软解码

前言

音视频解码是一个很常用的需求场景,同时它也是一个非常耗时的过程。压缩的音视频数据aac音频流,h264视频流等等,常常需要先解码为未压缩数据才能进行播放,ffmpeg为音视频的软解码提供了统一的接口,使用起来非常方便。

软解码相关流程

ffmpeg(十一)aac和h264软解码

软解码相关函数

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、遇到问题:视频解码失败 分析原因:未设置解码相关参数 解决方案:通过输入视频流的编码参数来设置解码参数

猜你喜欢