FFmpeg 解码 H264 格式的视频

FFmpeg 解码 H264 主要分三个步骤,其一获取解码器,其二向解码器中送入 H264 NALU,其三从解码器中获取解码后的 YUV 等数据。

一、H264

H.264,同时也是 MPEG-4 第十部分,是由 ITU-T 视频编码专家组(VCEG)和 ISO/IEC 动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为 H.264/AVC(或者 AVC/H.264 或者 H.264/MPEG-4 AVC 或 MPEG-4/H.264 AVC)而明确的说明它两方面的开发者。

H264 标准各主要部分有 Access Unit delimiter(访问单元分割符),SEI(附加增强信息),primary coded picture(基本图像编码),Redundant Coded Picture(冗余图像编码)。还有 Instantaneous Decoding Refresh(IDR,即时解码刷新)、Hypothetical Reference Decoder(HRD,假想参考解码)、Hypothetical Stream Scheduler(HSS,假想码流调度器)。

特点

1.低码率(Low Bit Rate):和 MPEG2 和 MPEG4 ASP 等压缩技术相比,在同等图像质量下,采用 H.264 技术压缩后的数据量只有 MPEG2 的 1/8,MPEG4 的 1/3。

2.高质量的图像:H.264 能提供连续、流畅的高质量图像(DVD质量)。

3.容错能力强:H.264 提供了解决在不稳定网络环境下容易发生的丢包等错误的必要工具。

4.网络适应性强:H.264 提供了网络抽象层(Network Abstraction Layer),使得 H.264 的文件能容易地在不同网络上传输(例如互联网、CDMA、GPRS、WCDMA 和 CDMA2000 等)。

SPS Sequence Paramater Set,序列的参数集(SPS)包括了一个图像序列的所有信息,SPS 中保存了一组编码视频序列(Coded video sequence)的全局参数。

PPS Picture Paramater Set,图像的参数集(PPS)包括了一个图像所有片的信息。

在这里插入图片描述
H264 原始码流是由一个接一个 NALU(NAL Unit)组成,一个 NALU = 一组对应于视频编码的 NALU 头部信息 + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。

一个原始的 H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成,其中 Start Code 用于标示这是一个 NALU 单元的开始,必须是 “00 00 00 01

GOP 是画面组,一个 GOP 是一组连续的画面。GOP 一般有两个数字,如 M = 3,N = 12,M 指定 I 帧与 P 帧之间的距离,N 指定两个 I 帧之间的距离。

IDR 一个序列的第一帧叫做 ID 帧(Instantaneous Decoding Refresh,立即解码刷新)。

在这里插入图片描述
在 H264 中,句法元素共被组织成:序列、图像(帧)、片、宏块和子宏块五个层次。

NALU Header 由三部分组成,forbidden_bit(1bit),nal_ref_idc(2bits)代表优先级,nal_unit_type(5bits)代表该 NALU 的类型。

forbidden_zero_bit

1 bit,H264 规定此位必须为 0

nal_ref_idc

用于表示当前 NALU 的重要性,值越大,越重要

解码器在解码处理不过来的时候,可以丢掉重要性为 0 的 NALU

  1. nal_ref_idc 不等于 0 时,NAL unit 的内容可能是 SPS/PPS/参考帧的片

  2. nal_ref_idc 等于 0 时,NAL unit 的内容可能是非参考图像的片

  3. 当某个图像的片的 nal_ref_id 等于 0 时,该图像的所有片均应等于 0

nal_unit_type

nal_unit_type 是否包含 VCL(Video Coding Layer 视频编码层) 层编码数据分为 VCL NAL units 和 non-VCL NAL units。VCL NAL units 中包含 VCL 层编码输出的数据,而 non-VCL NAL units 则不包含。
在这里插入图片描述

二、H264 解码

想要正确的解码图像,必须要向解码器送入关键解码信息,PPS、SPS 至关重要,还有一些必要的解码器参数也要设置。

  1. 调用 avcodec_find_decoder(…) 来查找 ID 为 AV_CODEC_ID_H264 的解码器;
  2. 调用 avcodec_alloc_context3(…) 分配解码器上下文 AVCodecContext;
  3. 调用 avcodec_parameters_alloc() 给 AVCodecParameters 结构体分配内存,用来设置解码器参数;
  4. 填充必要的解码参数到 AVCodecParameters,尤其 format,此处设置为 AV_PIX_FMT_YUV420P,表示要解码为 YUV420P 这种 YUV 类型;
  5. 调用 avcodec_parameters_to_context(…) 将解码器参数结构体的内容设置到解码器上下文中;
  6. 调用 avcodec_parameters_free(…) 释放解码器参数结构体占用的内存空间;
  7. 现在调用 avcodec_open2(…) 打开解码器;
  8. 调用 av_frame_alloc() 分配 AVFrame 结构体占用的内存空间,为后续解码提供便利。

