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_Hextern "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_NV12par->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/4int 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:spsif (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:ppsif (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:IDRif (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 01memcpy(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_EOFwhile (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_YUV420Pint 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);}}}
}

FFmpeg 解码 H264 格式的视频相关推荐

  1. ffmpeg转h264格式的视频

    2019独角兽企业重金招聘Python工程师标准>>> ffmpeg版本0.10 ffmpeg -i source.f4v -ac 1 -ar 16000 -acodec pcm_a ...

  2. ffmpeg 切片花屏_利用ffmpeg解码H264,花屏,该如何解决

    利用ffmpeg解码H264,花屏 UINT CMP4File::VideoCap_Thread_Fun(void* pParam) { CMP4File  *pMP4File=(CMP4File*) ...

  3. ffmpeg解码H264缺少帧的解决办法

    最近用ffmpeg解码H264裸码流文件,发现解码总是少几帧.上网查了些资料,解决了. 当使用avcodec_decode_video2时,如果第三个参数的值为1,则表示完成一帧的解码,如果为0,表示 ...

  4. FFmpeg 解码 AAC 格式的音频

    FFmpeg 默认是可以解码 AAC 格式的音频,但是如果需要获取 PCM16 此类数据则需要经过音频转码.首先要打开解码器,然后向解码器发送 AAC 音频帧(不带 ADTS),然后从解码器获取解码后 ...

  5. Java 解码 H264 格式视频流中的图片

    引入依赖 <dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artif ...

  6. FFmpeg解码H264裸流并转换成opencv Mat

    感谢雷霄骅博士的在中文视频编解码的付出,http://blog.csdn.net/leixiaohua1020 最近要搞一些视频推流的事情,要解析H264裸流并且获取opencv格式的Mat数据给算法 ...

  7. 用ffmpeg 创建WMV格式的视频

    虽然FFmpeg是一个非常好的工具,它的默认操作并不是一定能达到要求.转换各种视频格式中,通常ffmpeg不用任何调整就可以生成质量不错的视频.但是处理Windows Media Video (WMV ...

  8. ffmpeg 将h264格式文件编码为MP4文件

    1.在编码过程中涉及到一些时间戳相关概念主要: PTS:度量解码后的视频帧什么时候被显示出来: DTS:读入内存中的bit流在什么时候开始送入解码器中进行解码: duration:表示当前帧的持续时间 ...

  9. (转)FFMPEG解码H264拼帧简解

    http://blog.csdn.net/ikevin/article/details/7649095 H264的I帧通常 0x00 0x00 0x00 0x01 0x67 开始,到下一个帧头开始之前 ...

  10. ffmpeg将mov格式的视频转换成mp4格式

    import os # 获取目录下的所有文件列表 import fnmatch # 文件格式筛选模块,筛选指定格式文件#遍历 def dirlist(path, allfile):filelist = ...

最新文章

  1. [NHibernate]事务
  2. 人工智能、大数据、云计算、机器学习和深度学习,主要有什么关系?
  3. python3和python2 优势_python3和python2的区别
  4. 实现阿里云容器镜像服务反向访问代理
  5. opencv mat赋值_【3】OpenCV图像处理模块(18)重映射
  6. 从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用
  7. 2020年已裸辞5个月(软文)
  8. ABAP 自建透明表维护
  9. CloudComputing是什么
  10. 《数据结构与算法分析》习题-----第二章(3)(关于list的题目)
  11. 努力只是因为想去做想做的事
  12. DNF私服之PVF修改-装备篇
  13. Python-opencv 图片颜色域的识别选取
  14. 电脑上查看自己连接的WIFI密码
  15. 【老生谈算法】matlab实现方位角计算源码——方位角计算
  16. App Store商店图片文案填写说明
  17. 英飞凌TC264学习(三)定时器
  18. ubuntu16.0.4bug无法解析域名
  19. 在Ubuntu20.04运行VINS-Fusion
  20. 2022世界杯期间,独立站卖家如何借势营销?

热门文章

  1. 基于Matlab/Simulink的1/4车辆系统动力学模型的两种建模方法(动力学建模入门知识)
  2. hashmap java 排序_Java 对HashMap进行排序的三种常见方法
  3. 数据库中状态表的设计
  4. navicat 简体中文破解版
  5. 布客·ApacheCN 编程/后端/大数据/人工智能学习资源 2022.2
  6. 基于灰狼优化算法的线性规划问题求解matlab程序
  7. typora下载安装步骤
  8. laravel8-使用jwt
  9. 数据包络分析-两阶段网络DEA(two stage network)
  10. BC26:使用MQTT对接阿里云平台