ffmpeg c接口

卓致远
2023-12-01

ffmpeg新版本很多接口发生了变化,因此在使用时需要确定自己的版本,在这里使用的版本为4.1.4。 

在此特别感谢雷博提供的相关文章:FFmpeg_雷霄骅的博客-CSDN博客

  • ffmpeg视频解码

#include <iostream>
#include <stdio.h>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
}

#define ERROR_STR_SIZE 1024

int main() {
  
  int ret = -1;
  int err_code;
  char errors[ERROR_STR_SIZE];
  AVFormatContext *ifmt_ctx = NULL;
  AVCodecContext *pCodecCtx;
  const AVOutputFormat *ofmt = NULL;
  AVStream *in_stream1 = NULL;
  int vedio_stream_indes = 0;
  // 文件最大时长,保证音频和视频数据长度一致
  double max_duration = 0;
  AVPacket *pkt;
  int stream1 = 0, stream2 = 0;
  const char* video = "E:\\jiao\\project\\162300.mp4";
  /*//初始化网络,如果要解封装网络数据格式,则可调用该函数。
  avformat_network_init();*/
  //av_register_all();//高版本已弃用
  //打开输入文件(可解析视频参数)
  ifmt_ctx = avformat_alloc_context();
  if ((err_code = avformat_open_input(&ifmt_ctx, video, 0, 0)) != 0) {
    av_strerror(err_code, errors, ERROR_STR_SIZE);
    return -1;
  }
  if (avformat_find_stream_info(ifmt_ctx, NULL) < 0) {
    printf("Couldn't find stream information.\n");
    return -1;
  }

  // 找到视频流下标
  vedio_stream_indes = -1;
  for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
    if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      vedio_stream_indes = i;
      break;
    }
  }

  if (vedio_stream_indes == -1) {
    printf("Didn't find a video stream");
    return -1;  
  }
  // 获取文件中的视频流
  in_stream1 = ifmt_ctx->streams[vedio_stream_indes];
  int rate = in_stream1->r_frame_rate.num;   //视频帧率
  int w = in_stream1->codecpar->width;   //视频图像宽
  int h = in_stream1->codecpar->height;   //视频图像高
  if (in_stream1->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
    printf("the input is video.\n");
  }
  //软解码,查找解码器
  const AVCodec *pCodec = avcodec_find_decoder(in_stream1->codecpar->codec_id);
  if (pCodec == NULL) {
    printf("Codec not found.\n");
    return -1;
  }

  //获取编解码器上下文
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if (pCodecCtx == NULL)
  {
    printf("get av codec context failed.\n");
    return -1;
  }
  //设置编解码器上下文参数
  if (avcodec_parameters_to_context(pCodecCtx, in_stream1->codecpar) < 0) {
    printf("set param failed!\n");
    return -1;
  }

  //打开编解码器
  if (avcodec_open2(pCodecCtx, pCodec, NULL) != 0) {
    printf("Could not open codec.\n");
    return -1;
  }
  AVFrame *frame;
  //frame 初始化
  frame = av_frame_alloc();
  pkt = av_packet_alloc();
  int frames = 0;
  while (av_read_frame(ifmt_ctx, pkt)>=0) {

    if (pkt->stream_index == vedio_stream_indes) {
      //解封装,将包发送到解码队列中
      ret = avcodec_send_packet(pCodecCtx, pkt);
      
      if (ret < 0) {
        continue;
      }
      if (ret >= 0) {

        ret = avcodec_receive_frame(pCodecCtx, frame);
        int keyfrmae = frame->key_frame;  //是否为I帧
        printf("keyframe:%d\n", keyfrmae);
        if (ret == 0) {
          frames += 1;
          //printf("decodec sucess.\n");
          printf("frames:%d\n",frames);
        } else {
          printf("receive failed.\n");
        }
      }
    }
    av_packet_unref(pkt);    //必须要有,否则每帧解码av_read_frame都会产生堆内存,造成内存泄漏
  }
  av_free(frame->data);
  avcodec_free_context(&pCodecCtx);
  avformat_free_context(ifmt_ctx);
  av_packet_free(&pkt);
  return 1;

}