解码流程

如果 H264 序列中包含 SPS 和 PPS,则先保存到全局变量中,在 I 帧的前面添加 SPS 和 PPS, 这是为了更好的提升兼容性,比如外面如果先设置了 SPS 和 PPS,而码流中没有,也不影响解码流程。

  1. 调用 avcodec_send_packet(…) 将待解码的数据包发送到解码器;
  2. 调用 avcodec_receive_frame(…) 接收解码后的数据包,编码和解码都是一样的,都是 send 1 次,然后 receive 多次,直到 AVERROR(EAGAIN) 或者 AVERROR_EOF;
  3. 对解码后得到的 AVFrame 调用 av_frame_unref(…) 解除引用,为下一次复用 AVFrame 全局变量做好准备。

FFmpeg 解码 H264 代码

将 H264 解码封装到 VideoDecoder 类中。

//
// Created by liuhongwei on 2021/12/7.
//

#ifndef VIDEODECODER_H
#define VIDEODECODER_H

extern "C" {
//编解码
#include "libavcodec/avcodec.h"
}

#include "PacketQueue.h"
#include "cb/FrameDataCallback.h"

class VideoDecoder {

public:
    VideoDecoder(PacketQueue *packetQueue);
    ~VideoDecoder();

    bool open(unsigned int frameRate, unsigned int profile, unsigned int level,
              char *sps, unsigned int spsLen, char *pps, unsigned int ppsLen);

    void close();

    void decode();

    static void *_decode(void *self) {
        static_cast<VideoDecoder *>(self)->decode();
        return nullptr;
    }

    void setFrameDataCallback(FrameDataCallback *frameDataCallback);

private:
    PacketQueue *pPacketQueue;
    AVCodecContext *pVideoAVCodecCtx;
    AVFrame *pFrame;

    bool volatile isDecoding;
    pthread_t decodeThread;
    pthread_mutex_t *pFrameDataCallbackMutex;
    FrameDataCallback *pFrameDataCallback;

    char *pSPS;
    unsigned int volatile gSPSLen;
    char *pPPS;
    unsigned int volatile gPPSLen;

    bool volatile isFirstIDR;

    unsigned int gFrameRate;
};


#endif //VIDEODECODER_H

具体实现在 VideoDecoder.cpp 中。

//
// Created by liuhongwei on 2021/12/7.
//

#include <unistd.h>
#include "VideoDecoder.h"

const char H264_NAL_START[] = {0x00, 0x00, 0x00, 0x01};

VideoDecoder::VideoDecoder(PacketQueue *packetQueue) {
    pPacketQueue = packetQueue;
    pFrameDataCallbackMutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));

    int ret = pthread_mutex_init(pFrameDataCallbackMutex, nullptr);
    if (ret != 0) {
        LOGE("video FrameDataCallbackMutex init failed.\n");
    }

    gSPSLen = 0;
    pSPS = nullptr;

    gPPSLen = 0;
    pPPS = nullptr;

    isFirstIDR = false;

    gFrameRate = 25;

    pFrameDataCallback = nullptr;
}

VideoDecoder::~VideoDecoder() {
    pthread_mutex_destroy(pFrameDataCallbackMutex);

    if (nullptr != pFrameDataCallbackMutex) {
        free(pFrameDataCallbackMutex);
        pFrameDataCallbackMutex = nullptr;
    }
}

void VideoDecoder::setFrameDataCallback(FrameDataCallback *frameDataCallback) {
    pthread_mutex_lock(pFrameDataCallbackMutex);
    pFrameDataCallback = frameDataCallback;
    pthread_mutex_unlock(pFrameDataCallbackMutex);
}

void VideoDecoder::close() {
    isDecoding = false;
    pthread_join(decodeThread, nullptr);

    if (pSPS != nullptr) {
        free(pSPS);
        pSPS = nullptr;
    }

    if (pPPS != nullptr) {
        free(pPPS);
        pPPS = nullptr;
    }

    if (pFrame != nullptr) {
        av_frame_free(&pFrame);
        LOGI("%s video Frame free", __FUNCTION__);
    }

    if (pVideoAVCodecCtx != nullptr) {
        avcodec_free_context(&pVideoAVCodecCtx);
        LOGI("%s video avcodec_free_context", __FUNCTION__);
    }
}

