音视频开发十二:YUV数据编码

编码流程

  1. 查找编码器

  2. 创建编码器上下文

  3. 设置编码参数

    编码的一些信息,

  4. 编码器与编码器上下文绑定在一起

  5. 创建输出编码后的文件

  6. 创建(获取)AVFrame

    这个是原始的帧数据。

  7. 创建AVPacket

    编码后的是视频帧是保存在数据包中的。一个数据包包括多个数据帧。

  8. 生成(获取)视频内容。

  9. 进行编码。

    对视频内容进行编码。

  10. 得到编码后数据写入到输出文件

流程图如下:

实现代码

自定义填充yuv数据,生成编码格式为h264的编码文件

#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <SDL.h>
#include <stdbool.h>



static int encode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, FILE* out) {
    
    
    int ret = -1;

    // 要编码的数据传给编码器
    ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder!\n");
        goto _END;
    }

    while (ret >= 0) {
    
    
        // 从编码器中获取编码好的数据
        ret = avcodec_receive_packet(ctx, pkt);
        // 如果编码出错了或者编码的缓冲区没有数据
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
    
    
            return 0;
        }
        else if (ret < 0) {
    
    
            return -1; //退出tkyc
        }

        // 1.将什么数据, 每次写的大小, 编码后包的字节数
        fwrite(pkt->data, 1, pkt->size, out);
        av_packet_unref(pkt);
    }
_END:
    return 0;
}
/*
* 对视频内容进行编码
* 本次视频内容是自己生成的yuv数据
*/
void encode_vedio_new() {
    
    
    int ret = -1;

    FILE* f = NULL;

    char* dst = NULL;
    char* codecName = NULL;

    const AVCodec* codec = NULL;
    AVCodecContext* ctx = NULL;

    AVFrame* frame = NULL;
    AVPacket* pkt = NULL;

    av_log_set_level(AV_LOG_DEBUG);

    //1. 输入参数


    dst = "f:\\test_data\\encode_vedio.h264";
    codecName = "libx264";

    //2. 查找编码器
    codec = avcodec_find_encoder_by_name(codecName);
    if (!codec) {
    
    
        av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s", codecName);
        goto _ERROR;
    }

    //3. 创建编码器上下文
    ctx = avcodec_alloc_context3(codec);
    if (!ctx) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
        goto _ERROR;
    }

    //4. 设置编码器参数
    ctx->width = 640; // 视频宽高
    ctx->height = 480;
    ctx->bit_rate = 500000; // 视频码率

    ctx->time_base = (AVRational){
    
     1, 25 }; // 时间基
    ctx->framerate = (AVRational){
    
     25, 1 }; // 视频帧率

    ctx->gop_size = 10; // gop表示一组帧的大小,10代表每10帧代表1组
    ctx->max_b_frames = 1; // 一组帧中最大可以设置多少个B帧
    ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 视频像素格式

    // 是h264的话,可以设置一些私有的
    if (codec->id == AV_CODEC_ID_H264) {
    
    
        av_opt_set(ctx->priv_data, "preset", "slow", 0);
    }

    //5. 编码器与编码器上下文绑定到一起
    ret = avcodec_open2(ctx, codec, NULL);
    if (ret < 0) {
    
    
        av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
        goto _ERROR;
    }

    //6. 创建输出文件
    f = fopen(dst, "wb");
    if (!f) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Don't open file:%s", dst);
        goto _ERROR;
    }

    //7. 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }
    // 为frame 的data分配空间的时候,必须告诉它的宽高和格式,这样ffmpeg才知道为这个帧分配多大的空间。
    frame->width = ctx->width;
    frame->height = ctx->height;
    frame->format = ctx->pix_fmt;

    ret = av_frame_get_buffer(frame, 0); // 用于为AVFrame结构体分配内存空间
    if (ret < 0) {
    
    
        av_log(NULL, AV_LOG_ERROR, "Could not allocate the video frame \n");
        goto _ERROR;
    }

    //8. 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
    
    
        av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
        goto _ERROR;
    }

    //9. 生成视频内容 这个循环中,每一次循环,产生一帧数据
    for (int i = 0; i < 25; i++) {
    
    
        ret = av_frame_make_writable(frame);
        if (ret < 0) {
    
    
            break;
        }

        /*
        未编码的每一帧都是一张图像。x是横轴,y是纵轴
        frame->data[0]表示Y分量
        frame->data[1]表示U分量
        frame->data[2]表示V分量
        linesize表示行的大小
        下面就是自定义生成图像帧数据
        */
        //Y分量
        for (int y = 0; y < ctx->height; y++) {
    
    
            for (int x = 0; x < ctx->width; x++) {
    
    
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        //UV分量
        for (int y = 0; y < ctx->height / 2; y++) {
    
    
            for (int x = 0; x < ctx->width / 2; x++) {
    
    
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i;

        //10. 编码
        ret = encode(ctx, frame, pkt, f);
        if (ret == -1) {
    
    
            goto _ERROR;
        }
    }
    //10. 编码 再次调用编码方法,这是因为编码器的缓冲区中可能还残存剩余的数据,所以要让她刷新出来,否则发编码后的数据会少帧。
    //         我们传给编码器一个为null的frame,告诉编码器,现在没有数据,需要把缓冲区中的数据全部给刷新出来。
    encode(ctx, NULL, pkt, f);
_ERROR:
    //ctx
    if (ctx) {
    
    
        avcodec_free_context(&ctx);
    }

    //avframe
    if (frame) {
    
    
        av_frame_free(&frame);
    }

    //avpacket
    if (pkt) {
    
    
        av_packet_free(&pkt);
    }

    //dst
    if (f) {
    
    
        fclose(f);
    }

}

猜你喜欢

转载自blog.csdn.net/qq_38056514/article/details/130190797