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
-
nal_ref_idc 不等于 0 时,NAL unit 的内容可能是 SPS/PPS/参考帧的片
-
nal_ref_idc 等于 0 时,NAL unit 的内容可能是非参考图像的片
-
当某个图像的片的 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 至关重要,还有一些必要的解码器参数也要设置。
- 调用 avcodec_find_decoder(…) 来查找 ID 为 AV_CODEC_ID_H264 的解码器;
- 调用 avcodec_alloc_context3(…) 分配解码器上下文 AVCodecContext;
- 调用 avcodec_parameters_alloc() 给 AVCodecParameters 结构体分配内存,用来设置解码器参数;
- 填充必要的解码参数到 AVCodecParameters,尤其 format,此处设置为 AV_PIX_FMT_YUV420P,表示要解码为 YUV420P 这种 YUV 类型;
- 调用 avcodec_parameters_to_context(…) 将解码器参数结构体的内容设置到解码器上下文中;
- 调用 avcodec_parameters_free(…) 释放解码器参数结构体占用的内存空间;
- 现在调用 avcodec_open2(…) 打开解码器;
- 调用 av_frame_alloc() 分配 AVFrame 结构体占用的内存空间,为后续解码提供便利。
解码流程
如果 H264 序列中包含 SPS 和 PPS,则先保存到全局变量中,在 I 帧的前面添加 SPS 和 PPS, 这是为了更好的提升兼容性,比如外面如果先设置了 SPS 和 PPS,而码流中没有,也不影响解码流程。
- 调用 avcodec_send_packet(…) 将待解码的数据包发送到解码器;
- 调用 avcodec_receive_frame(…) 接收解码后的数据包,编码和解码都是一样的,都是 send 1 次,然后 receive 多次,直到 AVERROR(EAGAIN) 或者 AVERROR_EOF;
- 对解码后得到的 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);
}
}
}
}