Encoder编码器

发布于:2025-09-01 ⋅ 阅读:(23) ⋅ 点赞:(0)

Encoder编码器

#include <libavutil/log.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.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
        }
        
        fwrite(pkt->data, 1, pkt->size, out);
        av_packet_unref(pkt);
    }
_END:
    return 0;
}

int main(int argc, char* argv[]){

    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. 输入参数
    if(argc < 3){
        av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3\n");
        goto _ERROR;
    }

    dst = argv[1];
    codecName = argv[2];

    //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;
    ctx->max_b_frames = 1;
    ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    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->width = ctx->width;
    frame->height = ctx->height;
    frame->format = ctx->pix_fmt; 

    ret = av_frame_get_buffer(frame, 0);
    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;
        }

        //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. 编码
    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);
    }
    return 0;
}

我们来逐行分析这段新的 C 代码。这段代码的功能是创建一个视频编码器生成一些简单的 YUV 视频帧,将这些帧编码成指定的格式(例如 H.264),并将编码后的原始码流写入一个文件。


encode 函数分析

这个辅助函数负责将一帧 AVFrame 发送给编码器,并接收所有可能产生的 AVPacket,然后将这些包写入文件。

static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out){
  // 定义一个静态函数 encode。
  // - AVCodecContext *ctx: 编码器上下文。
  // - AVFrame *frame: 要编码的帧。如果为 NULL,表示要刷新编码器。
  // - AVPacket *pkt: 用于接收编码后的数据包。
  // - FILE *out: 输出文件指针。
  // - 返回值: 成功时返回 0,失败时返回 -1。

  int ret = -1;                          // 声明整型变量 ret 并初始化为 -1,用于存储函数返回值。

  ret = avcodec_send_frame(ctx, frame);  // 将 AVFrame 发送给编码器。
  if(ret < 0) {                          // 检查发送是否成功。
      av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder!\n"); // 失败则记录错误。
      goto _END;                          // 跳转到 _END 标签。
  }

  while( ret >= 0){                       // 进入循环,尝试从编码器接收编码后的 AVPacket。
      ret = avcodec_receive_packet(ctx, pkt); // 尝试接收一个包。
      if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
        // 如果返回 EAGAIN (表示编码器需要更多输入帧才能输出一个包) 
        // 或者 AVERROR_EOF (表示编码器已刷新完毕,没有更多包了)。
        return 0;                          // 这两种情况都算正常,直接返回 0 (成功)。
      } else if( ret < 0) {                 // 如果发生其他错误。
          // **** 注意:这里的中文注释 "退出tkyc" 可能是拼写错误或内部术语 ****
          return -1;                       // 返回 -1 表示发生错误。
      }
      
      fwrite(pkt->data, 1, pkt->size, out); // 将接收到的包的数据 (pkt->data) 写入输出文件。
      av_packet_unref(pkt);                // 释放对该包的引用,以便下次可以重用 pkt。
  }
_END:
  return 0;                              // 正常或出错(send_frame 失败)时返回 0 (这里的设计可能有点不一致,但_END 意味着返回 0)。
}

main 函数分析

这是程序的主体,负责设置编码器、生成视频帧并调用 encode 函数。

