Linux(Ubuntu) ffmpeg + opencv play video (C++)

简介:使用ffmpeg和opencv处理视频,显示画面。

学习ffmpeg视频处理,看之前老的教程,编译一下出现一堆警告,类似下面这样的

warning: ‘void xxx(xxx*)’ is deprecated [-Wdeprecated-declarations]

所以按照4.4.4的官方example写了下4.4.4api的视频解码C++程序

ffmpeg版本: 4.4.4

opencv版本: 4.7.0

file: ffmpeg_opencv.cc

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

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>
#include <libavutil/pixfmt.h>
#include <libswscale/swscale.h>
};

#include <chrono>
#include <opencv2/opencv.hpp>

int main(int argc, char **argv) {
  if (argc < 2) {
    printf("usage:\n\t %s filename\n", argv[0]);
    return -1;
  }
  /* 打开文件,并记录文件格式信息 */
  AVFormatContext *fmt_ctx = nullptr;  // 用于存储视频文件封装格式中包含的信息
  if (avformat_open_input(&fmt_ctx, argv[1], nullptr, nullptr) < 0) {
    av_log(nullptr, AV_LOG_ERROR, "Cannot open input file\n");
    return -1;
  }
  /* 在格式信息中检索流信息 */
  if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
    av_log(nullptr, AV_LOG_ERROR, "Cannot find stream information\n");
    return -1;
  }  // 流信息包含了视频流、音频流、字幕流等信息
  printf("文件格式信息:\n");
  av_dump_format(fmt_ctx, 0, argv[1], 0);  // 打印文件格式信息
  int total_frames = fmt_ctx->streams[0]->nb_frames;
  printf("总帧数: %d\n", total_frames);
  /* 根据格式信息选择其中的视频流 */
  AVCodec *dec = nullptr;                 // 用于存储解码器
  int video_stream_index = -1;  // 用于存储视频流索引
  video_stream_index =
      av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec,
                          0);  // dec会被赋值为视频流对应的解码器
  if (video_stream_index < 0 || !dec) {  // 没有找到视频流或者解码器
    av_log(nullptr, AV_LOG_ERROR,
           "Cannot find a video stream in the input file\n");
    return -1;
  }
  double fps = av_q2d(fmt_ctx->streams[video_stream_index]->avg_frame_rate);
  int64_t wait_time_ms = static_cast<int64_t>(1000 / fps);  // 获取延时时间,即帧间隔(ms)
  /* 创建解码器上下文指针 */
  AVCodecContext *dec_ctx = nullptr;
  dec_ctx = avcodec_alloc_context3(dec);  // 根据解码器创建解码器上下文指针
  if (!dec_ctx) return AVERROR(ENOMEM);  // 内存分配失败
  avcodec_parameters_to_context(
      dec_ctx,
      fmt_ctx->streams[video_stream_index]
          ->codecpar);  // 根据视频流编码信息填充解码器上下文
  /* 初始化视频解码器 */
  if (avcodec_open2(dec_ctx, dec, nullptr) < 0) {
    av_log(nullptr, AV_LOG_ERROR, "Cannot open decoder\n");
    return -1;
  }
  /* 创建packet、frame、frame_rgb和buffer用于存储解码前和解码后的数据 */
  int ret = 0;         // 用于存储函数返回值
  AVPacket packet;     // 用于存储一帧数据的packet
  AVFrame *frame = nullptr;      // 用于存储解码后的数据帧
  AVFrame *frame_rgb = nullptr;  // 用于存储转换为RGB格式后的数据帧
  frame =
      av_frame_alloc();  // 注意,av_frame_alloc不会为frame.data分配内存,
                         // 之后的avcodec_receive_frame会让frame.data指向解码后的数据
  frame_rgb =
      av_frame_alloc();  // 同理,av_frame_alloc不会为frame_rgb.data分配内存
                         // 之后的av_image_fill_arrays会让frame_rgb.data指向转换后的数据
  if (!frame || !frame_rgb) {
    perror("Could not allocate frame");
    exit(1);
  }
  /* 转换格式设置 */
  AVPixelFormat dst_pix_fmt =
      AV_PIX_FMT_BGR24;  // 将解码后的数据转换为RGB格式 AV_PIX_FMT_RGB24
                         // (对于opencv来说,转换为BGR格式)
  uint8_t *buffer = nullptr;  // 用于存储转换后的数据帧地址的指针
  int num_bytes = av_image_get_buffer_size(
      dst_pix_fmt, dec_ctx->width, dec_ctx->height,
      1);  // 计算转换后数据帧的大小,宽和高保持不变,1表示对齐字节数
  if (num_bytes < 0) {
    av_log(nullptr, AV_LOG_ERROR, "Could not get image buffer size\n");
    return -1;
  }
  // 为buffer分配内存
  buffer = static_cast<uint8_t *>(av_malloc(num_bytes * sizeof(uint8_t)));
  /* av_image_fill_arrays将转换后数据帧的指针指向分配的内存,
   * 同时linesize也会被赋值
   * width和height应该与buffer分配时使用的宽高一致 */
  av_image_fill_arrays(frame_rgb->data, frame_rgb->linesize, buffer,
                       dst_pix_fmt, dec_ctx->width, dec_ctx->height, 1);
  struct SwsContext *sws_ctx = nullptr;  // 创建转换上下文指针
  sws_ctx = sws_getContext(  // 根据解码器信息创建转换上下文
      dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, dec_ctx->width,
      dec_ctx->height, dst_pix_fmt, SWS_BILINEAR, nullptr, nullptr, nullptr);
  if (!sws_ctx) {  // 如果转换上下文创建失败,打印转换格式信息,用于调试
    av_log(nullptr, AV_LOG_ERROR,
           "Impossible to create scale context for the conversion\n");
    av_log(nullptr, AV_LOG_ERROR, "fmt:%s s:%dx%d -> fmt:%s s:%dx%d\n",
           av_get_pix_fmt_name(dec_ctx->pix_fmt), dec_ctx->width,
           dec_ctx->height, av_get_pix_fmt_name(dst_pix_fmt), dec_ctx->width,
           dec_ctx->height);
    return -1;
  }
  while (true) {  // 从视频文件循环读取视频帧
    std::chrono::system_clock::time_point start_time, end_time;
    start_time = std::chrono::system_clock::now();
    /* av_read_frame会将视频文件分解成frames,每次调用返回一个frame
     * 读取数据时,如果读取到的是视频数据,那么packet将只包含一帧数据
     * 如果读取到的是音频数据,那么可能包含整数倍数帧数据 */
    if (ret = av_read_frame(fmt_ctx, &packet) <
              0)  // 读取数据,并将其存储在packet中,packet.data指向数据
      break;  // ret < 0, 文件读取完毕,跳出循环
    if (packet.stream_index ==
        video_stream_index) {  // 如果packet.stream_index等于视频流索引,说明这个packet中包含的是视频数据
      /* avcodec_send_packet将packet中的数据发送到解码器
       * 注意:此时packet中可能包含多帧数据(例如:H.264编码中的NALU单元)
       * 所以需要循环调用avcodec_receive_frame()函数读取所有数据 */
      ret = avcodec_send_packet(dec_ctx, &packet);
      if (ret < 0) {  // 发送失败时,跳出循环
        av_log(nullptr, AV_LOG_ERROR,
               "Error while sending a packet to the decoder\n");
        break;
      }
      while (ret >= 0) {  // 当解码器中有多帧数据时,循环读取,
                          // 如果只有一帧数据,可以用if代替while
        ret = avcodec_receive_frame(
            dec_ctx,
            frame);  // 从解码器中读取一帧数据,存储在frame中,frame.data指向数据
        if (ret == AVERROR(EAGAIN) ||
            ret == AVERROR_EOF) {  // 如果解码器中没有数据了,跳出循环
          break;
        } else if (ret < 0) {  // 发生错误时,跳出循环
          av_log(nullptr, AV_LOG_ERROR,
                 "Error while receiving a frame from the decoder\n");
          goto end;
        }
        /* sws_scale根据sws_ctx转换上下文将数据帧转换为指定格式 */
        sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize,
                  0, dec_ctx->height, frame_rgb->data, frame_rgb->linesize);

        /*
         *
         *  至此我们已经得到了转换后的数据帧,可以对其进行处理
         *
         */

        /* 现在将转换后的数据帧通过opencv显示出来 */
        cv::imshow("frame", cv::Mat(frame->height, frame->width, CV_8UC3,
                                    frame_rgb->data[0]));
        while (true) {
          end_time = std::chrono::system_clock::now();
          if (cv::waitKey(1) == 'q') {  // 按下q键,跳出循环
            av_frame_unref(frame);
            av_packet_unref(&packet);  // 别忘了释放packet、frame、buffer
            goto end;
          }
          auto dura = (std::chrono::duration_cast<std::chrono::milliseconds>(
                           end_time - start_time))
                          .count();  // 计算时间差(毫秒)
          if (dura >= wait_time_ms) {
            // 达到延时时间,跳出循环
            break;
          }
        }
        av_frame_unref(frame);  // 别忘了释放frame
      }
    }
    av_packet_unref(&packet);
    // memset(buffer, 0, num_bytes * sizeof(uint8_t));  // 清空buffer
  }

end:
  av_freep(&buffer);
  sws_freeContext(sws_ctx);
  avcodec_free_context(&dec_ctx);
  avformat_close_input(&fmt_ctx);
  av_frame_free(&frame);
  av_frame_free(&frame_rgb);

  if (ret < 0 && ret != AVERROR_EOF) {
    // fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
    exit(1);
  }
  exit(0);
}

// TODO: 重构代码,将代码分成多个函数

编译命令:

g++ ffmpeg_open.cc -o player -L/usr/ffmpeg/lib -lavcodec -lavutil -lavformat -lswscale -L/usr/local/lib -lopencv_highgui -lopencv_core

运行命令:

./player path/to/video.type

还有一个很奇怪的问题,这个程序在运行的时候会偶尔出现总线错误(bus error),但是当我在gdb中调试的时候怎么都不会出现总线错误,很困扰,有大佬知道为啥会出现这个总线错误吗???

找到错误了,所有声明指针的地方初始化为nullptr就好了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值