五、IOS-FFmpeg解封装、解码、编码、封装

2019-06-01  本文已影响0人  Johnny_Wu

结合前面的知识,这次把FFmpeg的解封装、解码、编码和封装整合到一块。
代码仓库:https://github.com/wulang150/FFmpegTest.git
代码文件:MuxVideoViewController.m

一、解封装(MP4-->H264)

跟之前讨论的差不多,就是从MP4文件中解出:
1、视频流H264
2、音频流AAC
都存于AVPacket结构体中。

二、解码(H264-->YUV)

这里跟前面讲的不太一样。前面讲的是基于Annex-B格式的H264流的解码。这次是从Mp4解封装得到了,所以是AVCC格式的H264。
它们区别有两点:一个是参数集(SPS, PPS)组织格式,一个是分隔。

所以,对于Annex-B格式,因为有特定的分隔符,那么不需要其他信息,我就可以得到每个NAL,包括SPS和PPS等信息。但对于AVCC格式的H264流,如果不借助其他信息,我们根本无法得到每个NAL(因为存储NAL的长度不一定是4个字节)、SPS和PPS等信息。这时候就必须借助extradata头部信息。
下面是H264的extradata格式:


屏幕快照 2019-06-01 下午2.34.34.png

其中有几个比较重要的信息:
1、lengthSizeMinusOne:代表用几个字节来存NAL的长度。计算方法是 1 + (lengthSizeMinusOne & 3)
2、sequenceParameterSetNALUnits:SPS信息
3、pictureParameterSetNALUnits:PPS信息

所以,要解码AVCC格式的H264,就必须拿到extradata。去哪里拿呢?
方法一:直接通过in_stream->codecpar的拷贝给解码上下文de_CodecCtx,里面包含了extradata信息。简单粗暴。

avcodec_parameters_to_context(de_CodecCtx, in_stream->codecpar)

方法二:你也可以只拷贝extradata信息

AVStream *in_stream = ifmt_ctx->streams[in_stream_video];
de_CodecCtx->pix_fmt = STREAM_PIX_FMT;
//这个extradata才是最重要的,从这里才可以拿到解码相关的信息,其实就是怎么拿到每一个nal
de_CodecCtx->extradata_size = in_stream->codecpar->extradata_size;
de_CodecCtx->extradata = malloc(de_CodecCtx->extradata_size);
memcpy(de_CodecCtx->extradata, in_stream->codecpar->extradata, de_CodecCtx->extradata_size);

后面解码的代码跟之前差不多。我修改了timeBase,为了使AVCodeCtx里面的timeBase不同于AVStream里面的,当然不修改也是可以的。修改只是显示更直观,方面后面的操作。

//修改解码后的参数,转换为我们常见的pts是0 1 2,ctb
de_frame->pts = av_rescale_q(de_frame->pts, ifmt_ctx->streams[in_stream_video]->time_base, Base_TB);
de_frame->pkt_duration = av_rescale_q(de_frame->pkt_duration, ifmt_ctx->streams[in_stream_video]->time_base, Base_TB);
de_frame->pkt_dts = av_rescale_q(de_frame->pkt_dts, ifmt_ctx->streams[in_stream_video]->time_base, Base_TB);

三、编码(YUV-->H264)

上面的解码好像不需要配置什么参数,比如帧率、width、height,就可以解码成功了。为什么呢?编码其实就是压缩,压缩后的文件,解压后都只对应一种原文件。不会因为配置某些参数,就可以解压出更佳的文件,所以配置参数也没用。比如说我有一个200MB的低清Mp4文件,难道我配置某个参数就可以得到高清的原文件了?不可能。
但是,编码就不一样,我可以配置参数,比如压缩率10%、20%,这些我是可选择的。

en_CodecCtx->bit_rate = in_stream->codecpar->bit_rate;
/* resolution must be a multiple of two */
en_CodecCtx->width = width;
en_CodecCtx->height = height;
/* frames per second */
en_CodecCtx->time_base = Base_TB;

1、配置了width、height。对应YUV的分辨率。那我可以不按YUV的分辨率配置吗?
可以的,只是每一帧都不完整。比如,原分辨率:960x540,如果我设置为一半:480x270,那么每一帧只有原来的1/4。你喜欢这样,也是可以的。
2、那么,我可以改变分辨率吗?
可以的,需进行转分辨率。对应里面scaleVideo函数的内容:

