NDK硬解h264

有几点须注意,

  1. 不要让硬解码器自已绘制表面,也就是AMediaCodec_configure传入的surface应设为空。否则运行时间长了,会出现“weak global reference table overflow”的崩机错误。
  2. 我之前文章里也说过了,不要静态链编libmediandk.so,老的安卓机上没有这个库,要动态检测加载,详细过程见之后代码。
  3. AMediaCodec_dequeueInputBuffer不是每一次都返回成功,如果失败了而丢弃h264数据帧,则最终的播放效果就时不时出现花屏,正确处理见之后代码。
  4. 硬解码器解出的数据默认可能是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_;
};

Read more

数据结构好文推荐

红黑树演示
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/

https://github.com/fredericgermain/LeakTracer

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;
	...
};

Read more

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都很流畅了。

Read more

安卓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库是否被正确加载了。

recv 10060

请检查socket连接时有没有设置超时。