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

结合前面的知识,这次把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:使用start code分隔NAL(start code为三字节或四字节,0x000001或0x00000001,一般是四字节);SPS和PPS按流的方式写在头部。
  • AVCC:使用NALU长度(固定字节,通常为4字节)分隔NAL;在头部包含extradata(或sequence header)的结构体。

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


五、IOS-FFmpeg解封装、解码、编码、封装_第1张图片
屏幕快照 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};

你可能感兴趣的:(五、IOS-FFmpeg解封装、解码、编码、封装)