Android技术知识Android开发Android开发经验谈

从I帧到B帧,H.264编码技术为您构建画面与效果完美结合的视觉

2023-05-06  本文已影响0人  谁动了我的代码

H264之帧编码

H.264,也称为 MPEG-4 AVC (Advanced Video Coding),是一种高效的视频编码标准,用于压缩和存储视频。H.264 利用了预测编码和变换编码等先进的技术,其编码流程与普通视频编码类似,主要包括帧类型判定、运动估计、变换编码、熵编码等步骤。

H.264 的帧编码流程:

帧类型判断

首先,H.264 编码器需要确定当前帧属于哪种类型:I 帧、P 帧还是 B 帧。同样,I 帧是关键帧,P 帧和 B 帧是预测帧。

运动估计

对于 P 帧和 B 帧,H.264 编码器需要进行运动估计来找到当前帧和之前帧之间的运动信息。运动估计时会将待编码帧与之前一定长度的参考帧进行比较,以检测两帧之间的运动差异。

变换编码

H.264 编码器将经过运动估计的差异像素块(残差块)输入到离散余弦变换器 (DCT),将空间信息转换为频域信息。DCT 块的大小可以为 4x4、8x8 或其他尺寸,以匹配不同视频帧的压缩需求。变换编码可以有效地消除视频框架中的局部冗余。

量化

对于经过 DCT 变换之后的残差块,H.264 编码器会对其进行量化。量化通过将块的值除以一个预定义的值并四舍五入来减少数据量。不同的量化矩阵可以用于不同的视频帧,以调整视频质量和压缩率之间的平衡。

熵编码

最后,H.264 编码器将量化后的数据输出到熵编码器,以进一步减少数据量。熵编码是一种将数据序列转换为短码的技术,可以通过对于经常出现的数据序列使用较短的编码来减少数据量。H.264 使用 CABAC 或 CAVLC (Context-based Adaptive Variable Length Coding) 等熵编码方案。

I帧 P帧 B帧编码流程

I帧、P帧和B帧是视频编码中常用的三种帧类型,用于压缩和存储视频。它们的编码流程如下:

I帧编码流程

I (Intra-picture) 帧可以理解为关键帧,它是视频序列中的第一个帧或关键帧。每个 I 帧的编码是独立的,它包含了所有像素的数据,没有依赖其他的帧。因此,I 帧的压缩率不能很好地发挥。

I帧的编码流程如下:

  1. 图像分块:I 帧首先将原始图像分成若干个小块。
  2. DCT 变换:对于每个分块,将其进行离散余弦变换 (DCT),以转换为频域信号。
  3. 量化:对于每个分块,将 DCT 变换之后的频域系数进行量化。量化也是压缩图像的关键步骤之一。
  4. 熵编码:使用熵编码技术对量化后的数据进行编码,以进一步减少数据量。编码之后的结果就是 I 帧的输出。

P帧编码流程

P (Predicted picture) 帧是预测帧,它根据前一帧的参考像素进行编码。P 帧只编码与前一帧之间的像素差异,以从时间维度上减少视频数据的冗余。

P帧的编码流程如下:

  1. 运动估计:首先使用运动估计算法对前一帧和当前帧进行比较,获取两帧之间的运动信息。
  2. 帧间预测:利用得到的运动信息,在前一帧中对应的位置上获取像素值,从而对当前帧进行预测。
  3. 残差编码:将用预测帧预测出的图像和实际的图像进行比较,得到两个图像之间的差异。对这些差异进行编码并输出 P 帧。

B帧编码流程

B (Bidirectionally predictive picture) 帧是双向预测帧,该帧通过前后两个关键帧的像素值进行估计编码。B 帧同样只编码与前后帧之间的像素差异,以后缩小视频数据量。