bool VideoDecoder::open(unsigned int frameRate, unsigned int profile, unsigned int level,
                        char *sps, unsigned int spsLen, char *pps, unsigned int ppsLen) {
    gSPSLen = 0;
    pSPS = nullptr;

    gPPSLen = 0;
    pPPS = nullptr;

    LOGI("%s spsLen=%d ppsLen=%d", __FUNCTION__, spsLen, ppsLen);

    if (spsLen > 0) {
        pSPS = (char *) malloc(spsLen);
        if (nullptr == pSPS) {
            return false;
        }

        memcpy(pSPS, sps, spsLen);
        gSPSLen = spsLen;
    }

    if (ppsLen > 0) {
        pPPS = (char *) malloc(ppsLen);
        if (nullptr == pPPS) {
            free(pSPS);
            return false;
        }

        memcpy(pPPS, pps, ppsLen);
        gPPSLen = ppsLen;
    }

    isFirstIDR = false;

    if (frameRate > 0) {
        gFrameRate = frameRate;
    }

    int ret;
    AVCodec *dec = avcodec_find_decoder(AV_CODEC_ID_H264);
    LOGI("%s video decoder name: %s", __FUNCTION__, dec->name);
    pVideoAVCodecCtx = avcodec_alloc_context3(dec);

    if (pVideoAVCodecCtx == nullptr) {
        LOGE("%s VideoAVCodecCtx alloc failed", __FUNCTION__);
        return false;
    }

    AVCodecParameters *par = avcodec_parameters_alloc();
    if (par == nullptr) {
        LOGE("%s video AVCodecParameters alloc failed", __FUNCTION__);
        free(pSPS);
        free(pPPS);
        avcodec_free_context(&pVideoAVCodecCtx);
        return false;
    }

    par->codec_type = AVMEDIA_TYPE_VIDEO;
    par->codec_id = AV_CODEC_ID_H264;
    par->format = AV_PIX_FMT_YUV420P;//AV_PIX_FMT_NV12
    par->color_range = AVCOL_RANGE_JPEG;

    if (profile != 0) {
        par->profile = (int) profile;
    }

    if (level != 0) {
        par->level = (int) level;
    }

    avcodec_parameters_to_context(pVideoAVCodecCtx, par);
    avcodec_parameters_free(&par);

    LOGI("%s profile=%d level=%d", __FUNCTION__, profile, level);
    ret = avcodec_open2(pVideoAVCodecCtx, dec, nullptr);
    if (ret < 0) {
        LOGE("%s Can not open video encoder", __FUNCTION__);
        free(pSPS);
        free(pPPS);
        avcodec_free_context(&pVideoAVCodecCtx);
        return false;
    }
    LOGI("%s avcodec_open2 video SUCC", __FUNCTION__);
    pFrame = av_frame_alloc();
    if (pFrame == nullptr) {
        LOGE("%s video av_frame_alloc failed", __FUNCTION__);
        free(pSPS);
        free(pPPS);
        avcodec_free_context(&pVideoAVCodecCtx);
        return false;
    }

    isDecoding = true;
    ret = pthread_create(&decodeThread, nullptr, &VideoDecoder::_decode, (void *) this);
    if (ret != 0) {
        LOGE("video decode-thread create failed.\n");
        isDecoding = false;
        free(pSPS);
        free(pPPS);
        avcodec_free_context(&pVideoAVCodecCtx);
        av_frame_free(&pFrame);
        return false;
    }

    return true;
}

