Android下基于Http协议的网络摄像机开发

    这段时间在做Android平台下的网络摄像机的兼容,摄像机的通讯采用Http1.1协议。现将遇到的问题简单总结一下:


1. Http协议中需要用到身份认证部分,不同厂家的摄像机所采取的方案可能有所不同,但是大体无外乎都是将摄像机的用户名和密码简单的用Base64加密转换后封装成特定字段反馈给摄像机,摄像机对接收到的加密字段进行匹配。常见的方法是:Base64(用户名:密码)(将用户名和密码用‘:’连接,然后对其进行Base64转换)。


2. 视频数据传输一般是采用Rtsp实时数据流传输协议,对Rtsp数据流进行拆包组帧转换为H264数据帧成了首要解决的问题。这个过程可以放在Android上层用Java实现,也可以放到JNI底层实现。本人最初在上层用Java实现了Rtsp的拆包组帧,最后在实际的运行当中发现:低分辨率的摄像机可以勉强运行,一旦连接高分辨率的摄像机就会出现丢帧和花屏的现象。最后不得不将该模块用C重写封装成接口放到底层,运行1280*720分辨率都很流畅。总结:Java实现数据转换的效率不高,最好放到JNI底层来实现复杂的数据运算。


3. H264解码器的问题。最初是从网上下载了一个经过裁剪的H264解码器(基于FFMPEG开源项目)。在测试中发现有时候会出现底层解码错误导致整个程序崩溃,这个问题困扰了我们很久。最后决定移植一个完整的ffmpeg解码器来解决这个问题,网上有众多网友做过相关的事情。经过漫长的接口封装调试最终成功解决。从1280*720、640*360、到320*240分辨率的测试 都很流畅(Android.mk、JNI接口文件,解码器源码等相关代码将上传到资源)

项目中后来用到的解码器为ffmpeg1.2版本,通过移植其H264视频解码模块来满足了应用项目的要求,下面贴上Jni接口的C源码:
#include 
#include 
#include 
#include 
#include 
#include

#include "ffmpeg/libavformat/avformat.h"
#include "ffmpeg/libavcodec/avcodec.h"
#include "ffmpeg/libswscale/swscale.h"
////////////////////////////////
AVCodec *m_pCodec = NULL;
AVCodecContext *m_pCodecCtx = NULL;
AVFrame *m_pFrame = NULL;
struct SwsContext *pSwsCtx = NULL;
AVPacket m_packet;

int m_width = 0;
int m_height = 0;
const int MAX_VIDEO_W = 1280;
const int MAX_VIDEO_H = 720;
static int g_bInit = 0;
int g_iFrame = 0;

//picture
char    *fill_buffer;
AVFrame  *frame_rgb;
struct SwsContext  *img_convert_ctx;
jboolean bPicture = JNI_FALSE;
///////////////////////////////
#define MAX_RGB_BUF 1280*720*3

// 视频参数定义
#define PT_H264 96
#define PT_G726	97	
#define PT_G711 8
#define PT_DATA 100
// global
static int g_iConnState = 0;
int ret = -1;
int outSize = 0;

const int nVideoLen = 1280 * 720;
const int nBufLen = 512000;
char *g_pVideoData = NULL;
char *g_pBufData = NULL;
int g_nCopyLen = 0;
int g_nBufLen = 0;
int g_nFullPackLen = 0;
int g_nNeedLen = 0;
unsigned int g_ts = 0;
int g_tsLen = 0;
char *m_srcInbuf = NULL;

//2. RTP数据包头格式:
typedef struct {
	/* byte 0 */
	unsigned short cc :4; /* CSRC count */
	unsigned short x :1; /* header extension flag */
	unsigned short p :1; /* padding flag */
	unsigned short version :2; /* protocol version */
	/* byte 1 */
	unsigned short pt :7; /* payload type */ //pt说明: 96=>H.264, 97=>G.726, 8=>G.711a, 100=>报警数据
	unsigned short marker :1; /* marker bit */
	/* bytes 2, 3 */
	unsigned short seqno :16; /* sequence number */
	/* bytes 4-7 */
	unsigned int ts; /* timestamp in ms */
	/* bytes 8-11 */
	unsigned int ssrc; /* synchronization source */
} RTP_HDR_S; // sizeof: 12

