H.264
H.264 简介
H.264,是 ISO 和 ITU 共同开发的一个数字视频编码标准,目前是最常用的视频编码格式之一。优点如下:
- 低码率:与 MPEG2 和 MPEG4 ASP 等压缩技术相比,在同等图像质量下,采用 H.264 技术压缩后的数据量只有 MPEG2 的 1/8,MPEG4 的 1/3;
- 高编码效率:同 H.263 等标准编码效率相比,能够平均节约大约50%的码率;
- 高图像质量:H.264 能够提供连续、流畅的高质量图像;
- 容错能力强:H.264 提供了解决在不稳定网络环境下容易发生的丢包等错误的必要工具;
- 网络适应性强:H.264 提供了网络抽象层(Network Abstraction Layer, NAL),使得 H.264 的文件能容易地在不同网络上传输。
首先给出ffmpeg解封装命令,用于从音视频文件中提取 H.264 视频流:
// 使用 ffmpeg 命令提取 h.264
ffmpeg -i ./水流众生.mp4 -an -vcodec libx264 ./水流众生.h264
// 使用 ffmpeg 命令提取 h.264, h.264 的封装格式采用 annexb。
// 注意:这里封装的意思不是 muxer, 而是 h.264 数据的一种组织方式。
ffmpeg -i ./功夫熊猫.mp4 -an -vcodec copy -bsf: h264_mp4toannexb ./功夫熊猫.h264
// 使用 ffplay 播放
ffplay ./水流众生.h264
然后给出代码实现:
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavcodec/bsf.h"
#include <stdio.h>
/**
* step 1: 打开媒体文件 avformat_open_input
* step 2: 获取码流信息 av_format_find_stream_info
* step 3: 获取视频流 av_find_best_stream
* step 4: 读取 packet 数据 av_read_frame
* step 5: 释放 packet 资源 av_packet_unref
* step 6: 关闭媒体文件 avformat_close_input
*/
int main(int argc, char* argv[])
{
av_log_set_level(AV_LOG_DEBUG);
if (argc != 3)
{
av_log(NULL, AV_LOG_ERROR, "Usage: %s <infileName> <outfileName>\n", argv[0]);
exit(-1);
}
const char* infileName = argv[1];
const char* outfileName = argv[2];
AVFormatContext* inFmtCtx = NULL;
// 1. 打开媒体文件, 主要是探测协议类型, 如果是 网络文件 则创建网络连接
int ret = avformat_open_input(&inFmtCtx, infileName, NULL, NULL);
if (ret != 0)
{
av_log(NULL, AV_LOG_ERROR, "open input file format failed: %s\n", av_err2str(ret));
exit(-1);
}
// 2. 获取码流信息
ret = avformat_find_stream_info(inFmtCtx, NULL);
if (ret != 0)
{
av_log(NULL, AV_LOG_ERROR, "find input stream info failed: %s\n", av_err2str(ret));
ret = -1;
goto fail;
}
// 3. 获取视频流
ret = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "find best stream index failed.\n");
ret = -1;
goto fail;
}
// 视频流索引
int videoIndex = ret;
// 打开文件,用于存储提取后的视频流数据
FILE* dest_fp = fopen(outfileName, "wb+");
if (dest_fp == NULL)
{
av_log(NULL, AV_LOG_ERROR, "open %s file failed.\n", outfileName);
ret = -1;
goto fail;
}
AVPacket packet;
// 4. 读取 packet 数据
while (av_read_frame(inFmtCtx, &packet) == 0)
{
// 视频流数据
if (packet.stream_index == videoIndex)
{
int writeSize = fwrite(packet.data, 1, packet.size, dest_fp);
if (writeSize != packet.size)
{
av_packet_unref(&packet);
ret = -1;
break;
}
}
// 5. 释放 packet 资源
av_packet_unref(&packet);
}
fail:
// 6. 关闭媒体文件
if (inFmtCtx)
avformat_close_input(&inFmtCtx);
if (dest_fp)
fclose(dest_fp);
return ret;
}
1.编译并运行上述代码,解封装测试文件(oppenheimer.ts):
使用 ffpaly 播放:
从 ts 容器解封装出的 h.264 文件正常播放。
2.编译并运行上述代码,解封装测试文件(oppenheimer.mp4):
使用 ffplay 播放:
从 mp4 容器中解封装出的 h.264 文件无法正常播放。
造成上述问题的原因在于 H.264 的编码存储格式。
H.264 编码存储格式
H.264 通常有两种 编码存储格式:Annexb 和 AVCC。
Annexb 格式
Annexb 是 HEVC(H.265)/AVC(H.264) 参考标准。由 Start Code(0x000001 或 0x00000001) + NALU 数据构成,常用于实时流传输,TS、AVI 封装格式中的编码视频流通常采用这种方式存储。
AVCC 格式
AVCConfiguration 是 MP4 方式的存储格式 。 由 NALU 长度 + NALU 数据构成,适合 存储,常见于 MP4、FLV、MKV 容器中编码视频流。
ffmpeg 的解码器支持 Annexb 封装格式,不支持 AVCC 封装格式(缺少解码过程中依赖的信息,如SPS,PPS等),需要把 AVCC 转成 Annexb 封装格式。
- SPS(Sequence Parameter Set):序列参数集,包含了解码器配置和帧率等信息;
- PPS(Picture Parameter Set):图像参数集,包含了熵编码模式,Slice groups,motion prediction和去块滤波器控制等信息。
为什么需要 SPS 和 PPS?
- 解码器需要在码流中间开始解码;
- 编码器在编码的过程中改变了码流的参数(如图像分辨率等)。
h264_mp4toannexb
: Convert an H.264 bitstream from length prefixed mode to start code prefixed mode (as defined in the Annexb of the ITU-T H.264 specification).
NALU
NALU(Network Abstraction Layer Unit)是H.264标准中的一个重要概念。H.264的原始码流(裸流)由一个个NALU组成,它的功能分为两层:视频编码层(VCL)和网络抽象层(NAL)。在VCL数据传输或存储之前,编码的VCL数据需要先被映射或封装进NAL单元中。
- VCL(Video Coding Layer):经过编码器压缩的数据。包括核心压缩引擎、宏块和片(Slice)的语法级别定义。VCL的设计目标是尽可能独立于网络进行高效的编码。
- NAL(Network Abstract Layer):负责将 VCL 产生的比特字符串适配到各种各样的网络和多元环境中。简单来说就是将编码完毕的内容打包成符合 H.264 格式数据流。
一帧图像经过 H.264 编码器之后,就被编码为一个或多个 Slice ,每个 Slice 最后都会产生一个 NALU。但是并不是 NALU 内就一定是切片,因为 NALU 还有可能装载着其它用作描述视频的信息。NALU 由 nalu header 和 nalu payload 两个部分组成。
参考资料
[1]. 《深入理解FFmpeg》
[2]. 开课吧周李老师课程
[3]. https://blog.csdn.net/GrayOnDream/article/details/134903922
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)