B帧的编码流程如下:

  1. 运动估计:B 帧通常需要借助前一帧和后一帧进行运动估计。
  2. 帧间预测:参考前后两个关键帧,对当前帧进行帧间预测。
  3. 残差编码:与 P 帧相同,对用预测帧预测出的图像和实际的图像进行比较,得到两个图像之间的差异。对这些差异进行编码并输出 B 帧。

I帧 P帧 B帧编码流程代码示例分析

I帧、P帧、B帧是视频帧编码的重要概念,也是H.264视频编码的核心技术。它们在视频编码中发挥着不同的作用,对于视频质量和压缩率都有着不同的影响。下面是一个使用FFmpeg进行视频编码的I帧、P帧、B帧的编码流程代码示例分析。

#include <stdlib.h>
#include <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

// 帧频
#define FRAME_RATE 25

// 视频宽高
#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
#define VIDEO_PIX_FMT AV_PIX_FMT_YUV420P

int main(int argc, char **argv)
{
    // 初始化FFmpeg
    av_register_all();

    // 分配AVFormatContext和AVOutputFormat
    AVFormatContext *pFormatCtx;
    avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, "output.mp4");

    // 查找视频编码器(H.264)
    AVCodec *pCodec;
    pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!pCodec)
    {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    // 分配AVStream
    AVStream *pStream;
    pStream = avformat_new_stream(pFormatCtx, NULL);
    if (!pStream)
    {
        fprintf(stderr, "Could not allocate stream\n");
        exit(1);
    }

    // 设置编码器参数
    AVCodecContext *pCodecCtx;
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx)
    {
        fprintf(stderr, "Could not allocate codec context\n");
        exit(1);
    }
    pCodecCtx->bit_rate = 400000;
    pCodecCtx->width = VIDEO_WIDTH;
    pCodecCtx->height = VIDEO_HEIGHT;
    pCodecCtx->time_base = (AVRational){1, FRAME_RATE};
    pCodecCtx->framerate = (AVRational){FRAME_RATE, 1};
    pCodecCtx->gop_size = 10;
    pCodecCtx->max_b_frames = 1;
    if (pFormatCtx->oformat->flags & AVFMT_GLOBALHEADER)
    {
        pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开编码器并写入头文件
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
    if (avcodec_parameters_from_context(pStream->codecpar, pCodecCtx) < 0)
    {
        fprintf(stderr, "Could not copy the codec parameters to the output stream\n");
        exit(1);
    }
    if (avformat_write_header(pFormatCtx, NULL) < 0)
    {
        fprintf(stderr, "Error occurred when opening output file\n");
        exit(1);
    }

    // 准备输入的数据
    AVFrame *src_frame;
    uint8_t *src_data[4];
    int src_linesize[4];
    int ret;
    src_frame = av_frame_alloc();
    if (!src_frame)
    {
        fprintf(stderr, "Could not allocate source video frame\n");
        exit(1);
    }
    src_frame->format = VIDEO_PIX_FMT;
    src_frame->width = VIDEO_WIDTH;
    src_frame->height = VIDEO_HEIGHT;
    ret = av_image_alloc(src_frame->data, src_frame->linesize, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_PIX_FMT, 16);
    if (ret < 0)
    {
        fprintf(stderr, "Could not allocate source image\n");
        exit(1);
    }
    src_data[0] = src_frame->data[0];
    src_data[1] = src_frame->data[1];
    src_data[2] = src_frame->data[2];
    src_linesize[0] = src_frame->linesize[0];
    src_linesize[1] = src_frame->linesize[1];
    src_linesize[2] = src_frame->linesize[2];

    // 编码视频帧
    int64_t pts = 0;
    int i;
    for (i = 0; i < 100; i++)
    {
        // 准备一帧YUV420P数据
        int j, k;
        for (j = 0; j < VIDEO_HEIGHT; j++)
        {
            for (k = 0; k < VIDEO_WIDTH; k++)
            {
                src_data[0][j * src_linesize[0] + k] = (uint8_t)(j + i * 3);
            }
        }
        for (j = 0; j < VIDEO_HEIGHT / 2; j++)
        {
            for (k = 0; k < VIDEO_WIDTH / 2; k++)
            {
                src_data[1][j * src_linesize[1] + k] = (uint8_t)(j + i * 2);
                src_data[2][j * src_linesize[2] + k] = (uint8_t)(j + i * 5);
            }
        }

        // 编码一帧数据
        AVPacket pkt;
        av_init_packet(&pkt);
        pkt.data = NULL;
        pkt.size = 0;
        src_frame->pts = pts++;
        ret = avcodec_send_frame(pCodecCtx, src_frame);
        if (ret < 0)
        {
            fprintf(stderr, "Error sending a frame for encoding\n");
            exit(1);
        }
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(pCodecCtx, &pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            {
                break;
            }
            if (ret < 0)
            {
                fprintf(stderr, "Error during encoding\n");
                exit(1);
            }

            // 写入帧数据
            pkt.stream_index = pStream->index;
            av_packet_rescale_ts(&pkt, pCodecCtx->time_base, pStream->time_base);
            ret = av_interleaved_write_frame(pFormatCtx, &pkt);
            if (ret < 0)
            {
                fprintf(stderr, "Error while writing video frame\n");
                exit(1);
            }
            av_packet_unref(&pkt);
        }
    }

    // 输入数据完成,写入尾部文件
    av_write_trailer(pFormatCtx);

    // 清理工作
    avcodec_close(pCodecCtx);
    avcodec_free_context(&pCodecCtx);
    av_frame_free(&src_frame);
    avio_closep(&pFormatCtx->pb);
    avformat_free_context(pFormatCtx);
    return 0;
}