typedef struct {
	unsigned char daollar; /*8, $:dollar sign(24 decimal)*/
	unsigned char channelid; /*8, channel id*/
	unsigned short resv; /*16, reseved*/
	unsigned int payloadLen; /*32, payload length*/
	RTP_HDR_S rtpHead; /*rtp head*/
} RTSP_ITLEAVED_HDR_S; // sizeof: 20

typedef struct {
	unsigned char daollar; /*8, $:dollar sign(24 decimal)*/
	unsigned char channelid; /*8, channel id*/
	unsigned short resv; /*16, reseved*/
	unsigned int payloadLen; /*32, payload length*/
} RTSP_H_S; // sizeof: 8
///////////////////////////////////////////////////////////////

// 处理接收视频,音频等数据
int RecvPackData(const char *pBuf, int len) {

	const int N_RTP_HDR = sizeof(RTP_HDR_S);
	if (len < N_RTP_HDR) {
		printf("\n~~~~~~~~~~~~~~~~~~~~~ERR: PackData!");
		return -1;
	}

	RTP_HDR_S rtpHead;
	memcpy(&rtpHead, pBuf, N_RTP_HDR);
	unsigned int ts = ntohl(rtpHead.ts);

	//96=>H.264, 97=>G.726, 8=>G.711a, 100=>报警数据
	if (rtpHead.pt == PT_H264) {
		int nVideoLen = len - N_RTP_HDR;
		if (ts != g_ts && g_ts > 0){
			//[self showVideo:g_tsLen];

			//满帧解码
			__android_log_print(ANDROID_LOG_DEBUG, "jni_log","man yi zhen ,xia mian jie ma !");
			int ret = DecodeH264(m_srcInbuf, g_tsLen);
			if (ret < 1) {
				__android_log_print(ANDROID_LOG_DEBUG, "jni_log","Decoding failure!** ret= %d", ret);
			}
			//清理
			memset(m_srcInbuf,0,MAX_RGB_BUF);
			g_tsLen = 0;
			memcpy(&m_srcInbuf[g_tsLen], pBuf + N_RTP_HDR, nVideoLen); // 去除RTP_HDR头
			g_tsLen += nVideoLen;
			g_ts = ts;
		} else {
			memcpy(&m_srcInbuf[g_tsLen], pBuf + N_RTP_HDR, nVideoLen); // 去除RTP_HDR头
			g_tsLen += nVideoLen;
			g_ts = ts;
		}
	}
	return 1;
}
// 解析通道
int ParseChannelData(const char *pBuf, int len)
{
	const int N_H_S = sizeof(RTSP_H_S); // 8
	const int N_HDR_LEN = sizeof(RTSP_ITLEAVED_HDR_S); // 20	
	memcpy(&g_pBufData[g_nBufLen], pBuf, len);
	g_nBufLen += len;

	if (g_nBufLen < N_HDR_LEN)
		return 0;

	if (g_nNeedLen == 0) {
		RTSP_ITLEAVED_HDR_S header;
		memset(&header, 0, N_HDR_LEN);
		memcpy(&header, g_pBufData, N_HDR_LEN);

		int packet_len = ntohl(header.payloadLen); // - sizeof(RTP_HDR_S);
		unsigned int timestamp = ntohl(header.rtpHead.ts);
		int streamType = header.rtpHead.pt;
		printf("\npayloadLen: %d  time: %d streamType: %d\n", packet_len,
				timestamp, streamType);
		if (packet_len > 0 && header.daollar == 0x24) //"$"  // 验证
				{
			g_nFullPackLen = packet_len;

			// 当前包不够一个nalu包
			if (g_nBufLen < g_nFullPackLen + N_H_S) {
				int nCopy = g_nBufLen - N_H_S;
				memcpy(&g_pVideoData[0], &g_pBufData[N_H_S], nCopy);
				g_nCopyLen = nCopy;
				g_nBufLen = 0;
				if (g_nFullPackLen - nCopy > 0) {
					g_nNeedLen = g_nFullPackLen - nCopy;
				}
			} else // >= 单个nalu包
			{
				int nCopy = g_nFullPackLen;
				memcpy(&g_pVideoData[0], &g_pBufData[N_H_S], nCopy);
				g_nCopyLen = nCopy;
				g_nNeedLen = 0;
				//================================================
				//FULL PACK
				RecvPackData(g_pVideoData, nCopy);
				int nRemain = g_nBufLen - (g_nFullPackLen + N_H_S);
				g_nBufLen = 0;
				if (nRemain > 0) {
					char *pTemp = (char *) malloc(nRemain);
					memcpy(pTemp, &g_pBufData[g_nFullPackLen + N_H_S], nRemain);
					ParseChannelData(pTemp, nRemain);
					free(pTemp);
				}
			}
			return 0;
		} else {
			g_nCopyLen = 0;
			g_nBufLen = 0;
			g_nFullPackLen = 0;
			g_nNeedLen = 0;
			g_ts = 0;
			return 0;
		}
	}
	if (g_nNeedLen > 0) {
		if (g_nNeedLen > MAX_RGB_BUF) {
			g_nCopyLen = 0;
			g_nBufLen = 0;
			g_nFullPackLen = 0;
			g_nNeedLen = 0;
			return 0;
		}
		if (g_nBufLen < g_nNeedLen) {
			int nCopy = g_nBufLen;
			memcpy(&g_pVideoData[g_nCopyLen], g_pBufData, nCopy);
			g_nCopyLen += nCopy;
			g_nNeedLen -= nCopy;
			g_nBufLen = 0;
		} else {
			int nCopy = g_nNeedLen;
			memcpy(&g_pVideoData[g_nCopyLen], g_pBufData, nCopy);
			g_nCopyLen += nCopy;
			g_nNeedLen = 0;
			//================================================
			//FULL PACK
			RecvPackData(g_pVideoData, g_nFullPackLen);
			int nRemain = g_nBufLen - nCopy;
			g_nBufLen = 0;
			if (nRemain > 0) {
				char *pTemp = (char *) malloc(nRemain);
				memcpy(pTemp, &g_pBufData[nCopy], nRemain);
				ParseChannelData(pTemp, nRemain);
				free(pTemp);
			}
		}
	}
}
//====================================================
JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_init(JNIEnv* env,
		jobject thiz) {
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "Start: init decode");
	m_width = -1;
	m_height = -1;
	if (g_bInit == 1){
		return -1;
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "Err: init");
	}
	if (g_pVideoData == NULL) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 111");
		g_pVideoData = (char *) malloc(nVideoLen);
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 222");
		g_pBufData = (char *) malloc(nBufLen);
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 333");
		m_srcInbuf = (char *) malloc(MAX_RGB_BUF);
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 444");
	}
	//avcodec_init();
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 555");
	av_register_all(); // Register all formats and codecs
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 666");

	m_pCodec = avcodec_find_decoder(CODEC_ID_H264);
	if (!m_pCodec) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: avcodec_find_decoder : CODEC_ID_H264 = %d",
				(int) CODEC_ID_H264);
		return -1;
	}
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 777");
	m_pCodecCtx = avcodec_alloc_context3(m_pCodec);
	if (!m_pCodecCtx) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: avcodec_alloc_context3");
		return -2;
	}
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 888");
	if (avcodec_open2(m_pCodecCtx, m_pCodec,NULL) < 0)
		return -3;
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 999");
	m_pFrame = avcodec_alloc_frame();
	if (!m_pFrame) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: avcodec_alloc_frame");
		return -4;
	}
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "malloc 101010");

	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "finish: init decode");
	av_init_packet(&m_packet);
	g_bInit = 1;
	g_iFrame = 0;

	return 1;
}

