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单元中。

  1. VCL(Video Coding Layer):经过编码器压缩的数据。包括核心压缩引擎、宏块和片(Slice)的语法级别定义。VCL的设计目标是尽可能独立于网络进行高效的编码。
  2. 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

posted @   一只烤红薯  阅读(144)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示