ffmpeg:一個簡單的格式轉換器

        參考ffmpegAPI提供的例子實現了一個簡單的封裝格式轉換器,在實際轉換時候,需要考慮對h264的兩種封裝格式分別是h264和avc1。
       AVC1 描述:H.264 bitstream without start codes.一般通過ffmpeg轉碼生成的視頻,是不帶起始碼0×00000001的。
      H264 描述:H.264 bitstream with start codes.一般對於一下HDVD等電影的壓制格式,是帶有起始碼0×00000001的。
       一般的像(mp4、mkv、flv)的封裝格式都是沒有startcode的AVC1。 而像avi的封裝格式則是有start codes的H264 。ffmpeg提供了一個名稱爲“h264_mp4toannexb”的bitstream filter。其會將像mp4這樣的沒有start code的格式轉換爲像avi這樣的有start code。 使用方法如下: 

//註冊filter
 AVBitStreamFilterContext * vbsf = av_bitstream_filter_init("h264_mp4toannexb");

//轉換
if (pkt.stream_index == AVMEDIA_TYPE_VIDEO &&
                    in_stream->codecpar->codec_id == AV_CODEC_ID_H264) {
                AVPacket fpkt = pkt;
                int a = av_bitstream_filter_filter(vbsf,
                                                   out_stream->codec, NULL, &fpkt.data, &fpkt.size,
                                                   pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY);
                pkt.data = fpkt.data;
                pkt.size = fpkt.size;
            }
一個完整的格式轉換代碼如下:
if(In_File.isEmpty() || In_File.isNull()
            || Out_File.isEmpty() || Out_File.isNull())
        return -1;

    AVOutputFormat *ofmt = NULL;

    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;

    int ret,i;
    int frame_index = 0;

    // --[2]初始化輸入文件的格式上下文
    //將文件頭信息複製給avformatContext
    if((ret = avformat_open_input(&ifmt_ctx,In_File.toLocal8Bit(),NULL,NULL)) < 0)
    {
        qDebug()<<"could not open input file!";
        goto end;
    }
    //將流的參數信息複製給ifmt_ctx->streams
    if((ret = avformat_find_stream_info(ifmt_ctx,NULL)) < 0)
    {
        qDebug()<<"can not find a stream!\n";
        goto end;
    }

#if USE_DEBUG
    //cout<<"----------------------Input file Information-----------------"<<endl;
    av_dump_format(ifmt_ctx,0,In_File.toLocal8Bit(),0);
    // cout<<"-------------------------------------------------------------"<<endl;
#endif
    //[2]

            ifmt_ctx->streams[0]->codec_info_nb_frames)*(ifmt_ctx->streams[0]->codec_info_nb_frames
            +ifmt_ctx->streams[1]->codec_info_nb_frames);
    emit sendProgressMaxValue(frame_num);


    //[3] --初始化輸出文件的格式上下文
    avformat_alloc_output_context2(&ofmt_ctx,NULL,NULL,Out_File.toLocal8Bit());
    if(!ofmt_ctx)
    {
        qDebug()<<"could not creat output context";
        goto end;
    }
    ofmt = ofmt_ctx->oformat;
    //[3]

    //[4] --將輸入文件內容複製到輸出文件中
    for( i =0;i<ifmt_ctx->nb_streams;++i)
    {
        //獲取輸入流
        AVStream *in_stream = ifmt_ctx->streams[i];
        //創建輸出流
        AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
        if(!out_stream)
        {
            qDebug()<<"failed allocating output stram";
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        //複製輸入流的AVCodeContext到輸出流的AVCodeContext
        out_stream->time_base.num = in_stream->time_base.num;
        out_stream->time_base.den = in_stream->time_base.den;
        out_stream->avg_frame_rate = in_stream->avg_frame_rate;
        if(avcodec_copy_context(out_stream->codec,in_stream->codec) < 0)
        {
            qDebug()<<"failed to copy CodecContext!";
            goto end;
        }

        out_stream->codec->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

    }
    //[4]

#if USE_DEBUG
    //cout<<"----------------------Output file Information-----------------"<<endl;
    av_dump_format(ofmt_ctx,0,Out_File.toLocal8Bit(),1);
    //cout<<"-------------------------------------------------------------"<<endl;
#endif

    //打開輸出文件
    if(!(ofmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx->pb,Out_File.toLocal8Bit(),AVIO_FLAG_WRITE);
        if (ret < 0) {
            qDebug()<<"Could not open output file: "<<Out_File.toLocal8Bit();
            goto end;
        }
    }

    //寫頭文件
    if(avformat_write_header(ofmt_ctx,NULL) < 0)
    {
        qDebug()<<"Error occurred when opening output file";
        goto end;
    }

    AVBitStreamFilterContext * vbsf = av_bitstream_filter_init("h264_mp4toannexb");

    //寫入文件內容
    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];
        out_stream = ofmt_ctx->streams[pkt.stream_index];

        //qDebug()<<pkt.pts<<" "<<pkt.dts;
        QStringList inExt = QString(ifmt_ctx->iformat->extensions).split(",");
        QStringList outExt = QString(ofmt_ctx->oformat->extensions).split(",");
        //qDebug()<<"inExt:"<<inExt;
        //qDebug()<<"outExt:"<<outExt;
        if(!((inExt.contains("mp4") || inExt.contains("mkv") || inExt.contains("flv")) &&
             (outExt.contains("mp4") || outExt.contains("mkv")|| outExt.contains("flv"))))
        {qDebug()<<"run";
            if (pkt.stream_index == AVMEDIA_TYPE_VIDEO &&
                    in_stream->codecpar->codec_id == AV_CODEC_ID_H264) {
                AVPacket fpkt = pkt;
                int a = av_bitstream_filter_filter(vbsf,
                                                   out_stream->codec, NULL, &fpkt.data, &fpkt.size,
                                                   pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY);
                pkt.data = fpkt.data;
                pkt.size = fpkt.size;
            }
        }

        //轉換PTS/DTS
        /* copy packet */
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(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;

        //寫入
        if(av_interleaved_write_frame(ofmt_ctx,&pkt) < 0)
        {
            qDebug()<<"Error muxing packet";
            break;
        }
#if USE_DEBUG
        qDebug()<<"Write "<<frame_index<<" frames to output file";
#endif
        av_free_packet(&pkt);
        frame_index++;
    }

    //寫文件尾
    av_write_trailer(ofmt_ctx);

end:
    avformat_close_input(&ifmt_ctx);
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    av_bitstream_filter_close(vbsf);
    if (ret < 0 && ret != AVERROR_EOF) {
        qDebug()<<"Error occurred.";
        return -1;
    }

    return 1;
}
本人使用Qt完成了格式轉換器的GUI,並提供了一個進度條來預估當前轉換進度。程序效果如下:
     github下載地址: https://github.com/pencilmonster/Remuxer
參考文獻:
h264封裝
視頻複用器
AVC1、h264
點擊打開鏈接



發佈了109 篇原創文章 · 獲贊 168 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章