Encoder编码器

Encoder编码器

c 复制代码
#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,然后将这些包写入文件。

c 复制代码
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 函数。

c 复制代码
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 等工具将其封装到容器中。

相关推荐
扶尔魔ocy13 小时前
【QT window】ffmpeg实现录音功能之无损格式--PCM
ffmpeg·pcm
止礼14 小时前
FFmpeg8.0.1 源代码的深入分析
ffmpeg
小曾同学.com15 小时前
音视频中的“透传”与“DTS音频”
ffmpeg·音视频·透传·dts
vivo互联网技术15 小时前
数字人动画云端渲染方案
前端·ffmpeg·puppeteer·web3d
止礼16 小时前
FFmpeg8.0.1 编解码流程
ffmpeg
qs701617 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
止礼17 小时前
FFmpeg8.0.1 Mac环境 CMake本地调试配置
macos·ffmpeg
简鹿视频1 天前
视频转mp4格式具体作步骤
ffmpeg·php·音视频·实时音视频