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

来源:互联网 发布:尤克里里入门软件 编辑:程序博客网 时间:2024/06/04 18:46

    这段时间在做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 <string.h>#include <jni.h>#include <stdio.h>#include <stdlib.h>#include <android/log.h>#include<netinet/in.h>#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;//picturechar    *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_G72697#define PT_G711 8#define PT_DATA 100// globalstatic 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: 12typedef 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: 20typedef 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); // 8const int N_HDR_LEN = sizeof(RTSP_ITLEAVED_HDR_S); // 20memcpy(&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 PACKRecvPackData(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 PACKRecvPackData(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 codecif (m_pCodecCtx) {avcodec_close(m_pCodecCtx);m_pCodecCtx = NULL;}// Free the YUV frameif (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 <PackData>");//__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。欢迎大家对遇到的问题提出来进行交流!