NV12数据格式转H265编码格式实现过程

news2024/12/29 10:45:34

一、需求

在视频处理和传输应用中,将视频数据编码为高效的格式是非常重要的。H.265(也称为HEVC)是一种先进的视频编码标准,具有更好的压缩性能和图像质量,相比于传统的编码标准(如H.264),可以显著减少视频的带宽和存储需求。

NV12是一种常见的视频格式,用于表示YUV图像数据,尤其在实时视频处理中广泛使用。它将亮度(Y)和色度(UV)分量分开存储,其中Y分量占据连续的内存块,而UV分量交错存储在另一个连续的内存块中。

本需求实现将NV12格式的视频数据转换为H.265格式的数据,并将其保存在内存中。这样可以方便地对视频数据进行后续处理,如网络传输、存储或实时流媒体传输等。

为了实现这一需求,使用了C语言和FFmpeg库。FFmpeg是一个强大的开源多媒体处理库,提供了丰富的功能和编解码器,包括H.265编码器。

下面代码实现了如何使用FFmpeg库将NV12格式的视频数据编码为H.265格式的数据,并将其保存在内存中。函数接受NV12数据、宽度和高度作为输入,并返回编码后的H.265数据和数据大小。这样,用户可以方便地将NV12格式的视频数据转换为H.265格式,并在内存中进行进一步处理或传输。同时也提供了文件的方式。

这个功能可以在各种视频处理应用中使用,如视频编辑软件、实时视频流处理系统、视频通信应用等。通过使用H.265编码,可以提高视频传输的效率和质量,减少带宽和存储需求,同时保持良好的视觉体验。

image-20230720162116737

二、NV12和H265格式详细介绍

NV12和H265都是视频编码中经常使用的像素格式,下面分别介绍这两种格式的特点和使用场景。

【1】NV12像素格式

NV12是一种YUV像素格式,常用于视频编码和解码过程中。它是一种planar格式,即Y和UV分量分别存储在不同的平面中。其中,Y分量表示亮度信息,UV分量表示色度信息。在NV12格式中,UV分量的采样率为4:2:0,即每4个Y像素共用一个U和一个V像素。这种采样方式可以有效地减小数据量,同时保持视频质量。

NV12格式的存储顺序为:先存储所有的Y分量,然后存储所有的U和V分量,U和V交错存储。因此,NV12格式的数据大小为(1.5 x 图像宽度 x 图像高度)字节。

NV12格式常用于视频流传输和视频编解码器中,例如在H.264视频编解码器和DirectShow视频开发中都广泛使用。

【2】H265像素格式

H265(又称HEVC)是一种高效的视频编码标准,它可以在相同视频质量的情况下大幅度减小视频文件的大小。H265支持多种像素格式,其中最常用的是YUV 4:2:0和YUV 4:2:2。

YUV 4:2:0格式与NV12类似,也是一种planar格式,其中Y分量存储亮度信息,UV分量采用4:2:0采样方式存储色度信息。YUV 4:2:2格式则采用4:2:2的采样方式存储UV分量,即每2个Y像素共用一个U和一个V像素。

与H264相比,H265的主要改进在于更高的压缩率和更低的比特率,同时保持相同质量的视频输出。因此,H265格式可以在同样的视频质量下使用更低的比特率进行编码,达到更小的文件大小。H265格式常用于网络视频流媒体传输、4K和8K高清视频等领域。

三、代码实现

【1】内存数据处理

要将NV12格式的数据转换为H.265格式的数据并保存在内存中,可以使用FFmpeg库来实现编码操作。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <libavcodec/avcodec.h>

// 将NV12数据编码为H.265并保存在内存中
int encodeNV12toH265(uint8_t* nv12Data, int width, int height, uint8_t** h265Data, int* h265Size) {
    int ret = 0;
    AVCodec* codec = NULL;
    AVCodecContext* codecContext = NULL;
    AVFrame* frame = NULL;
    AVPacket* packet = NULL;

    // 注册所有的编解码器
    avcodec_register_all();

    // 查找H.265编码器
    codec = avcodec_find_encoder(AV_CODEC_ID_H265);
    if (!codec) {
        fprintf(stderr, "找不到H.265编码器\n");
        ret = -1;
        goto cleanup;
    }

    // 创建编码器上下文
    codecContext = avcodec_alloc_context3(codec);
    if (!codecContext) {
        fprintf(stderr, "无法分配编码器上下文\n");
        ret = -1;
        goto cleanup;
    }

    // 配置编码器参数
    codecContext->width = width;
    codecContext->height = height;
    codecContext->pix_fmt = AV_PIX_FMT_NV12;
    codecContext->codec_id = AV_CODEC_ID_H265;
    codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
    codecContext->time_base.num = 1;
    codecContext->time_base.den = 25; // 假设帧率为25fps

    // 打开编码器
    if (avcodec_open2(codecContext, codec, NULL) < 0) {
        fprintf(stderr, "无法打开编码器\n");
        ret = -1;
        goto cleanup;
    }

    // 创建输入帧
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "无法分配输入帧\n");
        ret = -1;
        goto cleanup;
    }

    // 设置输入帧的属性
    frame->format = codecContext->pix_fmt;
    frame->width = codecContext->width;
    frame->height = codecContext->height;

    // 分配输入帧的数据缓冲区
    ret = av_frame_get_buffer(frame, 32);
    if (ret < 0) {
        fprintf(stderr, "无法分配输入帧的数据缓冲区\n");
        ret = -1;
        goto cleanup;
    }

    // 将NV12数据复制到输入帧的数据缓冲区
    memcpy(frame->data[0], nv12Data, width * height); // Y分量
    memcpy(frame->data[1], nv12Data + width * height, width * height / 2); // UV分量

    // 创建输出数据包
    packet = av_packet_alloc();
    if (!packet) {
        fprintf(stderr, "无法分配输出数据包\n");
        ret = -1;
        goto cleanup;
    }

    // 发送输入帧给编码器
    ret = avcodec_send_frame(codecContext, frame);
    if (ret < 0) {
        fprintf(stderr, "无法发送输入帧给编码器\n");
        ret = -1;
        goto cleanup;
    }

    // 循环从编码器接收输出数据包
    while (ret >= 0) {
        ret = avcodec_receive_packet(codecContext, packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {
            fprintf(stderr, "从编码器接收输出数据包时发生错误\n");
            ret = -1;
            goto cleanup;
        }

        // 分配内存并复制输出数据包的数据
        *h265Data = (uint8_t*)malloc(packet->size);
        if (!*h265Data) {
            fprintf(stderr, "无法分配内存\n");
            ret = -1;
            goto cleanup;
        }
        memcpy(*h265Data, packet->data, packet->size);
        *h265Size = packet->size;

        // 释放输出数据包
        av_packet_unref(packet);
    }

cleanup:
    // 释放资源
    if (packet)
        av_packet_free(&packet);
    if (frame)
        av_frame_free(&frame);
    if (codecContext)
        avcodec_free_context(&codecContext);

    return ret;
}

使用以上的封装函数,可以将NV12格式的数据传入函数中,函数会将其编码为H.265格式的数据并保存在内存中。编码后的H.265数据存储在h265Data指针指向的内存中,数据的大小保存在h265Size中。

【2】文件数据处理

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>

// 将NV12数据编码为H.265格式
int encodeNV12ToH265(const char* nv12FilePath, const char* h265FilePath, int width, int height)
{
    // 注册所有的编解码器
    avcodec_register_all();

    // 打开编码器
    AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H265);
    if (!codec) {
        fprintf(stderr, "Failed to find H.265 encoder\n");
        return -1;
    }

    AVCodecContext* codecContext = avcodec_alloc_context3(codec);
    if (!codecContext) {
        fprintf(stderr, "Failed to allocate codec context\n");
        return -1;
    }

    // 配置编码器参数
    codecContext->width = width;
    codecContext->height = height;
    codecContext->time_base = (AVRational){1, 25};  // 帧率为25fps
    codecContext->framerate = (AVRational){25, 1};
    codecContext->pix_fmt = AV_PIX_FMT_NV12;

    // 打开编码器上下文
    if (avcodec_open2(codecContext, codec, NULL) < 0) {
        fprintf(stderr, "Failed to open codec\n");
        return -1;
    }

    // 创建输入文件的AVFrame
    AVFrame* frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Failed to allocate frame\n");
        return -1;
    }

    frame->format = codecContext->pix_fmt;
    frame->width = codecContext->width;
    frame->height = codecContext->height;

    // 分配输入帧缓冲区
    int ret = av_frame_get_buffer(frame, 32);
    if (ret < 0) {
        fprintf(stderr, "Failed to allocate frame buffer\n");
        return -1;
    }

    // 打开输入文件
    FILE* nv12File = fopen(nv12FilePath, "rb");
    if (!nv12File) {
        fprintf(stderr, "Failed to open NV12 file\n");
        return -1;
    }

    // 打开输出文件
    FILE* h265File = fopen(h265FilePath, "wb");
    if (!h265File) {
        fprintf(stderr, "Failed to open H.265 file\n");
        return -1;
    }

    // 创建编码用的AVPacket
    AVPacket* packet = av_packet_alloc();
    if (!packet) {
        fprintf(stderr, "Failed to allocate packet\n");
        return -1;
    }

    int frameCount = 0;
    while (1) {
        // 从输入文件读取NV12数据到AVFrame
        if (fread(frame->data[0], 1, width * height, nv12File) != width * height) {
            break;
        }
        if (fread(frame->data[1], 1, width * height / 2, nv12File) != width * height / 2) {
            break;
        }

        frame->pts = frameCount++;  // 设置帧的显示时间戳

        // 发送AVFrame到编码器
        ret = avcodec_send_frame(codecContext, frame);
        if (ret < 0) {
            fprintf(stderr, "Error sending frame to codec: %s\n", av_err2str(ret));
            return -1;
        }

        // 从编码器接收编码后的数据包
        while (ret >= 0) {
            ret = avcodec_receive_packet(codecContext, packet);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                fprintf(stderr, "Error receiving packet from codec: %s\n", av_err2str(ret));
                return -1;
            }

            // 将编码后的数据包写入输出文件
            fwrite(packet->data, 1, packet->size, h265File);
            av_packet_unref(packet);
        }
    }

    // 刷新编码器
    ret = avcodec_send_frame(codecContext, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error sending flush frame to codec: %s\n", av_err2str(ret));
        return -1;
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(codecContext, packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            fprintf(stderr, "Error receiving packet from codec: %s\n", av_err2str(ret));
            return -1;
        }

        // 将编码后的数据包写入输出文件
        fwrite(packet->data, 1, packet->size, h265File);
        av_packet_unref(packet);
    }

    // 释放资源
    fclose(nv12File);
    fclose(h265File);
    av_frame_free(&frame);
    av_packet_free(&packet);
    avcodec_free_context(&codecContext);

    return 0;
}

