N_FFmpeg

实例 FFmpeg 纯净版, 解码H264流/包

可以参考上: 解码 H264帧包(Javacv), 这里C++版, 且使用av_parser_parse2 可以把连续的流切一个个包帧 (虽然这里没什么用)

帧包数据 与 码流数据 的不同

简单记录一下这个只使用libavcodec的”纯净版”视频解码器和使用libavcodec+libavformat 的视频解码器的不同;

(1) 下列与libavformat相关的函数在”纯净版”视频解码器中都不存在;

av_register_all(): 注册所有的编解码器, 复用/解复用器等等组件; 其中调用了avcodec_register_all()注册所有编解码器相关的组件; avformat_alloc_context(): 创建AVFormatContext结构体; avformat_open_input(): 打开一个输入流(文件或者网络地址); 其中会调用avformat_new_stream()创建AVStream结构体; avformat_new_stream()中会调用avcodec_alloc_context3()创建AVCodecContext结构体; avformat_find_stream_info(): 获取媒体的信息; av_read_frame(): 获取媒体的一帧压缩编码数据; 其中调用了av_parser_parse2();

(2) 新增了如下几个函数;

avcodec_register_all(): 只注册编解码器有关的组件; 比如说编码器, 解码器, 比特流滤镜等, 但是不注册复用/解复用器这些和编解码器无关的组件; avcodec_alloc_context3(): 创建AVCodecContext结构体; av_parser_init(): 初始化AVCodecParserContext结构体; av_parser_parse2(): 使用AVCodecParser从输入的数据流中分离出一帧一帧的压缩编码数据;

(3) 程序的流程发生了变化;

在”libavcodec+libavformat”的视频解码器中, 使用avformat_open_input()avformat_find_stream_info()就可以解析出输入视频的信息(例如视频的宽, 高)并且赋值给相关的结构体; 因此我们在初始化的时候就可以通过读取相应的字段获取到这些信息;

在”纯净”的解码器则不能这样, 由于没有上述的函数, 所以不能在初始化的时候获得视频的参数; “纯净”的解码器中, 可以通过avcodec_decode_video2() 获得这些信息;

因此我们只有在成功解码第一帧之后, 才能通过读取相应的字段获取到这些信息;

代码

测试代码归档: main_h264_dec

 
#include <QApplication>
#include <QTextCodec>
 
#include <QTime>
#include <QDebug>
 
#include <stdio.h>
#include <iostream>
 
 #include "customlog.h"
 #include "mainwindow.h"
extern "C"
{
//ffmpeg
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
//librtp
    #include "librtp/payload/rtp-payload-internal.h"
// hiki sdk
    #include "haikang/include/DataType.h"
	//  #include "haikang/include/DecodeCardSdk.h"
    #include "haikang/include/plaympeg4.h"
    #include "haikang/include/HCNetSDK.h"
}
 
 
static void application_global_init_method(){
    NET_DVR_Init();
    avcodec_register_all();
    av_register_all();
    avformat_network_init();
    //日志等级
//     av_log_set_level(AV_LOG_ERROR);
   av_log_set_level(AV_LOG_QUIET);
//   av_log_set_level(AV_LOG_DEBUG);
}
 
 
//测试 输出到文件
static QDataStream * g_out_stream = nullptr;
 
 
int main_h264();
int main_h264_dec();
int main(int argc, char *argv[])
{
//        QApplication a(argc, argv);
//        qInstallMessageHandler(myMessageOutput);
//        application_global_init_method();
//        QTextCodec *codec = QTextCodec::codecForName("UTF-8"); //设置编码格式为UTF-8
//        QTextCodec::setCodecForLocale(codec);
//        MainWindow w;
//        w.show();
//        return a.exec();
    QApplication a(argc, argv);
    MainWindow w;
    main_h264_dec();
    w.show();
    return a.exec();
}
 

H264包 解码

///测试 h264 包 解码
int main_h264_dec()
{
    int width = 1280;//TODO 宽高
    int height = 960;
 
    ///创建sws Context 将解码后的YUV数据转换成RGB32 TODO :解码后读取原始宽高 , 初始化 SwsContext
    struct SwsContext *img_convert_ctx;
    img_convert_ctx = sws_getContext(width, height,
            AVPixelFormat::AV_PIX_FMT_YUV420P, width, height,
                    AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);
 
    uint8_t *out_buffer;
      //rgb frame
    AVFrame * pFrameRGB = av_frame_alloc();
    int numBytes = avpicture_get_size(AV_PIX_FMT_RGB32,
                                      width,height);
    out_buffer = new uint8_t[(numBytes * sizeof(uint8_t))];
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
                   width, height);
 
 
 
   //////////////////////////////////////////////////////////////////////////
    AVCodec *pCodec;
    pCodec = avcodec_find_decoder(AVCodecID::AV_CODEC_ID_H264 );
 
    AVCodecContext *pCodecCtx;
    pCodecCtx = avcodec_alloc_context3(pCodec);
 
    AVCodecParserContext *pCodecParserCtx;
    pCodecParserCtx=av_parser_init(AVCodecID::AV_CODEC_ID_H264);
    if (!pCodecParserCtx){
         fprintf(stderr,"Could not allocate video parser context\n");
        return -1;
    }
    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {//一定要open
          fprintf(stderr,"Could not open codec\n");
          return -1;
    }
    //复用
    AVFrame *pFrame = av_frame_alloc();
    AVPacket packet;
    av_init_packet(&packet);
 
    bool isFristFrame = true;
    int ret, got_picture;
    for (int var = 0; var < 40; ++var) {
        QString path = QString("F:/test/stream/h264/farme%1.dat").arg(var);
        QFile frame(path);
        frame.open(QIODevice::ReadOnly);
        QByteArray array = frame.readAll();
        uint8_t* buff = (uint8_t *)array.data();
 
        // 返回 解析的数据长度
        int len = av_parser_parse2(
                        pCodecParserCtx, pCodecCtx,//结构体
                        &packet.data, &packet.size,//出包
                        buff , array.size() ,//入数据
                        AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);//没PTS DTS ...
        if(packet.size==0){
            continue;
        }
 
        /// 直接解码..        got_picture 这个参数是, 是否成功解出一帧
        ///  至少成功解出一帧 才能在 pCodecCtx 读取 宽高 等信息.
        ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
        if(ret< 0 ){
            char errMesg[512];
            av_strerror(ret,errMesg,sizeof (errMesg) );
            qDebug()<<"avcodec_decode_video2   error mesg="<<errMesg;
            break;
        }
 
        if (got_picture) {
            if(isFristFrame ){
                isFristFrame = false;
                //打印 包帧信息
                fprintf(stderr,"[Packet]Size:%6d\t",packet.size);
                switch(pCodecParserCtx->pict_type){
                                case AV_PICTURE_TYPE_I: fprintf(stderr,"Type:I\t");break;
                                case AV_PICTURE_TYPE_P: fprintf(stderr,"Type:P\t");break;
                                case AV_PICTURE_TYPE_B: fprintf(stderr,"Type:B\t");break;
                                default: fprintf(stderr,"Type:Other\t");break;
                }
                fprintf(stderr,"Number:%4d\n",pCodecParserCtx->output_picture_number);
            }
 
            //转为 rgb frame
          ret =  sws_scale(img_convert_ctx,
                      pFrame->data, pFrame->linesize, 0,
                              pCodecCtx->height,
                                  pFrameRGB->data, pFrameRGB->linesize);
          if(ret <=0 ){
                qDebug()<<"sws_scale   error ?";
          }
// memcpy(pFrameRGB->data, out_buffer, frameSize);
            //把这个RGB数据 用QImage加载
               QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
               // 保存
               QString depath = QString("F:/test/stream/h264/a_defarme%1.PNG").arg(var);
               if(! tmpImg.save(depath , "PNG")) {
                      qDebug()<<"tmpImg.save   error ";
               }
        }
	}
    av_parser_close(pCodecParserCtx);
    avcodec_close(pCodecCtx);
    av_free(pCodecCtx);
    //
    av_free(pFrame);
    av_free(pFrameRGB);
    sws_freeContext(img_convert_ctx);
    delete  out_buffer;
}
 

H264包 封装格式

///测试 h264包  封装格式
int main_h264()
{
    int seq = 0;
    int ret = 0;
    char * filename = "F:/test/stream/h264/enVideo.mp4";
    AVOutputFormat *outputFormat  = av_guess_format("mp4",filename,nullptr);
 
//    char * filename = "F:/test/stream/h264/enVideo.index.m3u8";
//    AVOutputFormat *outputFormat  = av_guess_format("hls",filename,nullptr);
    //输出格式
    AVFormatContext *outputFormatCtx = avformat_alloc_context();
    if(avformat_alloc_output_context2(&outputFormatCtx ,outputFormat,nullptr, filename) < 0 ){
          fprintf(stderr,"Error avformat_alloc_output_context2 \n");
          return 1;
    }
    if(!outputFormatCtx){
     fprintf(stderr,"Error alloc output 2 \n");
     return 1;
   }
    //输出编码器
    AVCodec *outCodec = avcodec_find_decoder(AVCodecID::AV_CODEC_ID_H264 );
    if( !outCodec ){
      fprintf(stderr,"Error avcodec_find_encoder \n");
      return 1;
    }
    //视频流通道
    AVStream * outputStream = avformat_new_stream(outputFormatCtx,outCodec);
   if(!outputStream){
     fprintf(stderr,"Error outputStream\n");
     return 1;
   }
   //视频流通道 信息
   outputStream->codecpar->codec_id = AV_CODEC_ID_H264;
   outputStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
   outputStream->codecpar->width = 1280;//TODO 宽高
   outputStream->codecpar->height = 960;
   outputStream->codecpar->format = AV_PIX_FMT_YUV420P;
   outputStream->time_base = AVRational{1,25};//TODO 25帧每秒
 
   if ( !(outputFormatCtx->flags & AVFMT_NOFILE) )
      if( avio_open2(&outputFormatCtx->pb , filename , AVIO_FLAG_WRITE ,nullptr, nullptr) < 0 ){
        fprintf(stderr,"Error avio_open2");
       return 1;
      }
 
    if(avformat_write_header(outputFormatCtx , nullptr) < 0){
        fprintf(stderr,"Error avformat_write_header");
       return 1;
    }
 
    fprintf(stderr,"OUTPUT FORMAT-------------------------------");
    av_dump_format(outputFormatCtx , 0 ,filename ,1);
 
    int dts = 0;
    for (int var = 0; var < 49; var++) {
        QString path = QString("F:/test/stream/h264/farme%1.dat").arg(var);
        QFile frame(path);
        frame.open(QIODevice::ReadOnly);
        QByteArray array = frame.readAll();
        char* buff = array.data();
 
        ///// 把h264 转成 AVPacket
         AVPacket opkt;
         av_new_packet(&opkt, array.size() );
         memcpy(opkt.data ,buff, array.size());
         //转换时间戳
         AVRational time_base = outputStream->time_base;
         opkt.pts = opkt.dts = dts++;
         opkt.pts = av_rescale_q(opkt.pts, AVRational{1,25}, time_base);
         opkt.dts = av_rescale_q(opkt.dts, AVRational{1,25}, time_base);
         opkt.duration = av_rescale_q(1, AVRational{1,25}, time_base);
         ///
         ret = av_write_frame(outputFormatCtx, &opkt);
         if(ret < 0)
             fprintf(stderr, "Error write frame");
         else
             fprintf(stderr, "FFMPEG Successful ");
    }
    ret = av_write_trailer(outputFormatCtx);
    if(ret < 0 )
            fprintf(stderr,"Error write trailer");
 
}
 
 

最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)-雷神博客