美文网首页Android技术知识Android开发Android开发经验谈
从I帧到B帧,H.264编码技术为您构建画面与效果完美结合的视觉

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

作者: 谁动了我的代码 | 来源:发表于2023-05-06 21:53 被阅读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 的编码器使用的技术非常复杂,其中每一步都有很多细节,可以根据实际应用场景对编码器进行调优和优化。

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/orzcsdtx.html