ffmpeg(十一)aac和h264软解码

这把我C 2021-04-27 16:33:54 5042

ffmpeg(十一)aac和h264软解码

前言

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

软解码相关流程

img点击并拖拽以移动

软解码相关函数

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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 95 7 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
这把我C
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区