视频解码方法在rtsp流解码同样适用。 

  • 添加数据获取方式

AVDictionary *options = NULL;
av_dict_set(&options, "rtsp_transport", "tcp", 0);
avformat_open_input(&ifmt_ctx, rtsp, 0, &options)

使用tcp传输方式获取数据 

  • ffmpeg+opencv

ffmpeg解码出来的图像转为opencv可以使用的Mat类数据 (BGR格式)

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;

SwsContext *swsContext = sws_getContext(w, h, pCodecCtx->pix_fmt, w, h, AV_PIX_FMT_BGR24,
                     SWS_BICUBIC, NULL, NULL, NULL);
AVFrame *pFrameBGR = NULL;
uint8_t *out_bufferBGR = NULL;
pFrameBGR = av_frame_alloc();

//给pFrameBGR帧加上分配的内存;
out_bufferBGR = new uint8_t[w*h*3];
av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize,
                     out_bufferBGR, AV_PIX_FMT_BGR24, w, h, 1);
sws_scale(swsContext, frame->data, frame->linesize, 0,
          pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize);
Mat img(frame->height, frame->width, CV_8UC3, out_bufferBGR);
imshow("show", img);
waitKey(1);
av_free(pFrameBGR->data);
delete [] out_bufferRGB;    //一定要使用delete[],而不是delete
sws_freeContext(swsContext);    //释放内存
  • ffmpeg ubuntu编译 

官方文档:CompilationGuide/Ubuntu – FFmpeg 

参考文档: FFmpeg 3.1.11版本源码编译

源码下载地址:Index of /releases (ffmpeg.org) 

尝试了两天终于成功可以使用了,现在记录一下中间遇到的问题以及是如何解决的:

最终使用的版本为 ffmpeg4.1.4,链接:http://ffmpeg.org/releases/ffmpeg-4.1.4.tar.bz2

 步骤:

##下载、解码
wget http://ffmpeg.org/releases/ffmpeg-4.1.4.tar.bz2
tar xvf ffmpeg-4.1.4.tar.bz2
##编译安装
cd ffmpeg-4.1.4.tar.bz2
mkdir build
cd build
../configure --prefix=安装路径 --enable-shared --enable-pthreads --enable-gpl --enable-libx264
make -j
make install
export LD_LIBRARY_PATH=安装路径:$LD_LIBRARY_PATH
##配置路径
cd /etc/ld.so.conf.d/
vim ffmpeg.conf ##配置ffmpeg lib路径
ldconfig

问题一:

invalid suffix on literal; C++11 requires a space between literal and string

解决方案:

error: invalid suffix on literal; C++11 requires a space between literal and identifier · Issue #3 · konstantinmiller/dashp2p · GitHub

问题二:

 Invalid data found when processing input(c++代码运行avformat_open_input()抛出异常)

解决方案:

重新编译ffmpeg 

问题三:

EOORR:libx264 not found(在arm环境中出现)

解决方案:

 ../configure --prefix=安装路径 --enable-shared 去掉后面的参数

  • g++编译生成可执行文件(ubuntu下) 
g++ rtsp.cpp -fPIC -shared -o rtsp -I /opt/ffmpeg/include/ -L /opt/ffmpeg/lib -lavcodec -lavformat -lavutil -lswscale -lswresample -lavfilter

其中:/opt/ffmpeg/include/为头文件路径 ,/opt/ffmpeg/lib为库文件路径。 编译成功后直接运行./rtsp即可。

  • 封装python接口
from ctypes import *

self.handle.getframe.restype = POINTER(c_ubyte*self.size)
pp = self.handle.getframe()
data = np.frombuffer(pp.contents.value,np.uint8).reshape([self.height,self.width,3])

 主要难点在于uint8_t*数据的获取,转换后的指针数据通过np.frombuffer转换为numpy格式数据。

 类似资料: