• ffmpeg基础五:单独解码裸流aac或MP3或H264


    av_parse_parse2 用于从输入的数据流中将流中的压缩编码数据一帧一帧的分离出来,也就是从一端数据码流中将需要解码的packet读取出来
    由于H264由特殊的格式组成 [StartCode] [NALU Header] [NALU Payload] ,其中 Start Code ⽤于标示这是⼀个NALU 单元的开 始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个NAL header + RBSP;其中NALU Hearder,,长度一个字节,后面5位代表了这一帧的具体属性,比如IDR,psp,sps等

    核心函数是av_parser_parse2():
    av_parser_parse2():从输入的数据流中解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据。
    这个函数的功能是从一大片数据中,分割出一个个nal单元,这也是为什么后面使用fread读取文件流,使用av_parser_parse2就可以获取出packet

    解码视频H264

    //解码视频,使用了av_parser_parse2

    /**
     * @file
     * video decoding with libavcodec API example
     *
     * @example decode_video.c
     */
    
    #include 
    #include 
    #include 
    
    #include 
    
    #define INBUF_SIZE 4096
    
    static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
                         char *filename)
    {
        FILE *f;
        int i;
    
        f = fopen(filename,"wb");
        fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
        for (i = 0; i < ysize; i++)
            fwrite(buf + i * wrap, 1, xsize, f);
        fclose(f);
    }
    
    static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                       const char *filename)
    {
        char buf[1024];
        int ret;
    
        ret = avcodec_send_packet(dec_ctx, pkt);
        if (ret < 0) {
            fprintf(stderr, "Error sending a packet for decoding\n");
            exit(1);
        }
    
        while (ret >= 0) {
            ret = avcodec_receive_frame(dec_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                return;
            else if (ret < 0) {
                fprintf(stderr, "Error during decoding\n");
                exit(1);
            }
    
            printf("saving frame %3d\n", dec_ctx->frame_number);
            fflush(stdout);
    
            /* the picture is allocated by the decoder. no need to
               free it */
            snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
            pgm_save(frame->data[0], frame->linesize[0],
                     frame->width, frame->height, buf);
        }
    }
    
    //从本地读取一个h264/265文件,但是一定要保证这个文件没有被封装过
    //输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)
    int main(int argc, char **argv)
    {
        const char *filename, *outfilename;
        const AVCodec *codec;   // 编解码器
        AVCodecParserContext *parser; // 码流解析器
        AVCodecContext *c= NULL;  // 编解码器上下文
        FILE *f;
        AVFrame *frame;
        uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
        uint8_t *data;
        size_t   data_size;
        int ret;
        AVPacket *pkt;
    
        if (argc <= 2) {
            fprintf(stderr, "Usage: %s  \n"
                    "And check your input file is encoded by mpeg1video please.\n", argv[0]);
            exit(0);
        }
        filename    = argv[1];
        outfilename = argv[2];
    
        pkt = av_packet_alloc();
        if (!pkt)
            exit(1);
    
        /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
        memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    
        /* find the MPEG-1 video decoder */
        //查找解码器
        //比如这里AVCodecContext和
        codec = avcodec_find_decoder(AV_CODEC_ID_H264);
        if (!codec) {
            fprintf(stderr, "Codec not found\n");
            exit(1);
        }
    	//初始化解析器,也就是根据codec->id来确定解析器的具体类型,解析器具体类型可以看文章最后。
        parser = av_parser_init(codec->id);
        if (!parser) {
            fprintf(stderr, "parser not found\n");
            exit(1);
        }
    	// 根据编解码器创建编解码器上下文,为什么创建上下文在之前的文章有介绍
        c = avcodec_alloc_context3(codec);
        if (!c) {
            fprintf(stderr, "Could not allocate video codec context\n");
            exit(1);
        }
    
        /* For some codecs, such as msmpeg4 and mpeg4, width and height
           MUST be initialized there because this information is not
           available in the bitstream. */
    
        /* open it */
        //打开解码器
        //如果是cpu解码,可以设置解码线程数量
        //分配AVCodecContext
        /**
         * thread count
         * is used to decide how many independent tasks should be passed to execute()
         * - encoding: Set by user.
         * - decoding: Set by user.
         */
       c->thread_count=10;
       //打开解码器,并将解码器和上下文建立连接
        if (avcodec_open2(c, codec, NULL) < 0) {
            fprintf(stderr, "Could not open codec\n");
            exit(1);
        }
    
        f = fopen(filename, "rb");
        if (!f) {
            fprintf(stderr, "Could not open %s\n", filename);
            exit(1);
        }
    
        frame = av_frame_alloc();
        if (!frame) {
            fprintf(stderr, "Could not allocate video frame\n");
            exit(1);
        }
    
        while (!feof(f)) {
            /* read raw data from the input file */
            data_size = fread(inbuf, 1, INBUF_SIZE, f);
            if (!data_size)
                break;
    
            /* use the parser to split the data into frames */
            data = inbuf;
            //注意这里,这里是个小循环,也就是说,data中有可能包含多个nal单元,
            //每次循环只会找出一个nal,循环,直到所有nal找出来为止
            while (data_size > 0) {
            //这里就是最核心的部分,从输入的码流中查找NALU,然后将读取的NALU数据放到pkt的data中,然后返回的pkt->size大于0,这说明成功分离除了一个NALU
                ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                                       data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
                if (ret < 0) {
                    fprintf(stderr, "Error while parsing\n");
                    exit(1);
                }
                //这里向前移位
                data      += ret;
                data_size -= ret;
    
                if (pkt->size)
                    decode(c, frame, pkt, outfilename);
            }
        }
    
        /* flush the decoder */
        decode(c, frame, NULL, outfilename);
    
        fclose(f);
    
        av_parser_close(parser);
        avcodec_free_context(&c);
        av_frame_free(&frame);
        av_packet_free(&pkt);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184

    解码音频AAC或MP3

    /**
    * @brief         解码音频,主要的测试格式aac和mp3
    */
    #include 
    #include 
    #include 
    
    #include 
    #include 
    
    #include 
    
    #define AUDIO_INBUF_SIZE 20480
    #define AUDIO_REFILL_THRESH 4096
    
    static char err_buf[128] = {0};
    static char* av_get_err(int errnum)
    {
        av_strerror(errnum, err_buf, 128);
        return err_buf;
    }
    
    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 *dec_ctx, AVPacket *pkt, AVFrame *frame,
                       FILE *outfile)
    {
        int i, ch;
        int ret, data_size;
        /* send the packet with the compressed data to the decoder */
        ret = avcodec_send_packet(dec_ctx, pkt);   //向解码器发送packet,进行解码
        if(ret == AVERROR(EAGAIN))
        {
            fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
        }
        else if (ret < 0)
        {
            fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                    av_get_err(ret), pkt->size);
    //        exit(1);
            return;
        }
    
        /* read all the output frames (infile general there may be any number of them */
        while (ret >= 0)
        {
            // 对于frame, avcodec_receive_frame内部每次都先调用
            ret = avcodec_receive_frame(dec_ctx, frame);   // 从解码器读取出已经解码的frame
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                return;
            else if (ret < 0)
            {
                fprintf(stderr, "Error during decoding\n");
                exit(1);
            }
            data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
            if (data_size < 0)
            {
                /* This should not occur, checking just for paranoia */
                fprintf(stderr, "Failed to calculate data size\n");
                exit(1);
            }
            static int s_print_format = 0;
            if(s_print_format == 0)
            {
                s_print_format = 1;
                print_sample_format(frame);
            }
            /**
                P表示Planar(平面),其数据格式排列方式为 :
                LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
                而不带P的数据格式(即交错排列)排列方式为:
                LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
             播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
              */
            for (i = 0; i < frame->nb_samples; i++)
            {
                for (ch = 0; ch < dec_ctx->channels; ch++)  // 交错的方式写入, 大部分float的格式输出
                    fwrite(frame->data[ch] + data_size*i, 1, data_size, outfile);
            }
        }
    }
    // 播放范例:   ffplay -ar 48000 -ac 2 -f f32le believe.pcm
    
    
    //从main函数看起,这个案例是从本地读取一个h264/265,aac或mp3文件,(当然也可以是其它类型文件)
    //但是一定要保证这个文件没有被封装过
    //输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流aac,MP3文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)
    int main(int argc, char **argv)
    {
        const char *outfilename;
        const char *filename;
        const AVCodec *codec;
        AVCodecContext *codec_ctx= NULL;
        AVCodecParserContext *parser = NULL;
        int len = 0;
        int ret = 0;
        FILE *infile = NULL;
        FILE *outfile = NULL;
        uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
        uint8_t *data = NULL;
        size_t   data_size = 0;
        AVPacket *pkt = NULL;
        AVFrame *decoded_frame = NULL;
    
        if (argc <= 2)
        {
            fprintf(stderr, "Usage: %s  \n", argv[0]);
            exit(0);
        }
        filename    = argv[1];
        outfilename = argv[2];
    
        pkt = av_packet_alloc();
        enum AVCodecID audio_codec_id = AV_CODEC_ID_AAC;
        if(strstr(filename, "aac") != NULL)
        {
            audio_codec_id = AV_CODEC_ID_AAC;
        }
        else if(strstr(filename, "mp3") != NULL)
        {
            audio_codec_id = AV_CODEC_ID_MP3;
        }
        else
        {
            printf("default codec id:%d\n", audio_codec_id);
        }
    
        //这里是寻找解码器,一般来说,ffmpeg都包含一个contex上下文,然后其中包含一个具体的业务指针
        codec = avcodec_find_decoder(audio_codec_id);  // AV_CODEC_ID_AAC或AV_CODEC_ID_MP3
        if (!codec) {
            fprintf(stderr, "Codec not found\n");
            exit(1);
        }
        // 获取裸流的解析器 AVCodecParserContext(数据)  +  AVCodecParser(方法)
        // 初始化解析器,也就是根据codec->id来确定解析器的具体类型
        parser = av_parser_init(codec->id);
        if (!parser) {
            fprintf(stderr, "Parser not found\n");
            exit(1);
        }
        // 分配解码器codec上下文: AVCodecContext* codec_ctx
        codec_ctx = avcodec_alloc_context3(codec);
        if (!codec_ctx) {
            fprintf(stderr, "Could not allocate audio codec context\n");
            exit(1);
        }
    
        // 将解码器和解码器上下文进行关联,打开解码器
        if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
            fprintf(stderr, "Could not open codec\n");
            exit(1);
        }
    
        // 打开输入文件
        infile = fopen(filename, "rb");
        if (!infile) {
            fprintf(stderr, "Could not open %s\n", filename);
            exit(1);
        }
        // 打开输出文件
        outfile = fopen(outfilename, "wb");
        if (!outfile) {
            av_free(codec_ctx);
            exit(1);
        }
    
        // 读取文件进行解码
        data      = inbuf;
        data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);
    
        while (data_size > 0)
        {
            if (!decoded_frame)
            {
                if (!(decoded_frame = av_frame_alloc()))
                {
                    fprintf(stderr, "Could not allocate audio frame\n");
                    exit(1);
                }
            }
    
            ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                                   data, data_size,
                                   AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (ret < 0)
            {
                fprintf(stderr, "Error while parsing\n");
                exit(1);
            }
            data      += ret;   // 跳过已经解析的数据
            data_size -= ret;   // 对应的缓存大小也做相应减小
    
            if (pkt->size)
                decode(codec_ctx, pkt, decoded_frame, outfile);
    
            if (data_size < AUDIO_REFILL_THRESH)    // 如果数据少了则再次读取
            {
                memmove(inbuf, data, data_size);    // 把之前剩的数据拷贝到buffer的起始位置
                data = inbuf;
                // 读取数据 长度: AUDIO_INBUF_SIZE - data_size
                len = fread(data + data_size, 1, AUDIO_INBUF_SIZE - data_size, infile);
                if (len > 0)
                    data_size += len;
            }
        }
    
        /* 冲刷解码器 */
        pkt->data = NULL;   // 让其进入drain mode
        pkt->size = 0;
        decode(codec_ctx, pkt, decoded_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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
  • 相关阅读:
    数据分析与挖掘———SPSS Moderler
    防火墙的设置主要是为了防范什么
    码农的转型之路-造轮子还继续吗?
    elasticsearch7.6.2和logstash安装和初步
    Spring的AOP开发-基于xml配置的AOP
    机器人轨迹规划中经常用到的曲线特性小结:Cn连续与Gn连续、Frenet标架、曲率和挠率
    IDLE、Anaconda安装与使用
    软考重点10 知识产权
    2023/10/22总结
    Docker化Spring Boot应用
  • 原文地址:https://blog.csdn.net/qq_40170041/article/details/126486827