您的位置:首页 > 移动开发 > Android开发

Android使用FFmpeg 解码H264并播放(三)

2017-06-12 18:01 603 查看
上一节记录了Android使用FFmpeg解码H264的过程。这一节记录在Android上播放的过程。

问题描述

在开发中使用某摄像头的SDK,只能获取到一帧帧的 H264 视频数据,不知道视频流地址,需要自己解码出图像并播放。

问题解决

Android 播放解码后的视频帧

在Android上播放视频的总体思路是在Native层从 Surface 获取 到ANativeWindow,通过修改 ANativeWindow 的显示 buffer 来更新 Surface 的画面,这样一帧帧更新,最终看到的就是动画。

AVFrame 格式转化

一般从视频流里解析出的 AVFrame 的格式是 yuv 的,不能直接在 Android 的 nativewindow 上使用,需要先转换为 ARGB 或者 RGB565才行。

转码的代码网上很多,大致如下:

//1. 准备一个容器来装转码后的数据
AVFrame dst_frame = av_frame_alloc();
//在解码上下文使用extradata解析出第一帧图像后,ctx的width和height,pix_format 写入了实际的视频宽高,像素格式
dst_frame->width = ctx->width;
dst_frame->height = ctx->height;
//2. 转码为ARGB,来给NativeWindow显示
dst_frame->format = AV_PIX_FMT_ARGB;
//3. 根据输入图像和输出图像的信息(宽、高、像素),初始化格式转换上下文
//应该重复使用该上下文,不要每一帧都初始化一次
struct SwsContext * swsCtx = sws_getContext(
src_frame->width, src_frame->height,(enum AVPixelFormat) src_frame->format,
src_frame->width, src_frame->height,(enum AVPixelFormat) dst_frame->format,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
//4. 初始化之前准备的dst_frame的buffer
int buffer_size = av_image_get_buffer_size(
(enum AVPixelFormat) dst_frame->format,
src_frame->width,
src_frame->height, 1);
uint8_t * buffer = (uint8_t *) av_malloc(sizeof(uint8_t) * buffer_size );
//5. 绑定dst_frame和新申请的buffer
av_image_fill_arrays(dst_frame->data, dst_frame->linesize, buffer,
(enum AVPixelFormat) dst_frame->format,
dst_frame->width, dst_frame->height, 1);
//6. 转码
sws_scale(swsCtx , (const uint8_t *const *) src_frame->data,
src_frame->linesize, 0, src_frame->height,
dst_frame->data, dst_frame->linesize);


渲染画面到 NativeWindow

从surface 中获取 NativeWindow

设置NativeWindow缓冲区格式

锁定NativeWindow缓冲区

写入数据到缓冲区

释放缓冲区

如果不再渲染了,释放NativeWindow

代码如下:

H264FrameRender.java

/**
* 设置渲染的Surface
*/
public void setSurface(Surface surface) {
if (nativeObject <= 0) {
throw new IllegalStateException("H264FrameRender init failed");
}
_setSurface(this.nativeObject, surface);
}
/**
* JNI
*/
private native void _setSurface(long nativeObject, Surface surface);


JNI

JNIEXPORT void JNICALL Java_com_demo_H264FrameRender__1setSurface(JNIEnv *env, jobject instance,jlong nativeObject, jobject surface){
//
H264Render *render = (H264Render *) nativeObject;
//获取到NativeWindow,
ANativeWindow* window = ANativeWindow_fromSurface(surface);
//绑定当前的window到native 层的render上,等待渲染
if(render->window){
ANativeWindow_release(render->window);
render->window = NULL;
}
render->window = window;
}


渲染

解码器和格式转换器完成后会将调用渲染方法,将最终的数据渲染到
NativeWindow
上。

/**
* @param render 渲染器
* @param data   解码和转换处理后的图像数据
* @param len    data 的长度
* @param width  视频的宽
* @param height 视频的高
*/
void render_rend(NativeRender *render, signed char *data, size_t len, int width, int height) {
if (render->window == NULL) {
LOGE("window == null");
return;
}
//设置缓冲区(宽,高,像素格式)
ANativeWindow_setBuffersGeometry(render->window, width, height, WINDOW_FORMAT_RGBA_8888);
//绘制
int32_t locked = ANativeWindow_lock(render->window, render->buffer, NULL);

if (locked == 0) {
//由于window的 stride 和 frame 的 stride 不同,需要逐行拷贝
uint8_t *dst = (uint8_t *) render->buffer->bits;
uint8_t *src = dst_frame->data[0];
int dest_stride = buffer->stride * 4;
int src_stride = dst_frame->linesize[0];
int height = dst_frame->height, h;
for (h = 0; h < height; h++) {
memcpy(dst + h * dest_stride,
src + h * src_stride,
(size_t) src_stride);
}

ANativeWindow_unlockAndPost(render->window);
} else {
LOGD("failed to lock window");
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: