FFmpeg学习笔记(二)

libavformat:实现流协议,容器格式及IO访问,下面代码使用vim编写,提高记忆.

1 删除文件和文件的重用名

 #include 

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

int ret = 0;

//删除路径可以是个url

ret = avpriv_io_delete("./test.txt");

if(ret <0){

av_log(NULL,AV_LOG_ERROR,"文件删除失败\n");

return -1;
}

    // AV_LOG_ERROR //级别最高
   //AV_LOG_WARNING
   //AV_LOG_INFO
  //AV_LOG_DEBUG

   av_log(NULL,AV_LOG_DEBUG,"文件删除成功\n");

 // 重用名
 int removeRet;
removeRet = avpriv_io_move("1.txt","2.txt");

 if(removeRet < 0){
     av_log(NULL,AV_LOG_INFO,"1.txt文件重用名失败   \n");
 }else{
     av_log(NULL,AV_LOG_INFO,"1.txt文件改成了     2.txt\n");
   }
    return 0;
  }

在mac下 使用clang对上述代码进行编译.
clang -g -o ffmpeg_file DelectmoveFFmpeg.c `pkg-config --libs libavformat`

2 文件目录的操作

#include
//logo
#include
#include
int main(int argc, char *argv[]){
 printf("ffmpeg 文件目录的操作");

  // 获取上下文
  AVIODirContext *ctx = NULL;
  AVIODirEntry *entry = NULL;
  av_log_set_level(AV_LOG_INFO);
// 打开目录
  int isSucceed = avio_open_dir(&ctx,"./",NULL);
  if(isSucceed < 0 ){
   av_log(NULL,AV_LOG_ERROR,"目录打开失败");
   return -1;
}
 // 读取每一项
  while(1){
  isSucceed = avio_read_dir(ctx,&entry);
  if(isSucceed < 0){
   av_log(NULL,AV_LOG_ERROR,"不能读取目录:%s\n",av_err2str(isSucceed));
    //防止内存泄漏
    goto __fail;
   // return -1;
}
  if(entry == NULL){
   break;
}
  av_log(NULL,AV_LOG_INFO,"%12"PRId64" %s\n",entry->size,entry->name);
  // 释放entry
  avio_free_directory_entry(&entry);
}
// 释放
__fail:
avio_close_dir(&ctx);
 return 0;
}
     

clang编译:
clang -g -o testcatalogue FFmpegCatalogueHandle.c `pkg-config --libs libavformat,libavutil`

运行结果:

./testcatalogue
        6148 .DS_Store
       50320 testcatalogue
          96 testcatalogue.dSYM
         738 FFmpegCatalogueHandle.c

3 FFmpeg 打印音视频的信息

#include "libavutil/log.h"
#include "libavformat/avformat.h"

int main(int avgr ,char *argv[]){
    
    AVFormatContext *ctx = NULL;
    av_log_set_level(AV_LOG_INFO);
// 在ffmepg4.0以上 该方法已经废弃,不用了。
   // av_register_all();
    
    int ret = avformat_open_input(&ctx,"./scale.mp4",NULL,NULL);
    if(ret >= 0){
    // 音视频信息
     av_dump_format(ctx,0,"./scale.mp4",0);     

    }else{
     av_log(NULL,AV_LOG_ERROR,"不能打开文件:%s\n",av_err2str(ret));
    }
    
    avformat_close_input(&ctx);

  return 0;

}