/*
 * Class:     h264_com_VView
 * Method:    UninitDecoder
 * Signature: ()I
 */

JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_finit(JNIEnv* env,
		jobject thiz) {
	if (g_bInit <= 0)
		return -1;

	__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "End: close decode");

	if (g_pVideoData == NULL) {
		free(g_pVideoData);
		free(g_pBufData);
		free(m_srcInbuf);
		g_pVideoData = NULL;
		g_pBufData = NULL;
		m_srcInbuf = NULL;
	}
	// Close the codec
	if (m_pCodecCtx) {
		avcodec_close(m_pCodecCtx);
		m_pCodecCtx = NULL;
	}

	// Free the YUV frame
	if (m_pFrame != NULL) {
		av_free(m_pFrame);
		m_pFrame = NULL;
	}

	if(bPicture)
		FreePicture();
	m_width = -1;
	m_height = -1;
	g_bInit = 0;
	return 1;
}

/*
 * Class:     h264_com_VView
 * Method:    DecoderNal
 * Signature: ([B[I)I
 */
int InitPicture()
{
	frame_rgb = avcodec_alloc_frame();
	if(!frame_rgb)
	{
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "End:Init Picture");
		return -1;
	}
	int numBytes = avpicture_get_size(PIX_FMT_RGB565,m_width,m_height);
	fill_buffer = (char *)av_malloc(numBytes * sizeof(char));
	avpicture_fill((AVPicture *)frame_rgb, fill_buffer, PIX_FMT_RGB565,m_width,m_height);
	img_convert_ctx = sws_getContext(m_width,m_height,m_pCodecCtx->pix_fmt,
										m_width, m_height, PIX_FMT_RGB565, SWS_BICUBIC, NULL, NULL, NULL);
	bPicture = JNI_TRUE;
	return 1;
}
int FreePicture()
{
	if(fill_buffer!=NULL)
	{
		av_free(fill_buffer);
		fill_buffer = NULL;
	}
	if(frame_rgb!=NULL){
		av_free(frame_rgb);
		frame_rgb = NULL;
	}
	sws_freeContext(img_convert_ctx);
	bPicture = JNI_FALSE;
	return 1;
}
int DecodeH264(char *pByte, int nalLen) {
	if (g_bInit <= 0) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: No init decode");
		return -1;
	}
	int got_picture;
	int numBytes;
//	jbyte *pByte = (jbyte*)(*env)->GetByteArrayElements(env, in, 0);
	int nSrcLen = nalLen;
	m_packet.size = nSrcLen;
	m_packet.data = (unsigned char *) pByte;

	int consumed_bytes = avcodec_decode_video2(m_pCodecCtx, m_pFrame,
			&got_picture, &m_packet);
	if (consumed_bytes > 0) {
		if (m_pFrame->data[0]) {
			m_width = m_pCodecCtx->width;
			m_height = m_pCodecCtx->height;
			__android_log_print(ANDROID_LOG_DEBUG, "jni_log"," w:%d---- h:%d",m_width,m_height);
			if(!bPicture)
				InitPicture();
		}
	}else
	{
			m_width = -1;
			m_height = -1;
	}
//	(*env)->ReleaseByteArrayElements(env, in, pByte, 0);
	return 2;
}
JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_PackData(JNIEnv* env, jobject thiz, jbyteArray in, jint nalLen)
{
	jbyte * Buf = (jbyte*)(*env)->GetByteArrayElements(env, in, 0);
	//__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "enter into ");
	//__android_log_print(ANDROID_LOG_DEBUG, "jni_log", "nalLen = %d",nalLen);
	ParseChannelData(Buf,nalLen);
	(*env)->ReleaseByteArrayElements(env, in, Buf, 0);
}
JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_OutFrameData(JNIEnv* env,
		jobject thiz, jbyteArray out) {
	jbyte *pByte = (jbyte*) (*env)->GetByteArrayElements(env, out, 0);
	if (g_bInit <= 0) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: OutFrameData, No init decode");
		return -1;
	}

	if (m_width <= 0 || m_height <= 0) {
		__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
				"Err: jni::decode() w <0 or h < 0");
	}
	__android_log_print(ANDROID_LOG_DEBUG, "jni_log",
			"### OutFrame: %d w:%d h:%d", g_iFrame++, m_width, m_height);
	int ret = sws_scale(img_convert_ctx, m_pFrame->data,m_pFrame->linesize, 0,m_height,frame_rgb->data, frame_rgb->linesize);
	memcpy(pByte,frame_rgb->data[0],m_height * m_width * 2);

	if (pByte != NULL) {
		(*env)->ReleaseByteArrayElements(env, out, pByte, 0);
	}
	return 1;
}

JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_GetWidth(JNIEnv* env,
		jobject thiz) {
	if (m_width > 0)
		return m_width;
	return -1;
}

JNIEXPORT jint JNICALL Java_HttpCamera_HttpJniNative_GetHeight(JNIEnv* env,
		jobject thiz) {
	if (m_height > 0)
		return m_height;
	return -1;
}
在这里定义了五个供Java调用的接口(解码器初始化、解码器解码、取解码数据、获得解码图像的宽、高),其中集成了对特定视频数据进行拆包组帧的函数,结合不同的项目需求可以更改相关的接口声明和拆包组帧函数。完整的JNI源码可以通过下面的地址下载: http://download.csdn.net/detail/wtbee/6204311。欢迎大家对遇到的问题提出来进行交流!




你可能感兴趣的:(Android,视频开发)