• 音视频学习(十八)——使用ffmepg实现视音频解码


    视频解码

    初始化

    1. 视频常用的编解码器id定义(以h264和h265为例)
    // 定义在ffmpeg\include\libavcodec\avcodec.h
    AV_CODEC_ID_H264
    AV_CODEC_ID_H265
    
    • 1
    • 2
    • 3
    1. 查找解码器:根据编解码id查看解码器
    AVCodec* pCodecVideo = avcodec_find_decoder(codecID);
    if (!pCodecVideo)
    {
        printf("avcodec_find_decoder failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 申请编码器上下文结构体内存,保存了视频编解码相关信息
    AVCodecContext* pCodecCtxVideo = avcodec_alloc_context3(pCodecVideo);
    if (!pCodecCtxVideo)
    {
        printf("avcodec_alloc_context3 error\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 打开解码器
    if (avcodec_open2(pCodecCtxVideo, pCodecVideo, NULL) < 0)
    {
        printf("avcodec_open2 failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 申请帧内存:存储一帧解码后像素(采样)数据
    AVFrame* pFrameVideo = av_frame_alloc(); 
    if (!pFrameVideo)
    {
        printf("av_frame_alloc failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    视频解码

    1. 解码一帧压缩数据
    // data和len为压缩数据的指针和大小
    
    AVPacket packet;
    av_init_packet(&packet);
    packet.data = (uint8_t*)data;
    packet.size = len;
    
    int got_picture = 0;
    if (avcodec_decode_video2(pCodecCtxVideo, pFrameVideo, &got_picture, &packet) < 0)
    {
        printf("avcodec_decode_video2 failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 获取帧大小
    // 以YUV420为例
    int frameSize = avpicture_get_size(AV_PIX_FMT_YUV420P, pFrameVideo->linesize[0], pFrameVideo->height);
    
    • 1
    • 2
    1. 获取上下文,获取用于转码的参数**(初始化一次)**
    // pFrameVideo->width:输入帧数据宽
    // pFrameVideo->height:输入帧数据高
    // pCodecCtxVideo->pix_fmt:帧数据格式
    // pFrameVideo->width:输出帧数据宽
    // pFrameVideo->height:输出帧数据高
    // AV_PIX_FMT_YUV420P:输出帧数据格式,例如YUV420、RGB32等
    // SWS_BICUBIC:视频像素数据格式转换算法类型
    SwsContext* imgConvertCtx = sws_getContext(pFrameVideo->width, 
                                     pFrameVideo->height,
                                     pCodecCtxVideo->pix_fmt,
    								 pFrameVideo->width, 
                                     pFrameVideo->height, 
                                     AV_PIX_FMT_YUV420P, 
                                     SWS_BICUBIC, NULL, NULL, NULL);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 缓冲区分配缓存**(初始化一次)**
    int frameSize = avpicture_get_size(AV_PIX_FMT_YUV420P, pFrameVideo->width, pFrameVideo->height);
    AVFrame* picture = av_frame_alloc();
    uint8_t* pictureBuf = new uint8_t[frameSize];
    
    • 1
    • 2
    • 3
    1. 初始化缓冲区**(初始化一次)**
    avpicture_fill((AVPicture *)m_picture, m_pictureBuf, AV_PIX_FMT_YUV420P, pFrameVideo->width, pFrameVideo->height);
    
    • 1
    1. 图片转换**(针对实时流或读取的文件流,循环调用)**
    sws_scale(imgConvertCtx, (const uint8_t* const*)pFrameVideo->data, pFrameVideo->linesize, 0, pFrameVideo->height, picture->data, picture->linesize);
    
    • 1

    解码关闭

    if (nullptr != pCodecCtxVideo)
    {
        avcodec_close(pCodecCtxVideo);
        av_free(pCodecCtxVideo);
        pCodecCtxVideo = nullptr;
    }
    
    if (nullptr != pFrameVideo)
    {
        av_frame_free(&pFrameVideo);
        pFrameVideo = nullptr;
    }
    
    if (nullptr != picture)
    {
        av_frame_free(&picture);
        picture = nullptr;
    }
    
    if (nullptr != pictureBuf)
    {
        delete[] pictureBuf;
        pictureBuf = nullptr;
    }
    
    if (nullptr != imgConvertCtx)
    {
        sws_freeContext(imgConvertCtx);
        imgConvertCtx = nullptr;
    }
    
    • 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

    音频解码

    初始化

    1. 音频常用的编解码器id定义
    AV_CODEC_ID_PCM_ALAW
    AV_CODEC_ID_PCM_MULAW
    AV_CODEC_ID_FIRST_AUDIO
    AV_CODEC_ID_AAC
    
    • 1
    • 2
    • 3
    • 4
    1. 查找解码器:根据编解码id查看解码器
    AVCodec* pCodecAudio = avcodec_find_decoder(codecID);
    if (!pCodecAudio)
    {
        printf("audio avcodec_find_decoder failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 申请编码器上下文结构体内存,保存了音频编解码相关信息
    AVCodecContext* pCodecCtxAudio = avcodec_alloc_context3(pCodecAudio);
    if (!pCodecCtxAudio)
    {
        printf("audio avcodec_alloc_context3 failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 打开解码器
    int audioCodecType = (int)codec;
    switch (audioCodecType)
    {
        case CODEC_AUDIO_AAC:
            break;
        case CODEC_AUDIO_MP3:
            break;
        case CODEC_AUDIO_G711:
        case CODEC_AUDIO_G711U:
            pCodecCtxAudio->codec_type = AVMEDIA_TYPE_AUDIO;
            pCodecCtxAudio->sample_fmt = AV_SAMPLE_FMT_S16;
            pCodecCtxAudio->sample_rate = 8000;
            pCodecCtxAudio->channel_layout = AV_CH_LAYOUT_MONO;
            pCodecCtxAudio->channels = 1;
            break;
        case CODEC_AUDIO_G7231:
            break;
        case CODEC_AUDIO_G7221:
            break;
        default:
            break;
    }
    
    pCodecCtxAudio->codec_id = codecID;
    int ret = avcodec_open2(pCodecCtxAudio, pCodecAudio, NULL);
    if (ret < 0)
    {
        printf("audio avcodec_open2 failed\n");
        return -1;
    }
    
    • 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
    1. 申请内存和初始化参数
    AVFrame* frameAudio = av_frame_alloc();
    if (!frameAudio)
    {
        printf("audio av_frame_alloc failed\n");
        return -1;
    }
    
    AVPacket* audioPacket = av_packet_alloc();
    if (!audioPacket)
    {
        printf("av_packet_alloc failed\n");
        return -1;
    }
    av_init_packet(audioPacket);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    音频解码

    1. 解码一帧音频数据
    audioPacket->data = (uint8_t*)data;
    audioPacket->size = datalen;
    
    int ret = avcodec_send_packet(m_pCodecCtxAudio, m_audioPacket);
    if (ret < 0) 
    {
        av_packet_unref(audioPacket);
        printf("audio avcodec_send_packet failed\n");
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 接收一帧数据
    ret = avcodec_receive_frame(m_pCodecCtxAudio, m_frameAudio);
    if (ret < 0)
    {
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 设置输入和输出音频信息**(执行一次)**
    // 分配SwrContext
    SwrContext* audioSwrCtx = swr_alloc();
    int channelLayout = av_get_default_channel_layout(frameAudio->channels);
    
    // audioSwrCtx:重采样申请的内存。如果传NULL,内部会申请一块内存,非NULL可以复用之前的内存
    // AV_CH_LAYOUT_MONO:目标声道
    // AV_SAMPLE_FMT_S16:目标采样格式
    // frameAudio->sample_rate:目标采样率
    // channelLayout:原始声道布局
    // pCodecCtxAudio->sample_fmt:原始采样格式
    // frameAudio->sample_rate:原始采样率
    // 设置输入和输出的音频信息
    swr_alloc_set_opts(audioSwrCtx, 
                       AV_CH_LAYOUT_MONO, 
                       AV_SAMPLE_FMT_S16,
                       frameAudio->sample_rate,
                       channelLayout, 
                       pCodecCtxAudio->sample_fmt, 
                       frameAudio->sample_rate, 0, NULL);
    
    // 设置用户参数后初始化上下文
    swr_init(audioSwrCtx);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 重采样转换(循环执行)
    // audioSwrCtx:音频重采样的上下文
    // audioBuffer:输出的指针。传递的输出的数组
    // 1024*256:输出的样本数量,不是字节数。单通道的样本数量。
    // (const uint8_t**)frameAudio->data:输入的数组,AVFrame解码出来的DATA
    // frameAudio->nb_samples:输入的单通道的样本数量。
    // 以单声道为例
    int len = swr_convert(audioSwrCtx, 
                          &audioBuffer, 
                          1024*256,
    			          (const uint8_t**)frameAudio->data,
    			          frameAudio->nb_samples);
    
    // 获取音频大小
    av_get_channel_layout_nb_channels(AV_CH_LAYOUT_MONO);
    int bufSize = av_samples_get_buffer_size(NULL, 
                               av_get_channel_layout_nb_channels(AV_CH_LAYOUT_MONO),
    						   frameAudio->nb_samples,
    			               AV_SAMPLE_FMT_S16, 
                               0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    解码关闭

    if (nullptr != pCodecCtxAudio)
    {
        avcodec_close(pCodecCtxAudio);
        av_free(pCodecCtxAudio);
        pCodecCtxAudio = nullptr;
    }
    
    if (nullptr != frameAudio)
    {
        av_frame_free(&frameAudio);
        frameAudio = nullptr;
    }
    
    if (nullptr != audioPacket)
    {
        av_packet_unref(audioPacket);
        av_packet_free(&audioPacket);
        audioPacket = nullptr;
    }
    
    if (nullptr != audioSwrCtx)
    {
        swr_free(&audioSwrCtx);
        audioSwrCtx = nullptr;
    }
    
    // 其他资源释放
    
    • 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
  • 相关阅读:
    【小程序】低代码+小游戏=小游戏可视化开发
    C#使用PPT组件的CreateVideo方法生成视频
    Google Gmail Oauth Client ID 认证指南
    [内部资源] 想拿年薪30W的软件测试人员,这份资料必须领取
    github连接失败Host key verification failed.解决方案
    【动态规划】爬楼梯爬的不仅仅是楼梯
    vue3.2新增指令v-memo的使用
    布(隆/谷鸟)过滤器
    在ESP32-Arduino开发中添加其它Arduino库
    Python学习笔记-python基础语法-字面量,注释,变量,数据类型,数据类型转换,标识符,运算符,字符串
  • 原文地址:https://blog.csdn.net/www_dong/article/details/134484494