音视频解码--AAC解码获得pcm数据与H264解码获得yuv数据

  1. 简介

          如下图所示,解码就是将H264、AAC等数据解析为原始YUV、PCM数据。

 2:ffmpeg流程分析

1):音频解码

2):视频解码

 

3:主要函数

 (1):av_parser_parse2:解析获得一个packet

函数原型:

/**
 * 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.
 */
int av_parser_parse2(AVCodecParserContext *s,
                     AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts,
                     int64_t pos);

参数 poutbuf:这里指向解码后数据存放的位置,通常为AVPacket下的data即可;

参数poutbuf_size:解码数据的数据大小,相对为AVPacket下的size即可;

参数buf:读取到的待解析的数据;

参数buf_size:待解析数据大小。

4:代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C"
{
#include <libavutil/frame.h>
#include <libavutil/mem.h>

#include <libavcodec/avcodec.h>
}

#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096

using namespace std;

static void print_sample_format(const AVFrame *frame)
{
    printf("ar-samplerate: %uHz\n", frame->sample_rate);
    printf("ac-channel: %u\n", frame->channels);
    printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式
}

static void decode(AVCodecContext *ctx,AVPacket *pkt,AVFrame *frame,FILE *outfile)
{
    int i, ch;
    int ret, data_size;
    //7:解析出的数据包发送给解码器
    ret = avcodec_send_packet(ctx,pkt);
    if(ret == AVERROR(EAGAIN))
    {
        printf("Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    }else if(ret < 0)
    {
        printf("Error submitting the packet to the decoder, err");
        return;
    }

    while(ret >=  0)
    {
        //8:接受解码后的数据帧
        ret = avcodec_receive_frame(ctx,frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            printf("Error during decoding\n");
            exit(1);
        }
        //9:获取单个sample占用字节
        data_size = av_get_bytes_per_sample(ctx->sample_fmt);
        if(data_size < 0)
        {
            printf("failedto caculate data size\n");
            exit(1);
        }

        static int s_print_format = 0;
        if(s_print_format == 0)
        {
            s_print_format = 1;
            print_sample_format(frame);
        }

        for (i = 0; i < frame->nb_samples; i++)
        {
            for (ch = 0; ch < ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出
                //10:文件写入
                fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
        }
    }
}


int main()
{
    FILE *infile = NULL;
    FILE *outfile = NULL;

    int ret;
    uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = NULL;
    size_t   data_size = 0;

    AVCodecContext *codec_ctx;
    AVCodec *codec;
    AVCodecParserContext *parse;

    enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;


    //1:查找指定AAC音频解码器
    codec = avcodec_find_decoder(audio_codec_id);
    if(codec == NULL)
    {
        return -1;
    }

    //2:初始化解码器ID对应裸流解析器
    parse = av_parser_init(codec->id);
    if(!parse)
    {
        return -1;
    }

    //3:解码器上下文分配
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        return -1;
    }

    //4:打开解码器和关联解码器上下文
    if(avcodec_open2(codec_ctx,codec,NULL)<0)
    {
        return -1;
    }
    infile = fopen("believe.aac", "rb");
    if (!infile) {
        printf("fopen strInFile");
       return -1;
    }
    // 打开输出文件
    outfile = fopen("believe.pcm", "wb");
    if (!outfile) {
        printf("fopen");
        return -1;
    }
    //5:读取文件解码
    data      = inbuf;
    data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
    AVFrame *decodec_frame=NULL;
    AVPacket *pkt;
    pkt = av_packet_alloc();

    while(data_size > 0)
    {
        if(!decodec_frame)
        {
            decodec_frame = av_frame_alloc();
            if(!decodec_frame)
            {
                printf("could not allocate frame");
                return -1;
            }
        }
        //6:解析出一个数据包
        ret = av_parser_parse2(parse,codec_ctx,&pkt->data,&pkt->size,data,data_size,AV_NOPTS_VALUE,AV_NOPTS_VALUE,0);
        if(ret < 0)
        {
            printf("error while parsing\n");
            return -1;
        }

        data +=ret;
        data_size -= ret;

        if(pkt->size)
        {
            decode(codec_ctx,pkt,decodec_frame,outfile);
        }

        if(data_size < AUDIO_REFILL_THRESH)
        {
            //将还未读取剩余的数据重新拷贝到inbuf起始位置
            memmove(inbuf,data,data_size);
            data = inbuf;
            //Data_size是之前还剩的数据长度,从起始位置到data_size位置是上一次已经读过的
            int len = fread(data+data_size,1,AUDIO_INBUF_SIZE - data_size, infile) ;
            if (len > 0)
                data_size += len;
        }


    }
    pkt->data = NULL;
    pkt->size = 0;
    decode(codec_ctx, pkt, decodec_frame, outfile);

    fclose(outfile);
    fclose(infile);



    avcodec_free_context(&codec_ctx);
    //av_parser_close(parser);
   // av_frame_free(&decoded_frame);
    av_packet_free(&pkt);

    printf("main finish, please enter Enter and exit\n");
    return 0;
}

#include "decode_vedio.h"
#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFILL_THRESH 4096

decode_vedio::decode_vedio()
{

}

int decode_vedio::mainEntrance()
{
    AVCodec *codec = NULL;
    AVCodecContext *ctx = NULL;
    AVCodecParserContext *parse = NULL;
    AVPacket *pkt = NULL;
    AVFrame *frame = NULL;
    int ret = 0;
    FILE *p_inFile =NULL;
    FILE *p_outFile =NULL;
    uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = NULL;
    size_t   data_size = 0;


    p_inFile = fopen("source_766x322_10s.h264","rb");
    p_outFile = fopen("source_766x322_10s.yuv","wb");
    if(p_inFile == NULL || p_outFile == NULL)
    {
        fprintf(stderr,"p_inFile\p_outFile open failed\n");
        return -1;
    }

    enum AVCodecID decode_vedio_id = AV_CODEC_ID_H264;
    pkt = av_packet_alloc();
    if(pkt == NULL)
    {
        fprintf(stderr,"av_packet_alloc failed\n");
        return -1;
    }

    //1:查找解码器
    codec = avcodec_find_decoder(decode_vedio_id);
    if(codec == NULL)
    {
        fprintf(stderr,"find decoder failed\n");
        return -1;
    }
    //2:解析器初始化
    parse = av_parser_init(codec->id);
    if(parse ==NULL)
    {
        fprintf(stderr,"av_parser_init failed\n");
        return -1;
    }

    //3:分配解码器上下文
    ctx = avcodec_alloc_context3(codec);
    if(ctx ==NULL)
    {
        fprintf(stderr,"avcodec_alloc_context3 failed\n");
        return -1;
    }
    //4:解码器关联上下文
    ret = avcodec_open2(ctx,codec,NULL);
    if(ret < 0)
    {
        fprintf(stderr,"avcodec_open2 failed\n");
        return -1;
    }
    //5:读取文件
    //data = inbuf;
    //data_size = fread(inbuf,1,VIDEO_INBUF_SIZE,p_inFile);
    data      = inbuf;
    data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, p_inFile);
    while(data_size > 0)
    {
        if(frame == NULL)
        {
            frame = av_frame_alloc();
            if(frame == NULL)
            {
                fprintf(stderr,"av_frame_alloc failed\n");
                return -1;
            }
        }
        //6:解析出一个完整数据包
        ret = av_parser_parse2(parse,ctx,&pkt->data,&pkt->size,data,data_size,AV_NOPTS_VALUE,AV_NOPTS_VALUE,0);
        if (ret < 0)
        {
            fprintf(stderr,"av_parser_parse2 failed\n");
            return -1;
        }

        data += ret;
        data_size -= ret;

        if(pkt->size)
        {
            decode(ctx,frame,pkt,p_outFile);
        }

        if(data_size < VIDEO_REFILL_THRESH);//如果数据量少于设定值,则重新读取
        {
            memmove(inbuf,data,data_size);//将之前剩余的数据拷贝到buffer的起始位置,使得buffer存放剩余的数据
            data = inbuf;
            //重新读取数据,从data_size处开始存放,读取数据长度为VIDEO_INBUF_SIZE - data_size
            int len = fread(data+data_size,1,VIDEO_INBUF_SIZE - data_size,p_inFile);
            if(len > 0)
                data_size += len;
        }
    }
    /* 冲刷解码器 */
    pkt->data = NULL;   // 让其进入drain mode
    pkt->size = 0;
    decode(ctx,  frame,pkt, p_outFile);

    return 0;
}


void decode_vedio::decode(AVCodecContext *ctx,AVFrame *frame, AVPacket *pkt, FILE *outFile)
{
    int ret;
    ret = avcodec_send_packet(ctx,pkt);
    if(ret == AVERROR(EAGAIN))
    {
        fprintf(stderr,"egain\n");
    }else if(ret < 0)
    {
        fprintf(stderr,"avcodec_send_packet failed\n");
        return;
    }

    while(ret >= 0)
    {
        ret = avcodec_receive_frame(ctx,frame);
        if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            fprintf(stderr,"avcodec_receive_frame EAGAIN or AVERROR_EOF\n");
            return;
        }else if(ret < 0)
        {
            fprintf(stderr,"avcodec_receive_frame failed\n");
            return;
        }
        //写入y分量,data[0],linesize[0]表示存放y分量
        for(int y = 0;y < frame->height;y++)
        {
            fwrite(frame->data[0]+y*frame->linesize[0],1,frame->width,outFile);
        }
        //写入u分量,data[1],linesize[1]表示存放u分量
        for(int u = 0;u < frame->height/2;u++)
        {
            fwrite(frame->data[1]+u*frame->linesize[1],1,frame->width/2,outFile);
        }
        //写入v分量,data[2],linesize[2]表示存放v分量
        for(int v = 0;v < frame->height/2;v++)
        {
            fwrite(frame->data[2]+v*frame->linesize[2],1,frame->width/2,outFile);
        }
    }

}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值