ffmpeg 解码视频(h264、mpeg2)输出yuv420p文件

ffmpeg 解码视频(h264、mpeg2)输出yuv420p文件
播放yuv可以参考:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 out.yuv

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavutil/frame.h>
#include <libavutil/mem.h>

#include <libavcodec/avcodec.h>

#define VIDEO_INBUF_SIZE        10240
#define VIDEO_REFILL_THRESH     4096

static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame, FILE* outfile)
{
    
    
    int ret;
    //发一帧数据进行解码

    int send_ret = 1;
    do
    {
    
    
        ret = avcodec_send_packet(dec_ctx, pkt);
        if(ret == AVERROR(EAGAIN))//AVERROR(EAGAIN) 传入失败,表示先要receive frame再重新send packet
        {
    
    
            send_ret = 0;
            printf("avcodec_send_packet  ret == AVERROR(EAGAIN)\n");
        }
        else if(ret < 0)
        {
    
    
            printf("avcodec_send_packet  ret < 0\n");
            return;
        }

        while(ret >= 0)
        {
    
    
            //调用avcodec_receive_frame会在内部首先调用av_frame_unref来释放frame本来的数据
            //就是这次调用会将上次调用返回的frame数据释放
            ret = avcodec_receive_frame(dec_ctx, frame);
            if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                return;
            else if(ret < 0)
            {
    
    
                printf("avcodec_receive_frame = ret < 0\n");
                return;
            }

            //在第一帧的时候输出一下音频信息
            static int print_info = 1;
            if(print_info)
            {
    
    
                print_info = 0;
                printf("width : %uHz\n", frame->width);
                printf("height : %u\n", frame->height);
                printf("fformat : %u\n", frame->format);
            }

            //写入h264文件,yuv420p
            //yuv420格式 :yyyyyyy......uuuu.....vvvvv.......
            fwrite(frame->data[0], 1, frame->width * frame->height, outfile);//y分量
            fwrite(frame->data[1], 1, (frame->width) * (frame->height) / 4, outfile);//u分量
            fwrite(frame->data[2], 1, (frame->width) * (frame->height) / 4, outfile);//v分量
        }

    }while(!send_ret);
}



#define H264 0
int main()
{
    
    

    printf("Hello video decoder!\n");

    const char* outfilename = "out.yuv";


#if H264
    const char* filename = "test.h264";
#else
    const char* filename = "test.mpeg2";
#endif


    const AVCodec* codec = NULL;
    AVCodecContext* codec_ctx = NULL;
    AVCodecParserContext* parser = NULL;

    int len = 0;
    int ret = 0;
    FILE* infile = NULL;
    FILE* outfile = NULL;
    // AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码
    //具体什么影响不知道
    uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t* data = NULL;
    size_t data_size = 0;
    AVPacket* pkt = NULL;
    AVFrame* de_frame = NULL;


    //申请AVPacket本身的内存
    pkt = av_packet_alloc();
    enum AVCodecID video_codec_id = AV_CODEC_ID_NONE;
    if(strstr(filename, ".h264") != NULL)
    {
    
    
        video_codec_id = AV_CODEC_ID_H264;
    }
    else if(strstr(filename, ".mpeg2") != NULL)
    {
    
    
        video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
    }
    else
    {
    
    
        printf("video_codec_id = AV_CODEC_ID_NONE\n");
        return 0;
    }

    //查找解码器
    codec = avcodec_find_decoder(video_codec_id);
    if(!codec)
    {
    
    
        printf("Codec not find!\n");
        return 0;
    }

    //根据解码器ID获取裸流的解析器
    printf("video decoder end!\n");
    parser = av_parser_init(codec->id);
    if(!parser)
    {
    
    
        printf("Parser not find!\n");
        return 0;
    }

    //分配codec使用的上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if(!codec_ctx)
    {
    
    
        printf("avcodec_alloc_context3 failed!\n");
        return 0;
    }

    //将解码器和解码使用的上下文关联
    if(avcodec_open2(codec_ctx, codec, NULL) < 0)
    {
    
    
        printf("avcodec_open2 failed!\n");
        return 0;
    }

    //打开文件
    infile = fopen(filename, "rb");
    if(!infile)
    {
    
    
        printf("infile fopen failed!\n");
        return 0;
    }

    //输出文件
    outfile = fopen(outfilename, "wb");
    if(!outfile)
    {
    
    
        printf("outfilie fopen failed!\n");
        return 0;
    }

    int file_end = 0;
    //读取文件 开始解码
    data = inbuf;
    data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
    de_frame = av_frame_alloc();
    while (data_size > 0)
    {
    
    
        //获取传入到avcodec_send_packet一个packet的数据量
        //(一帧)
        ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if(ret < 0)
        {
    
    
            printf("av_parser_parser2 Error!\n");
            return 0;
        }
        //使用了多少数据做一个偏移
        data += ret;
        data_size -= ret;

        if(pkt->size)
            decode(codec_ctx, pkt, de_frame, outfile);

        //如果当前缓冲区中数据少于VIDEO_REFILL_THRESH就再读
        //避免多次读文件
        if((data_size < VIDEO_REFILL_THRESH) && !file_end)
        {
    
    
            //剩余数据移动缓冲区前
            memmove(inbuf, data, data_size);
            data = inbuf;
            //跨过已有数据存储
            len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
            if(len > 0)
                data_size += len;
            else if(len == 0)
            {
    
    
                file_end = 1;
                printf("file end!\n");
            }
        }
    }

    //冲刷解码器
    pkt->data = NULL;
    pkt->size = 0;
    decode(codec_ctx, pkt, de_frame, outfile);

    fclose(infile);
    fclose(outfile);

    avcodec_free_context(&codec_ctx);
    av_parser_close(parser);
    av_frame_free(&de_frame);
    av_packet_free(&pkt);

    printf("video decoder end!\n");
    return 0;
}






猜你喜欢

转载自blog.csdn.net/m0_37599645/article/details/112308280