YUV420
YUV420是指:Y : UV = 4 : 1
YUV420P
YUV420P是指:YUV的排列方式,先将Y排列完,再将U排列完,最后将V排列完。如:
YYYYYYYYYYYYYYYY UUUU VVVV
FFmpeg解码出来的视频YUV数据是存储在AVFrame中的data里面,以YUV420P为视频数据给OPenGL渲染。
Y分量:frame->data[0]
U分量:frame->data[1]
V分量:frame->data[2]
绝大多数视频都是YUV420P格式的,对于不是YUV420P格式的,先将其转换(sws_scale)为YUV420P后再给OPenGL渲染。
在获取 avFrame 获取YUV420P数据
在 Video.cpp 中 void WlVideo::play() 的回调方法 void *playVideo(void *data) 里面获取获取YUV420P数据
*注意 导包 在Video.h
extern "C"
{
/* ... */
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
};
LOGE("子线程解码一个avFrame成功");
if (avFrame->format == AV_PIX_FMT_YUV420P) {
//渲染
} else {
AVFrame *pFrameYUV420 = av_frame_alloc();
//获取存储空间大小
//1转换格式
//2视频宽
//2视频高
int num = av_image_get_buffer_size(
AV_PIX_FMT_YUV420P,
video->avCodecContext->width,
video->avCodecContext->height,
1);
uint8_t *buffer = static_cast<uint8_t *>(av_malloc(num * sizeof(uint8_t)));
//填充数据
av_image_fill_arrays(
pFrameYUV420->data,
pFrameYUV420->linesize,
buffer,
AV_PIX_FMT_YUV420P,
video->avCodecContext->width,
video->avCodecContext->height,
1);
//转码上下文
SwsContext *sws_cxt = sws_getContext(
video->avCodecContext->width,
video->avCodecContext->height,
video->avCodecContext->pix_fmt,
video->avCodecContext->width,
video->avCodecContext->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
if (!sws_cxt) {
av_frame_free(&pFrameYUV420);
av_free(pFrameYUV420);
pFrameYUV420 = NULL;
av_free(buffer);
continue;
}
//转换
sws_scale(
sws_cxt,
avFrame->data,
avFrame->linesize,
0,
avFrame->height,
pFrameYUV420->data,
pFrameYUV420->linesize);
//渲染
//释放
av_frame_free(&pFrameYUV420);
av_free(pFrameYUV420);
pFrameYUV420 = NULL;
av_free(buffer);
sws_freeContext(sws_cxt);
}
在 player.java 写一个回调函数
public void onCallRenderYUV(int width, int height, byte[] y, byte[] u, byte[] v) {
MyLog.d("获取到yuv数据");
}
在 WlCallJava.h 中写方法id 回调方法
jmethodID jmid_renderyuv;
void onCallRenderYUV(int width, int height, uint8_t *fy, uint8_t *fu, uint8_t *fv);
在 WlCallJava.cpp 构造函数中初始化方法id
jmid_renderyuv = env->GetMethodID(jlz, "onCallRenderYUV", "(II[B[B[B)V");
在 WlCallJava.cpp 中实现 onCallRenderYUV
void WlCallJava::onCallRenderYUV(int width, int height, uint8_t *fy, uint8_t *fu, uint8_t *fv) {
JNIEnv *jniEnv;
if (javaVM->AttachCurrentThread(&jniEnv, 0) != JNI_OK) {
if (LOG_DEBUG) {
LOGE("call onCallComplete worng");
}
return;
}
//将uint8_t转成jbyte数组
//填充y
jbyteArray y = jniEnv->NewByteArray(width * height);
jniEnv->SetByteArrayRegion(y, 0, width * height, reinterpret_cast<const jbyte *>(fy));
//填充u
jbyteArray u = jniEnv->NewByteArray(width * height / 4);
jniEnv->SetByteArrayRegion(u, 0, width * height / 4, reinterpret_cast<const jbyte *>(fu));
//填充v
jbyteArray v = jniEnv->NewByteArray(width * height / 4);
jniEnv->SetByteArrayRegion(v, 0, width * height / 4, reinterpret_cast<const jbyte *>(fv));
jniEnv->CallVoidMethod(jobj, jmid_renderyuv, width, height, y, u, v);
//释放
jniEnv->DeleteLocalRef(y);
jniEnv->DeleteLocalRef(u);
jniEnv->DeleteLocalRef(v);
javaVM->DetachCurrentThread();
}
在 Video.cpp 中的 playVideo 回调方法也就是上文标注 //渲染 的部分调用回调函数
// LOGE("子线程解码一个avFrame成功");
if (avFrame->format == AV_PIX_FMT_YUV420P) {
LOGE("AV_PIX_FMT_YUV420P");
//渲染
video->wlCallJava->onCallRenderYUV(
video->avCodecContext->width,
video->avCodecContext->height,
avFrame->data[0],
avFrame->data[1],
avFrame->data[2]);
} else {
/* ... */
//转换
/* ... */
//渲染
video->wlCallJava->onCallRenderYUV(
video->avCodecContext->width,
video->avCodecContext->height,
pFrameYUV420->data[0],
pFrameYUV420->data[1],
pFrameYUV420->data[2]);
//释放
/* ... */
}