开启辅助访问
 找回密码
 立即注册

FFmpeg4入门系列教程13:h264编码为mp4

jiayiming 回答数0 浏览数5566
本文首发于:FFmpeg4入门系列教程13:h264编码为mp4 - 食铁兽
索引地址:幽弥狂:FFmpeg4入门系列教程索引
上一篇:幽弥狂:FFmpeg4入门系列教程12:yuv编码为h264
上一篇将yuv源视频文件编码为*.h264的由libx264实现压缩的文件,将源文件从55M编码为620KB,但是h264文件只有视频数据,而且使用范围不太广。那么就需要进一步的封装,在此选用最常用的mp4格式为例。
随便选一台mp4格式文件,用ffmpeg_info或者ffprobe查看一下数据。
迈克尔杰克逊的beat it.mp4,输出如下:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'beat.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf57.73.100
  Duration: 00:05:00.88, start: 0.000000, bitrate: 683 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 852x480, 612 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 64 kb/s (default)
    Metadata:
      handler_name    : SoundHandler有两条流,一条h264的视频流,一条aac的音频流。目前我们有h264的流,创建一台mp4文件,新建一条流并将h264流插入进去。(暂时没有音频部分)
转换流程图为:


h264->mp4

打开输入文件(h264)

//======================输入部分============================//
        //打开输入文件
        if(avformat_open_input(&inVFmtCtx,inVFileName,NULL,NULL)<0){
            printf("Cannot open input file.\n");
            break;
        }

        //查找输入文件中的流
        if(avformat_find_stream_info(inVFmtCtx,NULL)<0){
            printf("Cannot find stream info in input file.\n");
            break;
        }

        //查找视频流在文件中的位置
        for(size_t i=0;i<inVFmtCtx->nb_streams;i++){
            if(inVFmtCtx->streams->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
                inVStreamIndex=(int)i;
                break;
            }
        }

        AVCodecParameters *codecPara = inVFmtCtx->streams[inVStreamIndex]->codecpar;//输入视频流的编码参数

        printf("===============Input information========>\n");
        av_dump_format(inVFmtCtx, 0, inVFileName, 0);
        printf("===============Input information========<\n");可以看到和正常打开视频文件一样,或者说除了原始视频文件(yuv格式)其他格式打开代码差不多。
打开输出文件(mp4)

//=====================输出部分=========================//
        //打开输出文件并填充格式数据
        if(avformat_alloc_output_context2(&outFmtCtx,NULL,NULL,outFileName)<0){
            printf("Cannot alloc output file context.\n");
            break;
        }

        //打开输出文件并填充数据
        if(avio_open(&outFmtCtx->pb,outFileName,AVIO_FLAG_READ_WRITE)<0){
            printf("output file open failed.\n");
            break;
        }

        //在输出的mp4文件中创建一条视频流
        AVStream *outVStream = avformat_new_stream(outFmtCtx,NULL);
        if(!outVStream){
            printf("Failed allocating output stream.\n");
            break;
        }
        outVStream->time_base.den=25;
        outVStream->time_base.num=1;
        outVStreamIndex=outVStream->index;

        //查找编码器
        AVCodec *outCodec = avcodec_find_encoder(codecPara->codec_id);
        if(outCodec==NULL){
            printf("Cannot find any encoder.\n");
            break;
        }

        //从输入的h264编码器数据复制一份到输出文件的编码器中
        AVCodecContext *outCodecCtx=avcodec_alloc_context3(outCodec);
        AVCodecParameters *outCodecPara = outFmtCtx->streams[outVStream->index]->codecpar;
        if(avcodec_parameters_copy(outCodecPara,codecPara)<0){
            printf("Cannot copy codec para.\n");
            break;
        }
        if(avcodec_parameters_to_context(outCodecCtx,outCodecPara)<0){
            printf("Cannot alloc codec ctx from para.\n");
            break;
        }
        outCodecCtx->time_base.den=25;
        outCodecCtx->time_base.num=1;

        //打开输出文件需要的编码器
        if(avcodec_open2(outCodecCtx,outCodec,NULL)<0){
            printf("Cannot open output codec.\n");
            break;
        }

        printf("============Output Information=============>\n");
        av_dump_format(outFmtCtx,0,outFileName,1);
        printf("============Output Information=============<\n");和上一篇类似。
编码部分

源文件是h264,MP4中的流也是h264,也就是说实际上并没有真正意义上的编码过程。
//写入文件头
        if(avformat_write_header(outFmtCtx,NULL)<0){
            printf("Cannot write header to file.\n");
            return -1;
        }

        //===============编码部分===============//

        AVStream *inVStream = inVFmtCtx->streams[inVStreamIndex];
        while(av_read_frame(inVFmtCtx,pkt)>=0){//循环读取每一帧直到读完
            if(pkt->stream_index==inVStreamIndex){//确保处理的是视频流
                //FIXME:No PTS (Example: Raw H.264)
                //Simple Write PTS
                //如果当前处理帧的显示时间戳为0或者没有等等不是正常值
                if(pkt->pts==AV_NOPTS_VALUE){
                    printf("frame_index:%d\n", frame_index);
                    //Write PTS
                    AVRational time_base1 = inVStream->time_base;
                    //Duration between 2 frames (us)
                    int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(inVStream->r_frame_rate);
                    //Parameters
                    pkt->pts = (double)(frame_index*calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);
                    pkt->dts = pkt->pts;
                    pkt->duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
                    frame_index++;
                }
                //Convert PTS/DTS
                pkt->pts = av_rescale_q_rnd(pkt->pts, inVStream->time_base, outVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->dts = av_rescale_q_rnd(pkt->dts, inVStream->time_base, outVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->duration = av_rescale_q(pkt->duration, inVStream->time_base, outVStream->time_base);
                pkt->pos = -1;
                pkt->stream_index = outVStreamIndex;
                printf("Write 1 Packet. size:%5d\tpts:%ld\n", pkt->size, pkt->pts);
                //Write
                if (av_interleaved_write_frame(outFmtCtx, pkt) < 0) {
                    printf("Error muxing packet\n");
                    break;
                }
                av_packet_unref(pkt);
            }
        }

        av_write_trailer(outFmtCtx);只是一些时间戳方面的处理。
软件输出

===============Input information========>
Input #0, h264, from 'result.h264':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Video: h264 (High), yuv420p(progressive), 352x288, 25 fps, 25 tbr, 1200k tbn, 50 tbc
===============Input information========<
Cannot open libmwv206dec.so, libmwv206dec.so: cannot open shared object file: No such file or directory
[libx264 @ 0x162cd00] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0x162cd00] profile High, level 1.3
============Output Information=============>
Output #0, mp4, to 'result.mp4':
    Stream #0:0: Video: h264 (High), yuv420p(progressive), 352x288, q=2-31, 25 tbn
============Output Information=============<
OutVIndex=0,pkt.stream_index=0
frame_index==0
Write 1 Packet. size: 6899      pts:0
...共300个Cannot open libmwv206dec.so ...是deepin linux系统错误。
执行完毕后得到只有视频没有音频的mp4文件。


完整代码在ffmpeg_Binner中13.video_encode_h2642mp4里面。
下一篇:FFmpeg4入门系列教程14:Linux下摄像头捕获并编码为h264
使用道具 举报
| 来自广东
当贝投影