该示例代码演示了如何使用FFmpeg对I帧、P帧、B帧进行编码,其中主要涉及到以下步骤:

  1. 查找编码器(H.264)
  2. 分配AVStream和AVCodecContext,并设置编码器参数
  3. 打开编码器并写入头文件
  4. 准备输入的数据
  5. 编码视频帧
  6. 写入帧数据
  7. 输入数据完成,写入尾部文件

在该示例代码中,通过avcodec_send_frame()和avcodec_receive_packet()函数完成了I帧、P帧、B帧的编码和输出。其中,通过pkt.flags判断了输出的帧类型是否为关键帧(I帧)。根据帧类型的不同,编码的方法也有所不同。I帧为关键帧,不依赖于其他帧;P帧为前向预测帧,依赖于前一关键帧或前一P帧;B帧为双向预测帧,依赖于前后的关键帧或P帧。因此,在编码视频帧时,需要对帧类型进行判断后再进行相应的编码,才能保证最终输出的视频流是高质量的。

小结

主要内容有 SystemUI车机音量控制注意事项、SystemUI车机音量控制的营销文章标题和H.264的帧编码流程。

在使用SystemUI实现车机音量控制时,需要注意权限、音量类型、音量范围、同步更新通知、提高适应性和双向同步等方面,并提供最佳用户体验。

SystemUI车机音量控制的营销文章标题可以搭配精美的图片和详细的介绍,吸引用户的眼球,让用户更愿意了解这个功能并尝试使用。

H.264的帧编码流程包括帧类型判定、运动估计、变换编码、量化和熵编码等步骤。通过I帧、P帧和B帧的编码,H.264能够准确预测和描述视频帧之间的差异,达到高效压缩和存储视频的目的。优化编码器的每一步操作,可以得到更好的视频质量和更高的压缩率。


更多有关音视频的学习资料可以参考《音视频开发从0到1精通手册》里面记录有几百个技术知识点,7个板块来帮助你快速进入音视频领域。

总结

通过 I 帧、P 帧和 B 帧的编码,H.264 能够准确预测和描述视频帧之间的差异,从而达到高效压缩和存储视频的目的。H.264 的编码器使用的技术非常复杂,其中每一步都有很多细节,可以根据实际应用场景对编码器进行调优和优化。

上一篇下一篇

猜你喜欢

热点阅读