用途
本次是因为项目中有一个需求,要将camera输出的yuv视频帧转为jpeg,用于视频流传输,在使用途中遇到一点问题,特此记录一下。
注意事项
在安卓的camera中输出的YUV视频帧格式大多是NV21,而Ffmpeg中编码jpeg时需要指定视频格式为AV_PIX_FMT_YUVJ420P,这两种格式存在区别,NV21属于YUV420SP,而Ffmpeg需要的是YUV420P格式,因此在输入数据给Ffmpeg编码前,需要对camera的数据进行格式转换,将NV21转为I420(YUV420P),然后再送入Ffmpeg进行编码。
AVPacket *av_packet_alloc(void)
{
AVPacket *pkt = static_cast<AVPacket *>(av_mallocz(sizeof(AVPacket)));
if (!pkt)
return pkt;
av_init_packet(pkt);
return pkt;
}
初始化Jpeg编码器
int MJpegEncoder::Open(int width, int height)
{
AVCodec* cc_encoder;
m_width = width;
m_height = height;
// 指定编码器为JPEG
cc_encoder = avcodec_find_encoder(CODEC_ID_MJPEG);
if(cc_encoder == NULL)
{
STACK_LOGE("CMJpegEncoder::Init avcodec_find_decoder(CODEC_ID_MJPEG) failed\n");
return false;
}
// 寻找上下文
codecContext = avcodec_alloc_context3(cc_encoder);
if(codecContext == NULL)
{
STACK_LOGE("CMJpegEncoder::Init avcodec_alloc_context3(cc_encoder) failed\n");
return false;
}
// 设置宽高以及视频格式
codecContext->coded_width = m_width;
codecContext->coded_height = m_height;
codecContext->time_base.den = 10;
codecContext->time_base.num = 1;
// 指定编码后的jpeg为YUVJ420P格式
codecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
if (avcodec_open2(codecContext, cc_encoder, NULL) < 0)
{
STACK_LOGE("avcodec_open(mjpeg_decoder_, cc_decoder) failed\n");
return false;
}
// 申请AVFrame,存放原始视频数据
srcFrame = av_frame_alloc();
if (!srcFrame)
{
Close();
return false;
}
srcFrame->width = m_width;
srcFrame->height = m_height;
// 原始视频格式,codecContext指定的是J420P,这里选择同格式420P,这里要保证输入的视频也是420P,否则会出错
srcFrame->format = AV_PIX_FMT_YUV420P;
if (av_frame_get_buffer(srcFrame, 0) < 0)
{
Close();
return false;
}
// 申请存放编码后的jpeg空间
dstPacket = av_packet_alloc();
if (!dstPacket)
{
Close();
return false;
}
return true;
}
这里需要注意,在安卓中camera一般输出NV21(YUV420SP),需要将NV21转为I420格式,再调用EncodeFrame进行编码,NV21转I420建议使用libyuv进行转换。
编码Jpeg数据, 输入一个包含YUV420P数据的数组,将编码后的JPEG数据保存带dst_data数组中,并返回数据长度。
int MJpegEncoder::EncodeFrame(u_int8_t* data, int length, uint8_t* dst_data)
{
if (!codecContext)
return 0;
// 将data中的数据填充到srcFrame中
int ret = av_image_fill_arrays(srcFrame->data, srcFrame->linesize, data, AV_PIX_FMT_YUV420P, m_width, m_height, 1);
if (ret < 0)
{
Close();
return 0;
}
int gotPicture = 0;
// 编码
if (avcodec_encode_video2(codecContext, dstPacket, srcFrame, &gotPicture) >= 0 && gotPicture)
{
int dst_length = dstPacket->size;
// 将jpeg数据复制到dst_data中,返回后用于网络传输
memcpy(dst_data, dstPacket->data, dst_length);
av_packet_unref(dstPacket);
return dst_length;
}
else
{
LOGD("jpeg encode failed");
return 0;
}
}
关闭JPEG编码器,释放相应数据以及资源
void MJpegEncoder::Close()
{
if (codecContext != NULL)
{
avcodec_flush_buffers(codecContext);
avcodec_close(codecContext);
av_free(codecContext);
}
if (srcFrame)
{
av_frame_free(&srcFrame);
}
if (dstPacket)
{
av_packet_unref(dstPacket);
av_free_packet(dstPacket);
}
}