void VideoDecoder::decode() {
    int ret;
    unsigned sleepDelta = 1000000 / gFrameRate / 4;// 一帧视频的 1/4
    int NAL_START_LEN = 4;

    while (isDecoding) {

        AVPacket *pkt = av_packet_alloc();

        if (pkt == nullptr) {
            usleep(sleepDelta);
            continue;
        }

        if (pPacketQueue == nullptr) {
            av_packet_free(&pkt);
            usleep(sleepDelta);
            continue;
        }

        PACKET_STRUCT *packetStruct;
        bool isDone = pPacketQueue->Take(packetStruct);
        if (isDone && packetStruct != nullptr && packetStruct->data != nullptr &&
            packetStruct->data_size > 0) {
            //0x67:sps
            if (packetStruct->data[0] == 0x67) {
                if (gSPSLen <= 0) {
                    gSPSLen = packetStruct->data_size;
                    pSPS = (char *) malloc(gSPSLen);
                    if (nullptr == pSPS) {
                        av_packet_free(&pkt);
                        free(packetStruct->data);
                        free(packetStruct);

                        usleep(sleepDelta);
                        continue;
                    }
                    memcpy(pSPS, packetStruct->data, gSPSLen);
                    LOGI("%s get sps spsLen=%d", __FUNCTION__, gSPSLen);
                }

                av_packet_free(&pkt);
                free(packetStruct->data);
                free(packetStruct);

                continue;
            }
            //0x68:pps
            if (packetStruct->data[0] == 0x68) {
                if (gPPSLen <= 0) {
                    gPPSLen = packetStruct->data_size;
                    pPPS = (char *) malloc(gPPSLen);
                    if (nullptr == pPPS) {
                        av_packet_free(&pkt);
                        free(packetStruct->data);
                        free(packetStruct);

                        usleep(sleepDelta);
                        continue;
                    }
                    memcpy(pPPS, packetStruct->data, gPPSLen);
                    LOGI("%s get pps ppsLen=%d", __FUNCTION__, gPPSLen);
                }

                av_packet_free(&pkt);
                free(packetStruct->data);
                free(packetStruct);

                continue;
            }

            if (!isFirstIDR) {
                //0x65:IDR
                if (packetStruct->data[0] == 0x65) {
                    isFirstIDR = true;
                    LOGI("%s get first idr.", __FUNCTION__);
                } else {
                    av_packet_free(&pkt);
                    free(packetStruct->data);
                    free(packetStruct);

                    continue;
                }
            }

            if (packetStruct->data[0] == 0x65 && gSPSLen > 0 && gPPSLen > 0) {
                ret = av_new_packet(pkt, (int) (NAL_START_LEN + gSPSLen +
                                                NAL_START_LEN + gPPSLen +
                                                packetStruct->data_size + NAL_START_LEN));
            } else {
                ret = av_new_packet(pkt, packetStruct->data_size + NAL_START_LEN);
            }

            if (ret < 0) {
                av_packet_free(&pkt);
                free(packetStruct->data);
                free(packetStruct);

                usleep(sleepDelta);
                continue;
            }
        } else {
            av_packet_free(&pkt);
            usleep(sleepDelta);
            continue;
        }

        if (packetStruct->data[0] == 0x65 && gSPSLen > 0 && gPPSLen > 0) {
            int pos = 0;
            //复制 0x 00 00 00 01
            memcpy(pkt->data + pos, H264_NAL_START, NAL_START_LEN);
            pos += NAL_START_LEN;
            memcpy(pkt->data + pos, pSPS, gSPSLen);
            pos += (int) gSPSLen;

            memcpy(pkt->data + pos, H264_NAL_START, NAL_START_LEN);
            pos += NAL_START_LEN;
            memcpy(pkt->data + pos, pPPS, gPPSLen);
            pos += (int) gPPSLen;

            memcpy(pkt->data + pos, H264_NAL_START, NAL_START_LEN);
            pos += NAL_START_LEN;
            memcpy(pkt->data + pos, packetStruct->data, packetStruct->data_size);
        } else {
            memcpy(pkt->data, H264_NAL_START, NAL_START_LEN);
            memcpy(pkt->data + NAL_START_LEN, packetStruct->data, packetStruct->data_size);
        }

        pkt->pts = packetStruct->timestamp;
        pkt->dts = packetStruct->timestamp;

        free(packetStruct->data);
        free(packetStruct);
        /* send the packet for decoding */
        ret = avcodec_send_packet(pVideoAVCodecCtx, pkt);
        //LOGD("%s send the video packet for decoding pkt size=%d", __FUNCTION__, pkt->size);

        av_packet_unref(pkt);
        av_packet_free(&pkt);

        if (ret < 0) {
            LOGE("%s Error sending the video pkt to the decoder ret=%d", __FUNCTION__, ret);
            usleep(sleepDelta);
            continue;
        } else {
            // 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
            while (ret >= 0) {
                ret = avcodec_receive_frame(pVideoAVCodecCtx, pFrame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    usleep(sleepDelta);
                    continue;
                } else if (ret < 0) {
                    LOGE("%s Error receive decoding video frame ret=%d", __FUNCTION__, ret);
                    usleep(sleepDelta);
                    continue;
                }

                pthread_mutex_lock(pFrameDataCallbackMutex);
                if (pFrameDataCallback != nullptr) {
                    // 解码固定为 AV_PIX_FMT_YUV420P
                    int planeNum = 3;
                    int yuvLens[planeNum];
                    yuvLens[0] = pFrame->linesize[0] * pFrame->height;
                    yuvLens[1] = pFrame->linesize[1] * pFrame->height / 2;
                    yuvLens[2] = pFrame->linesize[2] * pFrame->height / 2;
                    //LOGI("%s video onDataArrived", __FUNCTION__);
                    pFrameDataCallback->onDataArrived(StreamType::VIDEO,
                                                      (long long) pFrame->pts,
                                                      (char **) pFrame->data,
                                                      yuvLens,
                                                      planeNum,
                                                      -1,
                                                      -1,
                                                      pFrame->width,
                                                      pFrame->height);
                }

                pthread_mutex_unlock(pFrameDataCallbackMutex);

                av_frame_unref(pFrame);
            }
        }

    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TYYJ-洪伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值