1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void nv212Yv12(char *nv21, char *yv12, int width, int height) { int frameSize = width * height; memcpy(yv12, nv21, frameSize); nv21 += frameSize; yv12 += frameSize; int halfWidth = width / 2; int halfHeight = height / 2; int quadFrame = halfWidth * halfHeight; for (int i = 0; i < halfHeight; i++) { for (int j = 0; j < halfWidth; j++) { *(yv12 + i * halfWidth + j) = *nv21++; *(yv12 + quadFrame + i * halfWidth + j) = *nv21++; } } } void yv122Nv21(char *yv12, char *nv21, int width, int height) { int frameSize = width * height; memcpy(nv21, yv12, frameSize); nv21 += frameSize; yv12 += frameSize; int halfWidth = width / 2; int halfHeight = height / 2; int quadFrame = halfWidth * halfHeight; for (int i = 0; i < halfHeight; i++) { for (int j = 0; j < halfWidth; j++) { *nv21++ = *(yv12 + i * halfWidth + j); *nv21++ = *(yv12 + quadFrame + i * halfWidth + j); } } } |
编程
NDK硬解h264
有几点须注意,
- 不要让硬解码器自已绘制表面,也就是AMediaCodec_configure传入的surface应设为空。否则运行时间长了,会出现“weak global reference table overflow”的崩机错误。
- 我之前文章里也说过了,不要静态链编libmediandk.so,老的安卓机上没有这个库,要动态检测加载,详细过程见之后代码。
- AMediaCodec_dequeueInputBuffer不是每一次都返回成功,如果失败了而丢弃h264数据帧,则最终的播放效果就时不时出现花屏,正确处理见之后代码。
- 硬解码器解出的数据默认可能是NV12格式也可能是YV12格式,不同的手机不一样,预先配置解码器的"color-format"为19,也没用。为了与ffmpeg解出的数据一致为YU12格式,统一地我们自已转。
以下是源码,摘选自EuhatRtsp开源软件,经历过长时间拷机运行没问题。其网址是:http://euhat.com/rtsp.html
DecoderHard.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #pragma once #include "DecodeOp.h" class AMediaCodec; class EuhatDecoderHard : public EuhatDecoderBase { int updateSpsAndPps(); int spsChanged_; int ppsChanged_; char *sps_; char *pps_; int width_; int height_; int oriHeight_; int colorFormat_; int stride_; int sliceHeight_; int cropTop_; int cropBottom_; int cropLeft_; int cropRight_; int isCodecInited_; void *surface_; int surfaceChanged_; public: EuhatDecoderHard(); virtual ~EuhatDecoderHard(); virtual int init(EuhatDecoderCallback callback, void *context); virtual int fini(); virtual int updateSps(const char *sps); virtual int updatePps(const char *pps); virtual int updateWH(int width, int height); virtual int updateSurface(void *surface); virtual int decode(char *frame, int frameLen); static int canWork(); AMediaCodec *mediaCodec_; }; |
数据结构好文推荐
红黑树演示 https://sandbox.runjs.cn/show/2nngvn8w 有哪些最新的机器学习可视化工具? https://www.toutiao.com/a6567983574299967757/ NDK-FFmpeg视频解码 http://www.imooc.com/article/80058 RSA加密原理 http://blog.jobbole.com/42699/ |
Android Handler and Runnable
Runnable并不是一个线程,它是Thread运行过程中的一个片断。
以下例子是创建子线程获取图片后发送给UI线程显示,第一个Runnable运行在子线程中,第二个Runnable运行在UI线程中:
1 2 3 4 5 6 7 8 9 10 11 12 | public void onClick(View v) { new Thread(new Runnable() { // 第一个Runnable public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { // 第二个Runnable public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); } |
而Handler与线程的绑定有两种方式。
第一种是与当前线程绑定:
1 | public Handler handler = new Handler(); |
第二种是指定线程绑定:
1 | handler = new Handler(thread.getLooper()); |
MFC重绘控件
举个按钮简单重绘的例子。
头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class CMyButton : public CButton { public: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); }; class CMFCApplication1Dlg : public CDialogEx { ... protected: virtual void DoDataExchange(CDataExchange* pDX); CMyButton m_myButton; ... }; |
Android NDK动态获取系统版本
让电脑连上安卓手机上,执行
1 2 | adb shell cat /system/build.prop |
可看到
1 2 3 4 5 | ... ro.build.version.sdk=18 ro.build.version.codename=REL ro.build.version.release=4.3 ... |
于是jni中可写如下代码获取系统版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <sys/system_properties.h> static int test(){ char *key = (char *)"ro.build.version.sdk"; //char *key = (char *)"ro.build.version.release"; char value[1024] = {0}; int ret = __system_property_get(key, value); if (ret <= 0 ) { DBG(("get prop value failed.\n")); return 0; } DBG(("ro.build.id is [%s]\n", value)); return 0; } |
Android jni基于NdkMediaCodec硬解码mjpeg
NdkMediaCodec需要安卓5.0以上系统才能运行,在Android.mk里加入
1 | LOCAL_LDLIBS += -lmediandk |
如果是在安卓5.0以下系统中跑,程序会直接崩掉,主函数都不会进,所以要在旧手机中运行,要根据系统版本来判断是不是5.0以上系统,如果是则动态加载libmediandk.so,而不要写死在编译脚本里。
还有,要看手机上有没有解mjpeg的硬解码器,通过以下java代码来查看
1 2 3 4 5 6 7 8 9 10 11 12 | int n = MediaCodecList.getCodecCount(); for (int i = 0; i < n; ++i) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); String[] supportedTypes = info.getSupportedTypes(); boolean mime_support = false; if(info.isEncoder()){ continue; } for (int j = 0; j < supportedTypes.length; ++j) { Log.v("euhat", "codec info:" + info.getName()+" supportedTypes:" + supportedTypes[j]); } } |
以下是jni硬解码代码,因为没找到支持mjpeg硬解的手机,所以还没实际跑起来过。
但实际测试过程中,新的手机上软解1920x1080的mjpeg输出yuv都很流畅了。
fatal error: media/NdkMediaCodec.h: No such file or directory
在Application.mk文件里改为如下:
1 2 | #APP_PLATFORM := android-14 APP_PLATFORM := android-21 |
再ndk-build就不会出这个错误了,原因也明了,旧的文件夹里没有这个头文件。
安卓jni毫秒级打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include <errno.h> #include <fcntl.h> #include <string> using namespace std; void ivrLog(const char *format, ...) { va_list argptr; char buf[1024 * 2]; va_start(argptr, format); vsprintf(buf, format, argptr); va_end(argptr); struct timeval tv; int iRet = gettimeofday(&tv, NULL); time_t t = tv.tv_sec; tm* local = localtime(&t); char timeBuf[256]; strftime(timeBuf, 254, "[%Y-%m-%d %H:%M:%S", local); sprintf(timeBuf + strlen(timeBuf), ":%d] ", (int)(tv.tv_usec / 1000)); string dispStr = timeBuf; dispStr += buf; printf("%s", dispStr.c_str()); // please ensure /sdcard/Test dir existed first char *logFileName = (char *)"/sdcard/Test/log.txt"; int fd = open(logFileName, O_CREAT|O_WRONLY|O_APPEND, 0666); if (fd != -1) { write(fd, dispStr.c_str(), dispStr.length()); close(fd); } } |
Android jni fopen返回NULL而errno为13
除了要在AndroidManifest.xml加
1 2 3 | <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
关键是看传给fopen或open的文件路径是否“正确”,比如传入“/storage/6236-6439/down/log.txt”之类的路径会返回NULL,errno为EACCES。
比如传入“/sdcard/Test/log.txt”之类的路径则一切正常没任何问题。
如果还是没看到文件被创建,那就该好好看看该jni库是否被正确加载了。