作为在音视频行业持续发力多年的视频服务厂商,TSINGSEE青犀视频研发了开源平台EasyDarwin,还有多款音视频流媒体平台,我们开发流媒体平台基本都要使用ffmpeg,在ffmpeg中,H264在编码前必须要转换成YUV420P,本文就分享一下怎么将h264转成YUV420P。
以下就是yuv420:
八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8] 码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8 映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7]
注意:码流12字节个代表8个像素
理解需要画矩阵,如下:
码流数据:(4:2:0 ~ 4:0:2)
代码语言:javascript复制Y0 U0
Y1
Y2 U2
Y3
Y5 V5
Y6
Y7 V7
Y8
映射像素:
代码语言:javascript复制Y0 U0 V5
Y1 U0 V5
Y2 U2 V7
Y3 U2 V7
Y5 U0 V5
Y6 U0 V5
Y7 U2 V7
Y8 U2 V7
YUV 4:2:0采样,每四个Y共用一组UV分量。 所以要把H264解码YUV420。首先需要把ffmpeg初始化: 代码如下:
代码语言:javascript复制typedef struct __DECODER_OBJ
{
AVCodec *pVideoCodec;
AVCodecContext *pVideoCodecCtx;
AVFrame *mVideoFrame420; ///< 视频帧
AVPicture pYuvFrame;
struct SwsContext *pSws_ctx;
uint8_t *pBuffYuv420;
int codec;
int width;
int height;
int outputFormat;
int frameType;
int numBytes;
bool isInit;
}DECODER_OBJ;
avfilter_register_all();
avcodec_register_all();/*注册所有的编码解码器*/
av_register_all();// //注册所有可解码类型
decoderObj.pVideoCodec = avcodec_find_decoder(avCodecId);//H264
if (NULL == decoderObj.pVideoCodec) {
ReleaseVideoDecoder();
return -3;
}
decoderObj.pVideoCodecCtx = avcodec_alloc_context3(decoderObj.pVideoCodec);
if (NULL == decoderObj.pVideoCodecCtx) {
ReleaseVideoDecoder();
return -4;
}
AVDictionary *opts = NULL;
int ret = avcodec_open2(decoderObj.pVideoCodecCtx, decoderObj.pVideoCodec, &opts);
if (ret < 0) {
ReleaseVideoDecoder();
return -5;
}
decoderObj.mVideoFrame420 = av_frame_alloc();
if (NULL == decoderObj.mVideoFrame420) {
ReleaseVideoDecoder();
return -6;
}
avpicture_alloc(&decoderObj.pYuvFrame, AV_PIX_FMT_YUV420P, width, height);
decoderObj.numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1);
初始化完成,然后就需要把h264帧传进去进行解码出YUV420: 代码如下:
代码语言:javascript复制AVPacket pAvPacket = { 0 };
decoderObj.mVideoFrame420->pict_type = picType;
pAvPacket.data = buf;
pAvPacket.size = size;
int res = 0;
int gotPic = 0;
res = avcodec_decode_video2(decoderObj.pVideoCodecCtx, decoderObj.mVideoFrame420, &gotPic, &pAvPacket);
if (!gotPic) return -9;
decoderObj.pSws_ctx = sws_getContext(decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
decoderObj.pVideoCodecCtx->pix_fmt, decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
sws_scale(decoderObj.pSws_ctx, decoderObj.mVideoFrame420->data, decoderObj.mVideoFrame420->linesize, 0,
decoderObj.mVideoFrame420->height, decoderObj.pYuvFrame.data, decoderObj.pYuvFrame.linesize);
拿到的decoderObj.pYuvFrame.data[0]就是YUV420数据。 最后也不要忘记释放内存。 代码如下:
代码语言:javascript复制if (NULL != decoderObj.mVideoFrame420)
{
av_frame_free(&decoderObj.mVideoFrame420);
decoderObj.mVideoFrame420 = NULL;
}
if (NULL != decoderObj.pVideoCodecCtx)
{
avcodec_close(decoderObj.pVideoCodecCtx);
if (NULL != decoderObj.pVideoCodecCtx->priv_data) free(decoderObj.pVideoCodecCtx->priv_data);
if (NULL != decoderObj.pVideoCodecCtx->extradata) free(decoderObj.pVideoCodecCtx->extradata);
avcodec_free_context(&decoderObj.pVideoCodecCtx);
decoderObj.pVideoCodecCtx = NULL;
}
if (NULL != &decoderObj.pYuvFrame)
{
avpicture_free(&decoderObj.pYuvFrame);
//decoderObj.pYuvFrame = NULL;
}
if (NULL != decoderObj.pSws_ctx)
{
sws_freeContext(decoderObj.pSws_ctx);
decoderObj.pSws_ctx = NULL;
}
if (NULL != decoderObj.pVideoCodec)
{
decoderObj.pVideoCodec = NULL;
}
if (NULL != decoderObj.pBuffYuv420)
{
av_free(decoderObj.pBuffYuv420);
decoderObj.pBuffYuv420 = NULL;
}
if (decoderObj.pSws_ctx) {
sws_freeContext(decoderObj.pSws_ctx);
decoderObj.pSws_ctx = NULL;
}
最终效果:使用ffplay指令播放yuv一帧数据
代码语言:javascript复制ffplay -i -video_size 700*700 $FILE
在TSINGSEE青犀视频开发的流媒体平台中,EasyNVR、EasyDSS都已经是成熟稳定的视频流媒体平台,可以直接下载测试,EasyRTC的重制版还正在开发当中,其架构有了新的方向,在不久之后新的版本也会上线和大家见面,TSINGSEE青犀视频云边端架构全平台都欢迎大家测试和了解。