在上一篇文章中实现了Android平台MP4解码YUV数据,

本篇我们实现Android平台解码avi并用SurfaceView播放。

先上图看效果:

0818b9ca8b590ca3270a3433284dd417.png

思路:

1.把封装好的视频数据解码为YUV

2.YUV数据转化为RGB。

3.一帧一帧的传给SurfaceView显示出来

PS:

其实YUV数据可直接在SurfaceView显示,在研究Android系统多媒体框架的stagefright视频显示时发现,根本找不到omx解码后的yuv是怎么转换成RGB的代码,yuv数据在render之后就找不到去向了,可画面确确实实的显示出来了。

稍微看一下AsomePlayer的代码,不难发现,视频的每一帧是通过调用了SoftwareRenderer来渲染显示的、这是一个很大的突破,以后可以直接丢yuv数据到surface显示,无需耗时耗效率的yuv转RGB了,这部分知识点会在以后的文章中实现本篇不涉及。

下面看具体实现:

1.首先我们拷贝一份上一章的代码命名为ffmpegandroidplayer

2.根据本章需要修改代码

界面上一个SurfaceView

一个native 的play方法负责把Surface传给底层

@Override

public void surfaceCreated(SurfaceHolder holder) {

new Thread(new Runnable() {

@Override

public void run() {

play(surfaceHolder.getSurface());

}

}).start();

}

...

public native int play(Object surface);

3.修改CMakeLists.txt

target_link_libraries( native-lib log android avutil-55 swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57

${log-lib} )

4.实现play方法。

写这里方法的时候报了一个错,记录一下。

java.lang.UnsatisfiedLinkError: No implementation found for int com.ws.ffmpegandroidplayer.MainActivity.play

解决思路:

1、JNIEnv *env参数的使用

所有JNI接口的第一个参数是JNIEnv *env, 在C中,使用方法是

(*env)->NewStringUTF(env, "Hello from JNI!");

但在C++中,其调用方法是

env->NewStringUTF("Hello from JNI!");

为什么有这种区别呢,看看jni.h中关于JNIEnv的定义就可以知道了:

#if defined(__cplusplus)

typedef _JNIEnv JNIEnv;

#else

typedef const struct JNINativeInterface* JNIEnv;

#endif

可以看到,对于C和C++,定义有所不同,主要原因是C不支持类,所以采用了一种变通的方法。

2、接口找不到

在Java中调用JNI接口时,出现异常,察看日志,发现有如下错误:

WARN/dalvikvm(422): No implementation found for native Lcom/whty/wcity/HelixPlayer;.setDllPath (Ljava/lang/String;)V

检查了几遍代码,Cpp中确实定义了这个接口,而且仔细对照了Java的包名、类名,确实没有错误,那为什么会出现这种问题呢。后来突然想到,JNI接口 都是以C的方式定义的,现在使用C++实现,函数定义前是否需要加上extern "C"呢?为此定义了一个头文件,在CPP文件中include该头文件,头文件加上如下代码片断:

#ifdef __cplusplus

extern "C" {

#endif

#endif

...

#ifdef __cplusplus

}

Java_com_ws_ffmpegandroidplayer_MainActivity_play源码如下

#include #include #include #include extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" //}; #define LOG_TAG "ffmpegandroidplayer" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) JNIEXPORT jint JNICALL Java_com_ws_ffmpegandroidplayer_MainActivity_play (JNIEnv *env, jclass clazz, jobject surface) { LOGD("play"); // sd卡中的视频文件地址,可自行修改或者通过jni传入 //char *file_name = "/storage/emulated/0/ws2.mp4"; char *file_name = "/storage/emulated/0/video.avi"; av_register_all(); AVFormatContext *pFormatCtx = avformat_alloc_context(); // Open video file if (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) { LOGD("Couldn't open file:%s\n", file_name); return -1; // Couldn't open file } // Retrieve stream information if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { LOGD("Couldn't find stream information."); return -1; } // Find the first video stream int videoStream = -1, i; for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && videoStream < 0) { videoStream = i; } } if (videoStream == -1) { LOGD("Didn't find a video stream."); return -1; // Didn't find a video stream } // Get a pointer to the codec context for the video stream AVCodecContext *pCodecCtx = pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL) { LOGD("Codec not found."); return -1; // Codec not found } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { LOGD("Could not open codec."); return -1; // Could not open codec } // 获取native window ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface); // 获取视频宽高 int videoWidth = pCodecCtx->width; int videoHeight = pCodecCtx->height; // 设置native window的buffer大小,可自动拉伸 ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight, WINDOW_FORMAT_RGBA_8888); ANativeWindow_Buffer windowBuffer; if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { LOGD("Could not open codec."); return -1; // Could not open codec } // Allocate video frame AVFrame *pFrame = av_frame_alloc(); // 用于渲染 AVFrame *pFrameRGBA = av_frame_alloc(); if (pFrameRGBA == NULL || pFrame == NULL) { LOGD("Could not allocate video frame."); return -1; } // Determine required buffer size and allocate buffer // buffer中数据就是用于渲染的,且格式为RGBA int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height, 1); uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height, 1); // 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换 struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL); int frameFinished; AVPacket packet; while (av_read_frame(pFormatCtx, &packet) >= 0) { // Is this a packet from the video stream? if (packet.stream_index == videoStream) { // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // 并不是decode一次就可解码出一帧 if (frameFinished) { // lock native window buffer ANativeWindow_lock(nativeWindow, &windowBuffer, 0); // 格式转换 sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGBA->data, pFrameRGBA->linesize); // 获取stride uint8_t *dst = (uint8_t *) windowBuffer.bits; int dstStride = windowBuffer.stride * 4; uint8_t *src = (pFrameRGBA->data[0]); int srcStride = pFrameRGBA->linesize[0]; // 由于window的stride和帧的stride不同,因此需要逐行复制 int h; for (h = 0; h < videoHeight; h++) { memcpy(dst + h * dstStride, src + h * srcStride, srcStride); } ANativeWindow_unlockAndPost(nativeWindow); } } av_packet_unref(&packet); } av_free(buffer); av_free(pFrameRGBA); // Free the YUV frame av_free(pFrame); // Close the codecs avcodec_close(pCodecCtx); // Close the video file avformat_close_input(&pFormatCtx); return 0; } }

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