视频学习笔记:Android ffmpeg解码多路h264视频并显示
Posted vonchenchen1
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视频学习笔记:Android ffmpeg解码多路h264视频并显示相关的知识,希望对你有一定的参考价值。
背景
android设备上使用ffmpeg解码多路h264视频,抽取了一个简单demo方便日后参考,在此记录一下。demo中主要涉及以下功能:
1.ffmpeg解码h264视频为yuv帧
2.使用ffmpeg将yuv帧转换为可以在画布上渲染的rgb帧
3.将Android的SurfaceView类传入jni层并使用rgb帧进行渲染
4.使用java类包装c++类,多线程解码多路视频
5.集成了opencv相关功能,在本例中可以使用相关api保存arg帧
其中解码部分代码参考了雷神博客的相关文章,并已经在项目中多处使用,surfaceview渲染部分参考了ijkplayer中的相关源码。项目地址如下,具体功能可以参考源码。
项目地址:https://git.oschina.net/vonchenchen/android_ffmpge_muti_decode.git
下面简单介绍一下工程的主要内容
功能实现
总体功能
首先需要使用ndk编译ffmpeg源码,这部分网上已经有比较多的介绍,在此不再赘述,编译好的文件已经在工程中可以直接使用。加载ffmpeg动态链接库的命令在Android.mk中,内容如下:
#ffmpeg
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := ../jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
include $(PREBUILT_SHARED_LIBRARY)
也就是直接将其copy到jniLibs文件的对应目录即可。
decoder.cpp文件负责具体调用ffmpeg的功能进行解码。之前的项目中一直使用这个类,直接定义一个变量并调用相关解码方法。对于解码多路视频,如果直接在jni文件中定义多个对象则显得比较啰嗦,如果能使用java类包装一下decode类,需要一个decode类就new一个对应的java类,不需要时让java回收,这是比较理想的,对此专门学习了一下这种实现,原理见这里的另外一篇博客http://blog.csdn.net/lidec/article/details/72872037。
当我们解码完毕后,需要把rgb帧渲染到画布上,这个过程参考了ijkplayer的实现,将surface作为一个参数传入jni层,拿到surface的缓冲区后将生成的rgb数据直接copy到这个缓冲区即可完成显示。相关ndk的api可以参考ndk文档,链接https://developer.android.com/ndk/reference/group___native_activity.html。这里注意,在编译时,LOCAL_LDLIBS中需要加入-ljnigraphics -landroid两个库。
解码与显示
这个类参考了雷神的博客,使用c++简单封装了一下。主干功能如下
int decoder::decodeFrame(const char *data, int length, void (*handle_data)(AVFrame *pFrame, void *param, void *ctx), void *ctx)
int cur_size = length;
int ret = 0;
memcpy(inbuf, data, length);
const uint8_t *cur_ptr = inbuf;
// Parse input stream to check if there is a valid frame.
//std::cout << " in data -- -- " << length<< std::endl;
while(cur_size >0)
int parsedLength = av_parser_parse2(parser, codecContext, &avpkt.data,
&avpkt.size, (const uint8_t*)cur_ptr , cur_size, AV_NOPTS_VALUE,
AV_NOPTS_VALUE, AV_NOPTS_VALUE);
cur_ptr += parsedLength;
cur_size -= parsedLength;
...
if (!avpkt.size)
continue;
else
int len, got_frame;
len = avcodec_decode_video2(codecContext, frame, &got_frame,
&avpkt);
if (len < 0)
...
if (got_frame)
frame_count++;
LOGE("frame %d", frame_count);
if(img_convert_ctx == NULL)
img_convert_ctx = sws_getContext(codecContext->width, codecContext->height,
codecContext->pix_fmt, codecContext->width, codecContext->height,
pixelFormat, SWS_BICUBIC, NULL, NULL, NULL);
...
if(img_convert_ctx != NULL)
handle_data(frame, renderParam, ctx);
return length;
这里简单进行介绍,传入h264视频buffer后利用av_parser_parse2解析出h264头在当前buffer中的偏移量,然后使用avcodec_decode_video2函数进行解码,最后得到一个AVFrame帧,这个帧就是h264流中的yuv帧。拿到这个帧和其他相关信息后,我们将这些内容传递给handle_data这个函数指针,由外面传入的handle_data函数处理生成的yuv帧。
帧处理回调在com_vonchenchen_android_video_demos_codec_CodecWrapper.cpp中实现,这个函数主要完成将yuv数据使用ffmpeg转换为rgb帧,并且获取surface的缓冲区,将rgb拷贝到这段缓冲区中。具体实现如下:
void handle_data(AVFrame *pFrame, void *param, void *ctx)
RenderParam *renderParam = (RenderParam *)param;
//yuv420p转换为rgb565
AVFrame *rgbFrame = yuv420p_2_argb(pFrame, renderParam->swsContext, renderParam->avCodecContext, pixelFormat);//AV_PIX_FMT_RGB565LE
LOGE("width %d height %d",rgbFrame->width, rgbFrame->height);
//for test decode image
//save_rgb_image(rgbFrame);
//用于传递上下文信息
EnvPackage *envPackage = (EnvPackage *)ctx;
//创建一个用来维护显示缓冲区的变量
ANativeWindow_Buffer nwBuffer;
//将java中的Surface对象转换为ANativeWindow指针
ANativeWindow *aNativeWindow = ANativeWindow_fromSurface(envPackage->env, *(envPackage->surface));
if (aNativeWindow == NULL)
LOGE("ANativeWindow_fromSurface error");
return;
//用来缩放rgb帧与显示窗口的大小,使得rgb数据可以适应窗口大小
int retval = ANativeWindow_setBuffersGeometry(aNativeWindow, rgbFrame->width, rgbFrame->height, WINDOW_FORMAT_RGB_565);
//锁定surface
if (0 != ANativeWindow_lock(aNativeWindow, &nwBuffer, 0))
LOGE("ANativeWindow_lock error");
return;
//将rgb数据拷贝给suface的缓冲区
if (nwBuffer.format == WINDOW_FORMAT_RGB_565)
memcpy((__uint16_t *) nwBuffer.bits, (__uint16_t *)rgbFrame->data[0], rgbFrame->width * rgbFrame->height *2);
//解锁surface并显示新的缓冲区
if(0 !=ANativeWindow_unlockAndPost(aNativeWindow))
LOGE("ANativeWindow_unlockAndPost error");
return;
//清理垃圾
ANativeWindow_release(aNativeWindow);
//清理rgb帧结构以及帧结构所指向的rgb缓冲区
av_free(rgbFrame->data[0]);
av_free(rgbFrame);
yuv转rgb
AVFrame *yuv420p_2_argb(AVFrame *frame, SwsContext *swsContext, AVCodecContext *avCodecContext, enum AVPixelFormat format)
AVFrame *pFrameRGB = NULL;
uint8_t *out_bufferRGB = NULL;
pFrameRGB = av_frame_alloc();
pFrameRGB->width = frame->width;
pFrameRGB->height = frame->height;
//给pFrameRGB帧加上分配的内存; //AV_PIX_FMT_ARGB
int size = avpicture_get_size(format, avCodecContext->width, avCodecContext->height);
//out_bufferRGB = new uint8_t[size];
out_bufferRGB = av_malloc(size * sizeof(uint8_t));
avpicture_fill((AVPicture *)pFrameRGB, out_bufferRGB, format, avCodecContext->width, avCodecContext->height);
//YUV to RGB
sws_scale(swsContext, frame->data, frame->linesize, 0, avCodecContext->height, pFrameRGB->data, pFrameRGB->linesize);
return pFrameRGB;
总结
本文简单介绍了Android ffmpeg解码多路h264视频并显示的流程并附有完整示例,希望能够给有相关需求并且还在探索的同学一个参考,也希望大家多多指正,一起进步。
以上是关于视频学习笔记:Android ffmpeg解码多路h264视频并显示的主要内容,如果未能解决你的问题,请参考以下文章