返回

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

后端

前言

随着视频处理和传输应用的飞速发展,对视频数据的压缩和传输效率提出了更高的要求。H.265(也称为HEVC)是一种先进的视频编码标准,它具有更好的压缩性能和图像质量,相比于传统的编码标准(如H.264),在相同质量下,H.265可以将比特率降低50%以上。

NV12是一种常见的视频数据格式,它将亮度和色度信息分别存储在不同的内存空间中,亮度信息存储在Y平面,色度信息存储在UV平面,这种格式的优点是亮度和色度信息可以独立处理,提高了处理效率。

实现过程

将NV12数据格式转换为H265编码格式的过程可以分为以下几个步骤:

  1. 初始化编码器 :首先需要初始化H.265编码器,设置编码器的参数,如比特率、帧率、分辨率等。

  2. 读取NV12数据 :从视频源读取NV12格式的视频数据,这些数据通常存储在内存中或文件中。

  3. 转换NV12到YUV420P :H.265编码器要求输入的视频数据为YUV420P格式,因此需要将NV12格式的视频数据转换为YUV420P格式。

  4. 编码YUV420P数据 :将转换后的YUV420P数据输入到H.265编码器,编码器会根据设置的参数对数据进行编码。

  5. 输出H265码流 :编码器将编码后的数据输出为H.265码流,该码流可以存储在文件中或通过网络传输。

代码实现示例

以下是一个使用FFmpeg库实现NV12数据格式转换为H265编码格式的代码示例:

#include <stdio.h>
#include <stdlib.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

int main()
{
    // 初始化FFmpeg
    av_register_all();

    // 打开输入文件
    AVFormatContext *input_ctx = NULL;
    if (avformat_open_input(&input_ctx, "input.nv12", NULL, NULL) < 0) {
        fprintf(stderr, "Could not open input file\n");
        return -1;
    }

    // 查找视频流
    int video_stream_index = -1;
    AVStream *video_stream = NULL;
    for (int i = 0; i < input_ctx->nb_streams; i++) {
        if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            video_stream = input_ctx->streams[i];
            break;
        }
    }

    if (video_stream_index == -1) {
        fprintf(stderr, "Could not find video stream\n");
        return -1;
    }

    // 初始化H.265编码器
    AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H265);
    if (!encoder) {
        fprintf(stderr, "Could not find H.265 encoder\n");
        return -1;
    }

    AVCodecContext *encoder_ctx = avcodec_alloc_context3(encoder);
    if (!encoder_ctx) {
        fprintf(stderr, "Could not allocate encoder context\n");
        return -1;
    }

    encoder_ctx->width = video_stream->codecpar->width;
    encoder_ctx->height = video_stream->codecpar->height;
    encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    encoder_ctx->bit_rate = 2000000;
    encoder_ctx->gop_size = 10;
    encoder_ctx->keyint_min = 10;
    encoder_ctx->qmin = 10;
    encoder_ctx->qmax = 51;

    if (avcodec_open2(encoder_ctx, encoder, NULL) < 0) {
        fprintf(stderr, "Could not open H.265 encoder\n");
        return -1;
    }

    // 初始化转换器
    struct SwsContext *sws_ctx = sws_getContext(
        video_stream->codecpar->width,
        video_stream->codecpar->height,
        AV_PIX_FMT_NV12,
        video_stream->codecpar->width,
        video_stream->codecpar->height,
        AV_PIX_FMT_YUV420P,
        SWS_BICUBIC,
        NULL,
        NULL,
        NULL
    );

    if (!sws_ctx) {
        fprintf(stderr, "Could not initialize converter\n");
        return -1;
    }

    // 准备输出文件
    AVFormatContext *output_ctx = NULL;
    if (avformat_alloc_output_context2(&output_ctx, NULL, NULL, "output.h265") < 0) {
        fprintf(stderr, "Could not allocate output context\n");
        return -1;
    }

    AVStream *output_stream = avformat_new_stream(output_ctx, encoder);
    if (!output_stream) {
        fprintf(stderr, "Could not create output stream\n");
        return -1;
    }

    avcodec_parameters_from_context(output_stream->codecpar, encoder_ctx);
    output_stream->codecpar->codec_tag = 0;

    if (avio_open(&output_ctx->pb, "output.h265", AVIO_FLAG_WRITE) < 0) {
        fprintf(stderr, "Could not open output file\n");
        return -1;
    }

    // 写入文件头
    avformat_write_header(output_ctx, NULL);

    // 循环读取输入数据并编码
    AVPacket packet;
    while (av_read_frame(input_ctx, &packet) >= 0) {
        if (packet.stream_index == video_stream_index) {
            // 转换数据格式
            uint8_t *data[4];
            int linesize[4];
            sws_scale(
                sws_ctx,
                (const uint8_t *const *)packet.data,
                packet.linesize,
                0,
                video_stream->codecpar->height,
                data,
                linesize
            );

            // 填充编码器输入数据
            av_new_packet(&packet, linesize[0] * video_stream->codecpar->height);
            memcpy(packet.data, data[0], linesize[0] * video_stream->codecpar->height);
            memcpy(packet.data + linesize[0] * video_stream->codecpar->height, data[1], linesize[1] * video_stream->codecpar->height / 2);
            memcpy(packet.data + linesize[0] * video_stream->codecpar->height + linesize[1] * video_stream->codecpar->height / 2, data[2], linesize[2] * video_stream->codecpar->height / 2);

            // 编码数据
            int ret = avcodec_send_packet(encoder_ctx, &packet);
            if (ret < 0) {
                fprintf(stderr, "Error sending packet to encoder\n");
                return -1;
            }

            // 获取编码后的数据
            while (ret >= 0) {
                ret = avcodec_receive_packet(encoder_ctx, &packet);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                } else if (ret < 0) {
                    fprintf(stderr, "Error receiving packet from encoder\n");
                    return -1;
                }

                // 写入编码后的数据
                av_write_frame(output_ctx, &packet);
            }
        }

        av_packet_unref(&packet);
    }

    //