int main(int argc, char* argv[]){

  int ret = -1;                          // 声明整型变量 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);        // 设置 FFmpeg 的日志级别为 DEBUG,以便看到更多信息。

  // 1. 输入参数
  if(argc < 3){                          // 检查命令行参数数量是否足够。
      av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3\n"); // 不够则打印错误。
      goto _ERROR;                         // 跳转到 _ERROR 进行清理。
  }

  dst = argv[1];                         // 获取输出文件名。
  codecName = argv[2];                   // 获取编码器名称 (例如 "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;                      // 设置视频宽度为 640。
  ctx->height = 480;                     // 设置视频高度为 480。
  ctx->bit_rate = 500000;                // 设置目标比特率 (码率) 为 500 kbps。

  ctx->time_base = (AVRational){1, 25};  // 设置时间基准为 1/25 (表示 PTS 的单位是 1/25 秒)。
  ctx->framerate = (AVRational){25, 1};  // 设置帧率为 25/1 (25 fps)。

  ctx->gop_size = 10;                    // 设置 GOP (Group of Pictures) 大小为 10,即每 10 帧一个 I 帧。
  ctx->max_b_frames = 1;                 // 设置最大 B 帧数量为 1。
  ctx->pix_fmt = AV_PIX_FMT_YUV420P;     // 设置输入的像素格式为 YUV420P。

  if(codec->id == AV_CODEC_ID_H264){     // 如果选择的是 H.264 编码器。
      av_opt_set(ctx->priv_data, "preset", "slow", 0); // 设置 H.264 的私有选项 "preset" 为 "slow",以获得更好的压缩率。
  }

  // 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();                // 分配 AVFrame 结构体。
  if(!frame){                              // 检查分配是否成功。
      av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
      goto _ERROR;
  }
  frame->width = ctx->width;               // 设置 AVFrame 的宽度。
  frame->height = ctx->height;             // 设置 AVFrame 的高度。
  frame->format = ctx->pix_fmt;            // 设置 AVFrame 的像素格式。

  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();                 // 分配 AVPacket 结构体。
  if(!pkt){                                // 检查分配是否成功。
      av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
      goto _ERROR;
  }

  // 9. 生成视频内容并编码
  for(int i=0; i<25; i++){               // 循环 25 次,生成并编码 25 帧视频。
      ret = av_frame_make_writable(frame); // 确保 AVFrame 的数据区是可写的。
      if(ret < 0) {                      // 检查是否成功。
          break;                           // 如果失败,跳出循环。
      }

      // Y分量 (亮度)
      for(int y = 0; y < ctx->height; y++){ // 遍历 Y 分量的每一行。
          for(int x=0; x < ctx->width; x++){ // 遍历 Y 分量的每一列。
              frame->data[0][y*frame->linesize[0]+x] = x + y + i * 3; // 填充一个简单的渐变图案。
          }
      }

      // UV分量 (色度) - 注意尺寸是 Y 的一半
      for(int y=0; y< ctx->height/2; y++){ // 遍历 U/V 分量的每一行。
          for(int x=0; x < ctx->width/2; x++){ // 遍历 U/V 分量的每一列。
              frame->data[1][y * frame->linesize[1] + x ] = 128 + y + i * 2; // 填充 U 分量。
              frame->data[2][y * frame->linesize[2] + x ] = 64 + x + i * 5;  // 填充 V 分量。
          }
      }

      frame->pts = i;                      // 设置当前帧的 PTS (显示时间戳)。这里简单地使用帧序号。

      // 10. 编码
      ret = encode(ctx, frame, pkt, f);    // 调用 encode 函数处理这一帧。
      if(ret == -1){                       // 检查编码是否出错。
          goto _ERROR;                       // 出错则跳转到 _ERROR。
      }
  }
  // 10. 编码 (刷新编码器)
  encode(ctx, NULL, pkt, f);             // 发送一个 NULL 帧给 encode 函数,以刷新编码器中可能存在的缓存数据包。
_ERROR:                                  // 错误处理和资源释放标签。
  // ctx (释放编码器上下文)
  if(ctx){
      avcodec_free_context(&ctx);
  }

  // avframe (释放 AVFrame)
  if(frame){
      av_frame_free(&frame);
  }

  // avpacket (释放 AVPacket)
  if(pkt){
      av_packet_free(&pkt);
  }

  // dst (关闭文件)
  if(f){
      fclose(f);
  }
  return 0;                              // 程序结束,返回 0。
}

总结:

这个程序演示了 FFmpeg 视频编码的基本流程:

  1. 设置: 查找编码器、创建上下文、设置参数、打开编码器。
  2. 准备: 创建 AVFrame 用于存放原始数据,创建 AVPacket 用于接收编码后数据,打开输出文件。
  3. 循环:
    • 生成数据: 创建 YUV 图像数据并放入 AVFrame
    • 设置 PTS: 为 AVFrame 设置时间戳。
    • 编码: 调用 avcodec_send_frameavcodec_receive_packet (通过 encode 函数) 进行编码。
    • 写入: 将编码后的 AVPacket 写入文件。
  4. 刷新: 发送 NULL 帧以获取所有剩余的包。
  5. 清理: 释放所有分配的资源。

它生成的是裸码流 (Raw Stream),不包含任何容器格式(如 MP4 或 MKV)的头信息或元数据。要播放这种文件,通常需要播放器知道其编码格式,或者需要使用 FFmpeg 等工具将其封装到容器中。


网站公告

今日签到

点亮在社区的每一天
去签到