编译 clang -g -o testmeta VideoAudioMeta.c `pkg-config --libs libavformat,libavutil

image.png

需要怎么改进程序,解除这个警告呢??? 解决方法如下:

// 在ffmepg4.0以上 该方法已经废弃,不用了。
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
avcodec_register_all();
#endif

4 抽取音频流

#include "libavutil/log.h"
#include "libavformat/avformat.h"

int main(int argc, char *argv[]){
    
    char *src = NULL;
    char *dst = NULL;

    AVFormatContext *ctx = NULL;
    
    av_log_set_level(AV_LOG_INFO);
   // 在ffmepg4.0以上 该方法已经废弃,不用了。
  //  #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
    //  av_register_all();
    //#endif
  //  #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
    //   avcodec_register_all();
    //#endif
    
    // 读取参数
    if(argc < 3){
     av_log(NULL,AV_LOG_ERROR,"传入的参数不对");
     return -1;
    }    
    // 赋值
    src = argv[1];
    dst = argv[2];
    if(!src || !dst){
       av_log(NULL,AV_LOG_ERROR,"参数有误");
       return -1;
    }
    

    int ret = avformat_open_input(&ctx,src,NULL,NULL);
    
    if(ret >= 0){
     av_dump_format(ctx,0,src,0);
    }else{
     av_log(NULL,AV_LOG_ERROR,"不能打开文件%s\n",av_err2str(ret)); 
    }
    // 打开写入文件 二进制文件 aac
    FILE *dst_fd = fopen(dst,"wb");
    
    if(!dst_fd){
     av_log(NULL,AV_LOG_ERROR,"文件打开失败\n");
     avformat_close_input(&ctx);
     return -1;     
    }    
    
    AVPacket pkt;
    int audio_index;
    int  len;
    // 获取流
    ret = av_find_best_stream(ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
    if(ret < 0){
     av_log(NULL,AV_LOG_ERROR,"音频流获取失败\n");
     avformat_close_input(&ctx);
     return -1;
    }
    
    audio_index = ret;
    av_init_packet(&pkt);
    //  获得一帧音频的压缩数据
    while(av_read_frame(ctx,&pkt) >=0){
     if(pkt.stream_index == audio_index){
       // 写入流 是压缩后的流 我们需要最好的流写进去
        len = fwrite(pkt.data,1,pkt.size,dst_fd);
        if(len != pkt.size){
         av_log(NULL,AV_LOG_WARNING,"警告,写入数据出现未知的问题\n");
        }
     }
     //回收
    av_packet_unref(&pkt);
   }
    avformat_close_input(&ctx);
    
    if(dst_fd){
      fclose(dst_fd);   
    }   



    return 0;
}

编译完成后运行 ,需要传两个参数才可以运行: scale.mp4 ,getaudio.aac
./testAudioMeta scale.mp4 getaudio.aac

注意:这里的aac文件无法播放 .如果需要播放,似乎需要修改程序. 比如使用ffplay播放,会报错:

ffplay getaudio.aac

image.png

5 抽取视频流

#include 
#include 
#include 

#ifndef AV_WEB32
# define AV_WB32(p,val) do {         \
   uint32_t d = (val);               \
   ((uint8_t *)(p))[3] = (d);        \
   ((uint8_t *)(p))[2] = (d)>>8;     \
   ((uint8_t *)(p))[1] = (d)>>16;    \
   ((uint8_t *)(p))[0] = (d)>>24;    \
}while(0)
#endif

#ifndef AV_RB16
#  define AV_RB16(x)                 \
((((const uint8_t *)(x))[0] << 8) |  \
  ((const uint8_t *)(x))[1])
#endif


static int alloc_and_copy(AVPacket *out,const uint8_t *sps_pps,uint32_t sps_pps_size,const uint8_t *in ,uint32_t in_size){
  
   uint32_t offset  = out ->size;

   uint8_t nal_header_size =  offset ? 3: 4;
   int err ;
   // 扩容av_grow_packet
   if((err = av_grow_packet(out,sps_pps_size + in_size + nal_header_size)) <0){
     return err;
   }
   // 先copy sps和pps 
   if(sps_pps){
       memcpy(out->data + offset,sps_pps,sps_pps_size);
    }
    // 在拷贝特征码 startcode
    memcpy(out->data + sps_pps_size + nal_header_size +
      offset,in,in_size);
   
    if(!offset){
    // 0001 没有sps和pps
      AV_WB32(out->data + sps_pps_size,1);
    }else{
    // sps pps 后面三位设置成 001
      (out->data + offset + sps_pps_size)[0] = 0;
       (out->data + offset + sps_pps_size)[1] = 0;
       (out->data + offset + sps_pps_size)[2] = 1;
    }
    
  // for(int i = 0;i < nal_header_size;i++){
    // (out->data+offset + sps_pps_size)[i] = i == nal_header_size - 1?1:0;
  // }
  // memcpy(out->data + sps_pps_size + nal_header_size +
  //  offset,in,in_size);

    return 0;
}

// 读取sps/pps的数据,并拷贝
int h264_extraData_to_Annexb(const uint8_t *codec_Data,const int codecDataSize,AVPacket *out_extraData,int padding){

// sps/pps的数据长度
  uint16_t unitSize;
//所有sps/pps 加上其特征码后总长度
  uint64_t totalSize = 0;
  
  for(int i = 0; i < codecDataSize;i++){

    printf("%02x  ",*(codec_Data + i));
  }
  
  uint8_t *out = NULL;
  uint8_t unit_nb;
  uint8_t sps_done = 0;
  uint8_t sps_seen = 0;
  uint8_t  pps_seen = 0;
  // sps数据偏移
  uint8_t  sps_offset = 0;
  //pps数据偏移
  uint8_t pps_offset = 0;
  //过滤掉前4个字节
  const uint8_t *extraData = codec_Data + 4  ;

  static const uint8_t nalu_header[4] = {0,0,0,1};
  // 2位 
  int length_size = (*extraData++ & 0x3) + 1;

  sps_offset = pps_offset = -1;
  // sps和pps的个数
  unit_nb = *extraData++ & 0x1f;

  if(!unit_nb){
   goto pps;
  }else{
    sps_offset = 0;
    sps_seen = 1;
  }
  while(unit_nb--){
    int err;
   // unitSize = (extraData[0] << 8) | extraData[1];
    unitSize =  AV_RB16(extraData);
    totalSize += unitSize + 4;
    if(totalSize > INT_MAX - padding){
      av_log(NULL,AV_LOG_ERROR,"输入数据流太大,视频流损坏或者非法的MP4/AVCC 比特流\n");
      av_free(out);
      return AVERROR(EINVAL);
    }

    if(extraData + 2 + unitSize > codecDataSize + codec_Data ){
     
      av_log(NULL,AV_LOG_ERROR,"数据异常\n");
      av_free(out);
      return AVERROR(EINVAL);

    }
//    av_log(NULL,AV_LOG_ERROR,"是否执行\n");
    if((err = av_reallocp(&out,totalSize + padding)) < 0)
       return err;
     //拷贝nalu_header
     memcpy(out + totalSize - unitSize - 4,nalu_header,4 );
     // 拷贝spspps
     memcpy(out + totalSize - unitSize ,extraData + 2,unitSize);

     extraData += 2 + unitSize;

 pps:
      if(!unit_nb && !sps_done++){
        unit_nb = *extraData++;
    if(unit_nb){
         pps_offset = totalSize;
     pps_seen = 1;
    }
   }

  }

  if(out){
    memset(out + totalSize,0,padding);
  }

  if(!sps_seen){
   av_log(NULL,AV_LOG_WARNING,"警告没有sps数据");
  }

  if(!pps_seen){
    av_log(NULL,AV_LOG_WARNING,"警告没有pps数据");
  }

  out_extraData->data = out;
  out_extraData->size = totalSize;

  return length_size;

}


//为包数据添加始码startcode ,并将获取的sps/pps等信息写入文件
int h264_videotoannexb(AVFormatContext *ctx, AVPacket *in,FILE *dst_fd){

    AVPacket *out = NULL;
    AVPacket spspps_pkt;
    int len;
    uint8_t unit_type;
    int32_t nal_size;
    uint32_t cumul_size = 0;
    const uint8_t *buf;
    const uint8_t *buf_end;
    int  buf_size;
    int ret = 0,i;

    out = av_packet_alloc();
    buf = in->data;
    buf_size = in->size;
    //缓冲区的最后位置
    buf_end = in->data + in->size;
    do{
      ret = AVERROR(EINVAL);
     // 数据没有内容      
      if(buf + 4 > buf_end)
        goto fail;
      //取出packet前面4个字节和32位的高位地址和32位地位地址进行交换,得到nal_size大小
    for(nal_size = 0,i = 0; i < 4; i++)
          nal_size = (nal_size << 8) | buf[i];
      // 向后移4个字节
     buf += 4;

       // 第一个字节的后五位,就是单元类型
     unit_type = *buf & 0x1f;

     if(nal_size > buf_end - buf || nal_size < 0)
      goto fail;
    
       //获取sps/pps
        if(unit_type == 5){
          h264_extraData_to_Annexb(ctx ->streams[in->stream_index] ->codec ->extradata,ctx ->streams[in->stream_index]->codec->extradata_size,&spspps_pkt,AV_INPUT_BUFFER_PADDING_SIZE);
         //新增特征吗 startcode
          if((ret=alloc_and_copy(out,spspps_pkt.data,spspps_pkt.size,buf,nal_size)) <0 ){
           goto fail;
        }
    }else{
        if((ret = alloc_and_copy(out,NULL,0,buf,nal_size)) < 0)
       goto fail;
    }
   
     len = fwrite(out ->data,1,out->size,dst_fd);
     if(len != out -> size){
       av_log(NULL,AV_LOG_DEBUG,"警告,写入文件长度不等于包大小 %d, %d\n",len,    out->size);
     }
     fflush(dst_fd);
       
    buf += nal_size;
   cumul_size += nal_size + 4;

    }while(cumul_size < buf_size);

  //  len = fwrite(out ->data,1,out->size,dst_fd);

   // if(len != out -> size){
    //  av_log(NULL,AV_LOG_DEBUG,"警告,写入文件长度不等于包大小 %d, %d\n",len,out->size);
    //}

    // fflush(dst_fd);

     fail:
        av_packet_free(&out);
    
     return ret;



}

int main(int argc,char *argv[]){
  // 音视频文件
  char *src = NULL;
  // 输出文件
  char *dst = NULL;
  
  av_log_set_level(AV_LOG_INFO);
 
  #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
      av_register_all();
    #endif
    #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
       avcodec_register_all();
    #endif
  // 必须传入音频文件和视频流输出文件
    if(argc <3){
    
     av_log(NULL,AV_LOG_ERROR,"参数传入数量错误!\n");
     return -1;   
    } 
    
     src  = argv[1];
     dst = argv[2];
     
     if(!src || !dst){
     av_log(NULL,AV_LOG_ERROR,"传入参数有误,请检查\n");
     return -1;
    }
    AVFormatContext *ctx = NULL;
   // 错误描述
    char *errbuf;
    int ret = avformat_open_input(&ctx,src,NULL,NULL);
    FILE *dst_fd = fopen(dst,"wb");
    if(!dst_fd){
    av_log(NULL,AV_LOG_DEBUG,"不能打开文件%s\n",dst);
    return -1;
    }
    if(ret < 0){
    av_strerror(ret,errbuf,1024);
    av_log(NULL,AV_LOG_DEBUG,"不能打开资源文件:%s,%d(%s)\n",src,ret,errbuf);
    return -1;
    }
    // 打开资源文件,获取最好的流
    ret = avformat_find_stream_info(ctx,NULL);
    if(ret < 0){
     av_strerror(ret,errbuf,1024);
    av_log(NULL,AV_LOG_DEBUG,"不能打开资源文件:%s,%d(%s)\n",src,ret,errbuf);
    return -1;
    }
    av_dump_format(ctx,0,src,0);
    //初始化packet
     AVPacket pkt ;
     av_init_packet(&pkt);
     pkt.data = NULL;
     pkt.size = 0;
     // 视频流的index
     int video_stream_index = av_find_best_stream(ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
     if(video_stream_index < 0){
     av_log(NULL,AV_LOG_DEBUG,"不能找到%s流,输入到文件%s\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO),src); 
      return AVERROR(EINVAL);
    }
    //取出流中数据包  并处理
    while(av_read_frame(ctx,&pkt) >= 0){
      if(pkt.stream_index == video_stream_index){
          h264_videotoannexb(ctx,&pkt,dst_fd);
      }
      av_packet_unref(&pkt);
    }
    
    if(dst_fd){
        fclose(dst_fd);
        
    }

    avformat_close_input(&ctx);
 return 0;
}

6 mp4转化为flv

#include 
#include 
#include 
 //#include 
//log_packet' is invalid in C99 [-Werror,-Wimplicit-function-declaration] 函
  //  数没有被定义,不能调用隐式函数
//*** error for object 0x1: pointer being freed was not allocated 野指针


static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt,     const char *tag);


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

  av_log_set_level(AV_LOG_INFO);

 // 在ffmepg4.0以上 该方法已经废弃,不用了。
 // #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
 // av_register_all();
 //#endif
// #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
//  avcodec_register_all();
// #endif

  AVOutputFormat *ofmt = NULL;
  
  AVFormatContext *ifmt_ctx = NULL;
  AVFormatContext *ofmt_ctx = NULL;
  AVPacket pkt;

  const char *in_fileName, *out_fileName;

  if(argc < 3){
   
   av_log(NULL,AV_LOG_ERROR,"入参错误");
   return -1;
  }
  
  in_fileName = argv[1];
  out_fileName = argv[2];
  
  int ret;
  // 打开多媒体文件
  if((ret = avformat_open_input(&ifmt_ctx,in_fileName,0,0)) < 0){
    
    av_log(NULL,AV_LOG_ERROR,"多媒体文件打开失败\n");
   
    goto fail;
  }
 
  if((ret = avformat_find_stream_info(ifmt_ctx,0)) < 0){
    
    av_log(NULL,AV_LOG_ERROR,"多媒体文件无法获取流信息\n");

    goto fail;
  }
  
  // dump 信息
  av_dump_format(ifmt_ctx,0,in_fileName,0);

  // 创建输出上下文
   
  avformat_alloc_output_context2(&ofmt_ctx,NULL,NULL,out_fileName);
  
  if(!ofmt_ctx){
    
    av_log(NULL,AV_LOG_ERROR,"创建输出上下文失败\n");
    
    ret = AVERROR_UNKNOWN;

    goto fail;
  }

  int stream_index = 0;
  int *stream_mapping = NULL;
  int stream_mapping_size = 0;  
  
  stream_mapping_size = ifmt_ctx -> nb_streams;
  stream_mapping    =  av_mallocz_array(stream_mapping_size,sizeof(*stream_mapping));
  
  if(!stream_mapping){
    ret = AVERROR(ENOMEM);
    goto fail;
  }
  
  ofmt = ofmt_ctx -> oformat;

  for(int i = 0; i < ifmt_ctx ->nb_streams; i++){
     // copy输入音频流
     AVStream *out_stream;
     AVStream *in_stream = ifmt_ctx -> streams[i];
     AVCodecParameters *in_codecpar = in_stream->codecpar;

     if(in_codecpar -> codec_type != AVMEDIA_TYPE_AUDIO && in_codecpar ->codec_type != AVMEDIA_TYPE_VIDEO && in_codecpar ->codec_type != AVMEDIA_TYPE_SUBTITLE){
         stream_mapping[i] = -1;
        continue;
    }
    
    stream_mapping[i] =  stream_index++;
    out_stream = avformat_new_stream(ofmt_ctx,NULL);

    if(!out_stream){
       av_log(NULL,AV_LOG_ERROR,"错误的输出流\n");
       ret = AVERROR_UNKNOWN;
       goto fail;
    }
    
    ret = avcodec_parameters_copy(out_stream -> codecpar,in_codecpar );
    
    if(ret < 0){
      av_log(NULL,AV_LOG_ERROR,"编码器参数错误\n");
      goto fail;
    }
    
    out_stream->codecpar->codec_tag = 0;
  }
   av_dump_format(ofmt_ctx,0,out_fileName,1);
   
   if(!(ofmt -> flags & AVFMT_NOFILE)){
      
      ret = avio_open(&ofmt_ctx->pb,out_fileName,AVIO_FLAG_WRITE);

      if(ret < 0){
       av_log(NULL,AV_LOG_ERROR,"'%s' 文件不能打开\n",out_fileName);
       goto fail;
      }

   }
  
  // 写入haeder
   ret = avformat_write_header(ofmt_ctx,NULL);
    
   if(ret < 0){
      av_log(NULL,AV_LOG_ERROR,"写入head失败\n");

     goto fail;
   }
   
   while(1){

     AVStream *in_stream, *out_stream;
     ret = av_read_frame(ifmt_ctx,&pkt);
    
     if(ret < 0)
      break;

     in_stream = ifmt_ctx ->streams[pkt.stream_index];
     
      if(pkt.stream_index >= stream_mapping_size || stream_mapping[pkt.stream_index] < 0  ){
         av_packet_unref(&pkt);
        continue;
      }
     
      pkt.stream_index = stream_mapping[pkt.stream_index];
      out_stream = ofmt_ctx ->streams[pkt.stream_index];
       
      log_packet(ifmt_ctx, &pkt, "in");

    // 刻度转化 转化时间基
     pkt.pts = av_rescale_q_rnd(pkt.pts,in_stream->time_base,out_stream->time_base,AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
     pkt.dts = av_rescale_q_rnd(pkt.dts,in_stream->time_base,out_stream->time_base,AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
     pkt.duration = av_rescale_q(pkt.duration,in_stream->time_base,out_stream->time_base);
     pkt.pos = -1;
     log_packet(ofmt_ctx, &pkt, "out");
     ret = av_interleaved_write_frame(ofmt_ctx,&pkt);
     if(ret < 0){
       av_log(NULL,AV_LOG_ERROR,"error muxing packet\n");
       break;
     }
     av_packet_unref(&pkt);
 }
  av_write_trailer(ofmt_ctx);
  fail:
  avformat_close_input(&ifmt_ctx);
  
  if(ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
     avio_closep(&ofmt_ctx->pb);

   avformat_free_context(ofmt_ctx);
   av_freep(&stream_mapping);

   if(ret < 0 && ret != AVERROR_EOF){
      av_log(NULL,AV_LOG_ERROR,"文件解析出现异常:%s\n",av_err2str(ret));
      return -1;
   }

  return 0;
}


static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

编译clang -g -o HandelFormat VideoHandelFormatCharge.c `pkg-config --libs libavformat libavutil libavcodec

你可能感兴趣的:(FFmpeg学习笔记(二))