int main()
{
    const char* nv12FilePath = "input.nv12";
    const char* h265FilePath = "output.h265";
    int width = 1920;
    int height = 1080;

    int ret = encodeNV12ToH265(nv12FilePath, h265FilePath, width, height);
    if (ret < 0) {
        fprintf(stderr, "Failed to encode NV12 to H.265\n");
        return -1;
    }

    printf("Encoding complete\n");

    return 0;
}

要确保已经正确安装了FFmpeg库,并在编译选项中包含了FFmpeg的头文件和库文件。

需要提供NV12格式的输入文件路径、输出H.265格式文件路径以及图像的宽度和高度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARTS 2023.8.28-2023.9.03 (第二周)

ARTS 2023.8.28-2023.9.03 &#xff08;第二周&#xff09; &#x1f4a1;ARTS&#xff1a; A&#xff1a;至少每周完成一道Leecode的算法题&#xff1b; R&#xff1a;阅读并点评至少一篇英文技术文章&#xff1b; T&#xff1a;学习至少一个技术技巧&#xff1b; S&#xff…

【计算机基础知识4】网络通信协议:TCP、UDP、WebSockets

目录 一、TCP&#xff08;传输控制协议&#xff09; 1. TCP的特点 2. TCP的连接建立和终止 3. TCP的可靠性机制 4. TCP的流量控制 二、UDP&#xff08;用户数据报协议&#xff09; 1. UDP的特点 2. UDP的使用场景 三、WebSockets 1. WebSockets协议的特点 2. WebSock…

二分搜索树深度优先遍历(Java 实例代码)

目录 二分搜索树深度优先遍历 Java 实例代码 src/runoob/binary/Traverse.java 文件代码&#xff1a; 二分搜索树深度优先遍历 二分搜索树遍历分为两大类&#xff0c;深度优先遍历和层序遍历。 深度优先遍历分为三种&#xff1a;先序遍历&#xff08;preorder tree walk&am…

严选算法模型质量保障

在算法模型整个生命周期**&#xff08;算法模型生命周期&#xff1a;初始训练数据 --> 模型训练 --> 模型评估 --> 模型预估 --> 训练数据&#xff09;**中&#xff0c;任何环节的问题引入都可能导致算法模型质量问题。所以我们在做模型质量保障的过程中&#xff0…

【日常笔记】使用Server过程中可能遇到的一些问题

使用Server过程中可能遇到的一些问题 1. 如何查找GPU型号与驱动版本之间的关系&#xff1f;2. 如何查看当前Server的内核版本&#xff1f;3. 使用Nvidia过程中可能用到的命令4. 对Jupyter Notebook的一些配置5. TensorFlow的一般操作6. 使用PyTorch的一些操作7. 修改安装源为国…

【漏洞复现】网互联路由器存在密码泄露

漏洞描述 蜂网互联-让链接无限可能&#xff0c;灵活的多线分流&#xff0c;强大的策略分流&#xff0c;灵活调度各种软件应用&#xff0c;深度识别系统&#xff0c;各种应用一网打尽&#xff0c;灵活调整优先级&#xff0c;最简单的路由器&#xff0c;简洁易学的配置&#xff…

MySQL——多表查询

多表查询 多表查询的出现&#xff0c;是为了解决当我们的数据不能存放在一张表上&#xff0c;或者我们的数据本身就是存在多张表上&#xff0c;需要根据字段之间的关系&#xff0c;联合多张表查询出想要的数据。那么根据业务实现的关系&#xff0c;表与表之前也出现了三种基本…

构建微服务项目时启动网关服务失败的解决方案

启动网关服务时报“Unable to create the temporary folder: C:\WINDOWS\TEMP\/nio-file-upload”错误。 代码与之前没有任何变化&#xff0c;但就是启动不了&#xff0c;观察错误意思大概是不能创建临时文件夹&#xff1a;C盘下的WINDOWS下的TEMP目录下的nio-file-upload这个东…

C#,数值计算——多项式微分(Binomial Deviates)的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// 二项式偏差 /// Binomial Deviates /// </summary> public class Binomialdev : Ran { private double pp { get; set; } private double p…

day57 补

647. 回文子串 力扣题目链接(opens new window) 给定一个字符串&#xff0c;你的任务是计算这个字符串中有多少个回文子串。 具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 示例 1&#xff1a; 输入&#xff1a…

机器学习——boosting之提升树

提升树和adaboost基本流程是相似的 我看到提升树的时候&#xff0c;懵了 这…跟adaboost有啥区别&#xff1f;&#xff1f;&#xff1f; 直到看到有个up主说了&#xff0c;我才稍微懂 相当于&#xff0c;我在adaboost里的弱分类器&#xff0c;换成CART决策树就好了呗&#xff1…

Yolov8-pose关键点检测:模型轻量化创新 | ​BiLevelRoutingAttention 动态稀疏注意力 | CVPR2023 BiFormer

💡💡💡本文解决什么问题:BiLevelRoutingAttention ,通过双层路由(bi-level routing)提出了一种新颖的动态稀疏注意力(dynamic sparse attention ) ​BiLevelRoutingAttention | GFLOPs从9.6降低至8.5,参数量从6482kb降低至6134kb, mAP50从0.921提升至0.926 Yolov8…

云备份服务端——实用类工具实现

一&#xff0c;文件实用类设计实现 不管是客户端还是服务端&#xff0c;文件的传输备份都涉及到文件的读写&#xff0c;包括数据管理信息的持久化也是如此&#xff0c;因此首先设计封装文件操作类&#xff0c;这个类封装完毕之后&#xff0c;则在任意模块中对文件进行操作时都将…

SLAM ORB-SLAM2(1)总体框架

SLAM ORB-SLAM2(1)总体框架 1. 简介2. 框架3. TRACKING4. LOCAL MAPPING5. LOOP CLOSING6. MAP1. 简介 ORB-SLAM2 是一个实时和完整的视觉SLAM系统(包括闭环检测、重定位、地图重用等功能) 提供了利用单目、双目以及RGB-D相机完成稀疏三维重建的功能和接口 2. 框架 总体来说…

c++day3

1> 自行封装一个栈的类&#xff0c;包含私有成员属性&#xff1a;栈的数组、记录栈顶的变量 成员函数完成&#xff1a;构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小 #include <iostream>using namespace std; clas…

baichuan2(百川2)本地部署的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Vue3,Typescript中引用组件路径无法找到模块报错

是这么个事&#xff0c;我在vue3新创建的项目里&#xff0c;写了个组件叫headerIndex.vue&#xff0c;放到app.vue中import就会报错 路径肯定没写错&#xff0c;找到了解决方法&#xff0c;但是也没想明白为什么 解决方法如下 在vite-env.d.ts文件中加入 declare module &qu…

《向量数据库》——向量数据库Milvus 和大模型出联名款AI原生Milvus Cloud

大模型技术的发展正加速对千行百业的改革和重塑,向量数据库作为大模型的海量记忆体、云计算作为大模型的大算力平台,是大模型走向行业的基石。而电商行业因其高度的数字化程度,成为打磨大模型的绝佳“战场”。 在此背景下,Zilliz 联合亚马逊云科技举办的【向量数据库 X 云计…

Java类和对象(七千字详解!!!带你彻底理解类和对象)

目录 一、面向对象的初步认知 1、什么是面向对象 2、面向对象和面向过程 &#xff08;1&#xff09;传统洗衣服的过程 &#xff08;2&#xff09;现代洗衣服过程 ​编辑 二、类的定义和使用 1、类的定义格式 三、类的实例化 1、什么是实例化 2、类和对象说明 四、t…

【2023年11月第四版教材】第11章《成本管理》(合集篇)

第11章《成本管理》&#xff08;合集篇&#xff09; 1 章节说明2 管理基础3 管理过程3.1 管理ITTO汇总★★★ 4 规划成本管理4.1 成本管理计划★★★ 5 估算成本5.1 估算成本★★★ &#xff08;19上57&#xff09; &#xff08;19下35&#xff09;5.2 数据分析★★★5.4 成本估…