オーディオとビデオの開発 12: YUV データ エンコーディング

コーディングプロセス

  1. エンコーダを見つける

  2. エンコーダコンテキストを作成する

  3. エンコードパラメータを設定する

    何らかの情報をエンコードし、

  4. エンコーダはエンコーダ コンテキストにバインドされます

  5. 出力エンコードファイルを作成する

  6. AVFrame を作成 (取得)

    これが元のフレームデータです。

  7. AVパケットの作成

    エンコード後、ビデオ フレームはパケットに格納されます。データ パケットには複数のデータ フレームが含まれます。

  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