//创建转换器
if(!sws_scale_ctx){
    sws_scale_ctx = sws_getContext(src_w, src_h, frame->format,
                                       dst_w, dst_h, frame->format,
                                       SWS_BILINEAR, NULL, NULL, NULL);
}
//进行转换
sws_scale(sws_scale_ctx, (const uint8_t * const*)frame->data,
              frame->linesize, 0, src_h, dstFrame->data, dstFrame->linesize);

3、还有就是配置了码流和timeBase。为了控制编码后的大小,前面有解释过。

四、封装(H264-->MP4)

1、配置对应的AVCodecContext,可以使用上面编码使用的AVCodecContext,但注意的是,必须在avcodec_open2前加入:

//mp4一定要配这个,并且得在avcodec_open2前添加,要不然,就只有黑屏
/* Some formats want stream headers to be separate. */
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
      muxEnCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

要不然就只有黑屏。

2、创建视频流AVStream
创建AVStream后,需要配置它time_base和codecpar参数,具体就需要用到前面提到的AVCodecContext了:

videoStream->time_base = codecCtx->time_base;
avcodec_parameters_from_context(videoStream->codecpar, codecCtx);

3、写入头部信息:

avformat_write_header(ofmt_ctx, NULL);

执行这个之后,videoStream->time_base会自动修改为正常的值,小于15360,都会改为15360。

4、写入具体的pkt
在写入pkt之前,需进行timebase的转换,因为前面进行了转换。如果前面没进行转换,这里就不需要转换:

av_packet_rescale_ts(pkt, muxEnCodecCtx->time_base, videoStream->time_base);

videoStream->time_base很重要,控制视频总时间,播放速度,就靠它了。
最后,再写入pkt

av_interleaved_write_frame(ofmt_ctx, pkt);

五、后话

(一)前面我进行了timebase的转换,后面我想修改帧率就比较直观了。
1、比如设置帧率为15:
time_base = (AVRational){1, 15};
2、以两倍速率播放
time_base = (AVRational){1, 30};

(二)如果你需要合成mp4,需要自己构建extraData的时候。分两种情况。
1、如果流是Annex-B,那么就这样构建extraData

        extradata[0]=0x0;
        extradata[1]=0x0;
        extradata[2]=0x0;
        extradata[3]=0x01;
        memcpy(extradata+4, m_Sps, m_spsLen);
        endIndex = m_spsLen+4;
        
        extradata[endIndex + 0]=0x0;
        extradata[endIndex + 1]=0x0;
        extradata[endIndex + 2]=0x0;
        extradata[endIndex + 3]=0x01;
        memcpy(extradata+endIndex + 4, m_Pps, m_ppsLen);
        endIndex = endIndex + m_ppsLen + 4;

    formatSt->codec->extradata=extradata;
    formatSt->codec->extradata_size=endIndex;

2、如果流是AVCC,就得按标准格式构建

// Extradata contains PPS & SPS for AVCC format
        int extradata_len = 8 + spsFrameLen - 4 + 1 + 2 + ppsFrameLen - 4;
        c->extradata = (uint8_t*) av_mallocz(extradata_len);
        c->extradata_size = extradata_len;
        c->extradata[0] = 0x01;
        c->extradata[1] = spsFrame[4 + 1];
        c->extradata[2] = spsFrame[4 + 2];
        c->extradata[3] = spsFrame[4 + 3];
        c->extradata[4] = 0xFC | 3;
        c->extradata[5] = 0xE0 | 1;
        int tmp = spsFrameLen - 4;
        c->extradata[6] = (tmp >> 8) & 0x00ff;
        c->extradata[7] = tmp & 0x00ff;
        int i = 0;
        for (i = 0; i < tmp; i++)
            c->extradata[8 + i] = spsFrame[4 + i];
        c->extradata[8 + tmp] = 0x01;
        int tmp2 = ppsFrameLen - 4;
        c->extradata[8 + tmp + 1] = (tmp2 >> 8) & 0x00ff;
        c->extradata[8 + tmp + 2] = tmp2 & 0x00ff;
        for (i = 0; i < tmp2; i++)
            c->extradata[8 + tmp + 3 + i] = ppsFrame[4 + i];
上一篇 下一篇

猜你喜欢

热点阅读